瀏覽代碼

[spark] Improved result handling for charts

- It now keeps the previous graph visible until new results are in, reducing the flickering of the chart.
- It now shows a message telling the user to select the chart parameters to the left, visible until the required parameters are set.
- No more "data unavailable" message when the data is available.
- Keep the chart settings even when execute doesn't produce the required metadata or on execution error.
- Result settings toggle style now matches the other action icons
- Tables are properly resized when settings panel is shown/hidden
Johan Ahlen 10 年之前
父節點
當前提交
5d6736a

+ 4 - 11
apps/spark/src/spark/static/spark/css/spark.css

@@ -370,22 +370,15 @@ h1.empty {
   margin-top: 31px;
   width: 10px;
   text-align: center;
-}
-
-.toggle-result-settings i {
-  display: none;
-}
-
-.toggle-result-settings.hoverable {
   cursor: pointer;
 }
 
-.toggle-result-settings.hoverable i {
-  display: inline-block;
+.toggle-result-settings .close {
+  margin-right: 2px;
 }
 
-.toggle-result-settings.hoverable:hover {
-  background-color: #F0F0F0;
+.toggle-result-settings .open {
+  margin-left: 2px;
 }
 
 .chart-icon {

+ 36 - 19
apps/spark/src/spark/static/spark/js/spark.ko.js

@@ -24,8 +24,9 @@ var Result = function (snippet, result) {
 
   self.id = ko.observable(typeof result.id != "undefined" && result.id != null ? result.id : UUID());
   self.type = ko.observable(typeof result.type != "undefined" && result.type != null ? result.type : 'table');
-  self.hasResultset = ko.observable(typeof result.hasResultset != "undefined" && result.hasResultset != null ? result.hasResultset : true);
-  self.handle = ko.observable({});
+  self.hasResultset = ko.observable(typeof result.hasResultset != "undefined" && result.hasResultset != null ? result.hasResultset : true)
+    .extend("throttle", 100);
+  self.handle = ko.observable(typeof result.handle != "undefined" && result.handle != null ? result.handle : {});
   self.meta = ko.observableArray(typeof result.meta != "undefined" && result.meta != null ? result.meta : []);
   self.cleanedMeta = ko.computed(function () {
     return ko.utils.arrayFilter(self.meta(), function (item) {
@@ -85,18 +86,9 @@ var Result = function (snippet, result) {
         type: self.type,
         handle: self.handle
     };
-  }
-
-  if (typeof result.handle != "undefined" && result.handle != null) {
-    $.each(result.handle, function (key, val) {
-      self.handle()[key] = val;
-    });
-  }
+  };
 
   self.clear = function () {
-    $.each(self.handle, function (key, val) {
-      delete self.handle()[key];
-    });
     self.fetchedOnce(false);
     self.data.removeAll();
     self.images.removeAll();
@@ -293,11 +285,33 @@ var Snippet = function (vm, notebook, snippet) {
   self.chartData = ko.observableArray(typeof snippet.chartData != "undefined" && snippet.chartData != null ? snippet.chartData : []);
   self.chartMapLabel = ko.observable(typeof snippet.chartMapLabel != "undefined" && snippet.chartMapLabel != null ? snippet.chartMapLabel : null);
 
+  self.hasDataForChart = ko.computed(function () {
+    if (self.chartType() == ko.HUE_CHARTS.TYPES.BARCHART || self.chartType() == ko.HUE_CHARTS.TYPES.LINECHART) {
+      return typeof self.chartX() != "undefined" && self.chartX() != null && self.chartYMulti().length > 0;
+    }
+    return typeof self.chartX() != "undefined" && self.chartX() != null && typeof self.chartYSingle() != "undefined" && self.chartYSingle() != null ;
+  });
+
+  self.hasDataForChart.subscribe(function(newValue) {
+    if (!newValue) {
+      self.isResultSettingsVisible(true);
+    }
+    self.chartX.notifySubscribers();
+    self.chartX.valueHasMutated();
+  });
+
   self.chartType.subscribe(function (val) {
     $(document).trigger("forceChartDraw", self);
   });
 
-  self.tempChartOptions = {};
+  self.previousChartOptions = {};
+
+  self.result.meta.subscribe(function(newValue) {
+    self.chartX(self.previousChartOptions.chartX);
+    self.chartYSingle(self.previousChartOptions.chartYSingle);
+    self.chartMapLabel(self.previousChartOptions.chartMapLabel);
+    self.chartYMulti(self.previousChartOptions.chartYMulti || []);
+  });
 
   self.isResultSettingsVisible = ko.observable(typeof snippet.isResultSettingsVisible != "undefined" && snippet.isResultSettingsVisible != null ? snippet.isResultSettingsVisible : false);
   self.toggleResultSettings = function () {
@@ -329,7 +343,7 @@ var Snippet = function (vm, notebook, snippet) {
       properties: self.properties,
       result: self.result.getContext()
     };
-  }
+  };
 
   self._ajaxError = function (data, callback) {
     if (data.status == -2 || data.status == -1) {
@@ -363,13 +377,18 @@ var Snippet = function (vm, notebook, snippet) {
     if (self.status() == 'running' || self.status() == 'loading' || now - self.lastExecuted < 1000) {
       return;
     }
+    self.previousChartOptions = {
+      chartX: typeof self.chartX() !== "undefined" ? self.chartX() : self.previousChartOptions.chartX,
+      chartYSingle: typeof self.chartYSingle() !== "undefined" ? self.chartYSingle() : self.previousChartOptions.chartYSingle,
+      chartMapLabel: typeof self.chartMapLabel() !== "undefined" ? self.chartMapLabel() : self.previousChartOptions.chartMapLabel,
+      chartYMulti: typeof self.chartYMulti() !== "undefined" ? self.chartYMulti() : self.previousChartOptions.chartYMulti
+    };
     $(document).trigger("executeStarted", self);
     self.lastExecuted = now;
     $(".jHueNotify").hide();
     logGA('/execute/' + self.type());
 
     self.status('running');
-    self.result.clear();
     self.errors([]);
     self.result.logLines = 0;
     self.progress(0);
@@ -383,10 +402,8 @@ var Snippet = function (vm, notebook, snippet) {
       snippet: ko.mapping.toJSON(self.getContext())
     }, function (data) {
       if (data.status == 0) {
-        $.each(data.handle, function (key, val) {
-          self.result.handle()[key] = val;
-        });
-
+        self.result.clear();
+        self.result.handle(data.handle);
         self.result.hasResultset(data.handle.has_result_set);
         self.checkStatus();
       } else {

+ 119 - 119
apps/spark/src/spark/templates/editor_components.mako

@@ -477,133 +477,138 @@ from desktop.views import _ko
     <!-- /ko -->
   </div>
 
-
-  <div class="row-fluid" data-bind="slideVisible: result.hasSomeResults() && result.type() == 'table' && showGrid()" style="display:none; max-height: 400px; margin-top: 4px">
-    <div data-bind="visible: isResultSettingsVisible, css:{'span2 result-settings': isResultSettingsVisible, 'hidden': ! isResultSettingsVisible()}">
-      <ul class="nav nav-list" style="border: none; background-color: #FFF">
-        <li class="nav-header pointer" data-bind="click: toggleResultSettings" title="${_('Hide columns')}">${_('columns')}</li>
-        </a>
-      </ul>
-      <ul class="unstyled" data-bind="foreach: result.meta">
-        <li data-bind="visible: name != ''">
-          <input type="checkbox" checked="checked" data-bind="event: { change: function(){toggleColumn($element, $index());}}" />
-          <a class="pointer" data-bind="text: $data.name, click: function(){ scrollToColumn($element, $index()); }"></a>
-        </li>
-      </ul>
-    </div>
-    <div data-bind="css: {'span10': isResultSettingsVisible, 'span12 nomargin': !isResultSettingsVisible()}">
-      <div data-bind="attr: { 'id': 'toggleResultSettingsGrid' + id()}, event: { mouseover: function(){ $('#toggleResultSettingsGrid' + id()).addClass('hoverable'); }, mouseout: function(){ $('#toggleResultSettingsGrid' + id()).removeClass('hoverable'); } }, click: toggleResultSettings" class="toggle-result-settings">
-        <a title="${_('Show columns')}" class="pointer" data-bind="visible: !isResultSettingsVisible()">
-          <i class="fa fa-chevron-right"></i>
-        </a>
-        <a title="${_('Hide')}" class="pointer" data-bind="visible: isResultSettingsVisible">
-          <i class="fa fa-chevron-left"></i>
-        </a>
+  <div class="row-fluid table-results" data-bind="visible: result.hasSomeResults() && result.type() == 'table'" style="display: none; max-height: 400px; margin-top: 4px;">
+    <div data-bind="visible: showGrid" style="display: none;">
+      <div data-bind="visible: isResultSettingsVisible, css:{'span2 result-settings': isResultSettingsVisible, 'hidden': ! isResultSettingsVisible()}">
+        <ul class="nav nav-list" style="border: none; background-color: #FFF">
+          <li class="nav-header pointer" data-bind="click: toggleResultSettings" title="${_('Hide columns')}">${_('columns')}</li>
+          </a>
+        </ul>
+        <ul class="unstyled" data-bind="foreach: result.meta">
+          <li data-bind="visible: name != ''">
+            <input type="checkbox" checked="checked" data-bind="event: { change: function(){toggleColumn($element, $index());}}" />
+            <a class="pointer" data-bind="text: $data.name, click: function(){ scrollToColumn($element, $index()); }"></a>
+          </li>
+        </ul>
       </div>
-      <div data-bind="css: resultsKlass, event: { mouseover: function(){ $('#toggleResultSettingsGrid' + id()).addClass('hoverable'); }, mouseout: function(){ $('#toggleResultSettingsGrid' + id()).removeClass('hoverable'); } }">
-        <table class="table table-condensed resultTable" data-tablescroller-fixed-height="360" data-tablescroller-enforce-height="false">
-          <thead>
-          <tr data-bind="foreach: result.meta">
-            <th data-bind="html: ($index() == 0 ? '&nbsp;' : $data.name), css: { 'sort-numeric': isNumericColumn($data.type), 'sort-date': isDateTimeColumn($data.type), 'sort-string': isStringColumn($data.type)}, attr: {'width': $index() == 0 ? '1%' : ''}"></th>
-          </tr>
-          </thead>
-          <tbody>
-          </tbody>
-        </table>
+      <div data-bind="css: {'span10': isResultSettingsVisible, 'span12 nomargin': !isResultSettingsVisible()}">
+        <div data-bind="attr: { 'id': 'toggleResultSettingsGrid' + id() }, click: toggleResultSettings" class="toggle-result-settings show-result-settings">
+          <a title="${_('Show columns')}" class="pointer" data-bind="visible: !isResultSettingsVisible()">
+            <i class="fa fa-chevron-right"></i>
+          </a>
+          <a title="${_('Hide')}" class="pointer" data-bind="visible: isResultSettingsVisible">
+            <i class="fa fa-chevron-left"></i>
+          </a>
+        </div>
+        <div data-bind="css: resultsKlass">
+          <table class="table table-condensed resultTable" data-tablescroller-fixed-height="360" data-tablescroller-enforce-height="false">
+            <thead>
+            <tr data-bind="foreach: result.meta">
+              <th data-bind="html: ($index() == 0 ? '&nbsp;' : $data.name), css: { 'sort-numeric': isNumericColumn($data.type), 'sort-date': isDateTimeColumn($data.type), 'sort-string': isStringColumn($data.type)}, attr: {'width': $index() == 0 ? '1%' : ''}"></th>
+            </tr>
+            </thead>
+            <tbody>
+            </tbody>
+          </table>
+        </div>
       </div>
     </div>
-  </div>
 
-  <div class="row-fluid" data-bind="slideVisible: status() != 'ready' && showChart()" style="display:none; max-height: 400px; margin-top: 4px">
-    <div data-bind="visible: isResultSettingsVisible, css:{'span2 result-settings': isResultSettingsVisible, 'hidden': ! isResultSettingsVisible()}">
-      <div style="float: right; margin-right: -30px; margin-top:0" data-bind="attr: { 'class': 'toggle-result-settings toggleResultSettingsChart' + id()}, event: { mouseover: function(){ $('.toggleResultSettingsChart' + id()).addClass('hoverable'); }, mouseout: function(){ $('.toggleResultSettingsChart' + id()).removeClass('hoverable'); } }, click: toggleResultSettings">
-        <a title="${_('Hide settings')}" class="pointer">
-          <i class="fa fa-chevron-left"></i>
-        </a>
-      </div>
-      <div>
-        <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != ''">
-          <li data-bind="visible: [ko.HUE_CHARTS.TYPES.MAP, ko.HUE_CHARTS.TYPES.GRADIENTMAP, ko.HUE_CHARTS.TYPES.PIECHART].indexOf(chartType()) == -1" class="nav-header">${_('x-axis')}</li>
-          <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP" class="nav-header">${_('region')}</li>
-          <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.MAP" class="nav-header">${_('latitude')}</li>
-          <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.PIECHART" class="nav-header">${_('legend')}</li>
-        </ul>
-        <div data-bind="visible: chartType() != ''">
-          <select data-bind="options: (chartType() == ko.HUE_CHARTS.TYPES.BARCHART || chartType() == ko.HUE_CHARTS.TYPES.PIECHART || chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP) ? 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}" class="input-medium"></select>
+    <div data-bind="visible: showChart" style="display:none;">
+      <div data-bind="visible: isResultSettingsVisible, css:{'span2 result-settings': isResultSettingsVisible, 'hidden': ! isResultSettingsVisible()}">
+        <div style="float: right; margin-right: -30px; margin-top:0" data-bind="attr: { 'class': 'hover-actions toggle-result-settings toggleResultSettingsChart' + id() }, click: toggleResultSettings">
+          <a title="${_('Hide settings')}" class="pointer">
+            <i class="fa fa-chevron-left"></i>
+          </a>
         </div>
+        <div>
+          <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != ''">
+            <li data-bind="visible: [ko.HUE_CHARTS.TYPES.MAP, ko.HUE_CHARTS.TYPES.GRADIENTMAP, ko.HUE_CHARTS.TYPES.PIECHART].indexOf(chartType()) == -1" class="nav-header">${_('x-axis')}</li>
+            <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP" class="nav-header">${_('region')}</li>
+            <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.MAP" class="nav-header">${_('latitude')}</li>
+            <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.PIECHART" class="nav-header">${_('legend')}</li>
+          </ul>
+          <div data-bind="visible: chartType() != ''">
+            <select data-bind="options: (chartType() == ko.HUE_CHARTS.TYPES.BARCHART || chartType() == ko.HUE_CHARTS.TYPES.PIECHART || chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP) ? 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}" class="input-medium"></select>
+          </div>
 
-        <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != ''">
-          <li data-bind="visible: [ko.HUE_CHARTS.TYPES.MAP, ko.HUE_CHARTS.TYPES.GRADIENTMAP, ko.HUE_CHARTS.TYPES.PIECHART].indexOf(chartType()) == -1" class="nav-header">${_('y-axis')}</li>
-          <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP" class="nav-header">${_('value')}</li>
-          <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.MAP" class="nav-header">${_('longitude')}</li>
-          <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.PIECHART" class="nav-header">${_('value')}</li>
-        </ul>
+          <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != ''">
+            <li data-bind="visible: [ko.HUE_CHARTS.TYPES.MAP, ko.HUE_CHARTS.TYPES.GRADIENTMAP, ko.HUE_CHARTS.TYPES.PIECHART].indexOf(chartType()) == -1" class="nav-header">${_('y-axis')}</li>
+            <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP" class="nav-header">${_('value')}</li>
+            <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.MAP" class="nav-header">${_('longitude')}</li>
+            <li data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.PIECHART" class="nav-header">${_('value')}</li>
+          </ul>
+
+          <div style="overflow-y: scroll; max-height: 220px" data-bind="visible: chartType() != '' && (chartType() == ko.HUE_CHARTS.TYPES.BARCHART || chartType() == ko.HUE_CHARTS.TYPES.LINECHART)">
+            <ul class="unstyled" data-bind="foreach: result.cleanedNumericMeta">
+              <li><input type="checkbox" data-bind="checkedValue: name, checked: $parent.chartYMulti" /> <span data-bind="text: $data.name"></span></li>
+            </ul>
+          </div>
+          <div data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.PIECHART || chartType() == ko.HUE_CHARTS.TYPES.MAP || chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP || chartType() == ko.HUE_CHARTS.TYPES.SCATTERCHART">
+            <select data-bind="options: chartType() == ko.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}" class="input-medium"></select>
+          </div>
 
-        <div style="overflow-y: scroll; max-height: 220px" data-bind="visible: chartType() != '' && (chartType() == ko.HUE_CHARTS.TYPES.BARCHART || chartType() == ko.HUE_CHARTS.TYPES.LINECHART)">
-          <ul class="unstyled" data-bind="foreach: result.cleanedNumericMeta">
-            <li><input type="checkbox" data-bind="checkedValue: name, checked: $parent.chartYMulti" /> <span data-bind="text: $data.name"></span></li>
+          <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != '' && chartType() == ko.HUE_CHARTS.TYPES.MAP">
+            <li class="nav-header">${_('label')}</li>
           </ul>
-        </div>
-        <div data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.PIECHART || chartType() == ko.HUE_CHARTS.TYPES.MAP || chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP || chartType() == ko.HUE_CHARTS.TYPES.SCATTERCHART">
-          <select data-bind="options: chartType() == ko.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}" class="input-medium"></select>
-        </div>
+          <div data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.MAP">
+            <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}" class="input-medium"></select>
+          </div>
 
-        <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != '' && chartType() == ko.HUE_CHARTS.TYPES.MAP">
-          <li class="nav-header">${_('label')}</li>
-        </ul>
-        <div data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.MAP">
-          <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}" class="input-medium"></select>
-        </div>
+          <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != '' && chartType() == ko.HUE_CHARTS.TYPES.SCATTERCHART">
+            <li class="nav-header">${_('scatter group')}</li>
+          </ul>
+          <div data-bind="visible: chartType() != '' && chartType() == ko.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}" class="input-medium"></select>
+          </div>
 
-        <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != '' && chartType() == ko.HUE_CHARTS.TYPES.SCATTERCHART">
-          <li class="nav-header">${_('scatter group')}</li>
-        </ul>
-        <div data-bind="visible: chartType() != '' && chartType() == ko.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}" class="input-medium"></select>
-        </div>
+          <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.SCATTERCHART">
+            <li class="nav-header">${_('scatter size')}</li>
+          </ul>
+          <div data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.SCATTERCHART">
+            <select data-bind="options: result.cleanedMeta, value: chartScatterSize, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartScatterSize}" class="input-medium"></select>
+          </div>
 
-        <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.SCATTERCHART">
-          <li class="nav-header">${_('scatter size')}</li>
-        </ul>
-        <div data-bind="visible: chartType() == ko.HUE_CHARTS.TYPES.SCATTERCHART">
-          <select data-bind="options: result.cleanedMeta, value: chartScatterSize, optionsText: 'name', optionsValue: 'name', optionsCaption: '${_ko('Choose a column...')}', select2: { width: '100%', placeholder: '${ _ko("Choose a column...") }', update: chartScatterSize}" class="input-medium"></select>
+          <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != '' && chartType() != ko.HUE_CHARTS.TYPES.MAP && chartType() != ko.HUE_CHARTS.TYPES.GRADIENTMAP && chartType() != ko.HUE_CHARTS.TYPES.SCATTERCHART">
+            <li class="nav-header">${_('sorting')}</li>
+          </ul>
+          <div class="btn-group" data-toggle="buttons-radio" data-bind="visible: chartType() != '' && chartType() != ko.HUE_CHARTS.TYPES.MAP && chartType() != ko.HUE_CHARTS.TYPES.GRADIENTMAP && chartType() != ko.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>
+      </div>
 
-        <ul class="nav nav-list" style="border: none; background-color: #FFF" data-bind="visible: chartType() != '' && chartType() != ko.HUE_CHARTS.TYPES.MAP && chartType() != ko.HUE_CHARTS.TYPES.GRADIENTMAP && chartType() != ko.HUE_CHARTS.TYPES.SCATTERCHART">
-          <li class="nav-header">${_('sorting')}</li>
-        </ul>
-        <div class="btn-group" data-toggle="buttons-radio" data-bind="visible: chartType() != '' && chartType() != ko.HUE_CHARTS.TYPES.MAP && chartType() != ko.HUE_CHARTS.TYPES.GRADIENTMAP && chartType() != ko.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 data-bind="css:{'span10 chart-container': isResultSettingsVisible, 'span12 nomargin chart-container': !isResultSettingsVisible() }">
+        <div style="margin-right: -30px; margin-top:0" data-bind="visible: !isResultSettingsVisible(), click: toggleResultSettings, attr: { 'class': 'hover-actions toggle-result-settings toggleResultSettingsChart' + id()}">
+          <a title="${_('Show settings')}" class="pointer">
+            <i class="fa fa-chevron-right"></i>
+          </a>
         </div>
-      </div>
-    </div>
-    <div data-bind="css:{'span10 chart-container': isResultSettingsVisible, 'span12 nomargin chart-container': !isResultSettingsVisible()}, event: { mouseover: function(){ $('.toggleResultSettingsChart' + id()).addClass('hoverable'); }, mouseout: function(){ $('.toggleResultSettingsChart' + id()).removeClass('hoverable'); } }">
 
-      <div style="margin-right: -30px; margin-top:0" data-bind="visible: !isResultSettingsVisible(), click: toggleResultSettings, attr: { 'class': 'toggle-result-settings toggleResultSettingsChart' + id()}">
-        <a title="${_('Show settings')}" class="pointer">
-          <i class="fa fa-chevron-right"></i>
-        </a>
-      </div>
+        <h1 class="empty" data-bind="visible: !hasDataForChart()">${ _('Select the chart parameters on the left') }</h1>
 
-      <div data-bind="attr:{'id': 'pieChart_'+id()}, pieChart: {data: {counts: result.data, sorting: chartSorting(), snippet: $data}, fqs: ko.observableArray([]),
-                  transformer: pieChartDataTransformer, maxWidth: 350, parentSelector: '.chart-container' }, visible: chartType() == ko.HUE_CHARTS.TYPES.PIECHART" class="chart"></div>
+        <div data-bind="visible: hasDataForChart()">
+          <div data-bind="attr:{'id': 'pieChart_'+id()}, pieChart: {data: {counts: result.data, sorting: chartSorting(), snippet: $data}, fqs: ko.observableArray([]),
+                      transformer: pieChartDataTransformer, maxWidth: 350, parentSelector: '.chart-container' }, visible: chartType() == ko.HUE_CHARTS.TYPES.PIECHART" class="chart"></div>
 
-      <div data-bind="attr:{'id': 'barChart_'+id()}, barChart: {datum: {counts: result.data, sorting: chartSorting(), snippet: $data}, fqs: ko.observableArray([]), hideSelection: true,
-                  transformer: multiSerieDataTransformer, stacked: false, showLegend: true},  stacked: true, showLegend: true, visible: chartType() == ko.HUE_CHARTS.TYPES.BARCHART" class="chart"></div>
+          <div data-bind="attr:{'id': 'barChart_'+id()}, barChart: {datum: {counts: result.data, sorting: chartSorting(), snippet: $data}, fqs: ko.observableArray([]), hideSelection: true,
+                      transformer: multiSerieDataTransformer, stacked: false, showLegend: true},  stacked: true, showLegend: true, visible: chartType() == ko.HUE_CHARTS.TYPES.BARCHART" class="chart"></div>
 
-      <div data-bind="attr:{'id': 'lineChart_'+id()}, lineChart: {datum: {counts: result.data, sorting: chartSorting(), snippet: $data},
-                  transformer: multiSerieDataTransformer, showControls: false }, visible: chartType() == ko.HUE_CHARTS.TYPES.LINECHART" class="chart"></div>
+          <div data-bind="attr:{'id': 'lineChart_'+id()}, lineChart: {datum: {counts: result.data, sorting: chartSorting(), snippet: $data},
+                      transformer: multiSerieDataTransformer, showControls: false }, visible: chartType() == ko.HUE_CHARTS.TYPES.LINECHART" class="chart"></div>
 
-      <div data-bind="attr:{'id': 'leafletMapChart_'+id()}, leafletMapChart: {datum: {counts: result.data, sorting: chartSorting(), snippet: $data},
-                  transformer: leafletMapChartDataTransformer, showControls: false, height: 380, visible: chartType() == ko.HUE_CHARTS.TYPES.MAP, forceRedraw: true}" class="chart"></div>
+          <div data-bind="attr:{'id': 'leafletMapChart_'+id()}, leafletMapChart: {datum: {counts: result.data, sorting: chartSorting(), snippet: $data},
+                      transformer: leafletMapChartDataTransformer, showControls: false, height: 380, visible: chartType() == ko.HUE_CHARTS.TYPES.MAP, forceRedraw: true}" class="chart"></div>
 
-      <div data-bind="attr:{'id': 'gradientMapChart_'+id()}, mapChart: {data: {counts: result.data, sorting: chartSorting(), snippet: $data},
-                  transformer: mapChartDataTransformer, isScale: true, showControls: false, height: 380, maxWidth: 750, parentSelector: '.chart-container', visible: chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP}" class="chart"></div>
+          <div data-bind="attr:{'id': 'gradientMapChart_'+id()}, mapChart: {data: {counts: result.data, sorting: chartSorting(), snippet: $data},
+                      transformer: mapChartDataTransformer, isScale: true, showControls: false, height: 380, maxWidth: 750, parentSelector: '.chart-container', visible: chartType() == ko.HUE_CHARTS.TYPES.GRADIENTMAP}" class="chart"></div>
 
-      <div data-bind="attr:{'id': 'scatterChart_'+id()}, scatterChart: {datum: {counts: result.data, sorting: chartSorting(), snippet: $data},
-                  transformer: scatterChartDataTransformer, maxWidth: 350 }, visible: chartType() == ko.HUE_CHARTS.TYPES.SCATTERCHART" class="chart"></div>
+          <div data-bind="attr:{'id': 'scatterChart_'+id()}, scatterChart: {datum: {counts: result.data, sorting: chartSorting(), snippet: $data},
+                      transformer: scatterChartDataTransformer, maxWidth: 350 }, visible: chartType() == ko.HUE_CHARTS.TYPES.SCATTERCHART" class="chart"></div>
+        </div>
+      </div>
     </div>
   </div>
 </script>
@@ -1269,8 +1274,10 @@ from desktop.views import _ko
     }
 
     $(document).on("toggleResultSettings", function (e, snippet) {
-      $("#snippet_" + snippet.id()).find(".chart").trigger("forceUpdate");
-      redrawFixedHeaders();
+      window.setTimeout(function() {
+        $("#snippet_" + snippet.id()).find(".chart").trigger("forceUpdate");
+        redrawFixedHeaders();
+      }, 10)
     });
 
     $(document).on("editorSizeChanged", function () {
@@ -1289,12 +1296,6 @@ from desktop.views import _ko
         _el.dataTable().fnDestroy();
         _el.find("thead tr").empty();
       }
-      snippet.tempChartOptions = {
-        x: snippet.chartX(),
-        yS: snippet.chartYSingle(),
-        yM: snippet.chartYMulti(),
-        label: snippet.chartMapLabel()
-      }
     });
 
     function resizeToggleResultSettings(snippet) {
@@ -1305,6 +1306,9 @@ from desktop.views import _ko
       else {
         _dtElement = $("#snippet_" + snippet.id()).find(".chart:visible");
       }
+      if (_dtElement.length == 0) {
+        _dtElement = $("#snippet_" + snippet.id()).find(".table-results");
+      }
       _dtElement.parents(".snippet-body").find(".toggle-result-settings").css({
         "height": (_dtElement.height() - 30) + "px",
         "line-height": (_dtElement.height() - 30) + "px"
@@ -1330,17 +1334,13 @@ from desktop.views import _ko
           _dtElement.scrollTop(_dtElement.data("scrollPosition"));
           redrawFixedHeaders();
           resizeToggleResultSettings(options.snippet);
-        }, 100);
+        }, 300);
       }
       else {
         var _dtElement = $("#snippet_" + options.snippet.id()).find(".dataTables_wrapper");
         _dtElement.animate({opacity: '1'}, 50);
         _dtElement.off("scroll");
       }
-      options.snippet.chartX(options.snippet.tempChartOptions.x);
-      options.snippet.chartX(options.snippet.tempChartOptions.x);
-      options.snippet.chartYSingle(options.snippet.tempChartOptions.yS);
-      options.snippet.chartMapLabel(options.snippet.tempChartOptions.label);
       $("#snippet_" + options.snippet.id()).find("select").trigger('chosen:updated');
     });