D3 Force - 为具有动态refX的链接创建标记,以反映不同的节点大小

时间:2016-10-30 20:27:52

标签: javascript d3.js

我为每个拥有refX = link.target.radius的链接创建了一个标记,并为每个链接指定了不同的ID。 然后,我使用相应的id为每个链接添加了marker-end,以便根据目标节点半径指定refX来获取正确的标记。

    links = d3.select("svg").selectAll(".link")
        .data(force.links())
        .enter()
        .append("path")
        .attr("class", "link")
        .attr("stroke", "grey")
        .attr("fill", "black")
        .attr("stroke-width", 10)
        .attr("marker-end", function(d) { return "url(#marker" + force.nodes()[d.target].id + ")"})

    defs = d3.select("#svg").append("defs").selectAll(".marker")
        .data(force.links())
        .enter()
        .append("marker")
        .attr("class", "marker")
        .attr("id", function(d) { return "marker" + force.nodes()[d.target].id })
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", function(d) { return force.nodes()[d.target].radius})
        .attr("refY", 0)
        .attr("markerWidth", 13)
        .attr("markerHeight", 13)
        .attr("orient", "auto")
        .append("path")
            .attr("d", "M0,-5 L10,0 L0,5")
            .style("stroke", "black")
            .style("fill", "black")
            .style("opacity", "1");

jsfiddle here - https://jsfiddle.net/6hustc0h/2/

看起来每个人都应该从控制台工作 - 标记创建正常,具有不同的id。这些id正确附加到链接上,标记箭头本身肯定会从链接末端移回远离目标节点的位置。

但是他们没有正确替换。 refX = 40的标记已正确移动到节点的边缘。 refX = 20的标记一直没有移动到节点的末尾。

1 个答案:

答案 0 :(得分:2)

您的代码存在两个问题:

  1. markerWidth设置为 13 将定义标记元素所适合的视口的宽度。这本身很好,并且会在SVG使用的坐标系中为标记提供 13 的宽度。但是,通过在标记上指定viewBox,您已为<marker>元素的内容设置了内部坐标系。在评估refX

    时,将使用此内部坐标系
      

    在应用‘viewBox’‘preserveAspectRatio’属性后,坐标系统中定义了坐标。

    如果将refX设置为所需的偏移量,则不会考虑将viewPort调整到标记宽度所需的缩放比例。有一个比例因子 13/10

  2. 设置缺少的偏移量时,添加标记本身的宽度,即 10

  3. 只要您保持坐标系统同步,根据您的需要,有几种方法。在下面的代码段中,我将markerWidth设置为 10 ,它将通过使外部宽度和内部宽度具有相同的大小来处理它。这使您无需进行任何缩放,但也会略微减少SVG中的标记大小。如果您需要的大小完全 13 ,那么您将不得不进行一些计算。您的示例的相关行可能会更改为:

    .attr("viewBox", "0 -5 10 10")
    .attr("refX", function(d) { 
       return force.nodes()[d.target].radius + 10;   // Add the marker's width               
    })
    .attr("refY", 0)
    .attr("markerWidth", 10)                         // markerWidth equals viewBox width
    .attr("markerHeight", 10)
    

    var nodes = [{id:1, "radius": 20, "colour": "black"}, {id:2, "radius":40, "colour":"red"}, {id:3, "radius":30, "colour": "green"}];
    
    var links = [{source: 0, target: 1}, {source: 1, target: 2}, {source: 2, target: 0}];
        
    
    var svg = d3.select("body").append("svg").attr("width", 500).attr("height", 500).style("border", "1px solid black").attr("id", "svg");
        
    var force = d3.layout.force()
      .size([500,500])
      .links(links)
      .nodes(nodes)
      .linkDistance(150)
      .on("tick", tick);
            
    var nodes, links, defs;
            
        function render() {
            nodes = d3.select("#svg").selectAll(".node")
                .data(force.nodes())
                .enter()
                .append("circle")
                .attr("class", "node")
                .attr("r", function(d) { return d.radius})
                .attr("fill", "none")
                .attr("stroke", "black");
                
            links = d3.select("svg").selectAll(".link")
                .data(force.links())
                .enter()
                .append("path")
                .attr("class", "link")
                .attr("stroke", "grey")
                .attr("fill", "black")
                .attr("stroke-width", 1)
                .attr("marker-end", function(d) { return "url(#marker" + force.nodes()[d.target].id + ")"})
                
            defs = d3.select("#svg").append("defs").selectAll(".marker")
    			.data(force.links())
    			.enter()
                .append("marker")
    			.attr("class", "marker")
    			.attr("id", function(d) { return "marker" + force.nodes()[d.target].id })
    			.attr("viewBox", "0 -5 10 10")
                .attr("refX", function(d) { 
                  return force.nodes()[d.target].radius + 10;   // Add the marker's width of 10
                })
                .attr("refY", 0)
    			.attr("markerWidth", 10)                        // markerWidth equals viewBox width
    			.attr("markerHeight", 10)
    			.attr("orient", "auto")
    			.append("path")
    				.attr("d", "M0,-5 L10,0 L0,5")
    				.style("stroke", "black")
    				.style("fill", "black")
    				.style("opacity", "1");
        };
        
        function tick() {
        
            nodes.attr("cx", function(d) {return d.x})
                .attr("cy", function(d) {return d.y});
                
            links.attr("d", function(d) {
                var x0 = d.source.x,
                    y0 = d.source.y,
                    x1 = d.target.x,
                    y1 = d.target.y;
                return "M" + [x0,y0] + " L" + [x1,y1] + "";
                });
    
        };
        
        render()
        
        force.start();
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>