Home | History | Annotate | Download | only in flot
      1 /* Flot plugin for computing bottoms for filled line and bar charts.
      2 
      3 Copyright (c) 2007-2014 IOLA and Ole Laursen.
      4 Licensed under the MIT license.
      5 
      6 The case: you've got two series that you want to fill the area between. In Flot
      7 terms, you need to use one as the fill bottom of the other. You can specify the
      8 bottom of each data point as the third coordinate manually, or you can use this
      9 plugin to compute it for you.
     10 
     11 In order to name the other series, you need to give it an id, like this:
     12 
     13 	var dataset = [
     14 		{ data: [ ... ], id: "foo" } ,         // use default bottom
     15 		{ data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
     16 	];
     17 
     18 	$.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }});
     19 
     20 As a convenience, if the id given is a number that doesn't appear as an id in
     21 the series, it is interpreted as the index in the array instead (so fillBetween:
     22 0 can also mean the first series).
     23 
     24 Internally, the plugin modifies the datapoints in each series. For line series,
     25 extra data points might be inserted through interpolation. Note that at points
     26 where the bottom line is not defined (due to a null point or start/end of line),
     27 the current line will show a gap too. The algorithm comes from the
     28 jquery.flot.stack.js plugin, possibly some code could be shared.
     29 
     30 */
     31 
     32 (function ( $ ) {
     33 
     34 	var options = {
     35 		series: {
     36 			fillBetween: null	// or number
     37 		}
     38 	};
     39 
     40 	function init( plot ) {
     41 
     42 		function findBottomSeries( s, allseries ) {
     43 
     44 			var i;
     45 
     46 			for ( i = 0; i < allseries.length; ++i ) {
     47 				if ( allseries[ i ].id === s.fillBetween ) {
     48 					return allseries[ i ];
     49 				}
     50 			}
     51 
     52 			if ( typeof s.fillBetween === "number" ) {
     53 				if ( s.fillBetween < 0 || s.fillBetween >= allseries.length ) {
     54 					return null;
     55 				}
     56 				return allseries[ s.fillBetween ];
     57 			}
     58 
     59 			return null;
     60 		}
     61 
     62 		function computeFillBottoms( plot, s, datapoints ) {
     63 
     64 			if ( s.fillBetween == null ) {
     65 				return;
     66 			}
     67 
     68 			var other = findBottomSeries( s, plot.getData() );
     69 
     70 			if ( !other ) {
     71 				return;
     72 			}
     73 
     74 			var ps = datapoints.pointsize,
     75 				points = datapoints.points,
     76 				otherps = other.datapoints.pointsize,
     77 				otherpoints = other.datapoints.points,
     78 				newpoints = [],
     79 				px, py, intery, qx, qy, bottom,
     80 				withlines = s.lines.show,
     81 				withbottom = ps > 2 && datapoints.format[2].y,
     82 				withsteps = withlines && s.lines.steps,
     83 				fromgap = true,
     84 				i = 0,
     85 				j = 0,
     86 				l, m;
     87 
     88 			while ( true ) {
     89 
     90 				if ( i >= points.length ) {
     91 					break;
     92 				}
     93 
     94 				l = newpoints.length;
     95 
     96 				if ( points[ i ] == null ) {
     97 
     98 					// copy gaps
     99 
    100 					for ( m = 0; m < ps; ++m ) {
    101 						newpoints.push( points[ i + m ] );
    102 					}
    103 
    104 					i += ps;
    105 
    106 				} else if ( j >= otherpoints.length ) {
    107 
    108 					// for lines, we can't use the rest of the points
    109 
    110 					if ( !withlines ) {
    111 						for ( m = 0; m < ps; ++m ) {
    112 							newpoints.push( points[ i + m ] );
    113 						}
    114 					}
    115 
    116 					i += ps;
    117 
    118 				} else if ( otherpoints[ j ] == null ) {
    119 
    120 					// oops, got a gap
    121 
    122 					for ( m = 0; m < ps; ++m ) {
    123 						newpoints.push( null );
    124 					}
    125 
    126 					fromgap = true;
    127 					j += otherps;
    128 
    129 				} else {
    130 
    131 					// cases where we actually got two points
    132 
    133 					px = points[ i ];
    134 					py = points[ i + 1 ];
    135 					qx = otherpoints[ j ];
    136 					qy = otherpoints[ j + 1 ];
    137 					bottom = 0;
    138 
    139 					if ( px === qx ) {
    140 
    141 						for ( m = 0; m < ps; ++m ) {
    142 							newpoints.push( points[ i + m ] );
    143 						}
    144 
    145 						//newpoints[ l + 1 ] += qy;
    146 						bottom = qy;
    147 
    148 						i += ps;
    149 						j += otherps;
    150 
    151 					} else if ( px > qx ) {
    152 
    153 						// we got past point below, might need to
    154 						// insert interpolated extra point
    155 
    156 						if ( withlines && i > 0 && points[ i - ps ] != null ) {
    157 							intery = py + ( points[ i - ps + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px );
    158 							newpoints.push( qx );
    159 							newpoints.push( intery );
    160 							for ( m = 2; m < ps; ++m ) {
    161 								newpoints.push( points[ i + m ] );
    162 							}
    163 							bottom = qy;
    164 						}
    165 
    166 						j += otherps;
    167 
    168 					} else { // px < qx
    169 
    170 						// if we come from a gap, we just skip this point
    171 
    172 						if ( fromgap && withlines ) {
    173 							i += ps;
    174 							continue;
    175 						}
    176 
    177 						for ( m = 0; m < ps; ++m ) {
    178 							newpoints.push( points[ i + m ] );
    179 						}
    180 
    181 						// we might be able to interpolate a point below,
    182 						// this can give us a better y
    183 
    184 						if ( withlines && j > 0 && otherpoints[ j - otherps ] != null ) {
    185 							bottom = qy + ( otherpoints[ j - otherps + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx );
    186 						}
    187 
    188 						//newpoints[l + 1] += bottom;
    189 
    190 						i += ps;
    191 					}
    192 
    193 					fromgap = false;
    194 
    195 					if ( l !== newpoints.length && withbottom ) {
    196 						newpoints[ l + 2 ] = bottom;
    197 					}
    198 				}
    199 
    200 				// maintain the line steps invariant
    201 
    202 				if ( withsteps && l !== newpoints.length && l > 0 &&
    203 					newpoints[ l ] !== null &&
    204 					newpoints[ l ] !== newpoints[ l - ps ] &&
    205 					newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ] ) {
    206 					for (m = 0; m < ps; ++m) {
    207 						newpoints[ l + ps + m ] = newpoints[ l + m ];
    208 					}
    209 					newpoints[ l + 1 ] = newpoints[ l - ps + 1 ];
    210 				}
    211 			}
    212 
    213 			datapoints.points = newpoints;
    214 		}
    215 
    216 		plot.hooks.processDatapoints.push( computeFillBottoms );
    217 	}
    218 
    219 	$.plot.plugins.push({
    220 		init: init,
    221 		options: options,
    222 		name: "fillbetween",
    223 		version: "1.0"
    224 	});
    225 
    226 })(jQuery);
    227