I'm using d3-dag to build a graph.
I have all the graph nodes in place and placed the edges lines in the right place but the only thing I'm missing is the arrows pointer to point to the right direction. Not all the arrow pointers point to the right place and are visible.
The reason I'm using the edges line curve as curveStepBefore is because I need to place another element on top of the line so I need the curve to be curveStepBefore.
I created a simple fiddle to demonstrate:
The edges drawing is done on function drawEdges, line 181
const data = [
{
"id": "0",
"name": "node1",
"parentIds": []
},
{
"id": "1",
"name": "node2",
"parentIds": ["0"]
},
{
"id": "2",
"name": "node3",
"parentIds": ["1"]
},
{
"id": "3",
"name": "node4",
"parentIds": ["1"]
},
{
"id": "4",
"name": "node5",
"parentIds": ["1", "2", "3", "8"]
},
{
"id": "5",
"name": "node6",
"parentIds": ["4"]
},
{
"id": "6",
"name": "node7",
"parentIds": ["5"]
},
{
"id": "7",
"name": "node8",
"parentIds": []
},
{
"id": "8",
"name": "node9",
"parentIds": ["7"]
}
];
async function createGraph() {
const dag = d3.dagStratify()(data);
const layout = d3
.sugiyama() // base layout
.decross(d3.decrossOpt()) // minimize number of crossings
.nodeSize((node) => [200, 200]); // set node size instead of constraining to fit
layout(dag);
// This code only handles rendering
const svgSelection = d3.select("#TargetContainer");
svgSelection.append("defs"); // For gradients
addDefs(svgSelection);
// Initialize color map
const steps = dag.size();
const interp = d3.interpolateRainbow;
const colorMap = {};
for (const [i, node] of [...dag].entries()) {
colorMap[node.data.id] = interp(i / steps);
}
drawEdges(svgSelection, dag);
// Select nodes
const nodes = svgSelection
.append("g")
.selectAll("g")
.data(dag.descendants())
.enter()
.append("g")
.attr("transform", ({ x, y }) => `translate(${x - 70}, ${y})`);
nodes
.append("rect")
.attr("class", "node")
.attr("filter", "url(#nodeShadow)")
.attr("rx", "17")
.attr("fill", "#FFFFFF")
.attr("width", "150")
.attr("height", 30)
.each(function (p, j) {
d3.select(this.parentElement)
.append("circle")
.attr("cx", 15)
.attr("cy", 15)
.attr("r", 15)
.attr("fill", "#FFFFFF")
.attr("filter", "url(#circleShadow)")
.attr("stroke", "lightgray")
.attr("stroke-width", "0.1")
.attr("width", 15)
.attr("height", 15);
d3.select(this.parentElement)
.append("text")
.attr("x", 40)
.attr("y", 19)
.attr("class", "graph-element-text node-text-color")
//.text(function () { return graphNode.displayName });
.text(p.data.name);
d3.select(this.parentElement).append("image")
.attr("xlink:href", p.data.icon)
.attr("x", 0)
.attr("y", 1)
.attr("transform", "translate(7, 6.5)")
.attr("width", 15)
.attr("height", 15);
});
}
function addDefs(svg) {
svg
.select("defs")
.append("svg:marker")
.attr("id", "arrowStart")
.attr("refX", -5)
.attr("refY", 9)
.attr("markerWidth", 22)
.attr("markerHeight", 22)
.attr("orient", "auto")
.append("circle")
.attr("cx", "4")
.attr("cy", "9")
.attr("r", "3")
.attr("orient", "auto")
.style("stroke", "#3278E0")
.style("fill", "none");
svg
.append("defs")
.append("svg:marker")
.attr("id", "arrowEnd")
.attr("refX", 6)
.attr("refY", 6)
.attr("markerWidth", 12)
.attr("markerHeight", 12)
.attr("orient", "auto")
.append("path")
.attr("d", "M-5,-3 L7,6 L-8,20")
.style("stroke", "#3278E0")
.style("fill", "none");
svg
.select("defs")
.append("filter")
.attr("width", "115%")
.attr("height", "115%")
.attr("id", "nodeShadow")
.append("feDropShadow")
.attr("dx", 0)
.attr("dy", 1)
.attr("stdDeviation", 1)
.attr("flood-opacity", 1)
.attr("flood-color", "lightgray");
svg
.select("defs")
.append("filter")
.attr("id", "circleShadow")
.attr("width", "120%")
.attr("height", "120%")
.append("feDropShadow")
.attr("dx", -1)
.attr("dy", 1)
.attr("stdDeviation", 3)
.attr("flood-opacity", 1)
.attr("flood-color", "lightgray");
}
function drawEdges(svgSelection, dag) {
// How to draw edges
const curve = d3.curveStepBefore;
const line = d3
.line()
.curve(curve)
.x((d) => d.x)
.y((d) => d.y);
svgSelection
.append("g")
.selectAll("path")
.data(dag.links())
.enter()
.append("path")
.attr("d", ({ points }) => line(points))
.attr("fill", "none")
.attr("marker-end", "url(#arrowEnd)")
.attr("stroke-width", 3)
.attr("stroke", "#4F97FF");
}
createGraph();