import * as d3 from 'd3'; import { EventDispatcher } from '../EventDispatcher'; import { ShapeRenderer } from '../renderers/shapes/ShapeRenderer'; import { Node } from '../types/Node'; import { Point } from '../types/Point'; class DragManager extends EventDispatcher { private readonly _canvas: HTMLCanvasElement; private _quadtree: d3.Quadtree; private _renderers: Map; private _transform: d3.ZoomTransform; private _x: d3.ScaleLinear; private _y: d3.ScaleLinear; constructor (canvas: HTMLCanvasElement, x: d3.ScaleLinear, y: d3.ScaleLinear) { super(); this._canvas = canvas; this._quadtree = d3.quadtree(); this._renderers = new Map(); this._transform = d3.zoomIdentity; this._x = x; this._y = y; const drag = d3.drag() .container(canvas) .subject(this._dragSubject.bind(this)) .on('start', this._dragStart.bind(this)) .on('drag', this._drag.bind(this)) .on('end', this._dragEnd.bind(this)); d3.select(canvas) .call(drag); } setShapes (renderers: ShapeRenderer[]) { this._renderers = new Map(); const nodes = renderers .map(renderer => renderer.nodes()) .reduce((acc, cur) => acc.concat(cur), []); renderers.forEach(renderer => { renderer.nodes().forEach(node => { this._renderers.set(node, renderer); }); }); this._quadtree = d3.quadtree() .x(n => n.x) .y(n => n.y) .addAll(nodes); } setTransform (transform: d3.ZoomTransform): this { this._transform = transform; return this; } private _drag (): void { const mouse = d3.mouse(this._canvas); const node = d3.event.subject; const worldx = this._transform.invertX(mouse[0]); const worldy = this._transform.invertY(mouse[1]); const x = this._x.invert(worldx); const y = this._y.invert(worldy); node.x = x; node.y = y; this.dispatchEvent({ type: 'drag', node: node }); } private _dragEnd (): void { this._quadtree.add(d3.event.subject); } private _dragStart (): void { this._quadtree.remove(d3.event.subject); } private _dragSubject (datum: Point, index: number, groups: any): Point { const worldx = this._transform.invertX(d3.event.x); const worldy = this._transform.invertY(d3.event.y); const x = this._x.invert(worldx); const y = this._y.invert(worldy); const nearest = this._findNode(x, y); return nearest || datum; } private _findNode (x: number, y: number): Node | void { const node = this._quadtree.find(x, y); if (node) { const shape = this._renderers.get(node); if (shape && shape.containsPoint(node, x, y, this._transform.k)) { return node; } } } } export { DragManager }