Облаков тегов
Генератор облака тегов в форме круга.
HTML
<header class="page-header">
<div id="debug" class='debug-content'>
<span id="line1"></span><br/>
<span id="line2"></span><br/>
<span id="line3"></span>
</div>
</header>
<div id='wordle-container' class='container'></div>
<div class="center" style='display: none'></div>
CSS
body {
color: #aaa;
width: 100%;
height: 100%;
background: #333;
text-shadow: -1px -1px 0 #000, 1px 1px 0 #555;
}
a {
color: #eee;
text-decoration: none;
transition: color 0.3s;
-webkit-transition: color 0.3s;
-moz-transition: color 0.3s;
-ms-transition: color 0.3s;
-o-transition: color 0.3s;
}
a:visited { color: #eee; }
a:hover { color: #62A3D9; }
footer {
position: fixed;
left: 10px;
bottom: 10px;
width: 100%;
text-align: left;
font-size: 12px;
}
.page-header {
position: fixed;
left: 10px;
top: 10px;
}
.debug-content {
font-size: 12px;
}
.container {
position: absolute;
left: 50%;
top: 50%;
}
.wordle {
position: relative;
}
.word {
display: block;
position: absolute;
text-decoration: none;
border: 1px solid transparent;
/*some inset text effect*/
text-shadow: -1px -1px 0 #000, 1px 1px 0 #555;
}
.word:hover {
border: 1px dashed #ffff00;
}
.rotated {
text-shadow: -1px 1px 0 #000, -1px 1px 0 #555;
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-o-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
.center {
position: absolute;
width: 2px;
height: 2px;
left: 50%;
top: 50%;
background-color: #ff0000;
}
.unsupport {
position: fixed;
color: #FF0000;
background-color: #1A1E39;
text-align: center;
font-size: 16px;
width: 100%;
top: 50%;
}
JS
https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js
скрипт/**
* Author: Thanh Tran - int3ractive.com
* MIT License
*/
/*
* WORDLEJS namespace
*/
var WORDLEJS = WORDLEJS || {};
(function() {
/*
* Rectangle class
*/
WORDLEJS.Rectangle = function(x, y, w, h) {
this.x = x;
this.y = y;
this.width = w;
this.height = h;
};
WORDLEJS.Rectangle.prototype = {
constructor: WORDLEJS.Rectangle,
getRight: function() {
return this.x + this.width;
},
getBottom: function() {
return this.y + this.height;
},
getCenterX: function() {
return this.x + this.width / 2;
},
getCenterY: function() {
return this.y + this.height / 2;
},
intersects: function(r2) {
return !(
r2.x > this.getRight() ||
r2.getRight() < this.x ||
r2.y > this.getBottom() ||
r2.getBottom() < this.y
);
},
toString: function() {
return (
"Rect: " + this.x + "x" + this.y + ":" + this.width + "x" + this.height
);
}
};
})();
(function() {
/*
* Word class
*/
WORDLEJS.Word = function(initObj) {
if (!initObj) throw new Error("bad Word init");
this.text = initObj.text || "";
this.weight = initObj.weight;
this.fontFamily = initObj.fontFamily || "sans-serif";
this.fontSize = initObj.fontSize || 10;
this.fillColor = initObj.fillColor; //Color
this.strokeColor = initObj.strokeColor; //Color
this.url = initObj.url;
this.sprite = null; //considered later
this.bounds = null; //bounds of the rendered text
this._rotated = initObj.rotated;
};
WORDLEJS.Word.prototype = {
constructor: WORDLEJS.Word,
/**
* Calculate the metrics and position of this word
*/
calculate: function(ctx) {
//
var fontFamily = this.fontFamily,
fontSize = this.fontSize,
text = this.text,
textMetrics,
bounds,
w,
h,
tx = 0, //translate x;
ty = 0; //translate y;
ctx.font = fontSize + "px " + fontFamily;
textMetrics = ctx.measureText(text);
//log(text + ': ' + textMetrics.width);
if (this._rotated) {
//case vertical
w = fontSize;
h = textMetrics.width;
} else {
//case horizontal
w = textMetrics.width;
h = fontSize;
}
//register point offset to center of rectangle
tx = -w / 2;
ty = -h / 2;
bounds = new WORDLEJS.Rectangle(tx, ty, w, h);
this.bounds = bounds;
},
renderText: function(container) {
var bounds = this.bounds,
text = this.text;
var anchor = document.createElement("a"),
s = anchor.style,
xPos = bounds.x,
yPos = bounds.y,
fillColor = this.fillColor;
anchor.href = this.url;
anchor.target = "_blank";
anchor.textContent = text;
anchor.className = "word";
s.color = "#" + fillColor;
s.font =
"normal normal " +
this.fontSize +
"px/" +
this.fontSize +
"px " +
this.fontFamily;
if (this._rotated) {
//transform will be soon standardized
/*
s.webkitTransform = 'rotate(90deg)';
s.MozTransform = 'rotate(90deg)';
s.oTransform = 'rotate(90deg)';
s.msTransform = 'rotate(90deg)';
s.transform = 'rotate(90deg)';
*/
//using CSS is safer since the property names in each browser are too different
anchor.className += " rotated";
//object is rotated at its center
xPos = xPos - bounds.height / 2 + bounds.width / 2;
yPos = yPos + bounds.height / 2 - bounds.width / 2;
}
s.left = xPos + "px";
s.top = yPos + "px";
//DEBUG:
//s.background = 'rgba(99,0,99,0.5)';
container.appendChild(anchor);
},
toString: function() {
return this.text;
}
};
})();
(function() {
/*
* Wordle class
*/
WORDLEJS.Wordle = function(elContainer) {
var container = document.createElement("div");
container.className = "wordle";
elContainer.appendChild(container);
var canvas = document.createElement("canvas");
this.ctx = canvas.getContext("2d"); //canvas used to measure text only
this.parent = elContainer;
this.container = container;
};
WORDLEJS.Wordle.prototype = {
//member
biggestSize: 80,
smallestSize: 12,
words: null,
dRadius: 10.0,
dDeg: 10,
sortType: "",
allowRotate: true,
ctx: null,
container: null,
center: null,
current: null,
first: null,
wl: 0,
startTime: 0,
runTime: 0,
curIdx: 0,
keepCenter: false,
urlPrefix: "https://duckduckgo.com/?q=",
//methods
constructor: WORDLEJS.Wordle,
setWords: function(arr, maxium) {
var urlPrefix = this.urlPrefix;
maxium = maxium || 100;
this.words = [];
for (var i = 0, il = arr.length; i < il; ++i) {
var wordObject = arr[i];
if (i >= maxium) {
break;
}
//trace( 'wordObject : ' + wordObject.text, wordObject.count );
var w = {
text: wordObject.text,
weight: wordObject.count,
url: urlPrefix + wordObject.text,
//strokeColor: Random.getRandomColor(), //no use
fillColor: Random.getRandomColor(0x44, 0xff),
fontFamily: Random.getRandomBoolean() ? "sans-serif" : "serif",
rotated: Random.getRandomBoolean() // half chances of rotation
};
this.words.push(new WORDLEJS.Word(w));
}
trace(1, "Number of words: " + this.words.length);
},
/**
* calculate words positions
*/
doLayout: function() {
var words = this.words,
ctx = this.ctx,
high = -Number.MAX_VALUE,
low = Number.MAX_VALUE,
w,
wl = words.length,
i;
if (words.length <= 0) {
log("no word to render");
return;
}
//startTime = getTimer();
switch (this.sortType) {
case "a":
// sort from biggest to smallest
words.sort(function(w1, w2) {
return w2.weight - w1.weight;
});
break;
case "b":
//sort from smallest to biggest
words.sort(function(w1, w2) {
return w1.weight - w2.weight;
});
break;
case "c":
//sort by alphabet
words.sort(function(w1, w2) {
if (w1.text.toLowerCase() < w2.text.toLowerCase()) {
return -1;
} else if (w1.text.toLowerCase() > w2.text.toLowerCase()) {
return 1;
} else {
return 0;
}
});
break;
case "d":
//shuffle array
var randomSeq = Random.getRandomSequence(0, words.length - 1),
newArr = [];
for (i = 0; i < wl; i++) {
newArr[i] = words[randomSeq[i]];
}
this.words = words = newArr; //be careful with changing the variable pointer
break;
default:
//use the order in the words array
}
//trace( 'words : ' + words );
for (i = 0; i < wl; i++) {
w = words[i];
//check for highest & lowest weight to scatter the font size accordingly
high = high > w.weight ? high : w.weight;
low = low < w.weight ? low : w.weight;
}
//calculate word metrics first
for (i = 0; i < wl; i++) {
w = words[i];
w.fontSize =
(w.weight - low) / (high - low) * (this.biggestSize - this.smallestSize) +
this.smallestSize;
w.calculate(ctx);
}
//start position
this.center = { x: 0, y: 0 };
this.curIdx = 1;
this.wl = wl;
words[0].renderText(this.container);
this.layoutNextWord();
},
layoutNextWord: function() {
var i,
wl = this.wl,
curIdx = this.curIdx;
if (curIdx >= wl) {
//TODO: maybe do some benchmark here
return;
}
var words = this.words,
current = words[curIdx],
first = words[0],
center = this.center,
totalWeight = 0.0,
prev,
wPrev;
//calculate current center
center.x = 0;
center.y = 0;
//calculate the mass center of the wordle so far
for (prev = 0; prev < curIdx; ++prev) {
wPrev = words[prev];
center.x += wPrev.bounds.getCenterX() * wPrev.weight;
center.y += wPrev.bounds.getCenterY() * wPrev.weight;
totalWeight += wPrev.weight;
}
center.x /= totalWeight;
center.y /= totalWeight;
//trace( 'center : ' + center );
var bounds = current.bounds,
done = false,
radius = 0.5 * Math.min(first.bounds.width, first.bounds.height),
startDeg,
prev_x,
prev_y,
deg,
rad,
dDeg = this.dDeg,
candidateBounds,
container = this.container;
var loopPrevent = 0;
while (!done) {
loopPrevent++;
if (loopPrevent > 100000) {
log("maximum loop reach");
break;
}
startDeg = Math.floor(Math.random() * 360);
//log('startDeg ' + startDeg);
//loop over spiral
prev_x = -1;
prev_y = -1;
for (deg = startDeg; deg < startDeg + 360; deg += dDeg) {
rad = deg / Math.PI * 180.0;
var cx = Math.floor(center.x + radius * Math.cos(rad));
var cy = Math.floor(center.y + radius * Math.sin(rad));
if (prev_x == cx && prev_y == cy) continue;
prev_x = cx;
prev_y = cy;
//log( 'cx,cy : ' + cx + cy );
//test:
//graphics.beginFill(0xFF0000, 0.5);
//graphics.drawCircle(cx, cy, 0.5);
candidateBounds = new WORDLEJS.Rectangle(
current.bounds.x + cx,
current.bounds.y + cy,
current.bounds.width,
current.bounds.height
);
//any collision ?
for (prev = 0; prev < curIdx; ++prev) {
if (candidateBounds.intersects(words[prev].bounds)) {
break;
}
}
//no collision: we're done
if (prev == curIdx) {
current.bounds = candidateBounds;
current.renderText(container);
trace(2, "Word rendered: " + current);
trace(
3,
"Mass center: " + parseInt(center.x, 10) + "x" + parseInt(center.y, 10)
);
if (this.keepCenter) {
container.style.marginLeft = -center.x + "px";
container.style.marginTop = -center.y + "px";
}
done = true;
break;
}
}
radius += this.dRadius;
}
//_layoutProgress.dispatch(current);
this.curIdx++;
requestAnimationFrame(this.layoutNextWord.bind(this));
},
reset: function() {
var container = this.container;
container.innerHTML = "";
container.style.marginLeft = "0";
container.style.marginTop = "0";
this.words = null;
}
};
})();
//util
// usage: log('inside coolFunc', this, arguments);
// paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
window.log = function(){
log.history = log.history || []; // store logs to an array for reference
log.history.push(arguments);
if(this.console) {
arguments.callee = arguments.callee.caller;
var newarr = [].slice.call(arguments);
(typeof console.log === 'object' ? log.apply.call(console.log, console, newarr) : console.log.apply(console, newarr));
}
};
// make it safe to use console.log always
(function(b){function c(){}for(var d="assert,clear,count,debug,dir,dirxml,error,exception,firebug,group,groupCollapsed,groupEnd,info,log,memoryProfile,memoryProfileEnd,profile,profileEnd,table,time,timeEnd,timeStamp,trace,warn".split(","),a;a=d.pop();){b[a]=b[a]||c}})((function(){try
{console.log();return window.console;}catch(err){return window.console={};}})());
window.trace = function(line, str) {
window.document.getElementById('line' + line).innerHTML = str;
};
/**
* Provides requestAnimationFrame in a cross browser way.
* http://paulirish.com/2011/requestanimationframe-for-smart-animating/
*/
if ( !window.requestAnimationFrame ) {
window.requestAnimationFrame = ( function() {
return window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) {
window.setTimeout( callback, 1000 / 60 );
};
} )();
}
/**
* TextUtil
*/
var TextUtil = {
countWordOccurance: function (text) {
// Remove punctuations, non-word characters...
//note: this case also remove Vietnamese unicode characters, improve later when needed
text = text.replace(/[^A-Za-z0-9_\-\s]/g, '');
var words = text.split(/\s+/),
wordsObject = {},
i, il, w;
for (i = 0, il = words.length; i < il; i++) {
w = words[i];
if (wordsObject[w] && typeof(wordsObject[w]) === 'number') {
wordsObject[w] ++;
} else {
wordsObject[w] = 1;
}
}
//tranfer to array in order to sort
var result = [];
for (var item in wordsObject) {
if(wordsObject.hasOwnProperty(item)) {
result.push({ text: item, count:wordsObject[item] });
}
}
//bigger count stay at top
result.sort(function (wordA, wordB) {
return wordB.count - wordA.count;
});
return result;
}
};
/**
* Random util
*/
var Random = {
/**
* Creates a randomized boolean
* @return
*/
getRandomBoolean: function () {
return Math.random() >= 0.5;
},
/**
* Return random color number as hex string; avoid dark color
*/
getRandomColor: function (componentMin, componentMax) {
return (this.getRandomInt(componentMin, componentMax).toString(16) +
this.getRandomInt(componentMin, componentMax).toString(16) +
this.getRandomInt(componentMin, componentMax).toString(16));
},
/**
* Return a integer value within min - max (inclusive)
*/
getRandomInt: function (min, max) {
return min + Math.floor(Math.random() * (max - min + 1));
},
/**
* Get a random sequence
*/
getRandomSequence: function (min, max) {
var o = [], //original seq
r = [], //result
c, //child item
l; //length
if (min > max) {
var tmp = max;
max = min;
min = tmp;
}
l = Math.abs(max - min) + 1;
//log ('max: ' + max);
//log('length: ' + l);
//original sequence
for (var i = 0; i < l; i ++) {
o[i] = min + i;
//log(o[i]);
}
while (true) {
l = o.length;
if (l > 0) {
c = o.splice(this.getRandomInt(0,l - 1),1)[0];
r.push(c);
} else {
break;
}
}
log (r);
return r;
}
};
/* ============ MAIN SCRIPT ============== */
(function (win) {
//global
var doc = win.document,
container = doc.getElementById('wordle-container'),
wordle = new WORDLEJS.Wordle(container);
var demo = {
// text to render (with some preprositions already stripped)
testText: 'all people created equal; they endowed by their Creator with certain inalienable Rights; among these Life, Liberty, pursuit Happiness. This immortal statement was made Declaration Independence United States America 1776. broader sense, this means: all peoples on earth equal from birth, all peoples have right live, be happy free. Declaration Rights Man Citizen French Revolution made 1791 also states: all men born free with equal rights, must always remain free have equal rights. Those undeniable truths. Nevertheless, for more than eighty years, French imperialists, name Liberty, Equality, Fraternity, have violated our Fatherland oppressed our fellow citizens. They have acted contrary ideals humanity justice. all people created equal; they endowed by their Creator with certain inalienable Rights; among these Life, Liberty, pursuit Happiness. This immortal statement was made Declaration Independence United States America 1776. broader sense, this means: all peoples on earth equal from birth, all peoples hav toe right live, be happy free. For these reasons, we, members of Provisional Government, representing whole Vietnamese people, declare that from now on we break off all relations of colonial character with France; we repeal all international obligation that France has so far subscribed on behalf of Viet-Nam, and we abolish all special rights French have unlawfully acquired our Fatherland. whole Vietnamese people, animated by common purpose, are determined fight bitter end against any attempt by French colonialists reconquer country.',
wordsLimit: 200,
renderWordle: function () {
var sortResult = TextUtil.countWordOccurance(this.testText);
wordle.reset();
wordle.setWords(sortResult, this.wordsLimit);
log('wordle sortType: ' + wordle.sortType);
wordle.doLayout();
},
};
var gui = new dat.GUI();
gui.add(demo, 'testText');
gui.add(demo, 'wordsLimit', [10, 50, 100, 200]);
gui.add(wordle, 'urlPrefix');
gui.add(wordle, 'keepCenter');
gui.add(wordle, 'sortType', {'default': '', 'big -> small': 'a', 'small -> big': 'b', 'A -> Z': 'c', 'shuffle': 'd' });
gui.add(demo, 'renderWordle');
demo.renderWordle();
})(window);