Fixed attribute name typo in legend follow (#792)
[dygraphs.git] / src / plugins / legend.js
index 3db4d07..40a4863 100644 (file)
@@ -5,7 +5,6 @@
  */
 /*global Dygraph:false */
 
-Dygraph.Plugins.Legend = (function() {
 /*
 Current bits of jankiness:
 - Uses two private APIs:
@@ -18,6 +17,8 @@ Current bits of jankiness:
 /*global Dygraph:false */
 "use strict";
 
+import * as utils from '../dygraph-utils';
+
 
 /**
  * Creates the legend, which appears when the user hovers over the chart.
@@ -25,18 +26,15 @@ Current bits of jankiness:
  *
  * @constructor
  */
-var legend = function() {
+var Legend = function() {
   this.legend_div_ = null;
   this.is_generated_div_ = false;  // do we own this div, or was it user-specified?
 };
 
-legend.prototype.toString = function() {
+Legend.prototype.toString = function() {
   return "Legend Plugin";
 };
 
-// (defined below)
-var generateLegendDashHTML;
-
 /**
  * This is called during the dygraph constructor, after options have been set
  * but before the data is available.
@@ -49,9 +47,8 @@ var generateLegendDashHTML;
  * @param {Dygraph} g Graph instance.
  * @return {object.<string, function(ev)>} Mapping of event names to callbacks.
  */
-legend.prototype.activate = function(g) {
+Legend.prototype.activate = function(g) {
   var div;
-  var divWidth = g.getOption('labelsDivWidth');
 
   var userLabelsDiv = g.getOption('labelsDiv');
   if (userLabelsDiv && null !== userLabelsDiv) {
@@ -61,35 +58,8 @@ legend.prototype.activate = function(g) {
       div = userLabelsDiv;
     }
   } else {
-    // Default legend styles. These can be overridden in CSS by adding
-    // "!important" after your rule, e.g. "left: 30px !important;"
-    var messagestyle = {
-      "position": "absolute",
-      "fontSize": "14px",
-      "zIndex": 10,
-      "width": divWidth + "px",
-      "top": "0px",
-      "left": (g.size().width - divWidth - 2) + "px",
-      "background": "white",
-      "lineHeight": "normal",
-      "textAlign": "left",
-      "overflow": "hidden"};
-
-    // TODO(danvk): get rid of labelsDivStyles? CSS is better.
-    Dygraph.update(messagestyle, g.getOption('labelsDivStyles'));
     div = document.createElement("div");
     div.className = "dygraph-legend";
-    for (var name in messagestyle) {
-      if (!messagestyle.hasOwnProperty(name)) continue;
-
-      try {
-        div.style[name] = messagestyle[name];
-      } catch (e) {
-        console.warn("You are using unsupported css properties for your " +
-            "browser in labelsDivStyles");
-      }
-    }
-
     // TODO(danvk): come up with a cleaner way to expose this.
     g.graphDiv.appendChild(div);
     this.is_generated_div_ = true;
@@ -121,7 +91,7 @@ var escapeHTML = function(str) {
   return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
 };
 
-legend.prototype.select = function(e) {
+Legend.prototype.select = function(e) {
   var xValue = e.selectedX;
   var points = e.selectedPoints;
   var row = e.selectedRow;
@@ -135,7 +105,7 @@ legend.prototype.select = function(e) {
   if (legendMode === 'follow') {
     // create floating legend div
     var area = e.dygraph.plotter_.area;
-    var labelsDivWidth = e.dygraph.getOption('labelsDivWidth');
+    var labelsDivWidth = this.legend_div_.offsetWidth;
     var yAxisLabelWidth = e.dygraph.getOptionForAxis('axisLabelWidth', 'y');
     // determine floating [left, top] coordinates of the legend div
     // within the plotter_ area
@@ -155,12 +125,12 @@ legend.prototype.select = function(e) {
     this.legend_div_.style.top = topLegend + "px";
   }
 
-  var html = legend.generateLegendHTML(e.dygraph, xValue, points, this.one_em_width_, row);
+  var html = Legend.generateLegendHTML(e.dygraph, xValue, points, this.one_em_width_, row);
   this.legend_div_.innerHTML = html;
   this.legend_div_.style.display = '';
 };
 
-legend.prototype.deselect = function(e) {
+Legend.prototype.deselect = function(e) {
   var legendMode = e.dygraph.getOption('legend');
   if (legendMode !== 'always') {
     this.legend_div_.style.display = "none";
@@ -170,11 +140,11 @@ legend.prototype.deselect = function(e) {
   var oneEmWidth = calculateEmWidthInDiv(this.legend_div_);
   this.one_em_width_ = oneEmWidth;
 
-  var html = legend.generateLegendHTML(e.dygraph, undefined, undefined, oneEmWidth, null);
+  var html = Legend.generateLegendHTML(e.dygraph, undefined, undefined, oneEmWidth, null);
   this.legend_div_.innerHTML = html;
 };
 
-legend.prototype.didDrawChart = function(e) {
+Legend.prototype.didDrawChart = function(e) {
   this.deselect(e);
 };
 
@@ -187,29 +157,27 @@ legend.prototype.didDrawChart = function(e) {
  * - its top edge is flush with the top edge of the charting area
  * @private
  */
-legend.prototype.predraw = function(e) {
+Legend.prototype.predraw = function(e) {
   // Don't touch a user-specified labelsDiv.
   if (!this.is_generated_div_) return;
 
   // TODO(danvk): only use real APIs for this.
   e.dygraph.graphDiv.appendChild(this.legend_div_);
-  var area = e.dygraph.plotter_.area;
-  var labelsDivWidth = e.dygraph.getOption("labelsDivWidth");
+  var area = e.dygraph.getArea();
+  var labelsDivWidth = this.legend_div_.offsetWidth;
   this.legend_div_.style.left = area.x + area.w - labelsDivWidth - 1 + "px";
   this.legend_div_.style.top = area.y + "px";
-  this.legend_div_.style.width = labelsDivWidth + "px";
 };
 
 /**
  * Called when dygraph.destroy() is called.
  * You should null out any references and detach any DOM elements.
  */
-legend.prototype.destroy = function() {
+Legend.prototype.destroy = function() {
   this.legend_div_ = null;
 };
 
 /**
- * @private
  * Generates HTML for the legend which is displayed when hovering over the
  * chart. If no selected points are specified, a default legend is returned
  * (this may just be the empty string).
@@ -220,70 +188,110 @@ legend.prototype.destroy = function() {
  *   relevant when displaying a legend with no selection (i.e. {legend:
  *   'always'}) and with dashed lines.
  * @param {number} row The selected row index.
+ * @private
  */
-legend.generateLegendHTML = function(g, x, sel_points, oneEmWidth, row) {
+Legend.generateLegendHTML = function(g, x, sel_points, oneEmWidth, row) {
+  // Data about the selection to pass to legendFormatter
+  var data = {
+    dygraph: g,
+    x: x,
+    series: []
+  };
+
+  var labelToSeries = {};
+  var labels = g.getLabels();
+  if (labels) {
+    for (var i = 1; i < labels.length; i++) {
+      var series = g.getPropertiesForSeries(labels[i]);
+      var strokePattern = g.getOption('strokePattern', labels[i]);
+      var seriesData = {
+        dashHTML: generateLegendDashHTML(strokePattern, series.color, oneEmWidth),
+        label: labels[i],
+        labelHTML: escapeHTML(labels[i]),
+        isVisible: series.visible,
+        color: series.color
+      };
+
+      data.series.push(seriesData);
+      labelToSeries[labels[i]] = seriesData;
+    }
+  }
+
+  if (typeof(x) !== 'undefined') {
+    var xOptView = g.optionsViewForAxis_('x');
+    var xvf = xOptView('valueFormatter');
+    data.xHTML = xvf.call(g, x, xOptView, labels[0], g, row, 0);
+
+    var yOptViews = [];
+    var num_axes = g.numAxes();
+    for (var i = 0; i < num_axes; i++) {
+      // TODO(danvk): remove this use of a private API
+      yOptViews[i] = g.optionsViewForAxis_('y' + (i ? 1 + i : ''));
+    }
+
+    var showZeros = g.getOption('labelsShowZeroValues');
+    var highlightSeries = g.getHighlightSeries();
+    for (i = 0; i < sel_points.length; i++) {
+      var pt = sel_points[i];
+      var seriesData = labelToSeries[pt.name];
+      seriesData.y = pt.yval;
+
+      if ((pt.yval === 0 && !showZeros) || isNaN(pt.canvasy)) {
+        seriesData.isVisible = false;
+        continue;
+      }
+
+      var series = g.getPropertiesForSeries(pt.name);
+      var yOptView = yOptViews[series.axis - 1];
+      var fmtFunc = yOptView('valueFormatter');
+      var yHTML = fmtFunc.call(g, pt.yval, yOptView, pt.name, g, row, labels.indexOf(pt.name));
+
+      utils.update(seriesData, {yHTML});
+
+      if (pt.name == highlightSeries) {
+        seriesData.isHighlighted = true;
+      }
+    }
+  }
+
+  var formatter = (g.getOption('legendFormatter') || Legend.defaultFormatter);
+  return formatter.call(g, data);
+}
+
+Legend.defaultFormatter = function(data) {
+  var g = data.dygraph;
+
   // TODO(danvk): deprecate this option in place of {legend: 'never'}
+  // XXX should this logic be in the formatter?
   if (g.getOption('showLabelsOnHighlight') !== true) return '';
 
-  // If no points are selected, we display a default legend. Traditionally,
-  // this has been blank. But a better default would be a conventional legend,
-  // which provides essential information for a non-interactive chart.
-  var html, sepLines, i, dash, strokePattern;
-  var labels = g.getLabels();
+  var sepLines = g.getOption('labelsSeparateLines');
+  var html;
 
-  if (typeof(x) === 'undefined') {
+  if (typeof(data.x) === 'undefined') {
+    // TODO: this check is duplicated in generateLegendHTML. Put it in one place.
     if (g.getOption('legend') != 'always') {
       return '';
     }
 
-    sepLines = g.getOption('labelsSeparateLines');
     html = '';
-    for (i = 1; i < labels.length; i++) {
-      var series = g.getPropertiesForSeries(labels[i]);
-      if (!series.visible) continue;
+    for (var i = 0; i < data.series.length; i++) {
+      var series = data.series[i];
+      if (!series.isVisible) continue;
 
       if (html !== '') html += (sepLines ? '<br/>' : ' ');
-      strokePattern = g.getOption("strokePattern", labels[i]);
-      dash = generateLegendDashHTML(strokePattern, series.color, oneEmWidth);
-      html += "<span style='font-weight: bold; color: " + series.color + ";'>" +
-          dash + " " + escapeHTML(labels[i]) + "</span>";
+      html += `<span style='font-weight: bold; color: ${series.color};'>${series.dashHTML} ${series.labelHTML}</span>`;
     }
     return html;
   }
 
-  // TODO(danvk): remove this use of a private API
-  var xOptView = g.optionsViewForAxis_('x');
-  var xvf = xOptView('valueFormatter');
-  html = xvf.call(g, x, xOptView, labels[0], g, row, 0);
-  if (html !== '') {
-    html += ':';
-  }
-
-  var yOptViews = [];
-  var num_axes = g.numAxes();
-  for (i = 0; i < num_axes; i++) {
-    // TODO(danvk): remove this use of a private API
-    yOptViews[i] = g.optionsViewForAxis_('y' + (i ? 1 + i : ''));
-  }
-  var showZeros = g.getOption("labelsShowZeroValues");
-  sepLines = g.getOption("labelsSeparateLines");
-  var highlightSeries = g.getHighlightSeries();
-  for (i = 0; i < sel_points.length; i++) {
-    var pt = sel_points[i];
-    if (pt.yval === 0 && !showZeros) continue;
-    if (!Dygraph.isOK(pt.canvasy)) continue;
-    if (sepLines) html += "<br/>";
-
-    var series = g.getPropertiesForSeries(pt.name);
-    var yOptView = yOptViews[series.axis - 1];
-    var fmtFunc = yOptView('valueFormatter');
-    var yval = fmtFunc.call(g, pt.yval, yOptView, pt.name, g, row, labels.indexOf(pt.name));
-
-    var cls = (pt.name == highlightSeries) ? " class='highlight'" : "";
-
-    // TODO(danvk): use a template string here and make it an attribute.
-    html += "<span" + cls + ">" + " <b><span style='color: " + series.color + ";'>" +
-        escapeHTML(pt.name) + "</span></b>:&#160;" + yval + "</span>";
+  html = data.xHTML + ':';
+  for (var i = 0; i < data.series.length; i++) {
+    var series = data.series[i];
+    if (!series.isVisible) continue;
+    if (sepLines) html += '<br>';
+    var cls = series.isHighlighted ? ' class="highlight"' : '';
+    html += `<span${cls}> <b><span style='color: ${series.color};'>${series.labelHTML}</span></b>:&#160;${series.yHTML}</span>`;
   }
   return html;
 };
@@ -300,12 +308,11 @@ legend.generateLegendHTML = function(g, x, sel_points, oneEmWidth, row) {
  * @param oneEmWidth The width in pixels of 1em in the legend.
  * @private
  */
-generateLegendDashHTML = function(strokePattern, color, oneEmWidth) {
+// TODO(danvk): cache the results of this
+function generateLegendDashHTML(strokePattern, color, oneEmWidth) {
   // Easy, common case: a solid line
   if (!strokePattern || strokePattern.length <= 1) {
-    return "<div style=\"display: inline-block; position: relative; " +
-    "bottom: .5ex; padding-left: 1em; height: 1px; " +
-    "border-bottom: 2px solid " + color + ";\"></div>";
+    return `<div class="dygraph-legend-line" style="border-bottom-color: ${color};"></div>`;
   }
 
   var i, j, paddingLeft, marginRight;
@@ -352,15 +359,10 @@ generateLegendDashHTML = function(strokePattern, color, oneEmWidth) {
         // The repeated first segment has no right margin.
         marginRight = 0;
       }
-      dash += "<div style=\"display: inline-block; position: relative; " +
-        "bottom: .5ex; margin-right: " + marginRight + "em; padding-left: " +
-        paddingLeft + "em; height: 1px; border-bottom: 2px solid " + color +
-        ";\"></div>";
+      dash += `<div class="dygraph-legend-dash" style="margin-right: ${marginRight}em; padding-left: ${paddingLeft}em;"></div>`;
     }
   }
   return dash;
 };
 
-
-return legend;
-})();
+export default Legend;