Skip to main content

orbital_charts/shared/
chart_composition.rs

1//! Composition mode preview — mixed charts and custom SVG layers.
2
3use leptos::prelude::*;
4use orbital_macros::component_doc;
5
6#[cfg(feature = "preview")]
7use crate::preview::fixtures::{
8    full_grid, mixed_bar_line_series, mixed_bar_line_x_axis, revenue_y_axis,
9};
10use crate::shared::{BarPlot, ChartContainer, ChartCustomBaseline, LinePlot, PlotClip};
11
12/// Wrap your data definitions in a chart container, then stack plot, axis, and interaction components as children.
13///
14/// Explicit `chart_type` on each [`SeriesDef`] tells plot layers which geometry to render;
15/// child order controls z-order (bars beneath lines, annotations on top).
16///
17/// # When to use
18///
19/// Build mixed-type dashboards by placing explicit plot children inside
20/// [`ChartContainer`]. Each [`SeriesDef`] must declare `chart_type`; child
21/// order controls z-order.
22///
23/// # Usage
24///
25/// 1. Place [`ChartContainer`] at the root with series, axes, and optional `dataset` binding.
26/// 2. Add plot children (`BarPlot`, `LinePlot`, …) with required `chart_type` on each series.
27/// 3. Wrap plots in [`PlotClip`] when marks extend past plot bounds.
28/// 4. Add [`ChartCustomBaseline`] or custom SVG using scale hooks for annotations.
29///
30/// # Best Practices
31///
32/// ## Do's
33///
34/// * Place [`BarPlot`] before [`LinePlot`] when bars should sit beneath lines.
35/// * Use unique clip path ids per chart instance (`orb-clip-*` prefix).
36/// * Prefer [`ResponsiveChartContainer`] for dashboard tiles that fill parent width.
37/// * Respect [`ChartCompositionOrder`]: first plot child inside [`PlotClip`] renders on the bottom.
38///
39/// ## Don'ts
40///
41/// * Do not omit `chart_type` on series in composition mode — inference is disabled.
42/// * Do not mix composition and convenience `*Chart` wrappers on the same surface.
43/// * Do not place legend or tooltip as composition children — configure them via container props.
44///
45/// # Examples
46///
47/// ## Mixed bar and line chart
48/// Revenue bars with a target line overlay, clipped to the plot area. Demonstrates z-order,
49/// mixed `chart_type` series, and [`PlotClip`] for overflow control.
50/// <!-- preview -->
51/// ```rust,ignore
52/// use crate::ChartComposition;
53/// use crate::preview::fixtures::{full_grid, mixed_bar_line_series, mixed_bar_line_x_axis, revenue_y_axis};
54/// view! {
55///     <div data-testid="chart-composition-preview">
56///         <ChartComposition
57///             variant=ChartCompositionVariant::MixedBarLine
58///             series=mixed_bar_line_series()
59///             x_axis=vec![mixed_bar_line_x_axis()]
60///             y_axis=vec![revenue_y_axis()]
61///             grid=full_grid()
62///             width=560.0
63///             height=320.0
64///         />
65///     </div>
66/// }
67/// ```
68///
69/// ## Custom SVG layer via hooks
70/// Dashed revenue baseline using [`use_drawing_area`] and [`use_y_scale`]. Use when annotations
71/// must stay aligned to data coordinates on resize.
72/// <!-- preview -->
73/// ```rust,ignore
74/// use crate::ChartComposition;
75/// use crate::preview::fixtures::{quarter_x_axis, revenue_series, revenue_y_axis};
76/// view! {
77///     <div data-testid="chart-composition-custom-layer-preview">
78///         <ChartComposition
79///             variant=ChartCompositionVariant::CustomLayer
80///             series=vec![revenue_series()]
81///             x_axis=vec![quarter_x_axis()]
82///             y_axis=vec![revenue_y_axis()]
83///             width=520.0
84///             height=320.0
85///         />
86///     </div>
87/// }
88/// ```
89#[component_doc(
90    category = "Charts",
91    preview_slug = "chart-composition",
92    preview_label = "Chart Composition",
93    preview_icon = icondata::AiAppstoreOutlined,
94)]
95#[component]
96pub fn ChartComposition(
97    /// Which documented composition example to render.
98    #[prop(default = ChartCompositionVariant::MixedBarLine)]
99    variant: ChartCompositionVariant,
100    /// Series definitions with explicit `chart_type` per entry.
101    #[prop(default = mixed_bar_line_series())]
102    series: Vec<crate::SeriesDef>,
103    /// X-axis definitions.
104    #[prop(default = vec![mixed_bar_line_x_axis()])]
105    x_axis: Vec<crate::AxisDef>,
106    /// Y-axis definitions.
107    #[prop(default = vec![revenue_y_axis()])]
108    y_axis: Vec<crate::AxisDef>,
109    /// Background grid configuration.
110    #[prop(default = full_grid())]
111    grid: crate::GridConfig,
112    /// Chart width in pixels.
113    #[prop(default = 560.0)]
114    width: f64,
115    /// Chart height in pixels.
116    #[prop(default = 320.0)]
117    height: f64,
118    /// Optional CSS class on the root element.
119    #[prop(optional, into)]
120    class: MaybeProp<String>,
121) -> impl IntoView {
122    let _ = &class;
123
124    match variant {
125        ChartCompositionVariant::MixedBarLine => view! {
126            <ChartContainer
127                series=Some(series)
128                x_axis=Some(x_axis)
129                y_axis=Some(y_axis)
130                grid=Some(grid)
131                width=Some(width)
132                height=Some(height)
133            >
134                <PlotClip id="orb-clip-composition".to_string()>
135                    <BarPlot />
136                    <LinePlot />
137                </PlotClip>
138            </ChartContainer>
139        },
140        ChartCompositionVariant::CustomLayer => view! {
141            <ChartContainer
142                series=Some(series)
143                x_axis=Some(x_axis)
144                y_axis=Some(y_axis)
145                width=Some(width)
146                height=Some(height)
147            >
148                <BarPlot />
149                <ChartCustomBaseline />
150            </ChartContainer>
151        },
152    }
153}
154
155/// Composition preview variants.
156#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
157pub enum ChartCompositionVariant {
158    /// Bar + line overlay with clip path.
159    #[default]
160    MixedBarLine,
161    /// Custom SVG layer using chart context hooks.
162    CustomLayer,
163}