Diagram definition file
Orthogram uses YAML for its input file format. Of course, YAML being a superset of JSON, you can use JSON if you prefer so. The top-level structure must be a YAML mapping; the program recognizes the following keys, each one containing a different category of definitions:
diagram
rows
blocks
connections
styles
groups
include
diagram
The diagram
section contains the Attributes of the diagram
itself. It is entirely optional; you do not need one if you do not
intend to customize your diagram. Here is an example of a diagram
section:
diagram:
label: This is my diagram!
label_position: bottom
font_weight: bold
The optional label of the diagram is the title of the drawing. If you want a label with more than one line of text, use the newline character in the string:
diagram:
label: "Two lines separated by\na newline character"
For longer texts, the YAML literal style may be more convenient:
diagram:
label: |-
You can also use
YAML literal style
Consult the Attributes section for a list of attributes available for diagrams. Out of all those attributes, however, the following are of particular significance to the program:
collapse_connections
If you set this to
true
, collinear segments of connections belonging to the samegroup
will collapse into a single segment. This may help reduce the clutter, though it depends on the application. Try it out and see.scale
Use this attribute to scale the output image by a factor you specify. E.g.
scale: 1.5
will produce an image enlarged by 50%. The default value is 1 (i.e. no scaling).
rows
The rows
section of the diagram definition file defines the layout
of the diagram. It is essential; without one, the program cannot
determine the position of any element to draw!
The program lays out the elements of the diagram in a rectangular grid. The grid consists of cells, which are arranged in rows and columns. You define the grid one row at a time.
The rows
structure is a sequence of row definitions. Each row
definition is a sequence of strings. Each string corresponds to one
cell in the row. If the string is neither null nor empty the cell is
tagged with the string; otherwise it is an anonymous cell. Here is
an example:
rows:
- [a, ~ , c, c]
- [b, "", c, c]
The grid above contains a cell tagged with “a”, another one tagged with “b”, two anonymous cells, and four cells tagged with “c”.
You use the tags to refer to a cell or group of cells. More precisely, you use the tags to define the shape and position of the blocks, which is the subject of the section that follows.
Note that, since the grid is rectangular, all the rows must contain the same number of cells, which is the length of the longest row in the definition. You do not have to worry about it, though; the program will pad shorter rows with anonymous cells, until all rows have the same length.
Of course, using a text editor to manipulate the layout of the diagram can quickly become cumbersome, especially when the diagram grows large. To counter this, Orthogram provides the option to define the rows of the diagram in a separate CSV file. Maintaining a CSV file is quite easy using a spreadsheet program, like LibreOffice Calc. Please read the chapter on the include section to find out how you can add a reference to an external CSV file in your DDF.
blocks
Each block occupies a rectangular area of the diagram grid. You must have at least a couple of blocks to produce a meaningful diagram.
The blocks
section contains a sequence of block definitions. Here
is an example:
blocks:
- name: a
label: A block named 'a'
- label: An anonymous block
tags: [b1, b2]
stroke: [0, 0, 1]
Note that if you do not define a label for a block, the program will use its name as a label instead.
A block occupies the minimal rectangular area of the grid that contains all the cells tagged with the name of the block. In the example that follows, block “a” is just one cell, whereas block “b” covers six cells, including the cell on which “a” stands:
rows:
- [b, a ]
- [~, ~, b]
blocks:
- name: b
label: A block of 6 cells
- name: a
label: A single-cell block
Note that, in the example above, the definition of block “b” comes
before the definition of block “a”. This is important, because the
program draws the blocks in the order they appear in the definition
file. We do not want block “b” to hide block “a” under it! What is
more, the program will apply padding around block “a” (the amount of
padding depends on the values of the padding_*
attributes of block
“b”). The final image will be of block “a” lying inside block “b”,
which is what one actually wants in situations like this.
If you want to expand a block beyond the cells tagged with its own
name, you can add more tags to it using the tags
pseudo-attribute:
rows:
- [a, ~, b]
- [a, ~, c]
- [a ]
blocks:
- name: a
tags: [b, c]
label: Covers 9 cells!
Tags that are neither names of blocks nor mentioned in a tags
sequence are “leftover” tags. The program does not throw them away.
Instead, it uses them to autogenerate blocks, one block for each
unique tag. These automatically generated blocks come with default
attributes and are labelled with their name. This can be convenient
when constructing simple diagrams. The example below is a complete,
self-contained diagram definition, without a blocks
section in it:
rows:
- [a, b]
connections:
- start: a
end: b
connections
The connections
section defines the connections between the
blocks. It is a sequence of connection definitions. Each definition
must declare the start
and the end
of the connection; it may
also include any Attributes appropriate for connections. Here
is an example:
rows:
- [a, b]
- [~, c]
connections:
- start: a
end: b
stroke: [0, 0, 1]
- start: b
end: c
stroke: [1, 0.5, 0.25]
Regarding the value of the start
and end
pseudo-attributes, it
can be one of the following:
A block name.
A sequence of block names.
A mapping from block names to cell tags.
connections:
# This will create six connections.
- start: [a, b]
end: [c, d, e]
# This will create four connections starting from cell "x" under
# block "f". The second and third connections also aim at
# specific tagged cells under "h" and "i". The target of the
# first and last connections are just blocks "g" and "j".
- start: {f: x}
end: {g, h: y, i: z, j}
The order of the connection definitions is important, because the program draws the connections in the order that they appear in the definition file.
Since it is not easy to avoid the intersection of connection lines in
complex diagrams, it is better that you draw intersecting connections
with a different stroke
color to make obvious that the connection
lines are not connected at the intersection points.
Another way to avoid intersecting connection lines appearing as if
they were connected at the intersections is to draw a buffer around
the lines. Attributes buffer_fill
and buffer_width
control
the appearance of the buffer. By default, the program draws the
connections without a buffer.
Connections may have an additional group
pseudo-attribute, which
works together with the collapse_connections
diagram attribute.
If collapse_connections
is set to true, connections of the same
group that run along the same axis can be drawn on top of each other,
thus reducing the clutter and size of the diagram. The group
value is just a string. Note that setting this attribute affects the
drawing order of the connections. When the program encounters a
connection marked with a group name, it draws all other connections
that belong to the same group immediately after the first one. The
order of groups thus becomes more significant compared to the order of
the connections themselves. It is probably good practice to keep
connection definitions referring to the same group close together in
the file.
You can add labels to connections in a manner similar to blocks. A connection can have up to three labels: a start label, which is drawn near the first point of the connection, an end label, which is drawn near the last point, and a middle label, which is drawn somewhere between the other two.
The ability to have more than one label on a connection introduces some necessary complexity to the definition. Each label is a separate element of the diagram inside the connection element, which acts as its parent. Each label element can have its own attributes. You can define text attributes on the connection itself; if you do so, the values of the attributes in the connection serve as default values for the corresponding attributes in the label elements.
To add labels to a connection, use the following elements:
start_label
middle_label
end_label
The following example demonstrates how to use them:
connections:
- start: A
end: B
text_fill: [1, 0, 0]
start_label: Start
middle_label: Somewhere in between
end_label:
label: End
text_fill: [0, 0, 1]
text_orientation: vertical
The example can be explained as follows:
The
text_fill
attribute of the connection defines a default color for all the labels, which is red.The
label
attribute is not defined on the connection itself. If it were, it whould serve as the default text for all three labels.The values of the
start_label
andmiddle_label
elements are plain strings. This is shorthand we can use when there is no need to override attributes for a label.The
end_label
element overrides the value of thetext_fill
attribute of the connection; the label is drawn in blue. It also forces the label to be horizontal, regardless of the orientation of the connection segment over which it is drawn.
Note that if you define the label
attribute on a connection, the
program will implicitly set the middle label with it, i.e. the middle
label is the default label for a connection. The definitions in the
following example all have the exact same result:
connections:
- start: A
end: B
middle_label:
label: Some text
- start: A
end: B
middle_label: Some text
- start: A
end: B
label: Some text
styles
You can add style definitions to the styles
section to create
named styles that the elements of the diagram (blocks, connections and
groups) can refer to. Each style definition consists of attribute
key-value pairs. For example, the following two blocks are drawn in
the same color:
blocks:
- name: a
style: reddish
- name: b
style: reddish
rows:
- [a, b]
styles:
reddish:
stroke: [0.5, 0, 0]
stroke_width: 3
fill: [1, 0.85, 0.85]
You add style references to an element using the style
attribute.
The value of this attribute can be either a single style name or a
sequence of style names. Styles in a sequence override the ones
coming before them. Attributes you define in the element itself
override the attributes it inherits from the referenced named styles.
There are two special style names, default_block
and
default_connection
, which you can use to set default values for
all the blocks and connections in the diagram.
Styles themselves cannot reference other styles, i.e. the program
ignores the style
attribute in style definitions.
groups
You can use the groups
section to attach attributes to connection
groups. Since connections in the same group may collapse on one
another, it is usually desirable for all the connections in one group
to share the same attributes. In the example that follows, all
connections are drawn in blue:
groups:
water:
stroke: [0, 0, 1]
stroke_width: 4
connections:
- start: a
end: b
group: water
- start: c
end: d
group: water
A group definition may contain references to named styles. Note that
creating an entry in the groups
section is not necessary for the
grouping of the connections; a common group
name in each
connection definition is sufficient.
include
Starting with version 0.5.4, Orthogram lets you split a diagram
definition into multiple files. You can then compose the several
files into a single definition using include
definitions in your
main DDF.
The facility is general and can be used recursively: you can include other files in your included files and so on. The program includes each file just once, thus avoiding cyclical includes. Note, however, that deeply nested hierarchies can be confusing and you should probably avoid them. In particular, the sequence of merging elements into the definition, though well defined, can lead to surprising results. The facility was actually implemented with the following applications in mind:
Sharing styles between diagrams
Defining the diagram layout using CSV files
Although we describe the include
section last, it is probably
better to put it at the top of the DDF. The program merges included
files into the definition before considering any other section in
the file, so putting the include
section at the top looks more
natural. Within the include
section of a file, the program merges
the included files in the order they appear.
This is an example that shows how you can include styles and row definitions in your DDF:
include:
- path: include/styles.yaml
- path: include/rows.csv
Note that relative paths are relative to the location of the file that includes them. You may use absolute paths as well.
The program determines the type of the file (YAML or CSV) from the
extension of the file name. If it ends in .csv
or .txt
, the
program thinks it is a CSV rows file; otherwise it tries to load it as
a YAML file. You can enforce the type using the type
attribute:
include:
- path: styles.txt
type: yaml
- path: rows
type: csv
The character that delimits the block names in the CSV file is the
comma by default. If you have a file with a different delimiter, you
can declare it using the delimiter
attribute. In the following
example, the row definitions file employs the tab character as the
delimiter 1:
include:
- path: rows.txt
delimiter: "\t"
- 1
We should probably call this a TSV file, but people use the term “CSV” for any such file, regardless of the delimiter.