yog/render/dot

DOT (Graphviz) format export for visualizing graphs.

This module exports graphs to the DOT language, which is the native format for Graphviz - a powerful open-source graph visualization tool. The exported files can be rendered to PNG, SVG, PDF, and other formats using the dot, neato, circo, or other Graphviz layout engines.

Quick Start

import yog/render/dot

// Export with default styling
let dot_string = dot.to_dot(my_graph, dot.default_dot_options())

// Write to file and render with Graphviz CLI
// $ dot -Tpng output.dot -o graph.png

Customization

Use DotOptions to customize:

Generic Data Types

The to_dot function works with any node and edge data types. Use default_dot_options_with_edge_formatter() when your edge data is not a String:

let options = dot.default_dot_options_with_edge_formatter(fn(weight) {
  int.to_string(weight)
})
let dot_string = dot.to_dot(my_int_weighted_graph, options)

Per-Element Styling

Provide custom attribute functions for fine-grained control:

let options = DotOptions(
  ..dot.default_dot_options(),
  node_attributes: fn(id, data) {
    case id {
      1 -> [("fillcolor", "green"), ("shape", "diamond")]
      _ -> []
    }
  },
  edge_attributes: fn(from, to, weight) {
    case weight > 10 {
      True -> [("color", "red"), ("penwidth", "2")]
      False -> []
    }
  },
)

Subgraphs and Clusters

Group nodes visually using subgraphs:

let options = DotOptions(
  ..dot.default_dot_options(),
  subgraphs: Some([
    Subgraph(
      name: "cluster_0",
      label: Some("Cluster A"),
      node_ids: [1, 2, 3],
      style: Some(dot.Filled),
      fillcolor: Some("lightgrey"),
      color: None,
    ),
  ]),
)

Rendering Options

EngineBest For
dotHierarchical layouts (DAGs, trees)
neatoSpring-based layouts (undirected)
circoCircular layouts
fdpForce-directed layouts
sfdpLarge graphs

References

Types

Arrow head/tail style

pub type ArrowStyle {
  Normal
  ArrowDot
  ArrowDiamond
  ODiamond
  ArrowBox
  Crow
  Vee
  Inv
  Tee
  ArrowNone
  CustomArrow(String)
}

Constructors

  • Normal

    Standard arrow

  • ArrowDot

    Dot circle

  • ArrowDiamond

    Filled diamond

  • ODiamond

    Empty diamond

  • ArrowBox

    ArrowBox

  • Crow

    Crow’s foot (database notation)

  • Vee

    V-shaped

  • Inv

    Inverted V

  • Tee

    Tee (perpendicular line)

  • ArrowNone

    No arrow

  • CustomArrow(String)

    Custom arrow (e.g., “ediamond”, “odot”)

Options for customizing DOT (Graphviz) diagram rendering.

This type is generic over node data n and edge data e, allowing it to work with graphs of any data types. Use default_dot_options() for String edge data, or default_dot_options_with_edge_formatter() for custom edge types.

Per-Element Styling

Use node_attributes and edge_attributes to set custom DOT attributes for individual nodes and edges. These functions receive the node/edge data and should return a list of #(attribute_name, attribute_value) pairs.

Common node attributes: “fillcolor”, “shape”, “width”, “height”, “penwidth” Common edge attributes: “color”, “penwidth”, “style”, “arrowhead”, “constraint”

pub type DotOptions(n, e) {
  DotOptions(
    node_label: fn(Int, n) -> String,
    edge_label: fn(e) -> String,
    highlighted_nodes: option.Option(List(Int)),
    highlighted_edges: option.Option(List(#(Int, Int))),
    node_attributes: fn(Int, n) -> List(#(String, String)),
    edge_attributes: fn(Int, Int, e) -> List(#(String, String)),
    subgraphs: option.Option(List(Subgraph)),
    graph_name: String,
    layout: option.Option(Layout),
    rankdir: option.Option(RankDir),
    bgcolor: option.Option(String),
    splines: option.Option(Splines),
    overlap: option.Option(Overlap),
    nodesep: option.Option(Float),
    ranksep: option.Option(Float),
    node_shape: NodeShape,
    node_color: String,
    node_style: Style,
    node_fontname: String,
    node_fontsize: Int,
    node_fontcolor: String,
    edge_color: String,
    edge_style: Style,
    edge_fontname: String,
    edge_fontsize: Int,
    edge_penwidth: Float,
    arrowhead: option.Option(ArrowStyle),
    arrowtail: option.Option(ArrowStyle),
    highlight_color: String,
    highlight_penwidth: Float,
  )
}

Constructors

  • DotOptions(
      node_label: fn(Int, n) -> String,
      edge_label: fn(e) -> String,
      highlighted_nodes: option.Option(List(Int)),
      highlighted_edges: option.Option(List(#(Int, Int))),
      node_attributes: fn(Int, n) -> List(#(String, String)),
      edge_attributes: fn(Int, Int, e) -> List(#(String, String)),
      subgraphs: option.Option(List(Subgraph)),
      graph_name: String,
      layout: option.Option(Layout),
      rankdir: option.Option(RankDir),
      bgcolor: option.Option(String),
      splines: option.Option(Splines),
      overlap: option.Option(Overlap),
      nodesep: option.Option(Float),
      ranksep: option.Option(Float),
      node_shape: NodeShape,
      node_color: String,
      node_style: Style,
      node_fontname: String,
      node_fontsize: Int,
      node_fontcolor: String,
      edge_color: String,
      edge_style: Style,
      edge_fontname: String,
      edge_fontsize: Int,
      edge_penwidth: Float,
      arrowhead: option.Option(ArrowStyle),
      arrowtail: option.Option(ArrowStyle),
      highlight_color: String,
      highlight_penwidth: Float,
    )

    Arguments

    node_label

    Function to convert node ID and data to a display label

    edge_label

    Function to convert edge data to a display label

    highlighted_nodes

    Optional list of node IDs to highlight

    highlighted_edges

    Optional list of edges to highlight as (from, to) pairs

    node_attributes

    Function to provide custom DOT attributes for each node. Returns list of #(attribute_name, attribute_value) pairs. These attributes override any defaults or highlighting.

    edge_attributes

    Function to provide custom DOT attributes for each edge. Returns list of #(attribute_name, attribute_value) pairs. These attributes override any defaults or highlighting.

    subgraphs

    Optional list of subgraphs/clusters for visual node grouping.

    graph_name

    Graph name (default: “G”)

    layout

    Layout engine (default: None = auto-detect)

    rankdir

    Graph direction (default: Some(TopToBottom))

    bgcolor

    Background color (CSS color, default: None)

    splines

    Edge routing (default: None = Graphviz default)

    overlap

    Overlap handling (default: None)

    nodesep

    Minimum space between nodes in inches (default: 0.25)

    ranksep

    Minimum space between ranks in inches (default: 0.5)

    node_shape

    Node shape (default: Ellipse)

    node_color

    Default node fill color (CSS color)

    node_style

    Node style (default: Filled)

    node_fontname

    Node font name (default: “Helvetica”)

    node_fontsize

    Node font size in points (default: 12)

    node_fontcolor

    Node font color (CSS color, default: “black”)

    edge_color

    Default edge color (CSS color, default: “black”)

    edge_style

    Edge style (default: Solid)

    edge_fontname

    Edge font name (default: “Helvetica”)

    edge_fontsize

    Edge font size in points (default: 10)

    edge_penwidth

    Edge line thickness (default: 1.0)

    arrowhead

    Arrow head style (default: None = Graphviz default)

    arrowtail

    Arrow tail style (default: None)

    highlight_color

    Highlight color for nodes/edges (CSS color, default: “red”)

    highlight_penwidth

    Highlight pen width (default: 2.0)

Graphviz layout engine

pub type Layout {
  Dot
  Neato
  Circo
  Fdp
  Sfdp
  Twopi
  Osage
  CustomLayout(String)
}

Constructors

  • Dot

    Hierarchical layouts (DAGs, trees) - default for most use cases

  • Neato

    Spring-based layouts (undirected graphs)

  • Circo

    Circular layouts

  • Fdp

    Force-directed placement

  • Sfdp

    Scalable force-directed (for large graphs)

  • Twopi

    Radial layouts

  • Osage

    Clustered layouts (array-based)

  • CustomLayout(String)

    Custom layout engine

Node shapes

pub type NodeShape {
  Box
  Circle
  Ellipse
  Diamond
  Hexagon
  Pentagon
  Octagon
  Triangle
  Rectangle
  Square
  Rect
  InvTriangle
  House
  InvHouse
  Parallelogram
  Trapezoid
  CustomShape(String)
}

Constructors

  • Box
  • Circle
  • Ellipse
  • Diamond
  • Hexagon
  • Pentagon
  • Octagon
  • Triangle
  • Rectangle
  • Square
  • Rect

    Rounded rectangle

  • InvTriangle

    Inverted triangle

  • House

    House shape (pentagon with flat top)

  • InvHouse

    Inverted house

  • Parallelogram

    Parallelogram

  • Trapezoid

    Trapezoid

  • CustomShape(String)

    Custom shape (for advanced Graphviz shapes)

Overlap handling

pub type Overlap {
  OverlapTrue
  OverlapFalse
  Scale
  ScaleXY
  Prism
  CustomOverlap(String)
}

Constructors

  • OverlapTrue

    Allow overlaps (faster)

  • OverlapFalse

    Remove all overlaps (slower)

  • Scale

    Scale graph to remove overlaps

  • ScaleXY

    Scale x and y independently

  • Prism

    Prism algorithm (Voronoi-based)

  • CustomOverlap(String)

    Custom overlap mode

Graph direction (rank direction)

pub type RankDir {
  TopToBottom
  LeftToRight
  BottomToTop
  RightToLeft
}

Constructors

  • TopToBottom

    Top to Bottom (vertical, downward)

  • LeftToRight

    Left to Right (horizontal)

  • BottomToTop

    Bottom to Top (vertical, upward)

  • RightToLeft

    Right to Left (horizontal, reversed)

Edge routing style

pub type Splines {
  Line
  Polyline
  Curved
  Ortho
  Spline
  SplinesNone
}

Constructors

  • Line

    Straight lines

  • Polyline

    Polyline (bent lines)

  • Curved

    Curved edges

  • Ortho

    Orthogonal (right angles only)

  • Spline

    Bezier splines (smooth curves) - Graphviz default

  • SplinesNone

    No edges

Visual style

pub type Style {
  Solid
  Dashed
  Dotted
  Bold
  Filled
  Rounded
  Diagonals
  Striped
  Wedged
}

Constructors

  • Solid
  • Dashed
  • Dotted
  • Bold
  • Filled
  • Rounded
  • Diagonals
  • Striped
  • Wedged

A subgraph (cluster) for grouping nodes visually in the diagram.

In Graphviz, subgraphs with names starting with “cluster_” are rendered as bounded rectangles around the contained nodes. This is useful for:

  • Visualizing communities or partitions
  • Grouping related nodes
  • Highlighting logical components

Example

Subgraph(
  name: "cluster_0",
  label: Some("Module A"),
  node_ids: [1, 2, 3],
  style: Some(dot.Filled),
  fillcolor: Some("lightblue"),
  color: Some("blue"),
)
pub type Subgraph {
  Subgraph(
    name: String,
    label: option.Option(String),
    node_ids: List(Int),
    style: option.Option(Style),
    fillcolor: option.Option(String),
    color: option.Option(String),
  )
}

Constructors

  • Subgraph(
      name: String,
      label: option.Option(String),
      node_ids: List(Int),
      style: option.Option(Style),
      fillcolor: option.Option(String),
      color: option.Option(String),
    )

    Arguments

    name

    Subgraph name. Use “cluster_” prefix for visual clustering.

    label

    Optional label displayed at the top of the subgraph.

    node_ids

    List of node IDs to include in this subgraph.

    style

    Optional style for the subgraph boundary.

    fillcolor

    Optional fill color for the subgraph background.

    color

    Optional border color for the subgraph.

Values

pub fn default_dot_options() -> DotOptions(n, String)

Creates default DOT options with simple labeling and sensible styling.

Default configuration:

  • Layout: Auto-detected by Graphviz
  • Direction: Top-to-bottom
  • Node shape: Ellipse
  • Colors: Light blue nodes, black edges
  • Font: Helvetica 12pt

Note: This function returns DotOptions(n, String), meaning it works with any node data type (node labels use the ID only) but requires edge data to be String. For other edge types, use default_dot_options_with_edge_formatter().

Example

let options = dot.default_dot_options()
let dot_string = dot.to_dot(my_string_graph, options)
pub fn default_dot_options_with(
  node_label node_label: fn(Int, n) -> String,
  edge_label edge_label: fn(e) -> String,
) -> DotOptions(n, e)

Creates default DOT options with custom label formatters for both nodes and edges.

Use this when you need full control over how both node and edge data are displayed.

Example

let options = dot.default_dot_options_with(
  node_label: fn(id, data) { data.name <> " (" <> int.to_string(id) <> ")" },
  edge_label: fn(weight) { int.to_string(weight) <> " ms" },
)
pub fn default_dot_options_with_edge_formatter(
  edge_formatter: fn(e) -> String,
) -> DotOptions(n, e)

Creates default DOT options with a custom edge formatter.

Use this when your graph has non-String edge data (e.g., Int, Float, custom types). The provided formatter function converts edge data to strings for display.

Example

// For a graph with Int edge weights
let options = dot.default_dot_options_with_edge_formatter(fn(weight) {
  int.to_string(weight)
})

// For a graph with custom edge data
let options = dot.default_dot_options_with_edge_formatter(fn(edge_data) {
  edge_data.name <> ": " <> float.to_string(edge_data.weight)
})
pub fn path_to_dot_options(
  path: utils.Path(e),
  base_options: DotOptions(n, e),
) -> DotOptions(n, e)

Converts a shortest path result to highlighted DOT options.

Creates a copy of the base options with the path’s nodes and edges set to be highlighted. This is useful for visualizing algorithm results.

Example

case pathfinding.dijkstra(...) {
  Some(path) -> {
    let options = dot.path_to_dot_options(path, dot.default_dot_options())
    let dot_string = dot.to_dot(graph, options)
  }
  None -> ""
}
pub fn to_dot(
  graph: model.Graph(n, e),
  options: DotOptions(n, e),
) -> String

Converts a graph to DOT (Graphviz) syntax.

Works with any node data type n and edge data type e. Use the options to customize labels, styling, and to define subgraphs. Use default_dot_options() or default_dot_options_with_edge_formatter() to create appropriate options for your graph.

Time Complexity: O(V + E + S) where S is the total number of nodes across all subgraphs.

Example

let graph =
  model.new(Directed)
  |> model.add_node(1, "Start")
  |> model.add_node(2, "Process")
  |> model.add_edge(from: 1, to: 2, with: "5")

let diagram = dot.to_dot(graph, dot.default_dot_options())
// io.println(diagram)

Custom Styling Example

let options = DotOptions(
  ..dot.default_dot_options(),
  node_attributes: fn(id, data) {
    case id {
      1 -> [("fillcolor", "green"), ("shape", "diamond")]
      _ -> []
    }
  },
  subgraphs: Some([
    Subgraph(name: "cluster_0", label: Some("Group A"), node_ids: [1, 2]),
  ]),
)

This output can be processed by Graphviz tools (e.g., dot -Tpng -o graph.png):

digraph G {
  node [shape=ellipse];
  1 [label="Start"];
  2 [label="Process"];
  subgraph cluster_0 {
    label="Group A";
    1; 2;
  }
  1 -> 2 [label="5"];
}
Search Document