Skip to content

Commit

Permalink
New standalone example showing blueprint configuration of some stock (r…
Browse files Browse the repository at this point in the history
…erun-io#5603)

### What
This is the example that the blueprint getting started guide / tutorials
will be built off of.

However, this is a slightly more full-featured example that can be used
as a reference and standalone application.


![](https://static.rerun.io/blueprint_stocks/8bfe6f16963acdceb2debb9de9a206dc2eb9b280/1024w.png)

### 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 newly built examples:
[app.rerun.io](https://app.rerun.io/pr/5603/index.html)
* Using examples from latest `main` build:
[app.rerun.io](https://app.rerun.io/pr/5603/index.html?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[app.rerun.io](https://app.rerun.io/pr/5603/index.html?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/5603)
- [Docs
preview](https://rerun.io/preview/588e1abcebb80a6b5c2974b3067cfcdb0f7986f9/docs)
<!--DOCS-PREVIEW-->
- [Examples
preview](https://rerun.io/preview/588e1abcebb80a6b5c2974b3067cfcdb0f7986f9/examples)
<!--EXAMPLES-PREVIEW-->
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)

---------

Co-authored-by: Emil Ernerfeldt <[email protected]>
  • Loading branch information
jleibs and emilk authored Mar 21, 2024
1 parent 62c79c7 commit db27962
Show file tree
Hide file tree
Showing 8 changed files with 338 additions and 26 deletions.
1 change: 1 addition & 0 deletions crates/re_space_view_time_series/src/space_view_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ It can greatly improve performance (and readability) in such situations as it pr
time_zone_for_timestamps,
)
})
.y_axis_width(3) // in digits
.label_formatter(move |name, value| {
let name = if name.is_empty() { "y" } else { name };
let label = time_type.format(
Expand Down
38 changes: 38 additions & 0 deletions examples/python/blueprint_stocks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!--[metadata]
title = "Stock Charts"
description = "Uses stock data as an example of how to leverage Rerun blueprints to control the layout and presentation of the viewer."
tags = ["time-series", "blueprint"]
thumbnail = "https://static.rerun.io/blueprint_stocks/8bfe6f16963acdceb2debb9de9a206dc2eb9b280/480w.png"
thumbnail_dimensions = [480, 270]
-->

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

This example fetches the last 5 days of stock data for a few different stocks.
We show how Rerun blueprints can then be used to present many different views of the same data.

```bash
pip install -r examples/python/blueprint_stocks/requirements.txt
python examples/python/blueprint_stocks/blueprint_main.py
```

The different blueprints can be explored using the `--blueprint` flag. For example:

```
python examples/python/blueprint_stocks/main.py --blueprint=one-stock
```

Available choices are:

- `auto`: Reset the blueprint to the auto layout used by the viewer.
- `one-stock`: Uses a filter to show only a single chart.
- `one-stock-with-info`: Uses a container to layout a chart and its info document
- `one-stock-no-peaks`: Uses a filter to additionally remove some of the data from the chart.
- `compare-two`: Adds data from multiple sources to a single chart.
- `grid`: Shows all the charts in a grid layout.
215 changes: 215 additions & 0 deletions examples/python/blueprint_stocks/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
#!/usr/bin/env python3
"""
A simple application that fetches stock data from Yahoo Finance and visualizes it using the Rerun SDK.
The main focus of this example is using blueprints to control how the data is displayed in the viewer.
"""
from __future__ import annotations

import argparse
import datetime as dt
from typing import Any

import humanize
import pytz
import rerun as rr
import rerun.blueprint as rrb
import yfinance as yf

################################################################################
# Helper functions to create blueprints
################################################################################


def auto_blueprint() -> rrb.ViewportLike:
"""A blueprint enabling auto space views, which matches the application default."""
return rrb.Viewport(auto_space_views=True, auto_layout=True)


def one_stock(symbol: str) -> rrb.ViewportLike:
"""Create a blueprint showing a single stock."""
return rrb.TimeSeriesView(name=f"{symbol}", origin=f"/stocks/{symbol}")


def one_stock_with_info(symbol: str) -> rrb.ViewportLike:
"""Create a blueprint showing a single stock with its info arranged vertically."""
return rrb.Vertical(
rrb.TextDocumentView(name=f"{symbol}", origin=f"/stocks/{symbol}/info"),
rrb.TimeSeriesView(name=f"{symbol}", origin=f"/stocks/{symbol}"),
row_shares=[1, 4],
)


def compare_two(symbol1: str, symbol2: str, day: Any) -> rrb.ViewportLike:
"""Create a blueprint comparing 2 stocks for a single day."""
return rrb.TimeSeriesView(
name=f"{symbol1} vs {symbol2} ({day})",
contents=[
f"+ /stocks/{symbol1}/{day}",
f"+ /stocks/{symbol2}/{day}",
],
)


def one_stock_no_peaks(symbol: str) -> rrb.ViewportLike:
"""
Create a blueprint showing a single stock without annotated peaks.
This uses an exclusion pattern to hide the peaks.
"""
return rrb.TimeSeriesView(
name=f"{symbol}",
origin=f"/stocks/{symbol}",
contents=[
"+ $origin/**",
"- $origin/peaks/**",
],
)


def stock_grid(symbols: list[str], dates: list[Any]) -> rrb.ViewportLike:
"""Create a grid of stocks and their time series over all days."""
return rrb.Vertical(
contents=[
rrb.Horizontal(
contents=[rrb.TextDocumentView(name=f"{symbol}", origin=f"/stocks/{symbol}/info")]
+ [rrb.TimeSeriesView(name=f"{day}", origin=f"/stocks/{symbol}/{day}") for day in dates],
)
for symbol in symbols
]
)


def hide_panels(viewport: rrb.ViewportLike) -> rrb.BlueprintLike:
"""Wrap a viewport in a blueprint that hides the time and selection panels."""
return rrb.Blueprint(
viewport,
rrb.TimePanel(expanded=False),
rrb.SelectionPanel(expanded=False),
)


################################################################################
# Helper functions for styling
################################################################################

brand_colors = {
"AAPL": 0xA2AAADFF,
"AMZN": 0xFF9900FF,
"GOOGL": 0x34A853FF,
"META": 0x0081FBFF,
"MSFT": 0xF14F21FF,
}


def style_plot(symbol: str) -> rr.SeriesLine:
return rr.SeriesLine(
color=brand_colors[symbol],
name=symbol,
)


def style_peak(symbol: str) -> rr.SeriesPoint:
return rr.SeriesPoint(
color=0xFF0000FF,
name=f"{symbol} (peak)",
marker="Up",
)


################################################################################
# Main script
################################################################################


def main() -> None:
parser = argparse.ArgumentParser(description="Visualize stock data using the Rerun SDK")
parser.add_argument(
"--blueprint",
choices=["auto", "one-stock", "one-stock-with-info", "compare-two", "one-stock-no-peaks", "grid"],
default="grid",
help="Select the blueprint to use",
)
parser.add_argument(
"--show_panels",
action="store_true",
help="Show the time and selection panels",
)

rr.script_add_args(parser)
args = parser.parse_args()

et_timezone = pytz.timezone("America/New_York")
current_date = dt.datetime.now(et_timezone).date()
symbols = ["AAPL", "AMZN", "GOOGL", "META", "MSFT"]
dates = list(filter(lambda x: x.weekday() < 5, [current_date - dt.timedelta(days=i) for i in range(7, 0, -1)]))

blueprint: rrb.BlueprintLike

if args.blueprint == "auto":
blueprint = auto_blueprint()
elif args.blueprint == "one-stock":
blueprint = one_stock("AAPL")
elif args.blueprint == "one-stock-with-info":
blueprint = one_stock_with_info("AMZN")
elif args.blueprint == "one-stock-no-peaks":
blueprint = one_stock_no_peaks("GOOGL")
elif args.blueprint == "compare-two":
blueprint = compare_two("META", "MSFT", dates[-1])
elif args.blueprint == "grid":
blueprint = stock_grid(symbols, dates)
else:
raise ValueError(f"Unknown blueprint: {args.blueprint}")

if not args.show_panels:
blueprint = hide_panels(blueprint)

rr.init("rerun_example_blueprint_stocks", spawn=True, blueprint=blueprint)

# In a future blueprint release, this can move into the blueprint as well
for symbol in symbols:
for day in dates:
rr.log(f"stocks/{symbol}/{day}", style_plot(symbol), timeless=True)
rr.log(f"stocks/{symbol}/peaks/{day}", style_peak(symbol), timeless=True)

for symbol in symbols:
stock = yf.Ticker(symbol)

name = stock.info["shortName"]
industry = stock.info["industry"]
marketCap = humanize.intword(stock.info["marketCap"])
revenue = humanize.intword(stock.info["totalRevenue"])

info_md = (
f"- **Name**: {name}\n"
f"- **Industry**: {industry}\n"
f"- **Market cap**: ${marketCap}\n"
f"- **Total Revenue**: ${revenue}\n"
)

rr.log(
f"stocks/{symbol}/info",
rr.TextDocument(info_md, media_type=rr.MediaType.MARKDOWN),
timeless=True,
)

for day in dates:
open_time = dt.datetime.combine(day, dt.time(9, 30))
close_time = dt.datetime.combine(day, dt.time(16, 00))

hist = stock.history(start=open_time, end=close_time, interval="5m")

hist.index = hist.index - et_timezone.localize(open_time)
peak = hist.High.idxmax()

for row in hist.itertuples():
rr.set_time_seconds("time", row.Index.total_seconds())
rr.log(f"stocks/{symbol}/{day}", rr.Scalar(row.High))
if row.Index == peak:
rr.log(f"stocks/{symbol}/peaks/{day}", rr.Scalar(row.High))

rr.script_teardown(args)


if __name__ == "__main__":
main()
3 changes: 3 additions & 0 deletions examples/python/blueprint_stocks/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
humanize
rerun-sdk
yfinance
1 change: 1 addition & 0 deletions examples/python/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
-r arkit_scenes/requirements.txt
-r blueprint/requirements.txt
-r blueprint_stocks/requirements.txt
-r clock/requirements.txt
-r controlnet/requirements.txt
-r depth_guided_stable_diffusion/requirements.txt
Expand Down
2 changes: 2 additions & 0 deletions rerun_py/rerun_sdk/rerun/blueprint/__init__.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 32 additions & 9 deletions rerun_py/rerun_sdk/rerun/blueprint/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ class Container:

def __init__(
self,
*contents: Container | SpaceView,
*args: Container | SpaceView,
contents: Optional[Iterable[Container | SpaceView]] = None,
kind: ContainerKindLike,
column_shares: Optional[ColumnShareArrayLike] = None,
row_shares: Optional[RowShareArrayLike] = None,
Expand All @@ -131,8 +132,11 @@ def __init__(
Parameters
----------
*contents:
All positional arguments are the contents of the container, which may be either other containers or space views.
*args:
All positional arguments are forwarded to the `contents` parameter for convenience.
contents:
The contents of the container. Each item in the iterable must be a `SpaceView` or a `Container`.
This can only be used if no positional arguments are provided.
kind
The kind of the container. This must correspond to a known container kind.
Prefer to use one of the subclasses of `Container` which will populate this for you.
Expand All @@ -150,9 +154,17 @@ def __init__(
The active tab in the container. This is only applicable to `Tabs` containers.
"""

if args and contents is not None:
raise ValueError("Cannot provide both positional and keyword arguments for contents")

if contents is not None:
self.contents = contents
else:
self.contents = args

self.id = uuid.uuid4()
self.kind = kind
self.contents = contents
self.column_shares = column_shares
self.row_shares = row_shares
self.grid_columns = grid_columns
Expand Down Expand Up @@ -214,7 +226,11 @@ class Viewport:
"""

def __init__(
self, root_container: Container, *, auto_layout: bool | None = None, auto_space_views: bool | None = None
self,
root_container: Container | None = None,
*,
auto_layout: bool | None = None,
auto_space_views: bool | None = None,
):
"""
Construct a new viewport.
Expand Down Expand Up @@ -255,11 +271,18 @@ def to_blueprint(self) -> Blueprint:

def _log_to_stream(self, stream: RecordingStream) -> None:
"""Internal method to convert to an archetype and log to the stream."""
self.root_container._log_to_stream(stream)
if self.root_container is not None:
self.root_container._log_to_stream(stream)

root_container_id = self.root_container.id.bytes
space_views = list(self.root_container._iter_space_views())
else:
root_container_id = None
space_views = []

arch = ViewportBlueprint(
space_views=list(self.root_container._iter_space_views()),
root_container=self.root_container.id.bytes,
space_views=space_views,
root_container=root_container_id,
auto_layout=self.auto_layout,
auto_space_views=self.auto_space_views,
)
Expand Down Expand Up @@ -434,7 +457,7 @@ def _log_to_stream(self, stream: RecordingStream) -> None:
self.time_panel._log_to_stream(stream)


BlueprintLike = Union[Blueprint, Viewport, Container, SpaceView]
BlueprintLike = Union[Blueprint, ViewportLike]

"""
A type that can be converted to a blueprint.
Expand Down
Loading

0 comments on commit db27962

Please sign in to comment.