JSFiddle - React, Tailwind, and code Playground
by Rajesh Danabal
HTML
<script src="https://cdn.jsdelivr.net/npm/d3-quadtree@3"></script>
<canvas id="canvas" width="600" height="400" style="border:1px solid #ccc;"></canvas>
TypeScript
// Assuming D3 is loaded from CDN
const { quadtree } = d3
class InteractionManager {
constructor(canvas, context, shapes, handlers = {}) {
this.canvas = canvas
this.context = context
this.shapes = shapes
this.handlers = handlers
this.state = {
draggingShape: null,
draggingCanvas: false,
scale: 1,
offsetX: 0,
offsetY: 0,
lastX: 0,
lastY: 0,
}
this.quad = this.buildQuadTree()
this.bindEvents()
}
buildQuadTree() {
return quadtree()
.x((d) => d.x + d.width / 2)
.y((d) => d.y + d.height / 2)
.addAll(this.shapes)
}
toWorldCoords(x, y) {
return {
x: (x - this.state.offsetX) / this.state.scale,
y: (y - this.state.offsetY) / this.state.scale,
}
}
getShapeAt(x, y) {
let found = null
this.quad.visit((node, x0, y0, x1, y1) => {
if (!node.length) {
for (const shape of node.data ? [node.data] : []) {
if (
x >= shape.x &&
x <= shape.x + shape.width &&
y >= shape.y &&
y <= shape.y + shape.height
) {
found = shape
return true
}
}
}
return false
})
return found
}
bindEvents() {
const getMousePos = (e) => ({
x: e.offsetX,
y: e.offsetY,
})
this.canvas.addEventListener("mousedown", (e) => {
const { x, y } = getMousePos(e)
const world = this.toWorldCoords(x, y)
this.state.lastX = x
this.state.lastY = y
const target = this.getShapeAt(world.x, world.y)
if (e.button === 0) {
if (target) {
this.state.draggingShape = target
this.handlers.onShapeDragStart?.(target, e)
} else {
this.state.draggingCanvas = true
this.handlers.onCanvasDragStart?.(e)
}
}
})
this.canvas.addEventListener("mousemove", (e) => {
const { x, y } = getMousePos(e)
const dx = x -...