X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=src%2Fplugins%2Flegend.js;h=40a486344c5124bf0e303d29fc86bfdef6c66bf4;hb=623dd1d6e1562941750eb9f00ac28f2481b07f95;hp=3db4d0731698d774cba37bd3ba84429c63c0db4a;hpb=07cae5dd0adbcee97689ffa6099a6b0e8665011b;p=dygraphs.git diff --git a/src/plugins/legend.js b/src/plugins/legend.js index 3db4d07..40a4863 100644 --- a/src/plugins/legend.js +++ b/src/plugins/legend.js @@ -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.} 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, "&").replace(/"/g, """).replace(//g, ">"); }; -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 ? '
' : ' '); - strokePattern = g.getOption("strokePattern", labels[i]); - dash = generateLegendDashHTML(strokePattern, series.color, oneEmWidth); - html += "" + - dash + " " + escapeHTML(labels[i]) + ""; + html += `${series.dashHTML} ${series.labelHTML}`; } 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 += "
"; - - 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 += "" + " " + - escapeHTML(pt.name) + ": " + yval + ""; + html = data.xHTML + ':'; + for (var i = 0; i < data.series.length; i++) { + var series = data.series[i]; + if (!series.isVisible) continue; + if (sepLines) html += '
'; + var cls = series.isHighlighted ? ' class="highlight"' : ''; + html += ` ${series.labelHTML}: ${series.yHTML}`; } 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 "
"; + return `
`; } 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 += "
"; + dash += `
`; } } return dash; }; - -return legend; -})(); +export default Legend;