// Local canvas rendering. // Emit input events and receive draw calls seperately - these must be piped // together externally if desired. import { line, curveLinear } from "d3-shape" import { canvas } from "./elements.js" // TODO: switch to curve interpolation that respects mouse points based on velocity const lineFn = line() .x((d) => d[0]) .y((d) => d[1]) .curve(curveLinear) const pathElems = new Map() export const STROKE_COLOUR = "blue" export const STROKE_RADIUS = 2 export const input = new EventTarget() export const renderPath = (id, points) => { let pathElem = pathElems.get(id) if (pathElem == null) { pathElem = document.createElementNS("http://www.w3.org/2000/svg", "g") pathElem.setAttribute("stroke", STROKE_COLOUR) pathElem.setAttribute("stroke-width", STROKE_RADIUS * 2) pathElem.setAttribute("fill", "none") pathElem.setAttribute("pointer-events", "none") pathElem.setAttribute("marker-start", "url(#dot)") pathElem.setAttribute("marker-end", "url(#dot)") canvas.appendChild(pathElem) pathElems.set(id, pathElem) } pathElem.innerHTML = "" if (points.length == 0) { return pathElem } // Push a fake path split to generate the last path points.push([-1, -1, false]) let subpath = [] for (const point of points) { if (point[0] === undefined) { continue } if (point[2] === false) { if (subpath.length == 1) { const subpathElem = document.createElementNS( "http://www.w3.org/2000/svg", "circle", ) subpathElem.setAttribute("stroke", "none") subpathElem.setAttribute("fill", STROKE_COLOUR) subpathElem.setAttribute("cx", subpath[0][0]) subpathElem.setAttribute("cy", subpath[0][1]) subpathElem.setAttribute("r", STROKE_RADIUS) pathElem.appendChild(subpathElem) } else if (subpath.length > 0) { const subpathElem = document.createElementNS( "http://www.w3.org/2000/svg", "path", ) subpathElem.setAttribute("d", lineFn(subpath)) pathElem.appendChild(subpathElem) } subpath = [] continue } subpath.push(point) } return pathElem } export const clear = () => { while (canvas.children[1]) { canvas.removeChild(canvas.children[1]) } } // Note that the PointerEvent is passed as the detail in these 'stroke events'. canvas.addEventListener("pointerdown", (e) => { if (e.buttons & 1) { input.dispatchEvent(new CustomEvent("strokestart", { detail: e })) } }) canvas.addEventListener("pointerenter", (e) => { if (e.buttons & 1) { input.dispatchEvent(new CustomEvent("strokestart", { detail: e })) } }) canvas.addEventListener("pointerup", (e) => { if (e.buttons & 1) { input.dispatchEvent(new CustomEvent("strokeend", { detail: e })) } }) canvas.addEventListener("pointerleave", (e) => { if (e.buttons & 1) { input.dispatchEvent(new CustomEvent("strokeend", { detail: e })) } }) canvas.addEventListener("pointermove", (e) => { if (e.buttons & 1) { input.dispatchEvent(new CustomEvent("strokemove", { detail: e })) } }) canvas.addEventListener("touchmove", (e) => e.preventDefault())