1 |
|
2 |
|
3 |
|
4 |
|
5 | (function($) {
|
6 | var defaultOptions = {
|
7 | legend: {
|
8 | show: false,
|
9 | noColumns: 1,
|
10 | labelFormatter: null,
|
11 | container: null,
|
12 | position: 'ne',
|
13 | margin: 5,
|
14 | sorted: null
|
15 | }
|
16 | };
|
17 |
|
18 | function insertLegend(plot, options, placeholder, legendEntries) {
|
19 |
|
20 | if (options.legend.container != null) {
|
21 | $(options.legend.container).html('');
|
22 | } else {
|
23 | placeholder.find('.legend').remove();
|
24 | }
|
25 |
|
26 | if (!options.legend.show) {
|
27 | return;
|
28 | }
|
29 |
|
30 |
|
31 | var entries = options.legend.legendEntries = legendEntries,
|
32 | plotOffset = options.legend.plotOffset = plot.getPlotOffset(),
|
33 | html = [],
|
34 | entry, labelHtml, iconHtml,
|
35 | j = 0,
|
36 | i,
|
37 | pos = "",
|
38 | p = options.legend.position,
|
39 | m = options.legend.margin,
|
40 | shape = {
|
41 | name: '',
|
42 | label: '',
|
43 | xPos: '',
|
44 | yPos: ''
|
45 | };
|
46 |
|
47 | html[j++] = '<svg class="legendLayer" style="width:inherit;height:inherit;">';
|
48 | html[j++] = '<rect class="background" width="100%" height="100%"/>';
|
49 | html[j++] = svgShapeDefs;
|
50 |
|
51 | var left = 0;
|
52 | var columnWidths = [];
|
53 | var style = window.getComputedStyle(document.querySelector('body'));
|
54 | for (i = 0; i < entries.length; ++i) {
|
55 | var columnIndex = i % options.legend.noColumns;
|
56 | entry = entries[i];
|
57 | shape.label = entry.label;
|
58 | var info = plot.getSurface().getTextInfo('', shape.label, {
|
59 | style: style.fontStyle,
|
60 | variant: style.fontVariant,
|
61 | weight: style.fontWeight,
|
62 | size: parseInt(style.fontSize),
|
63 | lineHeight: parseInt(style.lineHeight),
|
64 | family: style.fontFamily
|
65 | });
|
66 |
|
67 | labelWidth = info.width;
|
68 |
|
69 | var iconWidth = 48;
|
70 | if (columnWidths[columnIndex]) {
|
71 | if (labelWidth > columnWidths[columnIndex]) {
|
72 | columnWidths[columnIndex] = labelWidth + iconWidth;
|
73 | }
|
74 | } else {
|
75 | columnWidths[columnIndex] = labelWidth + iconWidth;
|
76 | }
|
77 | }
|
78 |
|
79 |
|
80 | for (i = 0; i < entries.length; ++i) {
|
81 | var columnIndex = i % options.legend.noColumns;
|
82 | entry = entries[i];
|
83 | iconHtml = '';
|
84 | shape.label = entry.label;
|
85 | shape.xPos = (left + 3) + 'px';
|
86 | left += columnWidths[columnIndex];
|
87 | if ((i + 1) % options.legend.noColumns === 0) {
|
88 | left = 0;
|
89 | }
|
90 | shape.yPos = Math.floor(i / options.legend.noColumns) * 1.5 + 'em';
|
91 |
|
92 | if (entry.options.lines.show && entry.options.lines.fill) {
|
93 | shape.name = 'area';
|
94 | shape.fillColor = entry.color;
|
95 | iconHtml += getEntryIconHtml(shape);
|
96 | }
|
97 |
|
98 | if (entry.options.bars.show) {
|
99 | shape.name = 'bar';
|
100 | shape.fillColor = entry.color;
|
101 | iconHtml += getEntryIconHtml(shape);
|
102 | }
|
103 |
|
104 | if (entry.options.lines.show && !entry.options.lines.fill) {
|
105 | shape.name = 'line';
|
106 | shape.strokeColor = entry.color;
|
107 | shape.strokeWidth = entry.options.lines.lineWidth;
|
108 | iconHtml += getEntryIconHtml(shape);
|
109 | }
|
110 |
|
111 | if (entry.options.points.show) {
|
112 | shape.name = entry.options.points.symbol;
|
113 | shape.strokeColor = entry.color;
|
114 | shape.fillColor = entry.options.points.fillColor;
|
115 | shape.strokeWidth = entry.options.points.lineWidth;
|
116 | iconHtml += getEntryIconHtml(shape);
|
117 | }
|
118 |
|
119 | labelHtml = '<text x="' + shape.xPos + '" y="' + shape.yPos + '" text-anchor="start"><tspan dx="2em" dy="1.2em">' + shape.label + '</tspan></text>'
|
120 | html[j++] = '<g>' + iconHtml + labelHtml + '</g>';
|
121 | }
|
122 |
|
123 | html[j++] = '</svg>';
|
124 | if (m[0] == null) {
|
125 | m = [m, m];
|
126 | }
|
127 |
|
128 | if (p.charAt(0) === 'n') {
|
129 | pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
|
130 | } else if (p.charAt(0) === 's') {
|
131 | pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
|
132 | }
|
133 |
|
134 | if (p.charAt(1) === 'e') {
|
135 | pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
|
136 | } else if (p.charAt(1) === 'w') {
|
137 | pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
|
138 | }
|
139 |
|
140 | var width = 6;
|
141 | for (i = 0; i < columnWidths.length; ++i) {
|
142 | width += columnWidths[i];
|
143 | }
|
144 |
|
145 | var legendEl,
|
146 | height = Math.ceil(entries.length / options.legend.noColumns) * 1.6;
|
147 | if (!options.legend.container) {
|
148 | legendEl = $('<div class="legend" style="position:absolute;' + pos + '">' + html.join('') + '</div>').appendTo(placeholder);
|
149 | legendEl.css('width', width + 'px');
|
150 | legendEl.css('height', height + 'em');
|
151 | legendEl.css('pointerEvents', 'none');
|
152 | } else {
|
153 | legendEl = $(html.join('')).appendTo(options.legend.container)[0];
|
154 | options.legend.container.style.width = width + 'px';
|
155 | options.legend.container.style.height = height + 'em';
|
156 | }
|
157 | }
|
158 |
|
159 |
|
160 | function getEntryIconHtml(shape) {
|
161 | var html = '',
|
162 | name = shape.name,
|
163 | x = shape.xPos,
|
164 | y = shape.yPos,
|
165 | fill = shape.fillColor,
|
166 | stroke = shape.strokeColor,
|
167 | width = shape.strokeWidth;
|
168 | switch (name) {
|
169 | case 'circle':
|
170 | html = '<use xlink:href="#circle" class="legendIcon" ' +
|
171 | 'x="' + x + '" ' +
|
172 | 'y="' + y + '" ' +
|
173 | 'fill="' + fill + '" ' +
|
174 | 'stroke="' + stroke + '" ' +
|
175 | 'stroke-width="' + width + '" ' +
|
176 | 'width="1.5em" height="1.5em"' +
|
177 | '/>';
|
178 | break;
|
179 | case 'diamond':
|
180 | html = '<use xlink:href="#diamond" class="legendIcon" ' +
|
181 | 'x="' + x + '" ' +
|
182 | 'y="' + y + '" ' +
|
183 | 'fill="' + fill + '" ' +
|
184 | 'stroke="' + stroke + '" ' +
|
185 | 'stroke-width="' + width + '" ' +
|
186 | 'width="1.5em" height="1.5em"' +
|
187 | '/>';
|
188 | break;
|
189 | case 'cross':
|
190 | html = '<use xlink:href="#cross" class="legendIcon" ' +
|
191 | 'x="' + x + '" ' +
|
192 | 'y="' + y + '" ' +
|
193 |
|
194 | 'stroke="' + stroke + '" ' +
|
195 | 'stroke-width="' + width + '" ' +
|
196 | 'width="1.5em" height="1.5em"' +
|
197 | '/>';
|
198 | break;
|
199 | case 'rectangle':
|
200 | html = '<use xlink:href="#rectangle" class="legendIcon" ' +
|
201 | 'x="' + x + '" ' +
|
202 | 'y="' + y + '" ' +
|
203 | 'fill="' + fill + '" ' +
|
204 | 'stroke="' + stroke + '" ' +
|
205 | 'stroke-width="' + width + '" ' +
|
206 | 'width="1.5em" height="1.5em"' +
|
207 | '/>';
|
208 | break;
|
209 | case 'plus':
|
210 | html = '<use xlink:href="#plus" class="legendIcon" ' +
|
211 | 'x="' + x + '" ' +
|
212 | 'y="' + y + '" ' +
|
213 |
|
214 | 'stroke="' + stroke + '" ' +
|
215 | 'stroke-width="' + width + '" ' +
|
216 | 'width="1.5em" height="1.5em"' +
|
217 | '/>';
|
218 | break;
|
219 | case 'bar':
|
220 | html = '<use xlink:href="#bars" class="legendIcon" ' +
|
221 | 'x="' + x + '" ' +
|
222 | 'y="' + y + '" ' +
|
223 | 'fill="' + fill + '" ' +
|
224 |
|
225 |
|
226 | 'width="1.5em" height="1.5em"' +
|
227 | '/>';
|
228 | break;
|
229 | case 'area':
|
230 | html = '<use xlink:href="#area" class="legendIcon" ' +
|
231 | 'x="' + x + '" ' +
|
232 | 'y="' + y + '" ' +
|
233 | 'fill="' + fill + '" ' +
|
234 |
|
235 |
|
236 | 'width="1.5em" height="1.5em"' +
|
237 | '/>';
|
238 | break;
|
239 | case 'line':
|
240 | html = '<use xlink:href="#line" class="legendIcon" ' +
|
241 | 'x="' + x + '" ' +
|
242 | 'y="' + y + '" ' +
|
243 |
|
244 | 'stroke="' + stroke + '" ' +
|
245 | 'stroke-width="' + width + '" ' +
|
246 | 'width="1.5em" height="1.5em"' +
|
247 | '/>';
|
248 | break;
|
249 | default:
|
250 |
|
251 | html = '<use xlink:href="#circle" class="legendIcon" ' +
|
252 | 'x="' + x + '" ' +
|
253 | 'y="' + y + '" ' +
|
254 | 'fill="' + fill + '" ' +
|
255 | 'stroke="' + stroke + '" ' +
|
256 | 'stroke-width="' + width + '" ' +
|
257 | 'width="1.5em" height="1.5em"' +
|
258 | '/>';
|
259 | }
|
260 |
|
261 | return html;
|
262 | }
|
263 |
|
264 |
|
265 | var svgShapeDefs = '' +
|
266 | '<defs>' +
|
267 | '<symbol id="line" fill="none" viewBox="-5 -5 25 25">' +
|
268 | '<polyline points="0,15 5,5 10,10 15,0"/>' +
|
269 | '</symbol>' +
|
270 |
|
271 | '<symbol id="area" stroke-width="1" viewBox="-5 -5 25 25">' +
|
272 | '<polyline points="0,15 5,5 10,10 15,0, 15,15, 0,15"/>' +
|
273 | '</symbol>' +
|
274 |
|
275 | '<symbol id="bars" stroke-width="1" viewBox="-5 -5 25 25">' +
|
276 | '<polyline points="1.5,15.5 1.5,12.5, 4.5,12.5 4.5,15.5 6.5,15.5 6.5,3.5, 9.5,3.5 9.5,15.5 11.5,15.5 11.5,7.5 14.5,7.5 14.5,15.5 1.5,15.5"/>' +
|
277 | '</symbol>' +
|
278 |
|
279 | '<symbol id="circle" viewBox="-5 -5 25 25">' +
|
280 | '<circle cx="0" cy="15" r="2.5"/>' +
|
281 | '<circle cx="5" cy="5" r="2.5"/>' +
|
282 | '<circle cx="10" cy="10" r="2.5"/>' +
|
283 | '<circle cx="15" cy="0" r="2.5"/>' +
|
284 | '</symbol>' +
|
285 |
|
286 | '<symbol id="rectangle" viewBox="-5 -5 25 25">' +
|
287 | '<rect x="-2.1" y="12.9" width="4.2" height="4.2"/>' +
|
288 | '<rect x="2.9" y="2.9" width="4.2" height="4.2"/>' +
|
289 | '<rect x="7.9" y="7.9" width="4.2" height="4.2"/>' +
|
290 | '<rect x="12.9" y="-2.1" width="4.2" height="4.2"/>' +
|
291 | '</symbol>' +
|
292 |
|
293 | '<symbol id="diamond" viewBox="-5 -5 25 25">' +
|
294 | '<path d="M-3,15 L0,12 L3,15, L0,18 Z"/>' +
|
295 | '<path d="M2,5 L5,2 L8,5, L5,8 Z"/>' +
|
296 | '<path d="M7,10 L10,7 L13,10, L10,13 Z"/>' +
|
297 | '<path d="M12,0 L15,-3 L18,0, L15,3 Z"/>' +
|
298 | '</symbol>' +
|
299 |
|
300 | '<symbol id="cross" fill="none" viewBox="-5 -5 25 25">' +
|
301 | '<path d="M-2.1,12.9 L2.1,17.1, M2.1,12.9 L-2.1,17.1 Z"/>' +
|
302 | '<path d="M2.9,2.9 L7.1,7.1 M7.1,2.9 L2.9,7.1 Z"/>' +
|
303 | '<path d="M7.9,7.9 L12.1,12.1 M12.1,7.9 L7.9,12.1 Z"/>' +
|
304 | '<path d="M12.9,-2.1 L17.1,2.1 M17.1,-2.1 L12.9,2.1 Z"/>' +
|
305 | '</symbol>' +
|
306 |
|
307 | '<symbol id="plus" fill="none" viewBox="-5 -5 25 25">' +
|
308 | '<path d="M0,12 L0,18, M-3,15 L3,15 Z"/>' +
|
309 | '<path d="M5,2 L5,8 M2,5 L8,5 Z"/>' +
|
310 | '<path d="M10,7 L10,13 M7,10 L13,10 Z"/>' +
|
311 | '<path d="M15,-3 L15,3 M12,0 L18,0 Z"/>' +
|
312 | '</symbol>' +
|
313 | '</defs>';
|
314 |
|
315 |
|
316 | function getLegendEntries(series, labelFormatter, sorted) {
|
317 | var lf = labelFormatter,
|
318 | legendEntries = series.reduce(function(validEntries, s, i) {
|
319 | var labelEval = (lf ? lf(s.label, s) : s.label)
|
320 | if (s.hasOwnProperty("label") ? labelEval : true) {
|
321 | var entry = {
|
322 | label: labelEval || 'Plot ' + (i + 1),
|
323 | color: s.color,
|
324 | options: {
|
325 | lines: s.lines,
|
326 | points: s.points,
|
327 | bars: s.bars
|
328 | }
|
329 | }
|
330 | validEntries.push(entry)
|
331 | }
|
332 | return validEntries;
|
333 | }, []);
|
334 |
|
335 |
|
336 | if (sorted) {
|
337 | if ($.isFunction(sorted)) {
|
338 | legendEntries.sort(sorted);
|
339 | } else if (sorted === 'reverse') {
|
340 | legendEntries.reverse();
|
341 | } else {
|
342 | var ascending = (sorted !== 'descending');
|
343 | legendEntries.sort(function(a, b) {
|
344 | return a.label === b.label
|
345 | ? 0
|
346 | : ((a.label < b.label) !== ascending ? 1 : -1
|
347 | );
|
348 | });
|
349 | }
|
350 | }
|
351 |
|
352 | return legendEntries;
|
353 | }
|
354 |
|
355 |
|
356 | function checkOptions(opts1, opts2) {
|
357 | for (var prop in opts1) {
|
358 | if (opts1.hasOwnProperty(prop)) {
|
359 | if (opts1[prop] !== opts2[prop]) {
|
360 | return true;
|
361 | }
|
362 | }
|
363 | }
|
364 | return false;
|
365 | }
|
366 |
|
367 |
|
368 | function shouldRedraw(oldEntries, newEntries) {
|
369 | if (!oldEntries || !newEntries) {
|
370 | return true;
|
371 | }
|
372 |
|
373 | if (oldEntries.length !== newEntries.length) {
|
374 | return true;
|
375 | }
|
376 | var i, newEntry, oldEntry, newOpts, oldOpts;
|
377 | for (i = 0; i < newEntries.length; i++) {
|
378 | newEntry = newEntries[i];
|
379 | oldEntry = oldEntries[i];
|
380 |
|
381 | if (newEntry.label !== oldEntry.label) {
|
382 | return true;
|
383 | }
|
384 |
|
385 | if (newEntry.color !== oldEntry.color) {
|
386 | return true;
|
387 | }
|
388 |
|
389 |
|
390 | newOpts = newEntry.options.lines;
|
391 | oldOpts = oldEntry.options.lines;
|
392 | if (checkOptions(newOpts, oldOpts)) {
|
393 | return true;
|
394 | }
|
395 |
|
396 |
|
397 | newOpts = newEntry.options.points;
|
398 | oldOpts = oldEntry.options.points;
|
399 | if (checkOptions(newOpts, oldOpts)) {
|
400 | return true;
|
401 | }
|
402 |
|
403 |
|
404 | newOpts = newEntry.options.bars;
|
405 | oldOpts = oldEntry.options.bars;
|
406 | if (checkOptions(newOpts, oldOpts)) {
|
407 | return true;
|
408 | }
|
409 | }
|
410 |
|
411 | return false;
|
412 | }
|
413 |
|
414 | function init(plot) {
|
415 | plot.hooks.setupGrid.push(function (plot) {
|
416 | var options = plot.getOptions();
|
417 | var series = plot.getData(),
|
418 | labelFormatter = options.legend.labelFormatter,
|
419 | oldEntries = options.legend.legendEntries,
|
420 | oldPlotOffset = options.legend.plotOffset,
|
421 | newEntries = getLegendEntries(series, labelFormatter, options.legend.sorted),
|
422 | newPlotOffset = plot.getPlotOffset();
|
423 |
|
424 | if (shouldRedraw(oldEntries, newEntries) ||
|
425 | checkOptions(oldPlotOffset, newPlotOffset)) {
|
426 | insertLegend(plot, options, plot.getPlaceholder(), newEntries);
|
427 | }
|
428 | });
|
429 | }
|
430 |
|
431 | $.plot.plugins.push({
|
432 | init: init,
|
433 | options: defaultOptions,
|
434 | name: 'legend',
|
435 | version: '1.0'
|
436 | });
|
437 | })(jQuery);
|