【问题标题】:Target SVG path to the middle of rect element将 SVG 路径定位到 rect 元素的中间
【发布时间】:2018-02-02 02:45:09
【问题描述】:

我正在使用 D3 库来绘制相互关联的元素图。我的节点是circlesrects 由定向线paths 连接。

我的问题是指向rects 元素的线条具有丑陋的可视化效果,因为线条结束于矩形的左上角而不是其中心(就像圆形一样)。

如何使路径线同时定位circles 元素和rect 元素的中心?


defs箭头定义代码:
svg.append('defs')
  .append('marker')
  .attr('id', 'arrow')
  .attr('viewBox', '0 -5 10 10')
  .attr('refX', 17) // As far as I understood this provides the distance from the end of the path line.
  .attr('refY', -0.1)
  .attr('markerWidth', 6)
  .attr('markerHeight', 6)
  .attr('orient', 'auto')
  .attr('fill', function() {
    return 'red';
  })
  .append('path')
  .attr('d', 'M0,-5L10,0L0,5');

定向链接的定义:

let links = svg.selectAll('.link')
  .data(data.links)
  .enter()
  .append('path')
  .attr('id', function (d) {
    return d.id;
  })
  .attr('class', 'link')
  .attr('fill', 'none')
  .attr('stroke-width', 1.2)
  .attr('marker-end', 'url(#arrow)')
  .attr('stroke', function() {
    return 'blue';
  })
  .style('cursor', 'pointer');

正方形的定义

let squares = svg.selectAll('.square')
  .data(data.squares, function(d) {
    return d.id;
  })
  .enter().append('g')
  .call(dragger)
  .attr('class', 'square')
  .style('cursor', 'pointer');

squares.append('rect')
  .attr('width', 10)
  .attr('height', 10)
  .attr('fill', function (d) {
    return '#fff';
  })
  .style('opacity', 0.1)
  .style('stroke', function() {
    return '#555';
  })
  .style('stroke-width', '2');

在以下屏幕截图中,您可以看到它的行为方式。圆形和矩形的不透明度较低,可以显示路径目标的问题。


更新

新增tick函数定义及用法。

simulation
  .nodes(data.nodes)
  .on('tick', _tick);

simulation
  .force('link')
  .distance(80)
  .links(data.links);

simulation.alpha(1).restart();

function _tick() {
  links.attr('d', function(d) {
    let dx = d.target.x - d.source.x;
    let dy = d.target.y - d.source.y;
    let dr = Math.sqrt(dx * dx + dy * dy);
    return ('M' + d.source.x + ',' + d.source.y + 
            'A' + dr + ',' + dr + ' 0 0,1 ' + d.target.x + ',' + d.target.y);
  });
  circles.attr('transform', function (d) {
    return 'translate(' + d.x + ',' + d.y + ')';
  });
  squares.attr('transform', function (d) {
    return 'translate(' + d.x + ',' + d.y + ')';
  });
}

【问题讨论】:

    标签: javascript d3.js svg rect


    【解决方案1】:

    您现在拥有的是预期的行为。在力模拟中(我假设您正在运行力模拟),tick 函数会更改基准对象的xy 属性,您可以按照自己的方式使用它们。

    由于您没有共享您的 tick 函数,我想您正在更新矩形的 xy 位置,如下所示:

    squares.attr("x", function(d) {
        return d.x
    }).attr("y", function(d) {
        return d.y
    });
    

    如果确实如此,矩形的左上角对应于d.xd.y 坐标。而且,由于您使用相同的属性来绘制路径,因此路径将从左上角到另一个。

    这很容易展示,看看这个演示:

    var width = 200;
    var height = 200;
    
    var rectSide = 40;
    
    var svg = d3.select("body")
      .append("svg")
      .attr("width", width)
      .attr("height", height);
    
    var nodes = [{
      name: "foo",
      color: "blue"
    }, {
      name: "bar",
      color: "green"
    }, {
      name: "baz",
      color: "red"
    }];
    
    var links = [{
      "source": 0,
      "target": 1
    }, {
      "source": 0,
      "target": 2
    }];
    
    var simulation = d3.forceSimulation()
      .force("link", d3.forceLink().distance(100))
      .force("charge", d3.forceManyBody().strength(-50))
      .force("center", d3.forceCenter(width / 2, height / 2));
    
    var node = svg.selectAll(null)
      .data(nodes)
      .enter()
      .append("rect")
      .attr("width", rectSide)
      .attr("height", rectSide)
      .attr("fill", function(d) {
        return d.color
      });
    
    var link = svg.selectAll(null)
      .data(links)
      .enter()
      .append("line")
      .style("stroke", "#222")
      .style("stroke-width", 2);
    
    simulation.nodes(nodes);
    simulation.force("link")
      .links(links);
    
    simulation.on("tick", function() {
    
      link.attr("x1", function(d) {
          return d.source.x;
        })
        .attr("y1", function(d) {
          return d.source.y;
        })
        .attr("x2", function(d) {
          return d.target.x;
        })
        .attr("y2", function(d) {
          return d.target.y;
        })
    
      node.attr("x", function(d) {
        return d.x
      }).attr("y", function(d) {
        return d.y
      });
    
    });
    <script src="https://d3js.org/d3.v4.js"></script>

    解决方案:您可以移动矩形或路径。

    正如您的问题专门询问路径,解决方案很简单:将半宽和半高添加到目标和源坐标:

    link.attr("x1", function(d) {
            return d.source.x + rectangleWidth / 2;
        })
        .attr("y1", function(d) {
            return d.source.y + rectangleHeight / 2;
        })
        .attr("x2", function(d) {
            return d.target.x + rectangleWidth / 2;
        })
        .attr("y2", function(d) {
            return d.target.y + rectangleHeight / 2;
        })
    

    这是一个演示:

    var width = 200;
    var height = 200;
    
    var rectSide = 40;
    
    var svg = d3.select("body")
      .append("svg")
      .attr("width", width)
      .attr("height", height);
    
    var nodes = [{
      name: "foo",
      color: "blue"
    }, {
      name: "bar",
      color: "green"
    }, {
      name: "baz",
      color: "red"
    }];
    
    var links = [{
      "source": 0,
      "target": 1
    }, {
      "source": 0,
      "target": 2
    }];
    
    var simulation = d3.forceSimulation()
      .force("link", d3.forceLink().distance(100))
      .force("charge", d3.forceManyBody().strength(-50))
      .force("center", d3.forceCenter(width / 2, height / 2));
    
    var node = svg.selectAll(null)
      .data(nodes)
      .enter()
      .append("rect")
      .attr("width", rectSide)
      .attr("height", rectSide)
      .attr("fill", function(d) {
        return d.color
      });
    
    var link = svg.selectAll(null)
      .data(links)
      .enter()
      .append("line")
      .style("stroke", "#222")
      .style("stroke-width", 2);
    
    simulation.nodes(nodes);
    simulation.force("link")
      .links(links);
    
    simulation.on("tick", function() {
    
      link.attr("x1", function(d) {
          return d.source.x + rectSide / 2;
        })
        .attr("y1", function(d) {
          return d.source.y + rectSide / 2;
        })
        .attr("x2", function(d) {
          return d.target.x + rectSide / 2;
        })
        .attr("y2", function(d) {
          return d.target.y + rectSide / 2;
        })
    
      node.attr("x", function(d) {
        return d.x
      }).attr("y", function(d) {
        return d.y
      });
    
    });
    <script src="https://d3js.org/d3.v4.js"></script>

    【讨论】:

    • 嗨@Gerardo,感谢您的详细回答,您猜对了我正在使用forceSimulation。至于tick 功能略有不同,我已将其添加到问题中。如您所见,我使用d attr 而不是x1,x2,y1,y2,您认为这仍然可行吗?
    • 当然,只需在d.target.xd.target.y 中添加半宽和半高即可。
    • 抱歉,您的意思是在最后一个字符串中连接的最后两个值,或者在计算 dxdy 时?谢谢:)
    • 我计算并声明了let targetX = d.target.x + (10/2);y 相同)并使用了它们。现在的问题是路径已正确设置为以方形中心为目标,但是当路径以圆形为目标时,它们是现在超出​​了圆圈的边界。
    • 好吧,为了解决圆圈问题,最好的办法是创建一个minimal reproducible example
    【解决方案2】:
    1. 你能翻译正方形吗?如果你能翻译一半的正方形 宽度。
    2. 你能像 x2,y2 一样在正方形上找到那条路径的尽头吗?再加上你一半的正方形宽度。

    “希望这能启发你”

    squares.append('rect')
      .attr('width', 10)
      .attr('height', 10)
      .attr("transform", "translate(-5,0)")
      .attr('fill', function (d) {
        return '#fff';
      })
    

    【讨论】:

    • 我没想过要翻译这个正方形。我已经尝试过您提出的参数,但它们不是正确的,使用.attr("transform", "translate(-5,-5)") 反而看起来更好(考虑到每个项目都是可拖动的,因此我必须平移两个轴)。我正在等待进一步的答案,否则我会接受你的。
    • PS:你不需要像"translate("+(-5)+", "+0+")"那样将字符串和数字连接起来,只需像"translate(-5,-5)"那样在字符串中写入数字即可。
    • 我通常使用动态参数,它是如何工作的,无论如何它工作了吗?
    • 是的,我想像 ;) 是的,它确实适用于参数 -5,-5。无论如何,我想在接受之前等待其他答案,因为正如我在问题中所说,我想知道是否有办法让 path 指向 rect 的中心而不是它的角落,我觉得翻译rect 是一种解决方法。 :)
    猜你喜欢
    • 2018-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-13
    • 2023-03-03
    • 2021-08-02
    • 2016-09-12
    • 2017-07-24
    相关资源
    最近更新 更多