- 1 :
/**
- 2 :
* The TermsBerry tool provides a way of exploring high frequency terms and their collocates (words that occur in proximity).
- 3 :
*
- 4 :
* @example
- 5 :
*
- 6 :
* let config = {
- 7 :
* categories: null, // a query for the keywords (can be comma-separated list)
- 8 :
* context: null, // a named stopword list or comma-separated list of words
- 9 :
* docId: null, // document index to restrict to (can be comma-separated list)
- 10 :
* docIndex: null, // the size of the context (the number of words on each side of the keyword)
- 11 :
* numInitialTerms: null, // the initial number of terms to display
- 12 :
* query: null, // the initial number of terms to display
- 13 :
* stopList: null, // the initial number of terms to display
- 14 :
* };
- 15 :
*
- 16 :
* loadCorpus("austen").tool("termsberry", config);
- 17 :
*
- 18 :
* @class TermsBerry
- 19 :
* @tutorial termsberry
- 20 :
* @memberof Tools
- 21 :
*/
- 22 :
Ext.define('Voyant.panel.TermsBerry', {
- 23 :
extend: 'Ext.panel.Panel',
- 24 :
mixins: ['Voyant.panel.Panel'],
- 25 :
alias: 'widget.termsberry',
- 26 :
statics: {
- 27 :
i18n: {
- 28 :
},
- 29 :
api: {
- 30 :
/**
- 31 :
* @memberof Tools.TermsBerry
- 32 :
* @instance
- 33 :
* @property {stopList}
- 34 :
* @default
- 35 :
*/
- 36 :
stopList: 'auto',
- 37 :
- 38 :
/**
- 39 :
* @memberof Tools.TermsBerry
- 40 :
* @instance
- 41 :
* @property {context}
- 42 :
* @default
- 43 :
*/
- 44 :
context: 2,
- 45 :
- 46 :
/**
- 47 :
* @memberof Tools.TermsBerry
- 48 :
* @instance
- 49 :
* @property {Number} numInitialTerms The number of initial terms to display.
- 50 :
*/
- 51 :
numInitialTerms: 75,
- 52 :
- 53 :
/**
- 54 :
* @memberof Tools.TermsBerry
- 55 :
* @instance
- 56 :
* @property {query}
- 57 :
*/
- 58 :
query: undefined,
- 59 :
- 60 :
/**
- 61 :
* @memberof Tools.TermsBerry
- 62 :
* @instance
- 63 :
* @property {docIndex}
- 64 :
*/
- 65 :
docIndex: undefined,
- 66 :
- 67 :
/**
- 68 :
* @memberof Tools.TermsBerry
- 69 :
* @instance
- 70 :
* @property {docId}
- 71 :
*/
- 72 :
docId: undefined,
- 73 :
- 74 :
/**
- 75 :
* @memberof Tools.TermsBerry
- 76 :
* @instance
- 77 :
* @property {categories}
- 78 :
*/
- 79 :
categories: undefined
- 80 :
},
- 81 :
glyph: 'xf1db@FontAwesome'
- 82 :
},
- 83 :
config: {
- 84 :
options: [{xtype: 'stoplistoption'},{xtype: 'categoriesoption'}],
- 85 :
- 86 :
mode: undefined,
- 87 :
- 88 :
scalingFactor: 3,
- 89 :
- 90 :
minRawFreq: undefined,
- 91 :
maxRawFreq: undefined,
- 92 :
maxCollocateValue: undefined,
- 93 :
minFillValue: undefined,
- 94 :
maxFillValue: undefined,
- 95 :
- 96 :
currentData: {},
- 97 :
blacklist: {},
- 98 :
- 99 :
visLayout: undefined,
- 100 :
vis: undefined,
- 101 :
visInfo: undefined,
- 102 :
visId: undefined,
- 103 :
- 104 :
currentNode: undefined,
- 105 :
- 106 :
tip: undefined,
- 107 :
contextMenu: undefined
- 108 :
},
- 109 :
- 110 :
MODE_TOP: 'top',
- 111 :
MODE_DISTINCT: 'distinct',
- 112 :
- 113 :
MIN_TERMS: 5,
- 114 :
MAX_TERMS: 500,
- 115 :
- 116 :
COLLOCATES_LIMIT: 1000000, // a very large number so we get all of them
- 117 :
- 118 :
MIN_SCALING: 1,
- 119 :
MAX_SCALING: 5,
- 120 :
- 121 :
MIN_STROKE_OPACITY: 0.1,
- 122 :
MAX_STROKE_OPACITY: 0.3,
- 123 :
- 124 :
layout: 'fit',
- 125 :
- 126 :
constructor: function(config) {
- 127 :
this.setMode(this.MODE_TOP);
- 128 :
- 129 :
this.callParent(arguments);
- 130 :
this.mixins['Voyant.panel.Panel'].constructor.apply(this, arguments);
- 131 :
- 132 :
this.setVisId(Ext.id(null, 'termspack_'));
- 133 :
},
- 134 :
- 135 :
initComponent: function() {
- 136 :
Ext.apply(this, {
- 137 :
title: this.localize('title'),
- 138 :
dockedItems: [{
- 139 :
dock: 'bottom',
- 140 :
xtype: 'toolbar',
- 141 :
overflowHandler: 'scroller',
- 142 :
items: [{
- 143 :
xtype: 'querysearchfield',
- 144 :
clearOnQuery: true
- 145 :
},{
- 146 :
xtype: 'button',
- 147 :
text: this.localize('strategy'),
- 148 :
menu: {
- 149 :
items: [{
- 150 :
xtype: 'menucheckitem',
- 151 :
group: 'strategy',
- 152 :
checked: this.getMode() === this.MODE_TOP,
- 153 :
text: this.localize('topTerms'),
- 154 :
checkHandler: function(item, checked) {
- 155 :
if (checked) {
- 156 :
this.setMode(this.MODE_TOP);
- 157 :
this.doLoad();
- 158 :
}
- 159 :
},
- 160 :
scope: this
- 161 :
},{
- 162 :
xtype: 'menucheckitem',
- 163 :
group: 'strategy',
- 164 :
checked: this.getMode() === this.MODE_DISTINCT,
- 165 :
text: this.localize('distinctTerms'),
- 166 :
checkHandler: function(item, checked) {
- 167 :
if (checked) {
- 168 :
this.setMode(this.MODE_DISTINCT);
- 169 :
this.doLoad();
- 170 :
}
- 171 :
},
- 172 :
scope: this
- 173 :
}]
- 174 :
}
- 175 :
},{
- 176 :
fieldLabel: this.localize('numTerms'),
- 177 :
labelWidth: 50,
- 178 :
labelAlign: 'right',
- 179 :
width: 120,
- 180 :
xtype: 'slider',
- 181 :
increment: 1,
- 182 :
minValue: this.MIN_TERMS,
- 183 :
maxValue: this.MAX_TERMS,
- 184 :
listeners: {
- 185 :
afterrender: function(slider) {
- 186 :
slider.setValue(parseInt(this.getApiParam('numInitialTerms')));
- 187 :
},
- 188 :
changecomplete: function(slider, newvalue) {
- 189 :
this.setApiParam("numInitialTerms", newvalue);
- 190 :
this.doLoad();
- 191 :
},
- 192 :
scope: this
- 193 :
}
- 194 :
},{
- 195 :
fieldLabel: this.localize('context'),
- 196 :
labelWidth: 50,
- 197 :
labelAlign: 'right',
- 198 :
width: 120,
- 199 :
xtype: 'slider',
- 200 :
increment: 1,
- 201 :
minValue: 1,
- 202 :
maxValue: 30,
- 203 :
listeners: {
- 204 :
afterrender: function(slider) {
- 205 :
slider.setValue(this.getApiParam('context'));
- 206 :
},
- 207 :
changecomplete: function(slider, newvalue) {
- 208 :
this.setApiParams({context: newvalue});
- 209 :
this.doLoad();
- 210 :
},
- 211 :
scope: this
- 212 :
}
- 213 :
},{
- 214 :
fieldLabel: this.localize('scaling'),
- 215 :
labelWidth: 50,
- 216 :
labelAlign: 'right',
- 217 :
width: 120,
- 218 :
xtype: 'slider',
- 219 :
increment: 1,
- 220 :
minValue: this.MIN_SCALING,
- 221 :
maxValue: this.MAX_SCALING,
- 222 :
listeners: {
- 223 :
afterrender: function(slider) {
- 224 :
slider.setValue(this.getScalingFactor());
- 225 :
},
- 226 :
changecomplete: function(slider, newvalue) {
- 227 :
// use the inverse of the value since it'll make more sense to the user
- 228 :
var value = Math.abs(newvalue-(this.MAX_SCALING+1));
- 229 :
this.setScalingFactor(value);
- 230 :
this.reload();
- 231 :
},
- 232 :
scope: this
- 233 :
}
- 234 :
}
- 235 :
]
- 236 :
}]
- 237 :
});
- 238 :
- 239 :
this.setContextMenu(Ext.create('Ext.menu.Menu', {
- 240 :
renderTo: Ext.getBody(),
- 241 :
items: [{
- 242 :
xtype: 'box',
- 243 :
itemId: 'label',
- 244 :
margin: '5px 0px 5px 5px',
- 245 :
html: ''
- 246 :
},{
- 247 :
xtype: 'menuseparator'
- 248 :
},{
- 249 :
xtype: 'button',
- 250 :
text: 'Remove',
- 251 :
style: 'margin: 5px;',
- 252 :
handler: function(b, e) {
- 253 :
var node = this.getCurrentNode();
- 254 :
if (node !== undefined) {
- 255 :
delete this.getCurrentData()[node.data.term];
- 256 :
this.getBlacklist()[node.data.term] = true;
- 257 :
this.setCurrentNode(undefined);
- 258 :
}
- 259 :
this.getContextMenu().hide();
- 260 :
this.reload();
- 261 :
},
- 262 :
scope: this
- 263 :
}]
- 264 :
}));
- 265 :
- 266 :
this.on('query', function(src, query) {
- 267 :
if (query.length > 0) {
- 268 :
this.doLoad(query);
- 269 :
}
- 270 :
}, this);
- 271 :
- 272 :
this.callParent(arguments);
- 273 :
},
- 274 :
- 275 :
listeners: {
- 276 :
boxready: function() {
- 277 :
this.initVisLayout();
- 278 :
},
- 279 :
- 280 :
resize: function(panel, width, height) {
- 281 :
if (this.getVisLayout() && this.getCorpus()) {
- 282 :
var el = this.getLayout().getRenderTarget();
- 283 :
width = el.getWidth();
- 284 :
height = el.getHeight();
- 285 :
- 286 :
el.down('svg').set({width: width, height: height});
- 287 :
- 288 :
this.getVisLayout().size([width, height]);
- 289 :
- 290 :
this.reload();
- 291 :
}
- 292 :
},
- 293 :
- 294 :
loadedCorpus: function(src, corpus) {
- 295 :
if (this.isVisible()) {
- 296 :
this.doLoad();
- 297 :
}
- 298 :
},
- 299 :
activate: function() {
- 300 :
if (this.getCorpus()) {
- 301 :
this.doLoad();
- 302 :
}
- 303 :
}
- 304 :
},
- 305 :
- 306 :
doLoad: function(query) {
- 307 :
this.resetVis();
- 308 :
if (query === undefined) {
- 309 :
this.resetMinMax();
- 310 :
this.setCurrentData({});
- 311 :
}
- 312 :
if (this.getMode() === this.MODE_DISTINCT) {
- 313 :
this.getDistinctTerms(query);
- 314 :
} else {
- 315 :
this.getTopTerms(query);
- 316 :
}
- 317 :
},
- 318 :
- 319 :
reload: function() {
- 320 :
var data = this.processCollocates([]);
- 321 :
if (data.length > 0) {
- 322 :
this.resetVis();
- 323 :
this.buildVisFromData(data);
- 324 :
}
- 325 :
},
- 326 :
- 327 :
resetMinMax: function() {
- 328 :
this.setMinRawFreq(undefined);
- 329 :
this.setMaxRawFreq(undefined);
- 330 :
this.setMaxCollocateValue(undefined);
- 331 :
this.setMinFillValue(undefined);
- 332 :
this.setMaxFillValue(undefined);
- 333 :
},
- 334 :
- 335 :
resetVis: function() {
- 336 :
var vis = this.getVis();
- 337 :
if (vis) {
- 338 :
vis.selectAll('.node').remove();
- 339 :
}
- 340 :
},
- 341 :
- 342 :
getTopTerms: function(query) {
- 343 :
var limit = parseInt(this.getApiParam('numInitialTerms'));
- 344 :
var stopList = this.getApiParam('stopList');
- 345 :
var categories = this.getApiParam('categories');
- 346 :
if (query !== undefined) {
- 347 :
limit = undefined;
- 348 :
stopList = undefined;
- 349 :
}
- 350 :
this.getCorpus().getCorpusTerms().load({
- 351 :
params: {
- 352 :
query: query,
- 353 :
categories: categories,
- 354 :
limit: limit,
- 355 :
stopList: stopList
- 356 :
},
- 357 :
callback: function(records, operation, success) {
- 358 :
if (success) {
- 359 :
this.loadFromRecords(records);
- 360 :
}
- 361 :
},
- 362 :
scope: this
- 363 :
});
- 364 :
},
- 365 :
- 366 :
getDistinctTerms: function(query) {
- 367 :
var limit = parseInt(this.getApiParam('numInitialTerms'));
- 368 :
var stopList = this.getApiParam('stopList');
- 369 :
var categories = this.getApiParam('categories');
- 370 :
if (query !== undefined) {
- 371 :
limit = undefined;
- 372 :
stopList = undefined;
- 373 :
}
- 374 :
var perDocLimit = Math.ceil(parseInt(this.getApiParam('numInitialTerms')) / this.getCorpus().getDocumentsCount()); // ceil ensures there's at least 1 per doc
- 375 :
this.getCorpus().getDocumentTerms().load({
- 376 :
params: {
- 377 :
query: query,
- 378 :
categories: categories,
- 379 :
limit: limit,
- 380 :
perDocLimit: perDocLimit,
- 381 :
stopList: stopList,
- 382 :
sort: 'TFIDF',
- 383 :
dir: 'DESC'
- 384 :
},
- 385 :
callback: function(records, operation, success) {
- 386 :
if (success) {
- 387 :
this.loadFromRecords(records);
- 388 :
}
- 389 :
},
- 390 :
scope: this
- 391 :
});
- 392 :
},
- 393 :
- 394 :
loadFromQuery: function(query) {
- 395 :
this.getCorpus().getCorpusTerms().load({
- 396 :
params: {
- 397 :
query: query
- 398 :
},
- 399 :
callback: function(records, operation, success) {
- 400 :
if (success) {
- 401 :
this.loadFromRecords(records);
- 402 :
}
- 403 :
},
- 404 :
scope: this
- 405 :
});
- 406 :
},
- 407 :
- 408 :
loadFromRecords: function(records) {
- 409 :
if (Ext.isArray(records) && records.length>0) {
- 410 :
var maxFreq = this.getMaxRawFreq();
- 411 :
var minFreq = this.getMinRawFreq();
- 412 :
var minFillVal = this.getMinFillValue();
- 413 :
var maxFillVal = this.getMaxFillValue();
- 414 :
var terms = [];
- 415 :
records.forEach(function(r) {
- 416 :
var term = r.getTerm();
- 417 :
if (!this.getBlacklist()[term]) {
- 418 :
var rawFreq = r.getRawFreq();
- 419 :
var fillVal = this.getMode() === this.MODE_DISTINCT ? r.get('tfidf') : r.getInDocumentsCount();
- 420 :
- 421 :
if (maxFreq === undefined || rawFreq > maxFreq) maxFreq = rawFreq;
- 422 :
if (minFreq === undefined || rawFreq < minFreq) minFreq = rawFreq;
- 423 :
- 424 :
if (maxFillVal === undefined || fillVal > maxFillVal) maxFillVal = fillVal;
- 425 :
if (minFillVal === undefined || fillVal < minFillVal) minFillVal = fillVal;
- 426 :
- 427 :
this.getCurrentData()[term] = {
- 428 :
term: term,
- 429 :
rawFreq: rawFreq,
- 430 :
relativeFreq: r.get('relativeFreq'),//r.getRelativeFreq(),
- 431 :
fillValue: fillVal,
- 432 :
collocates: []
- 433 :
};
- 434 :
- 435 :
terms.push(term);
- 436 :
}
- 437 :
}, this);
- 438 :
- 439 :
this.setMaxRawFreq(maxFreq);
- 440 :
this.setMinRawFreq(minFreq);
- 441 :
this.setMinFillValue(minFillVal);
- 442 :
this.setMaxFillValue(maxFillVal);
- 443 :
- 444 :
this.getCollocatesForQuery(terms);
- 445 :
}
- 446 :
},
- 447 :
- 448 :
getCollocatesForQuery: function(query) {
- 449 :
var whitelist = [];
- 450 :
for (var term in this.getCurrentData()) {
- 451 :
whitelist.push(term);
- 452 :
}
- 453 :
- 454 :
this.setApiParams({
- 455 :
mode: 'corpus'
- 456 :
});
- 457 :
var params = this.getApiParams();
- 458 :
this.getCorpus().getCorpusCollocates().load({
- 459 :
params: Ext.apply(Ext.clone(params), {query: query, collocatesWhitelist: whitelist, limit: this.COLLOCATES_LIMIT}),
- 460 :
callback: function(records, op, success) {
- 461 :
if (success) {
- 462 :
this.buildVisFromData(this.processCollocates(records));
- 463 :
}
- 464 :
},
- 465 :
scope: this
- 466 :
});
- 467 :
},
- 468 :
- 469 :
processCollocates: function(records) {
- 470 :
var currentTerms = this.getCurrentData();
- 471 :
- 472 :
var maxCol = this.getMaxCollocateValue();
- 473 :
- 474 :
for (var i=0; i<records.length; i++) {
- 475 :
var r = records[i];
- 476 :
var term = r.getTerm();
- 477 :
var contextTerm = r.getContextTerm();
- 478 :
var contextFreq = r.getContextTermRawFreq();
- 479 :
- 480 :
if (maxCol === undefined || contextFreq > maxCol) {
- 481 :
maxCol = contextFreq;
- 482 :
}
- 483 :
- 484 :
if (currentTerms[term] === undefined) {
- 485 :
// should not be here
- 486 :
} else {
- 487 :
if (term != contextTerm) {
- 488 :
currentTerms[term].collocates.push({
- 489 :
term: contextTerm, value: contextFreq
- 490 :
});
- 491 :
}
- 492 :
}
- 493 :
}
- 494 :
- 495 :
this.setMaxCollocateValue(maxCol);
- 496 :
- 497 :
var data = [];
- 498 :
for (var term in currentTerms) {
- 499 :
// if (currentTerms[term].collocates.length > 0) {
- 500 :
data.push(currentTerms[term]);
- 501 :
// }
- 502 :
}
- 503 :
return data;
- 504 :
},
- 505 :
- 506 :
buildVisFromData: function(data) {
- 507 :
var me = this;
- 508 :
- 509 :
if (!this.getVis()) {return;} // not initialized
- 510 :
- 511 :
var rootId = '$$$root$$$';
- 512 :
data.push({term: rootId, collocates:[], rawFreq:1});
- 513 :
var root = d3.stratify()
- 514 :
.id(function(d) { return d.term; })
- 515 :
.parentId(function(d) {
- 516 :
if (d.term !== rootId) return rootId;
- 517 :
else return '';
- 518 :
})(data)
- 519 :
.sort(function(a, b) { return a.rawFreq < b.rawFreq ? 1 : a.rawFreq > b.rawFreq ? -1 : 0; })
- 520 :
.sum(function(d) { return Math.pow(d.rawFreq, 1/me.getScalingFactor()); });
- 521 :
this.getVisLayout()(root);
- 522 :
- 523 :
// join nodes with data
- 524 :
var nodes = this.getVis().selectAll('.node').data(root.descendants());
- 525 :
- 526 :
// update
- 527 :
nodes.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; });
- 528 :
nodes.selectAll('circle').attr('r', function(d) { return d.r; });
- 529 :
- 530 :
var idGet = function(term) {
- 531 :
return term.replace(/\W/g, '_'); // remove non-word characters to create valid DOM ids
- 532 :
};
- 533 :
- 534 :
var collocateFill = d3.scalePow().exponent(1/3)
- 535 :
.domain([0,this.getMaxCollocateValue()]).range(['#fff', '#bd3163']);
- 536 :
- 537 :
var defaultFill;
- 538 :
if (this.getMode() === this.MODE_DISTINCT) {
- 539 :
defaultFill = d3.scalePow().exponent(1/5)
- 540 :
.domain([this.getMinFillValue(), this.getMaxFillValue()]).range(['#dedede', '#fff']);
- 541 :
} else {
- 542 :
defaultFill = d3.scaleLinear()
- 543 :
.domain([this.getMaxFillValue(), this.getMinFillValue()]).range(['#dedede', '#fff']);
- 544 :
}
- 545 :
- 546 :
// roughly calculate font size based on available area and number of terms
- 547 :
var size = this.getVisLayout().size();
- 548 :
var layoutRadius = Math.min(size[0], size[1]) / 2;
- 549 :
var layoutArea = Math.PI*(layoutRadius*layoutRadius);
- 550 :
var totalTerms = data.length;
- 551 :
var termArea = layoutArea / totalTerms;
- 552 :
var termRadius = Math.sqrt(termArea / Math.PI);
- 553 :
var minFontSize = termRadius / 3;
- 554 :
var scalingInverse = Math.abs(this.getScalingFactor()-(this.MAX_SCALING+1));
- 555 :
scalingInverse = Math.max(1, scalingInverse-1); // substract one to avoid too large fonts
- 556 :
var maxFontSize = minFontSize * scalingInverse;
- 557 :
- 558 :
var textSizer = d3.scaleLinear()//pow().exponent(1/2)
- 559 :
.domain([this.getMinRawFreq(),this.getMaxRawFreq()]).range([minFontSize, maxFontSize]);
- 560 :
- 561 :
// enter
- 562 :
var node = nodes.enter().append('g')
- 563 :
.attr('class', 'node')
- 564 :
.style('visibility', function(d) { return d.depth > 0 ? 'visible' : 'hidden'; }) // hide root
- 565 :
.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; })
- 566 :
.on('click', function(d) {
- 567 :
me.dispatchEvent('termsClicked', me, [d.data.term]);
- 568 :
})
- 569 :
.on('mouseover', function(d, i) {
- 570 :
me.setCurrentNode(d);
- 571 :
- 572 :
me.getVis().selectAll('circle')
- 573 :
.style('stroke-width', 1)
- 574 :
.style('stroke', '#111')
- 575 :
.style('fill', function(d) { return defaultFill(d.data.fillValue); });
- 576 :
- 577 :
d3.select(this).select('circle')
- 578 :
.style('fill', '#89e1c2')
- 579 :
.style('stroke', '#26926c')
- 580 :
.style('stroke-opacity', me.MAX_STROKE_OPACITY);
- 581 :
- 582 :
var fillLabel;
- 583 :
if (me.getMode() === me.MODE_DISTINCT) {
- 584 :
fillLabel = me.localize('tfidf');
- 585 :
} else {
- 586 :
fillLabel = me.localize('inDocs');
- 587 :
}
- 588 :
- 589 :
if (!me.getContextMenu().isVisible()) {
- 590 :
var info = '<b>'+d.data.term+'</b> ('+d.data.rawFreq+')<br/>'+fillLabel+': '+d.data.fillValue;
- 591 :
var tip = me.getTip();
- 592 :
tip.update(info);
- 593 :
tip.show();
- 594 :
}
- 595 :
- 596 :
for (var i = 0; i < d.data.collocates.length; i++) {
- 597 :
var collocate = d.data.collocates[i];
- 598 :
var match = me.getVis().selectAll('.node').filter(function(d) { return d.data.term === collocate.term; });
- 599 :
match.select('circle')
- 600 :
.style('fill', function(d) { return collocateFill(collocate.value); })
- 601 :
.style('stroke', '#bd3163')
- 602 :
.style('stroke-opacity', me.MAX_STROKE_OPACITY);
- 603 :
match.select('tspan.value').text(function(d) { return collocate.value; });
- 604 :
}
- 605 :
})
- 606 :
.on('mousemove', function() {
- 607 :
me.getTip().setPosition(d3.event.pageX+5, d3.event.pageY-50);
- 608 :
})
- 609 :
.on('mouseout', function() {
- 610 :
if (!me.getContextMenu().isVisible()) {
- 611 :
me.setCurrentNode(undefined);
- 612 :
}
- 613 :
- 614 :
me.getVis().selectAll('circle').style('stroke-opacity', me.MIN_STROKE_OPACITY).style('stroke', '#111')
- 615 :
.style('fill', function(d) { return defaultFill(d.data.fillValue); });
- 616 :
me.getVis().selectAll('tspan.value').text('');
- 617 :
me.getTip().hide();
- 618 :
// me.getVisInfo().text('');
- 619 :
})
- 620 :
.on('contextmenu', function(d, i) {
- 621 :
d3.event.preventDefault();
- 622 :
me.getTip().hide();
- 623 :
var menu = me.getContextMenu();
- 624 :
menu.queryById('label').setHtml(d.data.term);
- 625 :
menu.showAt(d3.event.pageX+5, d3.event.pageY-50);
- 626 :
});
- 627 :
- 628 :
node.append('circle')
- 629 :
.attr('id', function(d) {
- 630 :
return idGet(d.data.term);
- 631 :
})
- 632 :
.attr('r', function(d) { return d.r; })
- 633 :
.style('fill', function(d) { return defaultFill(d.data.fillValue); })
- 634 :
.style('stroke', '#111')
- 635 :
.style('stroke-opacity', me.MIN_STROKE_OPACITY)
- 636 :
.style('stroke-width', 1);
- 637 :
- 638 :
node.append('clipPath').attr('id', function(d) { return 'clip-' + idGet(d.data.term); })
- 639 :
.append('use').attr('xlink:href', function(d) { return '#' + idGet(d.data.term); });
- 640 :
- 641 :
var text = node.append('text')
- 642 :
.attr('clip-path', function(d) { return 'url(#clip-' + idGet(d.data.term) + ')'; })
- 643 :
.style('font-family', function(d) { return me.getApplication().getCategoriesManager().getFeatureForTerm('font', d.data.term); })
- 644 :
.style('text-anchor', 'middle')
- 645 :
.style('cursor', 'default');
- 646 :
text.append('tspan')
- 647 :
.attr('class', 'term')
- 648 :
.attr('font-size', function(d) { return textSizer(d.data.rawFreq); })
- 649 :
.attr('x', 0)
- 650 :
.attr('y', function(d) { return textSizer(d.data.rawFreq)/4; })
- 651 :
.text(function(d) { return d.data.term; });
- 652 :
text.append('tspan')
- 653 :
.attr('class', 'value')
- 654 :
.attr('font-size', function(d) { return textSizer(d.data.rawFreq)*0.75; })
- 655 :
.attr('x', 0)
- 656 :
.attr('y', function(d) { return textSizer(d.data.rawFreq)+1; });
- 657 :
- 658 :
// exit
- 659 :
nodes.exit().remove();
- 660 :
- 661 :
},
- 662 :
- 663 :
initVisLayout: function() {
- 664 :
var el = this.getLayout().getRenderTarget();
- 665 :
el.update(''); // make sure to clear existing contents (especially for re-layout)
- 666 :
var width = el.getWidth();
- 667 :
var height = el.getHeight();
- 668 :
- 669 :
var me = this;
- 670 :
this.setVisLayout(
- 671 :
d3.pack().size([width, height]).padding(1.5)
- 672 :
);
- 673 :
- 674 :
var svg = d3.select(el.dom).append('svg').attr('id',this.getVisId()).attr('width', width).attr('height', height);
- 675 :
this.setVis(svg.append('g'));
- 676 :
- 677 :
this.setVisInfo(svg.append('text').attr('x', 10).attr('y', 10));
- 678 :
- 679 :
if (this.getTip() === undefined) {
- 680 :
this.setTip(Ext.create('Ext.tip.Tip', {}));
- 681 :
}
- 682 :
}
- 683 :
});