How Node Types Are Styled by Function in Diagram Design

Diagram Design determines node styling through a shape-family mapping system where parsers assign functional shape identifiers (rect, diamond, container, etc.) that resolve to CSS classes defining visual appearance.

The cathrynlavery/diagram-design repository implements a unified styling architecture that separates semantic node types from their visual representation. This approach enables consistent styling across Mermaid and Draw.io diagram formats while maintaining flexibility for different diagram functions.

The Shape-Family Mapping Architecture

Node styling in Diagram Design operates through three coordinated layers:

  1. Parser layer — extracts raw shape data from diagram sources
  2. Mapping layer — normalizes shapes into functional families via shape_family()
  3. Rendering layer — applies CSS classes based on the resolved family

This architecture ensures that a "decision" node looks identical whether it originates from Mermaid's diamond syntax or Draw.io's diamond geometry.

How Parsers Assign Initial Shapes

Mermaid Parser (mermaid_extract.py)

The Mermaid extractor in skills/diagram-design/scripts/mermaid_extract.py parses node syntax and assigns shape identifiers based on suffixes:

Syntax Pattern Assigned Shape Functional Purpose
Node (no suffix) rect Process/operation
Node? or Node⧈ diamond Decision/condition
Node() or NodeO ellipse Start/end terminator
Subgraph declaration container Logical grouping

The shape_family() function at line 999 normalizes these assignments.

Draw.io Parser (drawio_extract.py)

The Draw.io extractor in skills/diagram-design/scripts/drawio_extract.py extracts geometry types from XML and maps them to equivalent shape families. Its shape_family() function at line 278 handles:

  • Rectangle primitives → rect
  • Diamond geometries → diamond
  • Rounded rectangles → ellipse or state
  • Table structures → table
  • Swimlane/participant elements → lifeline

Functional Node Families and Their Styling

The shape-family mapper collapses diagramspecific shapes into semantic categories used for styling:

Function Shape Family CSS Class Visual Characteristics
Process/Operation rect .node-rect Solid rectangle, primary color fill
Decision/Condition diamond .node-diamond Diamond geometry, accent color
Start/End ellipse .node-ellipse Rounded ellipse, muted tone
Container/Subgraph container .node-container Light background, bordered enclosure
State state .node-state Rounded rectangle, distinct fill
Data/Table table .node-table Grid borders, structural appearance
Sequence Participant lifeline .node-lifeline Dashed vertical line representation

CSS Implementation in HTML Assets

The visual styling executes through scoped CSS in skills/diagram-design/assets/example-architecture.html. The stylesheet maps family classes to concrete appearance properties:

/* Primary process nodes */
.node-rect {
  fill: #1e90ff;
  stroke: #000;
}

/* Decision points requiring user attention */
.node-diamond {
  fill: #ffcc00;
  stroke: #000;
}

/* Terminal states */
.node-ellipse {
  fill: #d3d3d3;
  stroke: #000;
}

/* Logical groupings */
.node-container {
  fill: #f5f5f5;
  stroke: #666;
}

/* Structured data representations */
.node-table {
  fill: #fafafa;
  stroke: #999;
}

/* Sequence diagram timing */
.node-lifeline {
  stroke: #777;
  stroke-dasharray: 4 2;
}

Working with Node Styling Programmatically

Creating a Decision Node

from diagram_design import Diagram

diagram = Diagram()

# Parse Mermaid-style decision syntax

diagram.add_node(
    node_id="approve_gate",
    label="Budget Approved?",
    shape="diamond",  # Resolved by shape_family() 

    parent=None
)

# Output: shape_family("diamond") returns "diamond"

# Renders as: <g class="node-diamond">...</g>

Creating a Container Structure


# Extracted from Draw.io subgraph or Mermaid subflow

diagram.add_node(
    node_id="auth_boundary",
    label="Authentication Service",
    shape="container",
    parent=None,
    container=True  # Explicit container flag

)

# Output: shape_family("container") returns "container"  

# Renders as: <g class="node-container">...</g>

Custom Shape Handling


# Unrecognized shapes fallback to rect styling

diagram.add_node(
    node_id="legacy_node",
    label="Unknown Type",
    shape="custom_vendor_shape"  # Not in standard mapping

)

# Output: shape_family("custom_vendor_shape") defaults to "rect"

# Renders with .node-rect styling for consistency

Key Implementation Files

File Path Responsibility Critical Functions
skills/diagram-design/scripts/mermaid_extract.py Mermaid syntax parsing, shape suffix detection shape_family() (L999), node creation
skills/diagram-design/scripts/drawio_extract.py Draw.io XML parsing, geometry extraction shape_family() (L278), table handling
skills/diagram-design/assets/example-architecture.html CSS styling definitions, render templates Family class selectors
skills/diagram-design/SKILL.md Architecture documentation, supported types Node type reference

Summary

  • Shape assignment occurs at parse time: Mermaid suffixes and Draw.io geometry both resolve to canonical shape identifiers
  • Family mapping normalizes semantics: The shape_family() function in both extractors collapses variant shapes into functional categories
  • CSS classes drive rendering: Family names translate directly to .node-{family} classes with predefined visual properties
  • Container and special types receive distinct styling: Structural elements (containers, tables, lifelines) have dedicated families beyond basic flowchart shapes
  • Fallback behavior ensures consistency: Unrecognized shapes default to rect styling rather than failing

Frequently Asked Questions

How does Diagram Design handle custom shapes not in the standard mapping?

Unrecognized shape identifiers pass through shape_family() and receive the default rect family assignment. This guarantees consistent rendering rather than broken styling, though custom CSS can target specific non-standard shapes through additional class attributes if needed.

Where can I modify the color scheme for node types?

Edit the CSS rules in skills/diagram-design/assets/example-architecture.html. Each .node-{family} selector controls fill colors, stroke properties, and other SVG presentation attributes. The repository uses standard CSS, so modifications apply immediately to rendered output.

Why are there two separate shape_family() functions?

Each parser operates independently because Mermaid and Draw.io represent shapes through different mechanisms—text suffixes versus XML geometry. Maintaining separate mapping functions allows format-specific logic while converging on identical family outputs for unified styling.

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 →