]> arthur.barton.de Git - netdata.git/blob - web/lib/dygraph-smooth-plotter.js
dygraphs now support smooth plotting on line charts; dygraphs can now be rendered...
[netdata.git] / web / lib / dygraph-smooth-plotter.js
1 var smoothPlotter = (function() {
2 "use strict";
3
4 /**
5  * Given three sequential points, p0, p1 and p2, find the left and right
6  * control points for p1.
7  *
8  * The three points are expected to have x and y properties.
9  *
10  * The alpha parameter controls the amount of smoothing.
11  * If α=0, then both control points will be the same as p1 (i.e. no smoothing).
12  *
13  * Returns [l1x, l1y, r1x, r1y]
14  *
15  * It's guaranteed that the line from (l1x, l1y)-(r1x, r1y) passes through p1.
16  * Unless allowFalseExtrema is set, then it's also guaranteed that:
17  *   l1y ∈ [p0.y, p1.y]
18  *   r1y ∈ [p1.y, p2.y]
19  *
20  * The basic algorithm is:
21  * 1. Put the control points l1 and r1 α of the way down (p0, p1) and (p1, p2).
22  * 2. Shift l1 and r2 so that the line l1–r1 passes through p1
23  * 3. Adjust to prevent false extrema while keeping p1 on the l1–r1 line.
24  *
25  * This is loosely based on the HighCharts algorithm.
26  */
27 function getControlPoints(p0, p1, p2, opt_alpha, opt_allowFalseExtrema) {
28   var alpha = (opt_alpha !== undefined) ? opt_alpha : 1/3;  // 0=no smoothing, 1=crazy smoothing
29   var allowFalseExtrema = opt_allowFalseExtrema || false;
30
31   if (!p2) {
32     return [p1.x, p1.y, null, null];
33   }
34
35   // Step 1: Position the control points along each line segment.
36   var l1x = (1 - alpha) * p1.x + alpha * p0.x,
37       l1y = (1 - alpha) * p1.y + alpha * p0.y,
38       r1x = (1 - alpha) * p1.x + alpha * p2.x,
39       r1y = (1 - alpha) * p1.y + alpha * p2.y;
40
41   // Step 2: shift the points up so that p1 is on the l1–r1 line.
42   if (l1x != r1x) {
43     // This can be derived w/ some basic algebra.
44     var deltaY = p1.y - r1y - (p1.x - r1x) * (l1y - r1y) / (l1x - r1x);
45     l1y += deltaY;
46     r1y += deltaY;
47   }
48
49   // Step 3: correct to avoid false extrema.
50   if (!allowFalseExtrema) {
51     if (l1y > p0.y && l1y > p1.y) {
52       l1y = Math.max(p0.y, p1.y);
53       r1y = 2 * p1.y - l1y;
54     } else if (l1y < p0.y && l1y < p1.y) {
55       l1y = Math.min(p0.y, p1.y);
56       r1y = 2 * p1.y - l1y;
57     }
58
59     if (r1y > p1.y && r1y > p2.y) {
60       r1y = Math.max(p1.y, p2.y);
61       l1y = 2 * p1.y - r1y;
62     } else if (r1y < p1.y && r1y < p2.y) {
63       r1y = Math.min(p1.y, p2.y);
64       l1y = 2 * p1.y - r1y;
65     }
66   }
67
68   return [l1x, l1y, r1x, r1y];
69 }
70
71
72 // A plotter which uses splines to create a smooth curve.
73 // See tests/plotters.html for a demo.
74 // Can be controlled via smoothPlotter.smoothing
75 function smoothPlotter(e) {
76   var ctx = e.drawingContext,
77       points = e.points;
78
79   ctx.beginPath();
80   ctx.moveTo(points[0].canvasx, points[0].canvasy);
81
82   // right control point for previous point
83   var lastRightX = points[0].canvasx, lastRightY = points[0].canvasy;
84   var isOK = Dygraph.isOK;  // i.e. is none of (null, undefined, NaN)
85
86   for (var i = 1; i < points.length; i++) {
87     var p0 = points[i - 1],
88         p1 = points[i],
89         p2 = points[i + 1];
90     p0 = p0 && isOK(p0.canvasy) ? p0 : null;
91     p1 = p1 && isOK(p1.canvasy) ? p1 : null;
92     p2 = p2 && isOK(p2.canvasy) ? p2 : null;
93     if (p0 && p1) {
94       var controls = getControlPoints({x: p0.canvasx, y: p0.canvasy},
95                                       {x: p1.canvasx, y: p1.canvasy},
96                                       p2 && {x: p2.canvasx, y: p2.canvasy},
97                                       smoothPlotter.smoothing);
98       // Uncomment to show the control points:
99       // ctx.lineTo(lastRightX, lastRightY);
100       // ctx.lineTo(controls[0], controls[1]);
101       // ctx.lineTo(p1.canvasx, p1.canvasy);
102       lastRightX = (lastRightX !== null) ? lastRightX : p0.canvasx;
103       lastRightY = (lastRightY !== null) ? lastRightY : p0.canvasy;
104       ctx.bezierCurveTo(lastRightX, lastRightY,
105                         controls[0], controls[1],
106                         p1.canvasx, p1.canvasy);
107       lastRightX = controls[2];
108       lastRightY = controls[3];
109     } else if (p1) {
110       // We're starting again after a missing point.
111       ctx.moveTo(p1.canvasx, p1.canvasy);
112       lastRightX = p1.canvasx;
113       lastRightY = p1.canvasy;
114     } else {
115       lastRightX = lastRightY = null;
116     }
117   }
118
119   ctx.stroke();
120 }
121 smoothPlotter.smoothing = 1/3;
122 smoothPlotter._getControlPoints = getControlPoints;  // for testing
123
124 return smoothPlotter;
125
126 })();