From cf5eb584f38a165fa054a4260bdced0ef85f7e71 Mon Sep 17 00:00:00 2001 From: Thomas-git Date: Tue, 18 Dec 2012 00:10:59 +1100 Subject: [PATCH] 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' });