Layout Strategy Examples

Supported modes are:

  • auto (default) - This is the original PHART hierarchical layout method with explicit handling of triads vertically.
  • hierarchical - This is the legacy hierarchical layout. Falls back to "layered" if cycle detected.
  • vertical - This is the legacy "vertical" strategy implemented for explicit handling of triangle graphs.
  • layered - Most strategies have this as a fallback. Nodes are assigned to topological layers (for DAGs) or BFS-like depth layers (for cyclic/non-DAG directed graphs), then centered per layer.
  • bfs - Breadth-First Search
  • bipartite - Arrange the node in two straight lines.
  • btree - Arrange the nodes as a tree, respecting left/right 'side' attributes on edges.
  • circular - Arrange the nodes in a circular layout.
  • kamada-kawai - Positions nodes using Kamada-Kawai path-length cost function. (requires scipy)
  • spring - Position nodes using Fruchterman-Reingold force-directed algorithm. (fallback for kamada-kawai if scipy not available.)
  • arf - Position nodes using an attractive-repulsive force model.
  • spiral - Position nodes along a spiral.
  • shell - Position nodes in concentric shells (rings), using BFS-derived rings by default.
  • multipartite - Position nodes in layers of straight lines.
  • planar - Minimize edge (path) intersections.
  • random - Positions nodes uniformly at random within the unit square.

Default/Original (Orthogonal/Hierarchical) Layout

Here's an example of a Graph of circular software dependencies:

   dependencies = {
        "package_a": ["package_b", "requests"],
        "package_b": ["package_c"],
        "package_c": ["package_a"],  # Creates cycle
        "requests": ["urllib3", "certifi"],
    }

    for package, deps in dependencies.items():
        for dep in deps:
            G.add_edge(package, dep)

If we then render that with the phart defaults, we get:

             [package_a]
           ┌──────↑───────┐
           ↓      │       ↓
      [package_b] │  [requests]
    ┌──────┴──────┼───────┴─────┐
    ↓             ↓             ↓
[certifi]    [package_c]    [urllib3]

which is a little bit ambiguous. We can try adding bounding boxes,

setting layer_spacing=4, and colors=source:

circular_dep-default-layout

That is clearer. But maybe this graph is a good case for using one of the new layout algorithms.

Speaking of new.. I know I've already embedded these in the README, but here's these two again, just because I find them so nice to look at. And they're screenshots of my plain text terminal from within vscode:

unix-family-tree

and...

go-package-dependencies

OK. Now back to our circular dependency graph with a few different new strategies.

Let's see how it looks with the bipartite strategy:

bipartite

circular_dep-bipartite-dest_color

It is different. It is actually clearer as you can plainly see the circular inter-dependencies indicated by the layout, colors and edge connection indicators. We could also have instructed phart to draw all of the bounding boxes to be the size of the widest one, which would have the effect of making the two columns look more uniform and obviously straight vertical lines.

How about that new circular layout, since it's a circular dependency graph:

circular

circular_deps-color-shows-ambiguity-with-center-anchors

To me that makes it harder to see the cycle. But the color algorithm (this time I used "colors=path") and the arrowheads do allow you to deduce what's happening, even though some paths are atop one another. We can see that urllib appears to go to certifi with a single directed arrowhead and a solid green path. However, since package_b must be going to package_c (by the arrowhead and the color, and the fact that we know when there are colliding/overlapping edges/paths, the color becomes gray), which means that package_c must be going to package_a, and well..

Deducing connections is not ideally what we want from a visual representation, so we can take that as a cue to uses a different layout, or perhaps the colors being gray in so many edge paths gives us another idea. Let's set the edge_anchor strategy to "ports" instead of the default center anchor for edge paths to be drawn.

Heck, we may not even find color useful if we do that:

circular_dep-circular_layout

Yeah, it is clear what connects to what now.

Speaking of circular... Sometimes when there are many nodes, setting colors=target can give quick and useful visual information.

Here's a different graph in colored circular layout, with the edge paths colored to match their destination edge for easier visual tracing:

sem-circular-color-target

shell

Similar in shape to the circular layout we have shell, which is a layout made from nodes arranged in concentric circles:

sem-shell

spiral

Which brings us to another circle-adjacent layout pattern, the archimedes spiral:

sem-spiral

It's visually interesting, but it doesn't add much value in helping interpret any relationships; it's just cool-looking. Here are a few more where the layout is really just an aestheic or page-constrait concern; a matter of preference.

spring

sem-spring

Kamada-Kawai

sem-kamada-kawai

ARF (attractive-repulsive force)

sem-arf

I feel like there was a reason I did this one in b&w, but I can't recall what I wanted to demonstrate by that. shrug

random

sem-random1

Really, it is random. Rendered again with the exact same options:

sem-random2

planar

sem-planar

multipartite

sem-multipartite-colored-dest

bfs

sem-bfs

So, as the breadth-first sort makes clear in that image, it is not the best choice to layout this particular graph.

Speaking of choices. I wanted to demonstrate a little more about how other options might be worth tweaking before changing the entire layout strategy. (Then again, ease of trying out different options is the reason I put so much time into the phart cli. It's fun to experiment.)

So, take a look at this graph render, using phart's original "legacy" layout engine:

sempath-1

It's correct, but it seems intuitively inverted. We have a --flow up option for cases like that: sempath2

That makes more sense to me inverted like that, but what a mess at those bottom rows. We can increase layer_spacing:

sempath-4

And still confusing. We'll increases the layer_spacing further:

sempath-5

OK, so maybe one of the other layouts demonstrates why it is useful to have these additional options available for placing the nodes in a 2D plane for visualization. This one is just too hard to follow in the hierarchical layout with orthogonal edges.

I hope you enjoyed taking a look at how different layouts effect legibility of the information conveyed by your ascii visualization of a graph using phart.

Other layouts

Let me know if you wan't to discuss implementing another layout strategy by opening an Issue or starting a Discussion thread. Cheers!


<
Previous Post
New features in PHART 1.3.0
>
Blog Archive
Archive of all previous blog posts