AshWorkflow.Charts.Dot (AshWorkflow v0.10.1)

Copy Markdown View Source

Draws an AshWorkflow.Charts.Graph as a Graphviz DOT diagram: the steps, edges and labels of AshWorkflow.Charts.Mermaid, with colour and line styles. The dot command of Graphviz draws DOT as SVG, PNG or PDF.

# The DOT source:
AshWorkflow.Charts.render(MyApp.Candidate, :dot)

# The SVG. This runs `dot`, which must be installed:
AshWorkflow.Charts.render(MyApp.Candidate, :dot, svg: true)

What the chart shows

In the workflowIn the chart
Automatic stepBlue box, ⚙️ automatic under the name
Manual stepAmber box, ✋ manual under the name
Wait stateLavender box with a dashed border, ⏳ wait state under the name
Terminal stepDouble border. Green, red, amber or grey: see "Terminal steps"
Initial stepAn edge from a small filled circle
on_successThick blue edge, ⚙ and the action
on_errorRed edge, ✖ on_error
TimeoutDashed amber edge, ⏱, the timeout's name and its deadline
UndoDotted grey edge with a hollow arrowhead, ↶ undo, and the window when within is set
TransitionPlain edge, the transition's name
Step notesUnder a rule inside the step: policy:, retry:, ⏱ and ↻

Each edge kind other than a transition also has a symbol, so a reader who cannot tell the colours apart still knows the kind. The colours come from AshWorkflow.Charts.Palette. The label text comes from AshWorkflow.Charts.Format, so it is the same in every format.

Terminal steps

The colour of a terminal step comes from the edges that reach it:

  • Red when only on_error edges reach it.
  • Amber when only timeouts reach it.
  • Green when only transitions and on_success reach it.
  • Grey for a mix of these, or for no edge at all.

So a rejected step that a person chooses is green, the same as approved: the chart cannot tell a good outcome from a bad one. AshWorkflow.Charts.Palette.terminal_class/2 gives the class of one step.

Layout

dot puts the steps in ranks, from the initial step to the end steps. An edge that closes a loop, such as a transition back to an earlier step, goes back up the ranks.

An undo edge goes from a later step to an earlier step. The file writes it from the earlier step, with dir="back" to put the arrowhead on the earlier step. So dot ranks it as the edge that it undoes: the steps keep their forward order, and the undo edge runs next to that edge. The arrowhead is at the tail, so the :undo class sets arrowtail, not arrowhead.

IDs and escaping

Each step is a node with the ID step_<name>, so no step can have the ID of the start circle, start.

Every ID and every label is a double-quoted string, with " and \ escaped. So a step name or a condition cannot break the file, or start a Graphviz escape sequence such as \N.

Graphviz reads an HTML entity in a label, such as &amp;, as its character. So the file writes each & in a label as &amp;. An ID keeps its &.

Options

  • :svg — run dot and return the SVG, not the DOT source. Defaults to false. The SVG sets its width and height, so an <img> tag shows it at full size. See "Graphviz".
  • :direction — :down (the default) or :right. Right gives a long, thin chart. Use it only for a short chain of steps.
  • :theme — :light (the default) or :dark. See "Dark theme".
  • :classes — your own colours and line styles. See "Your own styles".

:theme and :classes can also go under config :ash_workflow, :dot, for every chart. An option merges over the config. A nil option or config key counts as not set. A config that is not a keyword list raises ArgumentError.

Your own styles

A class holds the DOT attributes of one kind of step or edge. DOT has no classes, so the file writes them on each node or edge. To change a class, pass :classes:

AshWorkflow.Charts.render(MyApp.Candidate, :dot,
  classes: [manual: [fillcolor: "#FFE4E6"], undo: [style: "dashed"]]
)

Each class merges over its default, key by key, so manual steps keep their amber border. The classes are:

  • :automatic, :manual and :wait_state, for the step kinds.
  • :done, :failed, :expired and :end, for terminal steps.
  • :on_success, :on_error, :timeout and :undo, for edges.

A key is a Graphviz attribute name, such as :fillcolor, :penwidth or :URL. A step class can set a node attribute, and an edge class can set an edge attribute: see the "Used By" column of the Graphviz attribute reference. No class can set :label, which the chart writes, and :undo cannot set :dir. See "Layout".

A value is a string, a number or a boolean. dot checks the value itself, such as a colour. Any other key, a value of another type, or an unknown class raises ArgumentError, and the message lists the keys that the class can set. default_classes/1 gives the defaults.

A list of classes applies to both themes, so a fill for the light theme can sit under the light text of the dark theme. To give each theme its own colours, put the classes under :light and :dark:

AshWorkflow.Charts.render(MyApp.Candidate, :dot,
  theme: :dark,
  classes: [
    light: [manual: [fillcolor: "#FFE4E6"]],
    dark: [manual: [fillcolor: "#881337"]]
  ]
)

Only the entry for the current theme applies. A theme given twice applies both entries in order. A list that mixes :light or :dark with class names raises ArgumentError.

Dark theme

theme: :dark selects a dark background and dark classes: deep fills, light borders and light text. An SVG has one theme, so a page that follows the reader's theme needs one chart for each theme.

Graphviz

svg: true runs the dot command of Graphviz. This library does not download Graphviz. Install it with your package manager, such as apt-get install graphviz or brew install graphviz.

config :ash_workflow, :dot, path: "/opt/graphviz/bin/dot"
  • :path — the dot to run, else dot from the PATH. A name with no /, such as "dot", is a command on the PATH. A path with a / is an executable file, relative to the current directory, and ~ is the home directory. nil and "" count as no :path, so path: System.get_env("DOT_PATH") uses dot from the PATH when the variable is not set or is empty. Any other value that is not a string raises ArgumentError.

With no dot, svg: true raises ArgumentError. executable/0 gives the dot that svg: true runs. When dot draws the chart but prints a message, such as a warning about an unknown colour, the chart logs it with Logger.warning/1.

Contrast

By the WCAG 2 formula:

PairLight themeDark theme
Step label text on its fillabove 16:1above 8.6:1
Edge label text on the backgroundabove 18.9:1above 15.6:1
Any stroke on the backgroundabove 3.6:1above 6.4:1

Summary

Functions

The default attributes of each class for a theme: a keyword list from the class name to its DOT attributes. A step class starts with fillcolor, color and fontcolor, and an edge class starts with color. See "Your own styles" in the module documentation.

The absolute path of the dot that svg: true runs, or nil when there is none: the configured :path, or dot from the PATH when there is no :path. See "Graphviz" in the module documentation.

The theme a chart draws with: the :theme option, else the :theme under config :ash_workflow, :dot, else :light.

Functions

default_classes(theme \\ :light)

@spec default_classes(:light | :dark) :: keyword(keyword())

The default attributes of each class for a theme: a keyword list from the class name to its DOT attributes. A step class starts with fillcolor, color and fontcolor, and an edge class starts with color. See "Your own styles" in the module documentation.

executable()

@spec executable() :: Path.t() | nil

The absolute path of the dot that svg: true runs, or nil when there is none: the configured :path, or dot from the PATH when there is no :path. See "Graphviz" in the module documentation.

Raises ArgumentError when :path is not a string, or when config :ash_workflow, :dot is not a keyword list.

theme(opts \\ [])

@spec theme(keyword()) :: :light | :dark

The theme a chart draws with: the :theme option, else the :theme under config :ash_workflow, :dot, else :light.

Raises ArgumentError for a theme that is not :light or :dark.