OLD | NEW |
(Empty) | |
| 1 /* Flot plugin for plotting images. |
| 2 |
| 3 Copyright (c) 2007-2014 IOLA and Ole Laursen. |
| 4 Licensed under the MIT license. |
| 5 |
| 6 The data syntax is [ [ image, x1, y1, x2, y2 ], ... ] where (x1, y1) and |
| 7 (x2, y2) are where you intend the two opposite corners of the image to end up |
| 8 in the plot. Image must be a fully loaded Javascript image (you can make one |
| 9 with new Image()). If the image is not complete, it's skipped when plotting. |
| 10 |
| 11 There are two helpers included for retrieving images. The easiest work the way |
| 12 that you put in URLs instead of images in the data, like this: |
| 13 |
| 14 [ "myimage.png", 0, 0, 10, 10 ] |
| 15 |
| 16 Then call $.plot.image.loadData( data, options, callback ) where data and |
| 17 options are the same as you pass in to $.plot. This loads the images, replaces |
| 18 the URLs in the data with the corresponding images and calls "callback" when |
| 19 all images are loaded (or failed loading). In the callback, you can then call |
| 20 $.plot with the data set. See the included example. |
| 21 |
| 22 A more low-level helper, $.plot.image.load(urls, callback) is also included. |
| 23 Given a list of URLs, it calls callback with an object mapping from URL to |
| 24 Image object when all images are loaded or have failed loading. |
| 25 |
| 26 The plugin supports these options: |
| 27 |
| 28 series: { |
| 29 images: { |
| 30 show: boolean |
| 31 anchor: "corner" or "center" |
| 32 alpha: [ 0, 1 ] |
| 33 } |
| 34 } |
| 35 |
| 36 They can be specified for a specific series: |
| 37 |
| 38 $.plot( $("#placeholder"), [{ |
| 39 data: [ ... ], |
| 40 images: { ... } |
| 41 ]) |
| 42 |
| 43 Note that because the data format is different from usual data points, you |
| 44 can't use images with anything else in a specific data series. |
| 45 |
| 46 Setting "anchor" to "center" causes the pixels in the image to be anchored at |
| 47 the corner pixel centers inside of at the pixel corners, effectively letting |
| 48 half a pixel stick out to each side in the plot. |
| 49 |
| 50 A possible future direction could be support for tiling for large images (like |
| 51 Google Maps). |
| 52 |
| 53 */ |
| 54 |
| 55 (function ($) { |
| 56 var options = { |
| 57 series: { |
| 58 images: { |
| 59 show: false, |
| 60 alpha: 1, |
| 61 anchor: "corner" // or "center" |
| 62 } |
| 63 } |
| 64 }; |
| 65 |
| 66 $.plot.image = {}; |
| 67 |
| 68 $.plot.image.loadDataImages = function (series, options, callback) { |
| 69 var urls = [], points = []; |
| 70 |
| 71 var defaultShow = options.series.images.show; |
| 72 |
| 73 $.each(series, function (i, s) { |
| 74 if (!(defaultShow || s.images.show)) |
| 75 return; |
| 76 |
| 77 if (s.data) |
| 78 s = s.data; |
| 79 |
| 80 $.each(s, function (i, p) { |
| 81 if (typeof p[0] == "string") { |
| 82 urls.push(p[0]); |
| 83 points.push(p); |
| 84 } |
| 85 }); |
| 86 }); |
| 87 |
| 88 $.plot.image.load(urls, function (loadedImages) { |
| 89 $.each(points, function (i, p) { |
| 90 var url = p[0]; |
| 91 if (loadedImages[url]) |
| 92 p[0] = loadedImages[url]; |
| 93 }); |
| 94 |
| 95 callback(); |
| 96 }); |
| 97 } |
| 98 |
| 99 $.plot.image.load = function (urls, callback) { |
| 100 var missing = urls.length, loaded = {}; |
| 101 if (missing == 0) |
| 102 callback({}); |
| 103 |
| 104 $.each(urls, function (i, url) { |
| 105 var handler = function () { |
| 106 --missing; |
| 107 |
| 108 loaded[url] = this; |
| 109 |
| 110 if (missing == 0) |
| 111 callback(loaded); |
| 112 }; |
| 113 |
| 114 $('<img />').load(handler).error(handler).attr('src', url); |
| 115 }); |
| 116 }; |
| 117 |
| 118 function drawSeries(plot, ctx, series) { |
| 119 var plotOffset = plot.getPlotOffset(); |
| 120 |
| 121 if (!series.images || !series.images.show) |
| 122 return; |
| 123 |
| 124 var points = series.datapoints.points, |
| 125 ps = series.datapoints.pointsize; |
| 126 |
| 127 for (var i = 0; i < points.length; i += ps) { |
| 128 var img = points[i], |
| 129 x1 = points[i + 1], y1 = points[i + 2], |
| 130 x2 = points[i + 3], y2 = points[i + 4], |
| 131 xaxis = series.xaxis, yaxis = series.yaxis, |
| 132 tmp; |
| 133 |
| 134 // actually we should check img.complete, but it |
| 135 // appears to be a somewhat unreliable indicator in |
| 136 // IE6 (false even after load event) |
| 137 if (!img || img.width <= 0 || img.height <= 0) |
| 138 continue; |
| 139 |
| 140 if (x1 > x2) { |
| 141 tmp = x2; |
| 142 x2 = x1; |
| 143 x1 = tmp; |
| 144 } |
| 145 if (y1 > y2) { |
| 146 tmp = y2; |
| 147 y2 = y1; |
| 148 y1 = tmp; |
| 149 } |
| 150 |
| 151 // if the anchor is at the center of the pixel, expand the |
| 152 // image by 1/2 pixel in each direction |
| 153 if (series.images.anchor == "center") { |
| 154 tmp = 0.5 * (x2-x1) / (img.width - 1); |
| 155 x1 -= tmp; |
| 156 x2 += tmp; |
| 157 tmp = 0.5 * (y2-y1) / (img.height - 1); |
| 158 y1 -= tmp; |
| 159 y2 += tmp; |
| 160 } |
| 161 |
| 162 // clip |
| 163 if (x1 == x2 || y1 == y2 || |
| 164 x1 >= xaxis.max || x2 <= xaxis.min || |
| 165 y1 >= yaxis.max || y2 <= yaxis.min) |
| 166 continue; |
| 167 |
| 168 var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height; |
| 169 if (x1 < xaxis.min) { |
| 170 sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1); |
| 171 x1 = xaxis.min; |
| 172 } |
| 173 |
| 174 if (x2 > xaxis.max) { |
| 175 sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1); |
| 176 x2 = xaxis.max; |
| 177 } |
| 178 |
| 179 if (y1 < yaxis.min) { |
| 180 sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1); |
| 181 y1 = yaxis.min; |
| 182 } |
| 183 |
| 184 if (y2 > yaxis.max) { |
| 185 sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1); |
| 186 y2 = yaxis.max; |
| 187 } |
| 188 |
| 189 x1 = xaxis.p2c(x1); |
| 190 x2 = xaxis.p2c(x2); |
| 191 y1 = yaxis.p2c(y1); |
| 192 y2 = yaxis.p2c(y2); |
| 193 |
| 194 // the transformation may have swapped us |
| 195 if (x1 > x2) { |
| 196 tmp = x2; |
| 197 x2 = x1; |
| 198 x1 = tmp; |
| 199 } |
| 200 if (y1 > y2) { |
| 201 tmp = y2; |
| 202 y2 = y1; |
| 203 y1 = tmp; |
| 204 } |
| 205 |
| 206 tmp = ctx.globalAlpha; |
| 207 ctx.globalAlpha *= series.images.alpha; |
| 208 ctx.drawImage(img, |
| 209 sx1, sy1, sx2 - sx1, sy2 - sy1, |
| 210 x1 + plotOffset.left, y1 + plotOffset.top, |
| 211 x2 - x1, y2 - y1); |
| 212 ctx.globalAlpha = tmp; |
| 213 } |
| 214 } |
| 215 |
| 216 function processRawData(plot, series, data, datapoints) { |
| 217 if (!series.images.show) |
| 218 return; |
| 219 |
| 220 // format is Image, x1, y1, x2, y2 (opposite corners) |
| 221 datapoints.format = [ |
| 222 { required: true }, |
| 223 { x: true, number: true, required: true }, |
| 224 { y: true, number: true, required: true }, |
| 225 { x: true, number: true, required: true }, |
| 226 { y: true, number: true, required: true } |
| 227 ]; |
| 228 } |
| 229 |
| 230 function init(plot) { |
| 231 plot.hooks.processRawData.push(processRawData); |
| 232 plot.hooks.drawSeries.push(drawSeries); |
| 233 } |
| 234 |
| 235 $.plot.plugins.push({ |
| 236 init: init, |
| 237 options: options, |
| 238 name: 'image', |
| 239 version: '1.1' |
| 240 }); |
| 241 })(jQuery); |
OLD | NEW |