One Architecture, Three Diagram Tools: Mermaid vs Graphviz vs PlantUML

2026-11-08

The question

Most comparisons of Mermaid, Graphviz, and PlantUML look at syntax: how a node or an arrow is written. In practice, a diagram is written once and then changed many times. So we asked a narrower question:

If you draw the same small architecture in all three tools and then make the same change, how much does the source change, and how much does the picture move?

We drew one diagram in each tool, then added a Redis cache. We rendered both versions with the same in-browser engines that this site's playgrounds use and measured the results. All sources and renders are linked below, so you can open any version in a playground and check it.

The setup

The architecture is a small order system:

  • A Web app calls an API gateway over HTTPS.
  • A Backend group holds the gateway, an Auth service, and an Orders service.
  • A Data group holds the Orders DB and an Order events queue.
  • An Email worker consumes the events.

Each version draws left to right, uses the tool's own grouping construct (Mermaid subgraph, Graphviz cluster_* subgraph, PlantUML package), and uses the closest built-in shape for the database. PlantUML has a native queue shape; Mermaid 10.9 and Graphviz don't, so the queue is a plain box there. We kept default themes and didn't tune any layout options.

The change: add a Redis cache node (a cylinder or database) inside the Data group, and an Orders service → Redis cache edge labeled read-through.

Engines used to render (the same ones behind the site's playgrounds):

ToolEngine in the playgroundVersion
Mermaidmermaid npm package10.9.0
Graphviz@viz-js/viz (WebAssembly)3.2.3, which reports Graphviz 9.0.0
PlantUML@plantuml/core (in-browser, no Java or server)1.2026.6, which uses its own bundled Graphviz 14.1.1 for layout

Version 1 in each tool

Mermaid

flowchart LR
  web[Web app]
  subgraph backend[Backend]
    gw[API gateway]
    auth[Auth service]
    orders[Orders service]
  end
  subgraph data[Data]
    db[(Orders DB)]
    queue[Order events]
  end
  worker[Email worker]

  web -->|HTTPS| gw
  gw -->|verify token| auth
  gw -->|REST| orders
  orders -->|SQL| db
  orders -->|OrderPlaced| queue
  queue -->|consume| worker

Mermaid render of version 1: Web app, Backend group, Data group with Orders DB and Order events, Email worker

Open version 1 in the Mermaid playground

Graphviz (DOT)

digraph architecture {
  rankdir=LR;
  node [shape=box, style=rounded];

  web [label="Web app"];
  subgraph cluster_backend {
    label="Backend";
    gw [label="API gateway"];
    auth [label="Auth service"];
    orders [label="Orders service"];
  }
  subgraph cluster_data {
    label="Data";
    db [label="Orders DB", shape=cylinder];
    queue [label="Order events"];
  }
  worker [label="Email worker"];

  web -> gw [label="HTTPS"];
  gw -> auth [label="verify token"];
  gw -> orders [label="REST"];
  orders -> db [label="SQL"];
  orders -> queue [label="OrderPlaced"];
  queue -> worker [label="consume"];
}

Graphviz render of version 1 with rounded boxes and two clusters

Open version 1 in the Graphviz playground

PlantUML

@startuml
left to right direction
rectangle "Web app" as web
package "Backend" {
  rectangle "API gateway" as gw
  rectangle "Auth service" as auth
  rectangle "Orders service" as orders
}
package "Data" {
  database "Orders DB" as db
  queue "Order events" as queue
}
rectangle "Email worker" as worker

web --> gw : HTTPS
gw --> auth : verify token
gw --> orders : REST
orders --> db : SQL
orders --> queue : OrderPlaced
queue --> worker : consume
@enduml

PlantUML render of version 1 with package tabs, a database shape and a queue shape

Open version 1 in the PlantUML playground

The change: adding a cache

In all three tools, the edit is the same: one line inside the Data group and one line in the edge list. Here is the diff -u output for each.

Mermaid

   subgraph data[Data]
     db[(Orders DB)]
+    cache[(Redis cache)]
     queue[Order events]
   end
@@
   orders -->|SQL| db
+  orders -->|read-through| cache
   orders -->|OrderPlaced| queue

Graphviz

   subgraph cluster_data {
     label="Data";
     db [label="Orders DB", shape=cylinder];
+    cache [label="Redis cache", shape=cylinder];
     queue [label="Order events"];
   }
@@
   orders -> db [label="SQL"];
+  orders -> cache [label="read-through"];
   orders -> queue [label="OrderPlaced"];

PlantUML

 package "Data" {
   database "Orders DB" as db
+  database "Redis cache" as cache
   queue "Order events" as queue
 }
@@
 orders --> db : SQL
+orders --> cache : read-through
 orders --> queue : OrderPlaced

Version 2 renders

Mermaid render of version 2: Redis cache added between Orders DB and Order events; the whole diagram is taller

Mermaid, version 2. Open in the playground

Graphviz render of version 2: Redis cache added between Orders DB and Order events inside the Data cluster

Graphviz, version 2. Open in the playground

PlantUML render of version 2: Redis cache database added between Orders DB and the Order events queue

PlantUML, version 2. Open in the playground

What we measured

Source size. We counted non-blank lines and bytes, then the lines added or removed between versions.

Toolv1 lines / bytesv2 lines / bytesDiff
Mermaid18 / 37920 / 437+2 / −0
Graphviz23 / 61925 / 710+2 / −0
PlantUML20 / 45822 / 524+2 / −0

Rendered size and layout movement. We measured sizes in diagram units (SVG user units, before any zoom). For movement, we took each node's center and measured it relative to the Web app node, so that the whole picture simply re-centering doesn't count as movement.

Toolv1 sizev2 sizeHeight changeLargest vertical shiftLargest horizontal shift
Mermaid1107 × 2511108 × 352+101 (+40%)Orders DB −69, Order events +54, Auth service −172
Graphviz815 × 180817 × 209+29 (+16%)Orders DB −31, Order events +23, Backend nodes ≤ 22
PlantUML975 × 199977 × 245+46 (+23%)Orders DB −46, Order events +34, Backend nodes 02

What we saw

  • The source change was the same size everywhere. Each tool needed exactly two added lines and no edits to existing lines. For a change like this, the choice of tool barely affects the diff a reviewer sees. Graphviz is the most verbose (about 60% more bytes than Mermaid: 619 vs 379), mostly because of label="…" attributes.
  • All three placed the cache in the same spot. Every engine put Redis cache between Orders DB and Order events inside the Data group, and kept the left-to-right order of columns. No node moved sideways by more than 2 units.
  • Mermaid's picture moved the most. Its diagram grew 40% taller. Orders DB and Order events moved apart by over 120 units, and Auth service also moved inside the Backend group, even though nothing in Backend changed. Mermaid also left a lot of empty space at the bottom of the Backend group in both versions.
  • Graphviz stayed the most compact. It grew only 16% taller, and the Backend nodes stayed within 2 units. The trade-off is crowding: in version 2, the read-through and OrderPlaced edge labels sit almost on top of each other.
  • PlantUML kept the unchanged group perfectly still. The Backend nodes didn't move at all relative to Web app. Only the Data group grew. It is also the only one of the three with a dedicated queue shape.

If you keep diagrams in a repository and review them as screenshots, Graphviz and PlantUML gave the most stable before/after pictures here. If you review them as source diffs, all three were equally small.

Method and limitations

  • One diagram, one change. This is a single small diagram with seven nodes and one kind of edit. A larger diagram, or edits like renaming a group or moving a node between groups, could behave differently.
  • Default settings. We used each tool's default theme and layout. Graphviz options like nodesep, or Mermaid's ELK renderer, would change the numbers.
  • Specific engines. The results come from the engine versions in the table above, run in Chromium on one Linux machine through this site's playgrounds. PlantUML's own Java build or a newer Mermaid may lay things out differently.
  • What "movement" means. We measured the center of each node's bounding box in SVG units, relative to the Web app node. The measure ignores how edges are routed and where labels go, which is why we describe label crowding separately.
  • Shapes aren't identical. Each tool used its nearest built-in shape, so the boxes are not the same size across tools. Compare the changes within each tool, not absolute sizes across tools.

All six source files are in this article's assets: v1.mmd, v2.mmd, v1.dot, v2.dot, v1.puml, v2.puml.

Try it yourself