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