]>
Commit | Line | Data |
---|---|---|
31f18b77 FG |
1 | /* Flot plugin for plotting images. |
2 | ||
3 | Copyright (c) 2007-2013 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); |