How Node Styles Are Converted from Graphviz to Draw.io: A Technical Deep Dive

The conversion extracts visual attributes like fill, stroke, and dash patterns from SVG elements rendered by PyGraphviz, maps Graphviz shapes to Draw.io style templates in Styles.py, and populates placeholder values to generate the final XML style strings.

The hbmartin/graphviz2drawio library automates the migration of Graphviz diagrams to the Draw.io format by transforming DOT language attributes into equivalent XML styling. Understanding how node styles are converted from Graphviz to Draw.io requires examining the data-driven pipeline that bridges these two visualization formats.

The Three-Stage Conversion Pipeline

The transformation operates through a sequence that renders, parses, and re-serializes graphical data without hard-coded attribute mappings.

Stage 1: SVG Generation via PyGraphviz

In graphviz2drawio/graphviz2drawio.py, the convert() function receives either a Graphviz object or raw DOT source. It invokes PyGraphviz to render the diagram as SVG using graph.draw(..., format="svg"). This SVG serves as the canonical intermediate representation containing all resolved visual attributes.

Stage 2: SVG Parsing and Node Extraction

The parse_nodes_edges_clusters function in graphviz2drawio/models/SvgParser.py traverses the SVG DOM and identifies elements with class="node". For each node element, it delegates instantiation to NodeFactory.from_svg in graphviz2drawio/mx/NodeFactory.py.

The factory detects the geometric SVG element—whether <rect>, <ellipse>, <polygon>, or <path>—to determine the node shape. It then extracts raw styling through helper methods:

  • _extract_fill: Parses the fill attribute and opacity, returning either a hex color string or a gradient tuple (start_color, end_color, direction).
  • _extract_stroke: Reads stroke, stroke-width, and stroke-dasharray to capture border color, thickness, and dash patterns.

Stage 3: Style Template Population

Each Node object calls get_node_style() (defined in graphviz2drawio/mx/Node.py#L50-L77) to assemble the final Draw.io style string. This method queries Styles.get_for_shape(self.shape) to retrieve the appropriate template from the Styles enum, then substitutes placeholders like {fill}, {stroke}, {stroke_width}, {dashed}, and {vertical_align} with values extracted during Stage 2.

Mapping Graphviz Shapes to Draw.io Templates

The shape-to-style mapping resides in graphviz2drawio/mx/Styles.py. The Styles enum defines base templates as string constants with named placeholders:


# Conceptual representation from Styles.py

NODE = "verticalAlign={vertical_align};html=1;rounded=0;strokeColor={stroke};fillColor={fill};strokeWidth={stroke_width};dashed={dashed};"
ELLIPSE = "ellipse;" + NODE
CIRCLE = "ellipse;aspect=fixed;" + NODE

The Styles.get_for_shape method (lines 88-102) consults an internal _shape_to_style dictionary. If a Graphviz shape lacks a specific entry, it safely falls back to Styles.NODE (the default rectangle template).

Handling Gradients and Line Styles

When NodeFactory._extract_fill detects a gradient, Node.get_node_style appends additional fragments to the base template:

  • gradientColor: The secondary color of the gradient.
  • gradientDirection: The angle or direction of the fade.

For line styling, the presence of a stroke-dasharray attribute in the SVG triggers dashed=1 in the template, converting Graphviz dotted or dashed borders into Draw.io equivalents.

Serializing to Draw.io XML

The final assembly occurs in graphviz2drawio/mx/MxGraph.py. The MxGraph class iterates over all node objects, calls node.get_node_style() for each, and injects the resolved string into the style attribute of the corresponding <mxCell> XML element. This produces the complete Draw.io document structure consumable by the diagrams.net editor.

Practical Conversion Example

The following Python script demonstrates how Graphviz attributes translate through the pipeline:

from graphviz2drawio import convert
from pygraphviz import AGraph

dot_source = """
digraph G {
    a [shape=box, style=filled, fillcolor="#ffdddd", color="#aa0000"];
    b [shape=circle, style=filled, fillcolor=lightblue];
    a -> b;
}
"""

# Convert to Draw.io XML

drawio_xml = convert(AGraph(string=dot_source))
print(drawio_xml[:500])

Internal transformation breakdown:

  1. Shape mapping: box resolves to Shape.RECT, selecting Styles.NODE (rectangle template).
  2. Fill application: The #ffdddd value populates the {fill} placeholder.
  3. Stroke application: The #aa0000 border color fills the {stroke} slot.
  4. Output: The resulting <mxCell> for node a contains style="verticalAlign=middle;html=1;rounded=0;strokeColor=#aa0000;fillColor=#ffdddd;strokeWidth=1;dashed=0;".

Summary

  • SVG intermediation: The library uses PyGraphviz to render Graphviz to SVG, creating a parseable source of truth for all visual attributes.
  • Data-driven extraction: NodeFactory reads fill, stroke, stroke-width, and stroke-dasharray directly from SVG elements, supporting both solid colors and gradients.
  • Template-based styling: The Styles enum maps detected shapes to parameterized Draw.io style strings, ensuring consistent XML generation.
  • Dynamic assembly: Node.get_node_style merges extracted values with templates, while MxGraph handles final XML serialization.

Frequently Asked Questions

How does the library handle unsupported Graphviz shapes?

According to the source code in graphviz2drawio/mx/Styles.py, the get_for_shape method implements a fallback mechanism. If a Graphviz shape name is not found in the _shape_to_style lookup table, it automatically defaults to Styles.NODE, which renders as a standard rectangle in Draw.io.

Are gradient fills preserved during the conversion?

Yes. The NodeFactory._extract_fill method detects SVG gradient definitions and returns them as tuples containing start color, end color, and direction. The Node.get_node_style method then appends gradientColor and gradientDirection parameters to the Draw.io style string, preserving the gradient effect.

What happens to Graphviz line styles like dotted or dashed?

The NodeFactory inspects the stroke-dasharray attribute on SVG elements. If present, it sets dashed=1 in the style attributes dictionary. This value populates the {dashed} placeholder in the Draw.io template, accurately reproducing dashed or dotted borders from the original Graphviz diagram.

Where is the entry point for converting a Graphviz file?

The primary conversion function resides in graphviz2drawio/graphviz2drawio.py at lines 13-21. The convert() function accepts either a PyGraphviz AGraph object or a DOT string, orchestrates the SVG rendering, and returns the complete Draw.io XML document as a string.

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 →