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
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;j<data.length;j++){
if (data[j].length==2) data[j][2]=0;
}
var pointsFill = calculateCurvePoints(data, series.curvedLines,2);
plot.hooks.processDatapoints.push(processDatapoints);
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 getTimeFromDate(timeElement) {
var xVal = timeElement[0];
var yVal = timeElement[1];
var ret = new Array;
function processDatapoints(plot, series, datapoints) {
if (series.lines.curved==true){
if (series.lines.fill){
if (timeElement[0] instanceof Date) {
ret[0] = xVal.getTime();
} else {
ret[0] = xVal;
}
var pointsTop=calculateCurvePoints2(datapoints, series.curvedLines,1);
if (timeElement[1] instanceof Date) {
ret[1] = yVal.getTime();
} else {
ret[1] = yVal;
}
return ret;
}
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)
//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 = calculateCurvePoints2(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.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;
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));
}
}
series.lines.lineWidth=0;
}
//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 (series.lines.lineWidth>0){
datapoints.points=calculateCurvePoints2(datapoints, series.curvedLines,1);
datapoints.pointsize=2;
}
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();
}
//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) {
function calculateCurvePoints2(datapoints, curvedLinesOptions,yPos) {
var points = datapoints.points, ps = datapoints.pointsize;
var num = curvedLinesOptions.curvePointFactor * (points.length/ps);
var num = curvedLinesOptions.curvePointFactor * data.length;
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'
});
......
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