# How Graphviz2Drawio Handles Edge Directions: Directed, Undirected, and Backward Edges

> Explore how Graphviz2Drawio manages edge directions, including directed, undirected, and backward edges, to accurately represent your diagrams.

- Repository: [Harold Martin/graphviz2drawio](https://github.com/hbmartin/graphviz2drawio)
- Tags: how-to-guide
- Published: 2026-03-03

---

**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`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/mx/Edge.py), line 31, the constructor sets `self.dir` based on whether the source graph is directed:

```python

# graphviz2drawio/mx/Edge.py, line 31

self.dir = DotAttr.FORWARD if is_directed else DotAttr.NONE

```

The `DotAttr` enum in [`graphviz2drawio/models/DotAttr.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/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:

```python

# 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`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/mx/Edge.py)) determines whether to render an arrowhead by checking if the edge's direction exists in the supplied `active_dirs` set:

```python

# 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`): The `active_dirs` set contains `FORWARD`, triggering an end-arrow block
- **Undirected edges** (`NONE`): No direction matches, resulting in `MxConst.NONE` and no arrowhead
- **Backward edges** (`BACK`): Uses `active_dirs={DotAttr.BOTH}` to render a start-arrow instead of an end-arrow
- **Bidirectional edges** (`BOTH`): Also uses the `BOTH` active set to generate arrows on both ends

## Complete Code Examples

### Converting an Undirected Graph

Undirected graphs produce edges without arrowheads by default:

```python
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:

```python
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:

```python
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 `Edge` class in [`graphviz2drawio/mx/Edge.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/mx/Edge.py) stores directional state using `DotAttr` constants (`FORWARD`, `BACK`, `BOTH`, `NONE`) based on the source graph's directedness.
- **Node ordering**: `MxGraph.get_edge_source_target` swaps source and target nodes when encountering `DotAttr.BACK` to ensure correct visual arrow orientation.
- **Visual styling**: Arrowheads are conditionally rendered in `Edge._get_arrow_shape_and_fill` by 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`](https://github.com/hbmartin/graphviz2drawio/blob/main/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`](https://github.com/hbmartin/graphviz2drawio/blob/main/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.