Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New example and tutorial showing how to create a live scrolling plot #6314

Merged
merged 13 commits into from
May 14, 2024
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
60 changes: 60 additions & 0 deletions docs/content/howto/fixed-window-plot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
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 a `VisibleTimeRange` object to the `time_ranges` parameter of the `TimeSeriesView` blueprint type.

The `VisibleTimeRange` object takes three parameters:
- `timeline`: The timeline to use for the time range. This must match the timeline used to log your data.
- `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`. This object can be used to specify a boundary,
either as an absolute time or as a cursor-relative time.
- 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).
40 changes: 40 additions & 0 deletions docs/snippets/all/tutorials/fixed_window_plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/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
70 changes: 70 additions & 0 deletions examples/python/live_scrolling_plot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<!--[metadata]
title = "Live Scrolling Plot"
tags = ["Plots", "Live"]
thumbnail = "https://static.rerun.io/live_scrolling_plot/9c9a9b3a4dd1d5e858ba58489f686b5d481cfb2e/480w.png"
thumbnail_dimensions = [480, 270]
-->

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 git@github.com: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
```
ddd
jleibs marked this conversation as resolved.
Show resolved Hide resolved
jleibs marked this conversation as resolved.
Show resolved Hide resolved
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"