How Graphviz2Drawio Handles Edge Directions: Directed, Undirected, and Backward Edges
Graphviz2Drawio handles edge directions by storing a direction flag in the Edge class, swapping source/target nodes for backward arrows during MX-Graph construction, and conditionally rendering arrowheads based on the direction attribute.
The hbmartin/graphviz2drawio library converts GraphViz DOT graphs into Draw.io-compatible XML by carefully preserving directional semantics. Whether your source graph uses directed, undirected, or explicitly reversed edges, the conversion process relies on three coordinated mechanisms in the Python source code to ensure the visual output matches the original graph's intent.
How Edge Directions Are Detected and Stored
When parsing the SVG output from GraphViz, EdgeFactory creates Edge objects that record the graph's directionality. In graphviz2drawio/mx/Edge.py, line 31, the constructor sets self.dir based on whether the source graph is directed:
# graphviz2drawio/mx/Edge.py, line 31
self.dir = DotAttr.FORWARD if is_directed else DotAttr.NONE
The DotAttr enum in graphviz2drawio/models/DotAttr.py defines four directional constants:
DotAttr.FORWARD– Standard directed edge (arrow points from source to target)DotAttr.NONE– Undirected edge (no arrowheads)DotAttr.BACK– Reverse direction (arrow points from target to source)DotAttr.BOTH– Bidirectional edge (arrows on both ends)
This directional metadata persists throughout the conversion pipeline, enabling downstream components to apply the correct visual styling and node ordering.
Source and Target Resolution for Reverse Arrows
Before generating the final XML, MxGraph.get_edge_source_target resolves which node serves as the source and which as the target. For backward edges, this method swaps the stored fr and to values so the arrow renders in reverse:
# graphviz2drawio/mx/MxGraph.py, lines 77-80
if edge.dir == DotAttr.BACK:
return self.nodes.get(edge.to), self.nodes.get(edge.fr)
return self.nodes.get(edge.fr), self.nodes.get(edge.to)
This swapping logic ensures that when DotAttr.BACK is detected, the visual arrow points from the original target node to the original source node, faithfully representing GraphViz's reverse-edge semantics in the Draw.io canvas.
Arrowhead Styling in Draw.io XML
The final rendering phase occurs in Edge.get_edge_style, which constructs the mxGraph style string. The helper method _get_arrow_shape_and_fill (lines 92-94 in graphviz2drawio/mx/Edge.py) determines whether to render an arrowhead by checking if the edge's direction exists in the supplied active_dirs set:
# graphviz2drawio/mx/Edge.py, lines 92-94
shape = MxConst.BLOCK if self.dir in active_dirs else MxConst.NONE
fill = 1 if self.dir in active_dirs else 0
- Directed edges (
FORWARD): Theactive_dirsset containsFORWARD, triggering an end-arrow block - Undirected edges (
NONE): No direction matches, resulting inMxConst.NONEand no arrowhead - Backward edges (
BACK): Usesactive_dirs={DotAttr.BOTH}to render a start-arrow instead of an end-arrow - Bidirectional edges (
BOTH): Also uses theBOTHactive set to generate arrows on both ends
Complete Code Examples
Converting an Undirected Graph
Undirected graphs produce edges without arrowheads by default:
from graphviz2drawio import convert
from pygraphviz import AGraph
g = AGraph() # Default is undirected
g.add_edge("A", "B")
drawio_xml = convert(g)
print(drawio_xml)
# Result: <mxCell ... edge="1" ...> with no arrow style attributes
Converting a Directed Graph
Directed graphs automatically apply forward-pointing arrows:
g = AGraph(directed=True)
g.add_edge("A", "B")
drawio_xml = convert(g)
print(drawio_xml)
# Result: style contains endArrow=block based on DotAttr.FORWARD
Handling Backward Edges Manually
For explicit reverse directionality, you can manipulate the dir attribute directly:
from graphviz2drawio.mx.Edge import Edge
from graphviz2drawio.models.DotAttr import BACK
# Create edge with backward direction
e = Edge(
sid="e1",
fr="A",
to="B",
is_directed=True, # Initially sets dir=FORWARD
curve=None,
line_style=None,
labels=[],
stroke="#000",
stroke_width="1"
)
e.dir = BACK # Override to reverse direction
# MxGraph will swap source/target and apply start-arrow styling
Summary
- Direction storage: The
Edgeclass ingraphviz2drawio/mx/Edge.pystores directional state usingDotAttrconstants (FORWARD,BACK,BOTH,NONE) based on the source graph's directedness. - Node ordering:
MxGraph.get_edge_source_targetswaps source and target nodes when encounteringDotAttr.BACKto ensure correct visual arrow orientation. - Visual styling: Arrowheads are conditionally rendered in
Edge._get_arrow_shape_and_fillby checking membership in direction-specific active sets, ensuring undirected edges remain plain while directed edges display appropriate arrowheads.
Frequently Asked Questions
How does graphviz2drawio distinguish between directed and undirected graphs?
The library checks the is_directed property when constructing Edge objects in graphviz2drawio/mx/EdgeFactory.py. If the source GraphViz graph is directed, edges receive DotAttr.FORWARD; otherwise, they receive DotAttr.NONE. This boolean propagates from the SVG parsing stage through the conversion pipeline.
What happens when an edge has the BACK direction attribute?
When self.dir == DotAttr.BACK, the MxGraph.get_edge_source_target method swaps the source and target node references before adding them to the MX-Graph. This reversal causes Draw.io to render the arrow pointing from the original target back to the original source, matching GraphViz's reverse-edge notation.
How are arrowheads rendered for bidirectional edges?
Bidirectional edges use DotAttr.BOTH, which triggers the active_dirs set to include both directions during style generation. In graphviz2drawio/mx/Edge.py, this results in both startArrow and endArrow attributes being set to block in the final XML style string, creating arrows at both ends of the connector.
Can I convert a mixed graph containing both directed and undirected edges?
While GraphViz typically treats graphs as either strictly directed or strictly undirected, graphviz2drawio supports mixed directionalities if individual edges specify dir attributes. The conversion logic evaluates each Edge object's self.dir independently, allowing specific edges to override the global graph direction when manually configured via the underlying DotAttr values.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →