- 1 :
/**
- 2 :
* Trends shows a line graph depicting the distribution of a word's occurrence across a corpus or document.
- 3 :
*
- 4 :
* @example
- 5 :
*
- 6 :
* let config = {
- 7 :
* "chartType": null,
- 8 :
* "docId": null,
- 9 :
* "docIndex": null,
- 10 :
* "labels": null,
- 11 :
* "limit": null,
- 12 :
* "mode": null,
- 13 :
* "query": null,
- 14 :
* "stopList": null,
- 15 :
* "withDistributions": null
- 16 :
* };
- 17 :
*
- 18 :
* loadCorpus("austen").tool("Trends", config);
- 19 :
*
- 20 :
* @class Trends
- 21 :
* @tutorial trends
- 22 :
* @memberof Tools
- 23 :
*/
- 24 :
Ext.define('Voyant.panel.Trends', {
- 25 :
extend: 'Ext.panel.Panel',
- 26 :
mixins: ['Voyant.panel.Panel'],
- 27 :
requires: ['Voyant.data.store.Documents'],
- 28 :
alias: 'widget.trends',
- 29 :
config: {
- 30 :
/**
- 31 :
* @private
- 32 :
*/
- 33 :
options: [{xtype: 'stoplistoption'},{xtype: 'categoriesoption'},
- 34 :
- 35 :
{
- 36 :
name: 'bins',
- 37 :
xtype: 'slider',
- 38 :
labelAlign: 'right',
- 39 :
width: 200,
- 40 :
minValue: 2,
- 41 :
maxValue: 100,
- 42 :
listeners: {
- 43 :
afterrender: function(slider) {
- 44 :
var trends = slider.up("window").panel;
- 45 :
slider.setFieldLabel(trends.localize("segmentsSlider"));
- 46 :
}
- 47 :
}
- 48 :
},{
- 49 :
xtype: 'radiogroup',
- 50 :
labelAlign: 'right',
- 51 :
columns: 3,
- 52 :
vertical: true,
- 53 :
name: 'withDistributions',
- 54 :
items: [{
- 55 :
boxLabel: 'raw',
- 56 :
name: 'withDistributions',
- 57 :
inputValue: 'raw'
- 58 :
},{
- 59 :
boxLabel: 'relative',
- 60 :
name: 'withDistributions',
- 61 :
inputValue: 'relative',
- 62 :
style: 'margin-left: 1em;'
- 63 :
}],
- 64 :
listeners: {
- 65 :
afterrender: function(radiogroup) {
- 66 :
var panel = this.up("window").panel;
- 67 :
this.setFieldLabel(panel.localize("freqsMode"));
- 68 :
var val = panel.getApiParam("withDistributions");
- 69 :
radiogroup.getBoxes().forEach(function(item) {
- 70 :
item.setBoxLabel(panel.localize(item.inputValue));
- 71 :
item.checked = item.inputValue==val;
- 72 :
});
- 73 :
this.setValue({withDistributions: val});
- 74 :
}
- 75 :
}
- 76 :
},{xtype: 'colorpaletteoption'}]
- 77 :
},
- 78 :
statics: {
- 79 :
i18n: {},
- 80 :
api: {
- 81 :
- 82 :
/**
- 83 :
* @memberof Tools.Trends
- 84 :
* @instance
- 85 :
* @property {limit}
- 86 :
* @default
- 87 :
*/
- 88 :
limit: 5,
- 89 :
- 90 :
/**
- 91 :
* @memberof Tools.Trends
- 92 :
* @instance
- 93 :
* @property {stopList}
- 94 :
* @default
- 95 :
*/
- 96 :
stopList: 'auto',
- 97 :
- 98 :
/**
- 99 :
* @memberof Tools.Trends
- 100 :
* @instance
- 101 :
* @property {query}
- 102 :
*/
- 103 :
query: undefined,
- 104 :
- 105 :
/**
- 106 :
* @memberof Tools.Trends
- 107 :
* @instance
- 108 :
* @property {withDistributions}
- 109 :
* @default
- 110 :
*/
- 111 :
withDistributions: 'relative',
- 112 :
- 113 :
/**
- 114 :
* @memberof Tools.Trends
- 115 :
* @instance
- 116 :
* @property {bins}
- 117 :
*
- 118 :
* TODO verify this:
- 119 :
*
- 120 :
* The default value will depend on the nature of the corpus:
- 121 :
*
- 122 :
* - corpus has one document: the default number of bins is 10
- 123 :
* - corpus has multiple documents:
- 124 :
* - corpus has up to 100 documents: the default number is the size of the corpus
- 125 :
* - corpus has more than 1000 documents: the default number is 100
- 126 :
*/
- 127 :
bins: 10,
- 128 :
- 129 :
/**
- 130 :
* @memberof Tools.Trends
- 131 :
* @instance
- 132 :
* @property {docIndex}
- 133 :
*/
- 134 :
docIndex: undefined,
- 135 :
- 136 :
/**
- 137 :
* @memberof Tools.Trends
- 138 :
* @instance
- 139 :
* @property {docId}
- 140 :
*/
- 141 :
docId: undefined,
- 142 :
- 143 :
/**
- 144 :
* @memberof Tools.Trends
- 145 :
* @instance
- 146 :
* @property {String} mode Force the mode to be either "corpus" (distribution of terms across documents) or "document" (distribution of terms within a document); usually this is correctly set by default according to whether the corpus has one document ("document") or more than one ("corpus").
- 147 :
* @default
- 148 :
*/
- 149 :
mode: "corpus",
- 150 :
- 151 :
/**
- 152 :
* @memberof Tools.Trends
- 153 :
* @instance
- 154 :
* @property {String} chartType The of chart to display: Options are: 'area', 'bar', 'line', 'stacked', and 'barline'.
- 155 :
* @default
- 156 :
*/
- 157 :
chartType: 'barline',
- 158 :
- 159 :
/**
- 160 :
* @memberof Tools.Trends
- 161 :
* @instance
- 162 :
* @property {Boolean} labels Whether to show term labels.
- 163 :
* @default
- 164 :
*/
- 165 :
labels: false
- 166 :
},
- 167 :
glyph: 'xf201@FontAwesome'
- 168 :
},
- 169 :
- 170 :
layout: 'fit',
- 171 :
documentTermsStore: undefined,
- 172 :
//segments: undefined,
- 173 :
- 174 :
/**
- 175 :
* @private
- 176 :
*/
- 177 :
constructor: function(config) {
- 178 :
this.callParent(arguments);
- 179 :
this.mixins['Voyant.panel.Panel'].constructor.apply(this, arguments);
- 180 :
},
- 181 :
- 182 :
initComponent: function() {
- 183 :
this.mixins['Voyant.util.Api'].constructor.apply(this, arguments); // we need api
- 184 :
Ext.apply(this, {
- 185 :
title: this.localize('title'),
- 186 :
dockedItems: [{
- 187 :
dock: 'bottom',
- 188 :
xtype: 'toolbar',
- 189 :
overflowHandler: 'scroller',
- 190 :
items: [{
- 191 :
xtype: 'querysearchfield'
- 192 :
},{
- 193 :
itemId: 'reset',
- 194 :
text: this.localize("reset"),
- 195 :
tooltip: this.localize("resetTip"),
- 196 :
handler: function(btn) {
- 197 :
this.setApiParams({
- 198 :
docIndex: undefined,
- 199 :
mode: undefined,
- 200 :
query: undefined
- 201 :
});
- 202 :
this.loadCorpusTerms();
- 203 :
},
- 204 :
scope: this
- 205 :
},{
- 206 :
text: this.localize('display'),
- 207 :
tooltip: this.localize('displayTip'),
- 208 :
glyph: 'xf013@FontAwesome',
- 209 :
menu: {
- 210 :
listeners: {
- 211 :
afterrender: function(menu) {
- 212 :
var val = this.getApiParam("chartType");
- 213 :
menu.items.each(function(item) {
- 214 :
if (item.getItemId()==val) {
- 215 :
item.addCls(item.activeCls);
- 216 :
}
- 217 :
})
- 218 :
},
- 219 :
scope: this
- 220 :
},
- 221 :
defaults: {
- 222 :
xtype: 'menuitem',
- 223 :
handler: function(item, checked) {
- 224 :
if (item.xtype=="menucheckitem") { // labels
- 225 :
this.setApiParam("labels", item.checked);
- 226 :
} else {
- 227 :
this.setApiParam("chartType", item.getItemId());
- 228 :
}
- 229 :
this.loadCorpusTerms();
- 230 :
},
- 231 :
scope: this
- 232 :
},
- 233 :
items: [{
- 234 :
xtype: 'menucheckitem',
- 235 :
text: this.localize('labels'),
- 236 :
tooltip: this.localize('labelsTip'),
- 237 :
checked: this.getApiParam("labels")===true || this.getApiParam("labels")=="true"
- 238 :
},'-',{
- 239 :
itemId: 'area',
- 240 :
text: this.localize('area'),
- 241 :
tooltip: this.localize('areaTip'),
- 242 :
glyph: 'xe76b@Sencha-Examples'
- 243 :
},{
- 244 :
itemId: 'bar',
- 245 :
text: this.localize('bar'),
- 246 :
tooltip: this.localize('barTip'),
- 247 :
glyph: 'xe768@Sencha-Examples'
- 248 :
},{
- 249 :
itemId: 'line',
- 250 :
text: this.localize('line'),
- 251 :
tooltip: this.localize('lineTip'),
- 252 :
glyph: 'xe773@Sencha-Examples'
- 253 :
},{
- 254 :
itemId: 'stacked',
- 255 :
text: this.localize('stacked'),
- 256 :
tooltip: this.localize('stackedTip'),
- 257 :
glyph: 'xe6c8@Sencha-Examples'
- 258 :
},{
- 259 :
itemId: 'barline',
- 260 :
text: this.localize('barline'),
- 261 :
tooltip: this.localize('barlineTip'),
- 262 :
glyph: 'xe779@Sencha-Examples'
- 263 :
}]
- 264 :
}
- 265 :
}]
- 266 :
}]
- 267 :
});
- 268 :
this.callParent(arguments);
- 269 :
},
- 270 :
- 271 :
listeners: {
- 272 :
loadedCorpus: function(src, corpus) {
- 273 :
this.loadCorpusTerms();
- 274 :
},
- 275 :
termsClicked: function(src, terms) {
- 276 :
var queryTerms = [];
- 277 :
terms.forEach(function(term) {
- 278 :
if (Ext.isString(term)) {queryTerms.push(term);}
- 279 :
else if (term.term) {queryTerms.push(term.term);}
- 280 :
else if (term.getTerm) {queryTerms.push(term.getTerm());}
- 281 :
- 282 :
});
- 283 :
this.setApiParam("query", queryTerms && queryTerms.length>0 ? queryTerms : undefined);
- 284 :
this.loadCorpusTerms();
- 285 :
},
- 286 :
corpusTermsClicked: function(src, terms) {
- 287 :
this.setApiParam("query", terms.map(function(term) {return term.getTerm()}));
- 288 :
this.loadCorpusTerms();
- 289 :
},
- 290 :
documentSelected: function(src, document) {
- 291 :
this.setApiParam("docIndex", this.getCorpus().getDocument(document).getIndex());
- 292 :
this.loadDocumentTerms();
- 293 :
},
- 294 :
documentsClicked: function(src, documents) {
- 295 :
if (documents.length==1) {
- 296 :
this.fireEvent("documentSelected", this, documents[0])
- 297 :
}
- 298 :
},
- 299 :
query: function(src, query) {
- 300 :
this.fireEvent("termsClicked", this, query)
- 301 :
},
- 302 :
entitiesClicked: function(src, ents) {
- 303 :
this.fireEvent("termsClicked", this, ents);
- 304 :
}
- 305 :
},
- 306 :
- 307 :
loadCorpusTerms: function(params) {
- 308 :
// if our corpus only has one document or if we have docIndex defined
- 309 :
if (
- 310 :
this.getCorpus().getDocumentsCount()<2 || // only one document
- 311 :
this.getApiParam("mode")=="document" || // in document mode
- 312 :
Array.from(this.getApiParam("docIndex") || "").length>0 // we have a docIndex defined
- 313 :
) {
- 314 :
return this.loadDocumentTerms();
- 315 :
}
- 316 :
if (!this.getApiParam("query")) {
- 317 :
this.getCorpus().getCorpusTerms().load({
- 318 :
params: {
- 319 :
limit: this.getApiParam('limit'),
- 320 :
stopList: this.getApiParam("stopList")
- 321 :
},
- 322 :
callback: function(records, operation, success) {
- 323 :
if (records.length==0) {
- 324 :
if (operation && operation.error) {
- 325 :
this.showError(this.localize("noResults")+"<p style='color: red'>"+operation.error+"</p>")
- 326 :
} else {
- 327 :
this.showError(this.localize("noResults"))
- 328 :
}
- 329 :
} else {
- 330 :
this.setApiParam("query", records.map(function(r) {return r.getTerm()}))
- 331 :
this.loadCorpusTerms();
- 332 :
}
- 333 :
},
- 334 :
scope: this
- 335 :
})
- 336 :
return;
- 337 :
}
- 338 :
params = params || {};
- 339 :
//this.segments.hide();
- 340 :
var withDistributions = this.getApiParam("withDistributions");
- 341 :
Ext.applyIf(params, {
- 342 :
bins: this.getCorpus().getDocumentsCount(),
- 343 :
limit: 100, // should have query, so no limit
- 344 :
stopList: "" // automatic queries should be stopped already
- 345 :
});
- 346 :
var docLabels = [];
- 347 :
var docLabelsFull = [];
- 348 :
this.getCorpus().each(function(doc) {
- 349 :
docLabels.push(doc.getTinyTitle());
- 350 :
docLabelsFull.push(doc.getTitle());
- 351 :
})
- 352 :
if (Ext.Array.unique(docLabels).length<docLabels.length) { // we have duplicates, add index
- 353 :
docLabels = docLabels.map(function(doc,i) {return (i+1)+")"+ doc})
- 354 :
}
- 355 :
Ext.applyIf(params, this.getApiParams());
- 356 :
this.getCorpus().getCorpusTerms().load({
- 357 :
params: params,
- 358 :
callback: function(records, operation, success) {
- 359 :
var data = [], series = [], chartType = this.getApiParam('chartType');
- 360 :
records.forEach(function(record, index) {
- 361 :
var term = record.get('term');
- 362 :
var color = this.getApplication().getColorForTerm(term, true);
- 363 :
record.get('distributions').forEach(function(r, i) {
- 364 :
if (!data[i]) {
- 365 :
data[i] = {"index": docLabels[i], "docTitle": docLabelsFull[i]};
- 366 :
}
- 367 :
data[i]["_"+index] = withDistributions=='relative' ? r.toFixed(7) : r;
- 368 :
data[i]["term"+index] = term;
- 369 :
}, this);
- 370 :
- 371 :
- 372 :
if (chartType!='bar') {
- 373 :
var kinds = chartType=='barline' ? ["bar","line"] : [chartType];
- 374 :
kinds.forEach(function(kind) {
- 375 :
series.push({
- 376 :
type: kind=='stacked' ? 'bar' : kind,
- 377 :
title: term,
- 378 :
xField: 'index',
- 379 :
yField: '_'+index,
- 380 :
term: term,
- 381 :
colors: [color],
- 382 :
label: chartType=='barline' && kind=='bar' ? {
- 383 :
display: 'none'
- 384 :
} : {
- 385 :
field: "term"+index
- 386 :
}
- 387 :
})
- 388 :
}, this);
- 389 :
}
- 390 :
}, this);
- 391 :
- 392 :
var terms = records.map(function(r) {return r.getTerm()})
- 393 :
var colors = terms.map(function(term) {
- 394 :
return this.getApplication().getColorForTerm(term, true);
- 395 :
}, this);
- 396 :
if (chartType=='bar') {
- 397 :
series.push({
- 398 :
type:'bar',
- 399 :
title: terms,
- 400 :
colors: colors,
- 401 :
xField: 'index',
- 402 :
yField: data.length>0 ? Object.keys(data[0]).filter(function(field) {return field.charAt(0)=="_"}) : undefined,
- 403 :
label: {
- 404 :
field: records.map(function(r,i) {return "term"+i;})
- 405 :
}
- 406 :
})
- 407 :
}
- 408 :
- 409 :
var store = Ext.create('Ext.data.JsonStore', {
- 410 :
fields: data.length>0 ? Object.keys(data[0]) : undefined,
- 411 :
data: data
- 412 :
});
- 413 :
- 414 :
this.buildChart({
- 415 :
store: store,
- 416 :
series: series,
- 417 :
axes: [{
- 418 :
type: 'numeric',
- 419 :
position: 'left',
- 420 :
increment: 1,
- 421 :
title: {
- 422 :
text: this.localize(this.getApiParam("withDistributions")+"Title")
- 423 :
}
- 424 :
},{
- 425 :
type: 'category',
- 426 :
position: 'bottom',
- 427 :
title: {
- 428 :
text: this.localize("corpusTitle")
- 429 :
}
- 430 :
- 431 :
}]
- 432 :
})
- 433 :
},
- 434 :
scope: this
- 435 :
});
- 436 :
- 437 :
},
- 438 :
- 439 :
loadDocumentTerms: function(params) {
- 440 :
if (!this.getApiParam("query")) {
- 441 :
this.getCorpus().getCorpusTerms().load({
- 442 :
params: {
- 443 :
limit: this.getApiParam('limit'),
- 444 :
stopList: this.getApiParam("stopList")
- 445 :
},
- 446 :
callback: function(records, operation, success) {
- 447 :
this.setApiParam("query", records.map(function(r) {return r.getTerm()}))
- 448 :
this.loadDocumentTerms();
- 449 :
},
- 450 :
scope: this
- 451 :
})
- 452 :
return;
- 453 :
}
- 454 :
//this.segments.show();
- 455 :
this.setApiParam("mode", "document"); // just to be sure
- 456 :
params = params || {};
- 457 :
var withDistributions = this.getApiParam("withDistributions");
- 458 :
Ext.applyIf(params, {
- 459 :
limit: 0, // always have query, so no limit, no stopList
- 460 :
sort: 'termasc',
- 461 :
stopList: undefined
- 462 :
});
- 463 :
var singleDoc;
- 464 :
if (this.getCorpus().getDocumentsCount()==1) {
- 465 :
singleDoc=this.getCorpus().getDocument(0)
- 466 :
}
- 467 :
else {
- 468 :
singleDoc=this.getCorpus().getDocument(this.getApiParam("docIndex"))
- 469 :
}
- 470 :
Ext.applyIf(params, this.getApiParams());
- 471 :
- 472 :
this.getCorpus().getDocumentTerms().load({
- 473 :
params: params,
- 474 :
callback: function(records, operation, success) {
- 475 :
var data = [], series = [], chartType = this.getApiParam('chartType');
- 476 :
if (!singleDoc) { // legend is easier to read if sorted by term then doc
- 477 :
records.sort(function(a,b) {
- 478 :
if (a.getTerm()==b.getTerm()) {
- 479 :
return a.getDocIndex() - b.getDocIndex()
- 480 :
}
- 481 :
return a.getTerm().localeCompare(b.getTerm())
- 482 :
})
- 483 :
}
- 484 :
records.forEach(function(record, index) {
- 485 :
var term = record.get('term');
- 486 :
var docIndex = record.get('docIndex');
- 487 :
var color = singleDoc ? this.getApplication().getColorForTerm(term, true) : this.getApplication().getColor(docIndex, true);
- 488 :
record.get('distributions').forEach(function(r, i) {
- 489 :
if (!data[i]) {
- 490 :
data[i] = {index: (i+1)};
- 491 :
}
- 492 :
data[i]["_"+index+"_"+docIndex] = withDistributions=='relative' ? r.toFixed(7) : r;
- 493 :
data[i]["term"+index] = term;
- 494 :
}, this);
- 495 :
- 496 :
if (chartType!='bar') {
- 497 :
var kinds = chartType=='barline' ? ["bar","line"] : [chartType];
- 498 :
kinds.forEach(function(kind) {
- 499 :
series.push({
- 500 :
type: kind=='stacked' ? 'bar' : kind,
- 501 :
title: singleDoc ? term : (docIndex+1)+") "+term,
- 502 :
xField: 'index',
- 503 :
yField: '_'+index+"_"+docIndex,
- 504 :
term: term,
- 505 :
colors: [color],
- 506 :
label: chartType=='barline' && kind=='bar' ? {
- 507 :
display: 'none'
- 508 :
} : {
- 509 :
field: "term"+index
- 510 :
}
- 511 :
})
- 512 :
}, this);
- 513 :
}
- 514 :
- 515 :
}, this);
- 516 :
- 517 :
if (chartType=='bar') {
- 518 :
var isOneTerm = Ext.Array.unique(records.map(function(r) {return r.getTerm()})).length === 1;
- 519 :
var terms = records.map(function(r) {return (1+r.get("docIndex")) +") "+r.getTerm()})
- 520 :
var colors = records.map(function(r) {
- 521 :
return isOneTerm ? this.getApplication().getColor(r.get("docIndex"), true) : this.getApplication().getColorForTerm(r.getTerm(), true);
- 522 :
}, this);
- 523 :
- 524 :
series.push({
- 525 :
type:'bar',
- 526 :
title: terms.length>0 ? terms : this.localize("noResults"),
- 527 :
colors: colors,
- 528 :
xField: 'index',
- 529 :
yField: data.length>0 ? Object.keys(data[0]).filter(function(field) {return field.charAt(0)=="_"}) : undefined,
- 530 :
label: {
- 531 :
field: terms
- 532 :
}
- 533 :
})
- 534 :
}
- 535 :
- 536 :
- 537 :
var store = Ext.create('Ext.data.JsonStore', {
- 538 :
fields: data.length>0 ? Object.keys(data[0]) : undefined,
- 539 :
data: data
- 540 :
});
- 541 :
- 542 :
this.buildChart({
- 543 :
store: store,
- 544 :
series: series,
- 545 :
axes: [{
- 546 :
type: 'numeric',
- 547 :
position: 'left',
- 548 :
title: {
- 549 :
text: this.localize(this.getApiParam("withDistributions")+"Title")
- 550 :
}
- 551 :
},{
- 552 :
type: 'category',
- 553 :
position: 'bottom',
- 554 :
title: {
- 555 :
text: this.localize("segmentsTitle") + (singleDoc ? " ("+singleDoc.getTitle()+")" : "")
- 556 :
}
- 557 :
- 558 :
}]
- 559 :
})
- 560 :
},
- 561 :
scope: this
- 562 :
});
- 563 :
- 564 :
},
- 565 :
- 566 :
getItemToolTip: function (toolTip, record, ctx) {
- 567 :
var parts = ctx.field.split("_"),
- 568 :
docIndex = parts.length==2 ? ctx.index : parts[2],
- 569 :
pos = parseInt(parts[1]),
- 570 :
title = ctx.series.getTitle(),
- 571 :
term = Ext.isArray(title) ? title[pos] : title,
- 572 :
colors = ctx.series.getColors(),
- 573 :
color = colors.length==1 ? colors[0] : colors[pos];
- 574 :
var html = "<span class='x-legend-item-marker' style='background:"+color+
- 575 :
"; left: 2px;'></span> <span style='padding-left: 1.2em; font-weight: bold;'>"+
- 576 :
term+"</span>: "+record.get(ctx.field)+
- 577 :
"<br/><i>"+this.getCorpus().getDocument(docIndex).getShortTitle()+"</i>"
- 578 :
if (this.getApiParam("mode")=="corpus") {
- 579 :
html+="<div style='font-size: smaller'>"+this.localize('dblClickItem')
- 580 :
} else {
- 581 :
html+="<br/>"+this.localize('segment')+" "+(ctx.index+1)
- 582 :
}
- 583 :
toolTip.setHtml(html);
- 584 :
},
- 585 :
- 586 :
buildChart: function(config) {
- 587 :
var chartType = this.getApiParam('chartType'), labels = false;
- 588 :
if (this.getApiParam("labels")===true || this.getApiParam("labels")=="true") {labels=true}
- 589 :
- 590 :
Ext.applyIf(config, {
- 591 :
cls: this.getApiParam("mode")
- 592 :
});
- 593 :
- 594 :
config.series.forEach(function(serie) {
- 595 :
Ext.applyIf(serie, {
- 596 :
stacked: serie.type=='bar' ? false : true,
- 597 :
showInLegend: chartType=='barline' && serie.type=='line' ? false : true,
- 598 :
smooth: true,
- 599 :
showMarkers: serie.type=='bar' ? false : true,
- 600 :
marker: chartType=='barline' && serie.type=='line' ? null : {
- 601 :
type: 'circle',
- 602 :
radius: 2
- 603 :
},
- 604 :
style: {
- 605 :
lineWidth: 1,
- 606 :
fillOpacity: chartType=='barline' && serie.type=='bar' ? .01 : 1,
- 607 :
strokeOpacity: chartType=='barline' && serie.type=='bar' ? .1 : 1
- 608 :
},
- 609 :
highlight: true,
- 610 :
highlightCfg: {
- 611 :
scaling: serie.type=="bar" ? 1.1 : 2
- 612 :
},
- 613 :
label: {
- 614 :
// display: 'none'
- 615 :
},
- 616 :
tooltip: {
- 617 :
trackMouse: true,
- 618 :
renderer: this.getItemToolTip,
- 619 :
scope: this
- 620 :
},
- 621 :
listeners: {
- 622 :
itemclick: function(chart,item,event,eOpts ) {
- 623 :
if (this.clickTimer) {clearTimeout(this.clickTimer);}
- 624 :
if (this.blockClick) {return;} // set by dblclick to avoid menu disappearing
- 625 :
this.blockClick = true // block other clicks within a sec
- 626 :
Ext.defer(function() {
- 627 :
this.blockClick = false;
- 628 :
}, 1000, this);
- 629 :
if (this.getApiParam("mode")=="document") {
- 630 :
var parts = item.field.split("_"),
- 631 :
docIndex = parseInt(parts[2]),
- 632 :
doc = this.getCorpus().getDocument(docIndex),
- 633 :
tokens = doc.get('tokensCount-lexical'),
- 634 :
position = parseInt(item.index * tokens / parseInt(this.getApiParam("bins")))
- 635 :
this.dispatchEvent("documentIndexTermsClicked", this, [{
- 636 :
term: item.series.term,
- 637 :
docIndex: docIndex,
- 638 :
position: position
- 639 :
}]);
- 640 :
} else {
- 641 :
if (this.clickTimer) {clearTimeout(this.clickTimer);}
- 642 :
var me = this;
- 643 :
this.clickTimer = setTimeout(function() {
- 644 :
me.dispatchEvent("documentIndexTermsClicked", me, [{
- 645 :
term: item.series.term,
- 646 :
docIndex: item.index
- 647 :
}]);
- 648 :
}, 300)
- 649 :
- 650 :
}
- 651 :
},
- 652 :
itemdblclick: function(chart,item,event,eOpts ) {
- 653 :
if (this.clickTimer) {clearTimeout(this.clickTimer);}
- 654 :
// block future single clicks to allow menu to appear
- 655 :
this.blockClick = true
- 656 :
Ext.defer(function() {
- 657 :
this.blockClick = false;
- 658 :
}, 1000, this);
- 659 :
// block future clicks
- 660 :
if (this.getApiParam("mode")!="document") {
- 661 :
var m = Ext.create('Ext.menu.Menu', {
- 662 :
items: [{
- 663 :
text: this.localize("drillTerm"),
- 664 :
tooltip: this.localize("drillTermTip"),
- 665 :
// glyph: 'xf02d@FontAwesome',
- 666 :
handler: function() {
- 667 :
this.setApiParams({
- 668 :
mode: 'document',
- 669 :
query: item.series.term
- 670 :
});
- 671 :
this.loadDocumentTerms();
- 672 :
},
- 673 :
scope: this
- 674 :
},{
- 675 :
text: this.localize("drillDocument"),
- 676 :
tooltip: this.localize("drillDocumentTip"),
- 677 :
// glyph: 'xf02d@FontAwesome',
- 678 :
handler: function() {
- 679 :
this.setApiParams({
- 680 :
mode: 'document',
- 681 :
docIndex: item.index
- 682 :
});
- 683 :
this.loadDocumentTerms();
- 684 :
},
- 685 :
scope: this
- 686 :
}],
- 687 :
listeners: {
- 688 :
hide: function(m) {
- 689 :
// defer hiding otherwise click handler not called
- 690 :
Ext.defer(function() {this.destroy()}, 200, m)
- 691 :
},
- 692 :
scope: this
- 693 :
}
- 694 :
}).showAt(event.pageX, event.pageY)
- 695 :
}
- 696 :
},
- 697 :
scope: this
- 698 :
}
- 699 :
})
- 700 :
Ext.applyIf(serie.label, {
- 701 :
display: 'over',
- 702 :
field: 'index',
- 703 :
fontSize: 11,
- 704 :
translateY: chartType=='line' ? 9 : undefined
- 705 :
});
- 706 :
if (!labels) {serie.label.display="none";} // hide label
- 707 :
}, this)
- 708 :
Ext.applyIf(config, {
- 709 :
animation: true,
- 710 :
plugins: {
- 711 :
ptype: 'chartitemevents',
- 712 :
moveEvents: true
- 713 :
},
- 714 :
legend: {
- 715 :
docked:'top',
- 716 :
listeners: {
- 717 :
itemclick: function(legend, record, dom, index) {
- 718 :
// make sure to hide related series
- 719 :
if (legend.getStore().getCount()<legend.chart.series.length && this.getApiParam("chartType")=="barline") {
- 720 :
var term = record.get("name"), disabled = record.get("disabled");
- 721 :
legend.chart.series.forEach(function(serie) {
- 722 :
if (serie.getTitle()==term) {
- 723 :
serie.setHidden(disabled);
- 724 :
- 725 :
}
- 726 :
})
- 727 :
legend.chart.redraw(); // not working?
- 728 :
}
- 729 :
},
- 730 :
scope: this
- 731 :
}
- 732 :
},
- 733 :
interactions: ['itemhighlight','crosszoom'],
- 734 :
listeners: {}
- 735 :
});
- 736 :
Ext.applyIf(config.listeners, {
- 737 :
itemhighlightchange: function (chart, item) {
- 738 :
chart.el.dom.style.cursor = item ? 'pointer' : '';
- 739 :
},
- 740 :
afterrender : function() {
- 741 :
return // TODO: this seems to cause problems, perhaps not destroying properly?
- 742 :
Ext.defer(function() { // seem to need to defer
- 743 :
Ext.tip.QuickTipManager.register({
- 744 :
target: this.getTargetEl().down(".x-legend-container"),
- 745 :
text: this.localize("toggleTip")
- 746 :
});
- 747 :
},1, this)
- 748 :
- 749 :
},
- 750 :
scope: this
- 751 :
})
- 752 :
config.axes.forEach(function(axis) {
- 753 :
Ext.applyIf(axis, {
- 754 :
title: {},
- 755 :
label: {}
- 756 :
})
- 757 :
Ext.applyIf(axis.title, {scaling: .75});
- 758 :
Ext.applyIf(axis.label, {scaling: .75});
- 759 :
if (axis.type=='category') {
- 760 :
var titles = "";
- 761 :
config.store.each(function(r) {titles+=r.get("index")})
- 762 :
if (titles.length>this.getTargetEl().getWidth()/9) {
- 763 :
Ext.applyIf(axis.label, {rotate: {degrees:-30}});
- 764 :
}
- 765 :
Ext.applyIf(axis, {
- 766 :
labelInSpan: true
- 767 :
})
- 768 :
}
- 769 :
}, this)
- 770 :
- 771 :
// remove existing chart
- 772 :
this.query('chart').forEach(function(chart) {this.remove(chart, true);}, this);
- 773 :
- 774 :
// create new chart
- 775 :
var chart = Ext.create("Ext.chart.CartesianChart", config);
- 776 :
this.add(chart);
- 777 :
},
- 778 :
- 779 :
reloadFromChart: function() {
- 780 :
var chart = this.down('chart');
- 781 :
if (chart) {
- 782 :
var terms = [];
- 783 :
chart.series.forEach(function(serie) {
- 784 :
terms.push(serie.getTitle());
- 785 :
});
- 786 :
this.fireEvent("termsClicked", this, terms);
- 787 :
}
- 788 :
},
- 789 :
- 790 :
getExtraDataExportItems: function() {
- 791 :
return [
- 792 :
{
- 793 :
name: 'export',
- 794 :
inputValue: 'dataAsTsv',
- 795 :
boxLabel: this.localize('exportGridCurrentTsv')
- 796 :
}
- 797 :
]
- 798 :
},
- 799 :
- 800 :
exportDataAsTsv: function(panel, form) {
- 801 :
var value = '';
- 802 :
- 803 :
var chart = panel.down('chart');
- 804 :
var store = chart.getStore();
- 805 :
var firstModel = store.first();
- 806 :
var data = firstModel.getData();
- 807 :
- 808 :
var fields = ['Index'];
- 809 :
- 810 :
var termKeys = Object.keys(data).filter(function(key) {
- 811 :
return key.indexOf('term') === 0;
- 812 :
}).sort();
- 813 :
var terms = [];
- 814 :
termKeys.forEach(function(termKey) {
- 815 :
terms.push(data[termKey]);
- 816 :
});
- 817 :
- 818 :
fields = fields.concat(terms);
- 819 :
value += fields.join("\t")+"\n";
- 820 :
- 821 :
var valueKeys = Object.keys(data).filter(function(key) {
- 822 :
return key.indexOf('_') === 0;
- 823 :
}).sort();
- 824 :
- 825 :
store.each(function(model) {
- 826 :
data = model.getData();
- 827 :
var entry = [];
- 828 :
if (data['docTitle'] !== undefined) {
- 829 :
entry.push(data['docTitle']);
- 830 :
} else {
- 831 :
entry.push(data['index']);
- 832 :
}
- 833 :
valueKeys.forEach(function(valueKey) {
- 834 :
entry.push(data[valueKey]);
- 835 :
});
- 836 :
value += entry.join("\t")+"\n";
- 837 :
}, panel);
- 838 :
- 839 :
Ext.Msg.show({
- 840 :
title: panel.localize('exportDataTitle'),
- 841 :
message: panel.localize('exportDataTsvMessage'),
- 842 :
buttons: Ext.Msg.OK,
- 843 :
icon: Ext.Msg.INFO,
- 844 :
prompt: true,
- 845 :
multiline: true,
- 846 :
value: value
- 847 :
});
- 848 :
}
- 849 :
});