X-Git-Url: https://adrianiainlam.tk/git/?a=blobdiff_plain;f=src%2Fplugins%2Flegend.js;h=8b918412089c1b3dd0b94cdc3c75495c74076a61;hb=cf61aeb77cda5e2638b1f00588dcd90394afd24c;hp=95a161c6aad593cd1b174a66112f26018915bdaa;hpb=6ecc073934b76e5076f917112a24ff7094857730;p=dygraphs.git diff --git a/src/plugins/legend.js b/src/plugins/legend.js index 95a161c..8b91841 100644 --- a/src/plugins/legend.js +++ b/src/plugins/legend.js @@ -26,18 +26,15 @@ import * as utils from '../dygraph-utils'; * * @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. @@ -50,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) { @@ -62,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; @@ -122,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; @@ -136,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 @@ -156,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"; @@ -171,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); }; @@ -188,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). @@ -221,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 (!utils.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; }; @@ -301,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; @@ -353,13 +359,10 @@ generateLegendDashHTML = function(strokePattern, color, oneEmWidth) { // The repeated first segment has no right margin. marginRight = 0; } - dash += "
"; + dash += `
`; } } return dash; }; -export default legend; +export default Legend;