Skip to content

refactor(cxx): move igraph to the standard library#83

Merged
haxscramper merged 226 commits into
masterfrom
feature/unified-graph-layout
Jun 12, 2026
Merged

refactor(cxx): move igraph to the standard library#83
haxscramper merged 226 commits into
masterfrom
feature/unified-graph-layout

Conversation

@haxscramper

@haxscramper haxscramper commented Apr 11, 2026

Copy link
Copy Markdown
Owner

This PR completely overhauls and unifies handling of the graph-like structures and visualizations on the library. This is a Phase 1 -- functionally complete, with the core tests added and working, but there are some leftover issues and polish that can be added in some follow-up PR.

The changes in this PR are split into several categories

  • Utility and testing additions to the hstd:: library: geometry types, geometry checks, various assertions etc, required to test or implement the main graph structure
  • Base graph implementation: initial idea partially migrated from the org diagramming tool, but updated and expanded to accommodate various graph structures and accomodations. The base graph structure is designed to accommodate visualization needs (hierarchical vertex structures, ports), but everything should be abstracted.
  • Base visualization class and visualiation backends for ELK, graphviz, libavoid and custom kiwi-based constraint placement solution.
  • Cascading changes that were implemented to accommodate for the updated API

The code removed

  • org_diagram tool -- the idea of using org-mode as a description for the diagram is sound, but the

  • adaptagrams_ir and the adaptagrams wrapeprs: the libadaptagrams library, specifically the libcola component, proved to be too buggy and unreliable to use them consistently, so the whole part of the code was completely removed and replaced with the new, kiwi-based layout solution, which is much more precise and flexible (at the expense of having to specify a lot more underlying elements and not having any overlap avoidance mechanism)

    The python interoperability for the new implementation is going to be done through protobuf data exchange.

  • More complex parts of the imgui demo applications, or parts that relied on the old graph IR/visualization to function. This code was a poor example (too complex) and a poor tool (not polished enough, unlikely to be finished).

Overall architecture of the new code

Base graph structure

The graph structure is split into three parts: graph containers, graph objects and graphs IDs. Overall API is similar to the DOD in a sense that objects stored in the collection don't have access to the IDs, and relations between objects are managed through a separate lookup tables/map. The design differs from the regular DOD in a way the data is stored: the stores are virtual classes and may implement set/get functionality in any way they can. Additionally, each object is required to have a unique string ID and it might be used for the reverse lookup.

All collection types may store the associated objects, but are not required to.

  • IGraph: main storage class, holds a collection of vertices and manages Vertex <> ID association. Holds the list of other collections: vertex hierarchies, edge collections and port collections. The IGraph has a single collection of vertices and vertex IDs that are used in any other operations.
  • IEdgeProvider: base class for edge operations. Edges are managed as an additional overlay on top of the existing vertex collections. Does not support hyper-edges.
    • IVertexHierarchy: tree-like arrangement of the nodes in the graph. Primarily used for the visualization purposes, but is designed in a way that would make it usable for a general tree-like data structures.
    • IEdgeCollection: free-form collection of the edges. Each edge has its own unique associated ID, so the multi-edges and self-loops are possible.
  • IPortCollection: collection of the ports associated with every vertex. The port connection can be related to a vertex or an intersection of a vertex and edge. The ports are not structural elements of the graph, instead they are managed as another overlay on top of the edges+vertices structure.

Collections associate IDs (VertexID, PortID, EdgeID) with the corresponding objects (IVertex, IPort, IEdge) respectively, and provide a way to track and un-track the elements in the graph.

Each object has a list of attributes: extra data associated with the graph object, in addition to the fields in the derived object itself. The attributes are currently used in the visualization backend to provide a general way to access the visualization data.

If protobuf build is enabled, every graph element: collection and object, can be serialized into a predefined protobuf schema, and de-serialized back from it.

Visualization logic

All graph visualization backends were rewritten under a unified API structure, now it is possible to combine the different visualization backends together, and even partially route the edges across different backends.

The general algorithm for the visualization is based on the IVisualAttribute and its derivatives.

  • Each backend may associate one visual-attribute derived object with each vertex.
  • The attributes hold the necessary layout data for the backend.
  • IGroupVisualAttribute holds additional context in form of the IPlacementAlgorithm-derived type. This type implements the actual layout logic and returns a mapping (Vertex|Edge|Port)ID -> ILayoutAttribute for placing the nodes. All results are relative to the parent, so the each layout algorithm does not require any additional context.

Visualization backends

  • graphviz: update and refactor of the existing backend, while leaving the original utility API in place. It is still possible to create a quick debug graphviz graph and render it to the PNG file
  • kiwi: new backend created as a replacement to the removed cola constraints. Based on the kiwi constraint solving library, allows to describe the diagram structure as a collection of inequalities and higher-level constraints. The API is simiar to the cola, but less buggy. The backend uses libavoid to route the edges.
  • ELK: Eclipse Layout Kernel, backend already existed in the org_diagram tool, now it is moved and integrated into the full package.

Remove the `IGraph` and related classes from the dia diagram editor, and
move it to the standard library components.
Extract graph hierarchy handling into its own dedicated class, support
multiple hierarchies in a single graph.
Add constraint and group types for the algorithm implementation.
Partially complete rewrite of the graphviz library wrapper to the new
IGraph interface.
Rewrite of the graphviz layout compiles. Next step is to add tests to
ensure the layout is still working.
working on the test implementation for new layout logic.
- Can run test without crashing
- Switched API to return IEdge/IVertex by pointer
Debugging runtime errors with missing edges.
Add operations tracer to the graph layout execution.
Remove separate methods to register and track vertices in the graph,
and make the track/untrack method accept a single vertex argument
instead of a full list.
Return the graphviz node/edge object from the setter functions to
support method chaining.
Initial implementation of the layout algorithm works for the node
positioning, and generates a correct debug preview file.
Test simple graphviz layout with two subgraphs and no algorithm
switching logic.
Move group layout algorithm object to a private field.
Initial test to verify multiple separate layout algorithms executed in a
graphviz visualization.
Add initial implementation of the standalone visualization primitives
for the graph interface. The idea is to have a backend/frontend-agnostic
representation of the graph structure.
Test visual layout data extraction from trivial graphviz layout.
Extract full visual data from the subgraph layout.
Reuse node attribute style enum for graphviz edges and graphs instead of
reading the string values.
Add a partial specialization for types declared with the sub-variants.
The JSON serialization will also create a `Kind` field with the enum
value. The `.data` field is serialized as before; the change is mainly
cosmetic to make it easier to understand what was serialized.
Add XML node builder to the hstd library and use to construct the output
SVG for the node visualization, instead of manually interpolating
strings.
Style updates for the SVG writer code.
Return the XML nodes from the writer instead of mutating the parent
node.
Simplify shape builder logic and final SVG structure, place nodes
directly in the output instead of wrapping in a secondary groups.
Executing layout on the graphviz graph with a single layout algorithm
now generates correct SVG outpout for simple graphs.
For each sub-graph with its own layout algorithm, use custom graphviz
contexts to avoid state pollution.
Running test with multiple mixed layouts in one graph correctly executes
nested layouts and then brings them together in the main one. The only
visual problem left over is the positioning of the nested nodes --
currently they are overlapping with each other in the graphviz layout.
Don't use "add root subgraph" for constructing nested layouts with the
switch. Now the visualization for the multi-layout of graphviz looks
correct, although the tests barely cover the relative positioning of the
rectangels and nodes with respect to each other.

The graph looks like this, approximately. The left side layout is done
using 'dot' algorithm, the right one is the circular placement.

```
...............................................
:                  :                          :
:   +---------+    : +---------+..............:
:   |  VERT-3 |<-\ : |  VERT-1 |              :
:   +---------+  | : +---------+              :
:        |       | :      ^     \             :
:        v       | :      |      \            :
:   +---------+  | :      |       v           :
:   |  VERT-4 |  | :      |    +---------+    :
:   +---------+  | :      |    |  VERT-2 |    :
:        |       | :      |    +---------+    :
:        v       | :      |       /           :
:   +---------+  | :      |      /            :
:   |  VERT-5 |--/ : +---------+<             :
:   +---------+    : |  VERT-0 |              :
:                  : +---------+..............:
:                  :                          :
...............................................
```
Add API to check relative geometry positions to verify the layout
algorithm integration logic.
Add the AnchorSpec to bundle the x/y anchors for a given shape into a
single structure.
initial test for the fixed absolute offset in the relative constraint.
Preserve the structure of the kiwi expressions until the final layout
run. This will simplify debugging and visualization a lot.
- Add a simplified representation of the variable constraints generated
  by the kiwi constraint build() function
- add `checkDistance()` function to the geometry testing to verify the
  relative positions of the geometries
- add DistanceCheck enum to optionally check the distance only along one
  of the dimensions instead `sqrt(x*x+y*y)` all the time.
- some formatting adjustments in the geometry testing header.
Put functions for writing kiwi IR representation to own file.
Update and verify the usage of the arguments for the relative constraint
class: the order of arguments must be "fixed, relative". Add test to
verify the relative position of the vertex in relation to the parent
other vertex.
Make the even gap constraint rectangle anchors configurable at
construction instead of using fixed anchor for every element in the
constraint.
Implement linear constraint API for the graph and verify trivial
relative positioning configuration.
Introduce grouping type for the separate constraint arguments.
Create two set of rectangle visualizations for the graphviz debug output
for the kiwi constraint.
Add more information to the constraint fail representation, now it will
show all preceding constraints directly in the exception message.
Kiwi IR test failure was caused by incorrectly used vertex ID during
the graph construction. The multi-layout diagram element lacked and
defensive diagnostics to prevent duplicate rectangle IDs in the layout
lanes, and because of that the code

```
  root->addConstraint<kw::MultiSeparateConstraint>(root)
      ->separateHorizontally()
      ->setSeparationDistance(60)
      ->addFullLane({g1, g5})
      ->addFullLane({g2, g6})
      ->addFullLane({g3, g7})
      ->addFullLane({g4, g8});
```

created an invalid solution that caused the lowered IR to generate
equation which was reducible to `60 == 0`, which is unsolvable.

```
g2.x + g2.width * 0.5 + 60 == g6.x + g6.width * 0.5
g6.x + g6.width * 0.5 == g2.x + g2.width * 0.5
```

The updated diagnostics representation for the equation helped to
resolve the issue, but it is clear there is a lot work to be done to
make the diagnostics truly useful: current implementation with an
ad-hoc loop that repeatedly tries to diagnose the problems still
generates an overly verbose output that is hard to reason about in
case of complicated graphs.
Exted test coverage for the mixed test graph. Verify the basic visual
elements are properly positioned in relation to each other.
Remove code examples and standalone test app from the imgui example app.
They used outdated graph abstraction layer and were otherwise very
un-intuitive and cumbersome to maintain, far exceeding the complexity of
what would reasonably constitute and "example".
The example was incomplete, with the new data model for diagramming it
is largely redudant. Properly implementing editing capabilities for the
application would be very difficult.
Initial implementation of the eager AST diff algorithm from the org
diagram editor component removed in the earlier commit. The new
implementation is under-tested but the original code worked correctly.
Fix the missing protobuf headers in the source genration command in the
workflow and re-run the source code generation for the current
repository.
Add protobuf serialization and deserialization logic for the basic graph
geometric elements (rect/point/polygon) and visual elements.
WIP serialization logic for protobuf and avoid graph routing.
Pure virtual method for constraint serialization and de-serialization.
added more protobuf types, added missing serialization
test for KiwiSeparateMultiSeparate passes the write and can generate
mostly correct JSON output with the full graph description.
add missing ifdef around protobuf, kiwi and adaptagrams usage.
add missing preprocessor guards around protobuf logic.
@haxscramper haxscramper merged commit e0a8195 into master Jun 12, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant