/* Flot plugin for drawing legends. */ (function($) { var defaultOptions = { legend: { show: false, noColumns: 1, labelFormatter: null, // fn: string -> string container: null, // container (as jQuery object) to put legend in, null means default on top of graph position: 'ne', // position of default legend container within plot margin: 5, // distance from grid edge to default legend container within plot sorted: null // default to no legend sorting } }; function insertLegend(plot, options, placeholder, legendEntries) { // clear before redraw if (options.legend.container != null) { $(options.legend.container).html(''); } else { placeholder.find('.legend').remove(); } if (!options.legend.show) { return; } // Save the legend entries in legend options var entries = options.legend.legendEntries = legendEntries, plotOffset = options.legend.plotOffset = plot.getPlotOffset(), html = [], entry, labelHtml, iconHtml, j = 0, i, pos = "", p = options.legend.position, m = options.legend.margin, shape = { name: '', label: '', xPos: '', yPos: '' }; html[j++] = ''; html[j++] = ''; html[j++] = svgShapeDefs; var left = 0; var columnWidths = []; var style = window.getComputedStyle(document.querySelector('body')); for (i = 0; i < entries.length; ++i) { let columnIndex = i % options.legend.noColumns; entry = entries[i]; shape.label = entry.label; var info = plot.getSurface().getTextInfo('', shape.label, { style: style.fontStyle, variant: style.fontVariant, weight: style.fontWeight, size: parseInt(style.fontSize), lineHeight: parseInt(style.lineHeight), family: style.fontFamily }); var labelWidth = info.width; // 36px = 1.5em + 6px margin var iconWidth = 48; if (columnWidths[columnIndex]) { if (labelWidth > columnWidths[columnIndex]) { columnWidths[columnIndex] = labelWidth + iconWidth; } } else { columnWidths[columnIndex] = labelWidth + iconWidth; } } // Generate html for icons and labels from a list of entries for (i = 0; i < entries.length; ++i) { let columnIndex = i % options.legend.noColumns; entry = entries[i]; iconHtml = ''; shape.label = entry.label; shape.xPos = (left + 3) + 'px'; left += columnWidths[columnIndex]; if ((i + 1) % options.legend.noColumns === 0) { left = 0; } shape.yPos = Math.floor(i / options.legend.noColumns) * 1.5 + 'em'; // area if (entry.options.lines.show && entry.options.lines.fill) { shape.name = 'area'; shape.fillColor = entry.color; iconHtml += getEntryIconHtml(shape); } // bars if (entry.options.bars.show) { shape.name = 'bar'; shape.fillColor = entry.color; iconHtml += getEntryIconHtml(shape); } // lines if (entry.options.lines.show && !entry.options.lines.fill) { shape.name = 'line'; shape.strokeColor = entry.color; shape.strokeWidth = entry.options.lines.lineWidth; iconHtml += getEntryIconHtml(shape); } // points if (entry.options.points.show) { shape.name = entry.options.points.symbol; shape.strokeColor = entry.color; shape.fillColor = entry.options.points.fillColor; shape.strokeWidth = entry.options.points.lineWidth; iconHtml += getEntryIconHtml(shape); } labelHtml = '' + shape.label + '' html[j++] = '' + iconHtml + labelHtml + ''; } html[j++] = ''; if (m[0] == null) { m = [m, m]; } if (p.charAt(0) === 'n') { pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; } else if (p.charAt(0) === 's') { pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; } if (p.charAt(1) === 'e') { pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; } else if (p.charAt(1) === 'w') { pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; } var width = 6; for (i = 0; i < columnWidths.length; ++i) { width += columnWidths[i]; } var legendEl, height = Math.ceil(entries.length / options.legend.noColumns) * 1.6; if (!options.legend.container) { legendEl = $('
' + html.join('') + '
').appendTo(placeholder); legendEl.css('width', width + 'px'); legendEl.css('height', height + 'em'); legendEl.css('pointerEvents', 'none'); } else { legendEl = $(html.join('')).appendTo(options.legend.container)[0]; options.legend.container.style.width = width + 'px'; options.legend.container.style.height = height + 'em'; } } // Generate html for a shape function getEntryIconHtml(shape) { var html = '', name = shape.name, x = shape.xPos, y = shape.yPos, fill = shape.fillColor, stroke = shape.strokeColor, width = shape.strokeWidth; switch (name) { case 'circle': html = ''; break; case 'diamond': html = ''; break; case 'cross': html = ''; break; case 'rectangle': html = ''; break; case 'plus': html = ''; break; case 'bar': html = ''; break; case 'area': html = ''; break; case 'line': html = ''; break; default: // default is circle html = ''; } return html; } // Define svg symbols for shapes var svgShapeDefs = '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + ''; // Generate a list of legend entries in their final order function getLegendEntries(series, labelFormatter, sorted) { var lf = labelFormatter, legendEntries = series.reduce(function(validEntries, s, i) { var labelEval = (lf ? lf(s.label, s) : s.label) if (s.hasOwnProperty("label") ? labelEval : true) { var entry = { label: labelEval || 'Plot ' + (i + 1), color: s.color, options: { lines: s.lines, points: s.points, bars: s.bars } } validEntries.push(entry) } return validEntries; }, []); // Sort the legend using either the default or a custom comparator if (sorted) { if ($.isFunction(sorted)) { legendEntries.sort(sorted); } else if (sorted === 'reverse') { legendEntries.reverse(); } else { var ascending = (sorted !== 'descending'); legendEntries.sort(function(a, b) { return a.label === b.label ? 0 : ((a.label < b.label) !== ascending ? 1 : -1 // Logical XOR ); }); } } return legendEntries; } // return false if opts1 same as opts2 function checkOptions(opts1, opts2) { for (var prop in opts1) { if (opts1.hasOwnProperty(prop)) { if (opts1[prop] !== opts2[prop]) { return true; } } } return false; } // Compare two lists of legend entries function shouldRedraw(oldEntries, newEntries) { if (!oldEntries || !newEntries) { return true; } if (oldEntries.length !== newEntries.length) { return true; } var i, newEntry, oldEntry, newOpts, oldOpts; for (i = 0; i < newEntries.length; i++) { newEntry = newEntries[i]; oldEntry = oldEntries[i]; if (newEntry.label !== oldEntry.label) { return true; } if (newEntry.color !== oldEntry.color) { return true; } // check for changes in lines options newOpts = newEntry.options.lines; oldOpts = oldEntry.options.lines; if (checkOptions(newOpts, oldOpts)) { return true; } // check for changes in points options newOpts = newEntry.options.points; oldOpts = oldEntry.options.points; if (checkOptions(newOpts, oldOpts)) { return true; } // check for changes in bars options newOpts = newEntry.options.bars; oldOpts = oldEntry.options.bars; if (checkOptions(newOpts, oldOpts)) { return true; } } return false; } function init(plot) { plot.hooks.setupGrid.push(function (plot) { var options = plot.getOptions(); var series = plot.getData(), labelFormatter = options.legend.labelFormatter, oldEntries = options.legend.legendEntries, oldPlotOffset = options.legend.plotOffset, newEntries = getLegendEntries(series, labelFormatter, options.legend.sorted), newPlotOffset = plot.getPlotOffset(); if (shouldRedraw(oldEntries, newEntries) || checkOptions(oldPlotOffset, newPlotOffset)) { insertLegend(plot, options, plot.getPlaceholder(), newEntries); } }); } $.plot.plugins.push({ init: init, options: defaultOptions, name: 'legend', version: '1.0' }); })(jQuery);