Reputation: 103
I am currently trying to get my d3 node links to end up on the node edge instead of the center of the node (rect). With the links being drawn to the center, I am having issues with the arrows placement. They are obviously all getting mixed up, since the links are of different length and angle.
How would I go ahead to successfully have them placed to the nodes edge instead of the center? I am also curious on how I would change my links to "curved" paths instead, I have included the "path" var but it's currently not working.
As of right now, this is part of the code:
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;
})
});
I have also created a jsFiddle with the complete code: https://jsfiddle.net/czdvjw8o/2/
Upvotes: 2
Views: 1677
Reputation: 3898
SVG contains marker-mid
directive for placing markers on middlepoints of path and polylines, but not on lines, because its does not contains midpoints.
i've changed your code to this:
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
Resulting image:
working snippet:
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: Sorry for my english, i hope i helps you...
Upvotes: 2