使用 d3.js 为 svg 图表创建标记
Creating Markers for svg chart using d3.js
我正在使用以下代码创建基于此 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
。这会将标记放置在路径的开头,在您的情况下,这将使线条从子项指向父项。您需要稍微修改一下路径函数,因为当前移动箭头会将它们全部放在父圆圈内的另一个顶部。
我正在使用以下代码创建基于此 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
。这会将标记放置在路径的开头,在您的情况下,这将使线条从子项指向父项。您需要稍微修改一下路径函数,因为当前移动箭头会将它们全部放在父圆圈内的另一个顶部。