- 1 :
// assuming Cirrus library is loaded by containing page (via voyant.jsp)
- 2 :
/**
- 3 :
* Cirrus tool, a wordcloud-like visualization.
- 4 :
*
- 5 :
* @example
- 6 :
*
- 7 :
* let config = {
- 8 :
* background: null,
- 9 :
* categories: null,
- 10 :
* docIndex: null,
- 11 :
* fontFamily: null,
- 12 :
* inlineData: null,
- 13 :
* limit: null,
- 14 :
* stopList: null,
- 15 :
* visible: null,
- 16 :
* whiteList: null,
- 17 :
* };
- 18 :
*
- 19 :
* loadCorpus("austen").tool("cirrus", config);
- 20 :
*
- 21 :
* @class Cirrus
- 22 :
* @tutorial cirrus
- 23 :
* @memberof Tools
- 24 :
*/
- 25 :
Ext.define('Voyant.panel.Cirrus', {
- 26 :
extend: 'Ext.panel.Panel',
- 27 :
mixins: ['Voyant.panel.Panel'],
- 28 :
alias: 'widget.cirrus',
- 29 :
statics: {
- 30 :
i18n: {
- 31 :
},
- 32 :
api: {
- 33 :
/**
- 34 :
* @memberof Tools.Cirrus
- 35 :
* @instance
- 36 :
* @property {stopList}
- 37 :
* @default
- 38 :
*/
- 39 :
stopList: 'auto',
- 40 :
/**
- 41 :
* @memberof Tools.Cirrus
- 42 :
* @instance
- 43 :
* @property {categories}
- 44 :
*/
- 45 :
categories: undefined,
- 46 :
- 47 :
/**
- 48 :
* @memberof Tools.Cirrus
- 49 :
* @instance
- 50 :
* @property {String|String[]} whiteList a list of words to always include
- 51 :
*/
- 52 :
whiteList: undefined,
- 53 :
- 54 :
/**
- 55 :
* @memberof Tools.Cirrus
- 56 :
* @instance
- 57 :
* @property {Number} limit Specify the number of terms to load (which is separate from the number of {@link Cirrus.visible} terms to show) at a time).
- 58 :
* @default 500
- 59 :
*/
- 60 :
limit: 500,
- 61 :
- 62 :
/**
- 63 :
* @memberof Tools.Cirrus
- 64 :
* @instance
- 65 :
* @property {Number} visible Specify the number of terms that are visible at a time.
- 66 :
* @default 50
- 67 :
*/
- 68 :
visible: 50,
- 69 :
- 70 :
// TODO unused??
- 71 :
terms: undefined,
- 72 :
- 73 :
/**
- 74 :
* @memberof Tools.Cirrus
- 75 :
* @instance
- 76 :
* @property {docId}
- 77 :
*/
- 78 :
docId: undefined,
- 79 :
/**
- 80 :
* @memberof Tools.Cirrus
- 81 :
* @instance
- 82 :
* @property {docIndex}
- 83 :
*/
- 84 :
docIndex: undefined,
- 85 :
- 86 :
/**
- 87 :
* @memberof Tools.Cirrus
- 88 :
* @instance
- 89 :
* @property {String} inlineData Directly specify the terms and their relative sizes.
- 90 :
* There data format is a comma-separated list of colon-separated term/size pairs.
- 91 :
* For example: love:20,like:15,dear:10,child:6
- 92 :
*/
- 93 :
inlineData: undefined,
- 94 :
- 95 :
/**
- 96 :
* @memberof Tools.Cirrus
- 97 :
* @instance
- 98 :
* @property {String} fontFamily The CSS font-family to use for the terms
- 99 :
* @default
- 100 :
*/
- 101 :
fontFamily: '"Palatino Linotype", "Book Antiqua", Palatino, serif',
- 102 :
- 103 :
// TODO remove these flash specific params
- 104 :
cirrusForceFlash: false,
- 105 :
background: '0xffffff',
- 106 :
fade: true,
- 107 :
smoothness: 2,
- 108 :
diagonals: 'none' // all, bigrams, none
- 109 :
},
- 110 :
glyph: 'xf06e@FontAwesome'
- 111 :
},
- 112 :
- 113 :
config: {
- 114 :
/**
- 115 :
* @private
- 116 :
*/
- 117 :
mode: undefined,
- 118 :
/**
- 119 :
* @private
- 120 :
*/
- 121 :
options: [
- 122 :
{xtype: 'stoplistoption'},
- 123 :
{
- 124 :
xtype: 'listeditor',
- 125 :
name: 'whiteList'
- 126 :
},
- 127 :
{xtype: 'categoriesoption'},
- 128 :
// {
- 129 :
// // TODO this field does nothing
- 130 :
// xtype: 'numberfield',
- 131 :
// name: 'label',
- 132 :
// fieldLabel: 'Max words',
- 133 :
// labelAlign: 'right',
- 134 :
// value: 500,
- 135 :
// minValue: 50,
- 136 :
// step: 50,
- 137 :
// listeners: {
- 138 :
// afterrender: function(field) {
- 139 :
// var win = field.up("window");
- 140 :
// if (win && win.panel) {field.setFieldLabel(win.panel.localize("maxTerms"))}
- 141 :
// }
- 142 :
// }
- 143 :
// },
- 144 :
{xtype: 'fontfamilyoption'},
- 145 :
{xtype: 'colorpaletteoption'}
- 146 :
- 147 :
],
- 148 :
/**
- 149 :
* @private
- 150 :
*/
- 151 :
records: undefined,
- 152 :
/**
- 153 :
* @private
- 154 :
*/
- 155 :
terms: undefined,
- 156 :
/**
- 157 :
* @private
- 158 :
*/
- 159 :
cirrusId: undefined,
- 160 :
/**
- 161 :
* @private
- 162 :
*/
- 163 :
visLayout: undefined, // cloud layout algorithm
- 164 :
/**
- 165 :
* @private
- 166 :
*/
- 167 :
vis: undefined, // actual vis
- 168 :
/**
- 169 :
* @private
- 170 :
*/
- 171 :
tip: undefined,
- 172 :
/**
- 173 :
* @private
- 174 :
*/
- 175 :
sizeAdjustment: 100, // amount to multiply a word's relative size by
- 176 :
/**
- 177 :
* @private
- 178 :
*/
- 179 :
minFontSize: 12,
- 180 :
/**
- 181 :
* @private
- 182 :
*/
- 183 :
largestWordSize: 0,
- 184 :
/**
- 185 :
* @private
- 186 :
*/
- 187 :
smallestWordSize: 1000000
- 188 :
},
- 189 :
- 190 :
MODE_CORPUS: 'corpus',
- 191 :
MODE_DOCUMENT: 'mode_document',
- 192 :
- 193 :
layout: 'fit',
- 194 :
- 195 :
/**
- 196 :
* @private
- 197 :
*/
- 198 :
constructor: function(config) {
- 199 :
this.callParent(arguments);
- 200 :
this.mixins['Voyant.panel.Panel'].constructor.apply(this, arguments);
- 201 :
- 202 :
this.getApplication().getCategoriesManager().addFeature('orientation', function() { return ~~(Math.random() * 2) * 90; });
- 203 :
- 204 :
this.setCirrusId(Ext.id(null, 'cirrus_'));
- 205 :
},
- 206 :
- 207 :
initComponent: function (config) {
- 208 :
Ext.apply(this, {
- 209 :
title: this.localize('title'),
- 210 :
dockedItems: [{
- 211 :
dock: 'bottom',
- 212 :
xtype: 'toolbar',
- 213 :
overflowHandler: 'scroller',
- 214 :
items: [{
- 215 :
xtype: 'corpusdocumentselector',
- 216 :
singleSelect: true
- 217 :
},{
- 218 :
fieldLabel: this.localize('visibleTerms'),
- 219 :
labelWidth: 40,
- 220 :
width: 120,
- 221 :
xtype: 'slider',
- 222 :
increment: 25,
- 223 :
minValue: 25,
- 224 :
maxValue: 500,
- 225 :
listeners: {
- 226 :
afterrender: function(slider) {
- 227 :
slider.maxValue = this.getApiParam("limit")
- 228 :
slider.minValue = Math.round(Math.max(5, parseInt(slider.maxValue/20)));
- 229 :
if (slider.maxValue % 25 === 0 && slider.minValue % 25 === 0) {
- 230 :
slider.increment = 25; // default values handling
- 231 :
} else {
- 232 :
slider.increment = Math.round((slider.maxValue - slider.minValue)/10); // 10 steps across entire range
- 233 :
}
- 234 :
slider.setValue(this.getApiParam("visible"));
- 235 :
},
- 236 :
changecomplete: function(slider, newvalue) {
- 237 :
this.setApiParams({visible: newvalue});
- 238 :
this.loadFromTermsRecords();
- 239 :
},
- 240 :
scope: this
- 241 :
}
- 242 :
}]
- 243 :
}]
- 244 :
});
- 245 :
- 246 :
this.callParent(arguments);
- 247 :
- 248 :
- 249 :
},
- 250 :
- 251 :
listeners: {
- 252 :
boxready: function() {
- 253 :
this.initVisLayout(); // force in case we've changed fontFamily from options
- 254 :
- 255 :
var dataString = this.getApiParam('inlineData');
- 256 :
if (dataString !== undefined) {
- 257 :
if (dataString.charAt(0)=="[") {
- 258 :
var jsonData = Ext.decode(dataString, true);
- 259 :
} else {
- 260 :
if (dataString.indexOf(":")>-1) {
- 261 :
jsonData = [];
- 262 :
dataString.split(",").forEach(function(term) {
- 263 :
parts = term.split(":");
- 264 :
jsonData.push({
- 265 :
text: parts[0],
- 266 :
rawFreq: parseInt(parts[1])
- 267 :
})
- 268 :
})
- 269 :
} else {
- 270 :
var terms = {}
- 271 :
jsonData = [];
- 272 :
dataString.split(",").forEach(function(term) {
- 273 :
if (term in terms) {
- 274 :
terms[term]++;
- 275 :
} else {
- 276 :
terms[term] = 1;
- 277 :
}
- 278 :
});
- 279 :
for (term in terms) {
- 280 :
jsonData.push({
- 281 :
text: term,
- 282 :
rawFreq: terms[term]
- 283 :
})
- 284 :
}
- 285 :
}
- 286 :
}
- 287 :
if (jsonData !== null && jsonData.length>0) {
- 288 :
this.setApiParam('inlineData', jsonData);
- 289 :
this.setTerms(jsonData);
- 290 :
this.buildFromTerms();
- 291 :
}
- 292 :
}
- 293 :
},
- 294 :
resize: function(panel, width, height) {
- 295 :
if (this.getVisLayout() && this.getCorpus()) {
- 296 :
this.setAdjustedSizes();
- 297 :
- 298 :
var el = this.getLayout().getRenderTarget();
- 299 :
width = el.getWidth();
- 300 :
height = el.getHeight();
- 301 :
- 302 :
el.down('svg').set({width: width, height: height});
- 303 :
if (this.getTerms()) {
- 304 :
this.getVisLayout().size([width, height]).stop().words(this.getTerms()).start();
- 305 :
}
- 306 :
}
- 307 :
},
- 308 :
- 309 :
loadedCorpus: function(src, corpus) {
- 310 :
this.getApplication().getCategoriesManager().addFeature('font', this.getApiParam('fontFamily')); // make sure the default for font is set from the api
- 311 :
this.initVisLayout(); // force in case we've changed fontFamily from options
- 312 :
if (this.getApiParam("docIndex")) {
- 313 :
this.fireEvent("documentSelected", this, corpus.getDocument(this.getApiParam("docIndex")));
- 314 :
} else if (this.getApiParam("docId")) {
- 315 :
this.fireEvent("documentSelected", this, corpus.getDocument(this.getApiParam("docId")));
- 316 :
} else {
- 317 :
this.loadFromCorpus(corpus);
- 318 :
}
- 319 :
},
- 320 :
- 321 :
corpusSelected: function(src, corpus) {
- 322 :
this.loadFromCorpus(corpus);
- 323 :
- 324 :
},
- 325 :
- 326 :
documentSelected: function(src, document) {
- 327 :
if (document) {
- 328 :
var corpus = this.getCorpus();
- 329 :
var document = corpus.getDocument(document);
- 330 :
this.setApiParam('docId', document.getId());
- 331 :
var documentTerms = document.getDocumentTerms({autoload: false, corpus: corpus, pageSize: this.getApiParam("maxVisible"), parentPanel: this});
- 332 :
this.loadFromDocumentTerms(documentTerms);
- 333 :
}
- 334 :
},
- 335 :
- 336 :
ensureCorpusView: function(src, corpus) {
- 337 :
if (this.getMode() != this.MODE_CORPUS) {this.loadFromCorpus(corpus);}
- 338 :
}
- 339 :
},
- 340 :
- 341 :
loadFromCorpus: function(corpus) {
- 342 :
var jsonData = this.getApiParam('inlineData');
- 343 :
if (jsonData === undefined) {
- 344 :
this.setApiParams({docId: undefined, docIndex: undefined});
- 345 :
this.loadFromCorpusTerms(corpus.getCorpusTerms({autoload: false, pageSize: this.getApiParam("maxVisible"), parentPanel: this}));
- 346 :
} else {
- 347 :
// if (jsonData !== undefined) {
- 348 :
// var records = [];
- 349 :
// for (var i = 0; i < jsonData.length; i++) {
- 350 :
// var wordData = jsonData[i];
- 351 :
// wordData.term = wordData.text; // inlineData/CorpusTerm format mismatch
- 352 :
// var record = Ext.create('Voyant.data.model.CorpusTerm', wordData);
- 353 :
// records.push(record);
- 354 :
// }
- 355 :
// this.setRecords(records);
- 356 :
// this.setMode(this.MODE_CORPUS);
- 357 :
// this.loadFromTermsRecords();
- 358 :
// }
- 359 :
}
- 360 :
},
- 361 :
- 362 :
loadFromDocumentTerms: function(documentTerms) {
- 363 :
documentTerms.load({
- 364 :
callback: function(records, operation, success) {
- 365 :
this.setMode(this.MODE_DOCUMENT);
- 366 :
this.setRecords(operation.getRecords()); // not sure why operation.records is different from records
- 367 :
this.loadFromTermsRecords();
- 368 :
},
- 369 :
scope: this,
- 370 :
params: this.getApiParams()
- 371 :
});
- 372 :
},
- 373 :
- 374 :
loadFromCorpusTerms: function(corpusTerms) {
- 375 :
corpusTerms.load({
- 376 :
callback: function(records, operation, success) {
- 377 :
this.setMode(this.MODE_CORPUS);
- 378 :
this.setRecords(operation.getRecords()); // not sure why operation.records is different from records
- 379 :
this.loadFromTermsRecords();
- 380 :
},
- 381 :
scope: this,
- 382 :
params: this.getApiParams()
- 383 :
});
- 384 :
},
- 385 :
- 386 :
loadFromTermsRecords: function() {
- 387 :
var records = this.getRecords();
- 388 :
var visible = this.getApiParam("visible");
- 389 :
if (visible>records.length) {visible=records.length;}
- 390 :
var terms = [];
- 391 :
for (var i=0; i<visible; i++) {
- 392 :
if (records[i].get('rawFreq')>0) {
- 393 :
terms.push({text: records[i].get('term').replace(/"/g,''), rawFreq: records[i].get('rawFreq')});
- 394 :
}
- 395 :
}
- 396 :
this.setTerms(terms);
- 397 :
this.buildFromTerms();
- 398 :
},
- 399 :
- 400 :
initVisLayout: function(forceLayout) {
- 401 :
if (forceLayout || this.getVisLayout() == undefined) {
- 402 :
var cirrusForceFlash = this.getApiParam('cirrusForceFlash');
- 403 :
if (cirrusForceFlash == 'true' || cirrusForceFlash === true) {
- 404 :
this.setApiParam('cirrusForceFlash', true);
- 405 :
var id = this.getCirrusId();
- 406 :
var appVars = {
- 407 :
id: id
- 408 :
};
- 409 :
var keys = ['background','fade','smoothness','diagonals'];
- 410 :
for (var i = 0; i < keys.length; i++) {
- 411 :
appVars[keys[i]] = this.getApiParam(keys[i]);
- 412 :
}
- 413 :
- 414 :
var swfscript = '<script type="text/javascript" src="'+this.getApplication().getBaseUrl()+'resources/swfobject/swfobject.js'+'"></script>';
- 415 :
var cirrusLinks = '<script type="text/javascript">'+
- 416 :
'cirrusClickHandler'+id+' = function(word, value) {\n'+
- 417 :
'\tif (window.console && console.info) console.info(word, value);\n'+
- 418 :
'\tvar cirrusTool = Ext.getCmp("'+this.id+'");\n'+
- 419 :
'\tcirrusTool.dispatchEvent("termsClicked", cirrusTool, [word]);\n'+
- 420 :
'}\n'+
- 421 :
'cirrusLoaded'+id+' = function() {\n'+
- 422 :
'\tif (window.console && console.info) console.info("cirrus flash loaded");\n'+
- 423 :
'}\n'+
- 424 :
'cirrusPNGHandler'+id+' = function(base64String) {\n'+
- 425 :
'\tvar cirrusTool = Ext.getCmp("'+this.id+'");\n'+
- 426 :
'\tcirrusTool.cirrusPNGHandler(base64String);\n'+
- 427 :
'}'+
- 428 :
'</script>';
- 429 :
- 430 :
this.update(swfscript+cirrusLinks, true, function() {
- 431 :
function loadFlash(component) {
- 432 :
if (typeof swfobject !== 'undefined') {
- 433 :
var el = component.getLayout().getRenderTarget();
- 434 :
var width = el.getWidth();
- 435 :
var height = el.getHeight();
- 436 :
- 437 :
var cirrusFlash = component.getApplication().getBaseUrl()+'resources/cirrus/flash/Cirrus.swf';
- 438 :
component.add({
- 439 :
xtype: 'flash',
- 440 :
id: appVars.id,
- 441 :
url: cirrusFlash,
- 442 :
width: width,
- 443 :
height: height,
- 444 :
flashVars: appVars,
- 445 :
flashParams: {
- 446 :
menu: 'false',
- 447 :
scale: 'showall',
- 448 :
allowScriptAccess: 'always',
- 449 :
bgcolor: '#222222',
- 450 :
wmode: 'opaque'
- 451 :
}
- 452 :
});
- 453 :
- 454 :
component.cirrusFlashApp = Ext.get(appVars.id).first().dom;
- 455 :
} else {
- 456 :
setTimeout(loadFlash, 50, component);
- 457 :
}
- 458 :
}
- 459 :
loadFlash(this);
- 460 :
- 461 :
}, this);
- 462 :
} else {
- 463 :
var el = this.getLayout().getRenderTarget();
- 464 :
el.update(""); // make sure to clear existing contents (especially for re-layout)
- 465 :
var width = el.getWidth();
- 466 :
var height = el.getHeight();
- 467 :
this.setVisLayout(
- 468 :
d3.layoutCloud()
- 469 :
.size([width, height])
- 470 :
.overflow(true)
- 471 :
.padding(1)
- 472 :
.rotate(function(d) {
- 473 :
var orientation = this.getApplication().getCategoriesManager().getFeatureForTerm('orientation', d.text);
- 474 :
if (orientation === undefined) {
- 475 :
orientation = ~~(Math.random() * 2) * 90;
- 476 :
}
- 477 :
return orientation;
- 478 :
}.bind(this))
- 479 :
.spiral('archimedean')
- 480 :
.font(function(d) { return this.getApplication().getCategoriesManager().getFeatureForTerm('font', d.text); }.bind(this))
- 481 :
.fontSize(function(d) {return d.fontSize; }.bind(this))
- 482 :
.text(function(d) { return d.text; })
- 483 :
.on('end', this.draw.bind(this))
- 484 :
);
- 485 :
- 486 :
var svg = d3.select(el.dom).append('svg').attr('id',this.getCirrusId()).attr('class', 'cirrusGraph').attr('width', width).attr('height', height);
- 487 :
this.setVis(svg.append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'));
- 488 :
- 489 :
if (this.getTip() === undefined) {
- 490 :
this.setTip(Ext.create('Ext.tip.Tip', {}));
- 491 :
}
- 492 :
}
- 493 :
}
- 494 :
},
- 495 :
- 496 :
buildFromTerms: function() {
- 497 :
var terms = this.getTerms();
- 498 :
if (this.rendered && terms) {
- 499 :
if (this.getApiParam('cirrusForceFlash') === true) {
- 500 :
if (this.cirrusFlashApp !== undefined && this.cirrusFlashApp.clearAll !== undefined) {
- 501 :
var words = [];
- 502 :
for (var i = 0; i < terms.length; i++) {
- 503 :
var t = terms[i];
- 504 :
if (!t.text && t.term) {t.text=t.term;}
- 505 :
words.push({word: t.text, size: t.rawFreq, label: t.rawFreq});
- 506 :
}
- 507 :
this.cirrusFlashApp.clearAll();
- 508 :
this.cirrusFlashApp.addWords(words);
- 509 :
this.cirrusFlashApp.arrangeWords();
- 510 :
} else {
- 511 :
Ext.defer(this.buildFromTerms, 50, this);
- 512 :
}
- 513 :
} else {
- 514 :
var minSize = 1000;
- 515 :
var maxSize = -1;
- 516 :
for (var i = 0; i < terms.length; i++) {
- 517 :
var size = terms[i].rawFreq;
- 518 :
if (size < minSize) minSize = size;
- 519 :
if (size > maxSize) maxSize = size;
- 520 :
}
- 521 :
this.setSmallestWordSize(minSize);
- 522 :
this.setLargestWordSize(maxSize);
- 523 :
- 524 :
// set the relative sizes for each word (0.0 to 1.0), then adjust based on available area
- 525 :
this.setRelativeSizes();
- 526 :
this.setAdjustedSizes();
- 527 :
- 528 :
// var fontSizer = d3.scalePow().range([10, 100]).domain([minSize, maxSize]);
- 529 :
- 530 :
this.getVisLayout().words(terms).start();
- 531 :
}
- 532 :
} else {
- 533 :
Ext.defer(this.buildFromTerms, 50, this);
- 534 :
}
- 535 :
},
- 536 :
- 537 :
draw: function(words, bounds) {
- 538 :
var panel = this;
- 539 :
var el = this.getLayout().getRenderTarget();
- 540 :
var width = this.getVisLayout().size()[0];
- 541 :
var height = this.getVisLayout().size()[1];
- 542 :
- 543 :
var scale = bounds ? Math.min(
- 544 :
width / Math.abs(bounds[1].x - width / 2),
- 545 :
width / Math.abs(bounds[0].x - width / 2),
- 546 :
height / Math.abs(bounds[1].y - height / 2),
- 547 :
height / Math.abs(bounds[0].y - height / 2)
- 548 :
) / 2 : 1;
- 549 :
- 550 :
var t = d3.transition().duration(1000);
- 551 :
- 552 :
var nodes = this.getVis().selectAll('text').data(words, function(d) {return d.text;});
- 553 :
- 554 :
nodes.exit().transition(t)
- 555 :
.style('font-size', '1px')
- 556 :
.remove();
- 557 :
- 558 :
var nodesEnter = nodes.enter().append('text')
- 559 :
.text(function(d) { return d.text; })
- 560 :
.attr('text-anchor', 'middle')
- 561 :
.attr('data-freq', function(d) { return d.rawFreq; })
- 562 :
.attr('transform', function(d) { return 'translate(' + [d.x, d.y] + ')rotate(' + d.rotate + ')'; })
- 563 :
.style('font-family', function(d) { return panel.getApplication().getCategoriesManager().getFeatureForTerm('font', d.text); })
- 564 :
.style('fill', function(d) { return panel.getApplication().getColorForTerm(d.text, true); })
- 565 :
.style('font-size', '1px')
- 566 :
.on('click', function(obj) {panel.dispatchEvent('termsClicked', panel, [obj.text]);})
- 567 :
.on('mouseover', function(obj) {
- 568 :
this.getTip().show();
- 569 :
}.bind(this))
- 570 :
.on('mousemove', function(obj) {
- 571 :
var tip = this.getTip();
- 572 :
tip.update(obj.text+': '+obj.rawFreq);
- 573 :
var container = Ext.get(this.getCirrusId()).dom;
- 574 :
var coords = d3.mouse(container);
- 575 :
coords[1] += 30;
- 576 :
tip.setPosition(coords);
- 577 :
}.bind(this))
- 578 :
.on('mouseout', function(obj) {
- 579 :
this.getTip().hide();
- 580 :
}.bind(this));
- 581 :
- 582 :
var nodesUpdate = nodes.merge(nodesEnter);
- 583 :
- 584 :
nodesUpdate.transition(t)
- 585 :
.style('font-family', function(d) { return panel.getApplication().getCategoriesManager().getFeatureForTerm('font', d.text); })
- 586 :
.style('fill', function(d) { return panel.getApplication().getColorForTerm(d.text, true); })
- 587 :
.attr('transform', function(d) { return 'translate(' + [d.x, d.y] + ')rotate(' + d.rotate + ')'; })
- 588 :
.style('font-size', function(d) { return d.fontSize + 'px'; });
- 589 :
- 590 :
this.getVis().transition(t).attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')scale(' + scale + ')');
- 591 :
},
- 592 :
- 593 :
map: function(value, istart, istop, ostart, ostop) {
- 594 :
return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
- 595 :
},
- 596 :
- 597 :
calculateSizeAdjustment: function() {
- 598 :
var terms = this.getTerms();
- 599 :
if (terms !== undefined) {
- 600 :
var el = this.getLayout().getRenderTarget();
- 601 :
- 602 :
var stageArea = el.getWidth() * el.getHeight();
- 603 :
if (stageArea < 100000) this.setMinFontSize(8);
- 604 :
else this.setMinFontSize(12);
- 605 :
- 606 :
var pixelsPerWord = stageArea / terms.length;
- 607 :
var totalWordsSize = 0;
- 608 :
for (var i = 0; i < terms.length; i++) {
- 609 :
var word = terms[i];
- 610 :
var wordArea = this.calculateWordArea(word);
- 611 :
totalWordsSize += wordArea;
- 612 :
}
- 613 :
- 614 :
this.setSizeAdjustment(stageArea / totalWordsSize);
- 615 :
}
- 616 :
},
- 617 :
- 618 :
calculateWordArea: function(word) {
- 619 :
var baseSize = Math.log(word.relativeSize * 10) * Math.LOG10E; // take the relativeSize (0.1 to 1.0), multiply by 10, then get the base-10 log of it
- 620 :
var height = (baseSize + word.relativeSize) / 2; // find the average between relativeSize and the log
- 621 :
var width = 0; //(baseSize / 1.5) * word.text.length;
- 622 :
for (var i = 0; i < word.text.length; i++ ) {
- 623 :
var letter = word.text.charAt(i);
- 624 :
if (letter == 'f' || letter == 'i' || letter == 'j' || letter == 'l' || letter == 'r' || letter == 't') width += baseSize / 3;
- 625 :
else if (letter == 'm' || letter == 'w') width += baseSize / (4 / 3);
- 626 :
else width += baseSize / 1.9;
- 627 :
}
- 628 :
var wordArea = height * width;
- 629 :
return wordArea;
- 630 :
},
- 631 :
- 632 :
setAdjustedSizes: function() {
- 633 :
this.calculateSizeAdjustment();
- 634 :
var terms = this.getTerms();
- 635 :
if (terms !== undefined) {
- 636 :
for (var i = 0; i < terms.length; i++) {
- 637 :
var term = terms[i];
- 638 :
var adjustedSize = this.findNewRelativeSize(term);
- 639 :
term.fontSize = adjustedSize > this.getMinFontSize() ? adjustedSize : this.getMinFontSize();
- 640 :
}
- 641 :
}
- 642 :
},
- 643 :
- 644 :
setRelativeSizes: function() {
- 645 :
var terms = this.getTerms();
- 646 :
if (terms !== undefined) {
- 647 :
for (var i = 0; i < terms.length; i++) {
- 648 :
var word = terms[i];
- 649 :
word.relativeSize = this.map(word.rawFreq, this.getSmallestWordSize(), this.getLargestWordSize(), 0.1, 1);
- 650 :
}
- 651 :
}
- 652 :
},
- 653 :
- 654 :
findNewRelativeSize: function(word) {
- 655 :
var areaMultiplier = this.getSizeAdjustment();
- 656 :
var area = this.calculateWordArea(word) * areaMultiplier;
- 657 :
// given the area = (x+6)*(2*x/3*y), solve for x
- 658 :
var newRelativeSize = (Math.sqrt(6) * Math.sqrt(6 * Math.pow(word.text.length, 2) + area * word.text.length) - 6 * word.text.length) / (2 * word.text.length);
- 659 :
return newRelativeSize;
- 660 :
}
- 661 :
});