Skip to content

Python API tutorial 5: graphs

Drawing custom graphs is a core use of the API. This tutorial builds a simple architecture-tree graph as a plugin. (Understand can already draw this — right-click a folder or architecture in the Architecture Browser → Graphical Views → Graph Architecture — but the code makes a good template to extend.)

Scripts become plugins

Until now you've run scripts from the command line. Understand turns any upython script into a plugin by giving it the .upy extension and registering it. The quickest way is to drag the .upy file onto the Understand GUI; see Install & run plugins for the plugin folders and management. Once registered, a graph plugin appears in the Graphical Views list under the name you give it.

Create a new file, arch_tree.upy.

Plugin hooks

A graph plugin defines a few well-known functions that Understand calls:

import understand

def name():
    return "Tutorial: Architecture Tree"

def description():
    return "Visually displays the hierarchical structure of a software architecture."

def test_architecture(arch):
    return True

def init(graph, target):
    graph.options().define("Fill", ["On", "Off"], "Off")
    graph.legend().define("func", "roundedrect", "Function", "blue", "#FFFFFF")
  • name() and description() set how the plugin appears in the GUI.
  • test_architecture(arch) returning True targets architectures (understand.Arch; there are matching test_* hooks for other target types).
  • init(graph, target) runs before drawing — a good place to declare user-configurable options with graph.options().define(...) and legend entries with graph.legend().define(...).

The graph argument Understand passes to these hooks is an understand.GraphContext (renamed from understand.Graph in 8.0 — see Breaking API changes in 8.0).

Drawing logic

Understand uses Graphviz for layout; your plugin defines the nodes, edges, and subgraphs. First a helper that returns the graph node for an architecture, creating it once and reusing it thereafter:

def grabNode(graph, nodes, arch):
    if arch in nodes:
        node = nodes[arch]
    else:
        node = graph.node(arch.name())
        node.sync(arch)  # architectures must be synced, not passed at creation
        nodes[arch] = node
    return node

Then the draw function, which Understand calls with the graph and the selected target:

def draw(graph, target):
    """
    Draw the graph. The second argument is the target the graph was created
    for — here, always an architecture, since test_architecture() above is
    the only test_* hook this plugin defines.
    """
    graph.set("rankdir", "LR")

    # Map arch -> node so each architecture appears once no matter how many edges
    nodes = dict()

    curLevel = [target]
    while curLevel:
        nextLevel = []
        for arch in curLevel:
            tail = grabNode(graph, nodes, arch)
            for child in arch.children():
                graph.edge(tail, grabNode(graph, nodes, child))
                nextLevel.append(child)
        curLevel = nextLevel
  • graph.set("rankdir", "LR") passes a Graphviz option — here, lay the graph out left-to-right.
  • graph.node(label) creates a node; node.sync(arch) links it to the architecture so double-clicking it navigates in Understand.
  • graph.edge(tail, head) connects two nodes.
  • arch.children() returns an architecture's child architectures. The while loop walks the tree breadth-first: start at the selected target, draw an edge to each child, then descend a level, until there are no more children.

More graph examples

The open-source plugin repository has many graph plugins to learn from, and the full Graph plugin reference is also under Help → Python API Documentation.

Next

You've built a graph plugin. Next you'll build an interactive text report the same way.

Tutorial 4: lexers and lexemes  ·  → Tutorial 6: interactive reports