Browse Source

HUE-8393 [report] Initial wiring for the document widget charting

Enrico Berti 7 years ago
parent
commit
3f4f308271

+ 1 - 0
desktop/libs/dashboard/src/dashboard/api.py

@@ -509,6 +509,7 @@ def _create_facet(collection, user, facet_id, facet_label, facet_field, widget_t
           'chartYMulti': [],
           'chartData': [],
           'chartMapLabel': None,
+          'chartSelectorType': 'bar'
         },
         "fieldsAttributes": [],
         "fieldsAttributesFilter": "",

+ 57 - 23
desktop/libs/dashboard/src/dashboard/static/dashboard/js/search.ko.js

@@ -799,22 +799,22 @@ var Collection = function (vm, collection) {
   };
 
   // Very top facet
-  self._addObservablesToFacet = function(facet, vm) {
+  self._addObservablesToFacet = function (facet, vm) {
     if (facet.properties && facet.properties.facets_form && facet.properties.facets_form.aggregate) { // Only Solr 5+
-      facet.properties.facets_form.aggregate.metrics = ko.computed(function() {
+      facet.properties.facets_form.aggregate.metrics = ko.computed(function () {
         var _field = self.getTemplateField(facet.properties.facets_form.field(), self.template.fieldsAttributes());
         return self._get_field_operations(_field, facet);
       });
 
       // Here we could weight the fields
-      facet.properties.facets_form.aggregate.facetFieldsNames = ko.computed(function() {
+      facet.properties.facets_form.aggregate.facetFieldsNames = ko.computed(function () {
         return self._getCompatibleMetricFields(facet.properties.facets_form);
-      }).extend({ trackArrayChanges: true });
+      }).extend({trackArrayChanges: true});
 
       facet.properties.facets_form.isEditing = ko.observable(true);
 
       if (facet.properties.facets) {
-        facet.properties.facets.subscribe(function(newValue) {
+        facet.properties.facets.subscribe(function (newValue) {
           vm.search();
         });
       }
@@ -844,20 +844,20 @@ var Collection = function (vm, collection) {
       facet.properties.compare.gap.subscribe(function () {
         vm.search();
       });
-     }
+    }
 
     // For Solr 5+  only
     if (typeof facet.template != 'undefined') {
-      facet.template.filteredAttributeFields = ko.computed(function() { // Dup of template.filteredAttributeFields
+      facet.template.filteredAttributeFields = ko.computed(function () { // Dup of template.filteredAttributeFields
         var _fields = [];
 
         var _iterable = facet.template.fieldsAttributes();
-        if (! facet.template.filteredAttributeFieldsAll()){
+        if (!facet.template.filteredAttributeFieldsAll()) {
           _iterable = facet.template.fields();
         }
 
         $.each(_iterable, function (index, field) {
-          if (facet.template.fieldsAttributesFilter() == "" || field.name().toLowerCase().indexOf(facet.template.fieldsAttributesFilter().toLowerCase()) > -1){
+          if (facet.template.fieldsAttributesFilter() == "" || field.name().toLowerCase().indexOf(facet.template.fieldsAttributesFilter().toLowerCase()) > -1) {
             _fields.push(field);
           }
         });
@@ -878,20 +878,50 @@ var Collection = function (vm, collection) {
         return _fields;
       });
 
-      facet.template.fieldsSelected.subscribe(function(newValue) { // Could be more efficient as we don't need to research, just redraw
+      facet.template.getMeta = function (extraCheck) {
+        return $.map(facet.template.fields(), function (field) {
+          var fieldType = field.type().toLowerCase();
+          if (fieldType.indexOf('_') > -1) {
+            fieldType = fieldType.split('_')[0];
+          }
+          if (typeof field !== 'undefined' && field.name() != '' && extraCheck(fieldType)) {
+            return field;
+          }
+        }).sort(function (a, b) {
+          return a.name().toLowerCase().localeCompare(b.name().toLowerCase());
+        });
+      }
+
+      facet.template.cleanedMeta = ko.computed(function () {
+        return facet.template.getMeta(alwaysTrue);
+      });
+
+      facet.template.cleanedNumericMeta = ko.computed(function () {
+        return facet.template.getMeta(isNumericColumn);
+      });
+
+      facet.template.cleanedStringMeta = ko.computed(function () {
+        return facet.template.getMeta(isStringColumn);
+      });
+
+      facet.template.cleanedDateTimeMeta = ko.computed(function () {
+        return facet.template.getMeta(isDateTimeColumn);
+      });
+
+      facet.template.fieldsSelected.subscribe(function (newValue) { // Could be more efficient as we don't need to research, just redraw
         if (newValue.length > 0) {
           vm.getFacetFromQuery(facet.id()).resultHash('');
           vm.search();
         }
       });
 
-      facet.template.chartSettings.chartType.subscribe(function(newValue) {
+      facet.template.chartSettings.chartType.subscribe(function (newValue) {
         facet.widgetType(
-            newValue == ko.HUE_CHARTS.TYPES.PIECHART ? 'pie2-widget' :
+          newValue == ko.HUE_CHARTS.TYPES.PIECHART ? 'pie2-widget' :
             (newValue == ko.HUE_CHARTS.TYPES.TIMELINECHART ? 'timeline-widget' :
-            (newValue == ko.HUE_CHARTS.TYPES.GRADIENTMAP ? 'gradient-map-widget' :
-            (newValue == ko.HUE_CHARTS.TYPES.COUNTER ? 'hit-widget' :
-            (newValue == ko.HUE_CHARTS.TYPES.TEXTSELECT ? 'text-facet-widget' : 'bucket-widget'))))
+              (newValue == ko.HUE_CHARTS.TYPES.GRADIENTMAP ? 'gradient-map-widget' :
+                (newValue == ko.HUE_CHARTS.TYPES.COUNTER ? 'hit-widget' :
+                  (newValue == ko.HUE_CHARTS.TYPES.TEXTSELECT ? 'text-facet-widget' : 'bucket-widget'))))
         );
       });
 
@@ -909,7 +939,8 @@ var Collection = function (vm, collection) {
         self._addObservablesToNestedFacet(facet, nestedFacet, vm, index);
       });
     }
-    function getFq () {
+
+    function getFq() {
       var _fq, fqs = vm.query && vm.query.fqs();
       for (var i = 0; fqs && i < fqs.length; i++) {
         var fq = fqs[i];
@@ -920,9 +951,11 @@ var Collection = function (vm, collection) {
       }
       return _fq;
     }
+
     facet.canReset = ko.computed(function () {
       var _fq = getFq();
-      function isNotInitial () {
+
+      function isNotInitial() {
         if (!facet.properties.canRange()) {
           return false;
         }
@@ -932,6 +965,7 @@ var Collection = function (vm, collection) {
           return facet.properties.start() !== facet.properties.initial_start() || facet.properties.end() !== facet.properties.initial_end();
         }
       }
+
       return _fq && _fq.filter().length || isNotInitial();
     });
     facet.canZoomIn = ko.computed(function () {
@@ -970,7 +1004,7 @@ var Collection = function (vm, collection) {
     return self.template.getMeta(isDateTimeColumn);
   });
 
-  self._addObservablesToNestedFacet = function(facet, nestedFacet, vm, index) {
+  self._addObservablesToNestedFacet = function (facet, nestedFacet, vm, index) {
     nestedFacet.limit.subscribe(function () {
       vm.search();
     });
@@ -986,7 +1020,7 @@ var Collection = function (vm, collection) {
         vm.search();
       });
 
-      nestedFacet.aggregate.metrics = ko.computed(function() {
+      nestedFacet.aggregate.metrics = ko.computed(function () {
         var _field = self.getTemplateField(nestedFacet.field(), self.template.fieldsAttributes());
         return self._get_field_operations(_field, facet);
       });
@@ -1003,17 +1037,17 @@ var Collection = function (vm, collection) {
         } else {
           return template.cleanedStringMeta();
         }
-      }).extend({ trackArrayChanges: true });
+      }).extend({trackArrayChanges: true});
     }
 
     nestedFacet.isEditing = ko.observable(false);
   }
 
-  self._getCompatibleMetricFields = function(nestedFacet) {
+  self._getCompatibleMetricFields = function (nestedFacet) {
     var fields = null;
 
     if (['avg', 'sum', 'median', 'percentile', 'stddev', 'variance'].indexOf(nestedFacet.aggregate.function()) != -1) {
-      fields = $.grep(self.template.fieldsAttributes(), function(field) {
+      fields = $.grep(self.template.fieldsAttributes(), function (field) {
         return isNumericColumn(field.type()) || isDateTimeColumn(field.type());
       })
     } else {
@@ -1022,7 +1056,7 @@ var Collection = function (vm, collection) {
 
     return fields.sort(function (a, b) {
       return a.name().toLowerCase().localeCompare(b.name().toLowerCase());
-   });
+    });
   };
 
   self.facets = ko.mapping.fromJS(collection.facets);

+ 50 - 32
desktop/libs/dashboard/src/dashboard/templates/common_search.mako

@@ -872,57 +872,57 @@ ${ dashboard.layout_skeleton(suffix='search') }
 
 
 <script type="text/html" id="grid-chart-settings">
-<!-- ko if: $parent.widgetType() == 'resultset-widget' -->
-  <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: [ko.HUE_CHARTS.TYPES.TIMELINECHART, ko.HUE_CHARTS.TYPES.BARCHART].indexOf(chartType()) >= 0">
+<!-- ko if: $parent.widgetType() === 'resultset-widget' || $parent.widgetType() === 'document-widget' -->
+  <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: [ko.HUE_CHARTS.TYPES.TIMELINECHART, ko.HUE_CHARTS.TYPES.BARCHART].indexOf(chartSettings.chartType()) >= 0">
     <li class="nav-header">${_('Chart Type')}</li>
   </ul>
-  <div data-bind="visible: [ko.HUE_CHARTS.TYPES.TIMELINECHART, ko.HUE_CHARTS.TYPES.BARCHART].indexOf(chartType()) >= 0">
+  <div data-bind="visible: [ko.HUE_CHARTS.TYPES.TIMELINECHART, ko.HUE_CHARTS.TYPES.BARCHART].indexOf(chartSettings.chartType()) >= 0">
     <select class="input-medium" data-bind="options: $root.timelineChartTypes,
                  optionsText: 'label',
                  optionsValue: 'value',
-                 value: $root.collection.template.chartSettings.chartSelectorType">
+                 value: chartSettings.chartSelectorType">
     </select>
   </div>
-  <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != ''">
-    <li data-bind="visible: [ko.HUE_CHARTS.TYPES.MAP, ko.HUE_CHARTS.TYPES.GRADIENTMAP, ko.HUE_CHARTS.TYPES.PIECHART].indexOf(chartType()) == -1" class="nav-header">${_('x-axis')}</li>
-    <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP" class="nav-header">${_('region')}</li>
-    <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.MAP" class="nav-header">${_('latitude')}</li>
-    <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.PIECHART" class="nav-header">${_('legend')}</li>
+  <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartSettings.chartType() != ''">
+    <li data-bind="visible: [ko.HUE_CHARTS.TYPES.MAP, ko.HUE_CHARTS.TYPES.GRADIENTMAP, ko.HUE_CHARTS.TYPES.PIECHART].indexOf(chartSettings.chartType()) == -1" class="nav-header">${_('x-axis')}</li>
+    <li data-bind="visible: chartSettings.chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP" class="nav-header">${_('region')}</li>
+    <li data-bind="visible: chartSettings.chartType() == ko.HUE_CHARTS.TYPES.MAP" class="nav-header">${_('latitude')}</li>
+    <li data-bind="visible: chartSettings.chartType() == ko.HUE_CHARTS.TYPES.PIECHART" class="nav-header">${_('legend')}</li>
   </ul>
-  <div data-bind="visible: chartType() != ''">
-    <select data-bind="options: (chartType() == ko.HUE_CHARTS.TYPES.BARCHART || chartType() == ko.HUE_CHARTS.TYPES.PIECHART) ? $root.collection.template.cleanedMeta : chartType() == ko.HUE_CHARTS.TYPES.TIMELINECHART ? $root.collection.template.cleanedDateTimeMeta : $root.collection.template.cleanedNumericMeta, value: chartX, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartX}" class="input-medium"></select>
+  <div data-bind="visible: chartSettings.chartType() != ''">
+    <select data-bind="options: (chartSettings.chartType() == ko.HUE_CHARTS.TYPES.BARCHART || chartSettings.chartType() == ko.HUE_CHARTS.TYPES.PIECHART) ? cleanedMeta : chartSettings.chartType() == ko.HUE_CHARTS.TYPES.TIMELINECHART ? cleanedDateTimeMeta : cleanedNumericMeta, value: chartSettings.chartX, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartSettings.chartX}" class="input-medium"></select>
   </div>
 
-  <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != ''">
-    <li data-bind="visible: [ko.HUE_CHARTS.TYPES.MAP, ko.HUE_CHARTS.TYPES.GRADIENTMAP, ko.HUE_CHARTS.TYPES.PIECHART].indexOf(chartType()) == -1" class="nav-header">${_('y-axis')}</li>
-    <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.MAP" class="nav-header">${_('longitude')}</li>
-    <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.PIECHART" class="nav-header">${_('value')}</li>
+  <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartSettings.chartType() != ''">
+    <li data-bind="visible: [ko.HUE_CHARTS.TYPES.MAP, ko.HUE_CHARTS.TYPES.GRADIENTMAP, ko.HUE_CHARTS.TYPES.PIECHART].indexOf(chartSettings.chartType()) == -1" class="nav-header">${_('y-axis')}</li>
+    <li data-bind="visible: chartSettings.chartType() == ko.HUE_CHARTS.TYPES.MAP" class="nav-header">${_('longitude')}</li>
+    <li data-bind="visible: chartSettings.chartType() == ko.HUE_CHARTS.TYPES.PIECHART" class="nav-header">${_('value')}</li>
   </ul>
 
-  <div style="overflow-y: auto; max-height: 220px" data-bind="visible: chartType() != '' && ([ko.HUE_CHARTS.TYPES.TIMELINECHART, ko.HUE_CHARTS.TYPES.BARCHART, ko.HUE_CHARTS.TYPES.LINECHART].indexOf(chartType()) >= 0 )">
-    <ul class="unstyled" data-bind="foreach: $root.collection.template.cleanedNumericMeta">
-      <li><input type="checkbox" data-bind="checkedValue: name, checked: $parent.chartYMulti" /> <span data-bind="text: $data.name"></span></li>
+  <div style="overflow-y: auto; max-height: 220px" data-bind="visible: chartSettings.chartType() != '' && ([ko.HUE_CHARTS.TYPES.TIMELINECHART, ko.HUE_CHARTS.TYPES.BARCHART, ko.HUE_CHARTS.TYPES.LINECHART].indexOf(chartSettings.chartType()) >= 0 )">
+    <ul class="unstyled" data-bind="foreach: cleanedNumericMeta">
+      <li><input type="checkbox" data-bind="checkedValue: name, checked: $parent.chartSettings.chartYMulti" /> <span data-bind="text: $data.name"></span></li>
     </ul>
   </div>
-  <div data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.PIECHART || chartType() == ko.HUE_CHARTS.TYPES.MAP">
-    <select data-bind="options: chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP ? $root.collection.template.cleanedMeta : $root.collection.template.cleanedNumericMeta, value: chartYSingle, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartYSingle}" class="input-medium"></select>
+  <div data-bind="visible: chartSettings.chartType() == ko.HUE_CHARTS.TYPES.PIECHART || chartSettings.chartType() == ko.HUE_CHARTS.TYPES.MAP">
+    <select data-bind="options: chartSettings.chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP ? cleanedMeta : cleanedNumericMeta, value: chartSettings.chartYSingle, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartSettings.chartYSingle}" class="input-medium"></select>
   </div>
 
-  <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != '' && chartType() == ko.HUE_CHARTS.TYPES.MAP">
+  <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartSettings.chartType() != '' && chartSettings.chartType() == ko.HUE_CHARTS.TYPES.MAP">
     <li class="nav-header">${_('label')}</li>
   </ul>
-  <div data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.MAP">
-    <select data-bind="options: $root.collection.template.cleanedMeta, value: chartMapLabel, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartMapLabel}" class="input-medium"></select>
+  <div data-bind="visible: chartSettings.chartType() == ko.HUE_CHARTS.TYPES.MAP">
+    <select data-bind="options: cleanedMeta, value: chartSettings.chartMapLabel, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartSettings.chartMapLabel}" class="input-medium"></select>
   </div>
 
 
-  <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != '' && chartType() != ko.HUE_CHARTS.TYPES.MAP">
+  <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartSettings.chartType() != '' && chartSettings.chartType() != ko.HUE_CHARTS.TYPES.MAP">
     <li class="nav-header">${_('sorting')}</li>
   </ul>
-  <div class="btn-group" data-toggle="buttons-radio" data-bind="visible: chartType() != '' && chartType() != ko.HUE_CHARTS.TYPES.MAP">
-    <a rel="tooltip" data-placement="top" title="${_('No sorting')}" href="javascript:void(0)" class="btn" data-bind="css: {'active': chartSorting() == 'none'}, click: function(){ chartSorting('none'); }"><i class="fa fa-align-left fa-rotate-270"></i></a>
-    <a rel="tooltip" data-placement="top" title="${_('Sort ascending')}" href="javascript:void(0)" class="btn" data-bind="css: {'active': chartSorting() == 'asc'}, click: function(){ chartSorting('asc'); }"><i class="fa fa-sort-amount-asc fa-rotate-270"></i></a>
-    <a rel="tooltip" data-placement="top" title="${_('Sort descending')}" href="javascript:void(0)" class="btn" data-bind="css: {'active': chartSorting() == 'desc'}, click: function(){ chartSorting('desc'); }"><i class="fa fa-sort-amount-desc fa-rotate-270"></i></a>
+  <div class="btn-group" data-toggle="buttons-radio" data-bind="visible: chartSettings.chartType() != '' && chartSettings.chartType() != ko.HUE_CHARTS.TYPES.MAP">
+    <a rel="tooltip" data-placement="top" title="${_('No sorting')}" href="javascript:void(0)" class="btn" data-bind="css: {'active': chartSettings.chartSorting() == 'none'}, click: function(){ chartSettings.chartSorting('none'); }"><i class="fa fa-align-left fa-rotate-270"></i></a>
+    <a rel="tooltip" data-placement="top" title="${_('Sort ascending')}" href="javascript:void(0)" class="btn" data-bind="css: {'active': chartSettings.chartSorting() == 'asc'}, click: function(){ chartSettings.chartSorting('asc'); }"><i class="fa fa-sort-amount-asc fa-rotate-270"></i></a>
+    <a rel="tooltip" data-placement="top" title="${_('Sort descending')}" href="javascript:void(0)" class="btn" data-bind="css: {'active': chartSettings.chartSorting() == 'desc'}, click: function(){ chartSettings.chartSorting('desc'); }"><i class="fa fa-sort-amount-desc fa-rotate-270"></i></a>
   </div>
 <!-- /ko -->
 
@@ -1458,7 +1458,7 @@ ${ dashboard.layout_skeleton(suffix='search') }
           </ul>
         </div>
 
-        <div data-bind="visible: template.showGrid() || (template.showChart() && widgetType() === 'resultset-widget')">
+        <div data-bind="visible: template.showGrid() || (template.showChart() && (widgetType() === 'resultset-widget' || widgetType() === 'document-widget'))">
           <a class="grid-side-btn" href="javascript:void(0)" data-bind="click: function(){ template.showFieldList(!template.showFieldList())}, css: { 'blue' : template.showFieldList() }">
             <!-- ko if: template.showFieldList() -->
               <i class="fa fa-fw fa-chevron-left"></i>
@@ -1545,8 +1545,8 @@ ${ dashboard.layout_skeleton(suffix='search') }
         </div>
       </div>
 
-      <div data-bind="visible: template.showFieldList() && template.showChart() &&  widgetType() === 'resultset-widget'" style="float:left; width:200px; margin-right:10px; background-color:#FFF; padding:5px;">
-        <span data-bind="template: {name: 'grid-chart-settings', data: template.chartSettings}"></span>
+      <div data-bind="visible: template.showFieldList() && template.showChart() && (widgetType() === 'resultset-widget' || widgetType() === 'document-widget')" style="float:left; width:200px; margin-right:10px; background-color:#FFF; padding:5px;">
+        <span data-bind="template: {name: 'grid-chart-settings', data: template}"></span>
       </div>
     </span>
 
@@ -1745,6 +1745,24 @@ ${ dashboard.layout_skeleton(suffix='search') }
           <!-- /ko -->
         <!-- /ko -->
 
+        <!-- ko if: widgetType() == 'document-widget' -->
+          <div data-bind="attr:{'id': 'pieChart_'+id()}, pieChart: {data: {counts: $parent.results(), sorting: template.chartSettings.chartSorting(), snippet: $data}, fqs: ko.observableArray([]),
+                transformer: pieChartDataTransformerGrid, maxWidth: 350, parentSelector: '.chart-container' }, visible: template.chartSettings.chartType() == ko.HUE_CHARTS.TYPES.PIECHART" class="chart"></div>
+
+          <div data-bind="attr:{'id': 'barChart_'+id()}, barChart: {datum: {counts: $parent.results(), sorting: template.chartSettings.chartSorting(), snippet: $data}, fqs: ko.observableArray([]), hideSelection: true, enableSelection: false, hideStacked: template.chartSettings.hideStacked,
+                transformer: multiSerieDataTransformerGrid, stacked: false, showLegend: true, type: template.chartSettings.chartSelectorType},  stacked: true, showLegend: true, visible: template.chartSettings.chartType() == ko.HUE_CHARTS.TYPES.BARCHART" class="chart"></div>
+
+          <div data-bind="attr:{'id': 'lineChart_'+id()}, lineChart: {datum: {counts: $parent.results(), sorting: template.chartSettings.chartSorting(), snippet: $data},
+                transformer: multiSerieDataTransformerGrid, showControls: false }, visible: template.chartSettings.chartType() == ko.HUE_CHARTS.TYPES.LINECHART" class="chart"></div>
+
+          <div data-bind="attr: {'id': 'leafletMapChart_'+id()}, leafletMapChart: {datum: {counts: $parent.results(), sorting: $root.collection.template.chartSettings.chartSorting(), snippet: $data},
+                transformer: leafletMapChartDataTransformerGrid, showControls: false, height: 380, visible: template.chartSettings.chartType() == ko.HUE_CHARTS.TYPES.MAP, forceRedraw: true}" class="chart"></div>
+
+          <div data-bind="attr:{'id': 'timelineChart_'+id()}, timelineChart: {datum: {counts: $parent.results(), sorting: $root.collection.template.chartSettings.chartSorting(), snippet: $data}, fqs: ko.observableArray([]), hideSelection: true, enableSelection: false, hideStacked: template.chartSettings.hideStacked,
+                transformer: multiSerieDataTransformerGrid, showControls: false }, visible: template.chartSettings.chartType() == ko.HUE_CHARTS.TYPES.TIMELINECHART" class="chart"></div>
+          <div class="clearfix"></div>
+        <!-- /ko -->
+
         <!-- ko if: widgetType() == 'resultset-widget' -->
           <div data-bind="attr:{'id': 'pieChart_'+id()}, pieChart: {data: {counts: $root.results(), sorting: $root.collection.template.chartSettings.chartSorting(), snippet: $data}, fqs: ko.observableArray([]),
                 transformer: pieChartDataTransformerGrid, maxWidth: 350, parentSelector: '.chart-container' }, visible: $root.collection.template.chartSettings.chartType() == ko.HUE_CHARTS.TYPES.PIECHART" class="chart"></div>
@@ -1784,7 +1802,7 @@ ${ dashboard.layout_skeleton(suffix='search') }
     <div>
 
       <input type="text" class="input-medium" data-bind="value: properties.engine"/>
-      <textarea data-bind="value: properties.statement"><textarea/>
+      <textarea data-bind="value: properties.statement"></textarea>
 
       ## Get sub widget by ID
       ## <div data-bind="template: { name: function() { return widgetType(); }}" class="widget-main-section"></div>