2014-10-15 09:55:46 -05:00
( 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 ] ) ;
}
2020-10-07 05:29:30 -05:00
// If an equal point precedes this, start the polygon at that equal point
2014-10-15 09:55:46 -05:00
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 ;
}
2020-10-07 05:29:30 -05:00
// If we go below equal, equal at any preceding point is irrelevant
2014-10-15 09:55:46 -05:00
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 ) ;