Skip to content

Commit

Permalink
New example and tutorial showing how to create a live scrolling plot (#…
Browse files Browse the repository at this point in the history
…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]>
  • Loading branch information
jleibs and teh-cmc authored May 14, 2024
1 parent d1e2bd5 commit 5e241ca
Show file tree
Hide file tree
Showing 11 changed files with 2,469 additions and 2,177 deletions.
1 change: 1 addition & 0 deletions docs/content/howto.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ order: 1

Guides for using Rerun in more advanced ways.
- [Configure the viewer through code](howto/configure-viewer-through-code.md)
- [Create a fixed-window plot](howto/fixed-window-plot.md)
- [Limit memory usage](howto/limit-ram.md)
- [Share recordings across multiple processes](howto/shared-recordings.md)
- [Clear out already logged data](howto/short-lived-entities.md)
Expand Down
62 changes: 62 additions & 0 deletions docs/content/howto/fixed-window-plot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
title: Create a fixed-window plot
order: 150
---

As of Rerun 0.16, the [TimeSeriesView](../reference/types/views/time_series_view.md) now supports direct
manipulation of the visible time range. This allows you to create a plot that only shows a fixed window of data.

## VisibleTimeRange

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.

The `VisibleTimeRange` object takes three parameters:
- `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"`.
- `start`: The start of the visible time range.
- `end`: The end of the visible time range.

The `start` and `end` parameters are set using a `TimeRangeBoundary`:
- To specify an absolute time, you can use the `TimeRangeBoundary.absolute()` method.
- To specify a cursor-relative time, you can use the `TimeRangeBoundary.cursor_relative()` method.
- You can also specify `TimeRangeBoundary.infinite()` to indicate that the start or end of the time range should be unbounded.

In order to account for the different types of timeline (temporal or sequence-based), both the
`TimeRangeBoundary.absolute()` and `TimeRangeBoundary.cursor_relative()` methods can be specified using one of
the keyword args:
- `seconds`: Use this if you called `rr.set_time_seconds()` to update the timeline.
- `nanos`: Use this if you called `rr.set_time_nanos()` to update the timeline.
- `seq`: Use this if you called `rr.set_time_seq()` to update the timeline.

## Example syntax
To create a trailing 5 second window plot, you can specify your `TimeSeriesView` like this:
```python
rrb.TimeSeriesView(
origin="plot_path",
time_ranges=rrb.VisibleTimeRange(
timeline="time",
start=rrb.TimeRangeBoundary.cursor_relative(seconds=-5.0),
end=rrb.TimeRangeBoundary.cursor_relative(),
)
)
```

## Full example
For a complete working example, you can run the following code:

snippet: tutorials/fixed_window_plot

This should create a plot that only shows the last 5 seconds of data. If you select the view, you should
see that the time range is configured as expected.

<picture>
<img src="https://static.rerun.io/fixed_window_example/f76228dc2e1212c148064c2193cdf75ef14bb2b9/full.png" alt="">
<source media="(max-width: 480px)" srcset="https://static.rerun.io/fixed_window_example/f76228dc2e1212c148064c2193cdf75ef14bb2b9/480w.png">
<source media="(max-width: 768px)" srcset="https://static.rerun.io/fixed_window_example/f76228dc2e1212c148064c2193cdf75ef14bb2b9/768w.png">
<source media="(max-width: 1024px)" srcset="https://static.rerun.io/fixed_window_example/f76228dc2e1212c148064c2193cdf75ef14bb2b9/1024w.png">
<source media="(max-width: 1200px)" srcset="https://static.rerun.io/fixed_window_example/f76228dc2e1212c148064c2193cdf75ef14bb2b9/1200w.png">
</picture>

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).

## Additional notes
- Any time you log data, it has two timepoints associated with it: "log_time", and "log_tick".
38 changes: 38 additions & 0 deletions docs/snippets/all/tutorials/fixed_window_plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""A live plot of a random walk using a scrolling fixed window size."""

from __future__ import annotations

import time

import numpy as np
import rerun as rr # pip install rerun-sdk
import rerun.blueprint as rrb

rr.init("rerun_example_fixed_window_plot", spawn=True)

rr.send_blueprint(
rrb.TimeSeriesView(
origin="random_walk",
time_ranges=rrb.VisibleTimeRange(
"time",
start=rrb.TimeRangeBoundary.cursor_relative(seconds=-5.0),
end=rrb.TimeRangeBoundary.cursor_relative(),
),
)
)

cur_time = time.time()
value = 0.0

while True:
cur_time += 0.01
sleep_for = cur_time - time.time()
if sleep_for > 0:
time.sleep(sleep_for)

value += np.random.normal()

rr.set_time_seconds("time", cur_time)

rr.log("random_walk", rr.Scalar(value))
6 changes: 5 additions & 1 deletion docs/snippets/snippets.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@
"rust",
"py",
]

"tutorials/fixed_window_plot" = [
"cpp", # Not implemented
"rust", # Not implemented
"py", # Doesn't terminate
]

"migration/log_line" = [ # Not a complete example -- just a single log line
"cpp",
Expand Down
1 change: 1 addition & 0 deletions examples/manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ examples = [
"multiprocess_logging",
"multithreading",
"plots",
"live_scrolling_plot",
"raw_mesh",
]

Expand Down
69 changes: 69 additions & 0 deletions examples/python/live_scrolling_plot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!--[metadata]
title = "Live Scrolling Plot"
tags = ["Plots", "Live"]
thumbnail = "https://static.rerun.io/live_scrolling_plot_thumbnail/73c6b11bd074af258b8d30092e15361e358d8069/480w.png"
thumbnail_dimensions = [480, 384]
-->

Visualize a live stream of several plots, scrolling horizontally to keep a fixed window of data.

<picture>
<img src="https://static.rerun.io/live_scrolling_plot/9c9a9b3a4dd1d5e858ba58489f686b5d481cfb2e/full.png" alt="">
<source media="(max-width: 480px)" srcset="https://static.rerun.io/live_scrolling_plot/9c9a9b3a4dd1d5e858ba58489f686b5d481cfb2e/480w.png">
<source media="(max-width: 768px)" srcset="https://static.rerun.io/live_scrolling_plot/9c9a9b3a4dd1d5e858ba58489f686b5d481cfb2e/768w.png">
<source media="(max-width: 1024px)" srcset="https://static.rerun.io/live_scrolling_plot/9c9a9b3a4dd1d5e858ba58489f686b5d481cfb2e/1024w.png">
<source media="(max-width: 1200px)" srcset="https://static.rerun.io/live_scrolling_plot/9c9a9b3a4dd1d5e858ba58489f686b5d481cfb2e/1200w.png">
</picture>

## Used Rerun types
[`Scalar`](https://www.rerun.io/docs/reference/types/archetypes/scalar)

## Setting up the blueprint

In order to only show a fixed window of data, this example creates a blueprint that uses
the `time_ranges` parameter of the `TimeSeriesView` blueprint type.

We dynamically create a `TimeSeriesView` for each plot we want to show, so that we can
set the `time_ranges`. The start of the visible time range is set to the current time
minus the window size, and the end is set to the current time.

```python
rr.send_blueprint(
rrb.Grid(
contents=[
rrb.TimeSeriesView(
origin=plot_path,
time_ranges=[
rrb.VisibleTimeRange(
"time",
start=rrb.TimeRangeBoundary.cursor_relative(seconds=-args.window_size),
end=rrb.TimeRangeBoundary.cursor_relative(),
)
],
plot_legend=rrb.PlotLegend(visible=False),
)
for plot_path in plot_paths
]
),
)

```

## Run the code
To run this example, make sure you have the Rerun repository checked out and the latest SDK installed:
```bash
# Setup
pip install --upgrade rerun-sdk # install the latest Rerun SDK
git clone [email protected]:rerun-io/rerun.git # Clone the repository
cd rerun
git checkout latest # Check out the commit matching the latest SDK release
```
Install the necessary libraries specified in the requirements file:
```bash
pip install -e examples/python/live_scrolling_plot
```

Then, simply execute the main Python script:
```bash
python -m live_scrolling_plot
```
87 changes: 87 additions & 0 deletions examples/python/live_scrolling_plot/live_scrolling_plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env python3
"""Show several live plots of random walk data using a scrolling fixed window size."""

from __future__ import annotations

import argparse
import time
from typing import Iterator

import numpy as np
import rerun as rr # pip install rerun-sdk
import rerun.blueprint as rrb


def random_walk_generator() -> Iterator[float]:
value = 0.0
while True:
value += np.random.normal()
yield value


def main() -> None:
parser = argparse.ArgumentParser(description="Plot dashboard stress test")
rr.script_add_args(parser)

parser.add_argument("--num-plots", type=int, default=6, help="How many different plots?")
parser.add_argument("--num-series-per-plot", type=int, default=5, help="How many series in each single plot?")
parser.add_argument("--freq", type=float, default=100, help="Frequency of logging (applies to all series)")
parser.add_argument("--window-size", type=float, default=5.0, help="Size of the window in seconds")
parser.add_argument("--duration", type=float, default=60, help="How long to log for in seconds")

args = parser.parse_args()

plot_paths = [f"plot_{i}" for i in range(0, args.num_plots)]
series_paths = [f"series_{i}" for i in range(0, args.num_series_per_plot)]

rr.script_setup(args, "rerun_example_live_scrolling_plot")

# Always send the blueprint since it is a function of the data.
rr.send_blueprint(
rrb.Grid(
contents=[
rrb.TimeSeriesView(
origin=plot_path,
time_ranges=[
rrb.VisibleTimeRange(
"time",
start=rrb.TimeRangeBoundary.cursor_relative(seconds=-args.window_size),
end=rrb.TimeRangeBoundary.cursor_relative(),
)
],
plot_legend=rrb.PlotLegend(visible=False),
)
for plot_path in plot_paths
]
),
)

# Generate a list of generators for each series in each plot
values = [[random_walk_generator() for _ in range(args.num_series_per_plot)] for _ in range(args.num_plots)]

cur_time = time.time()
end_time = cur_time + args.duration
time_per_tick = 1.0 / args.freq

while cur_time < end_time:
# Advance time and sleep if necessary
cur_time += time_per_tick
sleep_for = cur_time - time.time()
if sleep_for > 0:
time.sleep(sleep_for)

if sleep_for < -0.1:
print(f"Warning: missed logging window by {-sleep_for:.2f} seconds")

rr.set_time_seconds("time", cur_time)

# Output each series based on its generator
for plot_idx, plot_path in enumerate(plot_paths):
for series_idx, series_path in enumerate(series_paths):
rr.log(f"{plot_path}/{series_path}", rr.Scalar(next(values[plot_idx][series_idx])))

rr.script_teardown(args)


if __name__ == "__main__":
main()
12 changes: 12 additions & 0 deletions examples/python/live_scrolling_plot/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[project]
name = "live_scrolling_plot"
version = "0.1.0"
readme = "README.md"
dependencies = ["numpy", "rerun-sdk"]

[project.scripts]
live_scrolling_plot = "live_scrolling_plot:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Loading

0 comments on commit 5e241ca

Please sign in to comment.