Configure the viewer through code

This tutorial will walk you through using the Blueprint APIs to better control the layout and appearance of your data in the Rerun Viewer in Python.

This walkthrough is based on the stock charts example. The main differences between this tutorial and the linked example are related to additional processing of command-line flags, which are omitted here for simplicity.

All of the examples in this tutorial use the exact same data. However, by changing the blueprint using small statements such as:

rrb.Blueprint( rrb.Vertical( rrb.TextDocumentView(name="Info", origin="/stocks/AAPL/info"), rrb.TimeSeriesView(name="Chart", origin="/stocks/AAPL"), row_shares=[1, 4], ) )

we will completely change the way the data is presented.

Create an environment for the example

We start by creating a new virtual environment and installing the Rerun SDK along with the dependencies we will use in this example.

On linux or mac:

mkdir stocks_example cd stocks_example python -m venv venv source venv/bin/activate pip install rerun-sdk humanize yfinance

On windows:

mkdir stocks_example cd stocks_example python -m venv venv .\venv\Scripts\activate pip install rerun-sdk humanize yfinance

Create your script

In your project folder, add a new file, stocks.py.

First, we import the necessary libraries:

#!/usr/bin/env python3 import datetime as dt import humanize import pytz import yfinance as yf from typing import Any import rerun as rr import rerun.blueprint as rrb

Next, we create some helper functions for style data and a template for an info card:

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", ) def info_card( shortName: str, industry: str, marketCap: int, totalRevenue: int, **args: dict[str, Any], ) -> rr.TextDocument: markdown = f""" - **Name**: {shortName} - **Industry**: {industry} - **Market cap**: ${humanize.intword(marketCap)} - **Total Revenue**: ${humanize.intword(totalRevenue)} """ return rr.TextDocument(markdown, media_type=rr.MediaType.MARKDOWN)

And finally, we create our main function that queries and logs the data:

def main() -> None: symbols = ["AAPL", "AMZN", "GOOGL", "META", "MSFT"] # Use eastern time for market hours et_timezone = pytz.timezone("America/New_York") start_date = dt.date(2024, 3, 18) dates = [start_date + dt.timedelta(days=i) for i in range(5)] # Initialize Rerun and spawn a new viewer rr.init("rerun_example_blueprint_stocks", spawn=True) # This is where we will edit the blueprint blueprint = None #rr.send_blueprint(blueprint) # Log the stock data for each symbol and date for symbol in symbols: stock = yf.Ticker(symbol) # Log the stock info document as timeless rr.log(f"stocks/{symbol}/info", info_card(**stock.info), timeless=True) for day in dates: # Log the styling data as timeless rr.log(f"stocks/{symbol}/{day}", style_plot(symbol), timeless=True) rr.log(f"stocks/{symbol}/peaks/{day}", style_peak(symbol), timeless=True) # Query the stock data during market hours open_time = dt.datetime.combine(day, dt.time(9, 30), et_timezone) close_time = dt.datetime.combine(day, dt.time(16, 00), et_timezone) hist = stock.history(start=open_time, end=close_time, interval="5m") # Offset the index to be in seconds since the market open hist.index = hist.index - open_time peak = hist.High.idxmax() # Log the stock state over the course of the day 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)) if __name__ == "__main__": main()

Run your script

You can now run the script and view the results in the Rerun Viewer:

python stocks.py

You should see the application launch and display the stock data, but you will also notice the layout is far from ideal:

Create a blueprint

To improve the layout, we will now use the blueprint APIs to create some custom layouts.

All we need to do is modify the section of the code that currently reads:

# This is where we will edit the blueprint blueprint = None #rr.send_blueprint(blueprint)

Create a view for an origin

Replace these lines with the following:

# Create a single chart for all the AAPL data: blueprint = rrb.Blueprint( rrb.TimeSeriesView(name="AAPL", origin="/stocks/AAPL"), ) rr.send_blueprint(blueprint)

This blueprint uses the origin parameter to scope the view to just a portion of the entity tree.

If you run the script again, you should see a single chart for the AAPL data:

Control the default panel state

In addition to controlling the data, you can also control the default state of the blueprint, selection, and time panels.

Let's modify the code again to include additional blueprint specifications for these:

# Create a single chart for all the AAPL data, and collapse the selection and time panels: blueprint = rrb.Blueprint( rrb.TimeSeriesView(name="AAPL", origin="/stocks/AAPL"), rrb.BlueprintPanel(expanded=True), rrb.SelectionPanel(expanded=False), rrb.TimePanel(expanded=False), ) rr.send_blueprint(blueprint)

This time when you run the script, you will now see the panels start off collapsed, giving you a more focused view of your data:

Combining multiple views

When using blueprints, you don't have to limit yourself to a single view. You can create multiple views and use containers to combine them.

Let's modify the code to include the info card as well. We will use the Vertical container and the row_shares parameter to control the relative size of the views:

# Create a vertical layout of an info document and a time series chart blueprint = rrb.Blueprint( rrb.Vertical( rrb.TextDocumentView(name="Info", origin="/stocks/AAPL/info"), rrb.TimeSeriesView(name="Chart", origin="/stocks/AAPL"), row_shares=[1, 4], ), rrb.BlueprintPanel(expanded=True), rrb.SelectionPanel(expanded=False), rrb.TimePanel(expanded=False), ) rr.send_blueprint(blueprint)

Running the script now produces two views stacked vertically:

Including specific contents

Specifying the origin of a view is convenient, but sometimes you need more control. In this case, you can specify the contents of a view by providing multiple content expressions.

For example, we can create a stock that includes data from both META and MSFT for a single day on the same chart. Using origin alone there is no way we could have expressed this:

# Create a view with two stock time series blueprint = rrb.Blueprint( rrb.TimeSeriesView( name="META vs MSFT", contents=[ "+ /stocks/META/2024-03-19", "+ /stocks/MSFT/2024-03-19", ], ), rrb.BlueprintPanel(expanded=True), rrb.SelectionPanel(expanded=False), rrb.TimePanel(expanded=False), ) rr.send_blueprint(blueprint)

Running the script now produces a chart that combines data from multiple sources:

More complex filtering

Just specifying single path inclusions can also be challenging when dealing datasets that include large subtrees.

Filter expressions can be used to include or exclude data based on a path pattern. This pattern can optionally start with $origin to refer to the origin of the given space, and can end with the wildcard /** to include or exclude an entire subtree,

Going back to our single stock example, we can filter out the peaks data by excluding the peaks subtree:

# Create a single chart for all the AAPL data and filter out the peaks: blueprint = rrb.Blueprint( rrb.TimeSeriesView( name="AAPL", origin="/stocks/AAPL", contents=[ "+ $origin/**", "- $origin/peaks/**", ], ), rrb.BlueprintPanel(expanded=True), rrb.SelectionPanel(expanded=False), rrb.TimePanel(expanded=False), ) rr.send_blueprint(blueprint)

When you run the script you will see that the data from the peaks subtree is no longer part of the view:

Programmatic layouts

Since these layouts are created by executing python code, they can also be generated programmatically.

For example, we can create a create a separate view for every piece of data we were interested in. Setting this up by hand would be extremely tedious.

# Iterate over all the symbols and days to log the stock data in a grid blueprint = rrb.Blueprint( 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 ], name=symbol, ) for symbol in symbols ] ), rrb.BlueprintPanel(expanded=True), rrb.SelectionPanel(expanded=False), rrb.TimePanel(expanded=False), ) rr.send_blueprint(blueprint)

Running the script again this final chart is a significant improvement over the original heuristic-based layout: