1. 1 : /**
  2. 2 : * Corpus Terms tool, a grid that shows the terms in the corpus.
  3. 3 : *
  4. 4 : * @example
  5. 5 : *
  6. 6 : * let config = {
  7. 7 : * columns: null,
  8. 8 : * comparisonCorpus: null,
  9. 9 : * dir: null,
  10. 10 : * maxBins: null,
  11. 11 : * query: null,
  12. 12 : * sort: null,
  13. 13 : * stopList: null,
  14. 14 : * termColors: null
  15. 15 : * };
  16. 16 : *
  17. 17 : * loadCorpus("austen").tool("corpusterms", config);
  18. 18 : *
  19. 19 : *
  20. 20 : * @class CorpusTerms
  21. 21 : * @tutorial corpusterms
  22. 22 : * @memberof Tools
  23. 23 : */
  24. 24 : Ext.define('Voyant.panel.CorpusTerms', {
  25. 25 : extend: 'Ext.grid.Panel',
  26. 26 : mixins: ['Voyant.panel.Panel'],
  27. 27 : alias: 'widget.corpusterms',
  28. 28 : statics: {
  29. 29 : i18n: {
  30. 30 : comparisonCorpus: 'Comparison Corpus'
  31. 31 : },
  32. 32 : api: {
  33. 33 :
  34. 34 : /**
  35. 35 : * @memberof Tools.CorpusTerms
  36. 36 : * @instance
  37. 37 : * @property {stopList}
  38. 38 : * @default
  39. 39 : */
  40. 40 : stopList: 'auto',
  41. 41 :
  42. 42 : /**
  43. 43 : * @memberof Tools.CorpusTerms
  44. 44 : * @instance
  45. 45 : * @property {query}
  46. 46 : */
  47. 47 : query: undefined,
  48. 48 :
  49. 49 : /**
  50. 50 : * @memberof Tools.CorpusTerms
  51. 51 : * @instance
  52. 52 : * @property {Number} maxBins The maximum number of bins to use for distributions in Trend.
  53. 53 : *
  54. 54 : * By default this is set to 100 (in other words, if there are more than 100 documents in the corpus, they will be forced into 100 bins).
  55. 55 : * Higher values are possible but it can cause performance issues and necessitate more data transfer (values for each one of the bins for each one of the terms).
  56. 56 : * @default
  57. 57 : */
  58. 58 : maxBins: 100,
  59. 59 :
  60. 60 : /**
  61. 61 : * @memberof Tools.CorpusTerms
  62. 62 : * @instance
  63. 63 : * @property {termColors}
  64. 64 : * @default
  65. 65 : */
  66. 66 : termColors: 'categories',
  67. 67 :
  68. 68 : /**
  69. 69 : * @memberof Tools.CorpusTerms
  70. 70 : * @instance
  71. 71 : * @property {String} comparisonCorpus An existing corpus to be used for comparison purposes.
  72. 72 : *
  73. 73 : * None of the columns visible by default use comparisonCorpus so this is an advanced parameter used when the "Comparison" column is shown.
  74. 74 : * The comparison column shows the relative frequency of the term in the corpus compared to the relative frequency of the same term in a comparison corpus.
  75. 75 : */
  76. 76 : comparisonCorpus: undefined,
  77. 77 :
  78. 78 : /**
  79. 79 : * @memberof Tools.CorpusTerms
  80. 80 : * @instance
  81. 81 : * @property {columns} columns 'term', 'rawFreq', 'relativeFreq', 'relativePeakedness', 'relativeSkewness', 'comparisonRelativeFreqDifference', 'distributions'
  82. 82 : */
  83. 83 : columns: undefined,
  84. 84 :
  85. 85 : /**
  86. 86 : * @memberof Tools.CorpusTerms
  87. 87 : * @instance
  88. 88 : * @property {sort}
  89. 89 : */
  90. 90 : sort: undefined,
  91. 91 :
  92. 92 : /**
  93. 93 : * @memberof Tools.CorpusTerms
  94. 94 : * @instance
  95. 95 : * @property {dir}
  96. 96 : */
  97. 97 : dir: undefined,
  98. 98 : },
  99. 99 : glyph: 'xf0ce@FontAwesome'
  100. 100 : },
  101. 101 : config: {
  102. 102 : /**
  103. 103 : * @private
  104. 104 : */
  105. 105 : options: [{
  106. 106 : xtype: 'stoplistoption'
  107. 107 : },{
  108. 108 : xtype: 'categoriesoption'
  109. 109 : },{
  110. 110 : xtype: 'termcolorsoption'
  111. 111 : },{
  112. 112 : xtype: 'corpusselector',
  113. 113 : name: 'comparisonCorpus'
  114. 114 : }]
  115. 115 : },
  116. 116 :
  117. 117 : /**
  118. 118 : * @private
  119. 119 : */
  120. 120 : constructor: function(config) {
  121. 121 : this.mixins['Voyant.util.Api'].constructor.apply(this, arguments);
  122. 122 : this.callParent(arguments);
  123. 123 : this.getOptions().filter(function(option) { return option.xtype === 'corpusselector'})[0].fieldLabel = this.localize('comparisonCorpus');
  124. 124 : this.mixins['Voyant.panel.Panel'].constructor.apply(this, arguments);
  125. 125 : },
  126. 126 :
  127. 127 :
  128. 128 : initComponent: function() {
  129. 129 : var me = this;
  130. 130 :
  131. 131 : var store = Ext.create("Voyant.data.store.CorpusTermsBuffered", {
  132. 132 : parentPanel: this,
  133. 133 : proxy: {
  134. 134 : extraParams: {
  135. 135 : withDistributions: 'relative',
  136. 136 : forTool: 'corpusterms'
  137. 137 : }
  138. 138 : }
  139. 139 : });
  140. 140 :
  141. 141 : Ext.apply(me, {
  142. 142 : title: this.localize('title'),
  143. 143 : emptyText: this.localize("emptyText"),
  144. 144 : store : store,
  145. 145 : selModel: Ext.create('Ext.selection.CheckboxModel', {
  146. 146 : pruneRemoved: false,
  147. 147 : listeners: {
  148. 148 : selectionchange: {
  149. 149 : fn: function(sm, selections) {
  150. 150 : if (selections && selections.length>0) {
  151. 151 : this.getApplication().dispatchEvent('corpusTermsClicked', this, selections);
  152. 152 : }
  153. 153 : },
  154. 154 : scope: this
  155. 155 : }
  156. 156 : },
  157. 157 : mode: 'SIMPLE'
  158. 158 : }),
  159. 159 : dockedItems: [{
  160. 160 : dock: 'bottom',
  161. 161 : xtype: 'toolbar',
  162. 162 : overflowHandler: 'scroller',
  163. 163 : items: [{
  164. 164 : xtype: 'querysearchfield'
  165. 165 : }, {
  166. 166 : xtype: 'totalpropertystatus'
  167. 167 : }]
  168. 168 : }],
  169. 169 :
  170. 170 : plugins: [{
  171. 171 : ptype: 'rowexpander',
  172. 172 : rowBodyTpl: new Ext.XTemplate('')
  173. 173 : }],
  174. 174 : viewConfig: {
  175. 175 : listeners: {
  176. 176 : // TODO widget disappears when scrolled off screen
  177. 177 : expandbody: function(rowNode, record, expandRow, eOpts) {
  178. 178 : if (expandRow.textContent==='' || (eOpts && eOpts.force)) {
  179. 179 : Ext.create('Voyant.widget.CorpusTermSummary', {
  180. 180 : record: record,
  181. 181 : header: false,
  182. 182 : renderTo: expandRow.querySelector('div')
  183. 183 : });
  184. 184 : }
  185. 185 : },
  186. 186 : scope: this
  187. 187 : }
  188. 188 : },
  189. 189 : columns: [{
  190. 190 : xtype: 'rownumberer',
  191. 191 : width: 'autoSize',
  192. 192 : sortable: false
  193. 193 : },{
  194. 194 : text: this.localize("term"),
  195. 195 : tooltip: this.localize("termTip"),
  196. 196 : dataIndex: 'term',
  197. 197 : flex: 1,
  198. 198 : sortable: true,
  199. 199 : xtype: 'coloredtermfield',
  200. 200 : useCategoriesMenu: true
  201. 201 : },{
  202. 202 : text: this.localize("rawFreq"),
  203. 203 : tooltip: this.localize("rawFreqTip"),
  204. 204 : dataIndex: 'rawFreq',
  205. 205 : width: 'autoSize',
  206. 206 : sortable: true
  207. 207 : },{
  208. 208 : text: this.localize("relativeFreq"),
  209. 209 : tooltip: this.localize("relativeFreqTip"),
  210. 210 : dataIndex: 'relativeFreq',
  211. 211 : renderer: function(val) {
  212. 212 : return Ext.util.Format.number(val*1000000, "0,000")
  213. 213 : },
  214. 214 : width: 'autoSize',
  215. 215 : hidden: true,
  216. 216 : sortable: true
  217. 217 : },{
  218. 218 : text: this.localize("relativePeakedness"),
  219. 219 : tooltip: this.localize("relativePeakednessTip"),
  220. 220 : dataIndex: 'relativePeakedness',
  221. 221 : renderer: Ext.util.Format.numberRenderer("0,000.0"),
  222. 222 : width: 'autoSize',
  223. 223 : hidden: true,
  224. 224 : sortable: true
  225. 225 : },{
  226. 226 : text: this.localize("relativeSkewness"),
  227. 227 : tooltip: this.localize("relativeSkewnessTip"),
  228. 228 : dataIndex: 'relativeSkewness',
  229. 229 : renderer: Ext.util.Format.numberRenderer("0,000.0"),
  230. 230 : width: 'autoSize',
  231. 231 : hidden: true,
  232. 232 : sortable: true
  233. 233 : },{
  234. 234 : text: this.localize("corpusComparisonDifference"),
  235. 235 : tooltip: this.localize("corpusComparisonDifferenceTip"),
  236. 236 : dataIndex: 'comparisonRelativeFreqDifference',
  237. 237 : renderer: Ext.util.Format.numberRenderer("0,000.00000"),
  238. 238 : width: 'autoSize',
  239. 239 : hidden: !this.getApiParam('comparisonCorpus'),
  240. 240 : sortable: true,
  241. 241 : listeners: {
  242. 242 : show: function(ct, column, eopts) {
  243. 243 : if (!me.getApiParam('comparisonCorpus')) {
  244. 244 : me.showError(me.localize('noCorpusComparison'))
  245. 245 : }
  246. 246 : }
  247. 247 : }
  248. 248 : },{
  249. 249 : xtype: 'widgetcolumn',
  250. 250 : text: this.localize("trend"),
  251. 251 : tooltip: this.localize("trendTip"),
  252. 252 : flex: 1,
  253. 253 : dataIndex: 'distributions',
  254. 254 : widget: {
  255. 255 : xtype: 'sparklineline',
  256. 256 : tipTpl: new Ext.XTemplate('{[this.getDocumentTitle(values.x,values.y)]}', {
  257. 257 : getDocumentTitle: function(docIndex, relativeFreq) {
  258. 258 : return this.panel.store.getCorpus().getDocument(docIndex).getTitle()+"<br>"+this.panel.localize("relativeFreqLabel")+" "+Ext.util.Format.number(relativeFreq*1000000, "0,000")
  259. 259 : },
  260. 260 : panel: me
  261. 261 : })
  262. 262 : }
  263. 263 : }]
  264. 264 : });
  265. 265 :
  266. 266 : me.on('loadedCorpus', function(src, corpus) {
  267. 267 : if (corpus.getDocumentsCount()>100) {
  268. 268 : this.getStore().getProxy().setExtraParam('bins', this.getApiParam('maxBins'));
  269. 269 : }
  270. 270 : if (this.isVisible()) {
  271. 271 : if (corpus.getDocumentsCount() === 1) {
  272. 272 : this.getColumns().filter(function(col) { return col.dataIndex === 'distributions'})[0].hide();
  273. 273 : }
  274. 274 : this.getStore().load();
  275. 275 : }
  276. 276 : }, me);
  277. 277 :
  278. 278 : me.on("activate", function() { // load after tab activate (if we're in a tab panel)
  279. 279 : if (me.getStore().getCorpus()) {
  280. 280 : if (me.getStore().getCorpus().getDocumentsCount() === 1) {
  281. 281 : this.getColumns().filter(function(col) { return col.dataIndex === 'distributions'})[0].hide();
  282. 282 : }
  283. 283 : me.getStore().load({params: this.getApiParams()});
  284. 284 : }
  285. 285 : }, me);
  286. 286 :
  287. 287 :
  288. 288 : me.on("query", function(src, query) {
  289. 289 : this.setApiParam('query', query);
  290. 290 : this.getStore().removeAll();
  291. 291 : this.getStore().load();
  292. 292 : }, me);
  293. 293 :
  294. 294 :
  295. 295 : me.callParent(arguments);
  296. 296 :
  297. 297 : }
  298. 298 : })