From 56b371762bd92260e1507f2f940e83c5168b7ddb Mon Sep 17 00:00:00 2001 From: Thomas-git Date: Sun, 16 Dec 2012 15:39:09 +1100 Subject: [PATCH 01/10] Proper curves filling and require.js module Small bug fixes - fill opacity always defaulted to 0 - check of Array.map --- curvedLines.js | 927 +++++++++++++++++++++++++++++-------------------- 1 file changed, 550 insertions(+), 377 deletions(-) diff --git a/curvedLines.js b/curvedLines.js index 249add2..636fd9a 100644 --- a/curvedLines.js +++ b/curvedLines.js @@ -1,152 +1,176 @@ +define(['jquery','jquery.flot'],function(jQuery,jflot){ /* The MIT License - Copyright (c) 2011 by Michael Zinsmaier and nergal.dev - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - */ - -/* - - ____________________________________________________ - - what it is: - ____________________________________________________ - - curvedLines is a plugin for flot, that tries to display lines in a smoother way. - The plugin is based on nergal.dev's work https://code.google.com/p/flot/issues/detail?id=226 - and further extended with a mode that forces the min/max points of the curves to be on the - points. Both modes are achieved through adding of more data points - => 1) with large data sets you may get trouble - => 2) if you want to display the points too, you have to plot them as 2nd data series over the lines - - This is version 0.3 of curvedLines so it will probably not work in every case. However - the basic form of use descirbed next works (: - Feel free to further improve the code +Copyright (c) 2011 by Michael Zinsmaier and nergal.dev +Copyright (c) 2012 by Thomas Ritou - ____________________________________________________ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - how to use it: - ____________________________________________________ +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. - var d1 = [[5,5],[7,3],[9,12]]; +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ - var options = { series: { curvedLines: { active: true }}}; - - $.plot($("#placeholder"), [{data = d1, curvedLines: { show: true}}], options); - _____________________________________________________ - - options: - _____________________________________________________ - - fill: bool true => lines get filled - fillColor: null or the color that should be used for filling - active: bool true => plugin can be used - show: bool true => series will be drawn as curved line - fit: bool true => forces the max,mins of the curve to be on the datapoints - lineWidth: int width of the line - curvePointFactor int defines how many "virtual" points are used per "real" data point to - emulate the curvedLines - fitPointDist: int defines the x axis distance of the additional two points that are used - to enforce the min max condition. (you will get curvePointFactor * 3 * |datapoints| - "virtual" points if fit is true) - */ /* - * v0.1 initial commit - * v0.15 negative values should work now (outcommented a negative -> 0 hook hope it does no harm) - * v0.2 added fill option (thanks to monemihir) and multi axis support (thanks to soewono effendi) - * v0.3 improved saddle handling and added basic handling of Dates - * - */ - -(function($) { - - var options = { - series : { - curvedLines : { - active : false, - show : false, - fit : false, - fill : false, - fillColor : null, - lineWidth : 2, - curvePointFactor : 20, - fitPointDist : 0.0001 - } - } - }; - function init(plot) { +____________________________________________________ + +what it is: +____________________________________________________ + +curvedLines is a plugin for flot, that tries to display lines in a smoother way. +The plugin is based on nergal.dev's work https://code.google.com/p/flot/issues/detail?id=226 +and further extended with a mode that forces the min/max points of the curves to be on the +points. Both modes are achieved through adding of more data points +=> 1) with large data sets you may get trouble +=> 2) if you want to display the points too, you have to plot them as 2nd data series over the lines + +This is version 0.4 of curvedLines so it will probably not work in every case. However +the basic form of use descirbed next works (: + +Feel free to further improve the code + +____________________________________________________ + +how to use it: +____________________________________________________ + + + var d1 = [[5,5],[7,3],[9,12]]; + + var options = { series: { curvedLines: { active: true }}}; + + + $.plot($("#placeholder"), [{data = d1, curvedLines: { show: true}}], options); + +_____________________________________________________ + +options: +_____________________________________________________ + + fill: bool true => lines get filled + fillColor: null or the color that should be used for filling + active: bool true => plugin can be used + show: bool true => series will be drawn as curved line + fit: bool true => forces the max,mins of the curve to be on the datapoints + lineWidth: int width of the line + curvePointFactor int defines how many "virtual" points are used per "real" data point to + emulate the curvedLines + fitPointDist: int defines the x axis distance of the additional two points that are used + to enforce the min max condition. (you will get curvePointFactor * 3 * |datapoints| + "virtual" points if fit is true) + + +*/ + +/* + * v0.1 initial commit + * v0.15 negative values should work now (outcommented a negative -> 0 hook hope it does no harm) + * v0.2 added fill option (thanks to monemihir) and multi axis support (thanks to soewono effendi) + * v0.3 improved saddle handling and added basic handling of Dates + * v0.4 rewritten fill option (thomas ritou) mostly from original flot code (now fill between points rather than to graph bottom), corrected fill Opacity bug + * + */ - plot.hooks.processOptions.push(processOptions); +(function ($) { + + + + var options = { series: { curvedLines: { active: false, + show: false, + fit: false, + fill: false, + fillColor: null, + lineWidth: 2, + curvePointFactor: 20, + fitPointDist: 0.0001 + }}}; + + + function init(plot) { + + plot.hooks.processOptions.push(processOptions); //if the plugin is active register draw method - function processOptions(plot, options) { - if (options.series.curvedLines.active) { - plot.hooks.draw.push(draw); - } - } - - //select the data sets that should be drawn with curved lines and draws them - function draw(plot, ctx) { - var series; - var sdata = plot.getData(); - var offset = plot.getPlotOffset(); - - for (var i = 0; i < sdata.length; i++) { - series = sdata[i]; - if (series.curvedLines.show && series.curvedLines.lineWidth > 0) { - - axisx = series.xaxis; - axisy = series.yaxis; - - ctx.save(); - ctx.translate(offset.left, offset.top); - ctx.lineJoin = "round"; - ctx.strokeStyle = series.color; - if (series.curvedLines.fill) { - var fillColor = series.curvedLines.fillColor == null ? series.color : series.curvedLines.fillColor; - var c = $.color.parse(fillColor); - c.a = typeof fill == "number" ? fill : 0.4; - c.normalize(); - ctx.fillStyle = c.toString(); - } - ctx.lineWidth = series.curvedLines.lineWidth; - var points, dataX, dataY, data; - - //convenience check for x or y values if they are Dates if so apply .getTime() - //only check on first value mixing numeric and Date fields in one input array is not allowed - if (series.data[0][0] instanceof Date || series.data[0][1] instanceof Date) { - data = series.data.map(getTimeFromDate); - } else { - //default case - data = series.data; - } - - var points = calculateCurvePoints(data, series.curvedLines); - plotLine(ctx, points, axisx, axisy, series.curvedLines.fill); - ctx.restore(); - } - } - } - - //helper method that convertes Dates to a numeric representation + function processOptions(plot,options) { + if (options.series.curvedLines.active) { + plot.hooks.draw.push(draw); + } + } + + + //select the data sets that should be drawn with curved lines and draws them + function draw(plot, ctx) { + var series; + var sdata = plot.getData(); + var offset = plot.getPlotOffset(); + + for (var i = 0; i < sdata.length; i++) { + series = sdata[i]; + if (series.curvedLines.show) { + + axisx = series.xaxis; + axisy = series.yaxis; + + ctx.save(); + ctx.translate(offset.left, offset.top); + ctx.lineJoin = "round"; + ctx.strokeStyle = series.color; + + ctx.lineWidth = series.curvedLines.lineWidth; + + + var points,data; + if(series.curvedLines.fill || series.curvedLines.lineWidth) { + + //convenience check for x or y values if they are Dates if so apply .getTime() + //only check on first value mixing numeric and Date fields in one input array is not allowed + if ('map' in Array.prototype && (series.data[0][0] instanceof Date || series.data[0][1] instanceof Date)) { + data = series.data.map(getTimeFromDate); + } else { + //default case + data = series.data; + } + + points= calculateCurvePoints(data, series.curvedLines,1); + } + if(series.curvedLines.fill) { + var fillColor = series.curvedLines.fillColor == null ? series.color : series.curvedLines.fillColor; + var c = $.color.parse(fillColor); + c.a = typeof series.curvedLines.fill == "number" ? series.curvedLines.fill : 0.4; + c.normalize(); + ctx.fillStyle = c.toString(); + //Make sure we've got a second y point for filling area + for (var j=0;j0) + plotLine(ctx, points, axisx, axisy,2); + ctx.restore(); + } + } + } + + //helper method that convertes Dates to a numeric representation function getTimeFromDate(timeElement) { var xVal = timeElement[0]; var yVal = timeElement[1]; @@ -166,248 +190,397 @@ return ret; } - - //nearly the same as in the core library - //only ps is adjusted to 2 - function plotLine(ctx, points, axisx, axisy, fill) { - - var ps = 2; - var prevx = null; - var prevy = null; - var firsty = 0; - - ctx.beginPath(); - - for (var i = ps; i < points.length; i += ps) { - var x1 = points[i - ps], y1 = points[i - ps + 1]; - var x2 = points[i], y2 = points[i + 1]; - - if (x1 == null || x2 == null) - continue; - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min) { - if (y2 < axisy.min) - continue; - // line segment is outside - // compute new intersection point - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } else if (y2 <= y1 && y2 < axisy.min) { - if (y1 < axisy.min) - continue; - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max) { - if (y2 > axisy.max) - continue; - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } else if (y2 >= y1 && y2 > axisy.max) { - if (y1 > axisy.max) - continue; - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (x1 != prevx || y1 != prevy) - ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); - - if (prevx == null) { - firsty = y2; - } - prevx = x2; - prevy = y2; - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - } - if (fill) { - ctx.lineTo(axisx.p2c(axisx.max), axisy.p2c(axisy.min)); - ctx.lineTo(axisx.p2c(axisx.min), axisy.p2c(axisy.min)); - ctx.lineTo(axisx.p2c(axisx.min), axisy.p2c(firsty)); - ctx.fill(); - } - ctx.stroke(); - } - - //no real idea whats going on here code mainly from https://code.google.com/p/flot/issues/detail?id=226 - //if fit option is selected additional datapoints get inserted before the curve calculations in nergal.dev s code. - function calculateCurvePoints(data, curvedLinesOptions) { - - var num = curvedLinesOptions.curvePointFactor * data.length; - var xdata = new Array; - var ydata = new Array; + + function plotLineArea(ctx,points2,points3, axisx, axisy) { + var points = points2, + ps = 2, + bottom = Math.min(Math.max(0, axisy.min), axisy.max), + i = 0, top, areaOpen = false, + ypos = 1, segmentStart = 0, segmentEnd = 0; + + // we process each segment in two turns, first forward + // direction to sketch out top, then once we hit the + // end we go backwards to sketch the bottom + while (true) { + if (ps > 0 && i > points.length + ps) + break; + + i += ps; // ps is negative if going backwards + + var x1 = points[i - ps], + y1 = points[i - ps + ypos], + x2 = points[i], y2 = points[i + ypos]; + + if (areaOpen) { + if (ps > 0 && x1 != null && x2 == null) { + // at turning point + segmentEnd = i; + ps = -ps; + ypos = 1; + points=points3; + continue; + } + + if (ps < 0 && i == segmentStart + ps) { + // done with the reverse sweep + ctx.fill(); + areaOpen = false; + ps = -ps; + ypos = 1; + points=points2; + i = segmentStart = segmentEnd + ps; + continue; + } + } + + if (x1 == null || x2 == null) + continue; + + // clip x values + + // clip with xmin + if (x1 <= x2 && x1 < axisx.min) { + if (x2 < axisx.min) + continue; + y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.min; + } + else if (x2 <= x1 && x2 < axisx.min) { + if (x1 < axisx.min) + continue; + y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.min; + } + + // clip with xmax + if (x1 >= x2 && x1 > axisx.max) { + if (x2 > axisx.max) + continue; + y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.max; + } + else if (x2 >= x1 && x2 > axisx.max) { + if (x1 > axisx.max) + continue; + y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.max; + } + + if (!areaOpen) { + // open area + ctx.beginPath(); + ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); + areaOpen = true; + } + + // now first check the case where both is outside + if (y1 >= axisy.max && y2 >= axisy.max) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); + continue; + } + else if (y1 <= axisy.min && y2 <= axisy.min) { + ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); + continue; + } + + // else it's a bit more complicated, there might + // be a flat maxed out rectangle first, then a + // triangular cutout or reverse; to find these + // keep track of the current x values + var x1old = x1, x2old = x2; + + // clip the y values, without shortcutting, we + // go through all cases in turn + + // clip with ymin + if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { + x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.min; + } + else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { + x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.min; + } + + // clip with ymax + if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { + x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.max; + } + else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { + x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.max; + } + + // if the x value was changed we got a rectangle + // to fill + if (x1 != x1old) { + ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); + // it goes to (x1, y1), but we fill that below + } + + // fill triangular section, this sometimes result + // in redundant points if (x1, y1) hasn't changed + // from previous line to, but we just ignore that + ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + + // fill the other rectangle if it's there + if (x2 != x2old) { + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); + } + } + } + + + //nearly the same as in the core library + function plotLine(ctx, points, axisx, axisy,ps) { - var X = 0; - var Y = 1; - - if (curvedLinesOptions.fit) { - //insert a point before and after the "real" data point to force the line - //to have a max,min at the data point however only if it is a lowest or highest point of the - //curve => avoid saddles - var neigh = curvedLinesOptions.fitPointDist; - var j = 0; - - for (var i = 0; i < data.length; i++) { - - var front = new Array; - var back = new Array; - - //smooth front - front[X] = data[i][X] - 0.1; - if (i > 0) { - front[Y] = data[i-1][Y] * neigh + data[i][Y] * (1 - neigh); - } else { - front[Y] = data[i][Y]; - } - - - //smooth back - back[X] = data[i][X] + 0.1; - if ((i + 1) < data.length) { - back[Y] = data[i+1][Y] * neigh + data[i][Y] * (1 - neigh); - } else { - back[Y] = data[i][Y]; - } - - //test for a saddle - if ((front[Y] <= data[i][Y] && back[Y] <= data[i][Y]) || //max or partial horizontal - (front[Y] >= data[i][Y] && back[Y] >= data[i][Y])) { //min or partial horizontal - - //add curve points - xdata[j] = front[X]; - ydata[j] = front[Y]; - j++; - - xdata[j] = data[i][0]; - ydata[j] = data[i][1]; - j++; - - xdata[j] = back[X]; - ydata[j] = back[Y]; - j++; - } else { //saddle - //use original point only - xdata[j] = data[i][0]; - ydata[j] = data[i][1]; - j++; - } - - - } - } else { - //just use the datapoints - for (var i = 0; i < data.length; i++) { - xdata[i] = data[i][0]; - ydata[i] = data[i][1]; - } - } - - var n = xdata.length; - - var y2 = new Array(); - var delta = new Array(); - y2[0] = 0; - y2[n - 1] = 0; - delta[0] = 0; - - for (var i = 1; i < n - 1; ++i) { - var d = (xdata[i + 1] - xdata[i - 1]); - if (d == 0) { - return null; - } - - var s = (xdata[i] - xdata[i - 1]) / d; - var p = s * y2[i - 1] + 2; - y2[i] = (s - 1) / p; - delta[i] = (ydata[i + 1] - ydata[i]) / (xdata[i + 1] - xdata[i]) - (ydata[i] - ydata[i - 1]) / (xdata[i] - xdata[i - 1]); - delta[i] = (6 * delta[i] / (xdata[i + 1] - xdata[i - 1]) - s * delta[i - 1]) / p; - } - - for (var j = n - 2; j >= 0; --j) { - y2[j] = y2[j] * y2[j + 1] + delta[j]; - } - - var step = (xdata[n - 1] - xdata[0]) / (num - 1); - - var xnew = new Array; - var ynew = new Array; - var result = new Array; - - xnew[0] = xdata[0]; - ynew[0] = ydata[0]; - - for ( j = 1; j < num; ++j) { - xnew[j] = xnew[0] + j * step; - - var max = n - 1; - var min = 0; - - while (max - min > 1) { - var k = Math.round((max + min) / 2); - if (xdata[k] > xnew[j]) { - max = k; - } else { - min = k; - } - } - - var h = (xdata[max] - xdata[min]); - - if (h == 0) { - return null; - } - - var a = (xdata[max] - xnew[j]) / h; - var b = (xnew[j] - xdata[min]) / h; - - ynew[j] = a * ydata[min] + b * ydata[max] + ((a * a * a - a) * y2[min] + (b * b * b - b) * y2[max]) * (h * h) / 6; - // if (ynew[j] < 0.01){ - // ynew[j] = 0; - // } - result.push(xnew[j]); - result.push(ynew[j]); - } - - return result; + var prevx = null; + var prevy = null; + + ctx.beginPath(); + + for (var i = ps; i < points.length; i += ps) { + var x1 = points[i - ps], y1 = points[i - ps + 1]; + var x2 = points[i], y2 = points[i + 1]; + + if (x1 == null || x2 == null) + continue; + + // clip with ymin + if (y1 <= y2 && y1 < axisy.min) { + if (y2 < axisy.min) + continue; // line segment is outside + // compute new intersection point + x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.min; + } + else if (y2 <= y1 && y2 < axisy.min) { + if (y1 < axisy.min) + continue; + x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.min; + } + + // clip with ymax + if (y1 >= y2 && y1 > axisy.max) { + if (y2 > axisy.max) + continue; + x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y1 = axisy.max; + } + else if (y2 >= y1 && y2 > axisy.max) { + if (y1 > axisy.max) + continue; + x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; + y2 = axisy.max; + } + + // clip with xmin + if (x1 <= x2 && x1 < axisx.min) { + if (x2 < axisx.min) + continue; + y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.min; + } + else if (x2 <= x1 && x2 < axisx.min) { + if (x1 < axisx.min) + continue; + y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.min; + } + + // clip with xmax + if (x1 >= x2 && x1 > axisx.max) { + if (x2 > axisx.max) + continue; + y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x1 = axisx.max; + } + else if (x2 >= x1 && x2 > axisx.max) { + if (x1 > axisx.max) + continue; + y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; + x2 = axisx.max; + } + + if (x1 != prevx || y1 != prevy) + ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); + + + prevx = x2; + prevy = y2; + ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); + } + + ctx.stroke(); } - }//end init - - - $.plot.plugins.push({ - init : init, - options : options, - name : 'curvedLines', - version : '0.3' - }); + //no real idea whats going on here code mainly from https://code.google.com/p/flot/issues/detail?id=226 + //I don't understand what nergal computes here and to be honest I didn't even try + function calculateCurvePoints(data, curvedLinesOptions,yPos) { + + var num = curvedLinesOptions.curvePointFactor * data.length; + var xdata = new Array; + var ydata = new Array; + + var X = 0; + var Y = yPos; + + if (curvedLinesOptions.fit) { + //insert a point before and after the "real" data point to force the line + //to have a max,min at the data point however only if it is a lowest or highest point of the + //curve => avoid saddles + var neigh = curvedLinesOptions.fitPointDist; + var j = 0; + + for (var i = 0; i < data.length; i++) { + + var front = new Array; + var back = new Array; + + //smooth front + front[X] = data[i][X] - 0.1; + if (i > 0) { + front[Y] = data[i-1][Y] * neigh + data[i][Y] * (1 - neigh); + } else { + front[Y] = data[i][Y]; + } + + + //smooth back + back[X] = data[i][X] + 0.1; + if ((i + 1) < data.length) { + back[Y] = data[i+1][Y] * neigh + data[i][Y] * (1 - neigh); + } else { + back[Y] = data[i][Y]; + } + + //test for a saddle + if ((front[Y] <= data[i][Y] && back[Y] <= data[i][Y]) || //max or partial horizontal + (front[Y] >= data[i][Y] && back[Y] >= data[i][Y])) { //min or partial horizontal + + //add curve points + xdata[j] = front[X]; + ydata[j] = front[Y]; + j++; + + xdata[j] = data[i][X]; + ydata[j] = data[i][Y]; + j++; + + xdata[j] = back[X]; + ydata[j] = back[Y]; + j++; + } else { //saddle + //use original point only + xdata[j] = data[i][X]; + ydata[j] = data[i][Y]; + j++; + } + + + } + } else { + //just use the datapoints + for (var i = 0; i < data.length; i++) { + xdata[i] = data[i][X]; + ydata[i] = data[i][Y]; + } + } + + var n = xdata.length; + + var y2 = new Array(); + var delta = new Array(); + y2[0] = 0; + y2[n - 1] = 0; + delta[0] = 0; + + for(var i = 1; i < n - 1; ++i) { + var d = (xdata[i + 1] - xdata[i - 1]); + if(d == 0) { + return null; + } + + var s = (xdata[i] - xdata[i - 1]) / d; + var p = s * y2[i - 1] + 2; + y2[i] = (s - 1) / p; + delta[i] = (ydata[i + 1] - ydata[i]) / (xdata[i + 1] - xdata[i]) - (ydata[i] - ydata[i - 1]) / (xdata[i] - xdata[i - 1]); + delta[i] = (6 * delta[i] / (xdata[i + 1] - xdata[i - 1]) - s * delta[i - 1]) / p; + } + + for(var j = n - 2; j >= 0; --j) { + y2[j] = y2[j] * y2[j + 1] + delta[j]; + } + + var step = (xdata[n - 1] - xdata[0]) / (num - 1); + + var xnew = new Array; + var ynew = new Array; + var result = new Array; + + xnew[0] = xdata[0]; + ynew[0] = ydata[0]; + + + for(j = 1; j < num; ++j) { + xnew[j] = xnew[0] + j * step; + + var max = n - 1; + var min = 0; + + while(max - min > 1) { + var k = Math.round((max + min) / 2); + if(xdata[k] > xnew[j]) { + max = k; + } else { + min = k; + } + } + + var h = (xdata[max] - xdata[min]); + + if(h == 0) { + return null; + } + + var a = (xdata[max] - xnew[j]) / h; + var b = (xnew[j] - xdata[min]) / h; + + ynew[j] = a * ydata[min] + b * ydata[max] + ((a * a * a - a) * y2[min] + (b * b * b - b) * y2[max]) * (h * h) / 6; +// if (ynew[j] < 0.01){ +// ynew[j] = 0; +// } + result.push(xnew[j]); + result.push(ynew[j]); + } + + return result; + } + + + + + }//end init + + $.plot.plugincurvedLines=true; + + $.plot.plugins.push({ + init: init, + options: options, + name: 'curvedLines', + version: '0.4' + }); + + })(jQuery); + +return jQuery.plot.plugincurvedLines; + +}); From cf5eb584f38a165fa054a4260bdced0ef85f7e71 Mon Sep 17 00:00:00 2001 From: Thomas-git Date: Tue, 18 Dec 2012 00:10:59 +1100 Subject: [PATCH 02/10] Nearly complete rewrite Now let flot do the hard work. So all lines options are supported (date axis, shadow, gradient fill, ...) --- curvedLines.js | 444 ++++++++++++------------------------------------- 1 file changed, 104 insertions(+), 340 deletions(-) diff --git a/curvedLines.js b/curvedLines.js index 636fd9a..f672d73 100644 --- a/curvedLines.js +++ b/curvedLines.js @@ -1,4 +1,3 @@ -define(['jquery','jquery.flot'],function(jQuery,jflot){ /* The MIT License Copyright (c) 2011 by Michael Zinsmaier and nergal.dev @@ -22,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +define(['jquery','jquery.flot'],function(jQuery,jflot){ /* @@ -38,7 +37,7 @@ points. Both modes are achieved through adding of more data points => 1) with large data sets you may get trouble => 2) if you want to display the points too, you have to plot them as 2nd data series over the lines -This is version 0.4 of curvedLines so it will probably not work in every case. However +This is version 0.5 of curvedLines so it will probably not work in every case. However the basic form of use descirbed next works (: Feel free to further improve the code @@ -54,19 +53,17 @@ ____________________________________________________ var options = { series: { curvedLines: { active: true }}}; - $.plot($("#placeholder"), [{data = d1, curvedLines: { show: true}}], options); + $.plot($("#placeholder"), [{data = d1, lines: { show: true,curved:true}}], options); _____________________________________________________ options: _____________________________________________________ - fill: bool true => lines get filled - fillColor: null or the color that should be used for filling + active: bool true => plugin can be used - show: bool true => series will be drawn as curved line + curved: bool true => series will be drawn as curved line fit: bool true => forces the max,mins of the curve to be on the datapoints - lineWidth: int width of the line curvePointFactor int defines how many "virtual" points are used per "real" data point to emulate the curvedLines fitPointDist: int defines the x axis distance of the additional two points that are used @@ -82,7 +79,7 @@ _____________________________________________________ * v0.2 added fill option (thanks to monemihir) and multi axis support (thanks to soewono effendi) * v0.3 improved saddle handling and added basic handling of Dates * v0.4 rewritten fill option (thomas ritou) mostly from original flot code (now fill between points rather than to graph bottom), corrected fill Opacity bug - * + * v0.5 completly rewritten to let flot do the hard work. CurvedLines usage changed and will require code change but is more straightforward to use for new users (so all flot lines options (gradient fill, shadow are now supported) */ (function ($) { @@ -104,328 +101,93 @@ _____________________________________________________ plot.hooks.processOptions.push(processOptions); - //if the plugin is active register draw method + //if the plugin is active register processDatapoints method function processOptions(plot,options) { if (options.series.curvedLines.active) { - plot.hooks.draw.push(draw); - } - } - - - //select the data sets that should be drawn with curved lines and draws them - function draw(plot, ctx) { - var series; - var sdata = plot.getData(); - var offset = plot.getPlotOffset(); - - for (var i = 0; i < sdata.length; i++) { - series = sdata[i]; - if (series.curvedLines.show) { - - axisx = series.xaxis; - axisy = series.yaxis; - - ctx.save(); - ctx.translate(offset.left, offset.top); - ctx.lineJoin = "round"; - ctx.strokeStyle = series.color; - - ctx.lineWidth = series.curvedLines.lineWidth; - - - var points,data; - if(series.curvedLines.fill || series.curvedLines.lineWidth) { - - //convenience check for x or y values if they are Dates if so apply .getTime() - //only check on first value mixing numeric and Date fields in one input array is not allowed - if ('map' in Array.prototype && (series.data[0][0] instanceof Date || series.data[0][1] instanceof Date)) { - data = series.data.map(getTimeFromDate); - } else { - //default case - data = series.data; - } - - points= calculateCurvePoints(data, series.curvedLines,1); - } - if(series.curvedLines.fill) { - var fillColor = series.curvedLines.fillColor == null ? series.color : series.curvedLines.fillColor; - var c = $.color.parse(fillColor); - c.a = typeof series.curvedLines.fill == "number" ? series.curvedLines.fill : 0.4; - c.normalize(); - ctx.fillStyle = c.toString(); - //Make sure we've got a second y point for filling area - for (var j=0;j0) - plotLine(ctx, points, axisx, axisy,2); - ctx.restore(); - } + + plot.hooks.processDatapoints.push(processDatapoints); + } } - //helper method that convertes Dates to a numeric representation - function getTimeFromDate(timeElement) { - var xVal = timeElement[0]; - var yVal = timeElement[1]; - var ret = new Array; - - if (timeElement[0] instanceof Date) { - ret[0] = xVal.getTime(); - } else { - ret[0] = xVal; - } - - if (timeElement[1] instanceof Date) { - ret[1] = yVal.getTime(); - } else { - ret[1] = yVal; - } - - return ret; + function processDatapoints(plot, series, datapoints) { + if (series.lines.curved==true){ + if (series.lines.fill){ + + var pointsTop=calculateCurvePoints2(datapoints, series.curvedLines,1); + + //Make sure we've got a second y point for filling area + for (var j=0;j 0 && i > points.length + ps) - break; - - i += ps; // ps is negative if going backwards - - var x1 = points[i - ps], - y1 = points[i - ps + ypos], - x2 = points[i], y2 = points[i + ypos]; - - if (areaOpen) { - if (ps > 0 && x1 != null && x2 == null) { - // at turning point - segmentEnd = i; - ps = -ps; - ypos = 1; - points=points3; - continue; - } - - if (ps < 0 && i == segmentStart + ps) { - // done with the reverse sweep - ctx.fill(); - areaOpen = false; - ps = -ps; - ypos = 1; - points=points2; - i = segmentStart = segmentEnd + ps; - continue; - } - } - - if (x1 == null || x2 == null) - continue; - - // clip x values - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (!areaOpen) { - // open area - ctx.beginPath(); - ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); - areaOpen = true; - } - - // now first check the case where both is outside - if (y1 >= axisy.max && y2 >= axisy.max) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); - continue; - } - else if (y1 <= axisy.min && y2 <= axisy.min) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); - continue; - } - - // else it's a bit more complicated, there might - // be a flat maxed out rectangle first, then a - // triangular cutout or reverse; to find these - // keep track of the current x values - var x1old = x1, x2old = x2; - - // clip the y values, without shortcutting, we - // go through all cases in turn - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // if the x value was changed we got a rectangle - // to fill - if (x1 != x1old) { - ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); - // it goes to (x1, y1), but we fill that below - } - - // fill triangular section, this sometimes result - // in redundant points if (x1, y1) hasn't changed - // from previous line to, but we just ignore that - ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - - // fill the other rectangle if it's there - if (x2 != x2old) { - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); - } - } - } - - - //nearly the same as in the core library - function plotLine(ctx, points, axisx, axisy,ps) { - - var prevx = null; - var prevy = null; - - ctx.beginPath(); - - for (var i = ps; i < points.length; i += ps) { - var x1 = points[i - ps], y1 = points[i - ps + 1]; - var x2 = points[i], y2 = points[i + 1]; - - if (x1 == null || x2 == null) - continue; - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min) { - if (y2 < axisy.min) - continue; // line segment is outside - // compute new intersection point - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min) { - if (y1 < axisy.min) - continue; - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max) { - if (y2 > axisy.max) - continue; - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max) { - if (y1 > axisy.max) - continue; - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (x1 != prevx || y1 != prevy) - ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); - - - prevx = x2; - prevy = y2; - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - } - - ctx.stroke(); + k+=3; + } + + + + + if (series.lines.lineWidth>0){ //Let's draw line in separate series + var newSerie=$.extend({},series); + newSerie.lines=$.extend({},series.lines); + newSerie.lines.fill=undefined; + newSerie.label=undefined; + newSerie.lines.curved=false; //Don't redo curve point calculation as datapoint is copied to this new serie + //We find our series to add the line just after the fill (so other series you wanted above this one will still be) + var allSeries=plot.getData(); + for (i=0;i0){ + datapoints.points=calculateCurvePoints2(datapoints, series.curvedLines,1); + datapoints.pointsize=2; + } + } + } + + + //no real idea whats going on here code mainly from https://code.google.com/p/flot/issues/detail?id=226 + //I don't understand what nergal computes here and to be honest I didn't even try + function calculateCurvePoints2(datapoints, curvedLinesOptions,yPos) { - //no real idea whats going on here code mainly from https://code.google.com/p/flot/issues/detail?id=226 - //I don't understand what nergal computes here and to be honest I didn't even try - function calculateCurvePoints(data, curvedLinesOptions,yPos) { - - var num = curvedLinesOptions.curvePointFactor * data.length; + var points = datapoints.points, ps = datapoints.pointsize; + var num = curvedLinesOptions.curvePointFactor * (points.length/ps); + var xdata = new Array; var ydata = new Array; @@ -439,39 +201,41 @@ _____________________________________________________ var neigh = curvedLinesOptions.fitPointDist; var j = 0; - for (var i = 0; i < data.length; i++) { + for (var i = 0; i < points.length; i+=ps) { var front = new Array; var back = new Array; + var curX=i; + var curY=i+yPos; //smooth front - front[X] = data[i][X] - 0.1; - if (i > 0) { - front[Y] = data[i-1][Y] * neigh + data[i][Y] * (1 - neigh); + front[X] = points[curX] - 0.1; + if (i >= ps) { + front[Y] = points[curY-ps] * neigh + points[curY] * (1 - neigh); } else { - front[Y] = data[i][Y]; + front[Y] = points[curY]; } //smooth back - back[X] = data[i][X] + 0.1; - if ((i + 1) < data.length) { - back[Y] = data[i+1][Y] * neigh + data[i][Y] * (1 - neigh); + back[X] = points[curX] + 0.1; + if ((i + ps) < points.length) { + back[Y] = points[curY+ps] * neigh + points[curY] * (1 - neigh); } else { - back[Y] = data[i][Y]; + back[Y] = points[curY]; } //test for a saddle - if ((front[Y] <= data[i][Y] && back[Y] <= data[i][Y]) || //max or partial horizontal - (front[Y] >= data[i][Y] && back[Y] >= data[i][Y])) { //min or partial horizontal + if ((front[Y] <= points[curY] && back[Y] <= points[curY]) || //max or partial horizontal + (front[Y] >= points[curY] && back[Y] >= points[curY])) { //min or partial horizontal //add curve points xdata[j] = front[X]; ydata[j] = front[Y]; j++; - xdata[j] = data[i][X]; - ydata[j] = data[i][Y]; + xdata[j] = points[curX]; + ydata[j] = points[curY]; j++; xdata[j] = back[X]; @@ -479,8 +243,8 @@ _____________________________________________________ j++; } else { //saddle //use original point only - xdata[j] = data[i][X]; - ydata[j] = data[i][Y]; + xdata[j] = points[curX]; + ydata[j] = points[curY]; j++; } @@ -489,8 +253,8 @@ _____________________________________________________ } else { //just use the datapoints for (var i = 0; i < data.length; i++) { - xdata[i] = data[i][X]; - ydata[i] = data[i][Y]; + xdata[i] = points[curX]; + ydata[i] = points[curY]; } } @@ -575,7 +339,7 @@ _____________________________________________________ init: init, options: options, name: 'curvedLines', - version: '0.4' + version: '0.5' }); From 0c30f6cbf8f5f3a03518232e9e2b8c0da64643d3 Mon Sep 17 00:00:00 2001 From: Thomas-git Date: Tue, 18 Dec 2012 18:52:25 +1100 Subject: [PATCH 03/10] Update curvedLines.js --- curvedLines.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/curvedLines.js b/curvedLines.js index f672d73..67453c8 100644 --- a/curvedLines.js +++ b/curvedLines.js @@ -159,6 +159,9 @@ _____________________________________________________ newSerie.lines=$.extend({},series.lines); newSerie.lines.fill=undefined; newSerie.label=undefined; + newSerie.datapoints={};//Redefine datapoints to top only (else it can have null values which will open the cruve !) + newSerie.datapoints.points=pointsTop; + newSerie.datapoints.pointsize=2; newSerie.lines.curved=false; //Don't redo curve point calculation as datapoint is copied to this new serie //We find our series to add the line just after the fill (so other series you wanted above this one will still be) var allSeries=plot.getData(); From ec3a333f1eed069a3196fc60be0d294a701224c5 Mon Sep 17 00:00:00 2001 From: MichaelZinsmaier Date: Sun, 30 Dec 2012 19:09:51 +0100 Subject: [PATCH 04/10] - switched to data hook - removed require.js stuff - moved options from line: {} back to curvedLine: {} - resolved a bug with the fit option - updated examples --- curvedLines.js | 690 ++++++++++++++++++++-------------------- example.js | 11 +- exampleFillMultiAxis.js | 12 +- exampleFit.js | 11 +- 4 files changed, 357 insertions(+), 367 deletions(-) diff --git a/curvedLines.js b/curvedLines.js index 67453c8..af2a5ae 100644 --- a/curvedLines.js +++ b/curvedLines.js @@ -1,353 +1,347 @@ /* The MIT License -Copyright (c) 2011 by Michael Zinsmaier and nergal.dev -Copyright (c) 2012 by Thomas Ritou - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -*/ -define(['jquery','jquery.flot'],function(jQuery,jflot){ - -/* - -____________________________________________________ - -what it is: -____________________________________________________ - -curvedLines is a plugin for flot, that tries to display lines in a smoother way. -The plugin is based on nergal.dev's work https://code.google.com/p/flot/issues/detail?id=226 -and further extended with a mode that forces the min/max points of the curves to be on the -points. Both modes are achieved through adding of more data points -=> 1) with large data sets you may get trouble -=> 2) if you want to display the points too, you have to plot them as 2nd data series over the lines - -This is version 0.5 of curvedLines so it will probably not work in every case. However -the basic form of use descirbed next works (: - -Feel free to further improve the code - -____________________________________________________ - -how to use it: -____________________________________________________ - - - var d1 = [[5,5],[7,3],[9,12]]; - - var options = { series: { curvedLines: { active: true }}}; - - - $.plot($("#placeholder"), [{data = d1, lines: { show: true,curved:true}}], options); - -_____________________________________________________ - -options: -_____________________________________________________ - - - active: bool true => plugin can be used - curved: bool true => series will be drawn as curved line - fit: bool true => forces the max,mins of the curve to be on the datapoints - curvePointFactor int defines how many "virtual" points are used per "real" data point to - emulate the curvedLines - fitPointDist: int defines the x axis distance of the additional two points that are used - to enforce the min max condition. (you will get curvePointFactor * 3 * |datapoints| - "virtual" points if fit is true) - - -*/ - -/* - * v0.1 initial commit - * v0.15 negative values should work now (outcommented a negative -> 0 hook hope it does no harm) - * v0.2 added fill option (thanks to monemihir) and multi axis support (thanks to soewono effendi) - * v0.3 improved saddle handling and added basic handling of Dates - * v0.4 rewritten fill option (thomas ritou) mostly from original flot code (now fill between points rather than to graph bottom), corrected fill Opacity bug - * v0.5 completly rewritten to let flot do the hard work. CurvedLines usage changed and will require code change but is more straightforward to use for new users (so all flot lines options (gradient fill, shadow are now supported) + Copyright (c) 2011 by Michael Zinsmaier and nergal.dev + Copyright (c) 2012 by Thomas Ritou + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. */ -(function ($) { - - - - var options = { series: { curvedLines: { active: false, - show: false, - fit: false, - fill: false, - fillColor: null, - lineWidth: 2, - curvePointFactor: 20, - fitPointDist: 0.0001 - }}}; - - - function init(plot) { - - plot.hooks.processOptions.push(processOptions); - - //if the plugin is active register processDatapoints method - function processOptions(plot,options) { - if (options.series.curvedLines.active) { - - plot.hooks.processDatapoints.push(processDatapoints); - - } - } - - function processDatapoints(plot, series, datapoints) { - if (series.lines.curved==true){ - if (series.lines.fill){ - - var pointsTop=calculateCurvePoints2(datapoints, series.curvedLines,1); - - //Make sure we've got a second y point for filling area - for (var j=0;j0){ //Let's draw line in separate series - var newSerie=$.extend({},series); - newSerie.lines=$.extend({},series.lines); - newSerie.lines.fill=undefined; - newSerie.label=undefined; - newSerie.datapoints={};//Redefine datapoints to top only (else it can have null values which will open the cruve !) - newSerie.datapoints.points=pointsTop; - newSerie.datapoints.pointsize=2; - newSerie.lines.curved=false; //Don't redo curve point calculation as datapoint is copied to this new serie - //We find our series to add the line just after the fill (so other series you wanted above this one will still be) - var allSeries=plot.getData(); - for (i=0;i0){ - datapoints.points=calculateCurvePoints2(datapoints, series.curvedLines,1); - datapoints.pointsize=2; - } - } - } - - - - //no real idea whats going on here code mainly from https://code.google.com/p/flot/issues/detail?id=226 - //I don't understand what nergal computes here and to be honest I didn't even try - function calculateCurvePoints2(datapoints, curvedLinesOptions,yPos) { - - var points = datapoints.points, ps = datapoints.pointsize; - var num = curvedLinesOptions.curvePointFactor * (points.length/ps); - - var xdata = new Array; - var ydata = new Array; - - var X = 0; - var Y = yPos; - - if (curvedLinesOptions.fit) { - //insert a point before and after the "real" data point to force the line - //to have a max,min at the data point however only if it is a lowest or highest point of the - //curve => avoid saddles - var neigh = curvedLinesOptions.fitPointDist; - var j = 0; - - for (var i = 0; i < points.length; i+=ps) { - - var front = new Array; - var back = new Array; - var curX=i; - var curY=i+yPos; - - //smooth front - front[X] = points[curX] - 0.1; - if (i >= ps) { - front[Y] = points[curY-ps] * neigh + points[curY] * (1 - neigh); - } else { - front[Y] = points[curY]; - } - - - //smooth back - back[X] = points[curX] + 0.1; - if ((i + ps) < points.length) { - back[Y] = points[curY+ps] * neigh + points[curY] * (1 - neigh); - } else { - back[Y] = points[curY]; - } - - //test for a saddle - if ((front[Y] <= points[curY] && back[Y] <= points[curY]) || //max or partial horizontal - (front[Y] >= points[curY] && back[Y] >= points[curY])) { //min or partial horizontal - - //add curve points - xdata[j] = front[X]; - ydata[j] = front[Y]; - j++; - - xdata[j] = points[curX]; - ydata[j] = points[curY]; - j++; - - xdata[j] = back[X]; - ydata[j] = back[Y]; - j++; - } else { //saddle - //use original point only - xdata[j] = points[curX]; - ydata[j] = points[curY]; - j++; - } - - - } - } else { - //just use the datapoints - for (var i = 0; i < data.length; i++) { - xdata[i] = points[curX]; - ydata[i] = points[curY]; - } - } - - var n = xdata.length; - - var y2 = new Array(); - var delta = new Array(); - y2[0] = 0; - y2[n - 1] = 0; - delta[0] = 0; - - for(var i = 1; i < n - 1; ++i) { - var d = (xdata[i + 1] - xdata[i - 1]); - if(d == 0) { - return null; - } - - var s = (xdata[i] - xdata[i - 1]) / d; - var p = s * y2[i - 1] + 2; - y2[i] = (s - 1) / p; - delta[i] = (ydata[i + 1] - ydata[i]) / (xdata[i + 1] - xdata[i]) - (ydata[i] - ydata[i - 1]) / (xdata[i] - xdata[i - 1]); - delta[i] = (6 * delta[i] / (xdata[i + 1] - xdata[i - 1]) - s * delta[i - 1]) / p; - } - - for(var j = n - 2; j >= 0; --j) { - y2[j] = y2[j] * y2[j + 1] + delta[j]; - } - - var step = (xdata[n - 1] - xdata[0]) / (num - 1); - - var xnew = new Array; - var ynew = new Array; - var result = new Array; - - xnew[0] = xdata[0]; - ynew[0] = ydata[0]; - - - for(j = 1; j < num; ++j) { - xnew[j] = xnew[0] + j * step; - - var max = n - 1; - var min = 0; - - while(max - min > 1) { - var k = Math.round((max + min) / 2); - if(xdata[k] > xnew[j]) { - max = k; - } else { - min = k; - } - } - - var h = (xdata[max] - xdata[min]); - - if(h == 0) { - return null; - } - - var a = (xdata[max] - xnew[j]) / h; - var b = (xnew[j] - xdata[min]) / h; - - ynew[j] = a * ydata[min] + b * ydata[max] + ((a * a * a - a) * y2[min] + (b * b * b - b) * y2[max]) * (h * h) / 6; -// if (ynew[j] < 0.01){ -// ynew[j] = 0; -// } - result.push(xnew[j]); - result.push(ynew[j]); - } - - return result; - } - - - - - }//end init - - $.plot.plugincurvedLines=true; - - $.plot.plugins.push({ - init: init, - options: options, - name: 'curvedLines', - version: '0.5' - }); - - -})(jQuery); - -return jQuery.plot.plugincurvedLines; + /* + + ____________________________________________________ + + what it is: + ____________________________________________________ + + curvedLines is a plugin for flot, that tries to display lines in a smoother way. + The plugin is based on nergal.dev's work https://code.google.com/p/flot/issues/detail?id=226 + and further extended with a mode that forces the min/max points of the curves to be on the + points. Both modes are achieved through adding of more data points + => 1) with large data sets you may get trouble + => 2) if you want to display the points too, you have to plot them as 2nd data series over the lines + + This is version 0.5 of curvedLines so it will probably not work in every case. However + the basic form of use descirbed next works (: + + Feel free to further improve the code + + ____________________________________________________ + + how to use it: + ____________________________________________________ + + var d1 = [[5,5],[7,3],[9,12]]; + + var options = { series: { curvedLines: { active: true }}}; + + $.plot($("#placeholder"), [{data = d1, lines: { show: true}, curvedLines: {apply: true}}], options); + + _____________________________________________________ + + options: + _____________________________________________________ + + active: bool true => plugin can be used + apply: bool true => series will be drawn as curved line + fit: bool true => forces the max,mins of the curve to be on the datapoints + curvePointFactor int defines how many "virtual" points are used per "real" data point to + emulate the curvedLines + fitPointDist: int defines the x axis distance of the additional two points that are used + to enforce the min max condition. (you will get curvePointFactor * 3 * |datapoints| + "virtual" points if fit is true) + + + line options (since v0.5 curved lines use flots line implementation for drawing + => line options like fill, show ... are supported out of the box) + + */ + + /* + * v0.1 initial commit + * v0.15 negative values should work now (outcommented a negative -> 0 hook hope it does no harm) + * v0.2 added fill option (thanks to monemihir) and multi axis support (thanks to soewono effendi) + * v0.3 improved saddle handling and added basic handling of Dates + * v0.4 rewritten fill option (thomas ritou) mostly from original flot code (now fill between points rather than to graph bottom), corrected fill Opacity bug + * v0.5 rewritten instead of implementing a own draw function CurvedLines is now based on the processDatapoints flot hook (credits go to thomas ritou). + * This change breakes existing code however CurvedLines are now just many tiny straight lines to flot and therefore all flot lines options (like gradient fill, + * shadow) are now supported out of the box + */ + + (function($) { + + var options = { + series : { + curvedLines : { + active : false, + apply: false, + fit : false, + curvePointFactor : 20, + fitPointDist : 0.0001 + } + } + }; + + function init(plot) { + + plot.hooks.processOptions.push(processOptions); + + //if the plugin is active register processDatapoints method + function processOptions(plot, options) { + + if (options.series.curvedLines.active) { + plot.hooks.processDatapoints.push(processDatapoints); + } + } + + //only if the plugin is active + function processDatapoints(plot, series, datapoints) { + if (series.curvedLines.apply == true) { + if (series.lines.fill) { + + var pointsTop = calculateCurvePoints(datapoints, series.curvedLines, 1); + + //Make sure we've got a second y point for filling area + for (var j = 0; j < datapoints.length; j += 3) { + if (data[j] == null) + data[j] = series.yaxis.min; + //If second y point is null, let it be zero (else no curve !) + } + var pointsBottom = calculateCurvePoints(datapoints, series.curvedLines, 2); + + //Merge top and bottom curve + datapoints.pointsize = 3; + datapoints.points = []; + var j = 0; + var k = 0; + var i = 0; + var ps = 2; + while (i < pointsTop.length || j < pointsBottom.length) { + if (pointsTop[i] == pointsBottom[j]) { + datapoints.points[k] = pointsTop[i]; + datapoints.points[k + 1] = pointsTop[i + 1]; + datapoints.points[k + 2] = pointsBottom[j + 1]; + j += ps; + i += ps; + + } else if (pointsTop[i] < pointsBottom[j]) { + datapoints.points[k] = pointsTop[i]; + datapoints.points[k + 1] = pointsTop[i + 1]; + datapoints.points[k + 2] = null; + i += ps; + } else { + datapoints.points[k] = pointsBottom[j]; + datapoints.points[k + 1] = null; + datapoints.points[k + 2] = pointsBottom[j + 1]; + j += ps; + } + k += 3; + } + + if (series.lines.lineWidth > 0) {//Let's draw line in separate series + var newSerie = $.extend({}, series); + newSerie.lines = $.extend({}, series.lines); + newSerie.lines.fill = undefined; + newSerie.label = undefined; + newSerie.datapoints = {}; + //Redefine datapoints to top only (else it can have null values which will open the cruve !) + newSerie.datapoints.points = pointsTop; + newSerie.datapoints.pointsize = 2; + newSerie.lines.curved = false; + //Don't redo curve point calculation as datapoint is copied to this new serie + //We find our series to add the line just after the fill (so other series you wanted above this one will still be) + var allSeries = plot.getData(); + for ( i = 0; i < allSeries.length; i++) { + if (allSeries[i] == series) { + plot.getData().splice(i + 1, 0, newSerie); + break; + } + } + + series.lines.lineWidth = 0; + } + + } else if (series.lines.lineWidth > 0) { + datapoints.points = calculateCurvePoints(datapoints, series.curvedLines, 1); + datapoints.pointsize = 2; + } + } + } + + //no real idea whats going on here code mainly from https://code.google.com/p/flot/issues/detail?id=226 + //if fit option is selected additional datapoints get inserted before the curve calculations in nergal.dev s code. + function calculateCurvePoints(datapoints, curvedLinesOptions, yPos) { + + var points = datapoints.points, ps = datapoints.pointsize; + var num = curvedLinesOptions.curvePointFactor * (points.length / ps); + + var xdata = new Array; + var ydata = new Array; + + var X = 0; + var Y = yPos; + + var curX = -1; + var curY = -1; + var j = 0; + + if (curvedLinesOptions.fit) { + //insert a point before and after the "real" data point to force the line + //to have a max,min at the data point however only if it is a lowest or highest point of the + //curve => avoid saddles + var neigh = curvedLinesOptions.fitPointDist; + + for (var i = 0; i < points.length; i += ps) { + + var front = new Array; + var back = new Array; + curX = i; + curY = i + yPos; + + //smooth front + front[X] = points[curX] - 0.1; + if (i >= ps) { + front[Y] = points[curY - ps] * neigh + points[curY] * (1 - neigh); + } else { + front[Y] = points[curY]; + } + + //smooth back + back[X] = points[curX] + 0.1; + if ((i + ps) < points.length) { + back[Y] = points[curY + ps] * neigh + points[curY] * (1 - neigh); + } else { + back[Y] = points[curY]; + } + + //test for a saddle + if ((front[Y] <= points[curY] && back[Y] <= points[curY]) || //max or partial horizontal + (front[Y] >= points[curY] && back[Y] >= points[curY])) {//min or partial horizontal + + //add curve points + xdata[j] = front[X]; + ydata[j] = front[Y]; + j++; + + xdata[j] = points[curX]; + ydata[j] = points[curY]; + j++; + + xdata[j] = back[X]; + ydata[j] = back[Y]; + j++; + } else {//saddle + //use original point only + xdata[j] = points[curX]; + ydata[j] = points[curY]; + j++; + } + + } + } else { + //just use the datapoints + for (var i = 0; i < points.length; i += ps) { + curX = i; + curY = i + yPos; + + xdata[j] = points[curX]; + ydata[j] = points[curY]; + j++; + } + } + + var n = xdata.length; + + var y2 = new Array(); + var delta = new Array(); + y2[0] = 0; + y2[n - 1] = 0; + delta[0] = 0; + + for (var i = 1; i < n - 1; ++i) { + var d = (xdata[i + 1] - xdata[i - 1]); + if (d == 0) { + return null; + } + + var s = (xdata[i] - xdata[i - 1]) / d; + var p = s * y2[i - 1] + 2; + y2[i] = (s - 1) / p; + delta[i] = (ydata[i + 1] - ydata[i]) / (xdata[i + 1] - xdata[i]) - (ydata[i] - ydata[i - 1]) / (xdata[i] - xdata[i - 1]); + delta[i] = (6 * delta[i] / (xdata[i + 1] - xdata[i - 1]) - s * delta[i - 1]) / p; + } + + for (var j = n - 2; j >= 0; --j) { + y2[j] = y2[j] * y2[j + 1] + delta[j]; + } + + var step = (xdata[n - 1] - xdata[0]) / (num - 1); + + var xnew = new Array; + var ynew = new Array; + var result = new Array; + + xnew[0] = xdata[0]; + ynew[0] = ydata[0]; + + for ( j = 1; j < num; ++j) { + xnew[j] = xnew[0] + j * step; + + var max = n - 1; + var min = 0; + + while (max - min > 1) { + var k = Math.round((max + min) / 2); + if (xdata[k] > xnew[j]) { + max = k; + } else { + min = k; + } + } + + var h = (xdata[max] - xdata[min]); + + if (h == 0) { + return null; + } + + var a = (xdata[max] - xnew[j]) / h; + var b = (xnew[j] - xdata[min]) / h; + + ynew[j] = a * ydata[min] + b * ydata[max] + ((a * a * a - a) * y2[min] + (b * b * b - b) * y2[max]) * (h * h) / 6; + + result.push(xnew[j]); + result.push(ynew[j]); + } + + return result; + } + + }//end init + + + $.plot.plugincurvedLines = true; + + $.plot.plugins.push({ + init : init, + options : options, + name : 'curvedLines', + version : '0.5' + }); + + })(jQuery); -}); diff --git a/example.js b/example.js index a102006..91126d9 100644 --- a/example.js +++ b/example.js @@ -1,8 +1,8 @@ -
- - \ No newline at end of file + $.plot($("#flotOrig"), [{data: d1, lines: { show: true, lineWidth: 3}, curvedLines: {apply:true}}, {data: d1, points: { show: true }}], options); +}); \ No newline at end of file diff --git a/exampleFillMultiAxis.js b/exampleFillMultiAxis.js index f63ada2..39e1c38 100644 --- a/exampleFillMultiAxis.js +++ b/exampleFillMultiAxis.js @@ -1,8 +1,7 @@ -
- - \ No newline at end of file + }); \ No newline at end of file diff --git a/exampleFit.js b/exampleFit.js index fc37d66..bfe50ee 100644 --- a/exampleFit.js +++ b/exampleFit.js @@ -1,7 +1,7 @@ -
- - \ No newline at end of file + $.plot($("#flotFit"), [{data: d1, lines: { show: true, lineWidth: 3}, curvedLines: {apply:true, fit: true, fitPointDist: 0.000001}}, {data: d1, points: { show: true }}], options); +}); \ No newline at end of file From 70eb03dd745bae3a3834056d241a11ac04e0ec7c Mon Sep 17 00:00:00 2001 From: MichaelZinsmaier Date: Sun, 30 Dec 2012 19:15:39 +0100 Subject: [PATCH 05/10] resolved the "first datapoint is not properly connected" issue --- curvedLines.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/curvedLines.js b/curvedLines.js index af2a5ae..aa8fa12 100644 --- a/curvedLines.js +++ b/curvedLines.js @@ -298,6 +298,9 @@ xnew[0] = xdata[0]; ynew[0] = ydata[0]; + result.push(xnew[0]); + result.push(ynew[0]); + for ( j = 1; j < num; ++j) { xnew[j] = xnew[0] + j * step; From 79a6780f176863e5de297669ddf6588ff08c37b6 Mon Sep 17 00:00:00 2001 From: Thomas Ritou Date: Tue, 1 Jan 2013 17:58:52 +1100 Subject: [PATCH 06/10] Remove some more requirejs stuff --- curvedLines.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/curvedLines.js b/curvedLines.js index aa8fa12..0c888e2 100644 --- a/curvedLines.js +++ b/curvedLines.js @@ -336,9 +336,6 @@ }//end init - - $.plot.plugincurvedLines = true; - $.plot.plugins.push({ init : init, options : options, From 19c9c958e5e898d3466de09037b061bd4b0a80ec Mon Sep 17 00:00:00 2001 From: Thomas Ritou Date: Tue, 1 Jan 2013 18:38:48 +1100 Subject: [PATCH 07/10] Fix curvedLines.apply options scheme Prevent calculateCurvePoints from being called twice when filling & drawing line --- curvedLines.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curvedLines.js b/curvedLines.js index 0c888e2..3f325ca 100644 --- a/curvedLines.js +++ b/curvedLines.js @@ -161,7 +161,7 @@ //Redefine datapoints to top only (else it can have null values which will open the cruve !) newSerie.datapoints.points = pointsTop; newSerie.datapoints.pointsize = 2; - newSerie.lines.curved = false; + newSerie.curvedLines.apply = false; //Don't redo curve point calculation as datapoint is copied to this new serie //We find our series to add the line just after the fill (so other series you wanted above this one will still be) var allSeries = plot.getData(); From b9532aafc7811492543a7fb8f1adade613827c90 Mon Sep 17 00:00:00 2001 From: Thomas Ritou Date: Tue, 1 Jan 2013 19:05:37 +1100 Subject: [PATCH 08/10] Remove useless check Flot is already doing it by itself --- curvedLines.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/curvedLines.js b/curvedLines.js index 3f325ca..517dcb5 100644 --- a/curvedLines.js +++ b/curvedLines.js @@ -113,15 +113,8 @@ if (series.curvedLines.apply == true) { if (series.lines.fill) { - var pointsTop = calculateCurvePoints(datapoints, series.curvedLines, 1); - - //Make sure we've got a second y point for filling area - for (var j = 0; j < datapoints.length; j += 3) { - if (data[j] == null) - data[j] = series.yaxis.min; - //If second y point is null, let it be zero (else no curve !) - } - var pointsBottom = calculateCurvePoints(datapoints, series.curvedLines, 2); + var pointsTop = calculateCurvePoints(datapoints, series.curvedLines, 1) + ,pointsBottom = calculateCurvePoints(datapoints, series.curvedLines, 2); //flot makes sur for us that we've got a second y point if fill is true ! //Merge top and bottom curve datapoints.pointsize = 3; From 2c887a417561c5f7f86a13afadf70fbc225a776e Mon Sep 17 00:00:00 2001 From: Thomas Ritou Date: Tue, 1 Jan 2013 19:15:19 +1100 Subject: [PATCH 09/10] Improve handling of filled curvedLines No more holes is the curve when there is not the amount of points in top and bottom curved line. --- curvedLines.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/curvedLines.js b/curvedLines.js index 517dcb5..7612702 100644 --- a/curvedLines.js +++ b/curvedLines.js @@ -134,11 +134,11 @@ } else if (pointsTop[i] < pointsBottom[j]) { datapoints.points[k] = pointsTop[i]; datapoints.points[k + 1] = pointsTop[i + 1]; - datapoints.points[k + 2] = null; + datapoints.points[k + 2] = k > 0 ? datapoints.points[k-1] : null; i += ps; } else { datapoints.points[k] = pointsBottom[j]; - datapoints.points[k + 1] = null; + datapoints.points[k + 1] = k > 1 ? datapoints.points[k-2] : null; datapoints.points[k + 2] = pointsBottom[j + 1]; j += ps; } From 3c5463a073816a1d81d3f57a52f398b2ff83b9c1 Mon Sep 17 00:00:00 2001 From: MichaelZinsmaier Date: Fri, 4 Jan 2013 14:03:51 +0100 Subject: [PATCH 10/10] fixed the saddle issue (hopefully as it should be) --- curvedLines.js | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/curvedLines.js b/curvedLines.js index 7612702..1ad40f9 100644 --- a/curvedLines.js +++ b/curvedLines.js @@ -196,7 +196,7 @@ //insert a point before and after the "real" data point to force the line //to have a max,min at the data point however only if it is a lowest or highest point of the //curve => avoid saddles - var neigh = curvedLinesOptions.fitPointDist; + var fpDist = curvedLinesOptions.fitPointDist; for (var i = 0; i < points.length; i += ps) { @@ -205,25 +205,28 @@ curX = i; curY = i + yPos; - //smooth front - front[X] = points[curX] - 0.1; + //add point to front + front[X] = points[curX] - fpDist; + front[Y] = points[curY]; + + //add point to back + back[X] = points[curX] + fpDist; + back[Y] = points[curY]; + + + //get points (front and back) Y value for saddle test + var frontPointY = points[curY]; + var backPointY = points[curY]; if (i >= ps) { - front[Y] = points[curY - ps] * neigh + points[curY] * (1 - neigh); - } else { - front[Y] = points[curY]; + frontPointY = points[curY - ps]; } - - //smooth back - back[X] = points[curX] + 0.1; if ((i + ps) < points.length) { - back[Y] = points[curY + ps] * neigh + points[curY] * (1 - neigh); - } else { - back[Y] = points[curY]; + backPointY = points[curY + ps]; } //test for a saddle - if ((front[Y] <= points[curY] && back[Y] <= points[curY]) || //max or partial horizontal - (front[Y] >= points[curY] && back[Y] >= points[curY])) {//min or partial horizontal + if ((frontPointY <= points[curY] && backPointY <= points[curY]) || //max or partial horizontal + (frontPointY >= points[curY] && backPointY >= points[curY])) {//min or partial horizontal //add curve points xdata[j] = front[X];