Chapter 4: XVIZPanel#
Welcome back to the streetscape.gl
tutorial! So far, we've learned how to load autonomous vehicle (AV) log data using the XVIZ Loader Interface (Chapter 1), visualize the main 3D scene in the LogViewer (Chapter 2), and control playback through time with PlaybackControl (Chapter 3).
These components are great for seeing the vehicle's environment and trajectory, but AV logs contain a lot more information than just 3D positions! They include metrics like speed, acceleration, system status, detailed object properties, plots of values over time, and even raw camera feeds. How do you display all this additional, non-3D data alongside the main visualization?
This is where the XVIZPanel component comes in.
Think of the XVIZPanel as a dynamic dashboard module that can display various types of information widgets. But here's the clever part: what widgets are shown and how they are organized is defined within the XVIZ log data itself. This is done using something called the Declarative UI specification.
The Declarative UI allows the creator of the XVIZ log to define specific UI elements β like metrics, plots, tables, or videos β and group them into named "panels". When streetscape.gl
loads the log, it reads this definition from the log's metadata. The XVIZPanel
component then uses this definition to automatically build and display the specified UI elements, populated with data from the log at the current playback time.
This means you don't have to hardcode complex dashboards in your application for every single log format. If the log includes Declarative UI definitions, XVIZPanel
can render it automatically, providing a rich, data-driven user interface alongside your 3D view.
What is XVIZPanel?#
XVIZPanel is a React component that reads a specific "panel" definition from the loaded XVIZ log's metadata and renders the corresponding UI widgets. It's the part of streetscape.gl
that brings the Declarative UI from the log into your application's interface.
Its primary job is to:
- Look up a panel definition by its
name
in the log's metadata. - Interpret the structure and components defined in that panel.
- Render the appropriate
streetscape.gl
UI components (like components for metrics, plots, tables, videos) based on the definition. - Ensure these components are populated with data from the log's
current frame
.
Using the XVIZPanel#
To use the XVIZPanel component, you need two main things:
- An instance of an object that implements the XVIZ Loader Interface (your
logLoader
from Chapter 1). - The
name
of the specific panel you want to display, as defined in the XVIZ log's metadata.
Here's how you might add an XVIZPanel to your application, typically alongside the LogViewer and PlaybackControl:
import React from 'react';
import { LogViewer, PlaybackControl, XVIZPanel } from 'streetscape.gl';
import { XVIZFileLoader } from 'streetscape.gl'; // Or your preferred loader
// Assume 'logLoader' is already created and connected
// const logLoader = new XVIZFileLoader({...});
// logLoader.connect();
// Dummy loader for example structure - replace with your actual loader setup
const logLoader = new XVIZFileLoader({ /* ... options ... */ });
logLoader.connect();
const MAPBOX_TOKEN = 'YOUR_MAPBOX_ACCESS_TOKEN';
function CompleteLogDisplayWithPanels() {
// Assume the loaded log has Declarative UI panels named "Metrics" and "Camera"
return (
<div>
<div style={{ display: 'flex', height: '60vh' }}>
{/* The LogViewer for the 3D scene */}
<div style={{ flex: 1 }}>
<LogViewer
log={logLoader}
mapboxApiAccessToken={MAPBOX_TOKEN}
// ... other LogViewer props ...
/>
</div>
{/* The container for your XVIZ Panels */}
<div style={{ width: '300px', overflowY: 'auto', padding: '10px' }}>
{/* Render the "Metrics" panel */}
<XVIZPanel log={logLoader} name="Metrics" />
<hr /> {/* Separator */}
{/* Render the "Camera" panel */}
<XVIZPanel log={logLoader} name="Camera" />
{/* You could add more panels here */}
</div>
</div>
{/* The PlaybackControl */}
<div style={{ width: '100%', padding: '0 20px' }}>
<PlaybackControl log={logLoader} />
</div>
</div>
);
}
In this example:
- We import
XVIZPanel
. - We render two
<XVIZPanel />
components. - Both panels receive the same
logLoader
instance via thelog
prop. - Each panel specifies which definition to use from the log's metadata using the
name
prop ("Metrics"
or"Camera"
).
When this component renders and the logLoader
is ready:
* Each XVIZPanel
will look up the panel definition for its given name
in the log's metadata.
* It will then render the sequence of widgets defined within that panel.
* As the log plays or the user seeks (controlled by the PlaybackControl), the XVIZPanel
and its child widgets will automatically update to display the data corresponding to the loader's getCurrentTime()
.
This pattern is consistent with LogViewer and PlaybackControl: components connect to the same XVIZ Loader Interface instance, which acts as the central source of truth for the log's state and data.
XVIZ Declarative UI: The Blueprint#
The content that XVIZPanel
renders comes from the ui_config
section within the XVIZ log's metadata. This ui_config
is structured as an object where keys are panel names (like "Metrics"
, "Camera"
) and values are the definitions for those panels.
Each panel definition is essentially a tree-like structure composed of different component types. streetscape.gl
's XVIZPanel
knows how to translate the most common XVIZ Declarative UI components into corresponding React components:
XVIZ Declarative UI Type | Description | Rendered by XVIZPanel using... |
---|---|---|
PANEL |
A top-level container for a panel. | XVIZPanel itself or XVIZContainer (internal) |
CONTAINER |
Groups other components, horizontal or vertical layout. | XVIZContainer (internal) |
METRIC |
Displays numerical values, often over time as a chart. | _XVIZMetric (internal) |
PLOT |
Displays data series plotted against an independent variable. | _XVIZPlot (internal) |
TABLE / TREETABLE |
Displays tabular data. | _XVIZTable (internal) |
VIDEO |
Displays image streams (e.g., camera feeds). | _XVIZVideo (internal) |
When XVIZPanel
receives a panel definition, it walks through this structure. For each component type it finds (like METRIC
or VIDEO
), it renders the corresponding internal component (_XVIZMetric
, _XVIZVideo
). These internal components then connect to the log
instance passed down by XVIZPanel
to fetch the specific data they need (metrics, image frames, table rows) from the current frame
.
This is why XVIZPanel
only needs the log
instance and the panel name
. It uses the log
to get both the static panel definition (from metadata
) and the dynamic data (from getCurrentFrame()
).
Under the Hood (A Simple Look)#
Let's trace how XVIZPanel
gets its definition and renders content.
- Connection: Like other
streetscape.gl
components,XVIZPanel
uses a wrapper mechanism (theconnectToLog
helper, which we'll dive into in Chapter 5) to connect to thelog
instance provided via itslog
prop. - Getting UI Configuration: The
connectToLog
wrapper calls a special function (getLogState
) provided byXVIZPanel
. This function accesses the log'smetadata
and looks for theui_config
section. It then finds the specific panel definition that matches thename
prop passed toXVIZPanel
. This definition is then passed as a prop (uiConfig
) to the internalXVIZPanelComponent
. - Rendering Panel Structure: The
XVIZPanelComponent
receives theuiConfig
prop. If a definition is found, it iterates through thechildren
defined in theuiConfig
. - Rendering Child Widgets: For each child item in the definition (e.g., a
METRIC
orVIDEO
),XVIZPanelComponent
looks up the appropriate internal React component (_XVIZMetric
,_XVIZVideo
) from a map (DEFAULT_COMPONENTS
). - Passing Data Source: It renders the found component, passing the same
log
prop it received down to the child. This is crucial, as the child components will also useconnectToLog
(or directly accesslog
if needed) to get the specific data they require for their type (e.g.,_XVIZMetric
fetches metric values fromlog.getCurrentFrame().variables
).
Here's a simplified data flow:
sequenceDiagram
participant App as Your App
participant XVIZPanel as XVIZPanel Component
participant ConnectToLog as connectToLog (Wrapper)
participant Loader as XVIZ Loader Instance
participant InternalWidget as Internal Widget<br>(e.g., _XVIZMetric)
App->>XVIZPanel: Render <XVIZPanel log={loader} name="..." />
XVIZPanel->>ConnectToLog: Pass log instance and getLogState function
ConnectToLog->>Loader: getMetadata()
Loader->>ConnectToLog: metadata (including ui_config)
ConnectToLog->>ConnectToLog: Call getLogState(metadata, name)
ConnectToLog->>XVIZPanel: Provide uiConfig prop
XVIZPanel->>XVIZPanel: Read uiConfig prop
loop For each item in uiConfig.children
XVIZPanel->>XVIZPanel: Determine internal component type
XVIZPanel->>InternalWidget: Render <InternalWidget log={loader} ...item_props />
InternalWidget->>ConnectToLog: (or directly) Access log prop
loop On log update / seek
InternalWidget->>Loader: getCurrentFrame() / getStreams() etc.
Loader->>InternalWidget: Relevant data for widget
InternalWidget->>InternalWidget: Render specific widget UI
end
end
Note: The connectToLog
step for internal widgets is simplified in this diagram for clarity.
Let's look at snippets from the streetscape.gl
source code (modules/core/src/components/declarative-ui/xviz-panel.js
) to see parts of this process.
First, how XVIZPanel
gets the uiConfig
using connectToLog
:
// From modules/core/src/components/declarative-ui/xviz-panel.js (simplified)
// This function is used by connectToLog to get state from the log
const getLogState = (log, ownProps) => {
const metadata = log.getMetadata();
// Look up the panel definition by name in the metadata's ui_config
const panel = metadata && metadata.ui_config && metadata.ui_config[ownProps.name];
// Return the definition (or its 'config' property, depending on internal structure)
return {
uiConfig: (panel && panel.config) || panel
};
};
// Connect the component to the log using this function
export default connectToLog({getLogState, Component: XVIZPanelComponent});
uiConfig
prop is populated by calling log.getMetadata()
within the getLogState
function and looking for the panel matching the component's name
prop.
Next, how the XVIZPanelComponent
renders the child widgets:
// From modules/core/src/components/declarative-ui/xviz-panel.js (simplified)
// Map XVIZ UI types to internal React components
const DEFAULT_COMPONENTS = {
container: XVIZContainer, // Internal container component
metric: XVIZMetric, // Points to _XVIZMetric via default export setup
plot: XVIZPlot, // Points to _XVIZPlot
video: XVIZVideo, // Points to _XVIZVideo
table: XVIZTable, // Points to _XVIZTable
treetable: XVIZTable // Points to _XVIZTable
};
class XVIZPanelComponent extends PureComponent {
// ... propTypes, defaultProps ...
_renderItem = (item, i) => {
const {components, componentProps, log, style} = this.props; // Note log prop is available!
const type = item.type.toLowerCase();
// Find the component renderer, prioritize custom ones
const XVIZComponent = components[type] || DEFAULT_COMPONENTS[type];
const customProps = componentProps[type];
if (!XVIZComponent) {
console.warn(`Unknown XVIZ Declarative UI component type: ${item.type}`); // Handle unknown types
return null;
}
// Render the child component, passing down the log instance
return (
<XVIZComponent key={i} {...customProps} {...item} log={log} style={style[item.type]}>
{/* Recursively render children for container types */}
{item.children && item.children.map(this._renderItem)}
</XVIZComponent>
);
};
render() {
const {uiConfig} = this.props;
// If uiConfig is loaded, render its children
return uiConfig ? (
<div>{uiConfig.children && uiConfig.children.map(this._renderItem)}</div>
) : null; // Show nothing if no uiConfig is found for the name
}
}
XVIZPanelComponent
's render
method maps the uiConfig
structure to React components using the _renderItem
helper. Crucially, it passes the log
prop down to each child component, allowing them to connect to the loader and retrieve the specific data streams or variables they need from the current frame
.
For example, the internal _XVIZMetric
component (simplified from modules/core/src/components/declarative-ui/xviz-metric.js
) would use connectToLog
and log.getStreams()
or log.getCurrentFrame().variables
to get the numerical data it needs to render a chart:
// From modules/core/src/components/declarative-ui/xviz-metric.js (simplified)
const getLogState = log => ({
currentTime: log.getCurrentTime(), // Needed for highlighting current point
streamsMetadata: log.getStreamsMetadata(), // Might need for formatting
logStreams: log.getStreams(), // Get ALL stream data currently in buffer
// Or specifically for metrics, might need log.getCurrentFrame().variables
startTime: log.getBufferStartTime(), // For chart axis domain
endTime: log.getBufferEndTime() // For chart axis domain
});
// Connects XVIZMetricComponent to the log
export default connectToLog({getLogState, Component: XVIZMetricComponent});
// ... Inside XVIZMetricComponent render method ...
// It would use props like 'logStreams' or 'variables' and 'currentTime'
// to render the MetricChart component from the monochrome library.
log
prop, passed down by XVIZPanel
, enables the internal components to fetch their required data.
Conclusion#
The XVIZPanel
component provides a powerful way to display auxiliary data from your XVIZ logs using the Declarative UI specification defined within the log metadata. By simply providing the log
instance and the panel name
, you can automatically generate dynamic dashboard elements like metrics, plots, tables, and video feeds, all driven by the log data itself. This complements the 3D visualization from the LogViewer and the time control from PlaybackControl to create a comprehensive viewer experience.
We've seen how multiple components (LogViewer
, PlaybackControl
, XVIZPanel
, and its internal children) all rely on connecting to the single XVIZ Loader Interface
instance to get their state and data. In the next chapter, we'll examine the connectToLog
helper function that makes this seamless data flow and automatic updating possible.
Generated by AI Codebase Knowledge Builder. References: 1(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/docs/api-reference/xviz-panel.md), 2(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/docs/get-started/starter-kit.md), 3(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/components/declarative-ui/xviz-container.js), 4(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/components/declarative-ui/xviz-metric.js), 5(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/components/declarative-ui/xviz-panel.js), 6(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/components/declarative-ui/xviz-plot.js), 7(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/components/declarative-ui/xviz-table.js), 8(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/components/declarative-ui/xviz-video.js), 9(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/core/src/index.js)