1 | /** |
||
2 | * Flot plugin that provides spline interpolation for line graphs |
||
3 | * author: Alex Bardas < [email protected] > |
||
4 | * modified by: Avi Kohn https://github.com/AMKohn |
||
5 | * based on the spline interpolation described at: |
||
6 | * http://scaledinnovation.com/analytics/splines/aboutSplines.html |
||
7 | * |
||
8 | * Example usage: (add in plot options series object) |
||
9 | * for linespline: |
||
10 | * series: { |
||
11 | * ... |
||
12 | * lines: { |
||
13 | * show: false |
||
14 | * }, |
||
15 | * splines: { |
||
16 | * show: true, |
||
17 | * tension: x, (float between 0 and 1, defaults to 0.5), |
||
18 | * lineWidth: y (number, defaults to 2), |
||
19 | * fill: z (float between 0 .. 1 or false, as in flot documentation) |
||
20 | * }, |
||
21 | * ... |
||
22 | * } |
||
23 | * areaspline: |
||
24 | * series: { |
||
25 | * ... |
||
26 | * lines: { |
||
27 | * show: true, |
||
28 | * lineWidth: 0, (line drawing will not execute) |
||
29 | * fill: x, (float between 0 .. 1, as in flot documentation) |
||
30 | * ... |
||
31 | * }, |
||
32 | * splines: { |
||
33 | * show: true, |
||
34 | * tension: 0.5 (float between 0 and 1) |
||
35 | * }, |
||
36 | * ... |
||
37 | * } |
||
38 | * |
||
39 | */ |
||
40 | |||
41 | (function($) { |
||
42 | 'use strict' |
||
43 | |||
44 | /** |
||
45 | * @param {Number} x0, y0, x1, y1: coordinates of the end (knot) points of the segment |
||
46 | * @param {Number} x2, y2: the next knot (not connected, but needed to calculate p2) |
||
47 | * @param {Number} tension: control how far the control points spread |
||
48 | * @return {Array}: p1 -> control point, from x1 back toward x0 |
||
49 | * p2 -> the next control point, returned to become the next segment's p1 |
||
50 | * |
||
51 | * @api private |
||
52 | */ |
||
53 | function getControlPoints(x0, y0, x1, y1, x2, y2, tension) { |
||
54 | |||
55 | var pow = Math.pow, |
||
56 | sqrt = Math.sqrt, |
||
57 | d01, d12, fa, fb, p1x, p1y, p2x, p2y; |
||
58 | |||
59 | // Scaling factors: distances from this knot to the previous and following knots. |
||
60 | d01 = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2)); |
||
61 | d12 = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); |
||
62 | |||
63 | fa = tension * d01 / (d01 + d12); |
||
64 | fb = tension - fa; |
||
65 | |||
66 | p1x = x1 + fa * (x0 - x2); |
||
67 | p1y = y1 + fa * (y0 - y2); |
||
68 | |||
69 | p2x = x1 - fb * (x0 - x2); |
||
70 | p2y = y1 - fb * (y0 - y2); |
||
71 | |||
72 | return [p1x, p1y, p2x, p2y]; |
||
73 | } |
||
74 | |||
75 | var line = []; |
||
76 | |||
77 | function drawLine(points, ctx, height, fill, seriesColor) { |
||
78 | var c = $.color.parse(seriesColor); |
||
79 | |||
80 | c.a = typeof fill == "number" ? fill : .3; |
||
81 | c.normalize(); |
||
82 | c = c.toString(); |
||
83 | |||
84 | ctx.beginPath(); |
||
85 | ctx.moveTo(points[0][0], points[0][1]); |
||
86 | |||
87 | var plength = points.length; |
||
88 | |||
89 | for (var i = 0; i < plength; i++) { |
||
90 | ctx[points[i][3]].apply(ctx, points[i][2]); |
||
91 | } |
||
92 | |||
93 | ctx.stroke(); |
||
94 | |||
95 | ctx.lineWidth = 0; |
||
96 | ctx.lineTo(points[plength - 1][0], height); |
||
97 | ctx.lineTo(points[0][0], height); |
||
98 | |||
99 | ctx.closePath(); |
||
100 | |||
101 | if (fill !== false) { |
||
102 | ctx.fillStyle = c; |
||
103 | ctx.fill(); |
||
104 | } |
||
105 | } |
||
106 | |||
107 | /** |
||
108 | * @param {Object} ctx: canvas context |
||
109 | * @param {String} type: accepted strings: 'bezier' or 'quadratic' (defaults to quadratic) |
||
110 | * @param {Array} points: 2 points for which to draw the interpolation |
||
111 | * @param {Array} cpoints: control points for those segment points |
||
112 | * |
||
113 | * @api private |
||
114 | */ |
||
115 | function queue(ctx, type, points, cpoints) { |
||
116 | if (type === void 0 || (type !== 'bezier' && type !== 'quadratic')) { |
||
0 ignored issues
–
show
Coding Style
introduced
by
![]() |
|||
117 | type = 'quadratic'; |
||
118 | } |
||
119 | type = type + 'CurveTo'; |
||
120 | |||
121 | if (line.length == 0) line.push([points[0], points[1], cpoints.concat(points.slice(2)), type]); |
||
0 ignored issues
–
show
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later. Consider: if (a > 0)
b = 42;
If you or someone else later decides to put another statement in, only the first statement will be executed. if (a > 0)
console.log("a > 0");
b = 42;
In this case the statement if (a > 0) {
console.log("a > 0");
b = 42;
}
ensures that the proper code will be executed conditionally no matter how many statements are added or removed. ![]() |
|||
122 | else if (type == "quadraticCurveTo" && points.length == 2) { |
||
123 | cpoints = cpoints.slice(0, 2).concat(points); |
||
124 | |||
125 | line.push([points[0], points[1], cpoints, type]); |
||
126 | } |
||
127 | else line.push([points[2], points[3], cpoints.concat(points.slice(2)), type]); |
||
0 ignored issues
–
show
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.
Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later. Consider: if (a > 0)
b = 42;
If you or someone else later decides to put another statement in, only the first statement will be executed. if (a > 0)
console.log("a > 0");
b = 42;
In this case the statement if (a > 0) {
console.log("a > 0");
b = 42;
}
ensures that the proper code will be executed conditionally no matter how many statements are added or removed. ![]() |
|||
128 | } |
||
129 | |||
130 | /** |
||
131 | * @param {Object} plot |
||
132 | * @param {Object} ctx: canvas context |
||
133 | * @param {Object} series |
||
134 | * |
||
135 | * @api private |
||
136 | */ |
||
137 | |||
138 | function drawSpline(plot, ctx, series) { |
||
139 | // Not interested if spline is not requested |
||
140 | if (series.splines.show !== true) { |
||
141 | return; |
||
142 | } |
||
143 | |||
144 | var cp = [], |
||
145 | // array of control points |
||
146 | tension = series.splines.tension || 0.5, |
||
147 | idx, x, y, points = series.datapoints.points, |
||
148 | ps = series.datapoints.pointsize, |
||
149 | plotOffset = plot.getPlotOffset(), |
||
150 | len = points.length, |
||
151 | pts = []; |
||
152 | |||
153 | line = []; |
||
154 | |||
155 | // Cannot display a linespline/areaspline if there are less than 3 points |
||
156 | if (len / ps < 4) { |
||
157 | $.extend(series.lines, series.splines); |
||
158 | return; |
||
159 | } |
||
160 | |||
161 | for (idx = 0; idx < len; idx += ps) { |
||
162 | x = points[idx]; |
||
163 | y = points[idx + 1]; |
||
164 | if (x == null || x < series.xaxis.min || x > series.xaxis.max || y < series.yaxis.min || y > series.yaxis.max) { |
||
165 | continue; |
||
166 | } |
||
167 | |||
168 | pts.push(series.xaxis.p2c(x) + plotOffset.left, series.yaxis.p2c(y) + plotOffset.top); |
||
169 | } |
||
170 | |||
171 | len = pts.length; |
||
172 | |||
173 | // Draw an open curve, not connected at the ends |
||
174 | for (idx = 0; idx < len - 2; idx += 2) { |
||
175 | cp = cp.concat(getControlPoints.apply(this, pts.slice(idx, idx + 6).concat([tension]))); |
||
176 | } |
||
177 | |||
178 | ctx.save(); |
||
179 | ctx.strokeStyle = series.color; |
||
180 | ctx.lineWidth = series.splines.lineWidth; |
||
181 | |||
182 | queue(ctx, 'quadratic', pts.slice(0, 4), cp.slice(0, 2)); |
||
183 | |||
184 | for (idx = 2; idx < len - 3; idx += 2) { |
||
185 | queue(ctx, 'bezier', pts.slice(idx, idx + 4), cp.slice(2 * idx - 2, 2 * idx + 2)); |
||
186 | } |
||
187 | |||
188 | queue(ctx, 'quadratic', pts.slice(len - 2, len), [cp[2 * len - 10], cp[2 * len - 9], pts[len - 4], pts[len - 3]]); |
||
189 | |||
190 | drawLine(line, ctx, plot.height() + 10, series.splines.fill, series.color); |
||
191 | |||
192 | ctx.restore(); |
||
193 | } |
||
194 | |||
195 | $.plot.plugins.push({ |
||
196 | init: function(plot) { |
||
197 | plot.hooks.drawSeries.push(drawSpline); |
||
198 | }, |
||
199 | options: { |
||
200 | series: { |
||
201 | splines: { |
||
202 | show: false, |
||
203 | lineWidth: 2, |
||
204 | tension: 0.5, |
||
205 | fill: false |
||
206 | } |
||
207 | } |
||
208 | }, |
||
209 | name: 'spline', |
||
210 | version: '0.8.2' |
||
211 | }); |
||
212 | })(jQuery); |
||
213 |