How GraphViz Edge Labels Are Converted to draw.io Output: The Complete Technical Pipeline

Edge labels are converted into separate mxCell objects with specialized edgeLabel styling and relative geometry that attach to their parent edge cells, allowing draw.io to render GraphViz annotations as HTML-formatted text that follows the connector line.

When converting diagrams from GraphViz DOT format to draw.io (mxGraph) XML, handling edge labels requires a sophisticated multi-stage pipeline. The hbmartin/graphviz2drawio library implements this by parsing SVG text elements, merging duplicate connections, and generating dedicated label cells with relative geometry. Understanding how edge labels in draw.io output are structured helps developers customize the conversion process and troubleshoot formatting issues.

The Edge Label Conversion Pipeline

The conversion process treats edge labels as distinct mxCell objects rather than inline attributes. This approach preserves formatting and positioning while ensuring draw.io can manipulate the labels independently. The pipeline consists of six distinct phases, each handled by specific modules in the codebase.

Phase 1: Extracting Text from SVG

The process begins in EdgeFactory.from_svg, which walks the SVG <g class="edge"> element and collects every <text> child node. Each text element is converted to a Text instance via Text.from_svg. These objects capture the raw label content and formatting metadata from the GraphViz SVG output.

Phase 2: Building the Edge Object

The collected labels are passed as a list to the Edge constructor. The Edge class stores these labels unchanged in its internal state, maintaining the association between the geometric edge and its textual annotations until XML generation occurs.

Phase 3: Merging Duplicate Edges

In SvgParser.parse_nodes_edges_clusters, the system identifies edges that share the same logical connection using edge.key_for_label. When duplicate edges are detected, they are de-duplicated and their label arrays are concatenated using existing_edge.labels.extend(edge.labels). This ensures that multiple labels targeting the same source-target pair appear on a single connector.

Phase 4: Rendering HTML Label Values

The Edge.value_for_labels() method transforms the label list into draw.io-compatible HTML. The first label is inserted raw, while subsequent labels are wrapped in <div> elements to force line breaks. Each individual label's HTML is produced by Text.get_mx_value(), which handles font face, size, and color encoding.

Phase 5: Creating the Label mxCell

In MxGraph.add_edge, the system checks if len(edge.labels) > 0. When labels exist, it creates a new <mxCell> with:

  • id="label_{edge.sid}"

  • style=Styles.EDGE_LABEL.value (typically edgeLabel;html=1;strokeColor=none)

  • parent=edge.sid (attaches to the edge cell)

  • value=edge.value_for_labels() (the HTML-encoded string)

  • vertex="1" and connectable="0"

  • Source: graphviz2drawio/mx/MxGraph.py (lines 59-71)

  • Element: A dedicated vertex cell attached to the edge parent

Phase 6: Attaching Relative Geometry

Finally, add_mx_geo attaches a <mxGeometry as="geometry" relative="1"/> entry to the label cell. The relative="1" attribute instructs draw.io to position the label automatically along the edge line, maintaining visual association regardless of how the connector is manipulated.

Practical Example: Converting Labeled Edges

The following example demonstrates how multiple labels on duplicate edges are consolidated into a single label cell:

import graphviz2drawio as g2d

dot = """
digraph {
    A -> B [label="first"];
    A -> B [label="second"];
}
"""

# Convert the DOT source to a draw.io XML string

mx_xml = g2d.graphviz2drawio(dot, directed=True)

print(mx_xml)

Relevant excerpt from the generated XML:

<mxCell id="e1" style="edgeStyle=elbowEdgeStyle;strokeColor=#000000;endArrow=block;endFill=1;" parent="1" edge="1" source="n0" target="n1"/>
<mxCell id="label_e1" style="edgeLabel;html=1;strokeColor=none;" parent="e1"
        value="<div><font face=&quot;Helvetica&quot; size=&quot;12&quot; color=&quot;#000000&quot;>first</font></div><div><font face=&quot;Helvetica&quot; size=&quot;12&quot; color=&quot;#000000&quot;>second</font></div>"
        vertex="1" connectable="0">
    <mxGeometry as="geometry" relative="1"/>
</mxCell>

The two label strings (first and second) were collected during parsing, concatenated into a single label cell during the merge phase, and wrapped in <div> elements to preserve line-break ordering in the final draw.io output.

Key Source Files and Functions

Summary

  • Separate mxCell Objects: Edge labels are not inline attributes but distinct cells with vertex="1" that attach to their parent edge via the parent attribute.

  • HTML Encoding: The value_for_labels() method generates HTML with <div> wrappers for multi-line labels, preserving GraphViz formatting through Text.get_mx_value().

  • Duplicate Consolidation: The SvgParser merges edges sharing the same source-target pair, concatenating their labels into a single annotation.

  • Relative Geometry: Labels use <mxGeometry relative="1"/> to maintain position along the edge line when the diagram is edited in draw.io.

  • Style Constants: Labels receive the EDGE_LABEL style (typically edgeLabel;html=1;strokeColor=none) to ensure proper rendering in draw.io.

Frequently Asked Questions

Why are edge labels stored as separate mxCell objects instead of inline attributes?

Draw.io (mxGraph) requires edge labels to be separate vertex cells with connectable="0" in order to support rich HTML formatting, positioning, and independent manipulation. Storing labels as the value attribute of the edge cell itself would limit formatting options and prevent the label from following the edge when it is moved or reshaped. The separate cell approach with relative geometry ensures the label behaves as a first-class diagram element.

How does the converter handle multiple labels on the same edge?

When SvgParser.parse_nodes_edges_clusters detects multiple edges connecting the same source and target (identified by key_for_label), it merges them into a single edge instance and concatenates their label arrays using existing_edge.labels.extend(edge.labels). During XML generation, Edge.value_for_labels() renders the first label directly and wraps subsequent labels in <div> elements, creating a vertical stack of annotations in the final draw.io output.

Can I customize the HTML formatting of edge labels in the output?

Yes, by modifying the Text.get_mx_value() method in graphviz2drawio/mx/Text.py, you can change how individual labels are wrapped in HTML font tags. Additionally, adjusting Edge.value_for_labels() in graphviz2drawio/mx/Edge.py allows customization of the container structure—such as changing <div> wrappers to <br/> separators or adding CSS classes. The library encodes the HTML for XML compatibility before insertion into the value attribute.

What happens to edge labels when duplicate edges are merged?

Duplicate edges are consolidated during the parsing phase in SvgParser, where their label lists are combined through list extension. This means if two GraphViz edges connect the same nodes with different labels, both labels appear on the single resulting draw.io connector. The order of labels in the final output corresponds to the order in which the edges were processed and merged in the SVG parsing loop.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →