|
|
@@ -0,0 +1,289 @@
|
|
|
+(function($) {
|
|
|
+ "use strict";
|
|
|
+
|
|
|
+ var options = {
|
|
|
+ series: {
|
|
|
+ fillBelowTo: null
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ function init(plot) {
|
|
|
+ function findBelowSeries( series, allseries ) {
|
|
|
+
|
|
|
+ var i;
|
|
|
+
|
|
|
+ debugger;
|
|
|
+ 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);
|