chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
<!--[metadata]
|
||||
title = "Graphs"
|
||||
tags = ["Graph", "Layout", "Node-link diagrams", "Bubble charts"]
|
||||
thumbnail = "https://static.rerun.io/graphs/c1070214bed5e50c9e7d452835f32759b991383e/480w.png"
|
||||
thumbnail_dimensions = [480, 480]
|
||||
channel = "main"
|
||||
include_in_manifest = true
|
||||
-->
|
||||
|
||||
This example shows different types of graphs (and layouts) that you can visualize using Rerun.
|
||||
|
||||
<picture>
|
||||
<img src="https://static.rerun.io/graphs/c1070214bed5e50c9e7d452835f32759b991383e/full.png" alt="">
|
||||
<source media="(max-width: 480px)" srcset="https://static.rerun.io/graphs/c1070214bed5e50c9e7d452835f32759b991383e/480w.png">
|
||||
<source media="(max-width: 768px)" srcset="https://static.rerun.io/graphs/c1070214bed5e50c9e7d452835f32759b991383e/768w.png">
|
||||
<source media="(max-width: 1024px)" srcset="https://static.rerun.io/graphs/c1070214bed5e50c9e7d452835f32759b991383e/1024w.png">
|
||||
<source media="(max-width: 1200px)" srcset="https://static.rerun.io/graphs/c1070214bed5e50c9e7d452835f32759b991383e/1200w.png">
|
||||
</picture>
|
||||
|
||||
Rerun ships with an integrated engine to produce [force-based layouts](https://en.wikipedia.org/wiki/Force-directed_graph_drawing) to visualize graphs.
|
||||
Force-directed layout approaches have to advantage that they are flexible and can therefore be used to create different kinds of visualizations.
|
||||
This example shows different types of layouts:
|
||||
|
||||
* Regular force-directed layouts of node-link diagrams
|
||||
* Bubble charts, which are based on packing circles
|
||||
|
||||
## Used Rerun types
|
||||
|
||||
[`GraphNodes`](https://www.rerun.io/docs/reference/types/archetypes/graph_nodes),
|
||||
[`GraphEdges`](https://www.rerun.io/docs/reference/types/archetypes/graph_edges)
|
||||
|
||||
## Force-based layouts
|
||||
|
||||
To compute the graph layouts, Rerun implements a physics simulation that is very similar to [`d3-force`](https://d3js.org/d3-force). In particular, we implement the following forces:
|
||||
|
||||
* Centering force, which shifts the center of mass of the entire graph.
|
||||
* Collision radius force, which resolves collisions between nodes in the graph, taking their radius into account.
|
||||
* Many-Body force, which can be used to create attraction or repulsion between nodes.
|
||||
* Link force, which acts like a spring between two connected nodes.
|
||||
* Position force, which pull all nodes towards a given position, similar to gravity.
|
||||
|
||||
If you want to learn more about these forces, we recommend looking at the [D3 documentation](https://d3js.org/d3-force) as well.
|
||||
|
||||
Our implementation of the physics simulation is called _Fjädra_. You can find it on [GitHub](https://github.com/grtlr/fjadra) and on [`crates.io`](https://crates.io/crates/fjadra).
|
||||
|
||||
## Run the code
|
||||
|
||||
```bash
|
||||
pip install -e examples/python/graphs
|
||||
python -m graphs
|
||||
```
|
||||
|
||||
Executable
+206
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Examples of logging graph data to Rerun and performing force-based layouts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import itertools
|
||||
import random
|
||||
|
||||
import numpy as np
|
||||
|
||||
import rerun as rr
|
||||
import rerun.blueprint as rrb
|
||||
from rerun.blueprint.archetypes.force_collision_radius import ForceCollisionRadius
|
||||
from rerun.blueprint.archetypes.force_link import ForceLink
|
||||
from rerun.blueprint.archetypes.force_many_body import ForceManyBody
|
||||
|
||||
color_scheme = [
|
||||
[228, 26, 28, 255], # Red
|
||||
[55, 126, 184, 255], # Blue
|
||||
[77, 175, 74, 255], # Green
|
||||
[152, 78, 163, 255], # Purple
|
||||
[255, 127, 0, 255], # Orange
|
||||
[255, 255, 51, 255], # Yellow
|
||||
[166, 86, 40, 255], # Brown
|
||||
[247, 129, 191, 255], # Pink
|
||||
[153, 153, 153, 255], # Gray
|
||||
]
|
||||
|
||||
DESCRIPTION = """
|
||||
# Graphs
|
||||
This example shows various graph visualizations that you can create using Rerun.
|
||||
In this example, the node positions — and therefore the graph layout — are computed by Rerun internally using a force-based layout algorithm.
|
||||
|
||||
You can modify how these graphs look by changing the parameters of the force-based layout algorithm in the selection panel.
|
||||
|
||||
The full source code for this example is available
|
||||
[on GitHub](https://github.com/rerun-io/rerun/blob/latest/examples/python/graphs).
|
||||
""".strip()
|
||||
|
||||
|
||||
# We want reproducible results
|
||||
random.seed(42)
|
||||
|
||||
|
||||
def log_lattice(num_nodes: int) -> None:
|
||||
coordinates = itertools.product(range(num_nodes), range(num_nodes))
|
||||
|
||||
nodes, colors = zip(
|
||||
*[
|
||||
(
|
||||
str(i),
|
||||
rr.components.Color([round((x / (num_nodes - 1)) * 255), round((y / (num_nodes - 1)) * 255), 0, 255]),
|
||||
)
|
||||
for i, (x, y) in enumerate(coordinates)
|
||||
],
|
||||
strict=False,
|
||||
)
|
||||
|
||||
rr.log(
|
||||
"lattice",
|
||||
rr.GraphNodes(
|
||||
nodes,
|
||||
colors=colors,
|
||||
labels=[f"({x}, {y})" for x, y in itertools.product(range(num_nodes), range(num_nodes))],
|
||||
),
|
||||
static=True,
|
||||
)
|
||||
|
||||
edges = []
|
||||
for x, y in itertools.product(range(num_nodes), range(num_nodes)):
|
||||
if y > 0:
|
||||
source = (y - 1) * num_nodes + x
|
||||
target = y * num_nodes + x
|
||||
edges.append((str(source), str(target)))
|
||||
if x > 0:
|
||||
source = y * num_nodes + (x - 1)
|
||||
target = y * num_nodes + x
|
||||
edges.append((str(source), str(target)))
|
||||
|
||||
rr.log("lattice", rr.GraphEdges(edges, graph_type="directed"), static=True)
|
||||
|
||||
|
||||
def log_trees() -> None:
|
||||
nodes = ["root"]
|
||||
radii = [42]
|
||||
colors = [[81, 81, 81, 255]]
|
||||
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)
|
||||
radii.append(random.randint(10, 50))
|
||||
colors.append(random.choice(color_scheme))
|
||||
edges.append((existing, new_node))
|
||||
|
||||
rr.set_time("frame", sequence=i)
|
||||
rr.log(
|
||||
"node_link",
|
||||
rr.GraphNodes(nodes, labels=nodes, radii=radii, colors=colors),
|
||||
rr.GraphEdges(edges, graph_type=rr.GraphType.Directed),
|
||||
)
|
||||
rr.log(
|
||||
"bubble_chart",
|
||||
rr.GraphNodes(nodes, labels=nodes, radii=radii, colors=colors),
|
||||
)
|
||||
|
||||
|
||||
def log_markov_chain() -> None:
|
||||
transition_matrix = np.array([
|
||||
[0.8, 0.1, 0.1], # Transitions from sunny
|
||||
[0.3, 0.4, 0.3], # Transitions from rainy
|
||||
[0.2, 0.3, 0.5], # Transitions from cloudy
|
||||
])
|
||||
state_names = ["sunny", "rainy", "cloudy"]
|
||||
# For this example, we use hardcoded positions.
|
||||
positions = [[0, 0], [150, 150], [300, 0]]
|
||||
inactive_color = [153, 153, 153, 255] # Gray
|
||||
active_colors = [
|
||||
[255, 127, 0, 255], # Orange
|
||||
[55, 126, 184, 255], # Blue
|
||||
[152, 78, 163, 255], # Purple
|
||||
]
|
||||
|
||||
edges = [
|
||||
(state_names[i], state_names[j])
|
||||
for i in range(len(state_names))
|
||||
for j in range(len(state_names))
|
||||
if transition_matrix[i][j] > 0
|
||||
]
|
||||
edges.append(("start", "sunny"))
|
||||
|
||||
# We start in state "sunny"
|
||||
state = "sunny"
|
||||
|
||||
for i in range(50):
|
||||
current_state_index = state_names.index(state)
|
||||
next_state_index = np.random.choice(range(len(state_names)), p=transition_matrix[current_state_index])
|
||||
state = state_names[next_state_index]
|
||||
colors = [inactive_color] * len(state_names)
|
||||
colors[next_state_index] = active_colors[next_state_index]
|
||||
|
||||
rr.set_time("frame", sequence=i)
|
||||
rr.log(
|
||||
"markov_chain",
|
||||
rr.GraphNodes(state_names, labels=state_names, colors=colors, positions=positions),
|
||||
rr.GraphEdges(edges, graph_type="directed"),
|
||||
)
|
||||
|
||||
|
||||
def log_blueprint() -> None:
|
||||
rr.send_blueprint(
|
||||
rrb.Blueprint(
|
||||
rrb.Grid(
|
||||
rrb.GraphView(
|
||||
origin="node_link",
|
||||
name="Node-link diagram",
|
||||
force_link=ForceLink(distance=60),
|
||||
force_many_body=ForceManyBody(strength=-60),
|
||||
),
|
||||
rrb.GraphView(
|
||||
origin="bubble_chart",
|
||||
name="Bubble chart",
|
||||
force_link=ForceLink(enabled=False),
|
||||
force_many_body=ForceManyBody(enabled=False),
|
||||
force_collision_radius=ForceCollisionRadius(enabled=True),
|
||||
defaults=[rr.GraphNodes.from_fields(show_labels=False)],
|
||||
),
|
||||
rrb.GraphView(
|
||||
origin="lattice",
|
||||
name="Lattice",
|
||||
force_link=ForceLink(distance=60),
|
||||
force_many_body=ForceManyBody(strength=-60),
|
||||
defaults=[rr.GraphNodes.from_fields(show_labels=False, radii=10)],
|
||||
),
|
||||
rrb.Horizontal(
|
||||
rrb.GraphView(
|
||||
origin="markov_chain",
|
||||
name="Markov Chain",
|
||||
# We don't need any forces for this graph, because the nodes have fixed positions.
|
||||
),
|
||||
rrb.TextDocumentView(origin="description", name="Description"),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Logs various graphs using the Rerun SDK.")
|
||||
rr.script_add_args(parser)
|
||||
args = parser.parse_args()
|
||||
|
||||
rr.script_setup(args, "rerun_example_graphs")
|
||||
rr.log("description", rr.TextDocument(DESCRIPTION, media_type=rr.MediaType.MARKDOWN), static=True)
|
||||
log_trees()
|
||||
log_lattice(10)
|
||||
log_markov_chain()
|
||||
log_blueprint()
|
||||
rr.script_teardown(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,12 @@
|
||||
[project]
|
||||
name = "graphs"
|
||||
version = "0.1.0"
|
||||
readme = "README.md"
|
||||
dependencies = ["rerun-sdk"]
|
||||
|
||||
[project.scripts]
|
||||
graphs = "graphs:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
Reference in New Issue
Block a user