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