Graphviz

This theme adds several improvements to the built-in sphinx.ext.graphviz extension:

  • default fonts and colors are set to match the fonts and colors used by the theme, including support for both light and dark mode;

    Only compatible with Google fonts

    When not using a Google font via font in conf.py (eg. using a system/custom/self-hosted font), this extension will use the metrics from Graphviz’s default font, rather than the actual font, for the purpose of computing the graph layout. However, the text will still be displayed in the browser using the user-specified font.

  • the rendered diagram is included as inline svg content to allow hyperlinks to work;

  • colors can be manually specified in terms of CSS variables defined by the theme, such as var(--md-primary-fg-color);

  • labels can be specified as Sphinx cross references (rather than just plain URLs).

Configuration

This functionality is available as a separate optional extension included with this theme, which must be specified manually in your conf.py file:

extensions = [
    "sphinx_immaterial",
    # other extensions...
    "sphinx_immaterial.graphviz",
]

The graphviz_dot and graphviz_dot_args configuration options from sphinx.ext.graphviz are supported.

Note

The graphviz_output_format configuration option is not supported; instead, the graph is always included as inline SVG in the HTML output.

graphviz_ignore_incorrect_font_metrics : bool = False

This extension relies on the LibGD graphviz plugin to load the same font used by this theme, in order to correctly determine the size of text labels. While the LibGD plugin is normally included in the Linux and macOS graphviz builds, the official x86_64 Windows build does not include it (before graphviz v12.1.0). If the plugin is not found, by default this theme logs a warning. This option may be set to True silence that warning.

Add to conf.py to silence the warning
graphviz_ignore_incorrect_font_metrics = True

Warning

If LibGD is not available, graphviz will compute the size of labels using a default system font. Labels will still be rendered in the browser using the correct font; the layout may just be slightly incorrect.

Usage

Using the graphviz directive, graphs can be specified either inline or included from an external file.

Example of a graph defined inline
.. graphviz::

   digraph {
     graph [
       rankdir = LR
     ]

     node [
       shape=rectangle
     ]

     A [label="Start"]
     B [label="Error?", shape=diamond]
     C [
       label="Hmm...",
       style="solid,filled",
       fillcolor="var(--md-primary-fg-color, green)"
     ]
     D [label="Debug"]
     E [xref=":py:obj:`~tensorstore_demo.DimensionSelection`"]

     A -> B
     B -> C [label="Yes"]
     C -> D
     D -> B
     B -> E [label="No"]
     D -> E [style=invis]
   }
%3 A Start B Error? A->B C Hmm... B->C Yes E DimensionSelection tensorstore_demo.DimensionSelection (Python class) — This extends DimExpression. B->E No D Debug C->D D->B
Contents of test.dot
graph {
  // use a radial layout (because mermaid.js cannot)
  layout=twopi

  A [shape=diamond]
  B [shape=box]
  C [shape=circle]

  A -- B [style=dashed]
  // edge using a static color with transparent fill
  A -- C [color="black:invis:black"]
  A -- D [penwidth=5, arrowhead=none]

}
Example of a graph defined in an external file
.. graphviz:: test.dot
%3 A A B B A--B C C A--C D D A--D

Colors specified using CSS variables

As demonstrated in the example above, this extension adds support for specifying color attributes using the CSS syntax "var(--css-var)" or "var(--css-var, fallback)". This may be used to specify colors defined by the theme that vary depending on whether light or dark mode is enabled. When the latex builder is used, the fallback color, if specified, will be used instead. If no fallback value is specified, only HTML builders may be used.

Cross-references

This extension adds support for a special xref attribute, which may be set to a string value containing reStructuredText. This is demonstrated in the example above.

The reStructuredText will be parsed as a document fragment, and after resolving any cross references, will be substituted back into the graph definition as follows:

  • A label attribute will be generated from the parsed fragment’s text content.

  • If the parsed fragment contains at least one hyperlink, only the last such hyperlink is considered, and:

Overriding the cross-reference label

The label attribute generated with the new xref attribute uses graphviz’ HTML-like attribute syntax, but any HTML characters found within the xref parsed fragments’ text are escaped (eg. < f0 > becomes &lt; f0 &gt;). In some graphs, it is preferable to use unescaped HTML characters in the label attribute. In this case, the generated label attribute can be overridden by manually specifying the label attribute after the xref attribute.

A graph of record nodes
.. graphviz::

   digraph {
       graph [rankdir = "LR"]
       module [
           xref=":py:mod:`test_py_module.test`"
           label="<f0> test_py_module.test | <f1> functions | <f2> classes"
           shape = record
       ]
       class [
           xref = ":py:class:`test_py_module.test.Foo`"
           label = "<f0> Foo | <f1> attributes | <f2> methods"
           shape = record
       ]
       method [
           xref = ":py:meth:`test_py_module.test.Foo.capitalize()`"
           label = "<f0> capitalize() | <f1> variables"
           shape = record
       ]
       function [
           xref = ":py:func:`test_py_module.test.func()`"
           label = "<f0> func() | <f1> variables"
           shape = record
       ]
       class_attr [
           // NOTE: cannot adequately angle brackets in xref's text
           xref = ":py:attr:`spam | object <test_py_module.test.Foo.spam>`"
           shape = record
       ]
       module:f2 -> class:f0
       module:f1 -> function:f0
       class:f2 -> method:f0
       class:f1 -> class_attr // edge will point toward center of class_attr
   }

Implementation note

Using a cross reference in a single field of a record node (with multiple fields) is unsupported because the manually specified label attribute is used as is.


Last update: Nov 16, 2024