1. 1 : // assuming Knots library is loaded by containing page (via voyant.jsp)
  2. 2 : /**
  3. 3 : * Knots is a creative visualization that represents terms in a single document as a series of twisted lines.
  4. 4 : *
  5. 5 : * @example
  6. 6 : *
  7. 7 : * let config = {
  8. 8 : * "audio": false,
  9. 9 : * "docId": null,
  10. 10 : * "query": null,
  11. 11 : * "stopList": "auto"
  12. 12 : * };
  13. 13 : *
  14. 14 : * loadCorpus("austen").tool("knots", config);
  15. 15 : *
  16. 16 : * @class Knots
  17. 17 : * @tutorial knots
  18. 18 : * @memberof Tools
  19. 19 : */
  20. 20 : Ext.define('Voyant.panel.Knots', {
  21. 21 : extend: 'Ext.panel.Panel',
  22. 22 : mixins: ['Voyant.panel.Panel'],
  23. 23 : alias: 'widget.knots',
  24. 24 : statics: {
  25. 25 : i18n: {
  26. 26 : },
  27. 27 : api: {
  28. 28 : /**
  29. 29 : * @memberof Tools.Knots
  30. 30 : * @instance
  31. 31 : * @property {query}
  32. 32 : */
  33. 33 : query: null,
  34. 34 : /**
  35. 35 : * @memberof Tools.Knots
  36. 36 : * @instance
  37. 37 : * @property {stopList}
  38. 38 : * @default
  39. 39 : */
  40. 40 : stopList: 'auto',
  41. 41 :
  42. 42 : /**
  43. 43 : * @memberof Tools.Knots
  44. 44 : * @instance
  45. 45 : * @property {docId}
  46. 46 : */
  47. 47 : docId: undefined,
  48. 48 :
  49. 49 : /**
  50. 50 : * @memberof Tools.Knots
  51. 51 : * @instance
  52. 52 : * @property {Boolean} audio Whether or not to play audio during the visualization.
  53. 53 : * @default
  54. 54 : */
  55. 55 : audio: false
  56. 56 : },
  57. 57 : glyph: 'xf06e@FontAwesome'
  58. 58 : },
  59. 59 : config: {
  60. 60 : knots: undefined,
  61. 61 : termStore: undefined,
  62. 62 : docTermStore: undefined,
  63. 63 : tokensStore: undefined,
  64. 64 : options: [{xtype: 'stoplistoption'},{xtype: 'categoriesoption'},{xtype: 'colorpaletteoption'}],
  65. 65 : refreshInterval: 100,
  66. 66 : startAngle: 315,
  67. 67 : angleIncrement: 15,
  68. 68 : currentTerm: undefined
  69. 69 : },
  70. 70 :
  71. 71 : termTpl: new Ext.XTemplate(
  72. 72 : '<tpl for=".">',
  73. 73 : '<div class="term" style="color: rgb({color});float: left;padding: 3px;margin: 2px;">{term}</div>',
  74. 74 : '</tpl>'
  75. 75 : ),
  76. 76 :
  77. 77 : constructor: function() {
  78. 78 : this.callParent(arguments);
  79. 79 : this.mixins['Voyant.panel.Panel'].constructor.apply(this, arguments);
  80. 80 :
  81. 81 : this.on('loadedCorpus', function(src, corpus) {
  82. 82 : var firstDoc = corpus.getDocument(0);
  83. 83 : var pDoc = this.processDocument(firstDoc);
  84. 84 : this.getKnots().setCurrentDoc(pDoc);
  85. 85 :
  86. 86 : this.setApiParams({docId: firstDoc.getId()});
  87. 87 : this.getDocTermStore().getProxy().setExtraParam('corpus', corpus.getId());
  88. 88 : this.getTokensStore().setCorpus(corpus);
  89. 89 : this.getDocTermStore().load({params: {
  90. 90 : limit: 5,
  91. 91 : stopList: this.getApiParams('stopList')
  92. 92 : }});
  93. 93 : }, this);
  94. 94 :
  95. 95 : this.on('activate', function() { // load after tab activate (if we're in a tab panel)
  96. 96 : if (this.getCorpus()) {
  97. 97 : Ext.Function.defer(function() {
  98. 98 : this.getDocTermStore().load({params: {
  99. 99 : limit: 5,
  100. 100 : stopList: this.getApiParams('stopList')
  101. 101 : }});
  102. 102 : }, 100, this);
  103. 103 : }
  104. 104 : }, this);
  105. 105 :
  106. 106 : this.on('query', function(src, query) {
  107. 107 : if (query !== undefined && query != '') {
  108. 108 : this.getDocTermsFromQuery(query);
  109. 109 : }
  110. 110 : }, this);
  111. 111 :
  112. 112 : this.on('documentSelected', function(src, doc) {
  113. 113 :
  114. 114 : var document = this.getCorpus().getDocument(doc)
  115. 115 : this.setApiParam('docId', document.getId());
  116. 116 :
  117. 117 : var terms = this.getKnots().currentDoc.terms;
  118. 118 : var termsToKeep = [];
  119. 119 : for (var t in terms) {
  120. 120 : termsToKeep.push(t);
  121. 121 : }
  122. 122 :
  123. 123 : // this.getTermStore().removeAll();
  124. 124 : this.setApiParams({query: termsToKeep});
  125. 125 :
  126. 126 : var limit = termsToKeep.length;
  127. 127 : if (limit === 0) {
  128. 128 : limit = 5;
  129. 129 : }
  130. 130 :
  131. 131 : this.getKnots().setCurrentDoc(this.processDocument(document));
  132. 132 :
  133. 133 : this.getDocTermStore().load({params: {
  134. 134 : query: termsToKeep,
  135. 135 : limit: limit,
  136. 136 : stopList: this.getApiParams('stopList')
  137. 137 : }});
  138. 138 : }, this);
  139. 139 :
  140. 140 : this.on('termsClicked', function(src, terms) {
  141. 141 : var queryTerms = [];
  142. 142 : terms.forEach(function(term) {
  143. 143 : if (Ext.isString(term)) {queryTerms.push(term);}
  144. 144 : else if (term.term) {queryTerms.push(term.term);}
  145. 145 : else if (term.getTerm) {queryTerms.push(term.getTerm());}
  146. 146 : });
  147. 147 : if (queryTerms.length > 0) {
  148. 148 : this.getDocTermsFromQuery(queryTerms);
  149. 149 : }
  150. 150 : }, this);
  151. 151 :
  152. 152 : this.on('corpusTermsClicked', function(src, terms) {
  153. 153 : var queryTerms = [];
  154. 154 : terms.forEach(function(term) {
  155. 155 : if (term.getTerm()) {queryTerms.push(term.getTerm());}
  156. 156 : });
  157. 157 : this.getDocTermsFromQuery(queryTerms);
  158. 158 : }, this);
  159. 159 :
  160. 160 : this.on('documentTermsClicked', function(src, terms) {
  161. 161 : var queryTerms = [];
  162. 162 : terms.forEach(function(term) {
  163. 163 : if (term.getTerm()) {queryTerms.push(term.getTerm());}
  164. 164 : });
  165. 165 : this.getDocTermsFromQuery(queryTerms);
  166. 166 : }, this);
  167. 167 : },
  168. 168 :
  169. 169 : initComponent: function() {
  170. 170 : this.setTermStore(Ext.create('Ext.data.ArrayStore', {
  171. 171 : fields: ['term', 'color']
  172. 172 : }));
  173. 173 :
  174. 174 : this.setDocTermStore(Ext.create("Ext.data.Store", {
  175. 175 : model: "Voyant.data.model.DocumentTerm",
  176. 176 : autoLoad: false,
  177. 177 : remoteSort: false,
  178. 178 : proxy: {
  179. 179 : type: 'ajax',
  180. 180 : url: Voyant.application.getTromboneUrl(),
  181. 181 : extraParams: {
  182. 182 : tool: 'corpus.DocumentTerms',
  183. 183 : withDistributions: 'raw',
  184. 184 : withPositions: true
  185. 185 : },
  186. 186 : reader: {
  187. 187 : type: 'json',
  188. 188 : rootProperty: 'documentTerms.terms',
  189. 189 : totalProperty: 'documentTerms.total'
  190. 190 : },
  191. 191 : simpleSortMode: true
  192. 192 : },
  193. 193 : listeners: {
  194. 194 : beforeload: function(store) {
  195. 195 : store.getProxy().setExtraParam('docId', this.getApiParam('docId'));
  196. 196 : },
  197. 197 : load: function(store, records, successful, options) {
  198. 198 : var termObj = {};
  199. 199 : if (records && records.length>0) {
  200. 200 : records.forEach(function(record) {
  201. 201 : var termData = this.processTerms(record);
  202. 202 : var docId = record.get('docId');
  203. 203 : var term = record.get('term');
  204. 204 : termObj[term] = termData;
  205. 205 : }, this);
  206. 206 : this.getKnots().addTerms(termObj);
  207. 207 : this.getKnots().buildGraph();
  208. 208 : }
  209. 209 : else {
  210. 210 : this.toastInfo({
  211. 211 : html: this.localize("noTermsFound"),
  212. 212 : align: 'bl'
  213. 213 : })
  214. 214 : }
  215. 215 : },
  216. 216 : scope: this
  217. 217 : }
  218. 218 : }));
  219. 219 :
  220. 220 : this.setTokensStore(Ext.create("Voyant.data.store.Tokens", {
  221. 221 : stripTags: "all",
  222. 222 : listeners: {
  223. 223 : beforeload: function(store) {
  224. 224 : store.getProxy().setExtraParam('docId', this.getApiParam('docId'));
  225. 225 : },
  226. 226 : load: function(store, records, successful, options) {
  227. 227 : var context = '';
  228. 228 : var currTerm = this.getCurrentTerm();
  229. 229 : records.forEach(function(record) {
  230. 230 : if (record.getPosition() == currTerm.tokenId) {
  231. 231 : context += '<strong>'+record.getTerm()+'</strong>';
  232. 232 : } else {
  233. 233 : context += record.getTerm();
  234. 234 : }
  235. 235 : });
  236. 236 :
  237. 237 : Ext.Msg.show({
  238. 238 : title: this.localize('context'),
  239. 239 : message: context,
  240. 240 : buttons: Ext.Msg.OK,
  241. 241 : icon: Ext.Msg.INFO
  242. 242 : });
  243. 243 : },
  244. 244 : scope: this
  245. 245 : }
  246. 246 : }));
  247. 247 :
  248. 248 : Ext.apply(this, {
  249. 249 : title: this.localize('title'),
  250. 250 : dockedItems: [{
  251. 251 : dock: 'bottom',
  252. 252 : xtype: 'toolbar',
  253. 253 : overflowHandler: 'scroller',
  254. 254 : items: [{
  255. 255 : xtype: 'querysearchfield'
  256. 256 : },{
  257. 257 : text: this.localize('clearTerms'),
  258. 258 : glyph: 'xf00d@FontAwesome',
  259. 259 : handler: function() {
  260. 260 : this.down('#termsView').getSelectionModel().deselectAll(true);
  261. 261 : this.getTermStore().removeAll();
  262. 262 : this.setApiParams({query: null});
  263. 263 : this.getKnots().removeAllTerms();
  264. 264 : this.getKnots().drawGraph();
  265. 265 : },
  266. 266 : scope: this
  267. 267 : },{
  268. 268 : xtype: 'documentselectorbutton',
  269. 269 : singleSelect: true
  270. 270 : },{
  271. 271 : xtype: 'slider',
  272. 272 : itemId: 'speed',
  273. 273 : fieldLabel: this.localize("speed"),
  274. 274 : labelAlign: 'right',
  275. 275 : labelWidth: 50,
  276. 276 : width: 100,
  277. 277 : increment: 50,
  278. 278 : minValue: 0,
  279. 279 : maxValue: 500,
  280. 280 : value: 500-this.getRefreshInterval(),
  281. 281 : listeners: {
  282. 282 : changecomplete: function(slider, newvalue) {
  283. 283 : this.setRefreshInterval(500-newvalue);
  284. 284 : if (this.getKnots()) {this.getKnots().buildGraph();}
  285. 285 : },
  286. 286 : scope: this
  287. 287 : }
  288. 288 : },{
  289. 289 : xtype: 'slider',
  290. 290 : itemId: 'startAngle',
  291. 291 : fieldLabel: this.localize('startAngle'),
  292. 292 : labelAlign: 'right',
  293. 293 : labelWidth: 35,
  294. 294 : width: 85,
  295. 295 : increment: 15,
  296. 296 : minValue: 0,
  297. 297 : maxValue: 360,
  298. 298 : value: this.getStartAngle(),
  299. 299 : listeners: {
  300. 300 : changecomplete: function(slider, newvalue) {
  301. 301 : this.setStartAngle(newvalue);
  302. 302 : if (this.getKnots()) {this.getKnots().buildGraph();}
  303. 303 : },
  304. 304 : scope: this
  305. 305 : }
  306. 306 : },{
  307. 307 : xtype: 'slider',
  308. 308 : itemId: 'tangles',
  309. 309 : fieldLabel: this.localize('tangles'),
  310. 310 : labelAlign: 'right',
  311. 311 : labelWidth: 30,
  312. 312 : width: 80,
  313. 313 : increment: 5,
  314. 314 : minValue: 5,
  315. 315 : maxValue: 90,
  316. 316 : value: this.getAngleIncrement(),
  317. 317 : listeners: {
  318. 318 : changecomplete: function(slider, newvalue) {
  319. 319 : this.setAngleIncrement(newvalue);
  320. 320 : if (this.getKnots()) {this.getKnots().buildGraph();}
  321. 321 : },
  322. 322 : scope: this
  323. 323 : }
  324. 324 : },{
  325. 325 : xtype: 'checkbox',
  326. 326 : boxLabel: this.localize('sound'),
  327. 327 : listeners: {
  328. 328 : render: function(cmp) {
  329. 329 : cmp.setValue(this.getApiParam("audio")===true || this.getApiParam("audio")=="true")
  330. 330 : Ext.tip.QuickTipManager.register({
  331. 331 : target: cmp.getEl(),
  332. 332 : text: this.localize('soundTip')
  333. 333 : });
  334. 334 :
  335. 335 : },
  336. 336 : beforedestroy: function(cmp) {
  337. 337 : Ext.tip.QuickTipManager.unregister(cmp.getEl());
  338. 338 : },
  339. 339 : change: function(cmp, val) {
  340. 340 : if (this.getKnots()) {
  341. 341 : this.getKnots().setAudio(val);
  342. 342 : }
  343. 343 : },
  344. 344 : scope: this
  345. 345 : }
  346. 346 : }]
  347. 347 : }],
  348. 348 : border: false,
  349. 349 : layout: 'fit',
  350. 350 : items: {
  351. 351 : layout: {
  352. 352 : type: 'vbox',
  353. 353 : align: 'stretch'
  354. 354 : },
  355. 355 : defaults: {border: false},
  356. 356 : items: [{
  357. 357 : height: 30,
  358. 358 : itemId: 'termsView',
  359. 359 : xtype: 'dataview',
  360. 360 : store: this.getTermStore(),
  361. 361 : tpl: this.termTpl,
  362. 362 : itemSelector: 'div.term',
  363. 363 : overItemCls: 'over',
  364. 364 : selectedItemCls: 'selected',
  365. 365 : selectionModel: {
  366. 366 : mode: 'SIMPLE'
  367. 367 : },
  368. 368 : // cls: 'selected', // default selected
  369. 369 : focusCls: '',
  370. 370 : listeners: {
  371. 371 : beforeitemclick: function(dv, record, item, index, event, opts) {
  372. 372 : event.preventDefault();
  373. 373 : event.stopPropagation();
  374. 374 : dv.fireEvent('itemcontextmenu', dv, record, item, index, event, opts);
  375. 375 : return false;
  376. 376 : },
  377. 377 : beforecontainerclick: function() {
  378. 378 : // cancel deselect all
  379. 379 : event.preventDefault();
  380. 380 : event.stopPropagation();
  381. 381 : return false;
  382. 382 : },
  383. 383 : selectionchange: function(selModel, selections) {
  384. 384 : var dv = this.down('#termsView');
  385. 385 : var terms = [];
  386. 386 :
  387. 387 : dv.getStore().each(function(r) {
  388. 388 : if (selections.indexOf(r) !== -1) {
  389. 389 : terms.push(r.get('term'));
  390. 390 : Ext.fly(dv.getNodeByRecord(r)).removeCls('unselected').addCls('selected');
  391. 391 : } else {
  392. 392 : Ext.fly(dv.getNodeByRecord(r)).removeCls('selected').addCls('unselected');
  393. 393 : }
  394. 394 : });
  395. 395 :
  396. 396 : this.getKnots().termsFilter = terms;
  397. 397 : this.getKnots().drawGraph();
  398. 398 : },
  399. 399 : itemcontextmenu: function(dv, record, el, index, event) {
  400. 400 : event.preventDefault();
  401. 401 : event.stopPropagation();
  402. 402 : var isSelected = dv.isSelected(el);
  403. 403 : var menu = new Ext.menu.Menu({
  404. 404 : floating: true,
  405. 405 : items: [{
  406. 406 : text: isSelected ? this.localize('hideTerm') : this.localize('showTerm'),
  407. 407 : handler: function() {
  408. 408 : if (isSelected) {
  409. 409 : dv.deselect(index);
  410. 410 : } else {
  411. 411 : dv.select(index, true);
  412. 412 : }
  413. 413 : },
  414. 414 : scope: this
  415. 415 : },{
  416. 416 : text: this.localize('removeTerm'),
  417. 417 : handler: function() {
  418. 418 : dv.deselect(index);
  419. 419 : var term = this.getTermStore().getAt(index).get('term');
  420. 420 : this.getTermStore().removeAt(index);
  421. 421 : dv.refresh();
  422. 422 :
  423. 423 : this.getKnots().removeTerm(term);
  424. 424 : this.getKnots().drawGraph();
  425. 425 : },
  426. 426 : scope: this
  427. 427 : }]
  428. 428 : });
  429. 429 : menu.showAt(event.getXY());
  430. 430 : },
  431. 431 : scope: this
  432. 432 : }
  433. 433 : },{
  434. 434 : flex: 1,
  435. 435 : xtype: 'container',
  436. 436 : autoEl: 'div',
  437. 437 : itemId: 'canvasParent',
  438. 438 : layout: 'fit',
  439. 439 : overflowY: 'auto',
  440. 440 : overflowX: 'hidden'
  441. 441 : }],
  442. 442 : listeners: {
  443. 443 : render: function(component) {
  444. 444 : var canvasParent = this.down('#canvasParent');
  445. 445 : this.setKnots(new Knots({
  446. 446 : container: canvasParent,
  447. 447 : clickHandler: this.knotClickHandler.bind(this),
  448. 448 : audio: this.getApiParam("audio")===true || this.getApiParam("audio")=="true"
  449. 449 : }));
  450. 450 : },
  451. 451 : afterlayout: function(container) {
  452. 452 : if (this.getKnots().initialized === false) {
  453. 453 : this.getKnots().initializeCanvas();
  454. 454 : }
  455. 455 : },
  456. 456 : resize: function(cnt, width, height) {
  457. 457 : this.getKnots().doLayout();
  458. 458 : },
  459. 459 : scope: this
  460. 460 : }
  461. 461 : }
  462. 462 : });
  463. 463 :
  464. 464 : this.callParent(arguments);
  465. 465 : },
  466. 466 :
  467. 467 : updateRefreshInterval: function(value) {
  468. 468 : if (this.getKnots()) {
  469. 469 : if (value < 50) {
  470. 470 : value = 50;
  471. 471 : this.getKnots().progressiveDraw = false;
  472. 472 : } else {
  473. 473 : this.getKnots().progressiveDraw = true;
  474. 474 : }
  475. 475 : this.getKnots().refreshInterval = value;
  476. 476 : this.getKnots().buildGraph(this.getKnots().drawStep);
  477. 477 : }
  478. 478 : },
  479. 479 :
  480. 480 : updateStartAngle: function(value) {
  481. 481 : if (this.getKnots()) {
  482. 482 : this.getKnots().startAngle = value;
  483. 483 : this.getKnots().recache();
  484. 484 : this.getKnots().buildGraph();
  485. 485 : }
  486. 486 : },
  487. 487 :
  488. 488 : updateAngleIncrement: function(value) {
  489. 489 : if (this.getKnots()) {
  490. 490 : this.getKnots().angleIncrement = value;
  491. 491 : this.getKnots().recache();
  492. 492 : this.getKnots().buildGraph();
  493. 493 : }
  494. 494 : },
  495. 495 :
  496. 496 : loadFromCorpusTerms: function(corpusTerms) {
  497. 497 : if (this.getKnots()) { // get rid of existing terms
  498. 498 : this.getKnots().removeAllTerms();
  499. 499 : this.getTermStore().removeAll(true);
  500. 500 : }
  501. 501 : corpusTerms.load({
  502. 502 : callback: function(records, operation, success) {
  503. 503 : var query = []; //this.getApiParam('query') || [];
  504. 504 : if (typeof query == 'string') query = [query];
  505. 505 : records.forEach(function(record, index) {
  506. 506 : query.push(record.get('term'));
  507. 507 : }, this);
  508. 508 : this.getDocTermsFromQuery(query);
  509. 509 : },
  510. 510 : scope: this,
  511. 511 : params: {
  512. 512 : limit: 5,
  513. 513 : stopList: this.getApiParams('stopList')
  514. 514 : }
  515. 515 : });
  516. 516 : },
  517. 517 :
  518. 518 : /**
  519. 519 : * Get the results for the query(s) for each of the corpus documents.
  520. 520 : * @param query {String|Array}
  521. 521 : * @private
  522. 522 : */
  523. 523 : getDocTermsFromQuery: function(query) {
  524. 524 : if (query) {this.setApiParam("query", query);} // make sure it's set for subsequent calls
  525. 525 : var corpus = this.getCorpus();
  526. 526 : if (corpus && this.isVisible()) {
  527. 527 : this.setApiParams({query: query}); // assumes docId already set
  528. 528 : this.getDocTermStore().load({params: this.getApiParams()});
  529. 529 : }
  530. 530 : },
  531. 531 :
  532. 532 : reloadTermsData: function() {
  533. 533 : var terms = [];
  534. 534 : for (var term in this.bubblelines.currentTerms) {
  535. 535 : terms.push(term);
  536. 536 : }
  537. 537 : this.getDocTermsFromQuery(terms);
  538. 538 : },
  539. 539 :
  540. 540 : filterDocuments: function() {
  541. 541 : var docIds = this.getApiParam('docId');
  542. 542 : if (docIds == '') {
  543. 543 : docIds = [];
  544. 544 : this.getCorpus().getDocuments().each(function(item, index) {
  545. 545 : docIds.push(item.getId());
  546. 546 : });
  547. 547 : this.setApiParams({docId: docIds});
  548. 548 : }
  549. 549 : if (typeof docIds == 'string') docIds = [docIds];
  550. 550 :
  551. 551 : if (docIds == null) {
  552. 552 : this.selectedDocs = this.getCorpus().getDocuments().clone();
  553. 553 : var count = this.selectedDocs.getCount();
  554. 554 : if (count > 10) {
  555. 555 : for (var i = 10; i < count; i++) {
  556. 556 : this.selectedDocs.removeAt(10);
  557. 557 : }
  558. 558 : }
  559. 559 : docIds = [];
  560. 560 : this.selectedDocs.eachKey(function(docId, doc) {
  561. 561 : docIds.push(docId);
  562. 562 : }, this);
  563. 563 : this.setApiParams({docId: docIds});
  564. 564 : } else {
  565. 565 : this.selectedDocs = this.getCorpus().getDocuments().filterBy(function(doc, docId) {
  566. 566 : return docIds.indexOf(docId) != -1;
  567. 567 : }, this);
  568. 568 : }
  569. 569 : },
  570. 570 :
  571. 571 : // produce format that knots can use
  572. 572 : processDocument: function(doc) {
  573. 573 : var title = doc.getShortTitle();
  574. 574 : title = title.replace('&hellip;', '...');
  575. 575 :
  576. 576 : return {
  577. 577 : id: doc.getId(),
  578. 578 : index: doc.get('index'),
  579. 579 : title: title,
  580. 580 : totalTokens: doc.get('tokensCount-lexical'),
  581. 581 : terms: {},
  582. 582 : lineLength: undefined
  583. 583 : };
  584. 584 : },
  585. 585 :
  586. 586 : processTerms: function(termRecord) {
  587. 587 : var termObj;
  588. 588 : var term = termRecord.get('term');
  589. 589 : var rawFreq = termRecord.get('rawFreq');
  590. 590 : var positions = termRecord.get('positions');
  591. 591 : if (rawFreq > 0) {
  592. 592 : var color = this.getApplication().getColorForTerm(term);
  593. 593 : if (this.getTermStore().find('term', term) === -1) {
  594. 594 : this.getTermStore().loadData([[term, color]], true);
  595. 595 : var index = this.getTermStore().find('term', term);
  596. 596 : this.down('#termsView').select(index, true); // manually select since the store's load listener isn't triggered
  597. 597 : }
  598. 598 : var distributions = termRecord.get('distributions');
  599. 599 : termObj = {term: term, positions: positions, distributions: distributions, rawFreq: rawFreq, color: color};
  600. 600 : } else {
  601. 601 : termObj = false;
  602. 602 : }
  603. 603 :
  604. 604 : return termObj;
  605. 605 : },
  606. 606 :
  607. 607 : knotClickHandler: function(data) {
  608. 608 : this.setCurrentTerm(data);
  609. 609 : var start = data.tokenId - 10;
  610. 610 : if (start < 0) start = 0;
  611. 611 : this.getTokensStore().load({
  612. 612 : start: start,
  613. 613 : limit: 21
  614. 614 : });
  615. 615 :
  616. 616 : data = [data].map(function(item) {return item.term}); // make an array for the event dispatch
  617. 617 : this.getApplication().dispatchEvent('termsClicked', this, data);
  618. 618 : }
  619. 619 : });