DEV Community

Cover image for Creating Class Diagrams with Mermaid.js
Matt Eland
Matt Eland

Posted on • Originally published at newdevsguide.com

Creating Class Diagrams with Mermaid.js

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:

A class diagram

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
    }
Enter fullscreen mode Exit fullscreen mode

Class Diagram

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Class Diagram

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.

Class Diagram

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
Enter fullscreen mode Exit fullscreen mode

Class Diagram

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
Enter fullscreen mode Exit fullscreen mode

Class Diagram

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
Enter fullscreen mode Exit fullscreen mode

Class Diagram

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
    }
Enter fullscreen mode Exit fullscreen mode

Class Diagram

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
    }
Enter fullscreen mode Exit fullscreen mode

Class Diagram

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
Enter fullscreen mode Exit fullscreen mode

Class Diagram

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)

Collapse
 
cicirello profile image
Vincent A. Cicirello

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>>.

Collapse
 
integerman profile image
Matt Eland

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.

Collapse
 
cicirello profile image
Vincent A. Cicirello

You're welcome. Nice writeup on using mermaid for class diagrams.