Skip to content

Class diagrams

A class diagram is UML: classes drawn as boxes with attribute and method compartments, joined by typed relations. It suits a data model, a type hierarchy or the implementations of an interface. The class spec reference lists every field and its allowed values.

A class has an id, a label, and optionally attributes and methods. Each member has a visibility of + (public), - (private), # (protected) or ~ (package), and its text. A method may be static, drawn underlined, or abstract, drawn in italics. A stereotype adds a line such as «interface» above the name, and type_params adds <T> to it.

members.json
{
"type": "class",
"direction": "RIGHT",
"classes": [
{
"id": "repository",
"label": "Repository",
"stereotype": "interface",
"type_params": ["T"],
"methods": [
{"visibility": "+", "text": "find(id: string): T", "abstract": true},
{"visibility": "+", "text": "save(item: T)", "abstract": true}
]
},
{
"id": "orders",
"label": "OrderStore",
"attributes": [
{"visibility": "+", "text": "name: string"},
{"visibility": "-", "text": "cache: Map"},
{"visibility": "#", "text": "limit: int"},
{"visibility": "~", "text": "version: int"}
],
"methods": [
{"visibility": "+", "text": "find(id: string): Order"},
{"visibility": "+", "text": "open(): OrderStore", "static": true}
]
}
],
"relations": [
{"id": "orders-repository", "from": "orders", "to": "repository", "kind": "realization"}
]
}
An interface Repository with a type parameter T and two abstract methods, realized by an OrderStore class with four attributes of different visibility, a method, and a static method.An interface Repository with a type parameter T and two abstract methods, realized by an OrderStore class with four attributes of different visibility, a method, and a static method.
                                  ┌────────────────────────────┐
                                  │         OrderStore         │
  ┌──────────────────────────┐    ├────────────────────────────┤
  │       «interface»        │    │ + name: string             │
  │      Repository<T>       │    │ - cache: Map               │
  ├──────────────────────────┤◁╌╌╌│ # limit: int               │
  │ + find(id: string): T *  │    │ ~ version: int             │
  │ + save(item: T) *        │    ├────────────────────────────┤
  └──────────────────────────┘    │ + find(id: string): Order  │
                                  │ + open(): OrderStore $     │
                                  └────────────────────────────┘

Text art has no underline or italics, so it marks a static member with a $ suffix and an abstract one with *, as in the art above.

Show only the members the reader needs. check reports god-class above 15 members, and overlong-member for a member line over 40 characters, which widens the box.

There are six kinds of relation. Pick by what the two classes mean to each other:

Kind Use it for Drawn as
inheritance a subtype of a supertype a hollow triangle at the supertype
realization a class implementing an interface a dashed line, hollow triangle at the interface
composition owned parts: the part cannot outlive the whole a filled diamond at the whole
aggregation shared parts: the part exists on its own a hollow diamond at the whole
dependency uses: a parameter, a call a dashed line with an arrowhead
association anything else (the default) a plain line
relations.json
{
"type": "class",
"direction": "RIGHT",
"legend": true,
"legend_labels": {"inheritance": "is a"},
"classes": [
{"id": "animal", "label": "Animal"},
{"id": "dog", "label": "Dog"},
{"id": "pet", "label": "Pet", "stereotype": "interface"},
{"id": "owner", "label": "Owner"},
{"id": "kennel", "label": "Kennel"},
{"id": "collar", "label": "Collar"},
{"id": "vet", "label": "Vet"}
],
"relations": [
{"id": "dog-animal", "from": "dog", "to": "animal", "kind": "inheritance"},
{"id": "dog-pet", "from": "dog", "to": "pet", "kind": "realization"},
{
"id": "kennel-dog",
"from": "kennel",
"to": "dog",
"kind": "aggregation",
"from_card": "1",
"to_card": "*"
},
{
"id": "dog-collar",
"from": "dog",
"to": "collar",
"kind": "composition",
"from_card": "1",
"to_card": "1"
},
{"id": "vet-kennel", "from": "vet", "to": "kennel", "kind": "dependency"},
{
"id": "owner-vet",
"from": "owner",
"to": "vet",
"kind": "association",
"from_card": "1",
"to_card": "*"
}
]
}
A Dog inherits from Animal and realizes the Pet interface; a Kennel aggregates many Dogs, a Dog is composed of one Collar, a Vet depends on Kennel, and an Owner is associated with many Vets; a legend lists the six kinds.A Dog inherits from Animal and realizes the Pet interface; a Kennel aggregates many Dogs, a Dog is composed of one Collar, a Vet depends on Kennel, and an Owner is associated with many Vets; a legend lists the six kinds.
                            ┌────────┐
                            │ Animal │◁────────┐
                            └────────┘         │
                                               │
                                               │
                            ┌──────────────┐   │  ┌──────┐
                            │ «interface»  │   └──│ Dog  │  1 ┌────────┐
                            │     Pet      │◁╌╌╌╌╌│      │◆───│ Collar │
                            └──────────────┘   ┌──│      │ 1  └────────┘
                                               │  └──────┘
                                               │ *
  ┌────────┐  * ┌──────┐    ┌────────┐         │
  │ Owner  │────│ Vet  │╌╌╌►│ Kennel │◇────────┘
  └────────┘ 1  └──────┘    └────────┘ 1

◇── aggregation
─── association
◆── composition
╌╌► dependency
◁── is a
◁╌╌ realization

When two classes could be joined by more than one kind, choose the one with the strongest ownership that is true, and leave association for relations that fit none of the others.

For inheritance and realization, from is the subtype (or the implementing class) and to is the supertype or interface: dog-animal above reads “Dog inherits from Animal”. The engine lays the supertype out first and draws the hollow triangle at its end. For the other kinds the adornment sits at the from end of a composition or aggregation: from is the whole.

directed puts an arrowhead at to. It is on by default for dependency and off for association, and you may set it on an association to draw an arrowhead at to. It is not allowed on the four adorned kinds (inheritance, realization, aggregation, composition), whose adornment already says it.

from_card and to_card are free text placed beside the wire ends: 1, *, 0..*. Use them where multiplicity is the point, such as the 1 to * between Kennel and Dog.

Give every relation an id, as you would a flow edge, so that diago diff and render -previous can match relations across versions.

A package groups related classes in a labelled frame. It follows the same rules as a flow group, under the key packages: nesting up to 3 levels and an optional color. A package of one class only adds a frame, so give it at least 2.

packages.json
{
"type": "class",
"direction": "DOWN",
"classes": [
{"id": "order", "label": "Order", "attributes": [{"visibility": "-", "text": "id: string"}]},
{"id": "line", "label": "OrderLine"},
{
"id": "invoice",
"label": "Invoice",
"attributes": [{"visibility": "-", "text": "total: Money"}]
},
{"id": "payment", "label": "Payment"}
],
"relations": [
{
"id": "order-line",
"from": "order",
"to": "line",
"kind": "composition",
"from_card": "1",
"to_card": "*"
},
{
"id": "invoice-order",
"from": "invoice",
"to": "order",
"kind": "association",
"label": "bills"
},
{
"id": "invoice-payment",
"from": "invoice",
"to": "payment",
"kind": "aggregation",
"from_card": "1",
"to_card": "*"
}
],
"packages": [
{"id": "sales", "label": "Sales", "contains": ["order", "line"]},
{"id": "billing", "label": "Billing", "color": "gray", "contains": ["invoice", "payment"]}
]
}
A Sales package holds Order, composed of many OrderLines, and a gray Billing package holds an Invoice that bills the Order and aggregates many Payments.A Sales package holds Order, composed of many OrderLines, and a gray Billing package holds an Invoice that bills the Order and aggregates many Payments.
     ┌─ Billing ─────────────────┐
     │                           │
     │  ┌────────────────┐       │
     │  │    Invoice     │       │
     │  ├────────────────┤       │
     │  │ - total: Money │       │
     │  └────────────────┘       │
     │       │      ◇ 1          │
     │       │      │            │
     │       │      └────┐       │
     │       │           │ *     │
     │       │     ┌──────────┐  │
     │       │     │ Payment  │  │
     │ bills │     └──────────┘  │
     └───────┼───────────────────┘
             │
             │
             │
  ┌─ Sales ──┼─────────┐
  │          │         │
  │  ┌──────────────┐  │
  │  │    Order     │  │
  │  ├──────────────┤  │
  │  │ - id: string │  │
  │  └──────────────┘  │
  │          ◆ 1       │
  │          │         │
  │          │ *       │
  │   ┌────────────┐   │
  │   │ OrderLine  │   │
  │   └────────────┘   │
  └────────────────────┘

Use a package for a boundary that means something to the reader, and keep its label to one short word in text art, since a long one widens the frame.

"legend": true adds one row per relation kind present, which helps once 3 or more kinds appear and the reader may not know the notation. legend_labels overrides a row’s text: the relations diagram above renames the inheritance row to is a. An empty value hides a row. In text art the legend is printed under the diagram, as above.

For every field and value, see the class spec reference.