D3强制布局更改链接到路径中,并将箭头放置在节点边缘而不是中心

时间:2019-04-10 11:59:43

标签: javascript d3.js

我目前正在尝试使d3节点链接结束于节点边缘而不是节点中心(矩形)。随着链接被绘制到中心,我的箭头放置出现了问题。由于链接的长度和角度不同,因此它们显然混在一起。

我将如何成功地将它们放置在节点的边缘而不是中心?我也很好奇如何将链接更改为“弯曲”路径,我包含了“ path”变量,但目前无法正常工作。

截至目前,这是代码的一部分:

var graphWrapper = jQuery("#outputContainerFlowchart").append("<svg id='graphWrapper'></svg>");
var svg = d3.select("#graphWrapper").attr("width", width).attr("height", height);

var simulation = d3.forceSimulation(graph.nodes)
    .force("charge", d3.forceManyBody().strength(-30000))
    .force("center", d3.forceCenter(width / 2, height / 2))
    .force("link", d3.forceLink(graph.links).id(function(d) {return d.id; }).distance(50).strength(1))
    .force("x", d3.forceX(width / 2).strength(1))
    .force("y", d3.forceY(height / 2).strength(1))
    .stop();

graph.nodes[0].fixed = true;
graph.nodes[0].fx = width / 2;
graph.nodes[0].fy = height / 2;

d3.timeout(function() {
   let rectWidth = 180;
   let rectHeight = 60;
   for (var i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); i < n; ++i) {
         simulation.tick();
   }

   var g = svg.append("g")
       .attr("class", "everything");

   var arrow = g.append("defs").append("marker")
       .attr("id", "arrow")
       .attr("viewBox", "0 -5 10 10")
       .attr("refX", 80)
       .attr("refY", 0)
       .attr("markerWidth", 4)
       .attr("markerHeight", 4)
       .attr("orient", "auto")
       .append("svg:path")
       .attr("d", "M0,-5L10,0L0,5");


   var links = g.append("g")
       .attr("stroke", "#bbb")
       .attr("stroke-width", 3)
       .selectAll("line")
       .data(graph.links)
       .enter().append("line")
       .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; })
       .attr("marker-end", "url(#arrow)");


   var path = g.append("svg:g")
       .selectAll("line")
       .data(graph.links)
       .enter().append("svg:path")
       .attr("class", "link")
       .style("stroke-width", "10")
       .style("stroke", "red")
       .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; })
       .attr("marker-end", "url(#arrow)");


   var nodes = g.selectAll("foreignObject")
        .data(graph.nodes)
        .enter()
        .append("foreignObject")
        .attr("x", function(d) {
             return d.x - rectWidth / 2;
        })
        .attr("y", function(d) {
             return d.y - rectHeight / 2;
        })
        .attr("width", rectWidth)
        .attr("height", rectHeight)
        .attr("class", function(d) {
             return ("graphNode "+d.group)
        })
        .style("background-color", function(d) {
             return colors[d.group];
        })
        .append("xhtml:div")
             .classed("graphNodeDiv", true)
             .html(function(d) {
                  return d.id;
             })
});

我还用完整的代码创建了一个jsFiddle: https://jsfiddle.net/czdvjw8o/2/

1 个答案:

答案 0 :(得分:2)

SVG包含marker-mid指令,用于将标记放置在路径和折线的中点上,但不能放置在线上,因为它不包含中点。

我已将您的代码更改为此:

var arrow = g.append("defs").append("marker")
    .attr("id", "arrow")
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 0)    // here i was placed 0
    .attr("refY", 0)
    .attr("markerWidth", 4)
    .attr("markerHeight", 4)
    .attr("orient", "auto")
    .append("svg:path")
    .attr("d", "M0,-5L10,0L0,5");


 var links = g.append("g")
    .attr("stroke", "#bbb")
    .attr("stroke-width", 3)
    .selectAll("polyline") // selecting polylines instead of lines
    .data(graph.links)
    .enter().append("polyline") // adding polylines instead of lines
    .attr("points", function(d) { 
           return [
                d.source.x, d.source.y,
                // here i calculate midpoints where markers need to appear
                d.source.x/2+d.target.x/2, d.source.y/2+d.target.y/2,
                d.target.x, d.target.y
           ].join(','); 
    })
    .style("marker-mid", "url(#arrow)"); // here i changed type of marker

结果图像:

enter image description here


工作片段:

var width = jQuery("#outputContainerFlowchart").width();
                var height = jQuery("#outputContainerFlowchart").height();

                var colors = {
                                dummyprodVip: "#1eb543",
                                dummyaccVip: "#30cc30",
                                dummyprodLtmp: "#3be264",
                                dummyaccLtmp: "#74e874",
                                dummyprodPool: "#82ffa0",
                                dummyaccPool: "#bcffbc",
                                dummy2prodBVip: "#ff8438",
                                dummy2accAVip: "#becc6a",
                                dummy2prodBLtmp: "#ffac39",
                                dummy2accALtmp: "#d9e590",
                                dummy2prodBPool: "#ffdb3a",
                                dummy2accAPool: "#f6ffc1",
                }

                var graph = {
                  "nodes": [
                                {
                                  "id": "dummyvip1",
                                  "group": "dummyPrdVip"
                                },
                                {
                                  "id": "dummyltmp1",
                                  "group": "dummyPrdLtmp"
                                },
                                {
                                  "id": "dummypool1",
                                  "group": "dummyPrdPool"
                                },
                                {
                                  "id": "dummypool2",
                                  "group": "dummyPrdPool"
                                },
                                {
                                  "id": "dummy2vip1",
                                  "group": "dummy2PrdVip"
                                },
                                {
                                  "id": "dummy2vip2",
                                  "group": "dummy2PrdVip"
                                },
                                {
                                  "id": "dummy2ltmp1",
                                  "group": "dummy2PrdLtmp"
                                },
                                {
                                  "id": "dummy2ltmp2",
                                  "group": "dummy2PrdLtmp"
                                },
                                {
                                  "id": "dummy2pool1",
                                  "group": "dummy2PrdPool"
                                },
                                {
                                  "id": "dummy2pool2",
                                  "group": "dummy2PrdPool"
                                },
                                {
                                  "id": "dummy2pool3",
                                  "group": "dummy2PrdPool"
                                },
                                {
                                  "id": "dummy2pool4",
                                  "group": "dummy2PrdPool"
                                },
                                {
                                  "id": "dummy2pool5",
                                  "group": "dummy2PrdPool"
                                },
                                {
                                  "id": "dummy2pool6",
                                  "group": "dummy2PrdPool"
                                },
                                {
                                  "id": "dummy2pool7",
                                  "group": "dummy2PrdPool"
                                }
                  ],
                  "links": [
                                {
                                  "source": "dummyvip1",
                                  "target": "dummyltmp1",
                                  "value": 1
                                },
                                {
                                  "source": "dummyltmp1",
                                  "target": "dummypool1",
                                  "value": 6
                                },
                                {
                                  "source": "dummyltmp1",
                                  "target": "dummypool2",
                                  "value": 1
                                },
                                {
                                  "source": "dummypool1",
                                  "target": "dummy2vip1",
                                  "value": 1
                                },
                                {
                                  "source": "dummypool2",
                                  "target": "dummy2vip2",
                                  "value": 1
                                },
                                {
                                  "source": "dummy2vip1",
                                  "target": "dummy2ltmp1",
                                  "value": 1
                                },
                                {
                                  "source": "dummy2vip2",
                                  "target": "dummy2ltmp2",
                                  "value": 1
                                },
                                {
                                  "source": "dummy2ltmp1",
                                  "target": "dummy2pool1",
                                  "value": 1
                                },
                                {
                                  "source": "dummy2ltmp1",
                                  "target": "dummy2pool2",
                                  "value": 1
                                },
                                {
                                  "source": "dummy2ltmp1",
                                  "target": "dummy2pool3",
                                  "value": 1
                                },
                                {
                                  "source": "dummy2ltmp1",
                                  "target": "dummy2pool4",
                                  "value": 1
                                },
                                {
                                  "source": "dummy2ltmp1",
                                  "target": "dummy2pool5",
                                  "value": 1
                                },
                                {
                                  "source": "dummy2ltmp2",
                                  "target": "dummy2pool6",
                                  "value": 1
                                },
                                {
                                  "source": "dummy2ltmp2",
                                  "target": "dummy2pool7",
                                  "value": 1
                                }
                  ]
                };
						
                var graphWrapper = jQuery("#outputContainerFlowchart").append("<svg id='graphWrapper'></svg>");
                var svg = d3.select("#graphWrapper").attr("width", width).attr("height", height);
                var simulation = d3.forceSimulation(graph.nodes)
                                .force("charge", d3.forceManyBody().strength(-30000))
                                .force("center", d3.forceCenter(width / 2, height / 2))
                                .force("link", d3.forceLink(graph.links).id(function(d) {return d.id; }).distance(50).strength(1))
                                .force("x", d3.forceX(width / 2).strength(1))
                                .force("y", d3.forceY(height / 2).strength(1))
                                .stop();

                console.log(graph.nodes[0]);
                graph.nodes[0].fixed = true;
                graph.nodes[0].fx = width / 2;
                graph.nodes[0].fy = height / 2;
                d3.timeout(function() {
                        let rectWidth = 180;
                        let rectHeight = 60;
                        for (var i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); i < n; ++i) {
                                simulation.tick();
                        }

                        var g = svg.append("g")
                                .attr("class", "everything");

                        var arrow = g.append("defs").append("marker")
                                .attr("id", "arrow")
                                .attr("viewBox", "0 -5 10 10")
                                .attr("refX", 0)
                                .attr("refY", 0)
                                .attr("markerWidth", 4)
                                .attr("markerHeight", 4)
                                .attr("orient", "auto")
                                .append("svg:path")
                                        .attr("d", "M0,-5L10,0L0,5");


                        var links = g.append("g")
                                .attr("stroke", "#bbb")
                                .attr("stroke-width", 3)
                                .selectAll("polyline")
                                .data(graph.links)
                                .enter().append("polyline")
                                .attr("points", function(d) { 
                                	return [
                                    d.source.x, d.source.y,
                                    d.source.x/2+d.target.x/2, d.source.y/2+d.target.y/2,
                                    d.target.x, d.target.y
                                  ].join(','); 
                                })
                                .style("marker-mid", "url(#arrow)");


                        var path = g.append("svg:g")
                                .selectAll("line")
                                .data(graph.links)
                                .enter().append("svg:path")
                                .attr("class", "link")
                                .style("stroke-width", "10")
                                .style("stroke", "red")
                                .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; })
                                .attr("marker-end", "url(#arrow)");


                        var nodes = g.selectAll("foreignObject")
                                .data(graph.nodes)
                                .enter()
                                .append("foreignObject")
                                .attr("x", function(d) {
                                        return d.x - rectWidth / 2;
                                })
                                .attr("y", function(d) {
                                        return d.y - rectHeight / 2;
                                })
                                .attr("width", rectWidth)
                                .attr("height", rectHeight)
                                .attr("class", function(d) {
                                        return ("graphNode "+d.group)
                                })
                                .style("background-color", function(d) {
                                        return colors[d.group];
                                })
                                .append("xhtml:div")
                                .classed("graphNodeDiv", true)
                                .html(function(d) {
                                        let nodeHtml = "";
                                        let strippedId = d.id.replace(/\/.*\//, "")
                                        if (d.virtual !== undefined) {
                                                nodeHtml = "<div>"+d.partition+"</div><img src='/images/dummy-icon.png'></img>"
                                        } else if (d.policy !== undefined) {
                                                nodeHtml = "<div>"+d.partition+"</div><img src='/images/ltmp-icon.png'></img>"

                                        } else if (d.pool !== undefined) {
                                                nodeHtml = "<div>"+d.partition+"</div><img src='/images/pool-icon.png'></img>"
                                        }
                                        return strippedId+nodeHtml;
                                })

                        //add drag capabilities
                        var drag_handler = d3.drag()
                                .on("start", drag_start)
                                .on("drag", drag_drag)
                                .on("end", drag_end);
                        drag_handler(nodes);

                        //add zoom capabilities
                        var zoom_handler = d3.zoom()
                                .on("zoom", zoom_actions);
                        zoom_handler(svg);

                        //Drag functions
                        function drag_start(d) {
                                if (!d3.event.active) simulation.alphaTarget(0.3).restart();
                                        d.fx = d.x;
                                        d.fy = d.y;
                                }

                                //make sure you can't drag the circle outside the box
                        function drag_drag(d) {
                                d.fx = d3.event.x;
                                d.fy = d3.event.y;
                        }

                        function drag_end(d) {
                                if (!d3.event.active) simulation.alphaTarget(0);
                                        d.fx = null;
                                        d.fy = null;
                                }

                        //Zoom functions
                        function zoom_actions(){
                                g.attr("transform", d3.event.transform)
                        }
                });
@charset "utf-8";
/* CSS Document */

#outputContainer, #outputContainerFlowchart {
    width: 1000px;
/*  margin: 2px;*/
    margin-left: auto;
    margin-right: auto;
    color: #616161;
    font-weight: bold;
    border-radius: 3px;
    word-break: break-word;
    padding-top: 20px;
    padding-bottom: 20px;
    height: 1000px;
}


polyline { stroke: #ccc; stroke-width: 3px; stroke-dasharray: 7, 7; }
.link1 { stroke: #000; stroke-width: 2px;}
.nodetext { pointer-events: none;}

rect {
        border-radius: 20px;
}
div.tooltip {
    position: absolute;
    text-align: center;
    padding: 2px;
    font: 12px sans-serif;
    background: #efefef;
        pointer-events: none;
}

.graphNode {
/*      box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);*/
/*      transition: all 0.3s cubic-bezier(.25,.8,.25,1);*/
        border-radius: 3px;
}



.graphNodeDiv {
        width: 100%;
        height: 100%;
        font-size: 11px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        padding: 2px;
        background-color:#dddddd;
}

.graphNodeDiv:hover {
        filter: url(#blur);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<html>
  <head>
    <script src="https://d3js.org/d3.v5.min.js"></script>
  </head>
  <body>
    <div id="outputContainerFlowchart">
  </div>
  </body>
</html>

PS:对不起,我的英语,希望我对您有所帮助...