GPUI Box GitHub

Components / display

CartesianChart builder

Shared-coordinate line/area/bar/scatter layers over raw f64 observations. Missing samples break paths. All-zero data is ready, not empty. An invalid dataset is an explicit error. Stale data remains visible with its reason.

A builder is RenderOnce. Construct and mount it in one expression.

use gpui_kit::display::chart::cartesian::CartesianChart;

Reviewed in cartesian cartesian-linked cartesian-layout cartesian-lifecycle cartesian-dense cartesian-states raw-candlestick.

Construct
new(ident: impl Into<Ident>, label: impl Into<SharedString>, x: ChartScale, axes: impl IntoIterator<Item = ValueAxis>) -> Self
Options

Chain onto the value.

series(series: impl IntoIterator<Item = RawSeries>) -> Self
shared_series(series: Rc<Vec<RawSeries>>) -> Self
animate(enabled: bool) -> Self
motion(motion: CartesianMotion) -> Self
sampling(sampling: PathSampling) -> Self
custom_marks(build: impl Fn(&RawSeries, &RawPoint) -> Option<CustomMark> + 'static) -> Self
orientation(orientation: ChartOrientation) -> Self
x_axis_side(side: AxisSide) -> Self
axis_side(axis: impl Into<SharedString>, side: AxisSide) -> Result<Self, DataError>
state(state: impl HasPhase) -> Self
hidden(ids: impl IntoIterator<Item = impl Into<SharedString>>) -> Self
hovered(point: Option<ChartSelection>) -> Self
selected(point: Option<ChartSelection>) -> Self
emphasized(series: Option<impl Into<SharedString>>) -> Self
tooltip(mode: TooltipMode) -> Self
tooltip_content(build: impl Fn(&ChartTooltipData, &mut Window, &mut App) -> gpui::AnyElement + 'static) -> Self
floating_tooltip(visible: bool) -> Self
range(range: CartesianRange) -> Self
references(references: impl IntoIterator<Item = ChartReference>) -> Self
stale(reason: impl Into<SharedString>) -> Self
height(height: f32) -> Self
format_ticks(format: impl Fn(&str, f64) -> SharedString + 'static) -> Self
x_ticks(ticks: impl IntoIterator<Item = ChartTick>) -> Result<Self, TickError>
axis_ticks(axis: impl Into<SharedString>, ticks: impl IntoIterator<Item = ChartTick>) -> Result<Self, TickError>
on_event(handler: impl Fn(CartesianEvent, &mut Window, &mut App) + 'static) -> Self
Example · cartesian
fn cartesian(_window: &mut Window, cx: &mut App) -> AnyElement {
    let theme = cx.theme().clone();
    let scale =
        |domain| NumericScale::new(ScaleKind::Linear, domain).expect("finite fixture domain");
    let axis = |id: &str, label: &str, domain| ValueAxis {
        id: id.to_string().into(),
        label: label.to_string().into(),
        scale: scale(domain),
    };
    let points = |values: &[Option<f64>]| {
        [
            ("intake", 0.),
            ("validation", 1.),
            ("routing", 2.),
            ("processing", 3.),
            ("delivery", 4.),
        ]
        .into_iter()
        .zip(values)
        .map(|((id, x), y)| RawPoint::new(id, ChartValue::Number(x), *y))
        .collect::<Vec<_>>()
    };
    let mixed = CartesianChart::new(
        "scene.cartesian.mixed",
        "Mixed units · error intervals",
        ChartScale::Numeric(scale([-0.5, 4.5])),
        [
            axis("count", "Requests", [-20., 80.]),
            axis("latency", "Latency (ms)", [0., 400.]),
        ],
    )
    .x_ticks(
        [
            (0., "Intake"),
            (1., "Validate"),
            (2., "Route"),
            (3., "Process"),
            (4., "Deliver"),
        ]
        .into_iter()
        .map(|(value, label)| ChartTick {
            value: ChartValue::Number(value),
            label: label.into(),
        }),
    )
    .expect("caller process ticks in domain")
    .series([
        RawSeries::new("requests", "count", SeriesMark::Bar).points(points(&[
            Some(31.),
            Some(-12.),
            Some(64.),
            Some(48.),
            Some(23.),
        ])),
        RawSeries::new("latency", "latency", SeriesMark::Line)
            .curve(Curve::Monotone)
            .points(points(&[
                Some(80.),
                Some(230.),
                Some(160.),
                Some(310.),
                Some(280.),
            ])),
        RawSeries::new("observed", "count", SeriesMark::Scatter).points([RawPoint::new(
            "audit",
            ChartValue::Number(2.2),
            Some(55.),
        )
        .error([44., 69.])
        .text("Audit", "55 verified")]),
    ])
    .references([ChartReference {
        id: "capacity".into(),
        axis: "count".into(),
        range: [60., 70.],
        label: "Capacity band".into(),
        color: None,
    }])
    .height(180.)
    .selected(Some(ChartSelection::new("observed", "audit")))
    .tooltip(TooltipMode::Point);
    let categories = CategoryScale::new(["West district", "East district", "North district"])
        .expect("distinct fixture categories");
    let categorical =
        |id: &str, values: [f64; 3], percent| {
            RawSeries::new(id, "amount", SeriesMark::Bar)
                .stack(if percent {
                    Stack::Percent("group".into())
                } else {
                    Stack::Absolute("group".into())
                })
                .points(categories.categories().iter().zip(values).map(|(x, y)| {
                    RawPoint::new(x.clone(), ChartValue::Category(x.clone()), Some(y))
                }))
        };
    let stacks = CartesianChart::new(
        "scene.cartesian.stacks",
        "Diverging stacks · independent sign totals",
        ChartScale::Category(categories.clone()),
        [axis("amount", "Amount", [-50., 100.])],
    )
    .series([
        categorical("First", [37., -23., 52.], false),
        categorical("Second", [21., -17., 33.], false),
    ])
    .height(180.);
    let gaps = CartesianChart::new(
        "scene.cartesian.gaps",
        "Missing is not zero · retained stale data",
        ChartScale::Numeric(scale([0., 4.])),
        [axis("value", "Reading", [-10., 50.])],
    )
    .series([
        RawSeries::new("area", "value", SeriesMark::Area)
            .curve(Curve::StepAfter)
            .points(points(&[Some(12.), Some(36.), None, Some(-6.), Some(21.)])),
        RawSeries::new("line", "value", SeriesMark::Line)
            .curve(Curve::StepBefore)
            .points(points(&[Some(5.), Some(20.), None, Some(13.), Some(30.)])),
    ])
    .stale("Fixture refresh refused")
    .height(150.);
    let percent = CartesianChart::new(
        "scene.cartesian.percent",
        "Percent stacks · exact raw readout",
        ChartScale::Category(categories.clone()),
        [axis("amount", "Share (%)", [-100., 100.])],
    )
    .series([
        categorical("First", [37., -23., 52.], true),
        categorical("Second", [21., -17., 33.], true),
    ])
    .height(180.)
    .selected(Some(ChartSelection::new("First", "East district")))
    .tooltip(TooltipMode::SharedAxis);
    stack(&theme).w(px(920.)).child(caption(&theme,"Original fixtures · raw f64 data; shared coordinates, category identity and exact readouts"))
        .child(div().row().items_start().gap_token(&theme,Space::Lg).child(div().flex_1().min_w_0().child(mixed)).child(div().flex_1().min_w_0().child(stacks)))
        .child(div().row().items_start().gap_token(&theme,Space::Lg).child(div().flex_1().min_w_0().child(gaps)).child(div().flex_1().min_w_0().child(percent)))
        .into_any_element()
}

Source: crates/gpui-kit/src/display/chart/cartesian.rs