1 var smoothPlotter
= (function() {
5 * Given three sequential points, p0, p1 and p2, find the left and right
6 * control points for p1.
8 * The three points are expected to have x and y properties.
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).
13 * Returns [l1x, l1y, r1x, r1y]
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:
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.
25 * This is loosely based on the HighCharts algorithm.
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;
32 return [p1
.x
, p1
.y
, null, null];
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
;
41 // Step 2: shift the points up so that p1 is on the l1–r1 line.
43 // This can be derived w/ some basic algebra
.
44 var deltaY
= p1
.y
- r1y
- (p1
.x
- r1x
) * (l1y
- r1y
) / (l1x
- r1x
);
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
);
54 } else if (l1y
< p0
.y
&& l1y
< p1
.y
) {
55 l1y
= Math
.min(p0
.y
, p1
.y
);
59 if (r1y
> p1
.y
&& r1y
> p2
.y
) {
60 r1y
= Math
.max(p1
.y
, p2
.y
);
62 } else if (r1y
< p1
.y
&& r1y
< p2
.y
) {
63 r1y
= Math
.min(p1
.y
, p2
.y
);
68 return [l1x
, l1y
, r1x
, r1y
];
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
,
80 ctx
.moveTo(points
[0].canvasx
, points
[0].canvasy
);
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)
86 for (var i
= 1; i
< points
.length
; i
++) {
87 var p0
= 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;
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];
110 // We're starting again after a missing point.
111 ctx
.moveTo(p1
.canvasx
, p1
.canvasy
);
112 lastRightX
= p1
.canvasx
;
113 lastRightY
= p1
.canvasy
;
115 lastRightX
= lastRightY
= null;
121 smoothPlotter
.smoothing
= 1/3;
122 smoothPlotter
._getControlPoints
= getControlPoints
; // for testing
124 return smoothPlotter
;