Graphs and Graphviz DOT

In this tutorial you build a directed graph with the graph DSL, style its nodes and edges, and write it as Graphviz DOT source text ready for rendering.

Step 1: Add the dependencies

You need markup-graph (the graph model and DSL) and markup-dot-writer (the DOT writer):

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("org.markup-poet:markup-graph:0.2.0")
            implementation("org.markup-poet:markup-dot-writer:0.2.0")
        }
    }
}

Step 2: Build your first digraph

Create a directed graph with two nodes and one edge:

import org.markup.poet.dsl.graph.digraph
import org.markup.poet.dsl.write.dot.toDot

val g = digraph("Pipeline") {
    node("parse") { shape = "box" }
    node("write") { shape = "box" }
    edge("parse", "write") { label = "model" }
}

println(g.toDot())

You should see:

digraph Pipeline {
  parse [shape=box];
  write [shape=box];
  parse -> write [label=model];
}

digraph builds a directed graph; graph builds an undirected one (edges are written with -- instead of ).

Step 3: Style nodes and edges

Nodes have typed shape, color, and label properties; edges have style, color, and label:

val g = digraph("StyledGraph") {
    node("A") {
        shape = "box"
        color = "red"
    }
    node("B") {
        shape = "ellipse"
        color = "blue"
    }
    edge("A", "B") {
        style = "dashed"
        color = "black"
    }
    edge("B", "A") {
        style = "bold"
        color = "orange"
    }
}

toDot() produces:

digraph StyledGraph {
  A [shape=box, color=red];
  B [shape=ellipse, color=blue];
  A -> B [style=dashed, color=black];
  B -> A [style=bold, color=orange];
}

Step 4: Set arbitrary attributes

Beyond the typed properties, attr(name, value) passes any attribute through at graph, node, and edge level, so the full DOT attribute vocabulary is available:

val g = digraph("Pipeline") {
    attr("rankdir", "LR")
    node("A") {
        shape = "box"
        attr("penwidth", "2")
    }
    edge("A", "B") {
        attr("weight", "3")
    }
}
digraph Pipeline {
  rankdir=LR;
  A [shape=box, penwidth=2];
  A -> B [weight=3];
}

Note that the edge references node B without a matching node("B") declaration. That is allowed: edges may reference undeclared ids, which become implicit nodes, just as in DOT itself.

Step 5: Render with Graphviz

Write the output to a file and run Graphviz:

dot -Tsvg pipeline.dot -o pipeline.svg

Next steps

  • Check graphs for cycles and sort them topologically: Analyze DAGs

  • Look up how the graph model maps to DOT syntax: DOT mapping