Browse Source

HUE-9013 [editor] Extract chart component for notebook 2

Johan Ahlen 6 years ago
parent
commit
9590af8634

+ 465 - 0
desktop/core/src/desktop/js/apps/notebook2/components/resultChart/chartTransformers.js

@@ -0,0 +1,465 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import $ from 'jquery';
+
+import HueColors from 'utils/hueColors';
+import hueUtils from 'utils/hueUtils';
+
+const isNotNullForCharts = val => val !== 'NULL' && val !== null;
+
+export const pieChartTransformer = function(rawDatum) {
+  let _data = [];
+
+  if (rawDatum.snippet.chartX() != null && rawDatum.snippet.chartYSingle() != null) {
+    let _idxValue = -1;
+    let _idxLabel = -1;
+    rawDatum.snippet.result.meta().forEach((col, idx) => {
+      if (col.name === rawDatum.snippet.chartX()) {
+        _idxLabel = idx;
+      }
+      if (col.name === rawDatum.snippet.chartYSingle()) {
+        _idxValue = idx;
+      }
+    });
+    const colors = HueColors.cuiD3Scale();
+    $(rawDatum.counts()).each((cnt, item) => {
+      if (isNotNullForCharts(item[_idxValue])) {
+        let val = item[_idxValue] * 1;
+        if (isNaN(val)) {
+          val = 0;
+        }
+        _data.push({
+          label: hueUtils.html2text(item[_idxLabel]),
+          value: val,
+          color: colors[cnt % colors.length],
+          obj: item
+        });
+      }
+    });
+  }
+
+  if (rawDatum.sorting === 'asc') {
+    _data.sort((a, b) => a.value - b.value);
+  } else if (rawDatum.sorting === 'desc') {
+    _data.sort((a, b) => b.value - a.value);
+  }
+
+  if (rawDatum.snippet.chartLimit()) {
+    _data = _data.slice(0, rawDatum.snippet.chartLimit());
+  }
+
+  return _data;
+};
+
+export const mapChartTransformer = function(rawDatum) {
+  let _data = [];
+  if (rawDatum.snippet.chartX() != null && rawDatum.snippet.chartYSingle() != null) {
+    let _idxRegion = -1;
+    let _idxValue = -1;
+    rawDatum.snippet.result.meta().forEach((col, idx) => {
+      if (col.name === rawDatum.snippet.chartX()) {
+        _idxRegion = idx;
+      }
+      if (col.name === rawDatum.snippet.chartYSingle()) {
+        _idxValue = idx;
+      }
+    });
+
+    $(rawDatum.counts()).each((cnt, item) => {
+      if (isNotNullForCharts(item[_idxValue]) && isNotNullForCharts(item[_idxRegion])) {
+        _data.push({
+          label: item[_idxRegion],
+          value: item[_idxValue],
+          obj: item
+        });
+      }
+    });
+  }
+
+  if (rawDatum.snippet.chartLimit()) {
+    _data = _data.slice(0, rawDatum.snippet.chartLimit());
+  }
+
+  return _data;
+};
+
+// The leaflet map can freeze the browser with numbers outside the map
+const MIN_LAT = -90;
+const MAX_LAT = 90;
+const MIN_LNG = -180;
+const MAX_LNG = 180;
+
+export const leafletMapChartTransformer = function(rawDatum) {
+  let _data = [];
+  if (rawDatum.snippet.chartX() != null && rawDatum.snippet.chartYSingle() != null) {
+    let _idxLat = -1;
+    let _idxLng = -1;
+    let _idxLabel = -1;
+    let _idxHeat = -1;
+    rawDatum.snippet.result.meta().forEach((col, idx) => {
+      if (col.name === rawDatum.snippet.chartX()) {
+        _idxLat = idx;
+      }
+      if (col.name === rawDatum.snippet.chartYSingle()) {
+        _idxLng = idx;
+      }
+      if (col.name === rawDatum.snippet.chartMapLabel()) {
+        _idxLabel = idx;
+      }
+      if (col.name === rawDatum.snippet.chartMapHeat()) {
+        _idxHeat = idx;
+      }
+    });
+    if (rawDatum.snippet.chartMapLabel() != null) {
+      $(rawDatum.counts()).each((cnt, item) => {
+        if (isNotNullForCharts(item[_idxLat]) && isNotNullForCharts(item[_idxLng])) {
+          _data.push({
+            lat: Math.min(Math.max(MIN_LAT, item[_idxLat]), MAX_LAT),
+            lng: Math.min(Math.max(MIN_LNG, item[_idxLng]), MAX_LNG),
+            label: hueUtils.html2text(item[_idxLabel]),
+            isHeat: rawDatum.snippet.chartMapType() === 'heat',
+            intensity:
+              _idxHeat > -1 ? (item[_idxHeat] * 1 != NaN ? item[_idxHeat] * 1 : null) : null,
+            obj: item
+          });
+        }
+      });
+    } else {
+      $(rawDatum.counts()).each((cnt, item) => {
+        if (isNotNullForCharts(item[_idxLat]) && isNotNullForCharts(item[_idxLng])) {
+          _data.push({
+            lat: Math.min(Math.max(MIN_LAT, item[_idxLat]), MAX_LAT),
+            lng: Math.min(Math.max(MIN_LNG, item[_idxLng]), MAX_LNG),
+            isHeat: rawDatum.snippet.chartMapType() === 'heat',
+            intensity:
+              _idxHeat > -1 ? (item[_idxHeat] * 1 != NaN ? item[_idxHeat] * 1 : null) : null,
+            obj: item
+          });
+        }
+      });
+    }
+  }
+
+  if (rawDatum.snippet.chartLimit()) {
+    _data = _data.slice(0, rawDatum.snippet.chartLimit());
+  }
+
+  return _data;
+};
+
+export const timelineChartTransformer = function(rawDatum) {
+  const _datum = [];
+  let _plottedSerie = 0;
+
+  rawDatum.snippet.result.meta().forEach(meta => {
+    if (rawDatum.snippet.chartYMulti().indexOf(meta.name) > -1) {
+      const col = meta.name;
+      let _idxValue = -1;
+      let _idxLabel = -1;
+      rawDatum.snippet.result.meta().forEach((icol, idx) => {
+        if (icol.name === rawDatum.snippet.chartX()) {
+          _idxLabel = idx;
+        }
+        if (icol.name === col) {
+          _idxValue = idx;
+        }
+      });
+
+      if (_idxValue > -1) {
+        let _data = [];
+        const colors = HueColors.cuiD3Scale();
+        $(rawDatum.counts()).each((cnt, item) => {
+          if (isNotNullForCharts(item[_idxLabel]) && isNotNullForCharts(item[_idxValue])) {
+            _data.push({
+              series: _plottedSerie,
+              x: new Date(moment(hueUtils.html2text(item[_idxLabel])).valueOf()),
+              y: item[_idxValue] * 1,
+              color: colors[_plottedSerie % colors.length],
+              obj: item
+            });
+          }
+        });
+        if (rawDatum.sorting === 'asc') {
+          _data.sort((a, b) => {
+            return a.y - b.y;
+          });
+        }
+        if (rawDatum.sorting === 'desc') {
+          _data.sort((a, b) => {
+            return b.y - a.y;
+          });
+        }
+        if (rawDatum.snippet.chartLimit()) {
+          _data = _data.slice(0, rawDatum.snippet.chartLimit());
+        }
+        _datum.push({
+          key: col,
+          values: _data
+        });
+        _plottedSerie++;
+      }
+    }
+  });
+
+  return _datum;
+};
+
+export const multiSerieChartTransformer = function(rawDatum) {
+  let _datum = [];
+
+  console.log(rawDatum.snippet);
+  if (rawDatum.snippet.chartX() != null && rawDatum.snippet.chartYMulti().length > 0) {
+    let _plottedSerie = 0;
+
+    if (typeof rawDatum.snippet.chartXPivot() !== 'undefined') {
+      let _idxValue = -1;
+      let _idxLabel = -1;
+      let _isXDate = false;
+
+      rawDatum.snippet.result.meta().forEach((icol, idx) => {
+        if (icol.name === rawDatum.snippet.chartX()) {
+          _isXDate = icol.type.toUpperCase().indexOf('DATE') > -1;
+          _idxLabel = idx;
+        }
+        if (icol.name === rawDatum.snippet.chartYSingle()) {
+          _idxValue = idx;
+        }
+      });
+
+      rawDatum.snippet.result.meta().forEach((meta, cnt) => {
+        if (rawDatum.snippet.chartXPivot() === meta.name) {
+          const _idxPivot = cnt;
+          const colors = HueColors.cuiD3Scale();
+          let pivotValues = $.map(rawDatum.counts(), p => {
+            return p[_idxPivot];
+          });
+          pivotValues = pivotValues.filter((item, pos) => {
+            return pivotValues.indexOf(item) === pos;
+          });
+          pivotValues.forEach((val, pivotCnt) => {
+            const _data = [];
+            $(rawDatum.counts()).each((cnt, item) => {
+              if (item[_idxPivot] === val) {
+                if (isNotNullForCharts(item[_idxValue]) && isNotNullForCharts(item[_idxLabel])) {
+                  _data.push({
+                    x: _isXDate ? moment(item[_idxLabel]) : hueUtils.html2text(item[_idxLabel]),
+                    y: item[_idxValue] * 1,
+                    color: colors[pivotCnt % colors.length],
+                    obj: item
+                  });
+                }
+              }
+            });
+            _datum.push({
+              key: hueUtils.html2text(val),
+              values: _data
+            });
+          });
+        }
+      });
+
+      // fills in missing values
+      let longest = 0;
+      const allXValues = [];
+      _datum.forEach(d => {
+        d.values.forEach(val => {
+          if (allXValues.indexOf(val.x) === -1) {
+            allXValues.push(val.x);
+          }
+        });
+      });
+
+      _datum.forEach(d => {
+        allXValues.forEach(val => {
+          if (
+            !d.values.some(item => {
+              return item.x === val;
+            })
+          ) {
+            const zeroObj = jQuery.extend({}, d.values[0]);
+            zeroObj.y = 0;
+            zeroObj.x = val;
+            d.values.push(zeroObj);
+          }
+        });
+        if (d.values.length > longest) {
+          longest = d.values.length;
+        }
+      });
+
+      // this is to avoid D3 js errors when the data the user is trying to display is bogus
+      if (allXValues.length < longest) {
+        _datum.forEach(d => {
+          for (let i = d.values.length; i < longest; i++) {
+            const zeroObj = jQuery.extend({}, d.values[0]);
+            zeroObj.y = 0;
+            zeroObj.x = '';
+            d.values.push(zeroObj);
+          }
+        });
+      }
+
+      if (rawDatum.snippet.chartLimit()) {
+        _datum = _datum.slice(0, rawDatum.snippet.chartLimit());
+      }
+
+      if (rawDatum.sorting === 'desc') {
+        _datum.forEach(d => {
+          d.values.sort((a, b) => {
+            if (a.x > b.x) {
+              return -1;
+            }
+            if (a.x < b.x) {
+              return 1;
+            }
+            return 0;
+          });
+        });
+      } else {
+        _datum.forEach(d => {
+          d.values.sort((a, b) => {
+            if (a.x > b.x) {
+              return 1;
+            }
+            if (a.x < b.x) {
+              return -1;
+            }
+            return 0;
+          });
+        });
+      }
+    } else {
+      rawDatum.snippet.result.meta().forEach(meta => {
+        if (rawDatum.snippet.chartYMulti().indexOf(meta.name) > -1) {
+          const col = meta.name;
+          let _idxValue = -1;
+          let _idxLabel = -1;
+          let _isXDate = false;
+          rawDatum.snippet.result.meta().forEach((icol, idx) => {
+            if (icol.name === rawDatum.snippet.chartX()) {
+              _isXDate = icol.type.toUpperCase().indexOf('DATE') > -1;
+              _idxLabel = idx;
+            }
+            if (icol.name === col) {
+              _idxValue = idx;
+            }
+          });
+
+          if (_idxValue > -1) {
+            let _data = [];
+            const colors = HueColors.cuiD3Scale();
+            $(rawDatum.counts()).each((cnt, item) => {
+              if (isNotNullForCharts(item[_idxValue]) && isNotNullForCharts(item[_idxLabel])) {
+                _data.push({
+                  series: _plottedSerie,
+                  x: _isXDate ? moment(item[_idxLabel]) : hueUtils.html2text(item[_idxLabel]),
+                  y: item[_idxValue] * 1,
+                  color: colors[cnt % colors.length],
+                  obj: item
+                });
+              }
+            });
+            if (rawDatum.sorting === 'asc') {
+              _data.sort((a, b) => {
+                return a.y - b.y;
+              });
+            }
+            if (rawDatum.sorting === 'desc') {
+              _data.sort((a, b) => {
+                return b.y - a.y;
+              });
+            }
+            if (rawDatum.snippet.chartLimit()) {
+              _data = _data.slice(0, rawDatum.snippet.chartLimit());
+            }
+            _datum.push({
+              key: col,
+              values: _data
+            });
+            _plottedSerie++;
+          }
+        }
+      });
+    }
+  }
+  return _datum;
+};
+
+export const scatterChartTransformer = function(rawDatum) {
+  const datum = {};
+
+  if (rawDatum.snippet.chartX() != null && rawDatum.snippet.chartYSingle() != null) {
+    let idxX = -1;
+    let idxY = -1;
+    let idxSize = -1;
+    let idxGroup = -1;
+    rawDatum.snippet.result.meta().forEach((icol, idx) => {
+      if (icol.name === rawDatum.snippet.chartX()) {
+        idxX = idx;
+      }
+      if (icol.name === rawDatum.snippet.chartYSingle()) {
+        idxY = idx;
+      }
+      if (icol.name === rawDatum.snippet.chartScatterSize()) {
+        idxSize = idx;
+      }
+      if (icol.name === rawDatum.snippet.chartScatterGroup()) {
+        idxGroup = idx;
+      }
+    });
+
+    if (idxX > -1 && idxY > -1) {
+      const createAndAddToArray = function(key, item) {
+        if (!datum[key]) {
+          datum[key] = [];
+        }
+        if (isNotNullForCharts(item[idxX]) && isNotNullForCharts(item[idxY])) {
+          datum[key].push({
+            x: item[idxX],
+            y: item[idxY],
+            shape: 'circle',
+            size: idxSize > -1 ? item[idxSize] : 100,
+            obj: item
+          });
+        }
+      };
+
+      if (idxGroup > -1) {
+        $(rawDatum.counts()).each((cnt, item) => {
+          createAndAddToArray(item[idxGroup], item);
+        });
+      } else {
+        $(rawDatum.counts()).each((cnt, item) => {
+          createAndAddToArray('distro', item);
+        });
+      }
+    }
+  }
+
+  const returndDatum = [];
+
+  Object.keys(datum).forEach(key => {
+    returndDatum.push({
+      key: key,
+      values: rawDatum.snippet.chartLimit()
+        ? datum[key].slice(0, rawDatum.snippet.chartLimit())
+        : datum[key]
+    });
+  });
+
+  return returndDatum;
+};

+ 565 - 0
desktop/core/src/desktop/js/apps/notebook2/components/resultChart/ko.resultChart.js

@@ -0,0 +1,565 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// 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';
+import {
+  leafletMapChartTransformer,
+  mapChartTransformer,
+  multiSerieChartTransformer,
+  pieChartTransformer,
+  scatterChartTransformer,
+  timelineChartTransformer
+} from './chartTransformers';
+
+export const NAME = 'result-chart';
+
+const TYPES = window.HUE_CHARTS.TYPES;
+
+// prettier-ignore
+const TEMPLATE = `
+<div>
+  <div class="column-side" style="position:relative; white-space: nowrap;" data-bind="
+      visible: isResultSettingsVisible,
+      css: { 'span3 result-settings': isResultSettingsVisible, 'hidden': ! isResultSettingsVisible() }
+    ">
+    <div>
+      <!-- ko if: chartType -->
+      <!-- ko if: isTimeLineChart() || isBarChart() -->
+      <ul class="nav nav-list" style="border: none; background-color: #FFF">
+        <li class="nav-header">${ I18n('type') }</li>
+      </ul>
+      <div>
+        <select data-bind="
+            selectedOptions: chartTimelineType,
+            optionsCaption: '${ I18n('Choose a type...') }',
+            select2: {
+              width: '100%',
+              placeholder: '${ I18n("Choose a type...") }',
+              update: chartTimelineType,
+              dropdownAutoWidth: true
+            }
+          ">
+          <option value="bar">${ I18n("Bars") }</option>
+          <option value="line">${ I18n("Lines") }</option>
+        </select>
+      </div>
+      <!-- /ko -->
+
+      <!-- ko if: isPieChart -->
+      <ul class="nav nav-list" style="border: none; background-color: #FFF">
+        <li class="nav-header">${ I18n('value') }</li>
+      </ul>
+      <div>
+        <select class="input-medium" data-bind="
+            options: cleanedNumericMeta,
+            value: chartYSingle,
+            optionsText: 'name',
+            optionsValue: 'name',
+            optionsCaption: '${ I18n('Choose a column...') }',
+            select2: {
+              width: '100%',
+              placeholder: '${ I18n("Choose a column...") }',
+              update: chartYSingle,
+              dropdownAutoWidth: true
+            }
+          "></select>
+      </div>
+      <ul class="nav nav-list" style="border: none; background-color: #FFF">
+        <li class="nav-header">${ I18n('legend') }</li>
+      </ul>
+      <div>
+        <select class="input-medium" data-bind="
+            options: cleanedMeta,
+            value: chartX,
+            optionsText: 'name',
+            optionsValue: 'name',
+            optionsCaption: '${ I18n('Choose a column...') }',
+            select2: { 
+              width: '100%',
+              placeholder: '${ I18n("Choose a column...") }',
+              update: chartX,
+              dropdownAutoWidth: true
+            }"></select>
+      </div>
+      <!-- /ko -->
+
+      <!-- ko ifnot: isPieChart -->
+      <ul class="nav nav-list" style="border: none; background-color: #FFF">
+        <li data-bind="visible: !isMapChart() && !isGradientMapChart()" class="nav-header">${ I18n('x-axis') }</li>
+        <li data-bind="visible: isGradientMapChart" class="nav-header">${ I18n('region') }</li>
+        <li data-bind="visible: isMapChart" class="nav-header">${ I18n('latitude') }</li>
+      </ul>
+      <div>
+        <select class="input-medium" data-bind="
+            options: chartMetaOptions, 
+            value: chartX,
+            optionsText: 'name',
+            optionsValue: 'name',
+            optionsCaption: '${ I18n('Choose a column...') }',
+            select2: {
+              width: '100%',
+              placeholder: '${ I18n("Choose a column...") }',
+              update: chartX,
+              dropdownAutoWidth: true
+            }
+          "></select>
+      </div>
+
+      <ul class="nav nav-list" style="border: none; background-color: #FFF">
+        <li data-bind="visible: !isMapChart() && !isGradientMapChart()" class="nav-header">${ I18n('y-axis') }</li>
+        <li data-bind="visible: isGradientMapChart" class="nav-header">${ I18n('value') }</li>
+        <li data-bind="visible: isMapChart" class="nav-header">${ I18n('longitude') }</li>
+      </ul>
+
+      <div style="max-height: 220px" data-bind="
+          delayedOverflow,
+          visible: ((isBarChart() && !chartXPivot()) || isLineChart() || isTimeLineChart())
+        ">
+        <ul class="unstyled" data-bind="foreach: 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 class="input-medium" data-bind="visible: (isBarChart() && chartXPivot()) || isMapChart() || isGradientMapChart() || isScatterChart()">
+        <select data-bind="
+            options: isGradientMapChart() ? cleanedMeta : cleanedNumericMeta,
+            value: chartYSingle,
+            optionsText: 'name',
+            optionsValue: 'name',
+            optionsCaption: '${ I18n('Choose a column...') }',
+            select2: {
+              width: '100%',
+              placeholder: '${ I18n("Choose a column...") }',
+              update: chartYSingle,
+              dropdownAutoWidth: true
+            }
+          "></select>
+      </div>
+      <!-- /ko -->
+
+      <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: isBarChart">
+        <li class="nav-header">${ I18n('group') }</li>
+      </ul>
+      <div data-bind="visible: isBarChart">
+        <select class="input-medium" data-bind="
+            options: cleanedMeta,
+            value: chartXPivot,
+            optionsText: 'name',
+            optionsValue: 'name',
+            optionsCaption: '${ I18n('Choose a column to pivot...') }',
+            select2: {
+              width: '100%',
+              placeholder: '${ I18n("Choose a column to pivot...") }',
+              update: chartXPivot,
+              dropdownAutoWidth: true
+            }
+          "></select>
+      </div>
+
+      <ul class="nav nav-list" style="border: none; background-color: #FFF">
+        <li class="nav-header">${ I18n('limit') }</li>
+      </ul>
+      <div>
+        <select class="input-medium" data-bind="
+          options: chartLimits,
+          value: chartLimit,
+          optionsCaption: '${ I18n('Limit the number of results to...') }',
+          select2: {
+            width: '100%',
+            placeholder: '${ I18n('Limit the number of results to...') }',
+            update: chartLimit,
+            dropdownAutoWidth: true
+          }
+        "></select>
+      </div>
+
+      <!-- ko if: isMapChart -->
+      <ul class="nav nav-list" style="border: none; background-color: #FFF">
+        <li class="nav-header">${ I18n('type') }</li>
+      </ul>
+      <div>
+        <select data-bind="
+            selectedOptions: chartMapType,
+            optionsCaption: '${ I18n('Choose a type...') }',
+            select2: {
+              width: '100%',
+              placeholder: '${ I18n('Choose a type...') }',
+              update: chartMapType,
+              dropdownAutoWidth: true
+            }
+          ">
+          <option value="marker">${ I18n("Markers") }</option>
+          <option value="heat">${ I18n("Heatmap") }</option>
+        </select>
+      </div>
+
+      <!-- ko if: chartMapType() === 'marker' -->
+      <ul class="nav nav-list" style="border: none; background-color: #FFF">
+        <li class="nav-header">${ I18n('label') }</li>
+      </ul>
+      <div>
+        <select class="input-medium" data-bind="
+            options: cleanedMeta,
+            value: chartMapLabel,
+            optionsText: 'name',
+            optionsValue: 'name',
+            optionsCaption: '${ I18n('Choose a column...') }',
+            select2: { 
+              width: '100%',
+              placeholder: '${ I18n('Choose a column...') }',
+              update: chartMapLabel,
+              dropdownAutoWidth: true
+            }
+          "></select>
+      </div>
+      <!-- /ko -->
+
+      <!-- ko if: chartMapType() === 'heat' -->
+      <ul class="nav nav-list" style="border: none; background-color: #FFF">
+        <li class="nav-header">${ I18n('intensity') }</li>
+      </ul>
+      <div>
+        <select class="input-medium" data-bind="
+            options: cleanedNumericMeta,
+            value: chartMapHeat,
+            optionsText: 'name',
+            optionsValue: 'name',
+            optionsCaption: '${ I18n('Choose a column...') }',
+            select2: { 
+              width: '100%',
+              placeholder: '${ I18n('Choose a column...') }',
+              update: chartMapHeat,
+              dropdownAutoWidth: true
+            }
+          "></select>
+      </div>
+      <!-- /ko -->
+      <!-- /ko -->
+
+      <!-- ko if: isScatterChart -->
+      <ul class="nav nav-list" style="border: none; background-color: #FFF">
+        <li class="nav-header">${ I18n('scatter size') }</li>
+      </ul>
+      <div>
+        <select class="input-medium" data-bind="
+            options: cleanedNumericMeta,
+            value: chartScatterSize,
+            optionsText: 'name',
+            optionsValue: 'name',
+            optionsCaption: '${ I18n('Choose a column...') }',
+            select2: {
+              width: '100%',
+              placeholder: '${ I18n('Choose a column...') }',
+              update: chartScatterSize,
+              dropdownAutoWidth: true
+            }
+          "></select>
+      </div>
+
+      <ul class="nav nav-list" style="border: none; background-color: #FFF">
+        <li class="nav-header">${ I18n('scatter group') }</li>
+      </ul>
+      <div>
+        <select class="input-medium" data-bind="
+            options: cleanedMeta,
+            value: chartScatterGroup,
+            optionsText: 'name',
+            optionsValue: 'name',
+            optionsCaption: '${ I18n('Choose a column...') }',
+            select2: {
+              width: '100%',
+              placeholder: '${ I18n('Choose a column...') }',
+              update: chartScatterGroup,
+              dropdownAutoWidth: true
+            }
+          "></select>
+      </div>
+      <!-- /ko -->
+
+      <!-- ko if: isGradientMapChart -->
+      <ul class="nav nav-list" style="border: none; background-color: #FFF">
+        <li class="nav-header">${ I18n('scope') }</li>
+      </ul>
+      <div data-bind="visible: chartType() != ''">
+        <select data-bind="
+            selectedOptions: chartScope, 
+            optionsCaption: '${ I18n('Choose a scope...') }',
+            select2: {
+              width: '100%',
+              placeholder: '${ I18n('Choose a scope...') }',
+              update: chartScope,
+              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>
+        </select>
+      </div>
+      <!-- /ko -->
+
+      <!-- ko ifnot: isMapChart() || isGradientMapChart() || isScatterChart()-->
+      <ul class="nav nav-list" style="border: none; background-color: #FFF">
+        <li class="nav-header">${ I18n('sorting') }</li>
+      </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'); }
+          "><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'); }
+          "><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'); }
+          "><i class="fa fa-sort-amount-desc fa-rotate-270"></i></a>
+      </div>
+      <!-- /ko -->
+      <!-- /ko -->
+    </div>
+    <div class="resize-bar" style="top: 0; right: -10px; cursor: col-resize;"></div>
+  </div>
+  <div class="grid-side" data-bind="css: { 'span9': isResultSettingsVisible, 'span12 nomargin': ! isResultSettingsVisible() }">
+    <div class="chart-container">
+      <h1 class="empty" data-bind="visible: !hasDataForChart()">${ I18n('Select the chart parameters on the left') }</h1>
+
+      <div data-bind="visible: hasDataForChart">
+        <!-- ko if: isPieChart -->
+        <div class="chart" data-bind="attr: { 'id': chartId }, pieChart: pieChartParams()" />
+        <!-- /ko -->
+
+        <!-- ko if: isBarChart -->
+        <div class="chart" data-bind="attr: { 'id': chartId }, barChart: barChartParams()" />
+        <!-- /ko -->
+
+        <!-- ko if: isLineChart -->
+        <div class="chart" data-bind="attr: { 'id': chartId }, lineChart: lineChartParams()" />
+        <!-- /ko -->
+
+        <!-- ko if: isTimeLineChart -->
+        <div class="chart" data-bind="attr:{ 'id': chartId }, timelineChart: timeLineChartParams()" />
+        <!-- /ko -->
+
+        <!-- ko if: isMapChart -->
+        <div class="chart" data-bind="attr:{ 'id': chartId }, leafletMapChart: leafletMapChartParams()" />
+        <!-- /ko -->
+
+        <!-- ko if: isGradientMapChart -->
+        <div class="chart" data-bind="attr:{ 'id': chartId }, mapChart: mapChartParams()" />
+        <!-- /ko -->
+
+        <!-- ko if: isScatterChart -->
+        <div class="chart" data-bind="attr:{ 'id': chartId }, scatterChart: scatterChartParams()" />
+        <!-- /ko -->
+      </div>
+    </div>
+  </div>
+</div>
+`;
+
+class ResultChart extends DisposableComponent {
+  constructor(params) {
+    super();
+
+    this.data = params.data;
+    this.snippet = params.snippet;
+    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.cleanedMeta = params.cleanedMeta;
+    this.cleanedDateTimeMeta = params.cleanedDateTimeMeta;
+    this.cleanedNumericMeta = params.cleanedNumericMeta;
+
+    this.chartLimits = ko.observableArray([5, 10, 25, 50, 100]);
+
+    this.chartId = ko.pureComputed(() => this.chartType() + '_' + this.id());
+    this.isBarChart = ko.pureComputed(() => TYPES.BARCHART === this.chartType());
+    this.isLineChart = ko.pureComputed(() => TYPES.LINECHART === this.chartType());
+    this.isMapChart = ko.pureComputed(() => TYPES.MAP === this.chartType());
+    this.isScatterChart = ko.pureComputed(() => TYPES.SCATTERCHART === this.chartType());
+    this.isGradientMapChart = ko.pureComputed(() => TYPES.GRADIENTMAP === this.chartType());
+    this.isPieChart = ko.pureComputed(() => TYPES.PIECHART === this.chartType());
+    this.isTimeLineChart = ko.pureComputed(() => TYPES.TIMELINECHART === this.chartType());
+
+    this.hasDataForChart = ko.pureComputed(() => {
+      if (this.isBarChart() || this.isLineChart() || this.isTimeLineChart()) {
+        return (
+          typeof this.chartX() !== 'undefined' &&
+          this.chartX() !== null &&
+          this.chartYMulti().length
+        );
+      }
+      return (
+        typeof this.chartX() !== 'undefined' &&
+        this.chartX() !== null &&
+        typeof this.chartYSingle() !== 'undefined' &&
+        this.chartYSingle() !== null
+      );
+    });
+
+    this.hasDataForChart.subscribe(() => {
+      this.chartX.notifySubscribers();
+      this.chartX.valueHasMutated();
+    });
+
+    this.hideStacked = ko.pureComputed(() => !this.chartYMulti().length);
+
+    this.chartMetaOptions = ko.pureComputed(() => {
+      if (this.isBarChart() || this.isGradientMapChart()) {
+        return this.cleanedMeta();
+      }
+      if (this.isTimeLineChart()) {
+        return this.cleanedDateTimeMeta();
+      }
+      return this.cleanedNumericMeta();
+    });
+
+    this.pieChartParams = ko.pureComputed(() => ({
+      data: {
+        counts: this.data,
+        sorting: this.chartSorting(),
+        snippet: this.snippet,
+        limit: this.chartLimit()
+      },
+      fqs: ko.observableArray(),
+      transformer: pieChartTransformer,
+      maxWidth: 350,
+      parentSelector: '.chart-container'
+    }));
+
+    this.barChartParams = ko.pureComputed(() => ({
+      skipWindowResize: true,
+      datum: {
+        counts: this.data,
+        sorting: this.chartSorting(),
+        snippet: this.snippet,
+        limit: this.chartLimit()
+      },
+      fqs: ko.observableArray(),
+      hideSelection: true,
+      enableSelection: false,
+      hideStacked: this.hideStacked,
+      transformer: multiSerieChartTransformer,
+      stacked: false,
+      showLegend: true,
+      isPivot: typeof this.chartXPivot() !== 'undefined',
+      type: this.chartTimelineType
+    }));
+
+    this.lineChartParams = ko.pureComputed(() => ({
+      datum: {
+        counts: this.data,
+        sorting: this.chartSorting(),
+        snippet: this.snippet,
+        limit: this.chartLimit()
+      },
+      transformer: multiSerieChartTransformer,
+      showControls: false,
+      enableSelection: false
+    }));
+
+    this.timeLineChartParams = ko.pureComputed(() => ({
+      type: this.chartTimelineType,
+      skipWindowResize: true,
+      datum: {
+        counts: this.data,
+        sorting: this.chartSorting(),
+        snippet: this.snippet,
+        limit: this.chartLimit()
+      },
+      fqs: ko.observableArray(),
+      hideSelection: true,
+      enableSelection: false,
+      hideStacked: this.hideStacked,
+      transformer: timelineChartTransformer,
+      stacked: false,
+      showLegend: true
+    }));
+
+    this.leafletMapChartParams = ko.pureComputed(() => ({
+      datum: {
+        counts: this.data,
+        sorting: this.chartSorting(),
+        snippet: this.snippet,
+        limit: this.chartLimit()
+      },
+      transformer: leafletMapChartTransformer,
+      showControls: false,
+      height: 380,
+      forceRedraw: true
+    }));
+
+    this.mapChartParams = ko.pureComputed(() => ({
+      data: {
+        counts: this.data,
+        sorting: this.chartSorting(),
+        snippet: this.snippet,
+        scope: this.chartScope(),
+        limit: this.chartLimit()
+      },
+      transformer: mapChartTransformer,
+      isScale: true,
+      showControls: false,
+      height: 380,
+      maxWidth: 750,
+      parentSelector: '.chart-container'
+    }));
+
+    this.scatterChartParams = ko.pureComputed(() => ({
+      datum: {
+        counts: this.data,
+        snippet: this.snippet,
+        limit: this.chartLimit()
+      },
+      transformer: scatterChartTransformer,
+      maxWidth: 350,
+      y: this.chartYSingle(),
+      x: this.chartX(),
+      size: this.chartScatterSize(),
+      group: this.chartScatterGroup()
+    }));
+  }
+}
+
+componentUtils.registerComponent(NAME, ResultChart, TEMPLATE);

+ 4 - 4
desktop/core/src/desktop/js/apps/notebook2/snippet.js

@@ -823,7 +823,7 @@ export default class Snippet {
     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]);
+    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);
@@ -837,9 +837,9 @@ export default class Snippet {
     this.chartMapType = ko.observable(snippet.chartMapType || 'marker');
     this.chartMapLabel = ko.observable(snippet.chartMapLabel);
     this.chartMapHeat = ko.observable(snippet.chartMapHeat);
-    this.hideStacked = ko.pureComputed(() => this.chartYMulti().length <= 1);
+    this.hideStacked = ko.pureComputed(() => this.chartYMulti().length <= 1); // To delete
 
-    this.hasDataForChart = ko.pureComputed(() => {
+    this.hasDataForChart = ko.pureComputed(() => { // To delete
       if (
         this.chartType() === window.HUE_CHARTS.TYPES.BARCHART ||
         this.chartType() === window.HUE_CHARTS.TYPES.LINECHART ||
@@ -859,7 +859,7 @@ export default class Snippet {
       );
     });
 
-    this.hasDataForChart.subscribe(() => {
+    this.hasDataForChart.subscribe(() => { // To delete
       this.chartX.notifySubscribers();
       this.chartX.valueHasMutated();
     });

+ 2 - 2
desktop/core/src/desktop/js/ko/components/assist/ko.assistEditorContextPanel.js

@@ -128,8 +128,8 @@ const TEMPLATE =
           <!-- ko hueSpinner: { spin: uploadingTableStats, inline: true} --><!-- /ko -->
           <!-- ko ifnot: uploadingTableStats -->
           <a href="javascript:void(0)" data-bind="visible: activeTables().length > 0, click: function() { uploadTableStats(true) }, attr: { 'title': ('${I18n(
-            'Add table '
-          )}'  + (isMissingDDL() ? 'DDL' : '') + (isMissingDDL() && isMissingStats() ? ' ${I18n(
+            'Add table'
+          )} '  + (isMissingDDL() ? 'DDL' : '') + (isMissingDDL() && isMissingStats() ? ' ${I18n(
     'and'
   )} ' : '') + (isMissingStats() ? 'stats' : '')) }">
             <i class="fa fa-fw fa-plus-circle"></i> ${I18n('Improve Analysis')}

+ 89 - 9
desktop/core/src/desktop/templates/global_js_constants.mako

@@ -154,19 +154,34 @@
     'Assist': '${ _('Assist') }',
     'Assistant': '${ _('Assistant') }',
     'at cursor': '${ _('at cursor') }',
+    'Australia': '${ _('Australia') }',
     'Back': '${_('Back')}',
+    'Bars': '${ _('Bars') }',
+    'Brazil': '${ _('Brazil') }',
     'Browse column privileges': '${_('Browse column privileges')}',
+    'Browse db privileges': '${ _('Browse db privileges') }',
     'Browse DB privileges': '${_('Browse DB privileges')}',
     'Browse table privileges': '${_('Browse table privileges')}',
     'Browsers': '${ _('Browsers') }',
+    'Bundle': '${ _('Bundle') }',
+    'Canada': '${ _('Canada') }',
     'Cancel upload': '${ _('Cancel upload') }',
     'Cancel': '${_('Cancel')}',
+    'Change': '${ _('Change') }',
+    'Check compatibility': '${ _('Check compatibility') }',
+    'China': '${ _('China') }',
+    'Choose a column to pivot...': '${ _('Choose a column to pivot...') }',
+    'Choose a column...': '${ _('Choose a column...') }',
+    'Choose a scope...': '${ _('Choose a scope...') }',
+    'Choose a type...': '${ _('Choose a type...') }',
     'Choose...': '${ _('Choose...') }',
     'Clear cache': '${ _('Clear cache') }',
+    'Clear the current editor': '${ _('Clear the current editor') }',
     'Clear the query history': '${ _('Clear the query history') }',
+    'Clear': '${ _('Clear') }',
     'Click for more details': '${ _('Click for more details') }',
-    'Close': '${_('Close')}',
     'Close session': '${_('Close session')}',
+    'Close': '${_('Close')}',
     'Cluster': '${ _('Cluster') }',
     'Clusters': '${ _('Clusters') }',
     'CodeGen': '${ _('CodeGen') }',
@@ -176,11 +191,11 @@
     'Columns': '${ _('Columns') }',
     'Compilation': '${ _('Compilation') }',
     'Compute': '${ _('Compute') }',
-    'Connect': '${ _('Connect') }',
     'condition': '${ _('condition') }',
     'Confirm History Clearing': '${ _('Confirm History Clearing') }',
     'Confirm the deletion?': '${ _('Confirm the deletion?') }',
     'Connect to the data source': '${ _('Connect to the data source') }',
+    'Connect': '${ _('Connect') }',
     'Could not find details for the function': '${_('Could not find details for the function')}',
     'Could not find': '${_('Could not find')}',
     'CPU': '${ _('CPU') }',
@@ -191,6 +206,7 @@
     'Create table': '${_('Create table')}',
     'Create': '${ _('Create') }',
     'Created': '${ _('Created') }',
+    'Created: ': '${ _('Created: ') }',
     'cte': '${ _('cte') }',
     'CTEs': '${ _('CTEs') }',
     'Dashboard': '${ _('Dashboard') }',
@@ -234,12 +250,16 @@
     'Error loading table details.': '${ _('Error loading table details.') }',
     'Error loading tables.': '${ _('Error loading tables.') }',
     'Error while copying results.': '${ _('Error while copying results.') }',
+    'Europe': '${ _('Europe') }',
     'Example: SELECT * FROM tablename, or press CTRL + space': '${ _('Example: SELECT * FROM tablename, or press CTRL + space') }',
     'Execute a query to get query execution analysis.': '${ _('Execute a query to get query execution analysis.') }',
+    'Execute': '${ _('Execute') }',
     'Execution': '${ _('Execution') }',
     'Expand to all columns': '${ _('Expand to all columns') }',
     'Expand to selected columns': '${ _('Expand to selected columns') }',
     'Expected end of statement': '${_('Expected end of statement')}',
+    'Explain the current SQL query': '${ _('Explain the current SQL query') }',
+    'Explain': '${ _('Explain') }',
     'Failed': '${_('Failed')}',
     'Field': '${ _('Field') }',
     'Fields': '${ _('Fields') }',
@@ -254,27 +274,46 @@
     'Folder name': '${_('Folder name')}',
     'Foreign key': '${_('Foreign key')}',
     'Foreign keys': '${_('Foreign keys')}',
+    'Format the current SQL query': '${ _('Format the current SQL query') }',
+    'Format': '${ _('Format') }',
+    'France': '${ _('France') }',
     'Functions': '${ _('Functions') }',
+    'Germany': '${ _('Germany') }',
+    'Get hints on how to port SQL from other databases': '${ _('Get hints on how to port SQL from other databases') }',
     'Go Home': '${_('Go Home')}',
     'Go to column:': '${_('Go to column:')}',
     'group by': '${ _('group by') }',
+    'group': '${ _('group') }',
+    'HBase': '${ _('HBase') }',
     'Heatmap': '${ _('Heatmap') }',
     'Help': '${ _('Help') }',
     'Hide advanced': '${_('Hide advanced')}',
+    'Hide Details': '${ _('Hide Details') }',
     'Hive Query': '${_('Hive Query')}',
     'Identifiers': '${ _('Identifiers') }',
     'Ignore this type of error': '${_('Ignore this type of error')}',
     'Impala Query': '${_('Impala Query')}',
+    'Import complete!': '${ _('Import complete!') }',
+    'Import failed!': '${ _('Import failed!') }',
+    'Import Hue Documents': '${ _('Import Hue Documents') }',
     'Import Job': '${_('Import Job')}',
+    'Import Query History': '${ _('Import Query History') }',
+    'Import': '${ _('Import') }',
+    'Imported: ': '${ _('Imported: ') }',
+    'Importing...': '${ _('Importing...') }',
     'Improve Analysis': '${_('Improve Analysis')}',
     'Indexes': '${ _('Indexes') }',
+    'Insert ': '${ _('Insert ') }',
     'Insert at cursor': '${_('Insert at cursor')}',
     'Insert in the editor': '${ _('Insert in the editor') }',
     'Insert value here': '${ _('Insert value here') }',
     'Insert': '${ _('Insert') }',
+    'intensity': '${ _('intensity') }',
     'Invalidate all metadata and rebuild index.': '${ _('Invalidate all metadata and rebuild index.') }',
     'IO': '${ _('IO') }',
     'It looks like you are offline or an unknown error happened. Please refresh the page.': '${ _('It looks like you are offline or an unknown error happened. Please refresh the page.') }',
+    'Italy': '${ _('Italy') }',
+    'Japan': '${ _('Japan') }',
     'Java Job': '${_('Java Job')}',
     'Job browser': '${_('Job browser')}',
     'Job Design': '${_('Job Design')}',
@@ -284,12 +323,21 @@
     'Key': '${ _('Key') }',
     'keyword': '${ _('keyword') }',
     'Keywords': '${ _('Keywords') }',
+    'label': '${ _('label') }',
     'Language Reference': '${ _('Language Reference') }',
+    'latitude': '${ _('latitude') }',
+    'legend': '${ _('legend') }',
+    'Limit the number of results to...': '${ _('Limit the number of results to...') }',
+    'limit': '${ _('limit') }',
+    'Lines': '${ _('Lines') }',
+    'Load recent queries in order to improve recommendations': '${ _('Load recent queries in order to improve recommendations') }',
     'Loading metrics...': '${ _('Loading metrics...') }',
     'Lock this row': '${_('Lock this row')}',
-    'MapReduce Job': '${_('MapReduce Job')}',
+    'longitude': '${ _('longitude') }',
     'Manage Users': '${ _('Manage Users') }',
     'Manual refresh': '${_('Manual refresh')}',
+    'MapReduce Job': '${_('MapReduce Job')}',
+    'Markers': '${ _('Markers') }',
     'max': '${ _('max') }',
     'Memory': '${ _('Memory') }',
     'Metrics': '${ _('Metrics') }',
@@ -302,6 +350,7 @@
     'Missing value configuration.': '${ _('Missing value configuration.') }',
     'Missing x axis configuration.': '${ _('Missing x axis configuration.') }',
     'Missing y axis configuration.': '${ _('Missing y axis configuration.') }',
+    'More': '${ _('More') }',
     'My Profile': '${ _('My Profile') }',
     'Name': '${ _('Name') }',
     'Namespace': '${ _('Namespace') }',
@@ -309,6 +358,7 @@
     'New document': '${ _('New document') }',
     'New folder': '${ _('New folder') }',
     'No clusters available': '${ _('No clusters available') }',
+    'No clusters available.': '${ _('No clusters available.') }',
     'No clusters found': '${ _('No clusters found') }',
     'No columns found': '${ _('No columns found') }',
     'No computes found': '${ _('No computes found') }',
@@ -326,14 +376,15 @@
     'No namespaces found.': '${ _('No namespaces found.') }',
     'No optimizations identified.': '${ _('No optimizations identified.') }',
     'No privileges found for the selected object.': '${ _('No privileges found for the selected object.') }',
+    'No related computes': '${_('No related computes')}',
     'No results found': '${_('No results found')}',
     'No results found.': '${_('No results found.')}',
+    'No sorting': '${ _('No sorting') }',
     'No tables available.': '${ _('No tables available.') }',
     'No tables found': '${ _('No tables found') }',
     'No tables identified.': '${ _('No tables identified.') }',
     'No task history.': '${_('Not task history.')}',
     'No': '${ _('No') }',
-    'No related computes': '${_('No related computes')}',
     'Not available': '${_('Not available')}',
     'Notebook': '${_('Notebook')}',
     'of': '${_('of')}',
@@ -347,14 +398,14 @@
     'Open in Dashboard': '${_('Open in Dashboard')}',
     'Open in Dashboard...': '${ _('Open in Dashboard...') }',
     'Open in Editor': '${_('Open in Editor')}',
+    'Open in File Browser...': '${ _('Open in File Browser...') }',
     'Open in HBase': '${_('Open in HBase')}',
     'Open in Importer': '${_('Open in Importer')}',
-    'Open in File Browser...': '${ _('Open in File Browser...') }',
     'Open in Table Browser...': '${ _('Open in Table Browser...') }',
     'Open': '${ _('Open') }',
     'Options': '${ _('Options') }',
-    'Order': '${ _('Order') }',
     'order by': '${ _('order by') }',
+    'Order': '${ _('Order') }',
     'other': '${ _('other') }',
     'Output': '${ _('Output') }',
     'Overview': '${ _('Overview') }',
@@ -380,10 +431,12 @@
     'Query needs to be saved.': '${ _('Query needs to be saved.') }',
     'Query requires a select or aggregate.': '${ _('Query requires a select or aggregate.') }',
     'Query running': '${ _('Query running') }',
+    'Query settings': '${ _('Query settings') }',
     'queued': '${ _('queued') }',
-    'Re-create': '${ _('Re-create') }',
     'Re-create session': '${ _('Re-create session') }',
+    'Re-create': '${ _('Re-create') }',
     'Refresh': '${ _('Refresh') }',
+    'region': '${ _('region') }',
     'Remove': '${ _('Remove') }',
     'Replace the editor content...': '${ _('Replace the editor content...') }',
     'Result available': '${ _('Result available') }',
@@ -395,14 +448,19 @@
     'Sample': '${ _('Sample') }',
     'sample': '${ _('sample') }',
     'Samples': '${ _('Samples') }',
-    'Save': '${ _('Save') }',
     'Save changes': '${ _('Save changes') }',
     'Save session settings as default': '${ _('Save session settings as default') }',
+    'Save': '${ _('Save') }',
+    'scatter group': '${ _('scatter group') }',
+    'scatter size': '${ _('scatter size') }',
     'Schedule': '${ _('Schedule') }',
+    'scope': '${ _('scope') }',
     'Search Dashboard': '${_('Search Dashboard')}',
     'Search data and saved documents...': '${ _('Search data and saved documents...') }',
     'Search saved documents...': '${_('Search saved documents...')}',
     'Select a query or start typing to get optimization hints.': '${_('Select a query or start typing to get optimization hints')}',
+    'Select json file': '${ _('Select json file') }',
+    'Select the chart parameters on the left': '${ _('Select the chart parameters on the left') }',
     'Select this folder': '${_('Select this folder')}',
     'Selected dialect': '${_('Selected dialect')}',
     'Selected entry': '${_('Selected entry')}',
@@ -411,10 +469,12 @@
     'Sessions': '${ _('Sessions') }',
     'Set as default application': '${_('Set as default application')}',
     'Set as default settings': '${_('Set as default settings')}',
+    'Settings': '${ _('Settings') }',
     'Shell Script': '${_('Shell Script')}',
     'Show 50 more...': '${_('Show 50 more...')}',
     'Show advanced': '${_('Show advanced')}',
     'Show columns': '${_('Show columns')}',
+    'Show Details': '${ _('Show Details') }',
     'Show details': '${_('Show details')}',
     'Show in Assist...': '${_('Show in Assist...')}',
     'Show more...': '${_('Show more...')}',
@@ -424,13 +484,19 @@
     'Sign out': '${ _('Sign out') }',
     'Size': '${ _('Size') }',
     'Solr Search': '${ _('Solr Search') }',
+    'Sort ascending': '${ _('Sort ascending') }',
+    'Sort descending': '${ _('Sort descending') }',
+    'sorting': '${ _('sorting') }',
     'Sources': '${ _('Sources') }',
-    'Statement': '${ _('Statement') }',
     'Spark Job': '${_('Spark Job')}',
+    'SQL': '${ _('SQL') }',
     'Start': '${_('Start')}',
     'Starting': '${_('Starting')}',
+    'Statement': '${ _('Statement') }',
     'Stats': '${_('Stats')}',
+    'Stop': '${ _('Stop') }',
     'Stopped': '${_('Stopped')}',
+    'Streams': '${ _('Streams') }',
     'Summary': '${_('Summary')}',
     'Table Browser': '${ _('Table Browser') }',
     'table': '${ _('table') }',
@@ -444,9 +510,14 @@
     'The table has no columns': '${_('The table has no columns')}',
     'The trash is empty': '${_('The trash is empty')}',
     'The upload has been canceled': '${ _('The upload has been canceled') }',
+    'There are currently no information about the sessions.': '${ _('There are currently no information about the sessions.') }',
     'There are no stats to be shown': '${ _('There are no stats to be shown') }',
     'There are no terms to be shown': '${ _('There are no terms to be shown') }',
     'There is currently no information about the sessions.': '${ _('There is currently no information about the sessions.') }',
+    'There was a problem loading the databases': '${ _('There was a problem loading the databases') }',
+    'There was a problem loading the index preview': '${ _('There was a problem loading the index preview') }',
+    'There was a problem loading the indexes': '${ _('There was a problem loading the indexes') }',
+    'There was a problem loading the table preview': '${ _('There was a problem loading the table preview') }',
     'This field does not support stats': '${ _('This field does not support stats') }',
     'This will sync missing tables.': '${ _('This will sync missing tables.') }',
     'Timeline': '${ _('Timeline') }',
@@ -454,16 +525,22 @@
     'Top Nodes': '${ _('Top Nodes') }',
     'Topics': '${ _('Topics') }',
     'Type': '${ _('Type') }',
+    'type': '${ _('type') }',
     'UDFs': '${ _('UDFs') }',
+    'UK': '${ _('UK') }',
     'Undo': '${ _('Undo') }',
     'Unlock this row': '${_('Unlock this row')}',
     'Unset from default application': '${_('Unset from default application')}',
+    'Updated: ': '${ _('Updated: ') }',
     'Upload a file': '${_('Upload a file')}',
     'Upload file': '${_('Upload file')}',
+    'Upload optimizer history': '${ _('Upload optimizer history') }',
     'uploaded successfully': '${ _('uploaded successfully') }',
+    'USA': '${ _('USA') }',
     'used by': '${ _('used by') }',
     'Username': '${ _('Username') }',
     'Value': '${ _('Value') }',
+    'value': '${ _('value') }',
     'Values': '${ _('Values') }',
     'variable': '${ _('variable') }',
     'Variables': '${ _('Variables') }',
@@ -476,6 +553,9 @@
     'With grant option': '${ _('With grant option') }',
     'With grant': '${ _('With grant') }',
     'Workflow': '${ _('Workflow') }',
+    'World': '${ _('World') }',
+    'x-axis': '${ _('x-axis') }',
+    'y-axis': '${ _('y-axis') }',
     'Yes': '${ _('Yes') }',
     'Yes, delete': '${ _('Yes, delete') }',
   };

+ 29 - 44
desktop/libs/notebook/src/notebook/templates/editor_components2.mako

@@ -1384,14 +1384,14 @@
         </div>
 
         <div class="row-fluid table-results" data-bind="visible: result.type() == 'table'" style="display: none; max-height: 400px; min-height: 290px;">
+          <!-- ko if: showGrid -->
           <div>
             <div class="column-side" data-bind="visible: isResultSettingsVisible, css:{'span3 result-settings': isResultSettingsVisible, 'hidden': ! isResultSettingsVisible()}" style="position:relative;white-space: nowrap;">
-              <!-- ko template: { name: 'snippet-grid-settings${ suffix }', if: showGrid } --><!-- /ko -->
-              <!-- ko template: { name: 'snippet-chart-settings${ suffix }', if: showChart } --><!-- /ko -->
+              <!-- ko template: { name: 'snippet-grid-settings${ suffix }' } --><!-- /ko -->
               <div class="resize-bar" style="top: 0; right: -10px; cursor: col-resize;"></div>
             </div>
             <div class="grid-side" data-bind="css: {'span9': isResultSettingsVisible, 'span12 nomargin': ! isResultSettingsVisible() }">
-              <div data-bind="visible: showGrid, delayedOverflow: 'slow', css: resultsKlass" style="display: none;">
+              <div data-bind="delayedOverflow: 'slow', css: resultsKlass">
                 <table class="table table-condensed resultTable">
                   <thead>
                   <tr data-bind="foreach: result.meta">
@@ -1405,49 +1405,34 @@
                   <pre class="margin-top-10"><i class="fa fa-check muted"></i> ${ _("Results have expired, rerun the query if needed.") }</pre>
                 </div>
               </div>
-
-              <div data-bind="visible: showChart" class="chart-container" style="display:none;">
-                <h1 class="empty" data-bind="visible: !hasDataForChart()">${ _('Select the chart parameters on the left') }</h1>
-
-                <div data-bind="visible: hasDataForChart">
-                  <!-- ko if: chartType() == window.HUE_CHARTS.TYPES.PIECHART -->
-                  <div data-bind="attr:{'id': 'pieChart_'+id()}, pieChart: {data: {counts: result.data, sorting: chartSorting(), snippet: $data, limit: chartLimit()}, fqs: ko.observableArray([]),
-                      transformer: $root.ChartTransformers.pie, maxWidth: 350, parentSelector: '.chart-container' }, visible: chartType() == window.HUE_CHARTS.TYPES.PIECHART" class="chart"></div>
-                  <!-- /ko -->
-
-                  <!-- ko if: chartType() == window.HUE_CHARTS.TYPES.BARCHART -->
-                  <div data-bind="attr:{'id': 'barChart_'+id()}, barChart: {skipWindowResize: true, datum: {counts: result.data, sorting: chartSorting(), snippet: $data, limit: chartLimit()}, fqs: ko.observableArray([]), hideSelection: true, enableSelection: false, hideStacked: hideStacked,
-                      transformer: $root.ChartTransformers.multiSerie, stacked: false, showLegend: true, isPivot: typeof chartXPivot() !== 'undefined', type: chartTimelineType},  stacked: true, showLegend: true, visible: chartType() == window.HUE_CHARTS.TYPES.BARCHART" class="chart"></div>
-                  <!-- /ko -->
-
-                  <!-- ko if: chartType() == window.HUE_CHARTS.TYPES.LINECHART -->
-                  <div data-bind="attr:{'id': 'lineChart_'+id()}, lineChart: {datum: {counts: result.data, sorting: chartSorting(), snippet: $data, limit: chartLimit()},
-                      transformer: $root.ChartTransformers.multiSerie, showControls: false, enableSelection: false }, visible: chartType() == window.HUE_CHARTS.TYPES.LINECHART" class="chart"></div>
-                  <!-- /ko -->
-
-                  <!-- ko if: chartType() == window.HUE_CHARTS.TYPES.TIMELINECHART -->
-                  <div data-bind="attr:{'id': 'timelineChart_'+id()}, timelineChart: {type: chartTimelineType, skipWindowResize: true, datum: {counts: result.data, sorting: chartSorting(), snippet: $data, limit: chartLimit()}, fqs: ko.observableArray([]), hideSelection: true, enableSelection: false, hideStacked: hideStacked,
-                      transformer: $root.ChartTransformers.timeline, stacked: false, showLegend: true}, visible: chartType() == window.HUE_CHARTS.TYPES.TIMELINECHART" class="chart"></div>
-                  <!-- /ko -->
-
-                  <!-- ko if: chartType() == window.HUE_CHARTS.TYPES.MAP -->
-                  <div data-bind="attr:{'id': 'leafletMapChart_'+id()}, leafletMapChart: {datum: {counts: result.data, sorting: chartSorting(), snippet: $data, limit: chartLimit()},
-                      transformer: $root.ChartTransformers.leafletMap, showControls: false, height: 380, visible: chartType() == window.HUE_CHARTS.TYPES.MAP, forceRedraw: true}" class="chart"></div>
-                  <!-- /ko -->
-
-                  <!-- ko if: chartType() == window.HUE_CHARTS.TYPES.GRADIENTMAP -->
-                  <div data-bind="attr:{'id': 'gradientMapChart_'+id()}, mapChart: {data: {counts: result.data, sorting: chartSorting(), snippet: $data, scope: chartScope(), limit: chartLimit()},
-                      transformer: $root.ChartTransformers.map, isScale: true, showControls: false, height: 380, maxWidth: 750, parentSelector: '.chart-container', visible: chartType() == window.HUE_CHARTS.TYPES.GRADIENTMAP}" class="chart"></div>
-                  <!-- /ko -->
-
-                  <!-- ko if: chartType() == window.HUE_CHARTS.TYPES.SCATTERCHART -->
-                  <div data-bind="attr:{'id': 'scatterChart_'+id()}, scatterChart: {datum: {counts: result.data, snippet: $data, limit: chartLimit()},
-                      transformer: $root.ChartTransformers.scatter, maxWidth: 350, y: chartYSingle(), x: chartX(), size: chartScatterSize(), group: chartScatterGroup() }, visible: chartType() == window.HUE_CHARTS.TYPES.SCATTERCHART" class="chart"></div>
-                  <!-- /ko -->
-                </div>
-              </div>
             </div>
           </div>
+          <!-- /ko -->
+          <!-- ko if: showChart-->
+            <!-- ko component: { name: 'result-chart', params: {
+              data: result.data,
+              snippet: $data,
+              id: id,
+              isResultSettingsVisible: isResultSettingsVisible,
+              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,
+              cleanedMeta: result.cleanedMeta,
+              cleanedDateTimeMeta: result.cleanedDateTimeMeta,
+              cleanedNumericMeta: result.cleanedNumericMeta
+            } } --><!-- /ko -->
+          <!-- /ko -->
         </div>
       </div>
     </div>