Graph Display

Graph Display

The graph display utility renders directed acyclic graph (DAG) data as formatted text in the terminal. Unlike the Tree Display where each node has one parent, a DAG allows nodes to have multiple parents (fan-in). Shared dependencies are shown once rather than duplicated, making it ideal for visualizing build pipelines, dependency graphs, and task workflows.

Quick Start

import org.aesh.util.graph.Graph;
import org.aesh.util.graph.GraphNode;

GraphNode shared = GraphNode.of("C");
GraphNode root = GraphNode.of("Root")
        .child(GraphNode.of("A").child(shared))
        .child(GraphNode.of("B").child(shared));

String output = Graph.render(root);
invocation.println(output);

Output (diamond pattern — C is shared between A and B):

Root
┌─┴┐
A  B
└┬─┘
 C

GraphNode API

GraphNode provides a fluent API for building graph structures. The DAG semantics come from sharing the same node instance across multiple parents:

GraphNode shared = GraphNode.of("shared");
GraphNode root = GraphNode.of("root")
        .child(GraphNode.of("left").child(shared))
        .child(GraphNode.of("right").child(shared));
MethodReturnsDescription
GraphNode.of(label)GraphNodeCreates a new node
child(GraphNode)thisAdds an existing node as child
child(String)thisCreates and adds a leaf node
label()StringReturns the node’s label
children()ListReturns unmodifiable children list

Static API

For quick rendering of GraphNode graphs:

// Default UNICODE style
String output = Graph.render(root);

// Custom style
String output = Graph.render(root, GraphStyle.ASCII);

Builder API

For rendering existing typed DAGs without converting to GraphNode:

import org.aesh.util.graph.Graph;
import org.aesh.util.graph.GraphStyle;

String output = Graph.<Task>builder()
        .label(Task::getName)
        .children(Task::getDependencies)
        .style(GraphStyle.UNICODE)
        .build()
        .render(rootTask);
MethodDefaultDescription
label(Function<T, String>)requiredExtracts display text from each node
children(Function<T, List<T>>)requiredExtracts children (null/empty = leaf)
style(GraphStyle)UNICODEVisual style for connectors

Calling build() throws IllegalStateException if label or children are not set.

Graph Styles

Three predefined styles are available via GraphStyle:

UNICODE (default):

Root
┌─┴┐
A  B
└┬─┘
 C

ASCII:

Root
+-++
A  B
++-+
 C

ROUNDED:

Root
╭─┴╮
A  B
╰┬─╯
 C

Each style defines eleven box-drawing characters used for edge routing:

CharacterUNICODEASCIIROUNDEDPurpose
horizontal-Horizontal edge
vertical|Vertical edge
downTee+Parent splits down
upTee+Child joins up
cross+Vertical crosses horizontal
topLeft+Corner: down+right
topRight+Corner: down+left
bottomLeft+Corner: up+right
bottomRight+Corner: up+left
rightTee+T-junction: up+down+right
leftTee+T-junction: up+down+left

Common Patterns

Fan-out (one parent, multiple children)

GraphNode root = GraphNode.of("Root")
        .child("A")
        .child("B")
        .child("C");
 Root
┌──┼──┐
A  B  C

Diamond (shared dependency)

GraphNode shared = GraphNode.of("C");
GraphNode root = GraphNode.of("Root")
        .child(GraphNode.of("A").child(shared))
        .child(GraphNode.of("B").child(shared));
Root
┌─┴┐
A  B
└┬─┘
 C

Complex DAG (multiple shared nodes)

GraphNode d = GraphNode.of("D");
GraphNode a = GraphNode.of("A").child("C").child(d);
GraphNode b = GraphNode.of("B").child(d).child("E");
GraphNode root = GraphNode.of("Root").child(a).child(b);
 Root
 ┌─┴┐
 A  B
┌┴─┐│
│  ├┴─┐
C  D  E

Here D is shared between A and B — it appears once with edges from both parents. Each parent’s edges are drawn on separate routing rows to avoid visual ambiguity.

Using in Commands

Here is a complete command example that displays a build dependency graph:

@CommandDefinition(name = "deps", description = "Display dependency graph")
public class DepsCommand implements Command<CommandInvocation> {

    @Option(name = "style", shortName = 's', defaultValue = {"UNICODE"},
            description = "Graph style: ASCII, UNICODE, ROUNDED")
    private GraphStyle style;

    @Override
    public CommandResult execute(CommandInvocation invocation) {
        // Build a dependency graph
        GraphNode test = GraphNode.of("test");
        GraphNode compile = GraphNode.of("compile");
        GraphNode lint = GraphNode.of("lint");
        GraphNode validate = GraphNode.of("validate").child(compile).child(lint);
        GraphNode root = GraphNode.of("build")
                .child(validate)
                .child(test);

        String output = Graph.render(root, style);
        invocation.println(output);
        return CommandResult.SUCCESS;
    }
}

Cycle Detection

Graphs must be acyclic. If the graph contains a cycle, render() throws an IllegalArgumentException:

GraphNode a = GraphNode.of("A");
GraphNode b = GraphNode.of("B").child(a);
a.child(b); // creates A → B → A cycle

Graph.render(a); // throws IllegalArgumentException: "Graph contains a cycle"

Edge Cases

ScenarioBehavior
Root with no childrenOnly the root label is printed
children() returns nullTreated as a leaf node (no crash)
children() returns empty listTreated as a leaf node
Cyclic graphThrows IllegalArgumentException
Shared child nodeRendered once with edges from all parents

Tree vs Graph

FeatureTreeGraph
StructureEach node has one parentNodes can have many parents
RenderingVertical with indentationLayered horizontal layout
Shared nodesDuplicated in outputShown once with fan-in
Cycle detectionN/A (tree structure)Throws on cycles
Use caseFile trees, hierarchiesDependencies, pipelines

Use Tree for simple hierarchies (file systems, org charts). Use Graph when nodes can be shared across multiple parents (build steps, dependency resolution).

API Reference

GraphNode

MethodReturnsDescription
of(String)GraphNodeFactory method
child(GraphNode)GraphNodeAdd child node, returns this
child(String)GraphNodeAdd leaf child, returns this
label()StringGet label
children()ListUnmodifiable children list

Graph (static)

MethodReturnsDescription
render(GraphNode)StringRender with UNICODE style
render(GraphNode, GraphStyle)StringRender with specified style
builder()BuilderCreate a generic builder

GraphStyle

ConstantDescription
ASCIIUses +, -, | characters
UNICODEUses box-drawing characters (default)
ROUNDEDUses rounded corners (, , , )

See Also