Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1 | espaco | 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 | |||
| 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); |