source: node_modules/d3-brush/src/brush.js

Last change on this file was e4c61dd, checked in by istevanoska <ilinastevanoska@…>, 6 months ago

Prototype 1.1

  • Property mode set to 100644
File size: 17.8 KB
Line 
1import {dispatch} from "d3-dispatch";
2import {dragDisable, dragEnable} from "d3-drag";
3import {interpolate} from "d3-interpolate";
4import {pointer, select} from "d3-selection";
5import {interrupt} from "d3-transition";
6import constant from "./constant.js";
7import BrushEvent from "./event.js";
8import noevent, {nopropagation} from "./noevent.js";
9
10var MODE_DRAG = {name: "drag"},
11 MODE_SPACE = {name: "space"},
12 MODE_HANDLE = {name: "handle"},
13 MODE_CENTER = {name: "center"};
14
15const {abs, max, min} = Math;
16
17function number1(e) {
18 return [+e[0], +e[1]];
19}
20
21function number2(e) {
22 return [number1(e[0]), number1(e[1])];
23}
24
25var X = {
26 name: "x",
27 handles: ["w", "e"].map(type),
28 input: function(x, e) { return x == null ? null : [[+x[0], e[0][1]], [+x[1], e[1][1]]]; },
29 output: function(xy) { return xy && [xy[0][0], xy[1][0]]; }
30};
31
32var Y = {
33 name: "y",
34 handles: ["n", "s"].map(type),
35 input: function(y, e) { return y == null ? null : [[e[0][0], +y[0]], [e[1][0], +y[1]]]; },
36 output: function(xy) { return xy && [xy[0][1], xy[1][1]]; }
37};
38
39var XY = {
40 name: "xy",
41 handles: ["n", "w", "e", "s", "nw", "ne", "sw", "se"].map(type),
42 input: function(xy) { return xy == null ? null : number2(xy); },
43 output: function(xy) { return xy; }
44};
45
46var cursors = {
47 overlay: "crosshair",
48 selection: "move",
49 n: "ns-resize",
50 e: "ew-resize",
51 s: "ns-resize",
52 w: "ew-resize",
53 nw: "nwse-resize",
54 ne: "nesw-resize",
55 se: "nwse-resize",
56 sw: "nesw-resize"
57};
58
59var flipX = {
60 e: "w",
61 w: "e",
62 nw: "ne",
63 ne: "nw",
64 se: "sw",
65 sw: "se"
66};
67
68var flipY = {
69 n: "s",
70 s: "n",
71 nw: "sw",
72 ne: "se",
73 se: "ne",
74 sw: "nw"
75};
76
77var signsX = {
78 overlay: +1,
79 selection: +1,
80 n: null,
81 e: +1,
82 s: null,
83 w: -1,
84 nw: -1,
85 ne: +1,
86 se: +1,
87 sw: -1
88};
89
90var signsY = {
91 overlay: +1,
92 selection: +1,
93 n: -1,
94 e: null,
95 s: +1,
96 w: null,
97 nw: -1,
98 ne: -1,
99 se: +1,
100 sw: +1
101};
102
103function type(t) {
104 return {type: t};
105}
106
107// Ignore right-click, since that should open the context menu.
108function defaultFilter(event) {
109 return !event.ctrlKey && !event.button;
110}
111
112function defaultExtent() {
113 var svg = this.ownerSVGElement || this;
114 if (svg.hasAttribute("viewBox")) {
115 svg = svg.viewBox.baseVal;
116 return [[svg.x, svg.y], [svg.x + svg.width, svg.y + svg.height]];
117 }
118 return [[0, 0], [svg.width.baseVal.value, svg.height.baseVal.value]];
119}
120
121function defaultTouchable() {
122 return navigator.maxTouchPoints || ("ontouchstart" in this);
123}
124
125// Like d3.local, but with the name “__brush” rather than auto-generated.
126function local(node) {
127 while (!node.__brush) if (!(node = node.parentNode)) return;
128 return node.__brush;
129}
130
131function empty(extent) {
132 return extent[0][0] === extent[1][0]
133 || extent[0][1] === extent[1][1];
134}
135
136export function brushSelection(node) {
137 var state = node.__brush;
138 return state ? state.dim.output(state.selection) : null;
139}
140
141export function brushX() {
142 return brush(X);
143}
144
145export function brushY() {
146 return brush(Y);
147}
148
149export default function() {
150 return brush(XY);
151}
152
153function brush(dim) {
154 var extent = defaultExtent,
155 filter = defaultFilter,
156 touchable = defaultTouchable,
157 keys = true,
158 listeners = dispatch("start", "brush", "end"),
159 handleSize = 6,
160 touchending;
161
162 function brush(group) {
163 var overlay = group
164 .property("__brush", initialize)
165 .selectAll(".overlay")
166 .data([type("overlay")]);
167
168 overlay.enter().append("rect")
169 .attr("class", "overlay")
170 .attr("pointer-events", "all")
171 .attr("cursor", cursors.overlay)
172 .merge(overlay)
173 .each(function() {
174 var extent = local(this).extent;
175 select(this)
176 .attr("x", extent[0][0])
177 .attr("y", extent[0][1])
178 .attr("width", extent[1][0] - extent[0][0])
179 .attr("height", extent[1][1] - extent[0][1]);
180 });
181
182 group.selectAll(".selection")
183 .data([type("selection")])
184 .enter().append("rect")
185 .attr("class", "selection")
186 .attr("cursor", cursors.selection)
187 .attr("fill", "#777")
188 .attr("fill-opacity", 0.3)
189 .attr("stroke", "#fff")
190 .attr("shape-rendering", "crispEdges");
191
192 var handle = group.selectAll(".handle")
193 .data(dim.handles, function(d) { return d.type; });
194
195 handle.exit().remove();
196
197 handle.enter().append("rect")
198 .attr("class", function(d) { return "handle handle--" + d.type; })
199 .attr("cursor", function(d) { return cursors[d.type]; });
200
201 group
202 .each(redraw)
203 .attr("fill", "none")
204 .attr("pointer-events", "all")
205 .on("mousedown.brush", started)
206 .filter(touchable)
207 .on("touchstart.brush", started)
208 .on("touchmove.brush", touchmoved)
209 .on("touchend.brush touchcancel.brush", touchended)
210 .style("touch-action", "none")
211 .style("-webkit-tap-highlight-color", "rgba(0,0,0,0)");
212 }
213
214 brush.move = function(group, selection, event) {
215 if (group.tween) {
216 group
217 .on("start.brush", function(event) { emitter(this, arguments).beforestart().start(event); })
218 .on("interrupt.brush end.brush", function(event) { emitter(this, arguments).end(event); })
219 .tween("brush", function() {
220 var that = this,
221 state = that.__brush,
222 emit = emitter(that, arguments),
223 selection0 = state.selection,
224 selection1 = dim.input(typeof selection === "function" ? selection.apply(this, arguments) : selection, state.extent),
225 i = interpolate(selection0, selection1);
226
227 function tween(t) {
228 state.selection = t === 1 && selection1 === null ? null : i(t);
229 redraw.call(that);
230 emit.brush();
231 }
232
233 return selection0 !== null && selection1 !== null ? tween : tween(1);
234 });
235 } else {
236 group
237 .each(function() {
238 var that = this,
239 args = arguments,
240 state = that.__brush,
241 selection1 = dim.input(typeof selection === "function" ? selection.apply(that, args) : selection, state.extent),
242 emit = emitter(that, args).beforestart();
243
244 interrupt(that);
245 state.selection = selection1 === null ? null : selection1;
246 redraw.call(that);
247 emit.start(event).brush(event).end(event);
248 });
249 }
250 };
251
252 brush.clear = function(group, event) {
253 brush.move(group, null, event);
254 };
255
256 function redraw() {
257 var group = select(this),
258 selection = local(this).selection;
259
260 if (selection) {
261 group.selectAll(".selection")
262 .style("display", null)
263 .attr("x", selection[0][0])
264 .attr("y", selection[0][1])
265 .attr("width", selection[1][0] - selection[0][0])
266 .attr("height", selection[1][1] - selection[0][1]);
267
268 group.selectAll(".handle")
269 .style("display", null)
270 .attr("x", function(d) { return d.type[d.type.length - 1] === "e" ? selection[1][0] - handleSize / 2 : selection[0][0] - handleSize / 2; })
271 .attr("y", function(d) { return d.type[0] === "s" ? selection[1][1] - handleSize / 2 : selection[0][1] - handleSize / 2; })
272 .attr("width", function(d) { return d.type === "n" || d.type === "s" ? selection[1][0] - selection[0][0] + handleSize : handleSize; })
273 .attr("height", function(d) { return d.type === "e" || d.type === "w" ? selection[1][1] - selection[0][1] + handleSize : handleSize; });
274 }
275
276 else {
277 group.selectAll(".selection,.handle")
278 .style("display", "none")
279 .attr("x", null)
280 .attr("y", null)
281 .attr("width", null)
282 .attr("height", null);
283 }
284 }
285
286 function emitter(that, args, clean) {
287 var emit = that.__brush.emitter;
288 return emit && (!clean || !emit.clean) ? emit : new Emitter(that, args, clean);
289 }
290
291 function Emitter(that, args, clean) {
292 this.that = that;
293 this.args = args;
294 this.state = that.__brush;
295 this.active = 0;
296 this.clean = clean;
297 }
298
299 Emitter.prototype = {
300 beforestart: function() {
301 if (++this.active === 1) this.state.emitter = this, this.starting = true;
302 return this;
303 },
304 start: function(event, mode) {
305 if (this.starting) this.starting = false, this.emit("start", event, mode);
306 else this.emit("brush", event);
307 return this;
308 },
309 brush: function(event, mode) {
310 this.emit("brush", event, mode);
311 return this;
312 },
313 end: function(event, mode) {
314 if (--this.active === 0) delete this.state.emitter, this.emit("end", event, mode);
315 return this;
316 },
317 emit: function(type, event, mode) {
318 var d = select(this.that).datum();
319 listeners.call(
320 type,
321 this.that,
322 new BrushEvent(type, {
323 sourceEvent: event,
324 target: brush,
325 selection: dim.output(this.state.selection),
326 mode,
327 dispatch: listeners
328 }),
329 d
330 );
331 }
332 };
333
334 function started(event) {
335 if (touchending && !event.touches) return;
336 if (!filter.apply(this, arguments)) return;
337
338 var that = this,
339 type = event.target.__data__.type,
340 mode = (keys && event.metaKey ? type = "overlay" : type) === "selection" ? MODE_DRAG : (keys && event.altKey ? MODE_CENTER : MODE_HANDLE),
341 signX = dim === Y ? null : signsX[type],
342 signY = dim === X ? null : signsY[type],
343 state = local(that),
344 extent = state.extent,
345 selection = state.selection,
346 W = extent[0][0], w0, w1,
347 N = extent[0][1], n0, n1,
348 E = extent[1][0], e0, e1,
349 S = extent[1][1], s0, s1,
350 dx = 0,
351 dy = 0,
352 moving,
353 shifting = signX && signY && keys && event.shiftKey,
354 lockX,
355 lockY,
356 points = Array.from(event.touches || [event], t => {
357 const i = t.identifier;
358 t = pointer(t, that);
359 t.point0 = t.slice();
360 t.identifier = i;
361 return t;
362 });
363
364 interrupt(that);
365 var emit = emitter(that, arguments, true).beforestart();
366
367 if (type === "overlay") {
368 if (selection) moving = true;
369 const pts = [points[0], points[1] || points[0]];
370 state.selection = selection = [[
371 w0 = dim === Y ? W : min(pts[0][0], pts[1][0]),
372 n0 = dim === X ? N : min(pts[0][1], pts[1][1])
373 ], [
374 e0 = dim === Y ? E : max(pts[0][0], pts[1][0]),
375 s0 = dim === X ? S : max(pts[0][1], pts[1][1])
376 ]];
377 if (points.length > 1) move(event);
378 } else {
379 w0 = selection[0][0];
380 n0 = selection[0][1];
381 e0 = selection[1][0];
382 s0 = selection[1][1];
383 }
384
385 w1 = w0;
386 n1 = n0;
387 e1 = e0;
388 s1 = s0;
389
390 var group = select(that)
391 .attr("pointer-events", "none");
392
393 var overlay = group.selectAll(".overlay")
394 .attr("cursor", cursors[type]);
395
396 if (event.touches) {
397 emit.moved = moved;
398 emit.ended = ended;
399 } else {
400 var view = select(event.view)
401 .on("mousemove.brush", moved, true)
402 .on("mouseup.brush", ended, true);
403 if (keys) view
404 .on("keydown.brush", keydowned, true)
405 .on("keyup.brush", keyupped, true)
406
407 dragDisable(event.view);
408 }
409
410 redraw.call(that);
411 emit.start(event, mode.name);
412
413 function moved(event) {
414 for (const p of event.changedTouches || [event]) {
415 for (const d of points)
416 if (d.identifier === p.identifier) d.cur = pointer(p, that);
417 }
418 if (shifting && !lockX && !lockY && points.length === 1) {
419 const point = points[0];
420 if (abs(point.cur[0] - point[0]) > abs(point.cur[1] - point[1]))
421 lockY = true;
422 else
423 lockX = true;
424 }
425 for (const point of points)
426 if (point.cur) point[0] = point.cur[0], point[1] = point.cur[1];
427 moving = true;
428 noevent(event);
429 move(event);
430 }
431
432 function move(event) {
433 const point = points[0], point0 = point.point0;
434 var t;
435
436 dx = point[0] - point0[0];
437 dy = point[1] - point0[1];
438
439 switch (mode) {
440 case MODE_SPACE:
441 case MODE_DRAG: {
442 if (signX) dx = max(W - w0, min(E - e0, dx)), w1 = w0 + dx, e1 = e0 + dx;
443 if (signY) dy = max(N - n0, min(S - s0, dy)), n1 = n0 + dy, s1 = s0 + dy;
444 break;
445 }
446 case MODE_HANDLE: {
447 if (points[1]) {
448 if (signX) w1 = max(W, min(E, points[0][0])), e1 = max(W, min(E, points[1][0])), signX = 1;
449 if (signY) n1 = max(N, min(S, points[0][1])), s1 = max(N, min(S, points[1][1])), signY = 1;
450 } else {
451 if (signX < 0) dx = max(W - w0, min(E - w0, dx)), w1 = w0 + dx, e1 = e0;
452 else if (signX > 0) dx = max(W - e0, min(E - e0, dx)), w1 = w0, e1 = e0 + dx;
453 if (signY < 0) dy = max(N - n0, min(S - n0, dy)), n1 = n0 + dy, s1 = s0;
454 else if (signY > 0) dy = max(N - s0, min(S - s0, dy)), n1 = n0, s1 = s0 + dy;
455 }
456 break;
457 }
458 case MODE_CENTER: {
459 if (signX) w1 = max(W, min(E, w0 - dx * signX)), e1 = max(W, min(E, e0 + dx * signX));
460 if (signY) n1 = max(N, min(S, n0 - dy * signY)), s1 = max(N, min(S, s0 + dy * signY));
461 break;
462 }
463 }
464
465 if (e1 < w1) {
466 signX *= -1;
467 t = w0, w0 = e0, e0 = t;
468 t = w1, w1 = e1, e1 = t;
469 if (type in flipX) overlay.attr("cursor", cursors[type = flipX[type]]);
470 }
471
472 if (s1 < n1) {
473 signY *= -1;
474 t = n0, n0 = s0, s0 = t;
475 t = n1, n1 = s1, s1 = t;
476 if (type in flipY) overlay.attr("cursor", cursors[type = flipY[type]]);
477 }
478
479 if (state.selection) selection = state.selection; // May be set by brush.move!
480 if (lockX) w1 = selection[0][0], e1 = selection[1][0];
481 if (lockY) n1 = selection[0][1], s1 = selection[1][1];
482
483 if (selection[0][0] !== w1
484 || selection[0][1] !== n1
485 || selection[1][0] !== e1
486 || selection[1][1] !== s1) {
487 state.selection = [[w1, n1], [e1, s1]];
488 redraw.call(that);
489 emit.brush(event, mode.name);
490 }
491 }
492
493 function ended(event) {
494 nopropagation(event);
495 if (event.touches) {
496 if (event.touches.length) return;
497 if (touchending) clearTimeout(touchending);
498 touchending = setTimeout(function() { touchending = null; }, 500); // Ghost clicks are delayed!
499 } else {
500 dragEnable(event.view, moving);
501 view.on("keydown.brush keyup.brush mousemove.brush mouseup.brush", null);
502 }
503 group.attr("pointer-events", "all");
504 overlay.attr("cursor", cursors.overlay);
505 if (state.selection) selection = state.selection; // May be set by brush.move (on start)!
506 if (empty(selection)) state.selection = null, redraw.call(that);
507 emit.end(event, mode.name);
508 }
509
510 function keydowned(event) {
511 switch (event.keyCode) {
512 case 16: { // SHIFT
513 shifting = signX && signY;
514 break;
515 }
516 case 18: { // ALT
517 if (mode === MODE_HANDLE) {
518 if (signX) e0 = e1 - dx * signX, w0 = w1 + dx * signX;
519 if (signY) s0 = s1 - dy * signY, n0 = n1 + dy * signY;
520 mode = MODE_CENTER;
521 move(event);
522 }
523 break;
524 }
525 case 32: { // SPACE; takes priority over ALT
526 if (mode === MODE_HANDLE || mode === MODE_CENTER) {
527 if (signX < 0) e0 = e1 - dx; else if (signX > 0) w0 = w1 - dx;
528 if (signY < 0) s0 = s1 - dy; else if (signY > 0) n0 = n1 - dy;
529 mode = MODE_SPACE;
530 overlay.attr("cursor", cursors.selection);
531 move(event);
532 }
533 break;
534 }
535 default: return;
536 }
537 noevent(event);
538 }
539
540 function keyupped(event) {
541 switch (event.keyCode) {
542 case 16: { // SHIFT
543 if (shifting) {
544 lockX = lockY = shifting = false;
545 move(event);
546 }
547 break;
548 }
549 case 18: { // ALT
550 if (mode === MODE_CENTER) {
551 if (signX < 0) e0 = e1; else if (signX > 0) w0 = w1;
552 if (signY < 0) s0 = s1; else if (signY > 0) n0 = n1;
553 mode = MODE_HANDLE;
554 move(event);
555 }
556 break;
557 }
558 case 32: { // SPACE
559 if (mode === MODE_SPACE) {
560 if (event.altKey) {
561 if (signX) e0 = e1 - dx * signX, w0 = w1 + dx * signX;
562 if (signY) s0 = s1 - dy * signY, n0 = n1 + dy * signY;
563 mode = MODE_CENTER;
564 } else {
565 if (signX < 0) e0 = e1; else if (signX > 0) w0 = w1;
566 if (signY < 0) s0 = s1; else if (signY > 0) n0 = n1;
567 mode = MODE_HANDLE;
568 }
569 overlay.attr("cursor", cursors[type]);
570 move(event);
571 }
572 break;
573 }
574 default: return;
575 }
576 noevent(event);
577 }
578 }
579
580 function touchmoved(event) {
581 emitter(this, arguments).moved(event);
582 }
583
584 function touchended(event) {
585 emitter(this, arguments).ended(event);
586 }
587
588 function initialize() {
589 var state = this.__brush || {selection: null};
590 state.extent = number2(extent.apply(this, arguments));
591 state.dim = dim;
592 return state;
593 }
594
595 brush.extent = function(_) {
596 return arguments.length ? (extent = typeof _ === "function" ? _ : constant(number2(_)), brush) : extent;
597 };
598
599 brush.filter = function(_) {
600 return arguments.length ? (filter = typeof _ === "function" ? _ : constant(!!_), brush) : filter;
601 };
602
603 brush.touchable = function(_) {
604 return arguments.length ? (touchable = typeof _ === "function" ? _ : constant(!!_), brush) : touchable;
605 };
606
607 brush.handleSize = function(_) {
608 return arguments.length ? (handleSize = +_, brush) : handleSize;
609 };
610
611 brush.keyModifiers = function(_) {
612 return arguments.length ? (keys = !!_, brush) : keys;
613 };
614
615 brush.on = function() {
616 var value = listeners.on.apply(listeners, arguments);
617 return value === listeners ? brush : value;
618 };
619
620 return brush;
621}
Note: See TracBrowser for help on using the repository browser.