PBS 141 of X: Generating UML Class Diagrams with Mermaid
In the previous instalment we introduced the concept of UML Class Diagrams, explained the problems they solve, and then looked at the subset of the UML Class Diagram specification that’s most relevant to JavaScript classes. We looked at three important relationships between classes (inheritance, composition & aggregation), and class members (attributes & functions). We also looked at the Abstract class annotation, and how to mark class members as static.
We could use our favourite diagraming tools to create UML class diagrams, but we can do better, we can write them in a simple plain-text markup language and then automatically generate our diagrams from that markup. In this instalment we’ll learn how to use the open source Mermaid library to convert plain-text markup into diagrams!
Matching Podcast Episode
Listen along to this instalment on episode 752 of the Chit Chat Across the Pond Podcast.
You can also Download the MP3
Episode Resources
- The instalment ZIP file — pbs141.zip
Installing Mermaid
Mermaid is written in JavaScript, and it can be used in many ways. At its most fundamental level its a JavaScript module you can use in your own JavaScript code for creating diagrams, and it’s available in NPM as mermaid
. There’s also a live web interface at mermaid.live which can be useful for experimenting. What we’ll be using though is a command line wrapper around the library which is also available in NPM as @mermaid-js/mermaid-cli
.
To use Mermaid, start by creating a folder in which you’ll be working, then open that folder in a terminal and install the Mermaid CLI with the command:
npm install --save @mermaid-js/mermaid-cli
Once the package is installed in the folder you’ll be able to invoke the CLI version of Mermaid with npx mmdc
.
Play Along
If you’d like to play along with the examples in this instalment, open the folder pbs141
from the instalment ZIP in a terminal and install the Mermaid CLI as above.
To be sure mmdc
is working, build a simple diagram with the command:
npx mmdc --scale 2 -i helloWorld.mmd.txt -o helloWorld.png
This will generate a very simple diagram with two empty classes (Waffle
& Pancake
), each with one attribute and with no relationship between them. (I’ll explain attributes and relationships in a moment.)
Remember that the npx
command finds executable files in the node_modules
folder and executes them. In this case we are asking it to find the Mermaid command mmdc
. The -i
and -o
flags specify the input and output files, and the optional --scale
a scaling factor to apply. I find the default rendering too small, so I double the size.
If we look at the contents of helloWorld.mmd.txt
we can see what its markup looks like:
classDiagram-v2
class Pancake {
withSyrup
}
class Waffle {
withoutSyrup
}
Basic Mermaid Syntax
A single Mermaid file can contain multiple diagrams, so the first thing you declare is the type of diagram you are about to define, for us (at least for now), that will always be classDiagram-v2
. The definitions for a diagram are added underneath the diagram type, indented by a consistent amount.
Each different diagram type has its own rules for adding elements and relationships, but we’ll be confining ourselves to just the UML class diagram syntax in this instalment. In fact, since we confined ourselves to just a subset of the UML for class diagrams in the previous instalment, we’ll be confining ourselves to the matching subset of the Mermaid class diagram syntax. Finally, there are multiple valid Mermaid syntaxes for achieving some things, but to keep things simple we’ll be confining ourselves to a single variant. In other words, we’ll be looking at just a subset of a subset of a subset of the full Mermaid syntax!
For much of the language’s history, there was no syntax for including comments in diagrams, but if your Mermaid parser is modern enough, you can now add comment lines by starting them with two percentage signs followed by a space (%% some comment
).
Mermaid Class Diagram Syntax
Once we tell Mermaid we’re marking up a class diagram, we’re ready to add the classes and the relationships between them.
Something to bear in mind when marking up diagrams in Mermaid is that all your markup is read and processed before any part of the diagram is rendered. This means we can define things in any order we like.
Adding Classes
Each class definition start with the name of the class, then a space, then an opening curly brace, then one or more lines defining the class’ details and finally a closing brace on a line by itself. So, the simplest form of a class definition is simply:
class MyClass {
}
Adding Class Annotations
The easiest thing to add to a class is an annotation. We simply add the annotation as a single line wrapped in double chevrons, just like it will appear in the finished diagram:
class MyAbstractClass {
<<abstract>>
}
Adding Class Members
Other than the optional abstract
annotation, the only other contents our classes can contain are what UML calls members, or what we call attributes and functions. While the syntaxes are different for both, there are some important commonalities.
Firstly, all members are defined one per line. Secondly, all members share the same visibility markers: +
for public, and -
for private. Finally, all attributes use the same classifier to mark themselves as static, $
.
Attributes
The following syntax covers all the possibilities, but everything other than the name is optional:
≪VISIBILITY_MARKER≫≪TYPE≫ ≪NAME≫≪CLASSIFIER≫
Note that both the visibility marker and the classifier, if present, must be joined to name/type, there cannot be a space between them!
Here are a few basic examples:
classDiagram-v2
class MyClass {
aMostBasicAttribute
+aBasicPublicAttribute
-string aPrivateStringAttribute
+Date aPublicStaticDateAttribute$
}
If you’re playing along you can build this diagram with:
npx mmdc --scale 2 -i example-1-attributes.mmd.txt -o example-1-attributes.png
Functions
Again, the full syntax is below, and everything but the name and the parentheses are optional. If you omit the parentheses Mermaid will assume you defined an attribute.
≪VISIBILITY_MARKER≫≪NAME≫(≪PARAMETER_LIST≫)≪CLASSIFIER≫ ≪RETURN_TYPE≫
Where ≪RETURN_TYPE≫
can be void
for functions that don’t return anything, and ≪PARAMETER_LIST≫
is a comma-separated list of parameter names with optional types.
For functions there is a second possible classifier, *
marks a function as abstract.
Again, here are a few basic examples:
classDiagram-v2
class MyClass {
-aPrivateStaticFunction()$ void
+aPublicStaticFunction()$ string
-aPrivateFunction() number
+aPublicFunction() boolean
+aFunctionWithAnUntypedArg(arg1) string
+aFunctionWithAnArg(number arg1) string
+aFunctionWith2Args(number arg1, string arg2) string
+anAbstractFunction()* void
}
If you’re playing along you can build this diagram with:
npx mmdc --scale 2 -i example-2-functions.mmd.txt -o example-2-functions.png
Adding Relationships
Relationships are added at the top level of the diagram, one relationship per line. The basic structure is a class name to start and end, with an ASCII-art arrow connecting them, and optionally quoted cardinalities on each end:
≪CLASS_NAME≫ "≪CARDINALITY≫" ≪ARROW≫ "≪CARDINALITY≫" ≪CLASS_NAME≫
The three types of arrow are:
%% Inheritance
--|>
%% Composition
--*
%% Aggregation
--o
Note that you can draw the arrows in either direction, so you can use <|--
instead of --|>
, which ever you prefer.
Also note that if you don’t need to include detail in your classes, simply using them in a relationship is enough to get them added to a diagram, so the Markup for the atom example from the previous instalment is simply:
classDiagram-v2
class Nucleon {
<<abstract>>
}
Atom "0..1" o-- "1..*" Nucleon
Atom "*" o-- "*" Electron
Nucleon <-- Proton
Nucleon <-- Neutron
Nucleon "1" *-- "3" Quark
Again, if you’re playing along you can generate the diagram with:
npx mmdc --scale 2 -i example-3-atom.mmd.txt -o example-3-atom.png
Final Thoughts
This very simple syntax is all we need generate UML class diagrams for JavaScript code. If you want to learn how to do more with Mermaid, their documentation describes the syntax for all the different diagram types supported.
Our next step in this series will be to build the UML Class Diagram for the JavaScript port of the Crypt::HSXKPasswd Perl module.