在 D3.js 中创建一个可拖动、可调整大小并带有旋转手柄的框?

问题描述 投票:0回答:1

我正在开发一个项目,需要使用 D3.js 创建一个盒子,并且我想实现以下功能:

主框应使用该元素创建。 在盒子的每个角上,我想添加一个小圆圈,用作缩放和旋转锚点。 当用户单击并拖动任何这些角圆时,该框应根据鼠标移动放大或缩小。 如果用户在拖动圆角时按住“Shift”键,则应该触发旋转而不是缩放。

当拖动框上的任何其他位置(不是角圆上)时,应该拖动框

我已经开始使用 D3.js 创建基本的盒子,但我在集成拖动、缩放和旋转功能方面遇到了困难。有人可以提供指导或代码示例来实现这种交互行为吗?

这是我当前的代码片段:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Rotating Rectangle with D3</title>
    <script src="https://d3js.org/d3.v5.min.js"></script>
  </head>
  <body>
    <script>
      // Set up SVG container
      const svgWidth = 400;
      const svgHeight = 400;

      const svg = d3
        .select("body")
        .append("svg")
        .attr("width", svgWidth)
        .attr("height", svgHeight);

      // Set up rectangle
      let rectWidth = 100;
      let rectHeight = 50;

      const rectangle = svg
        .append("rect")
        .attr("x", (svgWidth - rectWidth) / 2)
        .attr("y", (svgHeight - rectHeight) / 2)
        .attr("width", rectWidth)
        .attr("height", rectHeight)
        .attr("fill", "blue")
        .attr(
          "transform",
          "rotate(0, " + svgWidth / 2 + ", " + svgHeight / 2 + ")"
        );

      // Set up rotation anchor
      const rotationAnchor = svg
        .append("circle")
        .attr("cx", svgWidth / 2)
        .attr("cy", (svgHeight - rectHeight) / 2)
        .attr("r", 8)
        .attr("fill", "red")
        .call(d3.drag().on("drag", rotateHandler));

      // Set up resize anchor for width
      const widthResizeAnchor = svg
        .append("circle")
        .attr("cx", (svgWidth + rectWidth) / 2)
        .attr("cy", (svgHeight - rectHeight) / 2)
        .attr("r", 8)
        .attr("fill", "green")
        .call(d3.drag().on("drag", widthResizeHandler));

      // Set up resize anchor for height
      const heightResizeAnchor = svg
        .append("circle")
        .attr("cx", (svgWidth + rectWidth) / 2)
        .attr("cy", (svgHeight + rectHeight) / 2)
        .attr("r", 8)
        .attr("fill", "purple")
        .call(d3.drag().on("drag", heightResizeHandler));

      // Drag behavior for rotation anchor
      function rotateHandler() {
        const mouseX = d3.event.x;
        const mouseY = d3.event.y;

        // Calculate angle of rotation based on mouse position
        const angle =
          Math.atan2(mouseY - svgHeight / 2, mouseX - svgWidth / 2) *
          (180 / Math.PI);

        // Update rectangle's rotation around its center
        rectangle.attr(
          "transform",
          "translate(" +
            svgWidth / 2 +
            "," +
            svgHeight / 2 +
            ") rotate(" +
            angle +
            ") translate(" +
            -(svgWidth / 2) +
            "," +
            -(svgHeight / 2) +
            ")"
        );
      }

      // Drag behavior for width resize anchor
      function widthResizeHandler() {
        const mouseX = d3.event.x;

        // Calculate the new width
        rectWidth = mouseX - rectangle.attr("x");

        // Update rectangle's width
        rectangle.attr("width", rectWidth);
      }

      // Drag behavior for height resize anchor
      function heightResizeHandler() {
        const mouseY = d3.event.y;

        // Calculate the new height
        rectHeight = mouseY - rectangle.attr("y");

        // Update rectangle's height
        rectangle.attr("height", rectHeight);
      }
    </script>
  </body>
</html>

使用 d3.js 的交互式框

javascript d3.js
1个回答
0
投票

我做了一个小程序来帮助你开始。再次需要进行一些修复..但我认为您可以找到解决方案来完成您的解决方案

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>D3.js Resizable and Draggable Box</title>
  <script src="https://d3js.org/d3.v6.min.js"></script>
  <style>
    svg {
      border: 1px solid #ccc;
    }
    .box {
      fill: lightblue;
      stroke: #333;
    }
    .handle {
      fill: white;
      stroke: #333;
      cursor: pointer;
    }
  </style>
</head>
<body>

<script>
  const width = 400;
  const height = 300;
  const handleRadius = 4;

  // Create SVG container
  const svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);

  const drag = d3.drag()
   .on("start", dragStart)
   .on("drag", dragging)
   .on("end", dragEnd);
  // Create resizable and draggable box with handles
  const box = svg.append("rect")
    .attr("x", 50)
    .attr("y", 50)
    .attr("width", 200)
    .attr("height", 150)
    .attr("class", "box")
    .style("cursor", "pointer")
    .call(drag);



  function dragStart(event) {
    box.raise().classed("active", true);
  }

  function dragging(event) {
    const newX = Math.max(0, Math.min(width - +box.attr("width"), event.x));
    const newY = Math.max(0, Math.min(height - +box.attr("height"), event.y));

    box.attr("x", newX);
    box.attr("y", newY);

    // Update handles' positions
    handles.data([
      { x: newX, y: newY ,cursor: "nw-resize"},
      { x: newX + +box.attr("width"), y: newY, cursor: "ne-resize" },
      { x: newX, y: newY + +box.attr("height"), cursor: "sw-resize" },
      { x: newX + +box.attr("width"), y: newY + +box.attr("height") , cursor: "se-resize"}
    ])
    .attr("cx", d => d.x)
    .attr("cy", d => d.y);
  }

  function dragEnd(event) {
    box.classed("active", false);
  }

  const handles = svg.selectAll("circle")
    .data([
      { x: 50, y: 50, cursor: "nw-resize" },
      { x: 250, y: 50, cursor: "ne-resize" },
      { x: 50, y: 200, cursor: "sw-resize" },
      { x: 250, y: 200, cursor: "se-resize" }
    ])
    .enter().append("circle")
    .attr("class", "handle")
    .attr("cx", d => d.x)
    .attr("cy", d => d.y)
    .attr("r", handleRadius)
    .style("cursor", d => d.cursor)
    .call(d3.drag()
      .on("start", handleDragStart)
      .on("drag", handleDrag)
    );

let rotating = false;

  d3.select("body")
    .on("keydown", (event) => {
      if (event.key === "Shift") {
        rotating = true;
      }
    })
    .on("keyup", (event) => {
      if (event.key === "Shift") {
        rotating = false;
      }
    });
    
  function handleDragStart(event, d) {
        
    d.startX = event.x;
    d.startY = event.y;
    d.initialBox = {
      x: +box.attr("x"),
      y: +box.attr("y"),
      width: +box.attr("width"),
      height: +box.attr("height"),
      rotation: +box.attr("transform")?.split("(")[1].split(")")[0] || 0
    };

  }

  function handleDrag(event, d) {

    const dx = event.x - d.startX;
    const dy = event.y - d.startY;
 if (rotating) {
      const angle = Math.atan2(event.y - height / 2, event.x - width / 2);
      box.attr("transform", `translate(${width / 2},${height / 2}) rotate(${angle * (180 / Math.PI) + 90}) translate(${-

width / 2},${-height / 2})`);

      handles.attr("cx", (d, i) => {
        const radians = (angle + 90) * (Math.PI / 180);
        const distance = Math.sqrt((d.x - box.attr("x")) ** 2 + (d.y - box.attr("y")) ** 2);
        return +box.attr("x") + distance * Math.cos(radians);
      })
        .attr("cy", (d, i) => {
          const radians = (angle + 90) * (Math.PI / 180);
          const distance = Math.sqrt((d.x - box.attr("x")) ** 2 + (d.y - box.attr("y")) ** 2);
          return +box.attr("y") + distance * Math.sin(radians);
        });

    } else {
    if (d.cursor.includes("nw")) {
      box.attr("x", Math.min(d.initialBox.x + d.initialBox.width, Math.max(0, d.initialBox.x + dx)));
      box.attr("y", Math.min(d.initialBox.y + d.initialBox.height, Math.max(0, d.initialBox.y + dy)));
      box.attr("width", Math.max(0, d.initialBox.width - dx));
      box.attr("height", Math.max(0, d.initialBox.height - dy));
    } else if  (d.cursor.includes("se")) {
      box.attr("width", Math.max(0, d.initialBox.width + dx));
      box.attr("height", Math.max(0, d.initialBox.height + dy));
    } else if  (d.cursor.includes("sw")) {
        box.attr("x", Math.min(d.initialBox.x + d.initialBox.width, Math.max(0, d.initialBox.x + dx)));
      box.attr("width", Math.max(0, d.initialBox.width - dx));
      box.attr("height", Math.max(0, d.initialBox.height + dy));

    } else if  (d.cursor.includes("ne")) {
        box.attr("y", Math.min(d.initialBox.y + d.initialBox.height, Math.max(0, d.initialBox.y + dy)));
      box.attr("width", Math.max(0, d.initialBox.width + dx));
      box.attr("height", Math.max(0, d.initialBox.height - dy));
    }
}

    // Update the position of all circles during resizing
    handles.attr("cx", (d, i) => {
        let t = 0;
      if( i == 0){
        t = +box.attr("x");
      } else if(i==1 ){
        t = +box.attr("x") + +box.attr("width");
      } else if (i == 2){
        t = +box.attr("x")
      } else if (i == 3){
        t = +box.attr("x") + +box.attr("width");
      }
      
      //const t = (i % 2 === 0) ? +box.attr("x") : +box.attr("x") + +box.attr("width");
      //console.log("cx", i, t);
      return t;
    })
      .attr("cy", (d, i) => {
      let t = 0;
      if( i == 0)
      {
        t = +box.attr("y");
      } else if( i == 1){
        t = +box.attr("y");
      } else if( i ==2){
        t = +box.attr("y") + +box.attr("height");
      }else if( i == 3){
        t = +box.attr("y") + +box.attr("height");
      }
      //console.log("cy", i, t);
      return t;
      //(i < 2) ? +box.attr("y") : +box.attr("y") + +box.attr("height")
     });
  }
</script>

</body>
</html>

© www.soinside.com 2019 - 2024. All rights reserved.