1 /**
  2  * @class Monk.component.FeaturesChartComponent
  3  * @description Display features from SEASR analyses.
  4  * @extends Workbench.component.Component
  5  * @author Andrew
  6  */
  7 
  8 Monk.component.FeaturesChartComponent = function(args) {
  9     
 10     this.featuresGrid = null;
 11     
 12     this.pageSize = 20;
 13     
 14     this.cachedData = null;
 15     this.cachedTree = null;
 16     this.cachedTreeSummary=null;
 17     this.cachedTreeImage = null;
 18     this.chartWin=null;
 19     
 20     this.classNames = [];
 21     
 22     this.colourArray = [
 23         {h: 240, s: 50, v: 85},
 24         {h: 92, s: 50, v: 85},
 25         {h: 349, s: 50, v: 85},
 26         {h: 45, s: 50, v: 85}
 27     ];
 28     
 29     Monk.component.FeaturesChartComponent.superclass.constructor.call(this, args);
 30 }
 31 
 32 Workbench.extend(Monk.component.FeaturesChartComponent, Workbench.component.Component, {
 33    
 34     label: 'Features Chart',
 35     description: 'For viewing text features.',
 36     "window": this.window,
 37     
 38     handle : function(monkEvent, data) {
 39         if (monkEvent.instanceOf(Workbench.event.ComponentLoaded)) {
 40             if (monkEvent.component == this) {
 41                 var features = Monk.component.dataManager.getFeatureList();
 42                 var decisionTreeSummary = Monk.component.dataManager.getDecisionTreeSummary();
 43                 var tree = Monk.component.dataManager.getDecisionTree();
 44                 var treeImage = Monk.component.dataManager.getDecisionTreeImage();
 45                 
 46                 if (features.length > 0) {
 47                     this.cachedData = features;
 48                 }
 49                 
 50                
 51                 if(decisionTreeSummary!=null){
 52                 	this.cachedTreeSummary= decisionTreeSummary;
 53                 }
 54                 if(decisionTreeSummary!=null){
 55                     this.cachedTree= tree;
 56                 }
 57                 
 58                 if(treeImage!=null){
 59                 	 this.cachedTreeImage= treeImage;
 60                 }
 61                 
 62                 //Workbench.console.info("Tree URL is: " + treeImage);
 63             }
 64         }
 65     },
 66     
 67     // taken from: http://johndyer.name/lab/colorpicker/
 68     hsvToRgb: function (hsv) {
 69 
 70         rgb = {r:0, g:0, b:0};
 71         
 72         var h = hsv.h;
 73         var s = hsv.s;
 74         var v = hsv.v;
 75 
 76         if (s == 0) {
 77             if (v == 0) {
 78                 rgb.r = rgb.g = rgb.b = 0;
 79             } else {
 80                 rgb.r = rgb.g = rgb.b = parseInt(v * 255 / 100);
 81             }
 82         } else {
 83             if (h == 360) {
 84                 h = 0;
 85             }
 86             h /= 60;
 87 
 88             // 100 scale
 89             s = s/100;
 90             v = v/100;
 91 
 92             var i = parseInt(h);
 93             var f = h - i;
 94             var p = v * (1 - s);
 95             var q = v * (1 - (s * f));
 96             var t = v * (1 - (s * (1 - f)));
 97             switch (i) {
 98                 case 0:
 99                     rgb.r = v;
100                     rgb.g = t;
101                     rgb.b = p;
102                     break;
103                 case 1:
104                     rgb.r = q;
105                     rgb.g = v;
106                     rgb.b = p;
107                     break;
108                 case 2:
109                     rgb.r = p;
110                     rgb.g = v;
111                     rgb.b = t;
112                     break;
113                 case 3:
114                     rgb.r = p;
115                     rgb.g = q;
116                     rgb.b = v;
117                     break;
118                 case 4:
119                     rgb.r = t;
120                     rgb.g = p;
121                     rgb.b = v;
122                     break;
123                 case 5:
124                     rgb.r = v;
125                     rgb.g = p;
126                     rgb.b = q;
127                     break;
128             }
129 
130             rgb.r = parseInt(rgb.r * 255);
131             rgb.g = parseInt(rgb.g * 255);
132             rgb.b = parseInt(rgb.b * 255);
133         }
134 
135         return rgb;
136     },
137     
138     initializeFeaturesGrid : function(initData) {
139         var data = [];
140         
141         if (initData != null) {
142             data = initData;
143         } else if (this.cachedData != null) {
144             data = this.cachedData;
145         }
146         
147         var json = {features: data};
148         
149         // find the min/max for feature class values
150         var minClass = null;
151         var maxClass = null;
152         for (var i = 0, len = data.length; i < len; i++) {
153             var feature = data[i];
154             for (var j = 0, len2 = feature.classes.entry.length; j < len2; j++) {
155                 var entry = parseFloat(feature.classes.entry[j]['double']);
156                 if (entry < minClass || minClass == null) minClass = entry;
157                 if (entry > maxClass || maxClass == null) maxClass = entry;
158             }
159         }
160         var modifier = minClass * -1;
161         minClass = minClass + modifier;
162         maxClass = maxClass + modifier;
163         
164         var confidenceGradient = function(value, metadata, record, rowIndex, colIndex, store, i){
165             var confidence = Math.floor((value + modifier) / maxClass * 100);
166             var colour = this.colourArray[i];
167             if (!colour) {
168                 var remainder = i / this.colourArray.length - Math.floor(i / this.colourArray.length);
169                 i = this.colourArray.length * remainder;
170                 colour = this.colourArray[i];
171             }
172             colour.s = confidence;
173             var rgb = this.hsvToRgb(colour);
174             metadata.attr = 'style = "background-color: rgb('+rgb.r+','+rgb.g+','+rgb.b+');"';
175             return ''; // just show the colour
176         }
177         
178         var columnHeaders = [
179             {header: 'Feature', width: 100, sortable: true, dataIndex: 'name', 
180             renderer : function (val) {return val.replace(/\s+\(.*?\)/g,'').replace(/\s+/g,' ')}
181             },
182             {header: 'POS', width: 75, sortable: true, dataIndex: 'name', renderer: function(val){
183 				var re = /\(.*?\)/g;
184 				var pos = new Array();
185 				while ((match = re.exec(val)) != null) {
186 					pos.push(match[0]);
187 				}
188 				return pos.join(' ');
189 			}
190 			
191 			},
192 		   {header:'type',width:50,sortable:true,dataIndex:'type',
193 		    renderer: function(content, el, data) {return content;}
194 		   },
195            {header: 'Avg. Freq Test', width: 125, sortable: true, dataIndex: 'avgFreqTestDocs',
196            renderer: function(content, el, data) {return '<span ext:qtip="Frequency per 10000 features in test documents">'+content+"</span>"}
197            },
198            {header: 'Avg. Freq Training', width: 125, sortable: true, dataIndex: 'avgFreqTrainingDocs',
199            convert:function(value){var num = parseFloat(value);return num.toFixed(4)},
200            renderer: function(content, el, data) {return '<span ext:qtip="Frequency per 10000 features in training documents">'+content+"</span>"}
201            }
202         ];
203         var recordArray = [
204             {name: 'name'},
205             {name: 'pos'},
206             {name: 'type'},
207             {name: 'avgFreqTestDocs', type: 'float',convert:function(value){var num = parseFloat(value);return num.toFixed(4)}},
208             {name: 'avgFreqTrainingDocs', type: 'float',convert:function(value){var num = parseFloat(value);return num.toFixed(4)}}
209         ];
210         if (data.length > 0) {
211         	var tableType = data[0].type;
212             var firstEntry = data[0].classes.entry;
213             for (var i = 0, len = firstEntry.length; i < len; i++) {
214                 var className = firstEntry[i].string;
215                 var trimmedClassName = className.replace('level_', '').replace('_Features', '')+" confidence";
216                 this.classNames.push(trimmedClassName);
217                 columnHeaders.push({
218                     header: trimmedClassName, sortable: true, dataIndex: trimmedClassName,
219                     renderer: confidenceGradient.createDelegate(this, [i], true)
220                 });
221                 recordArray.push({name: trimmedClassName, mapping: 'classes.entry['+i+'].double', type: 'float'});
222             }
223         }
224         
225         var recordDef = Ext.data.Record.create(recordArray);
226         
227         var reader = new Ext.data.JsonReader({
228             root: 'features'
229         }, recordDef);
230         
231         var store = new Ext.data.GroupingStore({
232             reader: reader,
233             proxy: new Ext.ux.data.PagingMemoryProxy(json, {reader: reader}),
234             remoteSort: true,
235             groupField: 'type',
236             sortInfo:{field: 'name', direction: "ASC"}
237         });
238         
239         var viewport = new Ext.Viewport({
240             layout: 'fit',
241             renderTo: 'gridParent',
242             items: this.featuresGrid
243         });
244         
245         Ext.EventManager.onWindowResize(function(w, h) {
246             this.featuresGrid.setHeight(h);
247         }, this);
248         
249         var columnModel = new Ext.grid.ColumnModel(columnHeaders);
250         
251         var selectionModel = new Ext.grid.RowSelectionModel({
252             singleSelect: false
253         });
254         
255         this.featuresGrid = new Ext.grid.GridPanel({
256             store: store,
257             stripeRows: true,
258             cm: columnModel,
259             sm: selectionModel,
260              view: new Ext.grid.GroupingView({
261                             forceFit:true,
262                             groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'
263                              }),
264             viewConfig: {
265                 emptyText: 'No features to display.', // TODO not working
266                 forceFit: true
267             },
268             renderTo: 'featuresGrid',
269             autoWidth: true,
270             listeners: {
271                 'render': function(grid){
272                     var h = viewport.getBox().height;
273                     grid.setHeight(h);
274                 }
275             },
276             bbar: new Ext.PagingToolbar({
277                 store: store,
278                 pageSize: this.pageSize,
279                 displayInfo: true,
280                 displayMsg: 'Displaying features {0} - {1} of {2}',
281                 emptyMsg: 'No features to display',
282                 plugins: [new Ext.ux.PageSizePlugin()]
283             }),
284             tbar: [{
285                 iconCls : 'infoBtn',
286                 text: 'Decision Tree',
287                 handler: function(button, event) {
288                     var result =  this.cachedTree;
289                    
290                      if(this.cachedTreeSummary==''){
291                      	Monk.component.messenger.show({ title: 'Results Summary',msg: 'Decision Tree not available.',
292                         width: 470, modal: false},this.window);
293                         return;                               
294                      }else{
295                        // if (this.chartWin == null) {
296                         this.chartWin = new Ext.Window({
297                             title: 'Decision Tree',
298                             height: 350,
299                             width: 475,
300                             modal: false,
301                             shadow: true,
302                             plain: true,
303                             border: false,
304                             collapsible: false,
305                             layout: 'fit',
306                             items: [
307                             {
308                             xtype: 'tabpanel',
309                             id:'tree-tabs',
310                             activeTab: 0,
311                             items:[
312                             {
313                             xtype: 'panel',
314                             title: 'Tree',
315                             id:'treeTab',
316                             border: false,
317                             autoScroll: true,
318                             html: '<div id="tree_img"></div>'
319                             },{
320                                 xtype:'panel',
321                                 title:'Summary',
322                                 id:'summaryTab',
323                                 border: false,
324                                 autoscroll:true,
325                                 html: '<div id="tree_summary"></div>'
326                             }
327                             ]
328                             }
329                           
330                           ]
331                         });
332                    //}
333                    // Workbench.console.info("tree img is: " + "/charts/c45/"+this.cachedTreeImage);
334                     this.chartWin.on('show', function(){
335                         Ext.getCmp('summaryTab').show();
336                         Ext.getCmp('treeTab').show();
337                     	Ext.get('tree_img').update('<img alt="Decision Tree" src="'+"/charts/c45/"+this.cachedTreeImage+'"/>');
338                         Ext.get('tree_summary').update("<pre>"+this.cachedTreeSummary+"</pre>");
339                                
340                                 
341                     }, this, {single: true});
342                     
343                     this.chartWin.show(button.getEl());
344                 }
345                 },
346                 scope: this
347             },{
348                 iconCls: 'exportBtn',
349                 text: 'Export Features',
350                 handler: function() {
351                     var worklist = Monk.component.dataManager.getWorklist();
352                     var output = ['Feature','POS','Avg. Freq Test','Avg. Freq Training'];
353                     output = output.concat(this.classNames).join("\t") + "\n";
354                     for (var i = 0, len = this.cachedData.length; i < len; i++) {
355                         var record = this.cachedData[i];
356                         
357                         var name = record.name.replace(/\s+\(.*?\)/g,'').replace(/\s+/g,' ');
358                         
359                         var re = /\(.*?\)/g;
360                         var pos = new Array();
361                         while ((match = re.exec(record.name)) != null) {
362                             pos.push(match[0]);
363                         }
364                         pos = pos.join(' ');
365                         
366                         var avgCountTest = record.avgFreqTestDocs;
367                         var avgCountTraining= record.avgFreqTrainingDocs;
368                         
369                         output += [name, pos, avgCountTest, avgCountTraining].join("\t");
370                         
371                         var classes = [];
372                         for (var j = 0, len2 = record.classes.entry.length; j < len2; j++) {
373                             classes.push(record.classes.entry[j]['double']);
374                         }
375                         output += classes.join("\t") + "\n";
376                     }
377                     Monk.component.messenger.show({
378                         title: 'Export Features as Tab Separated Values (TSV)',
379                         msg: 'Select all the data below, copy, and paste directly into your favourite spreadsheet program.',
380                         width: 600,
381                         defaultTextHeight: 25,
382                         buttons: Ext.MessageBox.OK,
383                         multiline: true,
384                         value: output,
385                         scope: this
386                     }, this.window.parent ? this.window.parent.window : this.window)
387                 },
388                 scope: this
389             }]
390         });
391         
392         this.featuresGrid.on('rowclick', function(grid, rowIndex, event){
393         	var record = grid.getSelectionModel().getSelected();
394             var featureInstance = record.get('name');
395             var posTest = /\(.+?\)$/;
396             var featureType = "Spelling";
397             if (featureInstance.match(posTest)) {
398                 featureType = "Lemma";
399             }
400             
401             var worksetId = Monk.component.dataManager.getWorksetId();
402             
403             var params = {
404                 worksetId: worksetId,
405                 searchType: 'both',
406                 sort: 'title_asc'
407             };
408             
409             if (featureType == 'Lemma') {
410                 params.lemmaPatternCriterion = featureInstance;
411             } else if (featureType == 'Spelling') {
412                 params.spellingPatternCriterion = featureInstance;
413             } else {
414             	Workbench.console.info(featureType+" not supported");
415             	return;
416                 //alert('unsupported feature type: '+featureType);
417             }
418             
419             if (this.chunksGrid != null) {
420                 this.chunksGrid.getStore().removeAll();
421             }
422             
423           //  Monk.data.chunk.getChunksContainingFeatureWithinWorkset(params);
424         
425               var featureParam = {
426                 term: featureInstance,
427                 type: featureType
428               };
429            
430               
431             this.notify(new Monk.event.chunk.FeatureSelected({
432                         label: 'Text feature selected: '+'"'+featureInstance+'"'
433                     }),
434                     featureParam
435                );
436 
437             
438             
439         }, this);
440         
441         var autoSizeColumns = function() {
442             for (var i = 0; i < this.colModel.getColumnCount(); i++) {
443                 autoSizeColumn(i, this);
444             }
445             this.store.removeListener('load', autoSizeColumns, this); // only run this the first time
446         }
447     
448         var autoSizeColumn = function(c, grid) {
449             var w = grid.view.getHeaderCell(c).firstChild.scrollWidth;
450             for (var i = 0, l = grid.store.getCount(); i < l; i++) {
451                 w = Math.max(w, grid.view.getCell(i, c).firstChild.scrollWidth);
452             }
453             grid.colModel.setColumnWidth(c, w+2);
454             return w;
455         }
456         
457         store.on('load', autoSizeColumns, this.featuresGrid);
458         
459         store.load({params:{start:0, limit: this.pageSize}});
460     },
461     
462     reset : function() {
463         if (this.featuresGrid != null)  this.featuresGrid.getStore().removeAll();
464         this.classNames = [];
465         this.cachedData = null;
466         this.cachedTree= null;
467         this.cachedTreeSummary=null;  
468         // get rid of the terms we were storing since we're getting new ones now
469         Monk.component.dataManager.clearTermsForChunks();
470     }
471     
472     
473 });