Commit cf5eb584 authored by Thomas-git's avatar Thomas-git

Nearly complete rewrite

Now let flot do the hard work. So all lines options are supported (date axis, shadow, gradient fill, ...)
parent 56b37176
define(['jquery','jquery.flot'],function(jQuery,jflot){
/* The MIT License /* The MIT License
Copyright (c) 2011 by Michael Zinsmaier and nergal.dev 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, ...@@ -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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
*/ */
define(['jquery','jquery.flot'],function(jQuery,jflot){
/* /*
...@@ -38,7 +37,7 @@ points. Both modes are achieved through adding of more data points ...@@ -38,7 +37,7 @@ points. Both modes are achieved through adding of more data points
=> 1) with large data sets you may get trouble => 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 => 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 (: the basic form of use descirbed next works (:
Feel free to further improve the code Feel free to further improve the code
...@@ -54,19 +53,17 @@ ____________________________________________________ ...@@ -54,19 +53,17 @@ ____________________________________________________
var options = { series: { curvedLines: { active: true }}}; 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: 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 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 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 curvePointFactor int defines how many "virtual" points are used per "real" data point to
emulate the curvedLines emulate the curvedLines
fitPointDist: int defines the x axis distance of the additional two points that are used fitPointDist: int defines the x axis distance of the additional two points that are used
...@@ -82,7 +79,7 @@ _____________________________________________________ ...@@ -82,7 +79,7 @@ _____________________________________________________
* v0.2 added fill option (thanks to monemihir) and multi axis support (thanks to soewono effendi) * 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.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.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 ($) { (function ($) {
...@@ -104,328 +101,93 @@ _____________________________________________________ ...@@ -104,328 +101,93 @@ _____________________________________________________
plot.hooks.processOptions.push(processOptions); 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) { function processOptions(plot,options) {
if (options.series.curvedLines.active) { if (options.series.curvedLines.active) {
plot.hooks.draw.push(draw);
} plot.hooks.processDatapoints.push(processDatapoints);
}
//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;j<data.length;j++){
if (data[j].length==2) data[j][2]=0;
}
var pointsFill = calculateCurvePoints(data, series.curvedLines,2);
plotLineArea(ctx,points,pointsFill,axisx,axisy);
}
if (series.curvedLines.lineWidth>0)
plotLine(ctx, points, axisx, axisy,2);
ctx.restore();
}
} }
} }
//helper method that convertes Dates to a numeric representation function processDatapoints(plot, series, datapoints) {
function getTimeFromDate(timeElement) { if (series.lines.curved==true){
var xVal = timeElement[0]; if (series.lines.fill){
var yVal = timeElement[1];
var ret = new Array; var pointsTop=calculateCurvePoints2(datapoints, series.curvedLines,1);
if (timeElement[0] instanceof Date) { //Make sure we've got a second y point for filling area
ret[0] = xVal.getTime(); for (var j=0;j<datapoints.length;j+=3){
} else { if (data[j]==null) data[j]=series.yaxis.min; //If second y point is null, let it be zero (else no curve !)
ret[0] = xVal; }
} var pointsBottom = calculateCurvePoints2(datapoints, series.curvedLines,2);
if (timeElement[1] instanceof Date) { //Merge top and bottom curve
ret[1] = yVal.getTime(); datapoints.pointsize=3;
} else { datapoints.points=[];
ret[1] = yVal; var j=0;
} var k=0;
var i=0;
return ret; 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;
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; if (series.lines.lineWidth>0){ //Let's draw line in separate series
var newSerie=$.extend({},series);
// we process each segment in two turns, first forward newSerie.lines=$.extend({},series.lines);
// direction to sketch out top, then once we hit the newSerie.lines.fill=undefined;
// end we go backwards to sketch the bottom newSerie.label=undefined;
while (true) { newSerie.lines.curved=false; //Don't redo curve point calculation as datapoint is copied to this new serie
if (ps > 0 && i > points.length + ps) //We find our series to add the line just after the fill (so other series you wanted above this one will still be)
break; var allSeries=plot.getData();
for (i=0;i<allSeries.length;i++){
i += ps; // ps is negative if going backwards if (allSeries[i]==series){
plot.getData().splice(i+1,0,newSerie);
var x1 = points[i - ps], break;
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();
} }
series.lines.lineWidth=0;
}
}else if (series.lines.lineWidth>0){
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 var points = datapoints.points, ps = datapoints.pointsize;
//I don't understand what nergal computes here and to be honest I didn't even try var num = curvedLinesOptions.curvePointFactor * (points.length/ps);
function calculateCurvePoints(data, curvedLinesOptions,yPos) {
var num = curvedLinesOptions.curvePointFactor * data.length;
var xdata = new Array; var xdata = new Array;
var ydata = new Array; var ydata = new Array;
...@@ -439,39 +201,41 @@ _____________________________________________________ ...@@ -439,39 +201,41 @@ _____________________________________________________
var neigh = curvedLinesOptions.fitPointDist; var neigh = curvedLinesOptions.fitPointDist;
var j = 0; 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 front = new Array;
var back = new Array; var back = new Array;
var curX=i;
var curY=i+yPos;
//smooth front //smooth front
front[X] = data[i][X] - 0.1; front[X] = points[curX] - 0.1;
if (i > 0) { if (i >= ps) {
front[Y] = data[i-1][Y] * neigh + data[i][Y] * (1 - neigh); front[Y] = points[curY-ps] * neigh + points[curY] * (1 - neigh);
} else { } else {
front[Y] = data[i][Y]; front[Y] = points[curY];
} }
//smooth back //smooth back
back[X] = data[i][X] + 0.1; back[X] = points[curX] + 0.1;
if ((i + 1) < data.length) { if ((i + ps) < points.length) {
back[Y] = data[i+1][Y] * neigh + data[i][Y] * (1 - neigh); back[Y] = points[curY+ps] * neigh + points[curY] * (1 - neigh);
} else { } else {
back[Y] = data[i][Y]; back[Y] = points[curY];
} }
//test for a saddle //test for a saddle
if ((front[Y] <= data[i][Y] && back[Y] <= data[i][Y]) || //max or partial horizontal if ((front[Y] <= points[curY] && back[Y] <= points[curY]) || //max or partial horizontal
(front[Y] >= data[i][Y] && back[Y] >= data[i][Y])) { //min or partial horizontal (front[Y] >= points[curY] && back[Y] >= points[curY])) { //min or partial horizontal
//add curve points //add curve points
xdata[j] = front[X]; xdata[j] = front[X];
ydata[j] = front[Y]; ydata[j] = front[Y];
j++; j++;
xdata[j] = data[i][X]; xdata[j] = points[curX];
ydata[j] = data[i][Y]; ydata[j] = points[curY];
j++; j++;
xdata[j] = back[X]; xdata[j] = back[X];
...@@ -479,8 +243,8 @@ _____________________________________________________ ...@@ -479,8 +243,8 @@ _____________________________________________________
j++; j++;
} else { //saddle } else { //saddle
//use original point only //use original point only
xdata[j] = data[i][X]; xdata[j] = points[curX];
ydata[j] = data[i][Y]; ydata[j] = points[curY];
j++; j++;
} }
...@@ -489,8 +253,8 @@ _____________________________________________________ ...@@ -489,8 +253,8 @@ _____________________________________________________
} else { } else {
//just use the datapoints //just use the datapoints
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
xdata[i] = data[i][X]; xdata[i] = points[curX];
ydata[i] = data[i][Y]; ydata[i] = points[curY];
} }
} }
...@@ -575,7 +339,7 @@ _____________________________________________________ ...@@ -575,7 +339,7 @@ _____________________________________________________
init: init, init: init,
options: options, options: options,
name: 'curvedLines', name: 'curvedLines',
version: '0.4' version: '0.5'
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment