Skip to content

Chapter 7: Monochrome UI Components#

Welcome back to the streetscape.gl tutorial! In our journey so far, we've learned how to load autonomous vehicle (AV) data using the XVIZ Loader Interface (Chapter 1), display the 3D scene in the LogViewer (Chapter 2), control playback with PlaybackControl (Chapter 3), show dynamic data panels with XVIZPanel (Chapter 4), understand the connectToLog utility that ties components to the loader (Chapter 5), and peeked into the XVIZLayer that handles the 3D drawing (Chapter 6).

All these pieces work together to display your log data. But what about the actual buttons, sliders, checkboxes, and input fields you interact with? When you click 'Play' on the PlaybackControl or toggle a setting on an XVIZPanel, what are those UI elements made of?

streetscape.gl needs a consistent look and feel for these standard user interface gadgets. To achieve this without reinventing common UI patterns, it uses a separate, specialized library: @streetscape.gl/monochrome, often just referred to as "Monochrome".

Think of Monochrome as a toolbox full of standard, well-designed UI controls. These controls are stateless, meaning they don't manage whether they are "on" or "off", or what their current value is internally. Instead, they receive their state (like value={true} or isPlaying={false}) as props and simply call a function (onChange, onClick, etc.) when a user interacts with them. This makes them flexible and easy to integrate into any React application, including streetscape.gl.

What are Monochrome UI Components?#

@streetscape.gl/monochrome is a separate library of reusable React components specifically for building user interface controls. While developed alongside streetscape.gl, it can actually be used independently in other React projects if you need stateless, stylable UI controls.

Its core purpose is to provide building blocks for common UI patterns found in applications like log viewers, such as:

  • Buttons (Play/Pause, etc.)
  • Sliders (Timeline scrubbers, Look Ahead controls)
  • Checkboxes and Radio buttons (Enabling/disabling features or streams)
  • Dropdowns (Selecting options)
  • Text input fields (Entering values)
  • Containers that float or can be dragged/dropped
  • Components for displaying metrics, plots, and tables (used by XVIZPanel)

These components are designed with a focus on:

  • Statelessness: They reflect the state passed down via props and report user interactions via callbacks, but don't hold onto their own state.
  • Stylability: They are built to be easily customized in appearance using CSS overrides and a theming system.

Why Use Monochrome?#

Using a dedicated UI library like Monochrome (or any other UI library) offers several benefits when building applications:

  1. Consistency: Components share a common design language and behavior, making your UI look and feel unified.
  2. Efficiency: You don't have to build common controls like buttons or sliders from scratch every time.
  3. Maintainability: Updates to the library can provide improvements or bug fixes across all uses of the components.
  4. Styling Control: They provide explicit ways to apply custom styles or switch between themes (like light and dark modes).

For streetscape.gl, using Monochrome ensures that the UI elements provided by core components like PlaybackControl and XVIZPanel have a consistent, clean look that can be easily themed to match your application's branding.

Common Monochrome Components#

Monochrome provides a variety of components. You can see a full list in its API documentation, but here are a few common examples:

Component Description Typical Use in streetscape.gl
Button A clickable button. Play/Pause button in PlaybackControl
Slider A control for selecting a value within a range by dragging a knob. Timeline scrubber, Look Ahead control in PlaybackControl
CheckBox A box that can be checked, unchecked, or indeterminate (3 states). Toggling individual streams or features visible in LogViewer
RadioBox A group of options where only one can be selected. Selecting different visualization modes or settings
Dropdown A clickable element that reveals a list of options when open. Selecting views, coordinate systems, or other options
TextBox An input field for typing text. Searching, entering numerical values
Toggle A switch to turn a feature on or off (boolean state). Simpler alternative to CheckBox for boolean states
FloatPanel A container that can be moved and resized like a window. Used internally by XVIZPanel for panels if configured this way
MetricCard A component to display a single metric with a title and description. Used by XVIZPanel for METRIC type widgets
MetricChart A component to display a line chart. Used by XVIZPanel for METRIC or PLOT type widgets
Table A component to display tabular data. Used by XVIZPanel for TABLE/TREETABLE type widgets

And many more! Including DragDropList, Form, Popover, Tooltip, Label, Spinner.

Using Monochrome in Your Application#

While core streetscape.gl components use Monochrome internally, you can also use them directly in your application to build custom UI around the viewer.

Let's say you want to add a simple button to your application that, when clicked, prints a message to the console.

First, you would import the Button component from @streetscape.gl/monochrome:

import { Button } from '@streetscape.gl/monochrome';

// ... rest of your React component

Then, you can render the button and provide it with an onClick handler:

import React from 'react';
import { Button } from '@streetscape.gl/monochrome';
// Assume other streetscape.gl imports are here

// ... Assume logLoader is created and connected ...

function MyAppControls({ logLoader }) { // Example component
  const handleButtonClick = () => {
    console.log('Button clicked!');
    // You could interact with the logLoader here if needed, e.g., logLoader.seek(0)
  };

  return (
    <div>
      {/* ... Other controls like PlaybackControl might be here ... */}
      <Button onClick={handleButtonClick} type="primary">
        Do Something
      </Button>
    </div>
  );
}

In this example:

  1. We import Button.
  2. We define a simple handleButtonClick function.
  3. We render <Button>, passing our function to the onClick prop.
  4. We set the button's type to "primary" (Monochrome buttons have different types for styling).
  5. The text "Do Something" becomes the button's label (passed as children).

When the user clicks the button, the handleButtonClick function is called. Notice how simple it is – the Button component is stateless; it doesn't need to know anything about where it's used or manage complex internal logic. It just receives props (onClick, type, children) and calls the provided callback when the user interacts.

Similarly, you could add a checkbox to toggle a setting:

import React, { useState } from 'react';
import { CheckBox } from '@streetscape.gl/monochrome';
// ... other imports

function MyAppSettings() {
  const [showGrids, setShowGrids] = useState(false); // Manage state in parent

  const handleCheckBoxChange = (newValue) => {
    // newValue will be CheckBox.ON or CheckBox.OFF
    console.log('Show grids toggled:', newValue === CheckBox.ON);
    setShowGrids(newValue === CheckBox.ON);
    // You might pass this state up or down to influence the LogViewer
  };

  return (
    <div>
      <CheckBox
        label="Show Grid Lines"
        value={showGrids ? CheckBox.ON : CheckBox.OFF} // Pass state down
        onChange={handleCheckBoxChange} // Receive interaction event
      />
    </div>
  );
}
Here, we use useState to manage the showGrids state in the parent component. The CheckBox component simply receives this state via the value prop and reports changes via the onChange prop. This is the typical stateless pattern of Monochrome components.

Styling Monochrome Components#

Monochrome components are designed to be highly stylable. There are two primary ways to customize their appearance:

  1. style prop: Most Monochrome components accept a style prop. This prop is an object where keys correspond to different internal parts of the component (e.g., wrapper, button, track, knob for a slider). The values can be CSS-in-JS objects or functions that receive component state (isHovered, isActive, value, theme, etc.) to apply dynamic styles.

    Example for a Slider's knob:

    import { Slider } from '@streetscape.gl/monochrome';
    
    const mySliderStyle = {
      knob: props => ({ // This is a function style
        backgroundColor: props.isActive ? 'yellow' : 'blue', // Change color when active
        border: '2px solid white',
      }),
      track: { // This is a simple object style
        backgroundColor: 'grey',
        height: 4,
      }
    };
    
    <Slider value={0.5} min={0} max={1} onChange={...} style={mySliderStyle} />
    
    The specific style keys available vary per component. You would need to check the documentation for each component (like the snippets in the provided modules/monochrome/*/README.md files).

  2. ThemeProvider: Monochrome supports a theming system. You can wrap your application (or a part of it) in a ThemeProvider component and provide a theme object. This theme object defines common colors, fonts, spacing, etc., that all Monochrome components under the provider will use by default.

    Example using ThemeProvider:

    import { ThemeProvider } from '@streetscape.gl/monochrome';
    
    const DARK_THEME = {
      extends: 'dark', // Start with the built-in dark theme
      background: '#222',
      textColorPrimary: '#EEE',
      controlColorPrimary: '#444',
      controlColorHovered: '#666',
      // ... define other theme properties
    };
    
    function App() {
      return (
        <ThemeProvider theme={DARK_THEME}>
          {/* All Monochrome components here will use DARK_THEME */}
          <CompleteLogDisplay /> {/* Contains LogViewer, PlaybackControl, XVIZPanel */}
        </ThemeProvider>
      );
    }
    
    This is how streetscape.gl applications typically apply a consistent visual style across all UI elements. The theme object structure is defined in the Monochrome styling documentation (see docs/api-reference/styling-guide.md / docs/get-started/styling.md).

Both the style prop and ThemeProvider mechanisms leverage the emotion library under the hood for styling in JavaScript.

How streetscape.gl Uses Monochrome Internally#

Core streetscape.gl components that provide UI controls are built using Monochrome components.

  • The PlaybackControl component (Chapter 3) internally uses:

    • Monochrome Slider for the timeline scrubber and potentially the look ahead control.
    • Monochrome Button for the Play/Pause button.
    • Monochrome components for displaying time labels.
    • It wires up the user interaction events from these Monochrome components (e.g., Slider's onChange, Button's onClick) to call methods on the XVIZ Loader instance (log.seek(), log.play(), log.pause()).
  • The XVIZPanel component (Chapter 4) acts as a container that renders different Monochrome components based on the XVIZ Declarative UI definition:

    • If the log defines a METRIC widget, XVIZPanel renders a Monochrome MetricCard containing a MetricChart or RichMetricChart.
    • If the log defines a TABLE or TREETABLE widget, XVIZPanel renders a Monochrome Table or TreeTable.
    • It might also use Monochrome Toggle or CheckBox if the Declarative UI includes controls defined this way.
    • XVIZPanel and its child components receive log data via mechanisms like connectToLog (Chapter 5) and pass that data down as props to the stateless Monochrome components (e.g., passing chart data to MetricChart's data prop).

This relationship looks something like this:

sequenceDiagram
    participant LogViewer as LogViewer
    participant PlaybackControl as PlaybackControl
    participant XVIZPanel as XVIZPanel
    participant Loader as XVIZ Loader
    participant MonoButton as Monochrome Button
    participant MonoSlider as Monochrome Slider
    participant MonoMetricChart as Monochrome MetricChart

    App->>Loader: Create loader instance
    App->>LogViewer: Render <LogViewer log={loader}>
    App->>PlaybackControl: Render <PlaybackControl log={loader}>
    App->>XVIZPanel: Render <XVIZPanel log={loader} name="...">

    PlaybackControl->>MonoButton: Render <Button onClick={...}>
    PlaybackControl->>MonoSlider: Render <Slider onChange={...}>

    XVIZPanel->>MonoMetricChart: Render <MetricChart data={...}>
    Note over XVIZPanel,MonoMetricChart: (Based on XVIZ UI config)

    User->>MonoButton: Click
    MonoButton->>PlaybackControl: onClick callback
    PlaybackControl->>Loader: log.play() / log.pause()
    Loader->>LogViewer: Notify update
    Loader->>PlaybackControl: Notify update
    LogViewer->>LogViewer: Re-render 3D
    PlaybackControl->>PlaybackControl: Update UI (e.g. Slider position, button icon)
This diagram simplifies interactions, particularly the role of connectToLog in receiving updates.

The key takeaway is that streetscape.gl components handle the logic of connecting to the log and fetching the right data/state, and then they use the stateless Monochrome components as their visual, interactive presentation layer.

Conclusion#

Monochrome UI Components, provided by the @streetscape.gl/monochrome library, are the building blocks for the user interface controls in streetscape.gl. They offer a collection of common, stateless, and highly stylable React components like buttons, sliders, checkboxes, and components for displaying data in charts and tables.

While not directly interacting with the XVIZ Loader themselves (they get their data and state from their parent components), they are used extensively by core streetscape.gl components like PlaybackControl and XVIZPanel to render interactive UI elements. Understanding that these components come from a separate, reusable UI library helps clarify the architecture of a streetscape.gl application and shows how you can customize the look and feel using Monochrome's styling and theming capabilities.

With this chapter, we've covered the major concepts and components introduced in this streetscape.gl tutorial, from loading the data to visualizing it in 3D, controlling playback, displaying auxiliary data, connecting components to the data source, and finally, understanding the UI building blocks themselves.

You now have a foundational understanding of how to build applications that load and visualize autonomous vehicle log data using streetscape.gl.


Generated by AI Codebase Knowledge Builder. References: 1(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/docs/api-reference/styling-guide.md), 2(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/docs/get-started/styling.md), 3(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/README.md), 4(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/drag-drop-list/README.md), 5(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/float-panel/README.md), 6(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/form/README.md), 7(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/index.js), 8(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/metric-card/README.md), 9(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/playback-control/README.md), 10(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/shared/button/README.md), 11(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/shared/checkbox/README.md), 12(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/shared/dropdown/README.md), 13(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/shared/label/README.md), 14(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/shared/radio-box/README.md), 15(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/shared/slider/README.md), 16(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/shared/text-box/README.md), 17(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/shared/toggle/README.md), 18(https://github.com/aurora-opensource/streetscape.gl/blob/befae1354ca8605c9f6cb1229b494858a8690e4f/modules/monochrome/src/table/README.md)