瀏覽代碼

HUE-9013 [editor] Move chart logic into the ko.resultChart component

Johan Ahlen 6 年之前
父節點
當前提交
40e37b7892

+ 18 - 14
desktop/core/src/desktop/js/apps/notebook/app.js

@@ -287,14 +287,16 @@ huePubSub.subscribe('app.dom.loaded', app => {
         }
       });
 
-      huePubSub.subscribeOnce('chart.hard.reset', () => {
-        // hard reset once the default opened chart
-        const oldChartX = snippet.chartX();
-        snippet.chartX(null);
-        window.setTimeout(() => {
-          snippet.chartX(oldChartX);
-        }, 0);
-      });
+      if (!window.ENABLE_NOTEBOOK_2) {
+        huePubSub.subscribeOnce('chart.hard.reset', () => {
+          // hard reset the default opened chart once
+          const oldChartX = snippet.chartX();
+          snippet.chartX(null);
+          window.setTimeout(() => {
+            snippet.chartX(oldChartX);
+          }, 0);
+        });
+      }
 
       return _dt;
     };
@@ -1487,12 +1489,14 @@ huePubSub.subscribe('app.dom.loaded', app => {
     });
 
     // TODO: Migrate to ko.resultChart.js for ENABLE_NOTEBOOK_2
-    $(document).on('forceChartDraw', (e, snippet) => {
-      window.setTimeout(() => {
-        snippet.chartX.notifySubscribers();
-        snippet.chartX.valueHasMutated();
-      }, 100);
-    });
+    if (!window.ENABLE_NOTEBOOK_2) {
+      $(document).on('forceChartDraw', (e, snippet) => {
+        window.setTimeout(() => {
+          snippet.chartX.notifySubscribers();
+          snippet.chartX.valueHasMutated();
+        }, 100);
+      });
+    }
 
     let hideTimeout = -1;
     $(document).on('hideAutocomplete', () => {

+ 31 - 31
desktop/core/src/desktop/js/apps/notebook2/components/ko.snippetResults.js

@@ -14,6 +14,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import ko from 'knockout';
+
 import componentUtils from 'ko/components/componentUtils';
 import DisposableComponent from 'ko/components/DisposableComponent';
 import I18n from 'utils/i18n';
@@ -23,6 +25,17 @@ import 'apps/notebook2/components/resultGrid/ko.resultGrid';
 
 export const NAME = 'snippet-results';
 
+const isNumericColumn = type =>
+  ['tinyint', 'smallint', 'int', 'bigint', 'float', 'double', 'decimal', 'real'].indexOf(type) !==
+  -1;
+
+const isDateTimeColumn = type => ['timestamp', 'date', 'datetime'].indexOf(type) !== -1;
+
+const isComplexColumn = type => ['array', 'map', 'struct'].indexOf(type) !== -1;
+
+const isStringColumn = type =>
+  !isNumericColumn(type) && !isDateTimeColumn(type) && !isComplexColumn(type);
+
 // prettier-ignore
 const TEMPLATE = `
 <div class="snippet-row">
@@ -149,27 +162,16 @@ const TEMPLATE = `
       </div>
       <div data-bind="visible: showChart" style="display: none;">
         <!-- ko component: { name: 'result-chart', params: {
-          chartLimit: chartLimit,
-          chartMapHeat: chartMapHeat,
-          chartMapLabel: chartMapLabel,
-          chartMapType: chartMapType,
-          chartScatterGroup: chartScatterGroup,
-          chartScatterSize: chartScatterSize,
-          chartScope: chartScope,
-          chartSorting: chartSorting,
-          chartTimelineType: chartTimelineType,
           chartType: chartType,
-          chartX: chartX,
-          chartXPivot: chartXPivot,
-          chartYMulti: chartYMulti,
-          chartYSingle: chartYSingle,
-          cleanedDateTimeMeta: cleanedDateTimeMeta,
           cleanedMeta: cleanedMeta,
+          cleanedDateTimeMeta: cleanedDateTimeMeta,
           cleanedNumericMeta: cleanedNumericMeta,
+          cleanedStringMeta: cleanedStringMeta,
           data: data,
           id: id,
           isResultSettingsVisible: isResultSettingsVisible,
-          meta: meta
+          meta: meta,
+          showChart: showChart
         } } --><!-- /ko -->
       </div>
     </div>
@@ -189,7 +191,6 @@ class SnippetResults extends DisposableComponent {
     this.images = params.images; // result
     this.showGrid = params.showGrid;
     this.showChart = params.showChart;
-    this.cleanedDateTimeMeta = params.cleanedDateTimeMeta;
     this.isResultSettingsVisible = params.isResultSettingsVisible;
     this.data = params.data; // result
     this.meta = params.meta; // result
@@ -204,22 +205,21 @@ class SnippetResults extends DisposableComponent {
     this.status = params.status;
 
     // Chart specific
-    this.chartLimit = params.chartLimit;
-    this.chartMapHeat = params.chartMapHeat;
-    this.chartMapLabel = params.chartMapLabel;
-    this.chartMapType = params.chartMapType;
-    this.chartScatterGroup = params.chartScatterGroup;
-    this.chartScatterSize = params.chartScatterSize;
-    this.chartScope = params.chartScope;
-    this.chartSorting = params.chartSorting;
-    this.chartTimelineType = params.chartTimelineType;
-    this.chartX = params.chartX;
-    this.chartXPivot = params.chartXPivot;
-    this.chartYMulti = params.chartYMulti;
-    this.chartYSingle = params.chartYSingle;
-    this.cleanedMeta = params.cleanedMeta;
-    this.cleanedNumericMeta = params.cleanedNumericMeta;
     this.id = params.id;
+
+    this.cleanedMeta = ko.pureComputed(() => this.meta().filter(item => item.name !== ''));
+
+    this.cleanedDateTimeMeta = ko.pureComputed(() =>
+      this.meta().filter(item => item.name !== '' && isDateTimeColumn(item.type))
+    );
+
+    self.cleanedStringMeta = ko.pureComputed(() =>
+      this.meta().filter(item => item.name !== '' && isStringColumn(item.type))
+    );
+
+    this.cleanedNumericMeta = ko.pureComputed(() =>
+      this.meta().filter(item => item.name !== '' && isNumericColumn(item.type))
+    );
   }
 }
 

+ 25 - 35
desktop/core/src/desktop/js/apps/notebook2/components/resultChart/chartTransformers.js

@@ -18,6 +18,10 @@ import $ from 'jquery';
 
 import HueColors from 'utils/hueColors';
 import { html2text } from 'utils/hueUtils';
+import {
+  CHART_MAP_TYPE,
+  CHART_SORTING
+} from 'apps/notebook2/components/resultChart/ko.resultChart';
 
 // The leaflet map can freeze the browser with numbers outside the map
 const MIN_LAT = -90;
@@ -59,9 +63,9 @@ export const pieChartTransformer = function(rawDatum) {
     });
   }
 
-  if (rawDatum.chartSorting() === 'asc') {
+  if (rawDatum.chartSorting() === CHART_SORTING.ASC) {
     data.sort((a, b) => a.value - b.value);
-  } else if (rawDatum.chartSorting() === 'desc') {
+  } else if (rawDatum.chartSorting() === CHART_SORTING.DESC) {
     data.sort((a, b) => b.value - a.value);
   }
 
@@ -127,34 +131,20 @@ export const leafletMapChartTransformer = function(rawDatum) {
       }
       return latIndex !== -1 && lngIndex !== -1 && labelIndex !== -1 && heatIndex !== -1;
     });
-    if (rawDatum.chartMapLabel() != null) {
-      rawDatum.data().forEach(item => {
-        if (isNotNullForCharts(item[latIndex]) && isNotNullForCharts(item[lngIndex])) {
-          datum.push({
-            lat: Math.min(Math.max(MIN_LAT, item[latIndex]), MAX_LAT),
-            lng: Math.min(Math.max(MIN_LNG, item[lngIndex]), MAX_LNG),
-            label: html2text(item[labelIndex]),
-            isHeat: rawDatum.chartMapType() === 'heat',
-            intensity:
-              heatIndex > -1 ? (!isNaN(item[heatIndex] * 1) ? item[heatIndex] * 1 : null) : null,
-            obj: item
-          });
-        }
-      });
-    } else {
-      rawDatum.data().forEach(item => {
-        if (isNotNullForCharts(item[latIndex]) && isNotNullForCharts(item[lngIndex])) {
-          datum.push({
-            lat: Math.min(Math.max(MIN_LAT, item[latIndex]), MAX_LAT),
-            lng: Math.min(Math.max(MIN_LNG, item[lngIndex]), MAX_LNG),
-            isHeat: rawDatum.chartMapType() === 'heat',
-            intensity:
-              heatIndex > -1 ? (!isNaN(item[heatIndex] * 1) ? item[heatIndex] * 1 : null) : null,
-            obj: item
-          });
-        }
-      });
-    }
+
+    rawDatum.data().forEach(item => {
+      if (isNotNullForCharts(item[latIndex]) && isNotNullForCharts(item[lngIndex])) {
+        datum.push({
+          lat: Math.min(Math.max(MIN_LAT, item[latIndex]), MAX_LAT),
+          lng: Math.min(Math.max(MIN_LNG, item[lngIndex]), MAX_LNG),
+          label: labelIndex !== -1 ? html2text(item[labelIndex]) : undefined,
+          isHeat: rawDatum.chartMapType() === CHART_MAP_TYPE.HEAT,
+          intensity:
+            heatIndex > -1 ? (!isNaN(item[heatIndex] * 1) ? item[heatIndex] * 1 : null) : null,
+          obj: item
+        });
+      }
+    });
   }
 
   if (rawDatum.chartLimit()) {
@@ -197,11 +187,11 @@ export const timelineChartTransformer = function(rawDatum) {
             });
           }
         });
-        if (rawDatum.chartSorting() === 'asc') {
+        if (rawDatum.chartSorting() === CHART_SORTING.ASC) {
           values.sort((a, b) => {
             return a.y - b.y;
           });
-        } else if (rawDatum.chartSorting() === 'desc') {
+        } else if (rawDatum.chartSorting() === CHART_SORTING.DESC) {
           values.sort((a, b) => {
             return b.y - a.y;
           });
@@ -318,7 +308,7 @@ export const multiSerieChartTransformer = function(rawDatum) {
         datum = datum.slice(0, rawDatum.chartLimit());
       }
 
-      if (rawDatum.chartSorting() === 'desc') {
+      if (rawDatum.chartSorting() === CHART_SORTING.DESC) {
         datum.forEach(d => {
           d.values.sort((a, b) => {
             if (a.x > b.x) {
@@ -375,11 +365,11 @@ export const multiSerieChartTransformer = function(rawDatum) {
                 });
               }
             });
-            if (rawDatum.chartSorting() === 'asc') {
+            if (rawDatum.chartSorting() === CHART_SORTING.ASC) {
               values.sort((a, b) => {
                 return a.y - b.y;
               });
-            } else if (rawDatum.chartSorting() === 'desc') {
+            } else if (rawDatum.chartSorting() === CHART_SORTING.DESC) {
               values.sort((a, b) => {
                 return b.y - a.y;
               });

+ 192 - 41
desktop/core/src/desktop/js/apps/notebook2/components/resultChart/ko.resultChart.js

@@ -18,6 +18,7 @@ import ko from 'knockout';
 
 import componentUtils from 'ko/components/componentUtils';
 import DisposableComponent from 'ko/components/DisposableComponent';
+import hueAnalytics from 'utils/hueAnalytics';
 import I18n from 'utils/i18n';
 import {
   leafletMapChartTransformer,
@@ -32,6 +33,37 @@ export const NAME = 'result-chart';
 
 const TYPES = window.HUE_CHARTS.TYPES;
 
+export const CHART_MAP_TYPE = {
+  HEAT: 'heat',
+  MARKER: 'marker'
+};
+
+export const CHART_SCOPE = {
+  AUS: 'aus',
+  BRA: 'bra',
+  CAN: 'can',
+  CHN: 'chn',
+  DEU: 'deu',
+  EUROPE: 'europe',
+  FRA: 'fra',
+  GBR: 'gbr',
+  ITA: 'ita',
+  JPN: 'jpn',
+  USA: 'usa',
+  WORLD: 'world'
+};
+
+export const CHART_SORTING = {
+  ASC: 'asc',
+  DESC: 'desc',
+  NONE: 'none'
+};
+
+export const CHART_TIMELINE_TYPE = {
+  BAR: 'bar',
+  LINE: 'line'
+};
+
 // prettier-ignore
 const TEMPLATE = `
 <div>
@@ -56,8 +88,8 @@ const TEMPLATE = `
               dropdownAutoWidth: true
             }
           ">
-          <option value="bar">${ I18n("Bars") }</option>
-          <option value="line">${ I18n("Lines") }</option>
+          <option value="${ CHART_TIMELINE_TYPE.BAR }">${ I18n("Bars") }</option>
+          <option value="${ CHART_TIMELINE_TYPE.LINE }">${ I18n("Lines") }</option>
         </select>
       </div>
       <!-- /ko -->
@@ -204,12 +236,12 @@ const TEMPLATE = `
               dropdownAutoWidth: true
             }
           ">
-          <option value="marker">${ I18n("Markers") }</option>
-          <option value="heat">${ I18n("Heatmap") }</option>
+          <option value="${ CHART_MAP_TYPE.MARKER }">${ I18n("Markers") }</option>
+          <option value="${ CHART_MAP_TYPE.HEAT }">${ I18n("Heatmap") }</option>
         </select>
       </div>
 
-      <!-- ko if: chartMapType() === 'marker' -->
+      <!-- ko if: chartMapType() === '${ CHART_MAP_TYPE.MARKER }' -->
       <ul class="nav nav-list" style="border: none; background-color: #FFF">
         <li class="nav-header">${ I18n('label') }</li>
       </ul>
@@ -230,7 +262,7 @@ const TEMPLATE = `
       </div>
       <!-- /ko -->
 
-      <!-- ko if: chartMapType() === 'heat' -->
+      <!-- ko if: chartMapType() === '${ CHART_MAP_TYPE.HEAT }' -->
       <ul class="nav nav-list" style="border: none; background-color: #FFF">
         <li class="nav-header">${ I18n('intensity') }</li>
       </ul>
@@ -307,18 +339,18 @@ const TEMPLATE = `
               dropdownAutoWidth: true
             }
           ">
-          <option value="world">${ I18n("World") }</option>
-          <option value="europe">${ I18n("Europe") }</option>
-          <option value="aus">${ I18n("Australia") }</option>
-          <option value="bra">${ I18n("Brazil") }</option>
-          <option value="can">${ I18n("Canada") }</option>
-          <option value="chn">${ I18n("China") }</option>
-          <option value="fra">${ I18n("France") }</option>
-          <option value="deu">${ I18n("Germany") }</option>
-          <option value="ita">${ I18n("Italy") }</option>
-          <option value="jpn">${ I18n("Japan") }</option>
-          <option value="gbr">${ I18n("UK") }</option>
-          <option value="usa">${ I18n("USA") }</option>
+          <option value="${ CHART_SCOPE.WORLD }">${ I18n("World") }</option>
+          <option value="${ CHART_SCOPE.EUROPE }">${ I18n("Europe") }</option>
+          <option value="${ CHART_SCOPE.AUS }">${ I18n("Australia") }</option>
+          <option value="${ CHART_SCOPE.BRA }">${ I18n("Brazil") }</option>
+          <option value="${ CHART_SCOPE.CAN }">${ I18n("Canada") }</option>
+          <option value="${ CHART_SCOPE.CHN }">${ I18n("China") }</option>
+          <option value="${ CHART_SCOPE.FRA }">${ I18n("France") }</option>
+          <option value="${ CHART_SCOPE.DEU }">${ I18n("Germany") }</option>
+          <option value="${ CHART_SCOPE.ITA }">${ I18n("Italy") }</option>
+          <option value="${ CHART_SCOPE.JPN }">${ I18n("Japan") }</option>
+          <option value="${ CHART_SCOPE.GBR }">${ I18n("UK") }</option>
+          <option value="${ CHART_SCOPE.USA }">${ I18n("USA") }</option>
         </select>
       </div>
       <!-- /ko -->
@@ -329,16 +361,16 @@ const TEMPLATE = `
       </ul>
       <div class="btn-group" data-toggle="buttons-radio">
         <a rel="tooltip" data-placement="top" title="${ I18n('No sorting') }" href="javascript:void(0)" class="btn" data-bind="
-            css: { 'active': chartSorting() === 'none' },
-            click: function() { chartSorting('none'); }
+            css: { 'active': chartSorting() === '${ CHART_SORTING.NONE }' },
+            click: function() { chartSorting('${ CHART_SORTING.NONE }'); }
           "><i class="fa fa-align-left fa-rotate-270"></i></a>
         <a rel="tooltip" data-placement="top" title="${ I18n('Sort ascending') }" href="javascript:void(0)" class="btn" data-bind="
-            css: { 'active': chartSorting() == 'asc' },
-            click: function() { chartSorting('asc'); }
+            css: { 'active': chartSorting() == '${ CHART_SORTING.ASC }' },
+            click: function() { chartSorting('${ CHART_SORTING.ASC }'); }
           "><i class="fa fa-sort-amount-asc fa-rotate-270"></i></a>
         <a rel="tooltip" data-placement="top" title="${ I18n('Sort descending') }" href="javascript:void(0)" class="btn" data-bind="
-            css: { 'active': chartSorting() == 'desc' },
-            click: function(){ chartSorting('desc'); }
+            css: { 'active': chartSorting() == '${ CHART_SORTING.DESC }' },
+            click: function(){ chartSorting('${ CHART_SORTING.DESC }'); }
           "><i class="fa fa-sort-amount-desc fa-rotate-270"></i></a>
       </div>
       <!-- /ko -->
@@ -391,27 +423,29 @@ class ResultChart extends DisposableComponent {
     this.data = params.data;
     this.id = params.id;
     this.isResultSettingsVisible = params.isResultSettingsVisible;
-    this.chartLimit = params.chartLimit;
-    this.chartMapHeat = params.chartMapHeat;
-    this.chartMapLabel = params.chartMapLabel;
-    this.chartMapType = params.chartMapType;
-    this.chartScatterGroup = params.chartScatterGroup;
-    this.chartScatterSize = params.chartScatterSize;
-    this.chartScope = params.chartScope;
-    this.chartSorting = params.chartSorting;
-    this.chartTimelineType = params.chartTimelineType;
     this.chartType = params.chartType;
-    this.chartX = params.chartX;
-    this.chartXPivot = params.chartXPivot;
-    this.chartYMulti = params.chartYMulti;
-    this.chartYSingle = params.chartYSingle;
 
     this.meta = params.meta;
     this.cleanedMeta = params.cleanedMeta;
     this.cleanedDateTimeMeta = params.cleanedDateTimeMeta;
     this.cleanedNumericMeta = params.cleanedNumericMeta;
+    this.cleanedStringMeta = params.cleanedNumericMeta;
 
+    this.chartLimit = ko.observable().extend({ notify: 'always' }); // TODO: Should be persisted
     this.chartLimits = ko.observableArray([5, 10, 25, 50, 100]);
+    this.chartMapHeat = ko.observable(); // TODO: Should be persisted
+    this.chartMapLabel = ko.observable(); // TODO: Should be persisted
+    this.chartMapType = ko.observable(CHART_MAP_TYPE.MARKER); // TODO: Should be persisted
+    this.chartScatterGroup = ko.observable(); // TODO: Should be persisted
+    this.chartScatterSize = ko.observable(); // TODO: Should be persisted
+    this.chartScope = ko.observable(CHART_SCOPE.WORLD); // TODO: Should be persisted
+    this.chartSorting = ko.observable(CHART_SORTING.NONE); // TODO: Should be persisted
+    this.chartTimelineType = ko.observable(CHART_TIMELINE_TYPE.BAR); // TODO: Should be persisted
+    this.chartX = ko.observable().extend({ notify: 'always' }); // TODO: Should be persisted
+    this.chartXPivot = ko.observable().extend({ notify: 'always' }); // TODO: Should be persisted
+    this.chartYMulti = ko.observableArray(); // TODO: Should be persisted
+    this.chartYSingle = ko.observable(); // TODO: Should be persisted
+    this.showChart = params.showChart;
 
     this.chartId = ko.pureComputed(() => this.chartType() + '_' + this.id());
     this.isBarChart = ko.pureComputed(() => TYPES.BARCHART === this.chartType());
@@ -438,10 +472,36 @@ class ResultChart extends DisposableComponent {
       );
     });
 
-    this.hasDataForChart.subscribe(() => {
-      this.chartX.notifySubscribers();
-      this.chartX.valueHasMutated();
-    });
+    this.trackKoSub(
+      this.meta.subscribe(() => {
+        if (this.chartX()) {
+          this.chartX(this.guessMetaField(this.chartX()));
+        }
+        if (this.chartXPivot()) {
+          this.chartXPivot(this.guessMetaField(this.chartXPivot()));
+        }
+        if (this.chartYSingle()) {
+          this.chartYSingle(this.guessMetaField(this.chartYSingle()));
+        }
+        if (this.chartMapLabel()) {
+          this.chartMapLabel(this.guessMetaField(this.chartMapLabel()));
+        }
+        if (this.chartYMulti()) {
+          this.chartYMulti(this.guessMetaFields(this.chartYMulti()));
+        }
+      })
+    );
+
+    this.trackKoSub(this.showChart.subscribe(this.prepopulateChart.bind(this)));
+    this.trackKoSub(this.chartType.subscribe(this.prepopulateChart.bind(this)));
+    this.trackKoSub(this.chartXPivot.subscribe(this.prepopulateChart.bind(this)));
+
+    this.trackKoSub(
+      this.hasDataForChart.subscribe(() => {
+        this.chartX.notifySubscribers();
+        this.chartX.valueHasMutated();
+      })
+    );
 
     this.hideStacked = ko.pureComputed(() => !this.chartYMulti().length);
 
@@ -522,6 +582,97 @@ class ResultChart extends DisposableComponent {
       group: this.chartScatterGroup()
     }));
   }
+
+  guessMetaField(originalField) {
+    let newField = undefined;
+    this.cleanedMeta().some(fld => {
+      if (
+        fld.name.toLowerCase() === originalField.toLowerCase() ||
+        originalField.toLowerCase() === fld.name.toLowerCase()
+      ) {
+        newField = fld.name;
+        return true;
+      }
+    });
+    return newField;
+  }
+
+  guessMetaFields(originalFields) {
+    const newFields = [];
+    originalFields.forEach(field => {
+      const newField = this.guessMetaField(field);
+      if (newField) {
+        newFields.push(newField);
+      }
+    });
+    return newFields;
+  }
+
+  prepopulateChart() {
+    const type = this.chartType();
+    hueAnalytics.log('notebook', 'chart/' + type);
+
+    if (this.isMapChart() && this.cleanedNumericMeta().length >= 2) {
+      if (this.chartX() === null || typeof this.chartX() === 'undefined') {
+        let name = this.cleanedNumericMeta()[0].name;
+        this.cleanedNumericMeta().some(column => {
+          if (
+            column.name.toLowerCase().indexOf('lat') > -1 ||
+            column.name.toLowerCase().indexOf('ltd') > -1
+          ) {
+            name = column.name;
+            return true;
+          }
+        });
+        this.chartX(name);
+      }
+      if (this.chartYSingle() === null || typeof this.chartYSingle() === 'undefined') {
+        let name = this.cleanedNumericMeta()[1].name;
+        this.cleanedNumericMeta().some(column => {
+          if (
+            column.name.toLowerCase().indexOf('lon') > -1 ||
+            column.name.toLowerCase().indexOf('lng') > -1
+          ) {
+            name = column.name;
+            return true;
+          }
+        });
+        this.chartYSingle(name);
+      }
+      return;
+    }
+
+    if (
+      (this.chartX() === null || typeof this.chartX() === 'undefined') &&
+      (this.isBarChart() || this.isPieChart() || this.isGradientMapChart()) &&
+      this.cleanedStringMeta().length
+    ) {
+      this.chartX(this.cleanedStringMeta()[0].name);
+    }
+
+    if (this.cleanedNumericMeta().length) {
+      if (!this.chartYMulti().length && (this.isBarChart() || this.isLineChart())) {
+        this.chartYMulti.push(
+          this.cleanedNumericMeta()[Math.min(this.cleanedNumericMeta().length - 1, 1)].name
+        );
+      } else if (
+        (this.chartYSingle() === null || typeof this.chartYSingle() === 'undefined') &&
+        (this.isPieChart() ||
+          this.isMapChart() ||
+          this.isGradientMapChart() ||
+          this.isScatterChart() ||
+          (this.isBarChart() && this.chartXPivot() !== null))
+      ) {
+        if (!this.chartYMulti().length) {
+          this.chartYSingle(
+            this.cleanedNumericMeta()[Math.min(this.cleanedNumericMeta().length - 1, 1)].name
+          );
+        } else {
+          this.chartYSingle(this.chartYMulti()[0]);
+        }
+      }
+    }
+  }
 }
 
 componentUtils.registerComponent(NAME, ResultChart, TEMPLATE);

+ 18 - 17
desktop/core/src/desktop/js/apps/notebook2/components/resultGrid/ko.resultGrid.js

@@ -158,14 +158,11 @@ class ResultGrid extends DisposableComponent {
       });
     };
 
-    adaptMeta();
-    const metaSub = this.meta.subscribe(() => {
-      adaptMeta();
-    });
-
-    this.disposals.push(() => {
-      metaSub.dispose();
-    });
+    this.trackKoSub(
+      this.meta.subscribe(() => {
+        adaptMeta();
+      })
+    );
 
     this.filteredMeta = ko.pureComputed(() => {
       if (!this.metaFilter() || this.metaFilter().query === '') {
@@ -202,12 +199,13 @@ class ResultGrid extends DisposableComponent {
       return this.filteredMeta().length;
     });
 
-    const dataSub = this.data.subscribe(() => {
-      this.render();
-    });
+    this.trackKoSub(
+      this.data.subscribe(() => {
+        this.render();
+      })
+    );
 
     this.disposals.push(() => {
-      dataSub.dispose();
       if (this.hueDatatable) {
         this.hueDatatable.fnDestroy();
       }
@@ -434,11 +432,14 @@ class ResultGrid extends DisposableComponent {
     this.disposals.push(() => {
       $scrollElement.off('scroll.resultGrid');
     });
-    this.isResultSettingsVisible.subscribe(newValue => {
-      if (newValue) {
-        dataScroll();
-      }
-    });
+
+    this.trackKoSub(
+      this.isResultSettingsVisible.subscribe(newValue => {
+        if (newValue) {
+          dataScroll();
+        }
+      })
+    );
 
     // huePubSub.subscribeOnce('chart.hard.reset', () => {
     //   // hard reset once the default opened chart

+ 0 - 22
desktop/core/src/desktop/js/apps/notebook2/editorViewModel.js

@@ -273,26 +273,6 @@ class EditorViewModel {
     $('#combinedContentModal' + self.suffix).modal('show');
   }
 
-  getPreviousChartOptions(snippet) {
-    return {
-      chartLimit: snippet.chartLimit() || snippet.previousChartOptions.chartLimit,
-      chartX: snippet.chartX() || snippet.previousChartOptions.chartX,
-      chartXPivot: snippet.chartXPivot() || snippet.previousChartOptions.chartXPivot,
-      chartYSingle: snippet.chartYSingle() || snippet.previousChartOptions.chartYSingle,
-      chartMapType: snippet.chartMapType() || snippet.previousChartOptions.chartMapType,
-      chartMapLabel: snippet.chartMapLabel() || snippet.previousChartOptions.chartMapLabel,
-      chartMapHeat: snippet.chartMapHeat() || snippet.previousChartOptions.chartMapHeat,
-      chartYMulti: snippet.chartYMulti() || snippet.previousChartOptions.chartYMulti,
-      chartScope: snippet.chartScope() || snippet.previousChartOptions.chartScope,
-      chartTimelineType:
-        snippet.chartTimelineType() || snippet.previousChartOptions.chartTimelineType,
-      chartSorting: snippet.chartSorting() || snippet.previousChartOptions.chartSorting,
-      chartScatterGroup:
-        snippet.chartScatterGroup() || snippet.previousChartOptions.chartScatterGroup,
-      chartScatterSize: snippet.chartScatterSize() || snippet.previousChartOptions.chartScatterSize
-    };
-  }
-
   getSnippetName(snippetType) {
     const self = this;
     const availableSnippets = self.availableSnippets();
@@ -362,8 +342,6 @@ class EditorViewModel {
           });
           snippet.result.statement_range.valueHasMutated();
         }
-
-        snippet.previousChartOptions = this.getPreviousChartOptions(snippet);
       });
 
       if (notebook.snippets()[0].result.data().length > 0) {

+ 0 - 1
desktop/core/src/desktop/js/apps/notebook2/notebook.js

@@ -155,7 +155,6 @@ export default class Notebook {
           snippet.status = 'ready'; // Protect from storm of check_statuses
           const _snippet = new Snippet(vm, this, snippet);
           _snippet.init();
-          _snippet.previousChartOptions = vm.getPreviousChartOptions(_snippet);
           this.presentationSnippets()[key] = _snippet;
         });
       }

+ 23 - 13
desktop/core/src/desktop/js/apps/notebook2/notebookSerde.js

@@ -68,20 +68,30 @@ export const notebookToJSON = notebook =>
       aceSize: snippet.aceSize(),
       associatedDocumentUuid: snippet.associatedDocumentUuid(),
       chartData: snippet.chartData(),
-      chartLimit: snippet.chartLimit(),
-      chartMapHeat: snippet.chartMapHeat(),
-      chartMapLabel: snippet.chartMapLabel(),
-      chartMapType: snippet.chartMapType(),
-      chartScatterGroup: snippet.chartScatterGroup(),
-      chartScatterSize: snippet.chartScatterSize(),
-      chartScope: snippet.chartScope(),
-      chartSorting: snippet.chartSorting(),
-      chartTimelineType: snippet.chartTimelineType(),
+      // chartLimit: snippet.chartLimit(), // TODO: Move somewhere else
+      // chartMapHeat: snippet.chartMapHeat(), // TODO: Move somewhere else
+      // chartMapLabel: snippet.chartMapLabel(), // TODO: Move somewhere else
+      // chartMapType: snippet.chartMapType(), // TODO: Move somewhere else
+      // chartScatterGroup: snippet.chartScatterGroup(), // TODO: Move somewhere else
+      // chartScatterSize: snippet.chartScatterSize(), // TODO: Move somewhere else
+      // chartScope: snippet.chartScope(), // TODO: Move somewhere else
+      // chartSorting: snippet.chartSorting(), // TODO: Move somewhere else
+      // chartTimelineType: snippet.chartTimelineType(), // TODO: Move somewhere else
+      //
+      // $.extend(
+      //   snippet,
+      //   snippet.chartType === 'lines' && {
+      //     // Retire line chart
+      //     chartType: 'bars',
+      //     chartTimelineType: 'line'
+      //   }
+      // );
+      //
       chartType: snippet.chartType(),
-      chartX: snippet.chartX(),
-      chartXPivot: snippet.chartXPivot(),
-      chartYMulti: snippet.chartYMulti(),
-      chartYSingle: snippet.chartYSingle(),
+      // chartX: snippet.chartX(), // TODO: Move somewhere else
+      // chartXPivot: snippet.chartXPivot(), // TODO: Move somewhere else
+      // chartYMulti: snippet.chartYMulti(), // TODO: Move somewhere else
+      // chartYSingle: snippet.chartYSingle(), // TODO: Move somewhere else
       compute: snippet.compute(),
       currentQueryTab: snippet.currentQueryTab(),
       database: snippet.database(),

+ 0 - 23
desktop/core/src/desktop/js/apps/notebook2/result.js

@@ -18,17 +18,6 @@ import ko from 'knockout';
 
 import { sleep, UUID } from 'utils/hueUtils';
 
-const isNumericColumn = type =>
-  ['tinyint', 'smallint', 'int', 'bigint', 'float', 'double', 'decimal', 'real'].indexOf(type) !==
-  -1;
-
-const isDateTimeColumn = type => ['timestamp', 'date', 'datetime'].indexOf(type) !== -1;
-
-const isComplexColumn = type => ['array', 'map', 'struct'].indexOf(type) !== -1;
-
-const isStringColumn = type =>
-  !isNumericColumn(type) && !isDateTimeColumn(type) && !isComplexColumn(type);
-
 class Result {
   constructor(result, snippet) {
     this.id = ko.observable(result.id || UUID());
@@ -57,7 +46,6 @@ class Result {
     // statements_count to 1. For the case when a selection is not starting at row 0.
     this.statements_count = ko.observable(1);
     this.previous_statement_hash = ko.observable(result.previous_statement_hash);
-    this.cleanedMeta = ko.pureComputed(() => this.meta().filter(item => item.name !== ''));
 
     this.fetchedOnce = ko.observable(!!result.fetchedOnce);
     this.startTime = ko.observable(result.startTime ? new Date(result.startTime) : new Date());
@@ -66,16 +54,6 @@ class Result {
       () => this.endTime().getTime() - this.startTime().getTime()
     );
 
-    this.cleanedNumericMeta = ko.pureComputed(() =>
-      this.meta().filter(item => item.name !== '' && isNumericColumn(item.type))
-    );
-    this.cleanedStringMeta = ko.pureComputed(() =>
-      this.meta().filter(item => item.name !== '' && isStringColumn(item.type))
-    );
-    this.cleanedDateTimeMeta = ko.pureComputed(() =>
-      this.meta().filter(item => item.name !== '' && isDateTimeColumn(item.type))
-    );
-
     this.data = ko.observableArray(result.data || []).extend({ rateLimit: 50 });
     this.explanation = ko.observable(result.explanation || '');
     this.images = ko.observableArray(result.images || []).extend({ rateLimit: 50 });
@@ -84,7 +62,6 @@ class Result {
     this.hasSomeResults = ko.pureComputed(() => this.hasResultset() && this.data().length > 0);
   }
 
-
   cancelBatchExecution() {
     this.statements_count(1);
     this.hasMore(false);

+ 1 - 148
desktop/core/src/desktop/js/apps/notebook2/snippet.js

@@ -750,15 +750,6 @@ export default class Snippet {
       return statement;
     });
 
-    $.extend(
-      snippet,
-      snippet.chartType === 'lines' && {
-        // Retire line chart
-        chartType: 'bars',
-        chartTimelineType: 'line'
-      }
-    );
-
     this.result = new Result(snippet.result || {}, this);
     if (!this.result.hasSomeResults()) {
       this.currentQueryTab('queryHistory');
@@ -796,7 +787,6 @@ export default class Snippet {
         this.isResultSettingsVisible(true);
         $(document).trigger('forceChartDraw', this);
         huePubSub.publish('editor.chart.shown', this);
-        this.prepopulateChart();
       }
     });
     this.showLogs.subscribe(val => {
@@ -818,26 +808,8 @@ export default class Snippet {
     this.is_redacted = ko.observable(!!snippet.is_redacted);
 
     this.chartType = ko.observable(snippet.chartType || window.HUE_CHARTS.TYPES.BARCHART);
-    this.chartType.subscribe(this.prepopulateChart.bind(this));
-    this.chartSorting = ko.observable(snippet.chartSorting || 'none');
-    this.chartScatterGroup = ko.observable(snippet.chartScatterGroup);
-    this.chartScatterSize = ko.observable(snippet.chartScatterSize);
-    this.chartScope = ko.observable(snippet.chartScope || 'world');
-    this.chartTimelineType = ko.observable(snippet.chartTimelineType || 'bar');
-    this.chartLimits = ko.observableArray([5, 10, 25, 50, 100]); // To Delete
-    this.chartLimit = ko.observable(snippet.chartLimit);
-    this.chartLimit.extend({ notify: 'always' });
-    this.chartX = ko.observable(snippet.chartX);
-    this.chartX.extend({ notify: 'always' });
-    this.chartXPivot = ko.observable(snippet.chartXPivot);
-    this.chartXPivot.extend({ notify: 'always' });
-    this.chartXPivot.subscribe(this.prepopulateChart.bind(this));
-    this.chartYSingle = ko.observable(snippet.chartYSingle);
-    this.chartYMulti = ko.observableArray(snippet.chartYMulti || []);
+
     this.chartData = ko.observableArray(snippet.chartData || []);
-    this.chartMapType = ko.observable(snippet.chartMapType || 'marker');
-    this.chartMapLabel = ko.observable(snippet.chartMapLabel);
-    this.chartMapHeat = ko.observable(snippet.chartMapHeat);
 
     this.chartType.subscribe(() => {
       $(document).trigger('forceChartDraw', this);
@@ -845,22 +817,6 @@ export default class Snippet {
 
     this.previousChartOptions = {};
 
-    this.result.meta.subscribe(() => {
-      this.chartLimit(this.previousChartOptions.chartLimit);
-      this.chartX(this.guessMetaField(this.previousChartOptions.chartX));
-      this.chartXPivot(this.previousChartOptions.chartXPivot);
-      this.chartYSingle(this.guessMetaField(this.previousChartOptions.chartYSingle));
-      this.chartMapType(this.previousChartOptions.chartMapType);
-      this.chartMapLabel(this.guessMetaField(this.previousChartOptions.chartMapLabel));
-      this.chartMapHeat(this.previousChartOptions.chartMapHeat);
-      this.chartYMulti(this.guessMetaFields(this.previousChartOptions.chartYMulti) || []);
-      this.chartSorting(this.previousChartOptions.chartSorting);
-      this.chartScatterGroup(this.previousChartOptions.chartScatterGroup);
-      this.chartScatterSize(this.previousChartOptions.chartScatterSize);
-      this.chartScope(this.previousChartOptions.chartScope);
-      this.chartTimelineType(this.previousChartOptions.chartTimelineType);
-    });
-
     this.isResultSettingsVisible = ko.observable(!!snippet.isResultSettingsVisible);
 
     this.isResultSettingsVisible.subscribe(() => {
@@ -1322,7 +1278,6 @@ export default class Snippet {
       this.lastAceSelectionRowOffset(Math.min(selectionRange.start.row, selectionRange.end.row));
     }
 
-    this.previousChartOptions = this.parentVm.getPreviousChartOptions(this);
     $(document).trigger('executeStarted', { vm: this.parentVm, snippet: this });
     $('.jHueNotify').remove();
     this.parentNotebook.forceHistoryInitialHeight(true);
@@ -1702,36 +1657,6 @@ export default class Snippet {
       });
   }
 
-  guessMetaField(field) {
-    let _fld = null;
-    if (field) {
-      if (this.result.cleanedMeta().length > 0) {
-        this.result.cleanedMeta().forEach(fld => {
-          if (
-            fld.name.toLowerCase() === field.toLowerCase() ||
-            field.toLowerCase() === fld.name.toLowerCase()
-          ) {
-            _fld = fld.name;
-          }
-        });
-      }
-    }
-    return _fld;
-  }
-
-  guessMetaFields(fields) {
-    const _fields = [];
-    if (fields) {
-      fields.forEach(fld => {
-        const _field = this.guessMetaField(fld);
-        if (_field) {
-          _fields.push(_field);
-        }
-      });
-    }
-    return _fields;
-  }
-
   handleAjaxError(data, callback) {
     if (data.status === -2) {
       // TODO: Session expired, check if handleAjaxError is used for ENABLE_NOTEBOOK_2
@@ -1916,78 +1841,6 @@ export default class Snippet {
     return true;
   }
 
-  prepopulateChart() {
-    const type = this.chartType();
-    hueAnalytics.log('notebook', 'chart/' + type);
-
-    if (type === window.HUE_CHARTS.TYPES.MAP && this.result.cleanedNumericMeta().length >= 2) {
-      if (this.chartX() === null || typeof this.chartX() === 'undefined') {
-        let name = this.result.cleanedNumericMeta()[0].name;
-        this.result.cleanedNumericMeta().forEach(fld => {
-          if (
-            fld.name.toLowerCase().indexOf('lat') > -1 ||
-            fld.name.toLowerCase().indexOf('ltd') > -1
-          ) {
-            name = fld.name;
-          }
-        });
-        this.chartX(name);
-      }
-      if (this.chartYSingle() === null || typeof this.chartYSingle() === 'undefined') {
-        let name = this.result.cleanedNumericMeta()[1].name;
-        this.result.cleanedNumericMeta().forEach(fld => {
-          if (
-            fld.name.toLowerCase().indexOf('lon') > -1 ||
-            fld.name.toLowerCase().indexOf('lng') > -1
-          ) {
-            name = fld.name;
-          }
-        });
-        this.chartYSingle(name);
-      }
-      return;
-    }
-
-    if (
-      (this.chartX() === null || typeof this.chartX() === 'undefined') &&
-      (type === window.HUE_CHARTS.TYPES.BARCHART ||
-        type === window.HUE_CHARTS.TYPES.PIECHART ||
-        type === window.HUE_CHARTS.TYPES.GRADIENTMAP) &&
-      this.result.cleanedStringMeta().length >= 1
-    ) {
-      this.chartX(this.result.cleanedStringMeta()[0].name);
-    }
-
-    if (this.result.cleanedNumericMeta().length > 0) {
-      if (
-        this.chartYMulti().length === 0 &&
-        (type === window.HUE_CHARTS.TYPES.BARCHART || type === window.HUE_CHARTS.TYPES.LINECHART)
-      ) {
-        this.chartYMulti.push(
-          this.result.cleanedNumericMeta()[Math.min(this.result.cleanedNumericMeta().length - 1, 1)]
-            .name
-        );
-      } else if (
-        (this.chartYSingle() === null || typeof this.chartYSingle() === 'undefined') &&
-        (type === window.HUE_CHARTS.TYPES.PIECHART ||
-          type === window.HUE_CHARTS.TYPES.MAP ||
-          type === window.HUE_CHARTS.TYPES.GRADIENTMAP ||
-          type === window.HUE_CHARTS.TYPES.SCATTERCHART ||
-          (type === window.HUE_CHARTS.TYPES.BARCHART && this.chartXPivot() !== null))
-      ) {
-        if (this.chartYMulti().length === 0) {
-          this.chartYSingle(
-            this.result.cleanedNumericMeta()[
-              Math.min(this.result.cleanedNumericMeta().length - 1, 1)
-            ].name
-          );
-        } else {
-          this.chartYSingle(this.chartYMulti()[0]);
-        }
-      }
-    }
-  }
-
   prevQueriesPage() {
     if (this.queriesCurrentPage() !== 1) {
       this.queriesCurrentPage(this.queriesCurrentPage() - 1);

+ 2 - 2
desktop/core/src/desktop/js/ko/bindings/charts/mapchart/ko.mapChart.js

@@ -46,8 +46,8 @@ ko.bindingHandlers.mapChart = {
 
       $element.height($element.width() / 2.23);
 
-      const _scope =
-        typeof _options.data.scope != 'undefined' ? String(_options.data.scope) : 'world';
+      const scopeObservable = _options.data.scope || _options.data.chartScope;
+      const _scope = scopeObservable ? String(ko.unwrap(scopeObservable)) : 'world';
       const _data = _options.transformer(_options.data);
       let _is2d = false;
       const _pivotCategories = [];

+ 6 - 0
desktop/core/src/desktop/js/ko/components/DisposableComponent.js

@@ -19,6 +19,12 @@ export default class DisposableComponent {
     this.disposals = [];
   }
 
+  trackKoSub(koSub) {
+    this.disposals.push(() => {
+      koSub.dispose();
+    });
+  }
+
   addDisposalCallback(callback) {
     this.disposals.push(callback);
   }

+ 0 - 179
desktop/libs/notebook/src/notebook/templates/editor_components2.mako

@@ -733,23 +733,7 @@
           <div class="tab-pane" id="queryResults" data-bind="css: {'active': currentQueryTab() == 'queryResults'}">
             <!-- ko if: ['text', 'jar', 'py', 'markdown'].indexOf(type()) === -1 -->
               <!-- ko component: { name: 'snippet-results', params: {
-                chartLimit: chartLimit,
-                chartMapHeat: chartMapHeat,
-                chartMapLabel: chartMapLabel,
-                chartMapType: chartMapType,
-                chartScatterGroup: chartScatterGroup,
-                chartScatterSize: chartScatterSize,
-                chartScope: chartScope,
-                chartSorting: chartSorting,
-                chartTimelineType: chartTimelineType,
                 chartType: chartType,
-                chartX: chartX,
-                chartXPivot: chartXPivot,
-                chartYMulti: chartYMulti,
-                chartYSingle: chartYSingle,
-                cleanedDateTimeMeta: result.cleanedDateTimeMeta,
-                cleanedMeta: result.cleanedMeta,
-                cleanedNumericMeta: result.cleanedNumericMeta,
                 data: result.data,
                 editorMode: parentVm.editorMode,
                 fetchResult: result.fetchMoreRows,
@@ -908,23 +892,7 @@
             <!-- /ko -->
             <!-- ko if: !$root.editorMode() && ['text', 'jar', 'java', 'distcp', 'shell', 'mapreduce', 'py', 'markdown'].indexOf(type()) === -1 -->
               <!-- ko component: { name: 'snippet-results', params: {
-                chartLimit: chartLimit,
-                chartMapHeat: chartMapHeat,
-                chartMapLabel: chartMapLabel,
-                chartMapType: chartMapType,
-                chartScatterGroup: chartScatterGroup,
-                chartScatterSize: chartScatterSize,
-                chartScope: chartScope,
-                chartSorting: chartSorting,
-                chartTimelineType: chartTimelineType,
                 chartType: chartType,
-                chartX: chartX,
-                chartXPivot: chartXPivot,
-                chartYMulti: chartYMulti,
-                chartYSingle: chartYSingle,
-                cleanedDateTimeMeta: result.cleanedDateTimeMeta,
-                cleanedMeta: result.cleanedMeta,
-                cleanedNumericMeta: result.cleanedNumericMeta,
                 data: result.data,
                 editorMode: parentVm.editorMode,
                 fetchResult: result.fetchMoreRows,
@@ -1227,153 +1195,6 @@
     </div>
   </script>
 
-
-  <script type="text/html" id="snippet-chart-settings${ suffix }">
-    <div>
-      <!-- ko if: chartType() != '' && [window.HUE_CHARTS.TYPES.TIMELINECHART, window.HUE_CHARTS.TYPES.BARCHART].indexOf(chartType()) >= 0  -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${_('type')}</li>
-      </ul>
-      <div data-bind="visible: chartType() != ''">
-        <select data-bind="selectedOptions: chartTimelineType, optionsCaption: '${_ko('Choose a type...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a type...") }', update: chartTimelineType, dropdownAutoWidth: true}">
-          <option value="bar">${ _("Bars") }</option>
-          <option value="line">${ _("Lines") }</option>
-        </select>
-      </div>
-      <!-- /ko -->
-
-      <!-- ko if: chartType() != '' && chartType() === window.HUE_CHARTS.TYPES.PIECHART -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != ''">
-        <li class="nav-header">${_('value')}</li>
-      </ul>
-      <div>
-        <select data-bind="options: result.cleanedNumericMeta, value: chartYSingle, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartYSingle, dropdownAutoWidth: true}" class="input-medium"></select>
-      </div>
-      <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != ''">
-        <li class="nav-header">${_('legend')}</li>
-      </ul>
-      <div>
-        <select data-bind="options: result.cleanedMeta, value: chartX, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartX, dropdownAutoWidth: true}" class="input-medium"></select>
-      </div>
-      <!-- /ko -->
-
-      <!-- ko if: chartType() != '' && chartType() !== window.HUE_CHARTS.TYPES.PIECHART -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != ''">
-        <li data-bind="visible: [window.HUE_CHARTS.TYPES.MAP, window.HUE_CHARTS.TYPES.GRADIENTMAP].indexOf(chartType()) == -1" class="nav-header">${_('x-axis')}</li>
-        <li data-bind="visible: chartType() == window.HUE_CHARTS.TYPES.GRADIENTMAP" class="nav-header">${_('region')}</li>
-        <li data-bind="visible: chartType() == window.HUE_CHARTS.TYPES.MAP" class="nav-header">${_('latitude')}</li>
-      </ul>
-      <div>
-        <select data-bind="options: ([window.HUE_CHARTS.TYPES.BARCHART, window.HUE_CHARTS.TYPES.GRADIENTMAP, window.HUE_CHARTS.TYPES.TIMELINECHART].indexOf(chartType()) > -1) ? ( chartType() == window.HUE_CHARTS.TYPES.TIMELINECHART ? result.cleanedDateTimeMeta : result.cleanedMeta ) : result.cleanedNumericMeta, value: chartX, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartX, dropdownAutoWidth: true}" class="input-medium"></select>
-      </div>
-
-      <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != ''">
-        <li data-bind="visible: [window.HUE_CHARTS.TYPES.MAP, window.HUE_CHARTS.TYPES.GRADIENTMAP].indexOf(chartType()) == -1" class="nav-header">${_('y-axis')}</li>
-        <li data-bind="visible: chartType() == window.HUE_CHARTS.TYPES.GRADIENTMAP" class="nav-header">${_('value')}</li>
-        <li data-bind="visible: chartType() == window.HUE_CHARTS.TYPES.MAP" class="nav-header">${_('longitude')}</li>
-      </ul>
-
-      <div style="max-height: 220px" data-bind="delayedOverflow, visible: chartType() != '' && ((chartType() == window.HUE_CHARTS.TYPES.BARCHART && !chartXPivot()) || chartType() == window.HUE_CHARTS.TYPES.LINECHART || chartType() == window.HUE_CHARTS.TYPES.TIMELINECHART)">
-        <ul class="unstyled" data-bind="foreach: result.cleanedNumericMeta" style="margin-bottom: 0">
-          <li><label class="checkbox"><input type="checkbox" data-bind="checkedValue: name, checked: $parent.chartYMulti" /> <span data-bind="text: $data.name"></span></label></li>
-        </ul>
-      </div>
-      <div data-bind="visible: (chartType() == window.HUE_CHARTS.TYPES.BARCHART && chartXPivot()) || chartType() == window.HUE_CHARTS.TYPES.MAP || chartType() == window.HUE_CHARTS.TYPES.GRADIENTMAP || chartType() == window.HUE_CHARTS.TYPES.SCATTERCHART">
-        <select data-bind="options: chartType() == window.HUE_CHARTS.TYPES.GRADIENTMAP ? result.cleanedMeta : result.cleanedNumericMeta, value: chartYSingle, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartYSingle, dropdownAutoWidth: true}" class="input-medium"></select>
-      </div>
-      <!-- /ko -->
-
-      <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() === window.HUE_CHARTS.TYPES.BARCHART">
-        <li class="nav-header">${_('group')}</li>
-      </ul>
-      <div data-bind="visible: chartType() === window.HUE_CHARTS.TYPES.BARCHART">
-        <select data-bind="options: result.cleanedMeta, value: chartXPivot, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column to pivot...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column to pivot...") }', update: chartXPivot, dropdownAutoWidth: true}" class="input-medium"></select>
-      </div>
-
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${_('limit')}</li>
-      </ul>
-      <div>
-        <select data-bind="options: chartLimits, value: chartLimit, optionsCaption: '${_ko('Limit the number of results to...')}', select2: { width: '100%', placeholder: '${ _ko("Limit the number of results to...") }', update: chartLimit, dropdownAutoWidth: true}" class="input-medium"></select>
-      </div>
-
-      <!-- ko if: chartType() != '' && chartType() == window.HUE_CHARTS.TYPES.MAP -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${_('type')}</li>
-      </ul>
-      <div data-bind="visible: chartType() != ''">
-        <select data-bind="selectedOptions: chartMapType, optionsCaption: '${_ko('Choose a type...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a type...") }', update: chartMapType, dropdownAutoWidth: true}">
-          <option value="marker">${ _("Markers") }</option>
-          <option value="heat">${ _("Heatmap") }</option>
-        </select>
-      </div>
-
-      <!-- ko if: chartMapType() == 'marker' -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${_('label')}</li>
-      </ul>
-      <div>
-        <select data-bind="options: result.cleanedMeta, value: chartMapLabel, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartMapLabel, dropdownAutoWidth: true}" class="input-medium"></select>
-      </div>
-      <!-- /ko -->
-
-      <!-- ko if: chartMapType() == 'heat' -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${_('intensity')}</li>
-      </ul>
-      <div>
-        <select data-bind="options: result.cleanedNumericMeta, value: chartMapHeat, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartMapHeat, dropdownAutoWidth: true}" class="input-medium"></select>
-      </div>
-      <!-- /ko -->
-      <!-- /ko -->
-
-      <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() == window.HUE_CHARTS.TYPES.SCATTERCHART">
-        <li class="nav-header">${_('scatter size')}</li>
-      </ul>
-      <div data-bind="visible: chartType() == window.HUE_CHARTS.TYPES.SCATTERCHART">
-        <select data-bind="options: result.cleanedNumericMeta, value: chartScatterSize, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartScatterSize, dropdownAutoWidth: true}" class="input-medium"></select>
-      </div>
-
-      <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != '' && chartType() == window.HUE_CHARTS.TYPES.SCATTERCHART">
-        <li class="nav-header">${_('scatter group')}</li>
-      </ul>
-      <div data-bind="visible: chartType() != '' && chartType() == window.HUE_CHARTS.TYPES.SCATTERCHART">
-        <select data-bind="options: result.cleanedMeta, value: chartScatterGroup, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartScatterGroup, dropdownAutoWidth: true}" class="input-medium"></select>
-      </div>
-
-      <!-- ko if: chartType() != '' && chartType() == window.HUE_CHARTS.TYPES.GRADIENTMAP -->
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header">${_('scope')}</li>
-      </ul>
-      <div data-bind="visible: chartType() != ''">
-        <select data-bind="selectedOptions: chartScope, optionsCaption: '${_ko('Choose a scope...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a scope...") }', update: chartScope, dropdownAutoWidth: true}">
-          <option value="world">${ _("World") }</option>
-          <option value="europe">${ _("Europe") }</option>
-          <option value="aus">${ _("Australia") }</option>
-          <option value="bra">${ _("Brazil") }</option>
-          <option value="can">${ _("Canada") }</option>
-          <option value="chn">${ _("China") }</option>
-          <option value="fra">${ _("France") }</option>
-          <option value="deu">${ _("Germany") }</option>
-          <option value="ita">${ _("Italy") }</option>
-          <option value="jpn">${ _("Japan") }</option>
-          <option value="gbr">${ _("UK") }</option>
-          <option value="usa">${ _("USA") }</option>
-        </select>
-      </div>
-      <!-- /ko -->
-
-      <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != '' && chartType() != window.HUE_CHARTS.TYPES.MAP && chartType() != window.HUE_CHARTS.TYPES.GRADIENTMAP && chartType() != window.HUE_CHARTS.TYPES.SCATTERCHART">
-        <li class="nav-header">${_('sorting')}</li>
-      </ul>
-      <div class="btn-group" data-toggle="buttons-radio" data-bind="visible: chartType() != '' && chartType() != window.HUE_CHARTS.TYPES.MAP && chartType() != window.HUE_CHARTS.TYPES.GRADIENTMAP && chartType() != window.HUE_CHARTS.TYPES.SCATTERCHART">
-        <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>
-    </div>
-  </script>
-
   <script type="text/html" id="snippet-explain${ suffix }">
     <pre class="no-margin-bottom" data-bind="text: result.explanation"></pre>
   </script>