-
Notifications
You must be signed in to change notification settings - Fork 2
Dictim Syntax
'dictim syntax' is a data format which is a subset of Clojure's edn data format for specifying a diagram.
'dictim format' a diagram specified in dictim syntax may be specified in either edn or json. They are equivalent.
The syntax of dictim closely resembles that of d2's text language. It is intended to be easy to read across between the two. dictim is nested format like d2. It also closely resembles that of graphviz' dot language (which is itself similar to d2). In the specification below, it is called out when a particular feature is d2 only. Dictim's primary target is d2, with dot secondary.
If dictim's nested syntax is not what you require, there are two other options:
-
The dictim.graph library allows you to specify a diagram as a graph. The graph ddefinition is then converted to dictim syntax.
-
dictim itself (i.e. this library) has a second syntax, called 'flat dictim' (which is covered at the bottom of this document). Its flat structure makes it easier to manipulate for some use cases, e.g. to break part for storage, than dictim's nested syntax.
- Elements
- Shapes
- Connections
- Containers
- Special Objects
- Attributes
- Comments
- Lists
- Empty Lines
- Reserved d2 keywords
- Types
- In Depth
- Gotchas
- Json syntax
- Flat Dictim Syntax
Dictim is a series of dictim elements - each one closely matching in syntax their d2 equivalent. The d2 tour explains the strcutre and semantics of the d2 language.
A dictim element can be one of six principal types: comments, shapes, connections, containers, attributes and lists.
All but attributes are modelled using Clojure vectors - they produce objects in the diagram when dictim is compiled to d2 and converted into an image. Attributes on the other hand are for styling how objects are rendered and are modelled as Clojure maps. Just remember; vectors for objects and maps for styling objects. Every good rule has an exception, and Vars which are d2's variables are modelled in dictim as maps, but may be used to specify both rendering/styling information and data.
A collection of dictim element is modlled as a Clojure list, (..elements..)
.
This is the only time that a Clojure list is used in the dictim syntax.
The syntax for a shape is:
[<shape-key> <label>(optional) <attribute-map>(optional)]
for example:
[:personA "Person A" {:style {:fill "red"}}]
which is represented in d2 as:
personA: Person A {style {fill "red"}}
The shape-key
can be either a string, a number or a keyword.
The label should be a string.
The attribute map should be a map whose keys are either keywords or strings, and whose values are either strings or numbers.
In dictim, the elements of a shape are laid out in a Clojure vector.
Examples with the optional parts missing:
[:personA "Person A" {:style {:fill "red"}}]
[:personA "Person A"]
[:personA {:style {:fill "red"}}]
[:personA]
["person A" "A person called Mike"]
["person A"]
[:people.personA]
are all valid shapes. The last one denotes that 'personA' is inside a container called people
d2 offers a shortcut for creating a shape and styling one aspect of it at the same time.
for example:
aShape.style.fill: red
which when parsed into dictim becomes
["aShape" {"style.fill" "red"}]
As a text format, d2 is quite free-form and occasionally offers more than one way to express the same thing. That means that when you round-trip dictim to d2 and back again (by compiling to d2, then parsing to dictim) you might not get the same as the original, even though semantically it's the same.
For example, let's compile a dictim shape to d2 and then parse it back
=> (dictim (d2 [:aShape.style.fill "red"]))
(["aShape" {"style.fill" "red"}])
Note how the attributes are put into their own map on the way back.
Clojure keywords are converted to strings for d2 and not automatically converted back again, although you can control this behaviour by passing a key-fn
to the dictim
function that handles parsing.
For shapes (and connections and containers), if you wish to have a multi-line label, use \\n
at the desired point in the label string. \n
must be escaped, so always \\n
.
There are two types of connections; single connections and multiple connections.
Syntax:
[<key1> <direction> <key2> <label>(optional) <attribute-map>(optional)]
for example:
[:personA "--" :personB "brothers" {:style.stroke-width 2}]
which is represented in d2 as:
personA -- personB: brothers {style.stroke-width: 2}
key1
and key2
must each be either the key of a shape or a container
direction
must be one of "<>" "--" "<-" "->"
Multiple connections are expressed all in one go..
Syntax:
[<key1> <dir1> <key2> <dir2> <key2> ... <keyN> <label>(optional) <attribute-map>(optional)]
for example:
[:a "<-" :b "->" :c "children" {:style.stroke-width 2}]
which is represented in d2 as:
a <- b -> c: children {style.stroke-width: 2}
Multiple connections have to share the same label and attributes if those are specified.
[!Note] This part of the dictim syntax applies to d2 only
Connection references are a way of referring to a connection that you've already specified, either to style it or null it out.
for example:
(x -> y)[0].style.stroke: red
in d2 becomes
[:x "->" :y [0] {:style.stroke "red"}]
where the index 0
would refer to the first occurence of the connection x -> y
in the d2 text.
An index of a connection reference can also be a glob *
when you wish to refer to all connections.
You can null out a connection reference by
[:x "->" :y [0] nil]
will not be displayed.
The syntax for containers is the same as for shapes, except that at the end you put the set of contained elements, which may be of any number and each of any of the six types.
Syntax:
[<ctr-key> <label>(optional) <attribute-map>(optional) <nested elem1> <nested elem2> ... <nested elemN>]]
for example:
[:family1 "The Jones'" {:style {:fill "red"}}
[:personA "Henrick"]
[:personB "Michael"]
[:personA "--" :personB "brothers"]]
which in d2 is:
family1: The Jones' {
style: {
fill: red
}
personA: Henrick
personB: Michael
personA -- personB: brothers
}
Containers may nest other containers which may nest ... and so on.
[!Note] This part of the dictim syntax is valid in dot, but has no special meaning
Special objects, e.g.:
- Text & Code
- Icons & Images
- SQL Tables
- UML classes
- Sequence diagrams
- Grid diagrams
are all supported via the dictim equivalent of the relevant d2 syntax.
We've seen attibutes already; attribute maps can belong to shapes, connections and containers to specify d2 rendering instructions.
They can also appear at the top level, i.e. an attribute map can exist as a top level element.
An example is the :direction/ direction: reserved keyword, e.g.
{:direction "right"}
which compiles to d2:
direction: right
note the lack of brackets in d2 for top level attributes, i.e. those occurring at diagram level.
Note that because d2 reserves the special character #
to indicate the start of a comment, hex colors must be either single or double-quoted. e.g.
{:style.fill "#bfe6a5"}
won't work. Instead do:
{:style.fill "'#bfe6a5'"}
which could be equivalently expressed as a nested version:
{:style {
:fill "'#bfe6a5'"
}
}
d2's Classes and Vars are also modelled as special maps which resemble an attribute map.
Please note that in dictim, attributes that exist as standalone elements at the top level of a set of elements are sometimes called 'directives'.
[!Note] This part of the dictim syntax is valid in dot, but has no special meaning
classes
is a reserved word in d2 and in dictim.
This d2 class definition:
classes: {
uno: {
label: load balancer
width: 100
height: 200
style: {
stroke-width: 0
fill: '#44C7B1'
}
}
dos: {
label: dos
}
}
which be represented in dictim as
{"classes"
{"uno"
{"label" "load balancer",
"width" 100,
"height" 200,
"style" {"stroke-width" 0, "fill" "'#44C7B1'"}}}}
As per d2, a class definition is later used on a shape, container or edge, for example in dictim.
{"x.class" [:list "uno" "dos"]}
[!Note] This part of the dictim syntax is valid in dot, but has no special meaning
vars
is a reserved word in d2 and in dictim.
This d2 var definition:
vars: {
server-name: Cat
}
is equivalent to the dictim
{"vars" {"server-name" "Cat"}}
Substitutions in dictim are the same as in d2. For example, substituting the above var into a shape's label
server2: ${server-name}-2
In d2 comment lines are started with the character #
.
Dictim mirrors this; a comment is a string that starts with the character #
;
"# Hans, accused of cheating"
In d2:
"# Hans, accused of cheating"
Lists are a second type of container, for a sequence of elements that should all be put on one line separated by semicolons:
[:list [:a-shape "A shape"] [:b-shape "B Shape"]]
In d2:
"a-shape: A shape; b-shape: B shape"
Lists may not nest other lists.
In d2, putting elements on one line doesn't have a special meaning, except inside sequence_diagrams where it denotes the order of the actors - hence the need for [:list <sequence-of-shapes-or-ctrs>]
in dictim. Note that only shapes or containers may be put into a list in dictim.
In d2, this is a top level list. i.e. it occupies its own line in a d2 text file.
There are inner lists as well in d2, which are surrounded by square brackets.
We've seen an example of an inner-list already, with this shape, x, that participates in two styling classes
{"x.class" [:list "uno" "dos"]}
[!Note] Inner lists are not allowed in dot
The goal of this project is to allow for both ways faithful conversion between clojure data structures (e.g. 'dictim') and d2. In d2, to improve readability, you'll often leave empty lines to separate different parts of the file.
Empty lines are parsed into dictim as:
[:empty-lines 1]
where the '1' indicates the number of empty lines at this point.
Whether empty-lines
are captured during parsing is an option (defaulted to not). Please see the parsing page for details.
Something to watch out for!
d2 has a number of reserved keywords for use only in attributes which are worth avoiding when you name the keys in shapes and containers.
The list of reserved keywords:
'shape' 'label' 'source-arrowhead' 'target-arrowhead' 'near' 'icon' 'width' 'height' 'constraint' 'direction' 'tooltip' 'style' 'class' 'grid-rows' 'grid-columns' 'link'
and sub-keys of style....
'opacity' 'fill' 'stroke' 'stroke-width' 'stroke-dash' 'border-radius' 'font-color' 'shadow' 'multiple' '3d' 'animated' 'link' 'filled' 'fill-pattern' 'underline' 'double-border' 'bold' 'italic' 'animated' 'text-transform'
For details of what these all do, please see the d2 [tour] (https://d2lang.com/tour/intro) of the d2 language.
and don't forget the dictim reserved keywords:
:list
and :empty-lines
keywords cannot be used in your shapes, connections and containers.
For dot, Dictim does not currently check against Graphviz' reserved attribute keywords during compilation.
Dictim is quite flexible with types. Element keys can be keywords or strings. In attr maps, the values can be integers, floats , booleans or strings. Non-string types will be converted to strings during compilation. Values in attr maps will be converted back to the primitive type during parsing, and there are the optional key-fn
and label-fn
functions in parsing that can be used to convert the type or format of keys and labels.
[!Note] Globs/asterisks and ampersands are not valid syntax in shape, container, connection keys in the dot language. In labels, they are fine.
In d2, a wildcard character '*' is called a glob.
globs may be used in two different positions in d2 and dictim.
In attributes, when trying to apply a style to more than one object. For example in the dictim
[:aShape] [:bShape] {:*Shape.style.fill "red"}
would cause both shapes to have a red fill.
The &
character can be used to filter where the style is applied as per the d2 docs.
Secondly, in connection keys to indicate multiple connections. The dictim elements
["zone-A"
["machine A"]
["machine B" ["submachine A"] ["submachine B"]]]
["zone-A.**" "->" "load balancer"]
indicates that there should be connections between all three shapes in zone-A and load balancer.
[!Note] This d2 symbol to denote an import '@' is not permitted in dot as a shape/ container/ connection key.
Imports in d2 are represented by the @
symbol which is compiled and parsed by dictim.
Layers, scenarios and steps are modelled in dictim as normal containers whose key is either layers
, scenarios
or steps
d2 is a text format so can be a bit looser in it's syntax than a data format. There are a small number of gotchas to be aware of.
d2 allows for a shape and one styling attribute to be specified all in one go:
aShape.style.fill: red
Is this a shape or an attribute? Dictim decides that since this line of d2 would result in a shape being displayed in the resultant diagram, its purpose is to be a shape, therefore the dictim equivalent is:
["aShape" {"style.fill" "red"}]
However the d2
*Shape.style.fill: red
would not produce a shape in the diagram. It's purpose is to style and the dictim equivalent is:
{"*Shape.style.fill" "red"}
i.e. it's an attribute.
dictim is made from plain Clojure data structures; vectors and maps which have their json equivalents as arrays and objects. Edn dictim and Json dictim therefore look very close and have the same syntax; only the format is different.
For example, the dictim to describe a shape and a container:
[:family1 "The Jones'" {:style {:fill "red"}}
[:personA "Henrick"]
[:personB "Michael"]
[:personA "--" :personB "brothers"]]
in json becomes
[
[
"aShape"
]
[
"family1",
"The Jones'",
{
"style": {
"fill": "red"
}
},
[
"personA",
"Henrick"
],
[
"personB",
"Michael"
],
[
"personA",
"--",
"personB",
"brothers"
]
]
]
The Utilities page details how to convert to/ from json.
In flat dictim, every element is represented by a Clojure map with the same form:
{:type <element type i.e. :shape/:ctr/:conn/:cmt/:attrs>
:key <key for the element>
:meta <a map of all other meta data about the element, e.g. it's label and attrs>
:parent <the key of the parent element (lists and containers)>
.. <any other keys you'd like to add!>}
The flat, homogenous nature of the syntax makes it easier to manipulate than dictim.
The dicitim.flat
namespace has utility functions for flattening and (re)building dictim to\from flat dictim.
:key
is a key generated for the element. For a shape or container, it is its key. For a connection, the connection information as a vector e.g. [:a "->" :b]
, for a comment, the comment string itself, for an attribute map, the map itself and for a list a sequence of the contained elements' keys.
:meta
is all the other information needed in the 'dictim format (as opposed to 'flat dictim') element. It's a map with up to two keys; :label
and :attrs
:parent
is created during the flattening process (dictim -> flat dictim) and use in the rebuilding process.
Please see the Uitilties page for more information about working with flat dictim.