The marker positions are influenced by the refx
and refy
attributes of the marker definition. You seem to know this, as you’ve got:
svg.append("defs").append("marker")
.attr("id", "arrowhead")
.attr("viewBox", "0 -5 10 10")
.attr("refX", nodeRadius + 3) // Adjusted to place arrow at node edge
.attr("refY", 0)
Your reference to nodeRadius
in the definition of refX
seems to suggest that you’re expecting the marker to be shifted back by exactly the radius of your nodes. The refX
and refY
attributes, though, refer to the coordinate system of the marker, which is different from the coordinate system of the containing figure. They’ve each got their own viewBox.
I’ve been hung up on this myself in the past and typically just fiddle with the code. I found that changing
.attr("refX", nodeRadius + 3)
to
.attr("refX", 10)
worked quite well.
Whether d3@7
is lazily loaded or not depends on whether you’re working within a .md
file defining a page or a .js
file defining a component. In the former case, you shouldn’t have to import d3
; in the latter you do.
Perhaps. But then, the SVG specification is relatively low level compared to, say, a charting library. You’re manipulating SVG directly and programmatically using D3 and Javascript so it can’t really be any simpler than SVG is.
One final point - you’re not using d3-graphviz
, which is a much higher level library and should, indeed, be much simpler. Observable’s dot
template is built on top of d3-graphviz
so you could use it. Your graph is defined by the following links:
links = [
{ source: "1", target: "B" },
{ source: "2", target: "B" },
{ source: "1", target: "A" },
{ source: "2", target: "A" },
{ source: "A", target: "B" }
]
Once you’ve got that, drawing something like your graph is as simple as so:
dot`digraph {
rankdir=LR
${links.map((o) => `${o.source} -> ${o.target}`).join("; ")}
}`
Of course, you don’t have the groovy animated effect but maybe this is sufficient for your needs.