You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

288 lines
9.7 KiB

(function($) {
"use strict";
var options = {
series: {
fillBelowTo: null
}
};
function init(plot) {
function findBelowSeries( series, allseries ) {
var i;
for ( i = 0; i < allseries.length; ++i ) {
if ( allseries[ i ].id === series.fillBelowTo ) {
return allseries[ i ];
}
}
return null;
}
/* top and bottom doesn't actually matter for this, we're just using it to help make this easier to think about */
/* this is a vector cross product operation */
function segmentIntersection(top_left_x, top_left_y, top_right_x, top_right_y, bottom_left_x, bottom_left_y, bottom_right_x, bottom_right_y) {
var top_delta_x, top_delta_y, bottom_delta_x, bottom_delta_y,
s, t;
top_delta_x = top_right_x - top_left_x;
top_delta_y = top_right_y - top_left_y;
bottom_delta_x = bottom_right_x - bottom_left_x;
bottom_delta_y = bottom_right_y - bottom_left_y;
s = (
(-top_delta_y * (top_left_x - bottom_left_x)) + (top_delta_x * (top_left_y - bottom_left_y))
) / (
-bottom_delta_x * top_delta_y + top_delta_x * bottom_delta_y
);
t = (
(bottom_delta_x * (top_left_y - bottom_left_y)) - (bottom_delta_y * (top_left_x - bottom_left_x))
) / (
-bottom_delta_x * top_delta_y + top_delta_x * bottom_delta_y
);
// Collision detected
if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
return [
top_left_x + (t * top_delta_x), // X
top_left_y + (t * top_delta_y) // Y
];
}
// No collision
return null;
}
function plotDifferenceArea(plot, ctx, series) {
if ( series.fillBelowTo === null ) {
return;
}
var otherseries,
ps,
points,
otherps,
otherpoints,
plotOffset,
fillStyle;
function openPolygon(x, y) {
ctx.beginPath();
ctx.moveTo(
series.xaxis.p2c(x) + plotOffset.left,
series.yaxis.p2c(y) + plotOffset.top
);
}
function closePolygon() {
ctx.closePath();
ctx.fill();
}
function validateInput() {
if (points.length/ps !== otherpoints.length/otherps) {
console.error("Refusing to graph inconsistent number of points");
return false;
}
var i;
for (i = 0; i < (points.length / ps); i++) {
if (
points[i * ps] !== null &&
otherpoints[i * otherps] !== null &&
points[i * ps] !== otherpoints[i * otherps]
) {
console.error("Refusing to graph points without matching value");
return false;
}
}
return true;
}
function findNextStart(start_i, end_i) {
console.assert(end_i > start_i, "expects the end index to be greater than the start index");
var start = (
start_i === 0 ||
points[start_i - 1] === null ||
otherpoints[start_i - 1] === null
),
equal = false,
i,
intersect;
for (i = start_i; i < end_i; i++) {
// Take note of null points
if (
points[(i * ps) + 1] === null ||
otherpoints[(i * ps) + 1] === null
) {
equal = false;
start = true;
}
// Take note of equal points
else if (points[(i * ps) + 1] === otherpoints[(i * otherps) + 1]) {
equal = true;
start = false;
}
else if (points[(i * ps) + 1] > otherpoints[(i * otherps) + 1]) {
// If we begin above the desired point
if (start) {
openPolygon(points[i * ps], points[(i * ps) + 1]);
}
// If an equal point preceeds this, start the polygon at that equal point
else if (equal) {
openPolygon(points[(i - 1) * ps], points[((i - 1) * ps) + 1]);
}
// Otherwise, find the intersection point, and start it there
else {
intersect = intersectionPoint(i);
openPolygon(intersect[0], intersect[1]);
}
topTraversal(i, end_i);
return;
}
// If we go below equal, equal at any preceeding point is irrelevant
else {
start = false;
equal = false;
}
}
}
function intersectionPoint(right_i) {
console.assert(right_i > 0, "expects the second point in the series line segment");
var i, intersect;
for (i = 1; i < (otherpoints.length/otherps); i++) {
intersect = segmentIntersection(
points[(right_i - 1) * ps], points[((right_i - 1) * ps) + 1],
points[right_i * ps], points[(right_i * ps) + 1],
otherpoints[(i - 1) * otherps], otherpoints[((i - 1) * otherps) + 1],
otherpoints[i * otherps], otherpoints[(i * otherps) + 1]
);
if (intersect !== null) {
return intersect;
}
}
console.error("intersectionPoint() should only be called when an intersection happens");
}
function bottomTraversal(start_i, end_i) {
console.assert(start_i >= end_i, "the start should be the rightmost point, and the end should be the leftmost (excluding the equal or intersecting point)");
var i;
for (i = start_i; i >= end_i; i--) {
ctx.lineTo(
otherseries.xaxis.p2c(otherpoints[i * otherps]) + plotOffset.left,
otherseries.yaxis.p2c(otherpoints[(i * otherps) + 1]) + plotOffset.top
);
}
closePolygon();
}
function topTraversal(start_i, end_i) {
console.assert(start_i <= end_i, "the start should be the rightmost point, and the end should be the leftmost (excluding the equal or intersecting point)");
var i,
intersect;
for (i = start_i; i < end_i; i++) {
if (points[(i * ps) + 1] === null && i > start_i) {
bottomTraversal(i - 1, start_i);
findNextStart(i, end_i);
return;
}
else if (points[(i * ps) + 1] === otherpoints[(i * otherps) + 1]) {
bottomTraversal(i, start_i);
findNextStart(i, end_i);
return;
}
else if (points[(i * ps) + 1] < otherpoints[(i * otherps) + 1]) {
intersect = intersectionPoint(i);
ctx.lineTo(
series.xaxis.p2c(intersect[0]) + plotOffset.left,
series.yaxis.p2c(intersect[1]) + plotOffset.top
);
bottomTraversal(i, start_i);
findNextStart(i, end_i);
return;
}
else {
ctx.lineTo(
series.xaxis.p2c(points[i * ps]) + plotOffset.left,
series.yaxis.p2c(points[(i * ps) + 1]) + plotOffset.top
);
}
}
bottomTraversal(end_i, start_i);
}
// Begin processing
otherseries = findBelowSeries( series, plot.getData() );
if ( !otherseries ) {
return;
}
ps = series.datapoints.pointsize;
points = series.datapoints.points;
otherps = otherseries.datapoints.pointsize;
otherpoints = otherseries.datapoints.points;
plotOffset = plot.getPlotOffset();
if (!validateInput()) {
return;
}
// Flot's getFillStyle() should probably be exposed somewhere
fillStyle = $.color.parse(series.color);
fillStyle.a = 0.4;
fillStyle.normalize();
ctx.fillStyle = fillStyle.toString();
// Begin recursive bi-directional traversal
findNextStart(0, points.length/ps);
}
plot.hooks.drawSeries.push(plotDifferenceArea);
}
$.plot.plugins.push({
init: init,
options: options,
name: "fillbelow",
version: "0.1.0"
});
})(jQuery);