|
1 d3.svg.brush = function() { |
|
2 var event = d3.dispatch("brushstart", "brush", "brushend"), |
|
3 x, // x-scale, optional |
|
4 y, // y-scale, optional |
|
5 extent = [[0, 0], [0, 0]]; // [x0, y0], [x1, y1] |
|
6 |
|
7 function brush(g) { |
|
8 var resizes = x && y ? ["n", "e", "s", "w", "nw", "ne", "se", "sw"] |
|
9 : x ? ["e", "w"] |
|
10 : y ? ["n", "s"] |
|
11 : []; |
|
12 |
|
13 g.each(function() { |
|
14 var g = d3.select(this).on("mousedown.brush", down), |
|
15 bg = g.selectAll(".background").data([,]), |
|
16 fg = g.selectAll(".extent").data([,]), |
|
17 tz = g.selectAll(".resize").data(resizes, String), |
|
18 e; |
|
19 |
|
20 // An invisible, mouseable area for starting a new brush. |
|
21 bg.enter().append("svg:rect") |
|
22 .attr("class", "background") |
|
23 .style("visibility", "hidden") |
|
24 .style("pointer-events", "all") |
|
25 .style("cursor", "crosshair"); |
|
26 |
|
27 // The visible brush extent; style this as you like! |
|
28 fg.enter().append("svg:rect") |
|
29 .attr("class", "extent") |
|
30 .style("cursor", "move"); |
|
31 |
|
32 // More invisible rects for resizing the extent. |
|
33 tz.enter().append("svg:rect") |
|
34 .attr("class", function(d) { return "resize " + d; }) |
|
35 .attr("width", 6) |
|
36 .attr("height", 6) |
|
37 .style("visibility", "hidden") |
|
38 .style("pointer-events", brush.empty() ? "none" : "all") |
|
39 .style("cursor", function(d) { return d3_svg_brushCursor[d]; }); |
|
40 |
|
41 // Remove any superfluous resizers. |
|
42 tz.exit().remove(); |
|
43 |
|
44 // Initialize the background to fill the defined range. |
|
45 // If the range isn't defined, you can post-process. |
|
46 if (x) { |
|
47 e = d3_scaleExtent(x.range()); |
|
48 bg.attr("x", e[0]).attr("width", e[1] - e[0]); |
|
49 d3_svg_brushRedrawX(g, extent); |
|
50 } |
|
51 if (y) { |
|
52 e = d3_scaleExtent(y.range()); |
|
53 bg.attr("y", e[0]).attr("height", e[1] - e[0]); |
|
54 d3_svg_brushRedrawY(g, extent); |
|
55 } |
|
56 }); |
|
57 } |
|
58 |
|
59 function down() { |
|
60 var target = d3.select(d3.event.target); |
|
61 |
|
62 // Store some global state for the duration of the brush gesture. |
|
63 d3_svg_brush = brush; |
|
64 d3_svg_brushTarget = this; |
|
65 d3_svg_brushExtent = extent; |
|
66 d3_svg_brushOffset = d3.svg.mouse(d3_svg_brushTarget); |
|
67 |
|
68 // If the extent was clicked on, drag rather than brush; |
|
69 // store the offset between the mouse and extent origin instead. |
|
70 if (d3_svg_brushDrag = target.classed("extent")) { |
|
71 d3_svg_brushOffset[0] = extent[0][0] - d3_svg_brushOffset[0]; |
|
72 d3_svg_brushOffset[1] = extent[0][1] - d3_svg_brushOffset[1]; |
|
73 } |
|
74 |
|
75 // If a resizer was clicked on, record which side is to be resized. |
|
76 // Also, set the offset to the opposite side. |
|
77 else if (target.classed("resize")) { |
|
78 d3_svg_brushResize = d3.event.target.__data__; |
|
79 d3_svg_brushOffset[0] = extent[+/w$/.test(d3_svg_brushResize)][0]; |
|
80 d3_svg_brushOffset[1] = extent[+/^n/.test(d3_svg_brushResize)][1]; |
|
81 } |
|
82 |
|
83 // If the ALT key is down when starting a brush, the center is at the mouse. |
|
84 else if (d3.event.altKey) { |
|
85 d3_svg_brushCenter = d3_svg_brushOffset.slice(); |
|
86 } |
|
87 |
|
88 // Restrict which dimensions are resized. |
|
89 d3_svg_brushX = !/^(n|s)$/.test(d3_svg_brushResize) && x; |
|
90 d3_svg_brushY = !/^(e|w)$/.test(d3_svg_brushResize) && y; |
|
91 |
|
92 // Notify listeners. |
|
93 d3_svg_brushDispatch = dispatcher(this, arguments); |
|
94 d3_svg_brushDispatch("brushstart"); |
|
95 d3_svg_brushMove(); |
|
96 d3_eventCancel(); |
|
97 } |
|
98 |
|
99 function dispatcher(that, argumentz) { |
|
100 return function(type) { |
|
101 var e = d3.event; |
|
102 try { |
|
103 d3.event = {type: type, target: brush}; |
|
104 event[type].apply(that, argumentz); |
|
105 } finally { |
|
106 d3.event = e; |
|
107 } |
|
108 }; |
|
109 } |
|
110 |
|
111 brush.x = function(z) { |
|
112 if (!arguments.length) return x; |
|
113 x = z; |
|
114 return brush; |
|
115 }; |
|
116 |
|
117 brush.y = function(z) { |
|
118 if (!arguments.length) return y; |
|
119 y = z; |
|
120 return brush; |
|
121 }; |
|
122 |
|
123 brush.extent = function(z) { |
|
124 var x0, x1, y0, y1, t; |
|
125 |
|
126 // Invert the pixel extent to data-space. |
|
127 if (!arguments.length) { |
|
128 if (x) { |
|
129 x0 = x.invert(extent[0][0]), x1 = x.invert(extent[1][0]); |
|
130 if (x1 < x0) t = x0, x0 = x1, x1 = t; |
|
131 } |
|
132 if (y) { |
|
133 y0 = y.invert(extent[0][1]), y1 = y.invert(extent[1][1]); |
|
134 if (y1 < y0) t = y0, y0 = y1, y1 = t; |
|
135 } |
|
136 return x && y ? [[x0, y0], [x1, y1]] : x ? [x0, x1] : y && [y0, y1]; |
|
137 } |
|
138 |
|
139 // Scale the data-space extent to pixels. |
|
140 if (x) { |
|
141 x0 = z[0], x1 = z[1]; |
|
142 if (y) x0 = x0[0], x1 = x1[0]; |
|
143 x0 = x(x0), x1 = x(x1); |
|
144 if (x1 < x0) t = x0, x0 = x1, x1 = t; |
|
145 extent[0][0] = x0, extent[1][0] = x1; |
|
146 } |
|
147 if (y) { |
|
148 y0 = z[0], y1 = z[1]; |
|
149 if (x) y0 = y0[1], y1 = y1[1]; |
|
150 y0 = y(y0), y1 = y(y1); |
|
151 if (y1 < y0) t = y0, y0 = y1, y1 = t; |
|
152 extent[0][1] = y0, extent[1][1] = y1; |
|
153 } |
|
154 |
|
155 return brush; |
|
156 }; |
|
157 |
|
158 brush.clear = function() { |
|
159 extent[0][0] = |
|
160 extent[0][1] = |
|
161 extent[1][0] = |
|
162 extent[1][1] = 0; |
|
163 return brush; |
|
164 }; |
|
165 |
|
166 brush.empty = function() { |
|
167 return (x && extent[0][0] === extent[1][0]) |
|
168 || (y && extent[0][1] === extent[1][1]); |
|
169 }; |
|
170 |
|
171 brush.on = function(type, listener) { |
|
172 event.on(type, listener); |
|
173 return brush; |
|
174 }; |
|
175 |
|
176 d3.select(window) |
|
177 .on("mousemove.brush", d3_svg_brushMove) |
|
178 .on("mouseup.brush", d3_svg_brushUp) |
|
179 .on("keydown.brush", d3_svg_brushKeydown) |
|
180 .on("keyup.brush", d3_svg_brushKeyup); |
|
181 |
|
182 return brush; |
|
183 }; |
|
184 |
|
185 var d3_svg_brush, |
|
186 d3_svg_brushDispatch, |
|
187 d3_svg_brushTarget, |
|
188 d3_svg_brushX, |
|
189 d3_svg_brushY, |
|
190 d3_svg_brushExtent, |
|
191 d3_svg_brushDrag, |
|
192 d3_svg_brushResize, |
|
193 d3_svg_brushCenter, |
|
194 d3_svg_brushOffset; |
|
195 |
|
196 function d3_svg_brushRedrawX(g, extent) { |
|
197 g.select(".extent").attr("x", extent[0][0]); |
|
198 g.selectAll(".n,.s,.w,.nw,.sw").attr("x", extent[0][0] - 2); |
|
199 g.selectAll(".e,.ne,.se").attr("x", extent[1][0] - 3); |
|
200 g.selectAll(".extent,.n,.s").attr("width", extent[1][0] - extent[0][0]); |
|
201 } |
|
202 |
|
203 function d3_svg_brushRedrawY(g, extent) { |
|
204 g.select(".extent").attr("y", extent[0][1]); |
|
205 g.selectAll(".n,.e,.w,.nw,.ne").attr("y", extent[0][1] - 3); |
|
206 g.selectAll(".s,.se,.sw").attr("y", extent[1][1] - 4); |
|
207 g.selectAll(".extent,.e,.w").attr("height", extent[1][1] - extent[0][1]); |
|
208 } |
|
209 |
|
210 function d3_svg_brushKeydown() { |
|
211 if (d3.event.keyCode == 32 && d3_svg_brushTarget && !d3_svg_brushDrag) { |
|
212 d3_svg_brushCenter = null; |
|
213 d3_svg_brushOffset[0] -= d3_svg_brushExtent[1][0]; |
|
214 d3_svg_brushOffset[1] -= d3_svg_brushExtent[1][1]; |
|
215 d3_svg_brushDrag = 2; |
|
216 d3_eventCancel(); |
|
217 } |
|
218 } |
|
219 |
|
220 function d3_svg_brushKeyup() { |
|
221 if (d3.event.keyCode == 32 && d3_svg_brushDrag == 2) { |
|
222 d3_svg_brushOffset[0] += d3_svg_brushExtent[1][0]; |
|
223 d3_svg_brushOffset[1] += d3_svg_brushExtent[1][1]; |
|
224 d3_svg_brushDrag = 0; |
|
225 d3_eventCancel(); |
|
226 } |
|
227 } |
|
228 |
|
229 function d3_svg_brushMove() { |
|
230 if (d3_svg_brushOffset) { |
|
231 var mouse = d3.svg.mouse(d3_svg_brushTarget), |
|
232 g = d3.select(d3_svg_brushTarget); |
|
233 |
|
234 if (!d3_svg_brushDrag) { |
|
235 |
|
236 // If needed, determine the center from the current extent. |
|
237 if (d3.event.altKey) { |
|
238 if (!d3_svg_brushCenter) { |
|
239 d3_svg_brushCenter = [ |
|
240 (d3_svg_brushExtent[0][0] + d3_svg_brushExtent[1][0]) / 2, |
|
241 (d3_svg_brushExtent[0][1] + d3_svg_brushExtent[1][1]) / 2 |
|
242 ]; |
|
243 } |
|
244 |
|
245 // Update the offset, for when the ALT key is released. |
|
246 d3_svg_brushOffset[0] = d3_svg_brushExtent[+(mouse[0] < d3_svg_brushCenter[0])][0]; |
|
247 d3_svg_brushOffset[1] = d3_svg_brushExtent[+(mouse[1] < d3_svg_brushCenter[1])][1]; |
|
248 } |
|
249 |
|
250 // When the ALT key is released, we clear the center. |
|
251 else d3_svg_brushCenter = null; |
|
252 } |
|
253 |
|
254 // Update the brush extent for each dimension. |
|
255 if (d3_svg_brushX) { |
|
256 d3_svg_brushMove1(mouse, d3_svg_brushX, 0); |
|
257 d3_svg_brushRedrawX(g, d3_svg_brushExtent); |
|
258 } |
|
259 if (d3_svg_brushY) { |
|
260 d3_svg_brushMove1(mouse, d3_svg_brushY, 1); |
|
261 d3_svg_brushRedrawY(g, d3_svg_brushExtent); |
|
262 } |
|
263 |
|
264 // Notify listeners. |
|
265 d3_svg_brushDispatch("brush"); |
|
266 } |
|
267 } |
|
268 |
|
269 function d3_svg_brushMove1(mouse, scale, i) { |
|
270 var range = d3_scaleExtent(scale.range()), |
|
271 offset = d3_svg_brushOffset[i], |
|
272 size = d3_svg_brushExtent[1][i] - d3_svg_brushExtent[0][i], |
|
273 min, |
|
274 max; |
|
275 |
|
276 // When dragging, reduce the range by the extent size and offset. |
|
277 if (d3_svg_brushDrag) { |
|
278 range[0] -= offset; |
|
279 range[1] -= size + offset; |
|
280 } |
|
281 |
|
282 // Clamp the mouse so that the extent fits within the range extent. |
|
283 min = Math.max(range[0], Math.min(range[1], mouse[i])); |
|
284 |
|
285 // Compute the new extent bounds. |
|
286 if (d3_svg_brushDrag) { |
|
287 max = (min += offset) + size; |
|
288 } else { |
|
289 |
|
290 // If the ALT key is pressed, then preserve the center of the extent. |
|
291 if (d3_svg_brushCenter) offset = Math.max(range[0], Math.min(range[1], 2 * d3_svg_brushCenter[i] - min)); |
|
292 |
|
293 // Compute the min and max of the offset and mouse. |
|
294 if (offset < min) { |
|
295 max = min; |
|
296 min = offset; |
|
297 } else { |
|
298 max = offset; |
|
299 } |
|
300 } |
|
301 |
|
302 // Update the stored bounds. |
|
303 d3_svg_brushExtent[0][i] = min; |
|
304 d3_svg_brushExtent[1][i] = max; |
|
305 } |
|
306 |
|
307 function d3_svg_brushUp() { |
|
308 if (d3_svg_brushOffset) { |
|
309 d3_svg_brushMove(); |
|
310 d3.select(d3_svg_brushTarget).selectAll(".resize").style("pointer-events", d3_svg_brush.empty() ? "none" : "all"); |
|
311 d3_svg_brushDispatch("brushend"); |
|
312 d3_svg_brush = |
|
313 d3_svg_brushDispatch = |
|
314 d3_svg_brushTarget = |
|
315 d3_svg_brushX = |
|
316 d3_svg_brushY = |
|
317 d3_svg_brushExtent = |
|
318 d3_svg_brushDrag = |
|
319 d3_svg_brushResize = |
|
320 d3_svg_brushCenter = |
|
321 d3_svg_brushOffset = null; |
|
322 d3_eventCancel(); |
|
323 } |
|
324 } |
|
325 |
|
326 var d3_svg_brushCursor = { |
|
327 n: "ns-resize", |
|
328 e: "ew-resize", |
|
329 s: "ns-resize", |
|
330 w: "ew-resize", |
|
331 nw: "nwse-resize", |
|
332 ne: "nesw-resize", |
|
333 se: "nwse-resize", |
|
334 sw: "nesw-resize" |
|
335 }; |