Sample code to convert lines to curves, duplicate them, move them around, etc.
Tue Nov 14 2023 20:21:26 GMT+0000 (Coordinated Universal Time)
Saved by @dr_dziedzorm
import React, { useEffect, useState } from "react"; import * as d3 from "d3"; function ControlPoint({ point, updateLine }) { useEffect(() => { const circle = d3.select(`#circle-${point.id}`); circle.call( d3.drag().on("drag", function (event) { point.x = event.x; point.y = event.y; updateLine(); circle.attr("cx", point.x).attr("cy", point.y); }) ); }, [point, updateLine]); return ( <circle id={`circle-${point.id}`} cx={point.x} cy={point.y} r={5} fill="red" stroke="black" /> ); } function LineCanvas({ lineColor, lineWidth }) { const [lines, setLines] = useState([ { id: 0, type: "horizontal", points: [ { id: 0, x: 100, y: 300 }, { id: 1, x: 700, y: 300 } ], isCurve: false }, { id: 1, type: "vertical", points: [ { id: 2, x: 400, y: 100 }, { id: 3, x: 400, y: 500 } ], isCurve: false } ]); const [history, setHistory] = useState([]); const [isDrawing, setIsDrawing] = useState(true); const pushToHistory = () => { setHistory([...history, JSON.parse(JSON.stringify(lines))]); }; const undo = () => { if (history.length === 0) return; const lastState = history.pop(); setLines(lastState); }; useEffect(() => { const svg = d3.select("#svgContainer").select("svg"); svg.on("dblclick", function (event) { const [x, y] = d3.pointer(event); const clickedLine = lines.find((line) => line.points.some( (point) => Math.abs(point.x - x) < 5 && Math.abs(point.y - y) < 5 ) ); if (clickedLine) { clickedLine.isCurve = !clickedLine.isCurve; redraw(); } }); svg.on("click", function (event) { if (!isDrawing) return; const [x, y] = d3.pointer(event); const lastLine = lines[lines.length - 1]; if (lastLine && !lastLine.isCurve) { return; } pushToHistory(); lastLine.points.splice(lastLine.points.length - 1, 0, { id: Date.now(), x, y }); redraw(); }); document.addEventListener("keydown", function (event) { if (event.key === "d") { pushToHistory(); const newLineData = lines[lines.length - 1].points.map((d) => ({ id: Date.now() + d.id, x: d.x + 20, y: d.y + 20 })); const newLine = { ...lines[lines.length - 1], points: newLineData }; setLines([...lines, newLine]); } else if (event.key === "u") { undo(); } else if (event.key === "t") { setIsDrawing((prev) => !prev); } }); redraw(); }, [lines]); const redraw = () => { const svg = d3.select("#svgContainer").select("svg"); svg.selectAll("*").remove(); const line = d3 .line() .x((d) => d.x) .y((d) => d.y) .curve(d3.curveBasis); lines.forEach((lineData) => { const path = svg .append("path") .datum(lineData.points) .attr("d", line) .attr("stroke", lineColor) .attr("stroke-width", lineWidth) .attr("fill", "none") .call( d3.drag().on("drag", function (event) { lineData.points = lineData.points.map((point) => ({ x: point.x + event.dx, y: point.y + event.dy })); redraw(); }) ); if (!lineData.isCurve) { const start = lineData.points[0]; const end = lineData.points[lineData.points.length - 1]; path.attr("d", `M ${start.x},${start.y} L ${end.x},${end.y}`); } lineData.points.forEach((point) => { svg .append("circle") .attr("cx", point.x) .attr("cy", point.y) .attr("r", 5) .attr("fill", "red") .attr("stroke", "black") .call( d3.drag().on("drag", function (event) { point.x = event.x; point.y = event.y; redraw(); }) ); }); }); }; return ( <div id="svgContainer"> <svg width={800} height={600} /> </div> ); } function LineSettings({ setLineColor, setLineWidth }) { return ( <div> <label> Line Color: <input type="color" onChange={(e) => setLineColor(e.target.value)} defaultValue="#000000" /> </label> <label> Line Width: <input type="range" min="1" max="10" onChange={(e) => setLineWidth(e.target.value)} defaultValue="2" /> </label> </div> ); } function App() { const [lineColor, setLineColor] = useState("black"); const [lineWidth, setLineWidth] = useState(2); return ( <div> <LineSettings setLineColor={setLineColor} setLineWidth={setLineWidth} /> <LineCanvas lineColor={lineColor} lineWidth={lineWidth} /> </div> ); } export default App;
Comments