123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- (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 precedes 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 preceding 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);
|