Skip to content

Commit

Permalink
Implement graph components and archetypes (#7500)
Browse files Browse the repository at this point in the history
<!--
Open the PR up as a draft until you feel it is ready for a proper
review.

Do not make PR:s from your own `main` branch, as that makes it difficult
for reviewers to add their own fixes.

Add any improvements to the branch as new commits to make it easier for
reviewers to follow the progress. All commits will be squashed to a
single commit once the PR is merged into `main`.

Make sure you mention any issues that this PR closes in the description,
as well as any other related issues.

To get an auto-generated PR description you can put "copilot:summary" or
"copilot:walkthrough" anywhere.
-->

Tracking issue: #7897 

This implements basic graph primitives in the Rerun data model. This is
a first step towards visualizing node-link diagrams in Rerun (related
issue: #6898).

In addition to the changes to the data model, this PR adds two example
crates, `node_link_graph` and `graph_view` to the Rust examples that
show how these primitives can be used.

## Design Decisions

- Nodes and edges are stored as `components` and can be batched. To have
a single node per entity we can use Rerun’s [[clamping
mechanism](https://rerun.io/docs/concepts/batches#component-clamping)](https://rerun.io/docs/concepts/batches#component-clamping).
- `GraphNodeId` is modeled as ~`u32` to improve performance when using
`petgraph`~ strings for better user experience.
- A node is unique identified by combining its `GraphNodeId` and its
`EntityPath`.
- Labels of the nodes can be set via the `labels` component and toggled
via `show_labels`
- ~Hierarchical graphs can be modeled through entity paths. For edges
that cross entity boundaries we can insert dummy nodes to properly
render subparts of the hierarchy.~
- ~Nodes and edges need to be logged to the same entity, otherwise the
selections will collide. We provider helper functions / conversions to
link nodes that are stored in different entities.~

> [!WARNING]
> This PR has changed considerably from its initial version:
> * No linking of nodes between entities (an therefore hierarchy) yet.
> * For now, Graphs are local only and therefore have to be logged to
the same entity.

## Logging example

```rs
rec.log(
        "simple",
        &rerun::GraphNodes::new(["a", "b", "c"])
            .with_positions([(0.0, 100.0), (-100.0, 0.0), (100.0, 0.0)])
            .with_labels(["A", "B", "C"]),
    )?;
    // Note: We log to the same entity here.
    rec.log(
        "simple",
        &rerun::GraphEdges::new([("a", "b"), ("b", "c"), ("c", "a")]).with_directed_edges(),
    )?;
```

## TODOs

- [x] ~Get rid of the `Default` derive for `GraphNodeId` and `GraphEdge`
in the flatbuffer definitions.~
- [x]  Improve ergonomics for generating graph edges during logging.
- [x]  Ensure that logging works from Python and C++ too.
- [x]  Fix remaining lints.

### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)
* [x] I have tested the web demo (if applicable):
* Using examples from latest `main` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/7500?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/7500?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json)
* [x] The PR title and labels are set such as to maximize their
usefulness for the next release's CHANGELOG
* [x] If applicable, add a new check to the [release
checklist](https://github.com/rerun-io/rerun/blob/main/tests/python/release_checklist)!
* [x] If have noted any breaking changes to the log API in
`CHANGELOG.md` and the migration guide

- [PR Build Summary](https://build.rerun.io/pr/7500)
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)

To run all checks from `main`, comment on the PR with `@rerun-bot
full-check`.

---------

Co-authored-by: Antoine Beyeler <antoine@rerun.io>
Co-authored-by: Andreas Reich <r_andreas2@web.de>
  • Loading branch information
3 people authored Nov 25, 2024
1 parent 8343647 commit 1202bd4
Show file tree
Hide file tree
Showing 146 changed files with 5,844 additions and 505 deletions.
1 change: 1 addition & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ Update instructions:
| re_space_view | Types & utilities for defining Space View classes and communicating with the Viewport. |
| re_space_view_bar_chart | A Space View that shows a single bar chart. |
| re_space_view_dataframe | A Space View that shows the data contained in entities in a table. |
| re_space_view_graph | A Space View that shows a graph (node-link diagram). |
| re_space_view_map | A Space View that shows geospatial data on a map. |
| re_space_view_spatial | Space Views that show entities in a 2D or 3D spatial relationship. |
| re_space_view_tensor | A Space View dedicated to visualizing tensors with arbitrary dimensionality. |
Expand Down
48 changes: 48 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2376,6 +2376,12 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"

[[package]]
name = "fjadra"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccc0416b27f53bba6c9bd564260f27d4784c5f430926eb16a519356be7d66bbc"

[[package]]
name = "flatbuffers"
version = "23.5.26"
Expand Down Expand Up @@ -2754,6 +2760,25 @@ dependencies = [
"bitflags 2.6.0",
]

[[package]]
name = "graph_binary_tree"
version = "0.21.0-alpha.1+dev"
dependencies = [
"anyhow",
"clap",
"rerun",
]

[[package]]
name = "graph_lattice"
version = "0.21.0-alpha.1+dev"
dependencies = [
"anyhow",
"clap",
"itertools 0.13.0",
"rerun",
]

[[package]]
name = "h2"
version = "0.3.26"
Expand Down Expand Up @@ -6052,6 +6077,28 @@ dependencies = [
"thiserror",
]

[[package]]
name = "re_space_view_graph"
version = "0.21.0-alpha.1+dev"
dependencies = [
"ahash",
"bytemuck",
"egui",
"fjadra",
"nohash-hasher",
"re_chunk",
"re_format",
"re_log_types",
"re_query",
"re_renderer",
"re_space_view",
"re_tracing",
"re_types",
"re_ui",
"re_viewer_context",
"re_viewport_blueprint",
]

[[package]]
name = "re_space_view_map"
version = "0.21.0-alpha.1+dev"
Expand Down Expand Up @@ -6462,6 +6509,7 @@ dependencies = [
"re_smart_channel",
"re_space_view_bar_chart",
"re_space_view_dataframe",
"re_space_view_graph",
"re_space_view_map",
"re_space_view_spatial",
"re_space_view_tensor",
Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ re_space_view = { path = "crates/viewer/re_space_view", version = "=0.21.0-alpha
re_space_view_bar_chart = { path = "crates/viewer/re_space_view_bar_chart", version = "=0.21.0-alpha.1", default-features = false }
re_space_view_spatial = { path = "crates/viewer/re_space_view_spatial", version = "=0.21.0-alpha.1", default-features = false }
re_space_view_dataframe = { path = "crates/viewer/re_space_view_dataframe", version = "=0.21.0-alpha.1", default-features = false }
re_space_view_graph = { path = "crates/viewer/re_space_view_graph", version = "=0.21.0-alpha.1", default-features = false }
re_space_view_map = { path = "crates/viewer/re_space_view_map", version = "=0.21.0-alpha.1", default-features = false }
re_space_view_tensor = { path = "crates/viewer/re_space_view_tensor", version = "=0.21.0-alpha.1", default-features = false }
re_space_view_text_document = { path = "crates/viewer/re_space_view_text_document", version = "=0.21.0-alpha.1", default-features = false }
Expand Down Expand Up @@ -186,6 +187,7 @@ enumset = "1.0.12"
env_logger = { version = "0.10", default-features = false }
ffmpeg-sidecar = { version = "2.0.2", default-features = false }
fixed = { version = "1.28", default-features = false }
fjadra = "0.1"
flatbuffers = "23.0"
futures-channel = "0.3"
futures-util = { version = "0.3", default-features = false }
Expand Down
2 changes: 2 additions & 0 deletions crates/store/re_types/definitions/rerun/archetypes.fbs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions crates/store/re_types/definitions/rerun/archetypes/graph_edges.fbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace rerun.archetypes;

// ---

// TODO(ab): Add images to snippets.

/// A list of edges in a graph.
///
/// By default, edges are undirected.
///
/// \example archetypes/graph_undirected !api title="Simple undirected graph" image=""
/// \example archetypes/graph_directed !api title="Simple directed graph" image=""
table GraphEdges (
"attr.docs.category": "Graph",
"attr.docs.unreleased",
"attr.docs.view_types": "GraphView",
"attr.rust.derive": "PartialEq, Eq",
"attr.rerun.experimental"
) {
// --- Required ---

/// A list of node tuples.
edges: [rerun.components.GraphEdge] ("attr.rerun.component_required", order: 1000);


// --- Recommended ---

/// Specifies if the graph is directed or undirected.
///
/// If no `GraphType` is provided, the graph is assumed to be undirected.
graph_type: rerun.components.GraphType ("attr.rerun.component_recommended", nullable, order: 2000);

}
39 changes: 39 additions & 0 deletions crates/store/re_types/definitions/rerun/archetypes/graph_nodes.fbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace rerun.archetypes;

// ---

// TODO(ab): Add images to snippets.

/// A list of nodes in a graph with optional labels, colors, etc.
///
/// \example archetypes/graph_undirected !api title="Simple undirected graph" image=""
/// \example archetypes/graph_directed !api title="Simple directed graph" image=""
table GraphNodes (
"attr.docs.category": "Graph",
"attr.docs.unreleased",
"attr.docs.view_types": "GraphView",
"attr.rust.derive": "PartialEq",
"attr.rerun.experimental"
) {
// --- Required ---

/// A list of node IDs.
node_ids: [rerun.components.GraphNode] ("attr.rerun.component_required", order: 1000);

// --- Optional ---

/// Optional center positions of the nodes.
positions: [rerun.components.Position2D] ("attr.rerun.component_optional", nullable, order: 3000);

/// Optional colors for the boxes.
colors: [rerun.components.Color] ("attr.rerun.component_optional", nullable, order: 3100);

/// Optional text labels for the node.
labels: [rerun.components.Text] ("attr.rerun.component_optional", nullable, order: 3200);

/// Optional choice of whether the text labels should be shown by default.
show_labels: rerun.components.ShowLabels ("attr.rerun.component_optional", nullable, order: 3250);

/// Optional radii for nodes.
radii: [rerun.components.Radius] ("attr.rerun.component_optional", nullable, order: 3300);
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions crates/store/re_types/definitions/rerun/blueprint/views/graph.fbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace rerun.blueprint.views;

/// A graph view to display time-variying, directed or undirected graph visualization.
///
/// \example views/graph title="Use a blueprint to create a graph view." image="https://static.rerun.io/graph_lattice/f9169da9c3f35b7260c9d74cd5be5fe710aec6a8/1200w.png"
table GraphView (
"attr.rerun.view_identifier": "Graph",
"attr.docs.unreleased"
) {
/// Everything within these bounds is guaranteed to be visible.
///
/// Somethings outside of these bounds may also be visible due to letterboxing.
visual_bounds: rerun.blueprint.archetypes.VisualBounds2D (order: 1000);
}
3 changes: 3 additions & 0 deletions crates/store/re_types/definitions/rerun/components.fbs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions crates/store/re_types/definitions/rerun/components/graph_edge.fbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace rerun.components;

// ---

/// An edge in a graph connecting two nodes.
table GraphEdge (
"attr.docs.unreleased",
"attr.rust.derive": "Default, PartialEq, Eq, PartialOrd, Ord",
"attr.rust.repr": "transparent"
) {
edge: rerun.datatypes.Utf8Pair (order: 100);
}
14 changes: 14 additions & 0 deletions crates/store/re_types/definitions/rerun/components/graph_node.fbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace rerun.components;

// ---

/// A string-based ID representing a node in a graph.
table GraphNode (
"attr.docs.unreleased",
"attr.python.aliases": "str",
"attr.python.array_aliases": "str, Sequence[str]",
"attr.rust.derive": "Default, PartialEq, Eq, PartialOrd, Ord, Hash",
"attr.rust.repr": "transparent"
) {
id: rerun.datatypes.Utf8 (order: 100);
}
18 changes: 18 additions & 0 deletions crates/store/re_types/definitions/rerun/components/graph_type.fbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace rerun.components;

// --

/// Specifies if a graph has directed or undirected edges.
enum GraphType: ubyte (
"attr.docs.unreleased",
"attr.rust.derive": "Default, PartialEq, Eq"
) {
/// Invalid value. Won't show up in generated types.
Invalid = 0,

/// The graph has undirected edges.
Undirected (default),

/// The graph has directed edges.
Directed,
}
1 change: 1 addition & 0 deletions crates/store/re_types/definitions/rerun/datatypes.fbs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions crates/store/re_types/definitions/rerun/datatypes/utf8_pair.fbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace rerun.datatypes;

/// Stores a tuple of UTF-8 strings.
table Utf8Pair (
"attr.docs.unreleased",
"attr.python.aliases": "Tuple[datatypes.Utf8Like, datatypes.Utf8Like]",
"attr.python.array_aliases": "npt.NDArray[np.str_]",
"attr.rust.derive": "Default, PartialEq, Eq, PartialOrd, Ord"
) {
/// The first string.
first: rerun.datatypes.Utf8 (order: 100);

/// The second string.
second: rerun.datatypes.Utf8 (order: 200);
}
2 changes: 2 additions & 0 deletions crates/store/re_types/src/archetypes/.gitattributes

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 1202bd4

Please sign in to comment.