Skip to content

Commit

Permalink
Implement incremental graph layouts (#8308)
Browse files Browse the repository at this point in the history
### Related

* Closes #8282

<!--
Include links to any related issues/PRs in a bulleted list, for example:
* Closes #1234
* Part of #1337
-->

### What

We made the decision to carry over layout information between timestamps
as it leads to a much nicer user experience. This PR implements that
feature.

@abey79 Some of the logic in `provider.rs` has to change again for
blueprint support. I plan to do a cleanup pass in #8299.



https://github.com/user-attachments/assets/ae5b8c8e-9482-452c-bcf0-feb73fc165f0

<!--
Make sure the PR title and labels are set to maximize their usefulness
for the CHANGELOG,
and our `git log`.

If you have noticed any breaking changes, include them in the migration
guide.

We track various metrics at <https://build.rerun.io>.

For maintainers:
* To run all checks from `main`, comment on the PR with `@rerun-bot
full-check`.
* To deploy documentation changes immediately after merging this PR, add
the `deploy docs` label.
-->

---------

Co-authored-by: Antoine Beyeler <49431240+abey79@users.noreply.github.com>
Co-authored-by: Andreas Reich <andreas@rerun.io>
Co-authored-by: Jan Procházka <honza.spacir@gmail.com>
  • Loading branch information
4 people authored Dec 4, 2024
1 parent c5278c2 commit c0fad11
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 8 deletions.
23 changes: 19 additions & 4 deletions crates/viewer/re_space_view_graph/src/layout/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,26 @@ pub struct ForceLayoutProvider {

impl ForceLayoutProvider {
pub fn new(request: LayoutRequest) -> Self {
Self::new_impl(request, None)
}

pub fn new_with_previous(request: LayoutRequest, layout: &Layout) -> Self {
Self::new_impl(request, Some(layout))
}

// TODO(grtlr): Consider consuming the old layout to avoid re-allocating the extents.
// That logic has to be revised when adding the blueprints anyways.
fn new_impl(request: LayoutRequest, layout: Option<&Layout>) -> Self {
let nodes = request.graphs.iter().flat_map(|(_, graph_template)| {
graph_template
.nodes
.iter()
.map(|n| (n.0, fj::Node::from(n.1)))
graph_template.nodes.iter().map(|n| {
let mut fj_node = fj::Node::from(n.1);
if let Some(rect) = layout.and_then(|l| l.get_node(n.0)) {
let pos = rect.center();
fj_node = fj_node.position(pos.x as f64, pos.y as f64);
}

(n.0, fj_node)
})
});

let mut node_index = ahash::HashMap::default();
Expand Down
18 changes: 14 additions & 4 deletions crates/viewer/re_space_view_graph/src/ui/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,25 @@ impl LayoutState {
self // no op
}
// We need to recompute the layout.
Self::None | Self::Finished { .. } => {
Self::None => {
let provider = ForceLayoutProvider::new(new_request);
let layout = provider.init();

Self::InProgress { layout, provider }
}
Self::InProgress { provider, .. } if provider.request != new_request => {
let provider = ForceLayoutProvider::new(new_request);
let layout = provider.init();
Self::Finished { layout, .. } => {
let mut provider = ForceLayoutProvider::new_with_previous(new_request, &layout);
let mut layout = provider.init();
provider.tick(&mut layout);

Self::InProgress { layout, provider }
}
Self::InProgress {
layout, provider, ..
} if provider.request != new_request => {
let mut provider = ForceLayoutProvider::new_with_previous(new_request, &layout);
let mut layout = provider.init();
provider.tick(&mut layout);

Self::InProgress { layout, provider }
}
Expand Down
63 changes: 63 additions & 0 deletions tests/python/release_checklist/check_graph_time_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from __future__ import annotations

import os
import random
from argparse import Namespace
from uuid import uuid4

import rerun as rr
import rerun.blueprint as rrb

README = """\
# Time-varying graph view
Please watch out for any twitching, jumping, or other wise unexpected changes to
the layout when navigating the timeline.
Please check the following:
* Scrub the timeline to see how the graph layout changes over time.
"""


def log_readme() -> None:
rr.log("readme", rr.TextDocument(README, media_type=rr.MediaType.MARKDOWN), static=True)


def log_graphs() -> None:
nodes = ["root"]
edges = []

# Randomly add nodes and edges to the graph
for i in range(50):
existing = random.choice(nodes)
new_node = str(i)
nodes.append(new_node)
edges.append((existing, new_node))

rr.set_time_sequence("frame", i)
rr.log("graph", rr.GraphNodes(nodes, labels=nodes), rr.GraphEdges(edges, graph_type=rr.GraphType.Directed))

rr.send_blueprint(
rrb.Blueprint(
rrb.Grid(
rrb.GraphView(origin="graph", name="Graph"),
rrb.TextDocumentView(origin="readme", name="Instructions"),
)
)
)


def run(args: Namespace) -> None:
rr.script_setup(args, f"{os.path.basename(__file__)}", recording_id=uuid4())

log_readme()
log_graphs()


if __name__ == "__main__":
import argparse

parser = argparse.ArgumentParser(description="Interactive release checklist")
rr.script_add_args(parser)
args = parser.parse_args()
run(args)

0 comments on commit c0fad11

Please sign in to comment.