View state

Retain viewport, selection, and replay state in the application.

A bounded numeric viewport

Viewport is a host-owned value, not a chart entity or gesture controller. Create it with finite increasing X/Y domains and handle InvalidViewport. Initial domains are also the default limits; supply wider limits when the initial view should be able to pan.

Rust
use gpui::Context;
use xgpui_luma_plot::{
    CartesianChartBuilder, CartesianChartView, Dataset, InvalidViewport,
    LineLayer, Viewport,
};

struct ChartState {
    viewport: Viewport,
}

impl ChartState {
    fn new() -> Result<Self, InvalidViewport> {
        let viewport = Viewport::new(20.0..=40.0, 0.0..=100.0)?
            .limits(0.0..=100.0, 0.0..=100.0)?;
        Ok(Self { viewport })
    }

    fn chart(&self, data: Dataset) -> CartesianChartView {
        CartesianChartBuilder::new(data)
            .id("viewport-chart")
            .line(LineLayer::new("signal"))
            .viewport(&self.viewport)
            .build()
    }

    fn pan_right(&mut self, cx: &mut Context<Self>) {
        if self.viewport.pan(5.0, 0.0).is_some() {
            cx.notify();
        }
    }

    fn zoom_in(&mut self, cx: &mut Context<Self>) {
        if self.viewport.zoom_at(2.0, 0.5, 0.5).is_some() {
            cx.notify();
        }
    }

    fn reset(&mut self, cx: &mut Context<Self>) {
        if self.viewport.reset().is_some() {
            cx.notify();
        }
    }
}

Instantiate ChartState in your owning entity constructor, handle its result, and supply a dataset containing signal. Wire these methods to your Look’s buttons or application input handlers. Render the built chart inside a sized host.

Pan, zoom, and reset

  • .pan(dx, dy) uses data units and clamps to limits.
  • .zoom_at(factor, anchor_x, anchor_y) uses fractions measured from each domain’s low end. A factor above one zooms in; below one zooms out.
  • .reset() restores the initial domains, not the configured outer limits.

Operations return Option<ViewportChange> with X/Y domains and a Pan, Zoom, or Reset action. Unchanged or invalid operations produce no change. Limit configuration must include the initial domains and have finite positive spans.

Connect input explicitly

.viewport(&viewport) applies numeric domains while preserving mapping and ticks. Category axes remain unchanged. It does not install drag, wheel, or keyboard handlers.

Translate pointer deltas and anchor positions using the actual plot bounds, excluding axis gutters. Screen Y increases downward, while numeric Y normally increases upward; account for orientation when converting coordinates. Avoid interpreting anchor fractions as raw data coordinates. For logarithmic axes, the Viewport arithmetic remains in data units; it is not a logarithmic gesture model.

Choose application focus and wheel-routing policies explicitly. Update host state and notify after an accepted change; rebuild the chart with its new viewport during rendering.

Selection, visibility, and animation

Store persistent selected source identities and hidden-series choices separately from viewport domains. Observation and legend events request host updates; they do not change data or retain selection automatically. See Interaction.

.draw_progress(...) is a host-driven reveal value from 0–1, not a playback timer. Cartesian line/area reveal, pie sectors, radar growth, and radial arcs use their family’s animation behavior. Drive progress from application time, schedule redraws while active, and stop requesting frames when settled.

Chart IDs should remain stable through redraws. Recreating a chart element is normal; recreating your retained data, viewport, or callback owner on every render is unnecessary.

Source

Viewport operations · Native scrolling example · Native replay example