Skip to content

Commit 5e241ca

Browse files
jleibsteh-cmc
andauthored
New example and tutorial showing how to create a live scrolling plot (#6314)
### What - Resolves: #5820 Although I would like to update the blueprint_stocks example for completeness, we decided it would be nice to have a tight live-scrolling example and how-to guide since this is one of the more common things people look for. Guide: - https://landing-e9j32r1lf-rerun.vercel.app/docs/howto/fixed-window-plot ### 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/6314?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/6314?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)! - [PR Build Summary](https://build.rerun.io/pr/6314) - [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: Clement Rey <[email protected]>
1 parent d1e2bd5 commit 5e241ca

File tree

11 files changed

+2469
-2177
lines changed

11 files changed

+2469
-2177
lines changed

docs/content/howto.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ order: 1
55

66
Guides for using Rerun in more advanced ways.
77
- [Configure the viewer through code](howto/configure-viewer-through-code.md)
8+
- [Create a fixed-window plot](howto/fixed-window-plot.md)
89
- [Limit memory usage](howto/limit-ram.md)
910
- [Share recordings across multiple processes](howto/shared-recordings.md)
1011
- [Clear out already logged data](howto/short-lived-entities.md)
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
---
2+
title: Create a fixed-window plot
3+
order: 150
4+
---
5+
6+
As of Rerun 0.16, the [TimeSeriesView](../reference/types/views/time_series_view.md) now supports direct
7+
manipulation of the visible time range. This allows you to create a plot that only shows a fixed window of data.
8+
9+
## VisibleTimeRange
10+
11+
To specify the visible time range, you must pass one or more `VisibleTimeRange` objects to the `time_ranges` parameter of the `TimeSeriesView` blueprint type. If your app only uses a single timeline, you can directly pass a single `VisibleTimeRange` object instead of wrapping it in a list.
12+
13+
The `VisibleTimeRange` object takes three parameters:
14+
- `timeline`: The timeline that the range will apply to. This must match the timeline used to log your data, or if you are only using the rerun-provided timestamps, you can use the strings `"log_time"`, or `"log_tick"`.
15+
- `start`: The start of the visible time range.
16+
- `end`: The end of the visible time range.
17+
18+
The `start` and `end` parameters are set using a `TimeRangeBoundary`:
19+
- To specify an absolute time, you can use the `TimeRangeBoundary.absolute()` method.
20+
- To specify a cursor-relative time, you can use the `TimeRangeBoundary.cursor_relative()` method.
21+
- You can also specify `TimeRangeBoundary.infinite()` to indicate that the start or end of the time range should be unbounded.
22+
23+
In order to account for the different types of timeline (temporal or sequence-based), both the
24+
`TimeRangeBoundary.absolute()` and `TimeRangeBoundary.cursor_relative()` methods can be specified using one of
25+
the keyword args:
26+
- `seconds`: Use this if you called `rr.set_time_seconds()` to update the timeline.
27+
- `nanos`: Use this if you called `rr.set_time_nanos()` to update the timeline.
28+
- `seq`: Use this if you called `rr.set_time_seq()` to update the timeline.
29+
30+
## Example syntax
31+
To create a trailing 5 second window plot, you can specify your `TimeSeriesView` like this:
32+
```python
33+
rrb.TimeSeriesView(
34+
origin="plot_path",
35+
time_ranges=rrb.VisibleTimeRange(
36+
timeline="time",
37+
start=rrb.TimeRangeBoundary.cursor_relative(seconds=-5.0),
38+
end=rrb.TimeRangeBoundary.cursor_relative(),
39+
)
40+
)
41+
```
42+
43+
## Full example
44+
For a complete working example, you can run the following code:
45+
46+
snippet: tutorials/fixed_window_plot
47+
48+
This should create a plot that only shows the last 5 seconds of data. If you select the view, you should
49+
see that the time range is configured as expected.
50+
51+
<picture>
52+
<img src="https://static.rerun.io/fixed_window_example/f76228dc2e1212c148064c2193cdf75ef14bb2b9/full.png" alt="">
53+
<source media="(max-width: 480px)" srcset="https://static.rerun.io/fixed_window_example/f76228dc2e1212c148064c2193cdf75ef14bb2b9/480w.png">
54+
<source media="(max-width: 768px)" srcset="https://static.rerun.io/fixed_window_example/f76228dc2e1212c148064c2193cdf75ef14bb2b9/768w.png">
55+
<source media="(max-width: 1024px)" srcset="https://static.rerun.io/fixed_window_example/f76228dc2e1212c148064c2193cdf75ef14bb2b9/1024w.png">
56+
<source media="(max-width: 1200px)" srcset="https://static.rerun.io/fixed_window_example/f76228dc2e1212c148064c2193cdf75ef14bb2b9/1200w.png">
57+
</picture>
58+
59+
Alternatively, you can check out a more full-featured example with multiple plot windows [here](https://github.com/rerun-io/rerun/tree/latest/examples/python/live_scrolling_plot?speculative-link).
60+
61+
## Additional notes
62+
- Any time you log data, it has two timepoints associated with it: "log_time", and "log_tick".
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env python3
2+
"""A live plot of a random walk using a scrolling fixed window size."""
3+
4+
from __future__ import annotations
5+
6+
import time
7+
8+
import numpy as np
9+
import rerun as rr # pip install rerun-sdk
10+
import rerun.blueprint as rrb
11+
12+
rr.init("rerun_example_fixed_window_plot", spawn=True)
13+
14+
rr.send_blueprint(
15+
rrb.TimeSeriesView(
16+
origin="random_walk",
17+
time_ranges=rrb.VisibleTimeRange(
18+
"time",
19+
start=rrb.TimeRangeBoundary.cursor_relative(seconds=-5.0),
20+
end=rrb.TimeRangeBoundary.cursor_relative(),
21+
),
22+
)
23+
)
24+
25+
cur_time = time.time()
26+
value = 0.0
27+
28+
while True:
29+
cur_time += 0.01
30+
sleep_for = cur_time - time.time()
31+
if sleep_for > 0:
32+
time.sleep(sleep_for)
33+
34+
value += np.random.normal()
35+
36+
rr.set_time_seconds("time", cur_time)
37+
38+
rr.log("random_walk", rr.Scalar(value))

docs/snippets/snippets.toml

+5-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@
3737
"rust",
3838
"py",
3939
]
40-
40+
"tutorials/fixed_window_plot" = [
41+
"cpp", # Not implemented
42+
"rust", # Not implemented
43+
"py", # Doesn't terminate
44+
]
4145

4246
"migration/log_line" = [ # Not a complete example -- just a single log line
4347
"cpp",

examples/manifest.toml

+1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ examples = [
131131
"multiprocess_logging",
132132
"multithreading",
133133
"plots",
134+
"live_scrolling_plot",
134135
"raw_mesh",
135136
]
136137

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<!--[metadata]
2+
title = "Live Scrolling Plot"
3+
tags = ["Plots", "Live"]
4+
thumbnail = "https://static.rerun.io/live_scrolling_plot_thumbnail/73c6b11bd074af258b8d30092e15361e358d8069/480w.png"
5+
thumbnail_dimensions = [480, 384]
6+
-->
7+
8+
Visualize a live stream of several plots, scrolling horizontally to keep a fixed window of data.
9+
10+
<picture>
11+
<img src="https://static.rerun.io/live_scrolling_plot/9c9a9b3a4dd1d5e858ba58489f686b5d481cfb2e/full.png" alt="">
12+
<source media="(max-width: 480px)" srcset="https://static.rerun.io/live_scrolling_plot/9c9a9b3a4dd1d5e858ba58489f686b5d481cfb2e/480w.png">
13+
<source media="(max-width: 768px)" srcset="https://static.rerun.io/live_scrolling_plot/9c9a9b3a4dd1d5e858ba58489f686b5d481cfb2e/768w.png">
14+
<source media="(max-width: 1024px)" srcset="https://static.rerun.io/live_scrolling_plot/9c9a9b3a4dd1d5e858ba58489f686b5d481cfb2e/1024w.png">
15+
<source media="(max-width: 1200px)" srcset="https://static.rerun.io/live_scrolling_plot/9c9a9b3a4dd1d5e858ba58489f686b5d481cfb2e/1200w.png">
16+
</picture>
17+
18+
## Used Rerun types
19+
[`Scalar`](https://www.rerun.io/docs/reference/types/archetypes/scalar)
20+
21+
## Setting up the blueprint
22+
23+
In order to only show a fixed window of data, this example creates a blueprint that uses
24+
the `time_ranges` parameter of the `TimeSeriesView` blueprint type.
25+
26+
We dynamically create a `TimeSeriesView` for each plot we want to show, so that we can
27+
set the `time_ranges`. The start of the visible time range is set to the current time
28+
minus the window size, and the end is set to the current time.
29+
30+
```python
31+
rr.send_blueprint(
32+
rrb.Grid(
33+
contents=[
34+
rrb.TimeSeriesView(
35+
origin=plot_path,
36+
time_ranges=[
37+
rrb.VisibleTimeRange(
38+
"time",
39+
start=rrb.TimeRangeBoundary.cursor_relative(seconds=-args.window_size),
40+
end=rrb.TimeRangeBoundary.cursor_relative(),
41+
)
42+
],
43+
plot_legend=rrb.PlotLegend(visible=False),
44+
)
45+
for plot_path in plot_paths
46+
]
47+
),
48+
)
49+
50+
```
51+
52+
## Run the code
53+
To run this example, make sure you have the Rerun repository checked out and the latest SDK installed:
54+
```bash
55+
# Setup
56+
pip install --upgrade rerun-sdk # install the latest Rerun SDK
57+
git clone [email protected]:rerun-io/rerun.git # Clone the repository
58+
cd rerun
59+
git checkout latest # Check out the commit matching the latest SDK release
60+
```
61+
Install the necessary libraries specified in the requirements file:
62+
```bash
63+
pip install -e examples/python/live_scrolling_plot
64+
```
65+
66+
Then, simply execute the main Python script:
67+
```bash
68+
python -m live_scrolling_plot
69+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/usr/bin/env python3
2+
"""Show several live plots of random walk data using a scrolling fixed window size."""
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
import time
8+
from typing import Iterator
9+
10+
import numpy as np
11+
import rerun as rr # pip install rerun-sdk
12+
import rerun.blueprint as rrb
13+
14+
15+
def random_walk_generator() -> Iterator[float]:
16+
value = 0.0
17+
while True:
18+
value += np.random.normal()
19+
yield value
20+
21+
22+
def main() -> None:
23+
parser = argparse.ArgumentParser(description="Plot dashboard stress test")
24+
rr.script_add_args(parser)
25+
26+
parser.add_argument("--num-plots", type=int, default=6, help="How many different plots?")
27+
parser.add_argument("--num-series-per-plot", type=int, default=5, help="How many series in each single plot?")
28+
parser.add_argument("--freq", type=float, default=100, help="Frequency of logging (applies to all series)")
29+
parser.add_argument("--window-size", type=float, default=5.0, help="Size of the window in seconds")
30+
parser.add_argument("--duration", type=float, default=60, help="How long to log for in seconds")
31+
32+
args = parser.parse_args()
33+
34+
plot_paths = [f"plot_{i}" for i in range(0, args.num_plots)]
35+
series_paths = [f"series_{i}" for i in range(0, args.num_series_per_plot)]
36+
37+
rr.script_setup(args, "rerun_example_live_scrolling_plot")
38+
39+
# Always send the blueprint since it is a function of the data.
40+
rr.send_blueprint(
41+
rrb.Grid(
42+
contents=[
43+
rrb.TimeSeriesView(
44+
origin=plot_path,
45+
time_ranges=[
46+
rrb.VisibleTimeRange(
47+
"time",
48+
start=rrb.TimeRangeBoundary.cursor_relative(seconds=-args.window_size),
49+
end=rrb.TimeRangeBoundary.cursor_relative(),
50+
)
51+
],
52+
plot_legend=rrb.PlotLegend(visible=False),
53+
)
54+
for plot_path in plot_paths
55+
]
56+
),
57+
)
58+
59+
# Generate a list of generators for each series in each plot
60+
values = [[random_walk_generator() for _ in range(args.num_series_per_plot)] for _ in range(args.num_plots)]
61+
62+
cur_time = time.time()
63+
end_time = cur_time + args.duration
64+
time_per_tick = 1.0 / args.freq
65+
66+
while cur_time < end_time:
67+
# Advance time and sleep if necessary
68+
cur_time += time_per_tick
69+
sleep_for = cur_time - time.time()
70+
if sleep_for > 0:
71+
time.sleep(sleep_for)
72+
73+
if sleep_for < -0.1:
74+
print(f"Warning: missed logging window by {-sleep_for:.2f} seconds")
75+
76+
rr.set_time_seconds("time", cur_time)
77+
78+
# Output each series based on its generator
79+
for plot_idx, plot_path in enumerate(plot_paths):
80+
for series_idx, series_path in enumerate(series_paths):
81+
rr.log(f"{plot_path}/{series_path}", rr.Scalar(next(values[plot_idx][series_idx])))
82+
83+
rr.script_teardown(args)
84+
85+
86+
if __name__ == "__main__":
87+
main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[project]
2+
name = "live_scrolling_plot"
3+
version = "0.1.0"
4+
readme = "README.md"
5+
dependencies = ["numpy", "rerun-sdk"]
6+
7+
[project.scripts]
8+
live_scrolling_plot = "live_scrolling_plot:main"
9+
10+
[build-system]
11+
requires = ["hatchling"]
12+
build-backend = "hatchling.build"

0 commit comments

Comments
 (0)