使用 d3.js 为 svg 图表创建标记

Creating Markers for svg chart using d3.js

My Fiddle Example

我正在使用以下代码创建基于此 example 的标记。如何让子节点指向父节点而不是父节点指向子节点?

vis.append("defs").selectAll("marker")
.data(force.links())
.enter().append("marker")
.attr("id", function(d) { console.log(d); return d.source.id; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 7)
.attr("refY", -1)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");

似乎将force.link数据绑定到标记上是不正确的。我发现示例中有重复的 ID 和标记:

<defs>
      <marker id="53" viewBox="0 -5 10 10" refX="7" refY="-1" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,-5L10,0L0,5"></path></marker>
      <marker id="53" viewBox="0 -5 10 10" refX="7" refY="-1" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,-5L10,0L0,5"></path></marker>
      <marker id="53" viewBox="0 -5 10 10" refX="7" refY="-1" markerWidth="6" markerHeight="6" orient="auto"><path d="M0,-5L10,0L0,5"></path></marker>
       ....................

完整代码:

var w = 960,
    h = 500,
    node,
    path,
    root, nodes, links;

var force, vis;
var LoadData = true;

function update() {
    if (force) force.stop();
    nodes = flatten(root);
    links = d3.layout.tree().links(nodes);

    force.nodes(nodes)
        .links(links)
        .linkDistance(120)
        .charge(-500)
        .start();

    vis.append("defs").selectAll("marker")
    .data(force.links())
    .enter().append("marker")
    .attr("id", function(d) { console.log(d); return d.source.id; })
    .attr("viewBox", "0 -5 10 10")
    .attr("refX", 7)
    .attr("refY", -1)
    .attr("markerWidth", 6)
    .attr("markerHeight", 6)
    .attr("orient", "auto")
    .append("path")
    .attr("d", "M0,-5L10,0L0,5");

    path = vis.selectAll("path.link");
    path = path.data(force.links());
    path.exit().remove();
    path.enter().append("svg:path")
        .attr("class", "link")
        .attr("marker-end",function(d){ 
          return "url(#"+ d.source.id + ")";
        });
    vis.selectAll(".node .simpleDiv").remove();

    node = vis.selectAll(".node");
    node = node.data(force.nodes());
    node.exit().remove();
    node.enter().append("g")
        .attr("class", "node")
        .on("click", click)
        .call(force.drag);

    node.append("foreignObject")
        .attr("class", "simpleDiv")
        .attr("width", function (d) {
        var f = document.createElement("span");
        f.id = "hiddenText";
        f.style.display = 'hidden';
        f.style.padding = '0px';
        f.innerHTML = d.name;
        document.body.appendChild(f);
        textWidth = f.offsetWidth;
        var f1 = document.getElementById('hiddenText');
        f1.parentNode.removeChild(f1);
        return textWidth + 50;
    })
        .attr("overflow", "visible")
        .attr("height", 40)
        .append("xhtml:div").attr("class", "mainDiv").style("cursor", hoverStyle)
        .html(function (d) {
        var htmlString = "";
            htmlString += "<div class='userImage' style='border-color:" + color(d) + "'><img src='' width='30' height='30'></div>";
            htmlString += "<div class='content' style='color:" + color(d) + ";'>" + d.name + "</div>";
            htmlString += "<div style='clear:both;'></div>";
        return htmlString;
    });
}

function tick() {
    path.attr("d", function (d) {

        var dx = d.target.x - d.source.x,
            dy = d.target.y - d.source.y,
            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;
    });

    node.attr("transform", function (d) {
        return "translate(" + (d.x - 15) + "," + (d.y - 15) + ")";
    });

}

function color(d) {
    return d._children ? "#3182bd" : d.children ? "#2044D2" : "#fd8d3c";
}

function hoverStyle(d) {
    return d._children ? "pointer" : d.children ? "pointer" : "default";
}
// Toggle children on click.
function click(d) {
    if (d.children) {
        d._children = d.children;
        d.children = null;
    } else {
        d.children = d._children;
        d._children = null;
    }
    update();
}

var findNode = function (node) {
    for (var i in force.nodes()) {
        if (force.nodes()[i] === node) return true
    };
    return false;
}

    function flatten(root) {
        var nodes = [],
            i = 0;

        function recurse(node) {
            if (node.children) node.children.forEach(recurse);
            if (!node.id) node.id = ++i;
            nodes.push(node);
        }

        recurse(root);
        return nodes;
    }

    function loadImage() {
       if (LoadData) {
            root = 
{ "brand":"32","id":"53","name":"AAA","parent_id":"0","children":
    [
        {"brand":"16","id":"536","name":"BBB","parent_id":"53"},         
        {"brand":"16","id":"547","name":"CCC","parent_id":"53"},
        {"brand":"16","id":"537","name":"EEE","parent_id":"53"},
        {"brand":"16","id":"538","name":"WWW","parent_id":"53"},
        {"brand":"14","id":"213","name":"KKK","parent_id":"53"},
        {"brand":"16","id":"540","name":"BBB","parent_id":"53"}
    ]
};
          force = d3.layout.force()
                .on("tick", tick)
                .size([w, h]);
          vis = d3.select("#chart").append("svg:svg")
                .attr("width", w)
                .attr("height", h);
            update();
            LoadData = false;
         }

    }

marker-end 更改为 marker-start。这会将标记放置在路径的开头,在您的情况下,这将使线条从子项指向父项。您需要稍微修改一下路径函数,因为当前移动箭头会将它们全部放在父圆圈内的另一个顶部。