/** * Flot plugin that provides spline interpolation for line graphs * author: Alex Bardas < alex.bardas@gmail.com > * modified by: Avi Kohn https://github.com/AMKohn * based on the spline interpolation described at: * http://scaledinnovation.com/analytics/splines/aboutSplines.html * * Example usage: (add in plot options series object) * for linespline: * series: { * ... * lines: { * show: false * }, * splines: { * show: true, * tension: x, (float between 0 and 1, defaults to 0.5), * lineWidth: y (number, defaults to 2), * fill: z (float between 0 .. 1 or false, as in flot documentation) * }, * ... * } * areaspline: * series: { * ... * lines: { * show: true, * lineWidth: 0, (line drawing will not execute) * fill: x, (float between 0 .. 1, as in flot documentation) * ... * }, * splines: { * show: true, * tension: 0.5 (float between 0 and 1) * }, * ... * } * */ (function($) { 'use strict' /** * @param {Number} x0, y0, x1, y1: coordinates of the end (knot) points of the segment * @param {Number} x2, y2: the next knot (not connected, but needed to calculate p2) * @param {Number} tension: control how far the control points spread * @return {Array}: p1 -> control point, from x1 back toward x0 * p2 -> the next control point, returned to become the next segment's p1 * * @api private */ function getControlPoints(x0, y0, x1, y1, x2, y2, tension) { var pow = Math.pow, sqrt = Math.sqrt, d01, d12, fa, fb, p1x, p1y, p2x, p2y; // Scaling factors: distances from this knot to the previous and following knots. d01 = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2)); d12 = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); fa = tension * d01 / (d01 + d12); fb = tension - fa; p1x = x1 + fa * (x0 - x2); p1y = y1 + fa * (y0 - y2); p2x = x1 - fb * (x0 - x2); p2y = y1 - fb * (y0 - y2); return [p1x, p1y, p2x, p2y]; } var line = []; function drawLine(points, ctx, height, fill, seriesColor) { var c = $.color.parse(seriesColor); c.a = typeof fill == "number" ? fill : .3; c.normalize(); c = c.toString(); ctx.beginPath(); ctx.moveTo(points[0][0], points[0][1]); var plength = points.length; for (var i = 0; i < plength; i++) { ctx[points[i][3]].apply(ctx, points[i][2]); } ctx.stroke(); ctx.lineWidth = 0; ctx.lineTo(points[plength - 1][0], height); ctx.lineTo(points[0][0], height); ctx.closePath(); if (fill !== false) { ctx.fillStyle = c; ctx.fill(); } } /** * @param {Object} ctx: canvas context * @param {String} type: accepted strings: 'bezier' or 'quadratic' (defaults to quadratic) * @param {Array} points: 2 points for which to draw the interpolation * @param {Array} cpoints: control points for those segment points * * @api private */ function queue(ctx, type, points, cpoints) { if (type === void 0 || (type !== 'bezier' && type !== 'quadratic')) { type = 'quadratic'; } type = type + 'CurveTo'; if (line.length == 0) line.push([points[0], points[1], cpoints.concat(points.slice(2)), type]); else if (type == "quadraticCurveTo" && points.length == 2) { cpoints = cpoints.slice(0, 2).concat(points); line.push([points[0], points[1], cpoints, type]); } else line.push([points[2], points[3], cpoints.concat(points.slice(2)), type]); } /** * @param {Object} plot * @param {Object} ctx: canvas context * @param {Object} series * * @api private */ function drawSpline(plot, ctx, series) { // Not interested if spline is not requested if (series.splines.show !== true) { return; } var cp = [], // array of control points tension = series.splines.tension || 0.5, idx, x, y, points = series.datapoints.points, ps = series.datapoints.pointsize, plotOffset = plot.getPlotOffset(), len = points.length, pts = []; line = []; // Cannot display a linespline/areaspline if there are less than 3 points if (len / ps < 4) { $.extend(series.lines, series.splines); return; } for (idx = 0; idx < len; idx += ps) { x = points[idx]; y = points[idx + 1]; if (x == null || x < series.xaxis.min || x > series.xaxis.max || y < series.yaxis.min || y > series.yaxis.max) { continue; } pts.push(series.xaxis.p2c(x) + plotOffset.left, series.yaxis.p2c(y) + plotOffset.top); } len = pts.length; // Draw an open curve, not connected at the ends for (idx = 0; idx < len - 2; idx += 2) { cp = cp.concat(getControlPoints.apply(this, pts.slice(idx, idx + 6).concat([tension]))); } ctx.save(); ctx.strokeStyle = series.color; ctx.lineWidth = series.splines.lineWidth; queue(ctx, 'quadratic', pts.slice(0, 4), cp.slice(0, 2)); for (idx = 2; idx < len - 3; idx += 2) { queue(ctx, 'bezier', pts.slice(idx, idx + 4), cp.slice(2 * idx - 2, 2 * idx + 2)); } queue(ctx, 'quadratic', pts.slice(len - 2, len), [cp[2 * len - 10], cp[2 * len - 9], pts[len - 4], pts[len - 3]]); drawLine(line, ctx, plot.height() + 10, series.splines.fill, series.color); ctx.restore(); } $.plot.plugins.push({ init: function(plot) { plot.hooks.drawSeries.push(drawSpline); }, options: { series: { splines: { show: false, lineWidth: 2, tension: 0.5, fill: false } } }, name: 'spline', version: '0.8.2' }); })(jQuery);