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}