Browse Source

HUE-8178 [charts] Move the value back to popup on mouseover

jdesjean 7 years ago
parent
commit
8d574c5399

+ 11 - 3
desktop/core/src/desktop/static/desktop/css/nv.d3.css

@@ -72,9 +72,17 @@ svg:not(.leaflet-zoom-animated) {
 .nvtooltip b {
   width: 100px;
   display: inline-block;
-  text-align: right;
-  direction: rtl;
+  text-align: left;
+  padding-left: 1px;
+  direction: ltr;
   overflow: hidden;
   text-overflow: ellipsis;
   vertical-align: middle;
-}
+}
+
+.nvtooltip span.circle {
+  border-radius: 6px;
+  width: 12px;
+  height: 12px;
+  vertical-align: middle;
+}

+ 28 - 30
desktop/core/src/desktop/static/desktop/js/ko.charts.js

@@ -15,7 +15,7 @@
 // limitations under the License.
 
 (function () {
-  var MS = 1,
+	var MS = 1,
   SECOND_MS = 1000 * MS,
   MINUTE_MS = SECOND_MS * 60,
   HOUR_MS = MINUTE_MS * 60,
@@ -23,12 +23,6 @@
   WEEK_MS = DAY_MS * 7,
   MONTH_MS = DAY_MS * 30.5,
   YEAR_MS = DAY_MS * 365;
-  TIME_INTERVALS = [{ms: SECOND_MS * 1, coeff: 1, unit: 'SECONDS'}, {ms: SECOND_MS * 2, coeff: 2, unit: 'SECONDS'}, {ms: SECOND_MS * 5, coeff: 5, unit: 'SECONDS'}, {ms: SECOND_MS * 10, coeff: 10, unit: 'SECONDS'}, {ms: SECOND_MS * 15, coeff: 15, unit: 'SECONDS'}, {ms: SECOND_MS * 30, coeff: 30, unit: 'SECONDS'},
-  {ms: MINUTE_MS * 1, coeff: 1, unit: 'MINUTES'}, {ms: MINUTE_MS * 2, coeff: 2, unit: 'MINUTES'}, {ms: MINUTE_MS * 5, coeff: 5, unit: 'MINUTES'}, {ms: MINUTE_MS * 10, coeff: 10, unit: 'MINUTES'}, {ms: MINUTE_MS * 15, coeff: 15, unit: 'MINUTES'}, {ms: MINUTE_MS * 30, coeff: 30, unit: 'MINUTES'},
-  {ms: HOUR_MS * 1, coeff: 1, unit: 'HOURS'}, {ms: HOUR_MS * 2, coeff: 2, unit: 'HOURS'}, {ms: HOUR_MS * 4, coeff: 4, unit: 'HOURS'}, {ms: HOUR_MS * 6, coeff:6, unit: 'HOURS'}, {ms: HOUR_MS * 8, coeff: 8, unit: 'HOURS'}, {ms: HOUR_MS * 12, coeff: 12, unit: 'HOURS'},
-  {ms: DAY_MS * 1, coeff: 1, unit: 'DAYS'}, {ms: DAY_MS * 2, coeff: 2, unit: 'MONTHS'}, {ms: WEEK_MS * 1, coeff: 7, unit: 'DAYS'}, {ms: WEEK_MS * 2, coeff: 14, unit: 'DAYS'},
-  {ms: MONTH_MS * 1, coeff: 1, unit: 'MONTHS'}, {ms: MONTH_MS * 2, coeff: 2, unit: 'MONTHS'}, {ms: MONTH_MS * 3, coeff: 3, unit: 'MONTHS'}, {ms: MONTH_MS * 6, coeff: 6, unit: 'MONTHS'},
-  {ms: YEAR_MS * 1, coeff: 1, unit: 'YEARS'}];
   ko.HUE_CHARTS = {
     TYPES: {
       COUNTER: "counter",
@@ -242,21 +236,6 @@
       }
     }
   };
-  function getInterval(domainMs, maxSlots) {
-    var biggestInterval = TIME_INTERVALS[TIME_INTERVALS.length-1];
-    var biggestIntervalIsTooSmall = domainMs / biggestInterval.ms > maxSlots;
-    if (biggestIntervalIsTooSmall) {
-      var coeff = Math.ceil(domainMs / maxSlots);
-      return "+" + coeff + "YEARS";
-    }
-    for (var i = TIME_INTERVALS.length - 2; i >= 0; i--) {
-      var slots = domainMs / TIME_INTERVALS[i].ms;
-      if (slots > maxSlots) {
-        return "+" + TIME_INTERVALS[i + 1].coeff + TIME_INTERVALS[i + 1].unit;
-      }
-    }
-    return "+" + TIME_INTERVALS[0].coeff + TIME_INTERVALS[0].unit;
-  }
   ko.bindingHandlers.timelineChart = {
     init: function (element, valueAccessor) {
       if (valueAccessor().type && valueAccessor().type() == "line"){
@@ -853,9 +832,10 @@
           _chart.xScale(d3v3.time.scale.utc());
           _chart.tooltipContent(function(values){
             return values.map(function (value) {
-              var x = moment(value.x).utc().format("YYYY-MM-DD HH:mm:ss"),
-              y = _chart.yAxis.tickFormat()(value.y);
-              return {x: x, y: y, key: value.key};
+              value = JSON.parse(JSON.stringify(value));
+              value.x = moment(value.x).utc().format("YYYY-MM-DD HH:mm:ss");
+              value.y = _chart.yAxis.tickFormat()(value.y);
+              return value;
             });
           });
           _chart.xAxis.tickFormat(multi(_chart.xAxis, _chart));
@@ -935,7 +915,7 @@
     return true;
   }
   function handleSelection(_chart, _options, _datum) {
-    var i;
+    var i, j;
     var serieEnabled = {};
     if (_options.selectedSerie) {
       var selectedSerie = _options.selectedSerie();
@@ -948,7 +928,24 @@
         }
       }
       if (enabledCount === 0) {
-        for (i = 0; i < Math.min(5, _datum.length); i++) {
+        // Get the 5 series with the most non zero elements on x axis & total value.
+        var stats = {};
+        for (i = 0; i < _datum.length; i++) {
+          if (!stats[_datum[i].key]) {
+            stats[_datum[i].key] = {count: 0, total: 0};
+          }
+          for (j = 0; j < _datum[i].values.length; j++) {
+            stats[_datum[i].key].count += Math.min(_datum[i].values[j].y, 1);
+            stats[_datum[i].key].total += _datum[i].values[j].y;
+          }
+        }
+        var aStats = Object.keys(stats).map(function(key) {
+          return {key: key, stat: stats[key]};
+        });
+        aStats.sort(function (a, b) {
+          return a.stat.count - b.stat.count || a.stat.total - b.stat.total;
+        });
+        for (i = aStats.length - 1; i >= Math.max(aStats.length - 5, 0); i--) {
           _datum[i].disabled = false;
           selectedSerie[_datum[i].key] = true;
         }
@@ -1070,9 +1067,10 @@
         _chart.staggerLabels(false);
         _chart.tooltipContent(function(values){
           return values.map(function (value) {
-            var x = moment(value.x).utc().format("YYYY-MM-DD HH:mm:ss"),
-            y = _chart.yAxis.tickFormat()(value.y);
-            return {x: x, y: y, key: value.key};
+            value = JSON.parse(JSON.stringify(value));
+            value.x = moment(value.x).utc().format("YYYY-MM-DD HH:mm:ss");
+            value.y = _chart.yAxis.tickFormat()(value.y);
+            return value;
           });
         });
         _chart.xAxis.tickFormat(multi(_chart.xAxis));

+ 38 - 17
desktop/core/src/desktop/static/desktop/js/nv.d3.lineWithBrushChart.js

@@ -57,9 +57,9 @@ nv.models.lineWithBrushChart = function() {
         '<p>' + hueUtils.htmlEncode(value.y) + ' on ' + hueUtils.htmlEncode(value.x) + '</p>';
     }
     , tooltipMultiple = function (values) {
-      return values.map(function (value) {
-          return '<p><b>' + hueUtils.htmlEncode(value.key) + '</b>: ' +  hueUtils.htmlEncode(value.y) + '</p>';
-        }).join("") + '<h3>' + hueUtils.htmlEncode(values[0] && values[0].x) + '</h3>';
+      return '<h3>' + hueUtils.htmlEncode(values[0] && values[0].x) + '</h3>' + values.map(function (value) {
+          return '<p><span class="circle" style="background-color:'+value.color+'"></span><b>' + hueUtils.htmlEncode(value.key) + '</b> ' +  hueUtils.htmlEncode(value.y) + '</p>';
+        }).join("");
     }
     , x
     , y
@@ -105,20 +105,20 @@ nv.models.lineWithBrushChart = function() {
       values = (e.list || [e]).map(function (e) {
         var x = xAxis.tickFormat()(lines.x()(e.point, e.pointIndex)),
         y = yAxis.tickFormat()(lines.y()(e.point, e.pointIndex));
-        return {x: x, y: y, key: displayValuesInLegend && (e.point.obj.field || e.point.obj.fq_fields && e.point.obj.fq_fields[0]) || e.series.key};
+        return {x: x, y: y, key: displayValuesInLegend && (e.point.obj.field || e.point.obj.fq_fields && e.point.obj.fq_fields[0]) || e.series.key, color: e.series.color || color(e.series, e.point.series)};
       });
     } else {
       values = tooltip((e.list || [e]).map(function (e) {
         var x = lines.x()(e.point, e.pointIndex),
         y = lines.y()(e.point, e.pointIndex);
-        return {x: x, y: y, key: displayValuesInLegend && (e.point.obj.field || e.point.obj.fq_fields && e.point.obj.fq_fields[0]) || e.series.key};
+        return {x: x, y: y, key: displayValuesInLegend && (e.point.obj.field || e.point.obj.fq_fields && e.point.obj.fq_fields[0]) || e.series.key, color: e.series.color || color(e.series, e.point.series)};
       }));
     }
 
 
     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
         top = e.pos[1] + ( offsetElement.offsetTop || 0),
-        content = displayValuesInLegend ? tooltipSimple(values[0]) : values.length > 1 && tooltipMultiple(values) || tooltipSingle(values[0]);
+        content = displayValuesInLegend ? tooltipSimple(values[0]) : tooltipMultiple(values);
 
     nv.tooltip.show([left, top], content, null, null, offsetElement);
   };
@@ -235,8 +235,12 @@ nv.models.lineWithBrushChart = function() {
             .call(legend)
           .selectAll('text')
           .text(function (d) {
-            var addEllipsis = d.key && d.key.length > 12;
-            return d.key && d.key.substring(0, 12) + (addEllipsis ? '...' : '');
+            if (displayValuesInLegend) {
+              var addEllipsis = d.key && d.key.length > 12;
+              return d.key && d.key.substring(0, 12) + (addEllipsis ? '...' : '');
+            } else {
+              return d.key;
+            }
           })
           .append('title')
           .text(function(d){
@@ -585,27 +589,44 @@ nv.models.lineWithBrushChart = function() {
       }
 
       function getElByMouse () {
-        var point = x.invert(d3v3.mouse(this)[0]);
+        var xy = d3v3.mouse(this);
+        var px = x.invert(xy[0]);
         if (!filteredData.length || !filteredData[0].values.length) {
           return null;
         }
-        var min = Math.abs(filteredData[0].values[0].x - point);
+        var minx = Math.abs(filteredData[0].values[0].x - px);
         var distances = {};
-
-        for (var j = 0; j < filteredData.length; j++) {
-          for (var i = 0; i < filteredData[j].values.length; i++) {
-            var diff = Math.abs(fGetNumericValue(filteredData[j].values[i].x) - point);
+        var py = y.invert(xy[1]);
+        var i, j, diff;
+        // Find closest on x axis
+        for (j = 0; j < filteredData.length; j++) {
+          for (i = 0; i < filteredData[j].values.length; i++) {
+            diff = Math.abs(fGetNumericValue(filteredData[j].values[i].x) - px);
             if (!distances[diff]) {
               distances[diff] = [];
             }
             filteredData[j].values[i].seriesKey = filteredData[j].key;
             distances[diff].push(filteredData[j].values[i]);
-            if (diff < min) {
-              min = diff;
+            if (diff < minx) {
+              minx = diff;
             }
           }
         }
-        return distances[min];
+        // Find series with y axis
+        var miny = Number.MAX_VALUE;
+        var distancesy = {};
+        for (i = 0; i < distances[minx].length; i++) {
+          diff = Math.abs(distances[minx][i].y - py);
+          if (!distancesy[diff]) {
+            distancesy[diff] = [];
+          }
+          distancesy[diff].push(distances[minx][i]);
+          if (diff < miny) {
+            miny = diff;
+          }
+        };
+
+        return distancesy[miny];
       }
       function onMouseMove () {
         var el = getElByMouse.call(this);

+ 47 - 13
desktop/core/src/desktop/static/desktop/js/nv.d3.multiBarWithBrushChart.js

@@ -61,9 +61,9 @@ nv.models.multiBarWithBrushChart = function() {
         '<p>' + hueUtils.htmlEncode(value.y) + ' on ' + hueUtils.htmlEncode(value.x) + '</p>';
     }
     , tooltipMultiple = function (values) {
-      return values.map(function (value) {
-          return '<p><b>' + hueUtils.htmlEncode(value.key) + '</b>: ' +  hueUtils.htmlEncode(value.y) + '</p>';
-        }).join("") + '<h3>' + hueUtils.htmlEncode(values[0] && values[0].x) + '</h3>';
+      return '<h3>' + hueUtils.htmlEncode(values[0] && values[0].x) + '</h3>' + values.map(function (value) {
+          return '<p><span class="circle" style="background-color:'+value.color+'"></span><b>' + hueUtils.htmlEncode(value.key) + '</b> ' +  hueUtils.htmlEncode(value.y) + '</p>';
+        }).join("");
     }
     , x //can be accessed via chart.xScale()
     , y //can be accessed via chart.yScale()
@@ -118,20 +118,20 @@ nv.models.multiBarWithBrushChart = function() {
       values = (e.list || [e]).map(function (e) {
         var x = xAxis.tickFormat()(multibar.x()(e.point, e.pointIndex)),
         y = yAxis.tickFormat()(multibar.y()(e.point, e.pointIndex));
-        return {x: x, y: y, key: displayValuesInLegend && (e.point.obj.field || e.point.obj.fq_fields && e.point.obj.fq_fields[0]) || e.series.key};
+        return {x: x, y: y, key: displayValuesInLegend && (e.point.obj.field || e.point.obj.fq_fields && e.point.obj.fq_fields[0]) || e.series.key, color: e.series.color || color(e.series, e.point.series)};
       });
     } else {
       values = tooltipContent((e.list || [e]).map(function (e) {
         var x = multibar.x()(e.point, e.pointIndex),
         y = multibar.y()(e.point, e.pointIndex);
-        return {x: x, y: y, key: displayValuesInLegend && (e.point.obj.field || e.point.obj.fq_fields && e.point.obj.fq_fields[0]) || e.series.key};
+        return {x: x, y: y, key: displayValuesInLegend && (e.point.obj.field || e.point.obj.fq_fields && e.point.obj.fq_fields[0]) || e.series.key, color: e.series.color || color(e.series, e.point.series)};
       }));
     }
 
 
     var left = e.pos[0] + ( offsetElement.offsetLeft || 0 ),
         top = e.pos[1] + ( offsetElement.offsetTop || 0),
-        content = displayValuesInLegend ? tooltipSimple(values[0]) : values.length > 1 && tooltipMultiple(values) || tooltipSingle(values[0]);
+        content = displayValuesInLegend ? tooltipSimple(values[0]) : tooltipMultiple(values);
 
     nv.tooltip.show([left, top], content, e.value < 0 ? 'n' : 's', null, offsetElement);
   };
@@ -261,8 +261,12 @@ nv.models.multiBarWithBrushChart = function() {
           .selectAll('text')
           .text(function (d) {
             var value = d.key && d.key + '';
-            var addEllipsis = value && value.length > 12;
-            return value && value.substring(0, 12) + (addEllipsis ? '...' : '');
+            if (displayValuesInLegend) {
+              var addEllipsis = value && value.length > 12;
+              return value && value.substring(0, 12) + (addEllipsis ? '...' : '');
+            } else {
+              return value;
+            }
           })
           .append('title')
           .text(function(d){
@@ -612,8 +616,9 @@ nv.models.multiBarWithBrushChart = function() {
           }
         }
       }
-      function getElByMouse (coords, filterSeries) {
-        var extent = coords || d3v3.mouse(this)[0];
+      function getElByMouse (coords, allSeries) {
+        var xy = d3v3.mouse(this);
+        var extent = coords || xy[0];
         var _l, _j;
         var _width = x.rangeBand();
         var _leftEdges = x.range();
@@ -621,8 +626,8 @@ nv.models.multiBarWithBrushChart = function() {
         _l = Math.max(_l - 1, 0);
         var value = fGetNumericValue(x.domain()[_l]);
         var values = [];
-        if (!filterSeries || multibar.stacked()) {
-          var j;
+        var i,j;
+        if (allSeries) {
           for (j = 0; j < filteredData.length; j++) {
             for (i = 0; i < filteredData[j].values.length; i++) {
               if (fGetNumericValue(getX(filteredData[j].values[i])) == value) {
@@ -630,7 +635,7 @@ nv.models.multiBarWithBrushChart = function() {
               }
             }
           }
-        } else {
+        } else if (!multibar.stacked()) {
           var serieIndex = Math.floor(Math.min(extent - _leftEdges[_l], _width - 0.001) / (_width / filteredData.length)); // Math.min(extent - _leftEdges[_l], _width - 0.001) to handle the padding at the end. Would it make sense to remove the padding?
           var i;
           if (serieIndex < 0) return null;
@@ -640,6 +645,35 @@ nv.models.multiBarWithBrushChart = function() {
               break;
             }
           }
+        } else {
+          // serieIndex depends on the y position of the bar
+          // y(getY(d) + (multibar.stacked() ? d.y0 : 0)
+          // get all series for the x value
+          var py = Math.round(y.invert(xy[1]));
+          var mindy = Number.MAX_VALUE;
+          var distances = {};
+          for (j = 0; j < filteredData.length; j++) {
+            for (i = 0; i < filteredData[j].values.length; i++) {
+              if (fGetNumericValue(getX(filteredData[j].values[i])) == value) {
+                if (py >= filteredData[j].values[i].y0 && py <= filteredData[j].values[i].y1) {
+                  values.push(filteredData[j].values[i]);
+                } else {
+                  var ym = filteredData[j].values[i].y0 + (filteredData[j].values[i].y1 - filteredData[j].values[i].y0) / 2;
+                  var dy = Math.abs(py - ym);
+                  if (!distances[dy]) {
+                    distances[dy] = [];
+                  }
+                  distances[dy].push(filteredData[j].values[i]);
+                  if (dy < mindy) {
+                    mindy = dy;
+                  }
+                }
+              }
+            }
+          }
+          if (!values.length) {
+            values = distances[mindy];
+          }
         }
         return values;
       }

+ 0 - 6
desktop/libs/dashboard/src/dashboard/templates/common_search.mako

@@ -1266,7 +1266,6 @@ ${ dashboard.layout_skeleton(suffix='search') }
         type: $root.collection.getFacetById($parent.id()).properties.timelineChartType,
         hideSelection: true,
         hideStacked: hideStacked,
-        displayValuesInLegend: displayValuesInLegend,
         selectedSerie: selectedSerie,
         fqs: $root.query.fqs,
         slot: $root.collection.getFacetById($parent.id()).properties.slot,
@@ -1350,7 +1349,6 @@ ${ dashboard.layout_skeleton(suffix='search') }
       enableSelection: true,
       hideSelection: true,
       hideStacked: hideStacked,
-      displayValuesInLegend: displayValuesInLegend,
       slot: $root.collection.getFacetById($parent.id()).properties.slot,
       transformer: ($data.type == 'range-up' ? barChartRangeUpDataTransformer : barChartDataTransformer),
       onStateChange: function(state){ $root.collection.getFacetById($parent.id()).properties.stacked(state.stacked); },
@@ -1623,7 +1621,6 @@ ${ dashboard.layout_skeleton(suffix='search') }
               enableSelection: true,
               hideSelection: true,
               hideStacked: hideStacked,
-              displayValuesInLegend: displayValuesInLegend,
               slot: $root.collection.getFacetById($parent.id()).properties.slot,
               transformer: barChartDataTransformer2,
               onStateChange: function(state){ $root.collection.getFacetById($parent.id()).properties.stacked(state.stacked); },
@@ -1652,7 +1649,6 @@ ${ dashboard.layout_skeleton(suffix='search') }
               enableSelection: true,
               hideSelection: true,
               hideStacked: hideStacked,
-              displayValuesInLegend: displayValuesInLegend,
               slot: $root.collection.getFacetById($parent.id()).properties.slot,
               transformer: pivotChartDataTransformer,
               onSelectRange: function(from, to){ $root.collection.selectTimelineFacet2({from: from, to: to, cat: field, widget_id: $parent.id()}) },
@@ -1674,7 +1670,6 @@ ${ dashboard.layout_skeleton(suffix='search') }
             hideSelection: true,
             hideStacked: hideStacked,
             selectedSerie: selectedSerie,
-            displayValuesInLegend: displayValuesInLegend,
             slot: $root.collection.getFacetById($parent.id()).properties.facets()[0].slot,
             onSelectRange: function(from, to){ $root.collection.selectTimelineFacet2({from: from, to: to, cat: field, widget_id: $parent.id()}) },
             onStateChange: function(state){ $root.collection.getFacetById($parent.id()).properties.stacked(state.stacked); },
@@ -1707,7 +1702,6 @@ ${ dashboard.layout_skeleton(suffix='search') }
             enableSelection: true,
             hideSelection: true,
             hideStacked: hideStacked,
-            displayValuesInLegend: displayValuesInLegend,
             selectedSerie: selectedSerie,
             slot: $root.collection.getFacetById($parent.id()).properties.slot,
             onSelectRange: function(from, to){ $root.collection.selectTimelineFacet2({from: from, to: to, cat: field, widget_id: $parent.id()}) },