d3js v7,当我用光标拖动圆圈时,圆圈在y坐标中向相反方向移动

d3js v7, the circle moves in the opposite direction in y coordinate when I drag the circle by the cursor

我想在 d3js 中通过拖动来移动一个圆,但是我无法让它在 y 轴方向上移动。

我使用的是d3js的v7,通过多次尝试发现拖动函数中的event.xevent.y是被拖动的光标在图形上的坐标。因此,我尝试用scale变换函数对这两个坐标进行变换,得到svg上显示的坐标。

这里有两个问题。
首先是event.y得到的y坐标与实际垂直移动方向相反。例如,如果我实际上 运行 代码并将圆圈拖动到右上角,则圆圈将移动到右下角。这是在圆初始位置的 y 坐标中的线对称运动; y坐标移动的正负值取反。 event.dy 也是如此。 event.xevent.dx 中的 x 坐标工作正常。此外,d3.pointer(event) 未按预期工作。

第二个问题是圆圈在拖动的开始跳动。我通过查看拖动过程中执行的函数找到了原因。 event.xevent.y 的初始值是圆的坐标初始值,即使经过多次拖动,这导致圆在拖动时总是从圆的初始值开始移动。

我有a program that reproduces the problem.
如何解决这些问题?

谢谢。
这是代码。

  var dataset = [
      {x: 5, y: 20, name: "one"},
      {x: 100, y: 33, name: "two"},
      {x: 250, y: 50, name: "three"},
      {x: 480, y: 90, name: "four"},
    ];

  const width = 400;
  const height = 300;
  const margin = { "top": 30, "bottom": 60, "right": 30, "left": 60 };
  const svgWidth = width + margin.left + margin.right;
  const svgHeight = height + margin.top + margin.bottom;

  var svg = d3.select("#graph-area").append("svg")
      .attr("width", svgWidth)
      .attr("height", svgHeight)
      .style("border-radius", "20px 20px 20px 20px")
      .append("g")
      .attr("transform", `translate(${margin.left}, ${margin.top})`);

  var xScale = d3.scaleLinear()
    .domain([0, d3.max(dataset, function(d) { return d.x; }) + 10])
    .range([0, width]);

  var yScale = d3.scaleLinear()
    .domain([0, d3.max(dataset, function(d) { return d.y; }) + 10])
    .range([height, 0]);

  var axisx = d3.axisBottom(xScale).ticks(5);
  var axisy = d3.axisLeft(yScale).ticks(5);

  var x = svg.append("g")
    .attr("transform", "translate(" + 0 + "," + height + ")")
    .call(axisx)
    .attr("class", "x_axis")

   x.append("text")
        .attr("x", (width - margin.left - margin.right) / 2 + margin.left)
        .attr("y", 35)
        .attr("text-anchor", "middle")
        .attr("font-size", "10pt")
        .attr("font-weight", "bold")
        .text("X Label")
        .attr("class", "x_axis")

  var y = svg.append("g")
    .call(axisy)
    .attr("class", "y_axis")

  y.append("text")
    .attr("x", -(height - margin.top - margin.bottom) / 2 - margin.top)
    .attr("y", -35)
    .attr("transform", "rotate(-90)")
    .attr("text-anchor", "middle")
    .attr("font-weight", "bold")
    .attr("font-size", "10pt")
    .text("Y Label")
    .attr("class", "y_axis")

  var clip = svg.append("defs").append("svg:clipPath")
            .attr("id", "clip")
            .append("svg:rect")
            .attr("width", width )
            .attr("height", height )
            .attr("x", 0) 
            .attr("y", 0);

 
  var circle = svg.append("g")
        .attr("id", "scatterplot")
        .attr("clip-path", "url(#clip)");
  
  circle.selectAll("circle")
    .data(dataset)
    .enter()
    .append("circle")
    .attr("cx", function(d) { return xScale(d.x); })
    .attr("cy", function(d) { return yScale(d.y); })
    .attr("fill", "steelblue")
    .attr("r", 20)
    .on('mouseover',function(elem, data) {
        //console.log("over data name: " + data.name);
    })
    .on('mouseout',function (elem, data) {
        //console.log("out data name: " + data.name);
    })
    .on("mousedown", function(elem, data){
        console.log("down data name: " + data.name);
    })
    .on("mouseup", function(elem, data){
        console.log("up data name: " + data.name);
    })
    .call(d3.drag().on("drag", circleDragged).on("start", circleStartDrag))

    function circleStartDrag(event){
        console.log("start drag");
    }

    function circleDragged(event){
        d3.select(this).attr('cx', xScale(event.x)).attr('cy', yScale(event.y));
    }
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>D3 Scatter Plot</title>
  <script src="https://code.jquery.com/jquery-3.6.0.slim.min.js" integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI=" crossorigin="anonymous"></script>
  <script src="https://d3js.org/d3.v7.min.js"></script>
</head>

<body>
  
  <div id="graph-area" stype="position: relative;"></div
  
</body>

</html>

编辑(拖动功能中坐标计算的感知)

drag函数引用数据中设置的值(cxcy)表示新的光标坐标反映dydx 来自这些值。所以在我的例子中,我将未缩放的数据值设置为 event.subject,因此当我直接拖动到上面时,它将根据 (cx=250, cy=-50)dy=-1 计算为 (cx=250, cy=-40)。这随后反映在 event.y 中。这就是为什么我说的那个圆在拖动的时候y坐标是倒过来的原因。

正确的值设置为(cx=204, cy=150)作为enter()执行时数据的缩放值。当圆圈直接向上拖动时,event.ydy=-1的基础上设置为cy=140。在svg中的像素坐标中,y坐标值减小意味着向上移动,所以圆圈向上移动。此时我需要更新 d.x=event.y 以在下一次拖动时考虑到对 event.subject 值的引用。

这是给定您的代码的预期结果。

您最初使用 scale(d.y) 定位数据点,这会将您的数据值转换为像素值。当您拖动时,您获取像素值 (event.y) 并再次应用缩放 - 缩放像素值,就好像它们是数据一样。

d.y = 0 的数据值被刻度映射到height。如果您拖动一个位于 SVG 顶部的圆圈 (y=0),那么比例尺将 return height 位于 SVG 的底部 - 因此您的反转。

解决方案是只使用 event.y / event.x 值,因为这些值已经表示像素值 - 无需缩放它们。如果你想将像素值转换为数据值,那么你可以使用 scale.invert() - 但这只对更新数据有用,而不是每个圆的像素坐标。

从拖动中删除缩放功能揭示了最后一个问题 - 当拖动与鼠标一起移动时,拖动开始时仍然有一个跳跃:默认情况下拖动主题(被拖动的参考坐标)基准面的 x,y 属性。由于这代表您的数据中的数据值,而不是缩放的像素值,因此您会得到一个跳跃。最快的解决方案是将基准的 x、y 属性重新定义为缩放像素值,并在拖动时更新这些属性。

var dataset = [
      {x: 5, y: 20, name: "one"},
      {x: 100, y: 33, name: "two"},
      {x: 250, y: 50, name: "three"},
      {x: 480, y: 90, name: "four"},
    ];

  const width = 400;
  const height = 300;
  const margin = { "top": 30, "bottom": 60, "right": 30, "left": 60 };
  const svgWidth = width + margin.left + margin.right;
  const svgHeight = height + margin.top + margin.bottom;

  var svg = d3.select("#graph-area").append("svg")
      .attr("width", svgWidth)
      .attr("height", svgHeight)
      .style("border-radius", "20px 20px 20px 20px")
      .append("g")
      .attr("transform", `translate(${margin.left}, ${margin.top})`);

  var xScale = d3.scaleLinear()
    .domain([0, d3.max(dataset, function(d) { return d.x; }) + 10])
    .range([0, width]);

  var yScale = d3.scaleLinear()
    .domain([0, d3.max(dataset, function(d) { return d.y; }) + 10])
    .range([height, 0]);

  var axisx = d3.axisBottom(xScale).ticks(5);
  var axisy = d3.axisLeft(yScale).ticks(5);

  var x = svg.append("g")
    .attr("transform", "translate(" + 0 + "," + height + ")")
    .call(axisx)
    .attr("class", "x_axis")

   x.append("text")
        .attr("x", (width - margin.left - margin.right) / 2 + margin.left)
        .attr("y", 35)
        .attr("text-anchor", "middle")
        .attr("font-size", "10pt")
        .attr("font-weight", "bold")
        .text("X Label")
        .attr("class", "x_axis")

  var y = svg.append("g")
    .call(axisy)
    .attr("class", "y_axis")

  y.append("text")
    .attr("x", -(height - margin.top - margin.bottom) / 2 - margin.top)
    .attr("y", -35)
    .attr("transform", "rotate(-90)")
    .attr("text-anchor", "middle")
    .attr("font-weight", "bold")
    .attr("font-size", "10pt")
    .text("Y Label")
    .attr("class", "y_axis")

  var clip = svg.append("defs").append("svg:clipPath")
            .attr("id", "clip")
            .append("svg:rect")
            .attr("width", width )
            .attr("height", height )
            .attr("x", 0) 
            .attr("y", 0);

 
  var circle = svg.append("g")
        .attr("id", "scatterplot")
        .attr("clip-path", "url(#clip)");
  
  circle.selectAll("circle")
    .data(dataset)
    .enter()
    .append("circle")
    .attr("cx", function(d) { return d.x = xScale(d.x); })
    .attr("cy", function(d) { return d.y = yScale(d.y); })
    .attr("fill", "steelblue")
    .attr("r", 20)
    .on('mouseover',function(elem, data) {
        //console.log("over data name: " + data.name);
    })
    .on('mouseout',function (elem, data) {
        //console.log("out data name: " + data.name);
    })
    .on("mousedown", function(elem, data){
        console.log("down data name: " + data.name);
    })
    .on("mouseup", function(elem, data){
        console.log("up data name: " + data.name);
    })
    .call(d3.drag().on("drag", circleDragged).on("start", circleStartDrag))

    function circleStartDrag(event){
        console.log("start drag");
    }

    function circleDragged(event){
        d3.select(this).attr('cx', d=>d.x=event.x).attr('cy', d=>d.y=event.y);
    }
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>D3 Scatter Plot</title>
  <script src="https://code.jquery.com/jquery-3.6.0.slim.min.js" integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI=" crossorigin="anonymous"></script>
  <script src="https://d3js.org/d3.v7.min.js"></script>
</head>

<body>
  
  <div id="graph-area" stype="position: relative;"></div
  
</body>

</html>