Visualizers and Overrides

This section explains the process by which logged data is used to produce a visualization and how it can be customized via the user interface or code.

How are visualizations produced? how-are-visualizations-produced

In the Rerun viewer, visualizations happen within views, which are defined by their blueprint.

The first step for a view to display its content is to determine which entities are involved. This is determined by the entity query, which is part of the view blueprint. The query is run against the data store to generate the list of view entities.

Views rely on visualizers to display each of their entities. For example, 3D views use the Points3D visualizer to display 3D point clouds, and time series views use the SeriesLines visualizer to display time series line plots. Which visualizers are available is highly dependent on the specific kind of view. For example, the SeriesLines visualizer only exist for time series views—not, e.g., for 3D views.

For a given view, each entity's components determine which visualizers are available. By default, visualizers are selected for entities logged with a corresponding archetype. For example, in a 3D view, an entity logged with the Points3D archetype results in the Points3D visualizer being selected by default. This happens because the components of an archetype are tagged with the archetype's name. With a few exceptions, archetypes are directly associated with a single visualizer.

Then, each selected visualizer determines the values for the components it supports. For example, the Points3D visualizer handles, among others, the Position3D, Radius, and Color components. For each of these (and the others it also supports), the visualizer must determine a value. By default, it will use the value that was logged to the data store, if any. Otherwise, it will use some fallback value that depends on the actual type of visualizer and view.

For an illustration, let's consider a simple example with just two Boxes2D:

"""Base example."""

import rerun as rr
import rerun.blueprint as rrb

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

# Data logged to the data store.
rr.log("boxes/1", rr.Boxes2D(centers=[0, 0], sizes=[1, 1], colors=[255, 0, 0]))
rr.log("boxes/2", rr.Boxes2D(centers=[2, 0], sizes=[1, 1], colors=[255, 0, 0]))

rr.send_blueprint(rrb.Spatial2DView())

Here is how the user interface represents the Boxes2D visualizers in the selection panel, when the corresponding entity is selected:

All components used by the visualizer are represented, along with their corresponding values as determined by the visualizer. For the Color component, we can see both the store and fallback values, the former taking precedence over the latter.

Per-entity component override perentity-component-override

To customize a visualization, the blueprint may override any component value for any view entity. This can be achieved either from the user interface or the logging SDK. When such an override is defined, it takes precedence over any value that might have been logged to the data store.

This is how it is achieved with the blueprint API:

"""Override a component."""

import rerun as rr
import rerun.blueprint as rrb

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

# Data logged to the data store.
rr.log("boxes/1", rr.Boxes2D(centers=[0, 0], sizes=[1, 1], colors=[255, 0, 0]))
rr.log("boxes/2", rr.Boxes2D(centers=[2, 0], sizes=[1, 1], colors=[255, 0, 0]))

rr.send_blueprint(
    rrb.Spatial2DView(
        # Override the values from the data store for the first box.
        overrides={
            "boxes/1": rr.Boxes2D.from_fields(colors=[0, 255, 0]),
        },
    ),
)

The color of /boxes/1 is overridden to green. Here is how the user interface represents the corresponding visualizer:

The override is listed above the store and fallback value since it has precedence. It can also be edited or removed from the user interface.

Per-view component default perview-component-default

The blueprint may also specify a default value for components of a given archetype, should their value not be logged to the store or overridden for a given view entity. This makes it easy to configure visual properties for a potentially large number of entities.

This is how it is achieved with the blueprint API:

"""Add a component default."""

import rerun as rr
import rerun.blueprint as rrb

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

# Data logged to the data store.
rr.log("boxes/1", rr.Boxes2D(centers=[0, 0], sizes=[1, 1], colors=[255, 0, 0]))
rr.log("boxes/2", rr.Boxes2D(centers=[2, 0], sizes=[1, 1]))

rr.send_blueprint(
    rrb.Spatial2DView(
        overrides={
            "boxes/1": rr.Boxes2D.from_fields(colors=[0, 255, 0]),
        },
        # Add a default value for all Color components in this view
        defaults=[rr.Boxes2D.from_fields(colors=[0, 0, 255])],
    ),
)

Here, the /boxes/2 entity is no longer logged with a color value, but a default box color is added to the blueprint. Here is how the user interface represents its visualizer:

The default color value is displayed above the fallback since it takes precedence. It can also be edited or removed from the user interface.

All component default values are displayed in the selection panel when selecting the corresponding view:

Again, it is possible to manually add, edit, and remove component defaults from the user interface.

Component value resolution order component-value-resolution-order

The previous sections showed that visualizers use a variety of sources to determine the values of the components they are interested in. Here is a summary of the priority order:

  1. Override: the per-entity override (the highest priority)
  2. Store: the value that was logged to the data store (e.g., with the rr.log() API)
  3. Default: the default value for this component
  4. Fallback: a context-specific fallback value which may depend on the specific visualizer and view type (the lowest priority)

As an illustration, all four values are available for the /boxes/1 entity of the previous example. Here is how its visualizer is represented in the user interface:

Visualizer override visualizer-override

So far, we discussed how visualizers determine values for the components they are interested in and how this can be customized. This section instead discusses the process of how visualizers themselves are determined and how to override this process.

⚠️ NOTE: the feature covered by this section, including its API, is very likely to change in future releases. Also, in Rerun versions prior to v0.24, it was possible to use visualizer overrides for arbitrary visualizers. Starting with v0.24, it is only possible to provide visualizer overrides for the time series view.

Internally, each view keeps track of the archetypes that it can visualize and how to interpret their respective fields. Sometimes it makes sense to override an entire visualizer, to change the way entities are visualized.

Here is how to force a SeriesPoints visualizer for /trig/sin, instead of the default SeriesLines visualizer:

"""Log a scalar over time and override the visualizer."""

from math import cos, sin, tau

import rerun as rr
import rerun.blueprint as rrb

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

# Log the data on a timeline called "step".
for t in range(int(tau * 2 * 10.0)):
    rr.set_time("step", sequence=t)

    rr.log("trig/sin", rr.Scalars(sin(float(t) / 10.0)))
    rr.log("trig/cos", rr.Scalars(cos(float(t) / 10.0)))

# Use the SeriesPoints visualizer for the sin series.
rr.send_blueprint(
    rrb.TimeSeriesView(
        overrides={
            "trig/sin": [
                rrb.VisualizerOverrides([rrb.visualizers.SeriesLines, rrb.visualizers.SeriesPoints]),
            ],
        },
    ),
)

The view now displays a series of points instead of connecting the values with lines. Here is how the visualizer is displayed in the user interface (note the visualizer of type SeriesPoints):