Sample code to convert lines to curves, duplicate them, move them around, etc.

PHOTO EMBED

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;
content_copyCOPY