如何在force-directed graph的所有边缘中间放置一个图标?
这就是我想象的图形:
我看过this post。但是,这似乎不适用于我的情况,或者我还没有理解这些机制。
这个图标如何配备点击事件监听器?图标元素 - 点击时 - "知道"它属于哪条路径?
非常感谢。
我用[?] HELP注释了3个有问题的代码片段 - 这在以下代码中不起作用。代码大大缩短,不会按原样运行。
<!DOCTYPE html>
<meta charset="utf-8">
<!--
Structure:
- #illustration_content (explicitly defined as div)
- svg
- g (The g element is a container used to group objects.)
- path (for links)
- text (for labels)
- circle (for nodes)
- marker (for arrow heads)
- icon (for icons)
-->
<head>
<!-- EDITED OUT-->
</head>
<body>
<!-- EDITED OUT-->
<script>
// Declare the object that will contain the nodes later on
var nodes = {};
// <-- EDITED OUT-->
// Set the size of the illustration (used multiple times --> hence defined here)
var width = 800, // make sure this fits to the width of the illustration div
height = 600;
// <-- EDITED OUT-->
var linkDistanceVariable = 50; // we will update this with increasing number of nodes
// Create the force layout for d3
var force = d3.layout.force()
.size([width, height])
.linkDistance(function() { return linkDistanceVariable; })
.friction(0.7) //at each tick of the simulation, the particle velocity is scaled by the specified friction.
.charge(-600) //A negative value results in node repulsion, while a positive value results in node attraction.
.alpha(0.2) // cooling parameter: If you do not stop the layout explicitly, it will still stop automatically after the layout's alpha decays below some threshold.
.on("tick", tick); // this calls the tick function (which is in the separate JS) - the function is executed for each step of the animation
// Append the d3 illustration to the illustration div container
var svg = d3.select("#illustration_content").append("svg")
.attr("width", width)
.attr("height", height)
.style("border", "1px solid black");
// Append stuff
var path = svg.append("g").selectAll("path"),
text = svg.append("g").selectAll("text"),
circle = svg.append("g").selectAll("circle"),
marker = svg.append("defs").selectAll("marker");
// [?] HELP - THIS DOES NOT WORK
var icon = svg.append("svg:g").selectAll("g").data(force.links()).enter().append("svg:g");
// Update function to update the visualisation with new data
function update(json){
// <-- EDITED OUT (~100 LOC)-->
// The idea here is to prevent the illustration from pulsing (i.e. updating when nothing has changed)
// This introduces unnecessary movements
// This will only update the screen if the new link array length differs from the previous one
// This should be improved (also other things might change like values of properties)
// Essentially, currently only a change in number of links will trigger an update (not a change in confidence etc.)
if (links_previously.length !== links.length){
console.info("Is the links array length different than previous one? -->" + (links_previously !== links));
// Stop the layout
force.stop();
// Set the new linkDistance val
linkDistanceVariable = 40 + 3 * Object.keys(nodes).length; // new
// Start the layout again
force.start();
force
.nodes(d3.values(nodes))
.links(links)
.start();
}
// Compute the data join. This returns the update selection.
marker = marker.data(["suit", "licensing", "resolved"]);
// Remove any outgoing/old markers.
marker.exit().remove();
// Compute new attributes for entering and updating markers.
marker.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 20) //this determines position of arrow head along path (x-axis)
.attr("refY", 0) // this determines y-position of arrow head on path (0 = centered)
.attr("markerWidth", 6) // width of arrow head?
.attr("markerHeight", 6) // width of arrow head?
.attr("orient", "auto")
.append("path") // use ".append("line") for lines instead of arrows
.attr("d", "M0,-5L10,0L0,5");
// -------------------------------
// Compute the data join. This returns the update selection.
path = path.data(force.links());
// Remove any outgoing/old paths.
path.exit().remove();
// Compute new attributes for entering and updating paths.
path.enter().append("path")
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; })
.on("dblclick", pathMouseDoubleClick) // allow path to be clicked;
// -------------------------------
// [?] HELP - THIS DOES NOT WORK
// Idea: https://stackoverflow.com/questions/14567809/how-to-add-an-image-to-an-svg-container-using-d3-js
icon.append("image").attr("xlink:href","imgs/icon.png")
.attr("x", -20)
.attr("y", -2)
.attr("width", 20).attr("height", 20)
.attr("class", "type-icon");
// -------------------------------
// Compute the data join. This returns the update selection.
circle = circle.data(force.nodes());
// Add any incoming circles.
circle.enter().append("circle");
// Remove any outgoing/old circles.
circle.exit().remove();
// Compute new attributes for entering and updating circles.
circle
.attr("r", 10) // size of the circles
.on("dblclick", nodeMouseDoubleClick) // allow nodes to be clicked
.call(force.drag);
// -------------------------------
// Compute the data join. This returns the update selection.
text = text.data(force.nodes());
// Add any incoming texts.
text.enter().append("text");
// Remove any outgoing/old texts.
text.exit().remove();
// Compute new attributes for entering and updating texts.
text
.attr("x", 11) // Distance of the text from the nodes (x-axis)
.attr("y", "0.5em") // Distance of the text from the nodes (y-axis)
.text(function(d) { return d.name; });
// <-- EDITED OUT-->
} // end update function
// ------------------------------------- loadNewData() --------------------------------------
function loadNewData(){
d3.json("data/dataFromPython.json", function(error, json){
// <-- EDITED OUT-->
update(json);
// <-- EDITED OUT-->
});
};
// ------------------------------ search functionality -----------------------------------
// <-- EDITED OUT-->
// -------------------------------------- Hide or show node labels ------------------------------
// <-- EDITED OUT-->
// ------------------------------------- Interval timer ----------------------------------------
// Regularly update the data every x milliseconds (normal JS)
setInterval(function () {
loadNewData();
console.info("Interval timer has just called loadNewData()");
}, 3000);
// ------------------------------------- Tick function ----------------------------------------
// The tick function is executed for each tiny step of the animation
// Use elliptical arc path segments to doubly-encode directionality
function tick(){
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
// For icon in the middle of the path
// does not work, taken from here: https://stackoverflow.com/questions/14582812/display-an-svg-image-at-the-middle-of-an-svg-path
// [?] HELP - THIS DOES NOT WORK
icon.attr("transform", function(d) {
return "translate(" +((d.target.x+d.source.x)/2) + "," + ((d.target.y+d.source.y))/2 + ")";
});
}
// ------------------------------------- Link arc and transform function ----------------------------------------
function linkArc(d){
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = (d.straight == 0)?Math.sqrt(dx * dx + dy * dy):0;
return "M" + d.source.x + "," + d.source.y +
"A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d){
return "translate(" + d.x + "," + d.y + ")";
}
</script>
</body>
&#13;
答案 0 :(得分:1)
正如我的评论中所述,您创建了一个零长度选择,因为您在将数据添加到强制布局之前绑定了数据。此外,您需要动态选择,而不只是一次。
如果您将图标选择与其他图标选择相同......
// Append stuff
var path = svg.append("g"),
text = svg.append("g"),
circle = svg.append("g"),
marker = svg.append("defs"),
icon = svg.append("svg:g");
然后在你的更新函数中添加这样的东西,在新的json绑定到强制布局之后......
function update(json) {
// ...
force
.nodes(d3.values(nodes))
.links(links)
.start();
// ...
// Compute the data join. This returns the update selection.
var markers = marker.selectAll("marker").data(["suit", "licensing", "resolved"]);
// ...
// Compute the data join. This returns the update selection.
var paths = path.selectAll("path").data(force.links());
// ...
var icons = icon.selectAll("g").data(force.links());
icons.enter().append("svg:g")
.append("image").attr("xlink:href", "imgs/icon.png")
.attr("x", -20)
.attr("y", -2)
.attr("width", 20).attr("height", 20)
.attr("class", "type-icon");
icons.exit().remove();
// ...
// -------------------------------
// Compute the data join. This returns the update selection.
var circles = circle.selectAll("circle").data(force.nodes());
// Add any incoming circles.
circles.enter().append("circle")
// Compute new attributes for entering circles.
.attr("r", 10) // size of the circles
.on("dblclick", nodeMouseDoubleClick) // allow nodes to be clicked
.call(force.drag);
// Remove any outgoing/old circles.
circles.exit().remove();
// -------------------------------
var texts = text.selectAll("text").data(force.nodes());
然后在你的滴答功能......
function tick() {
paths.attr("d", linkArc);
circles.attr("transform", transform);
texts.attr("transform", transform);
// For icon in the middle of the path
// does not work, taken from here: http://stackoverflow.com/questions/14582812/display-an-svg-image-at-the-middle-of-an-svg-path
// [?] HELP - THIS DOES NOT WORK
icons.attr("transform", function(d) {
return "translate(" + ((d.target.x + d.source.x) / 2) + "," + ((d.target.y + d.source.y)) / 2 + ")";
});
}
然后就好了。 我无法验证,因为您没有提供最小的工作示例。
另外,请注意绑定到图标的数据(force.links())与绑定到路径的数据相同,因此,如果您在其上放置一个事件侦听器,那么您可以使用该信息来帮助您查找链接。