Visualizer

Overview

The visualizer shows a structure in four forms: plain text for a terminal or a doctest, Mermaid for documentation pages, DOT for Graphviz, and an image file. It is a set of free functions: presentation stays out of the structure classes, which keep no drawing code and no dependency.

Function

Produces

Accepts

Dependency

to_text()

Plain text, one line or one node per line

every supported structure

none

to_mermaid()

Mermaid flowchart source

every supported structure

none

to_dot()

Graphviz DOT source

graphs only

none (Graphviz only to render the source)

render()

One of the three text formats, chosen by name

as the chosen format

none

save_image()

PNG, SVG, PDF or any format matplotlib writes

every supported structure

image extra

        graph LR
    S[structure] --> V[view<br/>nodes, edges, labels]
    V --> T[to_text]
    V --> M[to_mermaid]
    V --> D[to_dot]
    V --> L[layout] --> I[save_image]

    style V fill:#3498db,color:#fff
    

Every function first reads the structure into the same intermediate view, through public accessors only, then writes that view in its format. Two formats of the same structure therefore always show the same nodes in the same order.

Note

Output is deterministic. Linear structures are read in iteration order, trees from the root, graphs in node and edge insertion order. The same structure always renders to the same text, which makes the output safe to compare in tests and to commit in documentation.

Supported Structures

Family

Classes

Rendered as

Linear

LinkedList, DoublyLinkedList, CircularLinkedList, Stack, Queue, Deque, PriorityQueue

A chain, with top for a stack and front / rear for queues; a circular list points back to its first item

Binary trees

BinaryTree, BinarySearchTree, AVLTree, RedBlackTree

A tree whose children are tagged L or R; the sentinel leaves of a red-black tree are not shown

General trees

Tree

A tree, children in insertion order

B-trees

BTree

A tree of nodes, each showing its keys

Graphs

every graph of sds.graph

Nodes and edges, with direction and weights

Heaps, tries, segment trees and the structures of sds.advanced and sds.probabilistic are not supported yet: passing one raises StructureTypeError.

Labels

Every function takes an optional keyword argument label: a callable that returns the text to show for one element. What it receives depends on the structure:

Structure

label receives

Default

Linear structures

each item

str(item)

Binary and general trees, graphs

each node

str(node.data)

B-trees

each key

str(key)

# Show the node ids of a graph instead of their data
print(to_text(graph, label=lambda node: node.id))

Formats escape labels themselves: a label may hold quotes, pipes or line breaks without breaking the Mermaid or DOT source.

Text Formats

to_text

sds.utils.visualizer.to_text(structure, *, label=None)[source]

Render a structure as plain text.

Linear structures render on one line, with named ends where they have a meaning (top of a stack, front/rear of a queue). Trees render one node per line with box-drawing branches; children of a binary tree are tagged L/R. Graphs render as an adjacency list in insertion order, with -> for directed and -- for undirected graphs, and the weight of each edge in parentheses.

Parameters:
  • structure (Any) – A supported structure (see the module documentation).

  • label (Callable[[Any], str], optional) – Labelling function; see the module documentation.

Returns:

The rendering, (empty) for an empty structure.

Return type:

str

Raises:

StructureTypeError – If the structure is not supported.

Examples

>>> from sds.linear import Stack
>>> stack = Stack()
>>> for item in (1, 2, 3):
...     stack.push(item)
>>> print(to_text(stack))
top: 3 -> 2 -> 1
>>> from sds.tree import BinarySearchTree
>>> tree = BinarySearchTree()
>>> for key in (50, 30, 70, 20, 40):
...     tree.insert(key)
>>> print(to_text(tree))
50
├── L: 30
│   ├── L: 20
│   └── R: 40
└── R: 70

The three families render as follows. A doubly linked list shows its two-way links:

1 <-> 2 <-> 3

A general tree uses box-drawing branches:

root
├── src
│   └── main.py
└── README

A B-tree of minimum degree 2 after inserting 1 to 7 shows the keys of each node:

[2 | 4]
├── [1]
├── [3]
└── [5 | 6 | 7]

A weighted directed graph is an adjacency list, one line per node:

A -> B (4.0), C (1.0)
B
C -> B (2.0)

to_mermaid

sds.utils.visualizer.to_mermaid(structure, *, label=None)[source]

Render a structure as a Mermaid flowchart.

Linear structures and graphs are drawn left to right (flowchart LR), trees top-down (flowchart TD). Directed graphs use -->, undirected graphs ---; weights and the L/R side of binary-tree children become edge labels. Named ends of stacks and queues become rounded nodes. Node ids are generated (n0, n1…) and labels are escaped, so any label is safe to embed in a Markdown or reST page.

Parameters:
  • structure (Any) – A supported structure (see the module documentation).

  • label (Callable[[Any], str], optional) – Labelling function; see the module documentation.

Returns:

The Mermaid source, without the surrounding code fence. An empty structure gives the flowchart line alone.

Return type:

str

Raises:

StructureTypeError – If the structure is not supported.

Examples

>>> from sds.tree import BinarySearchTree
>>> tree = BinarySearchTree()
>>> for key in (2, 1, 3):
...     tree.insert(key)
>>> print(to_mermaid(tree))
flowchart TD
    n0["2"]
    n1["1"]
    n0 -->|L| n1
    n2["3"]
    n0 -->|R| n2

The source can be pasted into any Mermaid renderer, including the .. mermaid:: directive of this documentation. The weighted directed graph above gives:

flowchart LR
    n0["A"]
    n1["B"]
    n2["C"]
    n0 -->|4.0| n1
    n0 -->|1.0| n2
    n2 -->|2.0| n1

which renders as:

        flowchart LR
    n0["A"]
    n1["B"]
    n2["C"]
    n0 -->|4.0| n1
    n0 -->|1.0| n2
    n2 -->|2.0| n1
    

to_dot

sds.utils.visualizer.to_dot(graph, *, label=None)[source]

Render a graph as Graphviz DOT source.

Directed graphs become a digraph with -> edges, the others a graph with -- edges. Nodes are identified by their id and labelled with label; weights become edge labels. Producing the source needs no Graphviz install; rendering it does.

Parameters:
  • graph (AbstractGraph) – The graph to render.

  • label (Callable[[Any], str], optional) – Labelling function; it receives each node.

Returns:

The DOT source.

Return type:

str

Raises:

StructureTypeError – If graph is not a graph: DOT is offered for graphs only.

Examples

>>> from sds.graph import DirectedEdge, DirectedGraph, GraphNode
>>> graph = DirectedGraph()
>>> a, b = GraphNode("A", "n1"), GraphNode("B", "n2")
>>> graph.add_node(a)
>>> graph.add_node(b)
>>> graph.add_edge(DirectedEdge(a, b))
>>> print(to_dot(graph))
digraph {
    "n1" [label="A"];
    "n2" [label="B"];
    "n1" -> "n2";
}

The DOT source is plain text; turning it into an image is Graphviz’s job, for instance dot -Tpng graph.dot -o graph.png. The same graph gives:

digraph {
    "A" [label="A"];
    "B" [label="B"];
    "C" [label="C"];
    "A" -> "B" [label="4.0"];
    "A" -> "C" [label="1.0"];
    "C" -> "B" [label="2.0"];
}

render

sds.utils.visualizer.render(structure, fmt='text', *, label=None)[source]

Render a structure in one of the text formats.

Dispatches to to_text(), to_mermaid() or to_dot(); the command line uses it to honour its format option.

Parameters:
  • structure (Any) – A supported structure (see the module documentation).

  • fmt (str, default "text") – One of FORMATS: "text", "mermaid" or "dot".

  • label (Callable[[Any], str], optional) – Labelling function; see the module documentation.

Returns:

The rendering.

Return type:

str

Raises:

Examples

>>> from sds.linear import Queue
>>> queue = Queue()
>>> for item in (1, 2):
...     queue.enqueue(item)
>>> print(render(queue))
front: 1 -> 2 :rear
>>> render(queue, "svg")
Traceback (most recent call last):
    ...
sds.core.exceptions.InvalidParameterError: Unknown format 'svg': use one of text, mermaid, dot
sds.utils.visualizer.FORMATS

Text formats accepted by render().

FORMATS is the tuple ("text", "mermaid", "dot").

Image Output

save_image

sds.utils.visualizer.save_image(structure, path, *, label=None)[source]

Draw a structure and save the image to path.

Needs the image extra: pip install "pysds-tools[image]". The format follows the file extension (.png, .svg, .pdf, or any other format matplotlib writes). Nodes are placed by the visualiser: a row for linear structures, in-order columns for binary trees, children-centred parents for other trees, a circle for graphs.

Parameters:
  • structure (Any) – A supported structure (see the module documentation).

  • path (str or os.PathLike) – Destination file; its extension selects the format.

  • label (Callable[[Any], str], optional) – Labelling function; see the module documentation.

Raises:

Notes

The figure is built with matplotlib’s object API: no window opens and the global pyplot state is not touched.

The visualizer places the nodes itself, so the drawing needs nothing but matplotlib:

Structure

Layout

Linear structures

One row, left to right

Binary trees

One column per node in in-order position, one row per level

General trees and B-trees

Each parent centred above its children

Graphs

Nodes on a circle, from the top, clockwise in insertion order; two opposite edges are bent so both stay visible

Usage Example: Documenting a Graph

from sds.graph import GraphNode, WeightedDirectedEdge, WeightedDirectedGraph
from sds.utils.visualizer import render, save_image, to_text

a, b, c = (GraphNode(name, name) for name in "ABC")
graph = WeightedDirectedGraph()
for node in (a, b, c):
    graph.add_node(node)
graph.add_edge(WeightedDirectedEdge(a, b, 4))
graph.add_edge(WeightedDirectedEdge(a, c, 1))
graph.add_edge(WeightedDirectedEdge(c, b, 2))

print(to_text(graph))                       # quick look in a terminal
with open("graph.mmd", "w") as stream:      # for a Markdown page
    stream.write(render(graph, "mermaid"))
save_image(graph, "graph.svg")              # needs the image extra

Best Practices

✅ Use to_text in doctests and assertions: its output is short, deterministic and dependency-free.

✅ Use to_mermaid for documentation pages, and to_dot when Graphviz is already part of the toolchain or the graph is large.

✅ Pass a label callable rather than changing the data stored in the structure to get a readable drawing.

❌ Do not call to_dot on a tree or a list: DOT output is offered for graphs only and raises StructureTypeError otherwise.

❌ Do not import matplotlib.pyplot just to save an image: save_image builds its figure with matplotlib’s object API and leaves the pyplot state untouched.

References