js/flot/jquery.flot.spline.js   A
last analyzed

Complexity

Total Complexity 25
Complexity/F 4.17

Size

Lines of Code 172
Function Count 6

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
eloc 88
nc 120
dl 0
loc 172
rs 10
c 0
b 0
f 0
wmc 25
mnd 2
bc 16
fnc 6
bpm 2.6666
cpm 4.1666
noi 3

5 Functions

Rating   Name   Duplication   Size   Complexity  
A jquery.flot.spline.js ➔ getControlPoints 0 21 1
B jquery.flot.spline.js ➔ queue 0 14 7
A $.plot.plugins.push.init 0 3 1
C jquery.flot.spline.js ➔ drawSpline 0 56 11
A jquery.flot.spline.js ➔ drawLine 0 29 4
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
Consider using undefined instead of void(0). It is equivalent and more straightforward to read.
Loading history...
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
Coding Style Best Practice introduced by
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 b = 42 will always be executed, while the logging statement will be executed conditionally.

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.

Loading history...
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
Coding Style Best Practice introduced by
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 b = 42 will always be executed, while the logging statement will be executed conditionally.

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.

Loading history...
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