Recently I've fallen in love with using Mermaid.js to take plain text and convert it to a technical diagram. In this article I'll show you how Mermaid.js class diagrams work and how you can use them to communicate class relationships in markdown documents.
Class Diagrams 101
If you're not familiar with class diagrams, they're a subset of Unified Markdown Language (UML) used to describe the relationship between different classes using boxes and arrows as shown below:
Here this diagram describes a Player and Monster class that both inherit from an abstract DamageableObject that in turn inherits from GameObject.
The diagram also illustrates the methods and attributes each class has. For example, in the diagram above, the Monster class has a private ThreatLevel attribute of type int, a private Color attribute of type Color, and two public methods called MakeNoise and OnKilled that return string and void respectively.
Class diagrams are effective at quickly communicating class relationships between engineers, but should not be over-valued in lieu of working software. For more information on class diagrams and their usage, I recommend their Wikipedia article.
Using Mermaid.js
Now that we've covered what class diagrams are, let's briefly cover Mermaid.js.
Mermaid.js is an open-source JavaScript library that takes specialized markdown and generates and renders diagrams from it.
You can use Mermaid.js in a variety of places, including GitHub markdown, Polyglot Notebooks, and a live mermaid editor.
In the rest of this article we'll explore the use of Mermaid.js to render class diagrams.
Defining a Class
Let's start simple with some Mermaid code to define a single class:
classDiagram
class GameObject {
-String Name
-int PosX
-int PosY
+Despawn() void
}
The first line tells Mermaid which type of diagram we're creating.
Next we declare a class in a semi-familiar way using the class keyword, the name of the class, and { } to denote the class boundaries.
Inside of the class we can declare any number of members. Fields are defined using their data type following their type name while methods are defined using the name of the method, parentheses and optionally a list of parameters, and then finally the return type of the method.
We also use + to denote public members and - to denote private members.
Alternative Class Syntax
The syntax I described above is the more modern Mermaid.js syntax for describing classes. Not all tools supporting Mermaid support this format.
For the older versions of Mermaid.js class diagrams you may need to use the following syntax:
classDiagram
class GameObject
GameObject : -String Name
GameObject : -int PosX
GameObject : -int PosY
GameObject : +Despawn() void
The end result is the same as if you had used the more familiar { } syntax earlier. My personal preference is a strong leaning towards the earlier syntax that uses { } so I only use this older syntax if a tool uses a version of Mermaid.js that doesn't support my preferred syntax.
Relationships Between Classes
Most class diagrams have more than one class in them, so let's take a look at a relationship between two classes:
classDiagram
class GameObject {
-String Name
-int PosX
-int PosY
+Despawn() void
}
class DamageableObject {
+int MaxHealth
-int Health
+IsDead() bool
+TakeDamage(int damage) void
+OnKilled() void
}
GameObject <|-- DamageableObject
Here we define two classes and use the GameObject <|-- DamageableObject line to declare that DamageableObject inherits from GameObject.
While inheritance is the most common relationship type, Mermaid.js supports a number of different connectors between classes, including:
- <|-- inheritance
- *-- composition
- o-- aggregation
- --> association
- ..> dependency
- ..|> realization
- -- solid link
- .. dashed link
As a brief refresher, the difference between composition and aggregation is that while both are composed of one or more of the other object, composition implies that the first item cannot exist without the collection of things while aggregation allows the item to exist without any items.
For example, the human body is composed of cells while a salesperson aggregates sales.
See the official Mermaid.js documentation for full details and examples of the different relationship types.
Documenting Relationships
You may have noticed the diagram above included a pair of labels on their relationships.
You can describe any relationship in a class diagram by adding a : and then the descriptive text after the relationship as follows:
classDiagram
Human *-- Cells : composed of
Salesperson o-- Sales : makes
Human <|-- Salesperson : is
Note how Mermaid.js has rearranged the diagram slightly in light of the new information that salespeople are humans. This highlights one of the key benefits of Mermaid.js in my opinion: all of your effort goes to describing relationships instead of trying to get the perfect layout.
Multiplicity in Relationships
Sometimes you want to get into more granular details about the multiplicity of your relationships, particularly when dealing with composition and aggregation.
In these cases, you may want to say that each cell will belong to 1 person and 1 person will have many cells.
Mermaid.js lets you do this by specifying the multiplicity details to the left and right of the arrow in quotes as follows:
classDiagram
Human "1" *-- "*" Cells : composed of
Salesperson "1" o-- "0..*" Sales : makes
Human <|-- Salesperson : is
I'll be the first to admit that relationship names and multiplicities together make this hard to read. Thankfully, these features are independent and you can describe the relationship without the label as just Human "1" -- "" Cells if that makes the diagram clearer.
Before we move on, a class diagram with multiplicities is very similar to an entity relationship diagram (ERD) which Mermaid.js also supports. Check out my article on Entity Relationship Diagrams with Mermaid.js for more details.
Special Cases for Mermaid.js Class Diagrams
Annotating Classes for Interfaces, Abstract, and More
Sometimes you want to be able to denote a class as being somehow special. For example, you may want to mark a class as abstract, an interface, an enumeration, as sealed / final, or to denote generic type parameters.
For these reasons, mermaid gives us annotations that can be applied to classes.
For example, the following code uses the <<abstract>>
annotation to denote a class as abstract and the * marker to denote individual abstract members:
classDiagram
class GameObject {
-String Name
-int PosX
-int PosY
+Despawn() void
}
class DamageableObject {
<<abstract>>
+int MaxHealth
-int Health
+IsDead() bool
+TakeDamage(int damage) void
+OnKilled()* void
}
GameObject <|-- DamageableObject
Other uses of annotations typically include <<interface>>
, <<enumeration>>
, <<sealed>>
, <<static>>
, and <<service>>
, though there's nothing stopping you from putting whatever annotations you want on your own classes.
Generic Type Parameters
Sometimes you'll want to have generic classes in your application.
These classes are typically represented in code as ClassName<T>
where T is the type they work with.
For example, a List class is a list containing only integers while a Queue is a Queue that only handles Request instances.
Mermaid.js does not support the standard <T>
way of declaring generic classes or type parameters. Instead, you have to surround the generic type parameters with tildes like ~T~
as shown below:
classDiagram
class Queue~T~ {
-IEnumerable~T~ items
+Enqueue(T item) void
+Dequeue() T
+HasItems() bool
}
Warning: it does not appear to currently be possible to specify multiple generic type parameters. For example, if you wanted to declare a Dictionary to map from strings to color values, the C# code for this would be Dictionary<string, Color>
hexColors and you would assume that the Mermaid.js code would be -Dictionary~string,Color~ hexColors
.
However, at the time of this writing that does not appear to work correctly. Instead I recommend adding an underscore instead of the comma and using -Dictionary~string_Color~ hexColors
to denote the multiple generic type parameters.
Static Members
Static members are members associated with the class itself instead of an instance of the class. For example, int.Parse and Console.WriteLine are both examples of static methods in C# .
To denote a method as static, you add a $ after its declaration as shown below:
classDiagram
class ColorManager {
-Dictionary~string_Color~ hexColors$
+BuildColorFromHex(string hexCode)$ Color
}
Adding Notes to Classes
If you wanted to add textual annotations to the diagram overall, you can add a note element for the document with the following syntax:
note "This is a note for the whole diagram"
Alternatively, you can annotate a specific class with a note by using the following syntax:
note for Player "This is a note on the Player class"
Here's these two features put together for a larger diagram:
classDiagram
note "This is a note for the whole diagram"
note for Player "This is a note on the Player class"
class GameObject {
+String Name
+int PosX
+int PosY
+Despawn() void
}
class DamageableObject {
<<abstract>>
+int MaxHealth
-int Health
+IsDead() bool
+TakeDamage(int damage) void
+OnKilled()* void
}
class Player {
-int Score
-int LivesRemaining
+OnKilled() void
}
class Monster {
-int ThreatLevel
-Color Color
+MakeNoise(double decibelLevel) string
+OnKilled() void
}
GameObject <|-- DamageableObject
DamageableObject <|-- Player
DamageableObject <|-- Monster
Warning: The notes feature is relatively new as of this writing and may not work in all editors currently in use.
Next Steps
Mermaid.js has a lot of features built in for class diagram creation. While I personally don't find myself using class diagrams as much as I did in my undergraduate education, I do see value in this.
For example, if you are maintaining an open-source library, being able to put a class diagram showing the classes that inherit from a specific abstract class will quickly help developers understand how to interact with your code in a very visual way.
I'd encourage you to try class diagrams out in GitHub markdown, Polyglot Notebooks, or the live editor and see what you think.
I'd also recommend checking out the Mermaid.js documentation for additional features, including details on customizing the style and layout of these diagrams.
I'm not done writing about Mermaid.js, so stay tuned for more information about other diagrams you can build using this fantastic library!
Top comments (3)
In your section on annotating classes, DEV's editor cut out your examples. I think its sanitizer probably interpreted them as attempts to use unsupported html tags. All you see in the sentence listing the examples is <>. You can fix it with inline code using backticks around each one like
<<interface>>
.Fixed. Yep, this is what happens when you port over a non-MD source article and don't take the time to review all of it. Thank you.
You're welcome. Nice writeup on using mermaid for class diagrams.