UNPKG

24.4 kBJavaScriptView Raw
1/**
2## jquery.flot.drawSeries.js
3
4This plugin is used by flot for drawing lines, plots, bars or area.
5
6### Public methods
7*/
8
9(function($) {
10 "use strict";
11
12 function DrawSeries() {
13 function plotLine(datapoints, xoffset, yoffset, axisx, axisy, ctx, steps) {
14 var points = datapoints.points,
15 ps = datapoints.pointsize,
16 prevx = null,
17 prevy = null;
18 var x1 = 0.0,
19 y1 = 0.0,
20 x2 = 0.0,
21 y2 = 0.0,
22 mx = null,
23 my = null,
24 i = 0;
25
26 ctx.beginPath();
27 for (i = ps; i < points.length; i += ps) {
28 x1 = points[i - ps];
29 y1 = points[i - ps + 1];
30 x2 = points[i];
31 y2 = points[i + 1];
32
33 if (x1 === null || x2 === null) {
34 mx = null;
35 my = null;
36 continue;
37 }
38
39 if (isNaN(x1) || isNaN(x2) || isNaN(y1) || isNaN(y2)) {
40 prevx = null;
41 prevy = null;
42 continue;
43 }
44
45 if(steps){
46 if (mx !== null && my !== null) {
47 // if middle point exists, transfer p2 -> p1 and p1 -> mp
48 x2 = x1;
49 y2 = y1;
50 x1 = mx;
51 y1 = my;
52
53 // 'remove' middle point
54 mx = null;
55 my = null;
56
57 // subtract pointsize from i to have current point p1 handled again
58 i -= ps;
59 } else if (y1 !== y2 && x1 !== x2) {
60 // create a middle point
61 y2 = y1;
62 mx = x2;
63 my = y1;
64 }
65 }
66
67 // clip with ymin
68 if (y1 <= y2 && y1 < axisy.min) {
69 if (y2 < axisy.min) {
70 // line segment is outside
71 continue;
72 }
73 // compute new intersection point
74 x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
75 y1 = axisy.min;
76 } else if (y2 <= y1 && y2 < axisy.min) {
77 if (y1 < axisy.min) {
78 continue;
79 }
80
81 x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
82 y2 = axisy.min;
83 }
84
85 // clip with ymax
86 if (y1 >= y2 && y1 > axisy.max) {
87 if (y2 > axisy.max) {
88 continue;
89 }
90
91 x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
92 y1 = axisy.max;
93 } else if (y2 >= y1 && y2 > axisy.max) {
94 if (y1 > axisy.max) {
95 continue;
96 }
97
98 x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
99 y2 = axisy.max;
100 }
101
102 // clip with xmin
103 if (x1 <= x2 && x1 < axisx.min) {
104 if (x2 < axisx.min) {
105 continue;
106 }
107
108 y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
109 x1 = axisx.min;
110 } else if (x2 <= x1 && x2 < axisx.min) {
111 if (x1 < axisx.min) {
112 continue;
113 }
114
115 y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
116 x2 = axisx.min;
117 }
118
119 // clip with xmax
120 if (x1 >= x2 && x1 > axisx.max) {
121 if (x2 > axisx.max) {
122 continue;
123 }
124
125 y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
126 x1 = axisx.max;
127 } else if (x2 >= x1 && x2 > axisx.max) {
128 if (x1 > axisx.max) {
129 continue;
130 }
131
132 y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
133 x2 = axisx.max;
134 }
135
136 if (x1 !== prevx || y1 !== prevy) {
137 ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
138 }
139
140 prevx = x2;
141 prevy = y2;
142 ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
143 }
144 ctx.stroke();
145 }
146
147 function plotLineArea(datapoints, axisx, axisy, fillTowards, ctx, steps) {
148 var points = datapoints.points,
149 ps = datapoints.pointsize,
150 bottom = fillTowards > axisy.min ? Math.min(axisy.max, fillTowards) : axisy.min,
151 i = 0,
152 ypos = 1,
153 areaOpen = false,
154 segmentStart = 0,
155 segmentEnd = 0,
156 mx = null,
157 my = null;
158
159 // we process each segment in two turns, first forward
160 // direction to sketch out top, then once we hit the
161 // end we go backwards to sketch the bottom
162 while (true) {
163 if (ps > 0 && i > points.length + ps) {
164 break;
165 }
166
167 i += ps; // ps is negative if going backwards
168
169 var x1 = points[i - ps],
170 y1 = points[i - ps + ypos],
171 x2 = points[i],
172 y2 = points[i + ypos];
173
174 if (ps === -2) {
175 /* going backwards and no value for the bottom provided in the series*/
176 y1 = y2 = bottom;
177 }
178
179 if (areaOpen) {
180 if (ps > 0 && x1 != null && x2 == null) {
181 // at turning point
182 segmentEnd = i;
183 ps = -ps;
184 ypos = 2;
185 continue;
186 }
187
188 if (ps < 0 && i === segmentStart + ps) {
189 // done with the reverse sweep
190 ctx.fill();
191 areaOpen = false;
192 ps = -ps;
193 ypos = 1;
194 i = segmentStart = segmentEnd + ps;
195 continue;
196 }
197 }
198
199 if (x1 == null || x2 == null) {
200 mx = null;
201 my = null;
202 continue;
203 }
204
205 if(steps){
206 if (mx !== null && my !== null) {
207 // if middle point exists, transfer p2 -> p1 and p1 -> mp
208 x2 = x1;
209 y2 = y1;
210 x1 = mx;
211 y1 = my;
212
213 // 'remove' middle point
214 mx = null;
215 my = null;
216
217 // subtract pointsize from i to have current point p1 handled again
218 i -= ps;
219 } else if (y1 !== y2 && x1 !== x2) {
220 // create a middle point
221 y2 = y1;
222 mx = x2;
223 my = y1;
224 }
225 }
226
227 // clip x values
228
229 // clip with xmin
230 if (x1 <= x2 && x1 < axisx.min) {
231 if (x2 < axisx.min) {
232 continue;
233 }
234
235 y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
236 x1 = axisx.min;
237 } else if (x2 <= x1 && x2 < axisx.min) {
238 if (x1 < axisx.min) {
239 continue;
240 }
241
242 y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
243 x2 = axisx.min;
244 }
245
246 // clip with xmax
247 if (x1 >= x2 && x1 > axisx.max) {
248 if (x2 > axisx.max) {
249 continue;
250 }
251
252 y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
253 x1 = axisx.max;
254 } else if (x2 >= x1 && x2 > axisx.max) {
255 if (x1 > axisx.max) {
256 continue;
257 }
258
259 y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
260 x2 = axisx.max;
261 }
262
263 if (!areaOpen) {
264 // open area
265 ctx.beginPath();
266 ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
267 areaOpen = true;
268 }
269
270 // now first check the case where both is outside
271 if (y1 >= axisy.max && y2 >= axisy.max) {
272 ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
273 ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
274 continue;
275 } else if (y1 <= axisy.min && y2 <= axisy.min) {
276 ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
277 ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
278 continue;
279 }
280
281 // else it's a bit more complicated, there might
282 // be a flat maxed out rectangle first, then a
283 // triangular cutout or reverse; to find these
284 // keep track of the current x values
285 var x1old = x1,
286 x2old = x2;
287
288 // clip the y values, without shortcutting, we
289 // go through all cases in turn
290
291 // clip with ymin
292 if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
293 x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
294 y1 = axisy.min;
295 } else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
296 x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
297 y2 = axisy.min;
298 }
299
300 // clip with ymax
301 if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
302 x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
303 y1 = axisy.max;
304 } else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
305 x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
306 y2 = axisy.max;
307 }
308
309 // if the x value was changed we got a rectangle
310 // to fill
311 if (x1 !== x1old) {
312 ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
313 // it goes to (x1, y1), but we fill that below
314 }
315
316 // fill triangular section, this sometimes result
317 // in redundant points if (x1, y1) hasn't changed
318 // from previous line to, but we just ignore that
319 ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
320 ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
321
322 // fill the other rectangle if it's there
323 if (x2 !== x2old) {
324 ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
325 ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
326 }
327 }
328 }
329
330 /**
331 - drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
332
333 This function is used for drawing lines or area fill. In case the series has line decimation function
334 attached, before starting to draw, as an optimization the points will first be decimated.
335
336 The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
337 plotHeight are the corresponding parameters of flot used to determine the drawing surface.
338 The function getColorOrGradient is used to compute the fill style of lines and area.
339 */
340 function drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
341 ctx.save();
342 ctx.translate(plotOffset.left, plotOffset.top);
343 ctx.lineJoin = "round";
344
345 if (series.lines.dashes && ctx.setLineDash) {
346 ctx.setLineDash(series.lines.dashes);
347 }
348
349 var datapoints = {
350 format: series.datapoints.format,
351 points: series.datapoints.points,
352 pointsize: series.datapoints.pointsize
353 };
354
355 if (series.decimate) {
356 datapoints.points = series.decimate(series, series.xaxis.min, series.xaxis.max, plotWidth, series.yaxis.min, series.yaxis.max, plotHeight);
357 }
358
359 var lw = series.lines.lineWidth;
360
361 ctx.lineWidth = lw;
362 ctx.strokeStyle = series.color;
363 var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight, getColorOrGradient);
364 if (fillStyle) {
365 ctx.fillStyle = fillStyle;
366 plotLineArea(datapoints, series.xaxis, series.yaxis, series.lines.fillTowards || 0, ctx, series.lines.steps);
367 }
368
369 if (lw > 0) {
370 plotLine(datapoints, 0, 0, series.xaxis, series.yaxis, ctx, series.lines.steps);
371 }
372
373 ctx.restore();
374 }
375
376 /**
377 - drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
378
379 This function is used for drawing points using a given symbol. In case the series has points decimation
380 function attached, before starting to draw, as an optimization the points will first be decimated.
381
382 The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
383 plotHeight are the corresponding parameters of flot used to determine the drawing surface.
384 The function drawSymbol is used to compute and draw the symbol chosen for the points.
385 */
386 function drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
387 function drawCircle(ctx, x, y, radius, shadow, fill) {
388 ctx.moveTo(x + radius, y);
389 ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
390 }
391 drawCircle.fill = true;
392 function plotPoints(datapoints, radius, fill, offset, shadow, axisx, axisy, drawSymbolFn) {
393 var points = datapoints.points,
394 ps = datapoints.pointsize;
395
396 ctx.beginPath();
397 for (var i = 0; i < points.length; i += ps) {
398 var x = points[i],
399 y = points[i + 1];
400 if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) {
401 continue;
402 }
403
404 x = axisx.p2c(x);
405 y = axisy.p2c(y) + offset;
406
407 drawSymbolFn(ctx, x, y, radius, shadow, fill);
408 }
409 if (drawSymbolFn.fill && !shadow) {
410 ctx.fill();
411 }
412 ctx.stroke();
413 }
414
415 ctx.save();
416 ctx.translate(plotOffset.left, plotOffset.top);
417
418 var datapoints = {
419 format: series.datapoints.format,
420 points: series.datapoints.points,
421 pointsize: series.datapoints.pointsize
422 };
423
424 if (series.decimatePoints) {
425 datapoints.points = series.decimatePoints(series, series.xaxis.min, series.xaxis.max, plotWidth, series.yaxis.min, series.yaxis.max, plotHeight);
426 }
427
428 var lw = series.points.lineWidth,
429 radius = series.points.radius,
430 symbol = series.points.symbol,
431 drawSymbolFn;
432
433 if (symbol === 'circle') {
434 drawSymbolFn = drawCircle;
435 } else if (typeof symbol === 'string' && drawSymbol && drawSymbol[symbol]) {
436 drawSymbolFn = drawSymbol[symbol];
437 } else if (typeof drawSymbol === 'function') {
438 drawSymbolFn = drawSymbol;
439 }
440
441 // If the user sets the line width to 0, we change it to a very
442 // small value. A line width of 0 seems to force the default of 1.
443
444 if (lw === 0) {
445 lw = 0.0001;
446 }
447
448 ctx.lineWidth = lw;
449 ctx.fillStyle = getFillStyle(series.points, series.color, null, null, getColorOrGradient);
450 ctx.strokeStyle = series.color;
451 plotPoints(datapoints, radius,
452 true, 0, false,
453 series.xaxis, series.yaxis, drawSymbolFn);
454 ctx.restore();
455 }
456
457 function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
458 var left = x + barLeft,
459 right = x + barRight,
460 bottom = b, top = y,
461 drawLeft, drawRight, drawTop, drawBottom = false,
462 tmp;
463
464 drawLeft = drawRight = drawTop = true;
465
466 // in horizontal mode, we start the bar from the left
467 // instead of from the bottom so it appears to be
468 // horizontal rather than vertical
469 if (horizontal) {
470 drawBottom = drawRight = drawTop = true;
471 drawLeft = false;
472 left = b;
473 right = x;
474 top = y + barLeft;
475 bottom = y + barRight;
476
477 // account for negative bars
478 if (right < left) {
479 tmp = right;
480 right = left;
481 left = tmp;
482 drawLeft = true;
483 drawRight = false;
484 }
485 }
486 else {
487 drawLeft = drawRight = drawTop = true;
488 drawBottom = false;
489 left = x + barLeft;
490 right = x + barRight;
491 bottom = b;
492 top = y;
493
494 // account for negative bars
495 if (top < bottom) {
496 tmp = top;
497 top = bottom;
498 bottom = tmp;
499 drawBottom = true;
500 drawTop = false;
501 }
502 }
503
504 // clip
505 if (right < axisx.min || left > axisx.max ||
506 top < axisy.min || bottom > axisy.max) {
507 return;
508 }
509
510 if (left < axisx.min) {
511 left = axisx.min;
512 drawLeft = false;
513 }
514
515 if (right > axisx.max) {
516 right = axisx.max;
517 drawRight = false;
518 }
519
520 if (bottom < axisy.min) {
521 bottom = axisy.min;
522 drawBottom = false;
523 }
524
525 if (top > axisy.max) {
526 top = axisy.max;
527 drawTop = false;
528 }
529
530 left = axisx.p2c(left);
531 bottom = axisy.p2c(bottom);
532 right = axisx.p2c(right);
533 top = axisy.p2c(top);
534
535 // fill the bar
536 if (fillStyleCallback) {
537 c.fillStyle = fillStyleCallback(bottom, top);
538 c.fillRect(left, top, right - left, bottom - top)
539 }
540
541 // draw outline
542 if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
543 c.beginPath();
544
545 // FIXME: inline moveTo is buggy with excanvas
546 c.moveTo(left, bottom);
547 if (drawLeft) {
548 c.lineTo(left, top);
549 } else {
550 c.moveTo(left, top);
551 }
552
553 if (drawTop) {
554 c.lineTo(right, top);
555 } else {
556 c.moveTo(right, top);
557 }
558
559 if (drawRight) {
560 c.lineTo(right, bottom);
561 } else {
562 c.moveTo(right, bottom);
563 }
564
565 if (drawBottom) {
566 c.lineTo(left, bottom);
567 } else {
568 c.moveTo(left, bottom);
569 }
570
571 c.stroke();
572 }
573 }
574
575 /**
576 - drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
577
578 This function is used for drawing series represented as bars. In case the series has decimation
579 function attached, before starting to draw, as an optimization the points will first be decimated.
580
581 The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
582 plotHeight are the corresponding parameters of flot used to determine the drawing surface.
583 The function getColorOrGradient is used to compute the fill style of bars.
584 */
585 function drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
586 function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
587 var points = datapoints.points,
588 ps = datapoints.pointsize,
589 fillTowards = series.bars.fillTowards || 0,
590 calculatedBottom = fillTowards > axisy.min ? Math.min(axisy.max, fillTowards) : axisy.min;
591
592 for (var i = 0; i < points.length; i += ps) {
593 if (points[i] == null) {
594 continue;
595 }
596
597 // Use third point as bottom if pointsize is 3
598 var bottom = ps === 3 ? points[i + 2] : calculatedBottom;
599 drawBar(points[i], points[i + 1], bottom, barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
600 }
601 }
602
603 ctx.save();
604 ctx.translate(plotOffset.left, plotOffset.top);
605
606 var datapoints = {
607 format: series.datapoints.format,
608 points: series.datapoints.points,
609 pointsize: series.datapoints.pointsize
610 };
611
612 if (series.decimate) {
613 datapoints.points = series.decimate(series, series.xaxis.min, series.xaxis.max, plotWidth);
614 }
615
616 ctx.lineWidth = series.bars.lineWidth;
617 ctx.strokeStyle = series.color;
618
619 var barLeft;
620 var barWidth = series.bars.barWidth[0] || series.bars.barWidth;
621 switch (series.bars.align) {
622 case "left":
623 barLeft = 0;
624 break;
625 case "right":
626 barLeft = -barWidth;
627 break;
628 default:
629 barLeft = -barWidth / 2;
630 }
631
632 var fillStyleCallback = series.bars.fill ? function(bottom, top) {
633 return getFillStyle(series.bars, series.color, bottom, top, getColorOrGradient);
634 } : null;
635
636 plotBars(datapoints, barLeft, barLeft + barWidth, fillStyleCallback, series.xaxis, series.yaxis);
637 ctx.restore();
638 }
639
640 function getFillStyle(filloptions, seriesColor, bottom, top, getColorOrGradient) {
641 var fill = filloptions.fill;
642 if (!fill) {
643 return null;
644 }
645
646 if (filloptions.fillColor) {
647 return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
648 }
649
650 var c = $.color.parse(seriesColor);
651 c.a = typeof fill === "number" ? fill : 0.4;
652 c.normalize();
653 return c.toString();
654 }
655
656 this.drawSeriesLines = drawSeriesLines;
657 this.drawSeriesPoints = drawSeriesPoints;
658 this.drawSeriesBars = drawSeriesBars;
659 this.drawBar = drawBar;
660 };
661
662 $.plot.drawSeries = new DrawSeries();
663})(jQuery);