Skip to main content

Draw diagrams of graphs.

Project description

WARNING: This is work in progress. There is no guarantee as to the stability and reliability of this program.

Orthogram is a command line program and Python library that lets you draw diagrams of graphs. It reads a YAML file and produces a SVG file like this one:

examples/powerplant.svg

Orthogram does not aim to be a fully-featured graph layout solution. Actually, it offers just a single layout: grid. More than that, you have to arrange the connected objects manually in the grid and they stay fixed in place; the program will not attempt to move them around trying to optimize any aspect of the diagram. Styling is also rather basic at the moment. It does however try to do a decent job arranging the connections around the objects to produce a tidy, readable drawing.

When used as a command line tool, Orthogram reads a diagram definition file and produces a Scalable Vector Graphics file. The definition file is written in YAML.

Installation

You must have Python 3.8 or newer installed in your system. Run the following command to install package orthogram from PyPi:

python -m pip install orthogram

Note however that some of Orthogram’s dependencies rely on C/C++ libraries that you may have to install on your system:

Python Package

Library

python-igraph

igraph

pyyaml

LibYAML

shapely

GEOS

Command line interface

The orthogram module is designed to be used from the command line. To generate a drawing of a diagram defined in a diagram definition file named diagram.yaml, enter the following command:

python -m orthogram diagram.yaml

This will create a diagram.svg file in the same directory. If you wish to create a file with a different name or in a different directory, pass the path as a second argument.

The program creates Scalable Vector Graphics (SVG) files. Use your web browser to view the drawing. Use Inkscape to convert it to PNG or other supported formats.

NOTE: The character encoding of the definition file must be UTF-8.

Hello world

Start the text editor of your choice and type in the following lines of YAML:

terminals:
  a:
    label: Hello
  b:
    label: world
rows:
  - pins: [a]
  - pins: ["", b]
links:
  - start: a
    end: b

Save it as hello.yaml and run:

python -m orthogram hello.yaml

This should create a hello.svg file in the same directory.

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 following keys are recognized, each one containing a different category of definitions:

  • diagram

  • terminals

  • rows

  • links

  • styles

  • groups

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!
  font_weight: bold

The following diagram attributes are of particular significance:

collapse_links

If you set this to true, collinear segments of links belonging to the same group will collapse into a single segment. This may help reduce the clutter, though it depends on the application. Try it out and see.

stretch

By default, the diagram resizes itself to fill the element that contains it. Set this attribute to false to make the browser render the diagram in its original dimensions.

terminals

The terminals section contains mappings between terminal names and terminal definitions. You must have at least a couple of terminals to produce a meaningful diagram. Here is an example:

terminals:
  a:
    label: A terminal
  b:
    label: Another terminal
    stroke: blue

If you want a label with more than one line of text, use the newline character in the string. Even better, you can use the YAML literal style:

terminals:

  single-line:
    label: A single line label

  multi-with-newlines:
    label: Two lines separated by\na newline character

  multi-with-newlines-again:
    label: |-
      You can also use
      YAML literal style

Note that if the label of a terminal is not defined, the name of the terminal is used as a label instead.

rows

The rows section of the diagram definition file is used to arrange the terminals in the layout grid. It is essential; terminals that have not been placed in the grid are not drawn at all.

The rows structure is a sequence of row definitions. Each row definition contains a sequence of terminal names. You can use an empty string between the terminal names to leave an empty spot in the row. Here is an example:

terminals:
  a:
  b:
  c:
rows:
  - pins: [a]
  - pins: [b, "", c]

Note that the pins key is necessary. Row definitions do not have any attributes, though this may change in the future.

A terminal can have many pins in multiple rows as long as the area they occupy in the grid is rectangular. This way you can draw terminals that span multiple diagram rows or columns. The following example contains valid arrangements:

rows:
  - pins: [a, b , b , c, c]
  - pins: [a, "", "", c, c]

However, the program will reject the following invalid arrangements:

# These are wrong!  Terminal 'a' has a gap; terminal 'b' is
# L-shaped.
rows:
  - pins: [a , "", a , b, ""]
  - pins: ["", "", "", b, b ]

styles

You can add style definitions to the styles section to create named styles that can be referred to by the terminals and links. Each style definition consists of attribute key-value pairs. For example, the following two terminals are drawn in the same color:

terminals:
  a:
    style: reddish
  b:
    style: reddish
rows:
  - pins: [a, b]
styles:
  reddish:
    stroke: "#880000"
    stroke_width: 3.0
    fill: "#FFDDDD"

Attributes you define in the element itself override the attributes it inherits from the linked named style.

There are two special style names, default_terminal and default_link, which you can use to set default values for all the terminals and links in the diagram respectively.

groups

The groups section may be used to attach attributes to link groups. Since links in the same group may collapse on one another, it is usually desirable for all the links in one group to share the same attributes. In the example that follows, all links are drawn in blue:

groups:
  water:
    stroke: blue
    stroke_width: 4.0
links:
  - start: a
    end: b
    group: water
  - start: c
    end: d
    group: water

A group definition may contain a reference to a named style if needed. Note that creating an entry in the groups section is not necessary for the grouping of the links; a common group name in each link definition is sufficient.

Attributes

The following table summarizes the attributes available to the diagram and its components. Where an attribute is applicable, it shows the default value:

Attribute

Diagram

Terminal

Link

arrow_aspect

1.5

arrow_back

False

arrow_base

3.0

arrow_forward

True

buffer_fill

“none”

buffer_width

0.0

collapse_links

False

column_margin

24.0

drawing_priority

0

end_bias

None

fill

“none”

“none”

font_family

None

None

None

font_size

14.0

10.0

font_style

None

None

None

font_weight

None

None

None

group

None

label_distance

6.0

label_position

“top”

label

None

None

link_distance

4.0

min_height

300.0

48.0

min_width

300.0

96.0

padding

0.0

row_margin

24.0

start_bias

None

stretch

True

stroke_dasharray

None

None

None

stroke_width

0.0

2.0

2.0

stroke

“none”

“black”

“black”

text_fill

“black”

“black”

text_line_height

1.25

1.25

text_orientation

“horizontal”

The options for the enumerated attributes are:

  • label_position:

    • bottom

    • top

  • start_bias and end_bias:

    • horizontal

    • vertical

  • text_orientation:

    • horizontal

    • vertical

API

Using the Diagram class

If you want to create a diagram using Python, you can start by creating an empty Diagram object:

from orthogram import Diagram, write_svg

diagram = Diagram(label="A hand-made diagram", text_fill="blue")

You can pass any diagram attributes to the constructor as key-value pairs.

You can now add terminals to the diagram:

diagram.add_terminal("a", label="Hello")
diagram.add_terminal("b", label="Beautiful")
diagram.add_terminal("c", label="World")

Again, you can provide attributes for the terminals as key-value pairs.

In order to use the terminals, you must first place pins for them in the grid:

diagram.add_row(["a"])
diagram.add_row(["b", "", "c"])

The add_row method takes a list of terminal names. Note that you must have added the terminals to the diagram before placing pins for them in the grid. Use an empty string or None to leave an empty space between terminals.

After placing the terminals, you can connect them via links like this:

diagram.add_link("a", "b", stroke="red")

Note that the Diagram class offers an add_links method as well, which lets you create links en masse (all having the same attributes).

After you have finished building your diagram, use the write_svg() function to write the SVG file:

write_svg(diagram, "hello.svg")

Using the Builder class

The Builder class lets you create Diagram objects from Python dictionaries like the ones you load from a YAML file. The add() method imports a complete diagram definition into the builder:

import yaml
from orthogram import Builder, write_svg

builder = Builder()
with open("diagram.yaml") as f:
    data = yaml.safe_load(f)
    builder.add(data)
write_svg(builder.diagram, "diagram.svg")

If you have to be more specific, Builder provides the following methods:

Do one

Do many

add_style()

add_styles()

add_group()

add_groups()

add_terminal()

add_terminals()

add_row()

add_rows()

add_link()

add_links()

configure_diagram()

For example:

terminal_def = {
    'label': "Hello",
    'fill': "yellow",
    'stroke': "none",
}
builder.add_terminal('hello', terminal_def)

Use the help() Python function to read the documentation of each method. Note that you have to do the imports in a logical order: you must import the styles before using them, place the pins before linking the terminals etc.

The diagram property of a Builder object holds the diagram which is being built. If you want to use the Diagram API on it, as described in the previous section, after or while using the builder, you can certainly do so.

Convenience functions

The load_ddf() and translate() functions are provided as shortcuts:

from orthogram import load_ddf, translate, write_svg

# You can do this:
diagram = load_ddf("diagram.yaml")
write_svg(diagram, "diagram.svg")

# or just this:
translate("diagram.yaml", "diagram.svg")

Acknowledgements

This program depends on the following excellent pieces of software:

  • Python: The author’s preferred programming language. Flexible, convenient and nice to look at. Has a library for everything.

  • python-igraph: Python interface to the powerful igraph library. Orthogram uses it everywhere in its layout engine. The performance offered by the C-based implementation is much needed.

  • pyyaml: Simple to use, efficient YAML parser.

  • shapely: Python interface to the powerful GEOS geometry library. A bit overkill for the simple geometry manipulations Orthogram does, but why reinvent the wheel?

  • svgwrite: Orthogram uses this library to write the SVG files. Much better than having to use straight XML!

The following programs improved the development experience of the author a lot:

  • mypy: Python static analyzer. Makes refactoring so much easier.

  • Poetry: Great Python package manager. Also made publishing to PyPI a breeze.

Release history

0.1.0 (2020-12-09)

  • First release.

  • Important functionality already in place.

0.1.1 (2020-12-10)

  • API breaking change: renamed convert_ddf() public function to the arguably more user friendly translate().

  • Added the arrow_aspect and arrow_base attributes.

  • Fixed bug when buffer_width is not set.

  • Updated the documentation to reflect the changes and correct a few errors; made the stability warning a bit less scary.

  • Added the scripts.

0.2.0 (2020-12-14)

  • Introduced the ability to create shapes spanning multiple rows and columns.

  • Major API breaking changes: Replaced nodes with terminals and pins in order to facilitate the expansion of connected objects. Both API and diagram definition files affected.

  • Added the text_orientation attribute.

  • Updated the documentation to reflect the changes. Added the acknowledgments and release history sections.

0.2.1 (2020-12-15)

  • Enforce the UTF-8 character encoding for the definition file.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

orthogram-0.2.1.tar.gz (57.3 kB view hashes)

Uploaded Source

Built Distribution

orthogram-0.2.1-py3-none-any.whl (54.0 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page