Commit 3db2c2ee authored by Michael Zinsmaier's avatar Michael Zinsmaier

API break refactored the code to prepare it for a new algorithmic core

parent 3fa41abe
...@@ -22,296 +22,315 @@ ...@@ -22,296 +22,315 @@
THE SOFTWARE. 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
&& 3) consecutive x data points are not allowed to have the same value
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 (points total = real points * curvePointFactor)
fitPointDist: int defines the x axis distance of the additional two points that are used
to enforce the min max condition.
+ 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
* v0.6 flot 0.8 compatibility and some bug fixes
* v0.6.0 changed versioning schema
*/
(function($) {
var options = {
series : {
curvedLines : {
active : false,
apply: false,
fit : false,
curvePointFactor : 20,
fitPointDist : undefined
}
}
};
function init(plot) { ____________________________________________________
plot.hooks.processOptions.push(processOptions); what it is:
____________________________________________________
//if the plugin is active register processDatapoints method curvedLines is a plugin for flot, that tries to display lines in a smoother way.
function processOptions(plot, options) { This is achieved through adding of more data points. The plugin is a data processor and can thus be used
if (options.series.curvedLines.active) { in combination with standard line / point rendering options.
plot.hooks.processDatapoints.unshift(processDatapoints);
} => 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
&& 3) consecutive x data points are not allowed to have the same value
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
deprecated options from flot prior to 1.0.0:
------------------------------------------------
legacyOverride bool true => use old default OR
legacyOverride optionArray
{
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 (points total = real points * curvePointFactor)
fitPointDist: int defines the x axis distance of the additional two points that are used
} to enforce the min max condition.
*/
/*
* 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
* v0.6 flot 0.8 compatibility and some bug fixes
* v0.6.x changed versioning schema
*
* v1.0.0 API Break marked existing implementation/options as deprecated
*/
(function($) {
var options = {
series : {
curvedLines : {
active : false,
apply : false,
monotonicFit : false,
tension : 0.0,
legacyOverride : undefined
} }
}
};
function init(plot) {
plot.hooks.processOptions.push(processOptions);
//only if the plugin is active //if the plugin is active register processDatapoints method
function processDatapoints(plot, series, datapoints) { function processOptions(plot, options) {
var nrPoints = datapoints.points.length / datapoints.pointsize; if (options.series.curvedLines.active) {
var EPSILON = 0.5; //pretty large epsilon but save plot.hooks.processDatapoints.unshift(processDatapoints);
}
if (series.curvedLines.apply == true && series.originSeries === undefined && nrPoints > (1 + EPSILON)) { }
if (series.lines.fill) {
//only if the plugin is active
var pointsTop = calculateCurvePoints(datapoints, series.curvedLines, 1) function processDatapoints(plot, series, datapoints) {
,pointsBottom = calculateCurvePoints(datapoints, series.curvedLines, 2); //flot makes sure for us that we've got a second y point if fill is true ! var nrPoints = datapoints.points.length / datapoints.pointsize;
var EPSILON = 0.005;
//Merge top and bottom curve
datapoints.pointsize = 3; if (series.curvedLines.apply == true && series.originSeries === undefined && nrPoints > (1 + EPSILON)) {
datapoints.points = []; if (series.lines.fill) {
var j = 0;
var k = 0; var pointsTop = calculateCurvePoints(datapoints, series.curvedLines, 1);
var i = 0; var pointsBottom = calculateCurvePoints(datapoints, series.curvedLines, 2);
var ps = 2; //flot makes sure for us that we've got a second y point if fill is true !
while (i < pointsTop.length || j < pointsBottom.length) {
if (pointsTop[i] == pointsBottom[j]) { //Merge top and bottom curve
datapoints.points[k] = pointsTop[i]; datapoints.pointsize = 3;
datapoints.points[k + 1] = pointsTop[i + 1]; datapoints.points = [];
datapoints.points[k + 2] = pointsBottom[j + 1]; var j = 0;
j += ps; var k = 0;
i += ps; var i = 0;
var ps = 2;
} else if (pointsTop[i] < pointsBottom[j]) { while (i < pointsTop.length || j < pointsBottom.length) {
datapoints.points[k] = pointsTop[i]; if (pointsTop[i] == pointsBottom[j]) {
datapoints.points[k + 1] = pointsTop[i + 1]; datapoints.points[k] = pointsTop[i];
datapoints.points[k + 2] = k > 0 ? datapoints.points[k-1] : null; datapoints.points[k + 1] = pointsTop[i + 1];
i += ps; datapoints.points[k + 2] = pointsBottom[j + 1];
} else { j += ps;
datapoints.points[k] = pointsBottom[j]; i += ps;
datapoints.points[k + 1] = k > 1 ? datapoints.points[k-2] : null;
datapoints.points[k + 2] = pointsBottom[j + 1]; } else if (pointsTop[i] < pointsBottom[j]) {
j += ps; datapoints.points[k] = pointsTop[i];
} datapoints.points[k + 1] = pointsTop[i + 1];
k += 3; datapoints.points[k + 2] = k > 0 ? datapoints.points[k - 1] : null;
i += ps;
} else {
datapoints.points[k] = pointsBottom[j];
datapoints.points[k + 1] = k > 1 ? datapoints.points[k - 2] : null;
datapoints.points[k + 2] = pointsBottom[j + 1];
j += ps;
} }
} else if (series.lines.lineWidth > 0) { k += 3;
datapoints.points = calculateCurvePoints(datapoints, series.curvedLines, 1);
datapoints.pointsize = 2;
} }
} else if (series.lines.lineWidth > 0) {
datapoints.points = calculateCurvePoints(datapoints, series.curvedLines, 1);
datapoints.pointsize = 2;
} }
} }
}
function calculateCurvePoints(datapoints, curvedLinesOptions, yPos) {
if (typeof curvedLinesOptions.legacyOverride != 'undefined' && curvedLinesOptions.legacyOverride != false) {
var defaultOptions = {
fit : false,
curvePointFactor : 20,
fitPointDist : undefined
};
var legacyOptions = jQuery.extend(defaultOptions, curvedLinesOptions.legacyOverride);
return calculateLegacyCurvePoints(datapoints, legacyOptions, yPos);
}
return datapoints.points;
}
//no real idea whats going on here code mainly from https://code.google.com/p/flot/issues/detail?id=226 //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. //if fit option is selected additional datapoints get inserted before the curve calculations in nergal.dev s code.
function calculateCurvePoints(datapoints, curvedLinesOptions, yPos) { function calculateLegacyCurvePoints(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 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.
var fpDist;
if(typeof curvedLinesOptions.fitPointDist == 'undefined') {
//estimate it
var minX = points[0];
var maxX = points[points.length-ps];
fpDist = (maxX - minX) / (500 * 100); //x range / (estimated pixel length of placeholder * factor)
} else {
//use user defined value
fpDist = curvedLinesOptions.fitPointDist;
}
for (var i = 0; i < points.length; i += ps) { var points = datapoints.points, ps = datapoints.pointsize;
var num = curvedLinesOptions.curvePointFactor * (points.length / ps);
var frontX;
var backX; var xdata = new Array;
curX = i; var ydata = new Array;
curY = i + yPos;
var curX = -1;
//add point X s var curY = -1;
frontX = points[curX] - fpDist; var j = 0;
backX = points[curX] + fpDist;
if (curvedLinesOptions.fit) {
var factor = 2; //insert a point before and after the "real" data point to force the line
while (frontX == points[curX] || backX == points[curX]) { //to have a max,min at the data point.
//inside the ulp
frontX = points[curX] - (fpDist * factor); var fpDist;
backX = points[curX] + (fpDist * factor); if ( typeof curvedLinesOptions.fitPointDist == 'undefined') {
factor++; //estimate it
} var minX = points[0];
var maxX = points[points.length - ps];
//add curve points fpDist = (maxX - minX) / (500 * 100);
xdata[j] = frontX; //x range / (estimated pixel length of placeholder * factor)
ydata[j] = points[curY];
j++;
xdata[j] = points[curX];
ydata[j] = points[curY];
j++;
xdata[j] = backX;
ydata[j] = points[curY];
j++;
}
} else { } else {
//just use the datapoints //use user defined value
for (var i = 0; i < points.length; i += ps) { fpDist = curvedLinesOptions.fitPointDist;
curX = i;
curY = i + yPos;
xdata[j] = points[curX];
ydata[j] = points[curY];
j++;
}
} }
var n = xdata.length; for (var i = 0; i < points.length; i += ps) {
var y2 = new Array(); var frontX;
var delta = new Array(); var backX;
y2[0] = 0; curX = i;
y2[n - 1] = 0; curY = i + yPos;
delta[0] = 0;
for (var i = 1; i < n - 1; ++i) { //add point X s
var d = (xdata[i + 1] - xdata[i - 1]); frontX = points[curX] - fpDist;
if (d == 0) { backX = points[curX] + fpDist;
//point before current point and after current point need some space in between
return []; var factor = 2;
while (frontX == points[curX] || backX == points[curX]) {
//inside the ulp
frontX = points[curX] - (fpDist * factor);
backX = points[curX] + (fpDist * factor);
factor++;
} }
var s = (xdata[i] - xdata[i - 1]) / d; //add curve points
var p = s * y2[i - 1] + 2; xdata[j] = frontX;
y2[i] = (s - 1) / p; ydata[j] = points[curY];
delta[i] = (ydata[i + 1] - ydata[i]) / (xdata[i + 1] - xdata[i]) - (ydata[i] - ydata[i - 1]) / (xdata[i] - xdata[i - 1]); j++;
delta[i] = (6 * delta[i] / (xdata[i + 1] - xdata[i - 1]) - s * delta[i - 1]) / p;
xdata[j] = points[curX];
ydata[j] = points[curY];
j++;
xdata[j] = backX;
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;
for (var j = n - 2; j >= 0; --j) { var y2 = new Array();
y2[j] = y2[j] * y2[j + 1] + delta[j]; 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) {
//point before current point and after current point need some space in between
return [];
} }
// xmax - xmin / #points var s = (xdata[i] - xdata[i - 1]) / d;
var step = (xdata[n - 1] - xdata[0]) / (num - 1); 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;
}
var xnew = new Array; for (var j = n - 2; j >= 0; --j) {
var ynew = new Array; y2[j] = y2[j] * y2[j + 1] + delta[j];
var result = new Array; }
xnew[0] = xdata[0]; // xmax - xmin / #points
ynew[0] = ydata[0]; var step = (xdata[n - 1] - xdata[0]) / (num - 1);
result.push(xnew[0]); var xnew = new Array;
result.push(ynew[0]); var ynew = new Array;
var result = new Array;
for ( j = 1; j < num; ++j) { xnew[0] = xdata[0];
//new x point (sampling point for the created curve) ynew[0] = ydata[0];
xnew[j] = xnew[0] + j * step;
var max = n - 1; result.push(xnew[0]);
var min = 0; result.push(ynew[0]);
while (max - min > 1) { for ( j = 1; j < num; ++j) {
var k = Math.round((max + min) / 2); //new x point (sampling point for the created curve)
if (xdata[k] > xnew[j]) { xnew[j] = xnew[0] + j * step;
max = k;
} else {
min = k;
}
}
//found point one to the left and one to the right of generated new point var max = n - 1;
var h = (xdata[max] - xdata[min]); var min = 0;
if (h == 0) { while (max - min > 1) {
//similar to above two points from original x data need some space between them var k = Math.round((max + min) / 2);
return []; if (xdata[k] > xnew[j]) {
max = k;
} else {
min = k;
} }
}
var a = (xdata[max] - xnew[j]) / h; //found point one to the left and one to the right of generated new point
var b = (xnew[j] - xdata[min]) / h; var h = (xdata[max] - xdata[min]);
ynew[j] = a * ydata[min] + b * ydata[max] + ((a * a * a - a) * y2[min] + (b * b * b - b) * y2[max]) * (h * h) / 6; if (h == 0) {
//similar to above two points from original x data need some space between them
result.push(xnew[j]); return [];
result.push(ynew[j]);
} }
return result; 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]);
} }
}//end init return result;
}
}//end init
$.plot.plugins.push({ $.plot.plugins.push({
init : init, init : init,
options : options, options : options,
name : 'curvedLines', name : 'curvedLines',
version : '0.6.4' version : '1.0.0'
}); });
})(jQuery); })(jQuery);
...@@ -121,10 +121,12 @@ ...@@ -121,10 +121,12 @@
}, },
curvedLines: { curvedLines: {
apply: true, apply: true,
legacyOverride: {
/*play with parameters (lines)*/ /*play with parameters (lines)*/
//fitPointDist: 86400, //fitPointDist: 86400,
//curvePointFactor: 200, //curvePointFactor: 200,
fit: true fit: true
}
}, },
clickable: false, clickable: false,
hoverable: false, hoverable: false,
...@@ -138,10 +140,12 @@ ...@@ -138,10 +140,12 @@
}, },
curvedLines: { curvedLines: {
apply: true, apply: true,
legacyOverride: {
/*play with parameters (points)*/ /*play with parameters (points)*/
//fitPointDist: 86400, //fitPointDist: 86400,
//curvePointFactor: 200, //curvePointFactor: 200,
fit: true fit: true
}
}, },
color:'#FF0000' color:'#FF0000'
}; };
......
...@@ -47,7 +47,11 @@ $(function() { ...@@ -47,7 +47,11 @@ $(function() {
], ],
{ {
series: { series: {
curvedLines: { active: true, fit: true} curvedLines: {
active: true,
apply: true,
legacyOverride : { fit: true }
}
} }
} }
); );
......
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
}, },
curvedLines : { curvedLines : {
apply : true, apply : true,
legacyOverride: true
} }
}, { }, {
data : d1, data : d1,
...@@ -87,6 +88,7 @@ ...@@ -87,6 +88,7 @@
}, },
curvedLines : { curvedLines : {
apply : true, apply : true,
legacyOverride: true
} }
}, { }, {
data : d1, data : d1,
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
yaxis: { min:10, max: 90} yaxis: { min:10, max: 90}
}; };
$.plot($("#flotOrig"), [{data: d1, lines: { show: true, lineWidth: 3}, curvedLines: {apply: true}}, {data: d1, points: { show: true }}], options); $.plot($("#flotOrig"), [{data: d1, lines: { show: true, lineWidth: 3}, curvedLines: {apply: true, legacyOverride: true}}, {data: d1, points: { show: true }}], options);
}); });
</script> </script>
</body> </body>
......
...@@ -26,8 +26,8 @@ ...@@ -26,8 +26,8 @@
$.plot($("#fillAndMultiAxis"), $.plot($("#fillAndMultiAxis"),
[ [
{data: d1, lines: { show: true, fill: true, fillColor: "rgba(195, 195, 195, 0.4)", lineWidth: 3}, curvedLines: {apply: true}}, {data: d1, points: { show: true }}, {data: d1, lines: { show: true, fill: true, fillColor: "rgba(195, 195, 195, 0.4)", lineWidth: 3}, curvedLines: {apply: true, legacyOverride: true}}, {data: d1, points: { show: true }},
{data: d2, lines: { show: true, lineWidth: 3}, curvedLines: {apply: true}, yaxis:2}, {data: d2, points: { show: true }, yaxis:2} {data: d2, lines: { show: true, lineWidth: 3}, curvedLines: {apply: true, legacyOverride: true}, yaxis:2}, {data: d2, points: { show: true }, yaxis:2}
], options); ], options);
}); });
</script> </script>
......
...@@ -36,9 +36,13 @@ ...@@ -36,9 +36,13 @@
show : true, show : true,
lineWidth : 3 lineWidth : 3
}, },
curvedLines: {apply: true, curvedLines:
fit : true, {
apply: true,
legacyOverride: {
fit : true,
} }
}
}, { }, {
data : d1, data : d1,
points : { points : {
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
yaxis: { min:10, max: 45} yaxis: { min:10, max: 45}
}; };
$.plot($("#flotOrig"), [{data: d1, lines: { show: true, lineWidth: 3}, curvedLines: {apply: true}}, {data: d1, points: { show: true }}], options); $.plot($("#flotOrig"), [{data: d1, lines: { show: true, lineWidth: 3}, curvedLines: {apply: true, legacyOverride: true}}, {data: d1, points: { show: true }}], options);
}); });
</script> </script>
</body> </body>
......
...@@ -26,7 +26,8 @@ ...@@ -26,7 +26,8 @@
show : true show : true
}, },
curvedLines : { curvedLines : {
apply : true apply : true,
legacyOverride: true
} }
}, { }, {
data : d1, data : d1,
......
...@@ -41,7 +41,8 @@ ...@@ -41,7 +41,8 @@
lineWidth : 3 lineWidth : 3
}, },
curvedLines : { curvedLines : {
apply : true apply : true,
legacyOverride: true
} }
}, { }, {
data : d1, data : d1,
......
...@@ -50,7 +50,8 @@ $(function () { ...@@ -50,7 +50,8 @@ $(function () {
//general options //general options
var options = { var options = {
series: {curvedLines : { series: {curvedLines : {
active : true active : true,
legacyOverride: true
}, },
lines: { lineWidth: 5}, lines: { lineWidth: 5},
points: { radius: 4 } points: { radius: 4 }
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
yaxis: { min:0, max: 10} yaxis: { min:0, max: 10}
}; };
$.plot($("#flotOrig"), [{data: d1, lines: { show: true, lineWidth: 3}, curvedLines: {apply: true}}, {data: d1, points: { show: true }}], options); $.plot($("#flotOrig"), [{data: d1, lines: { show: true, lineWidth: 3}, curvedLines: {apply: true, legacyOverride: true}}, {data: d1, points: { show: true }}], options);
}); });
</script> </script>
</body> </body>
......
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