1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! Zoom and pan preview demos.
use *;
use component_doc;
use crate::;
/// Build a monthly x-axis with zoom enabled for preview examples.
/// Zoom and pan let users focus on part of a dense chart — scroll to zoom, drag to pan, pinch on touch devices.
///
/// Turn it on per axis with `ZoomConfig`, constrain span with `min_span`, and optionally
/// sync companion axes with `filter_mode` when the vertical axis should follow the visible horizontal range.
///
/// # When to use
///
/// - Long time series where the default view is too dense to read.
/// - Exploration dashboards where users drill into a date range before taking action.
/// - Line and bar charts with `ChartFeatures::ZOOM_PAN` enabled on the chart root.
///
/// # Usage
///
/// 1. Set `zoom` config on the axis definition (`enabled: true`, optional `min_span`).
/// 2. Enable `features=ChartFeatures::ZOOM_PAN` on the chart component.
/// 3. Hold zoom state in an `RwSignal<Vec<ZoomWindow>>` for controlled mode and reset buttons.
/// 4. Wire `on_zoom_change` when parent state must persist across re-renders.
///
/// # Best Practices
///
/// ## Do's
///
/// * Provide a reset control when users can zoom far enough to lose context.
/// * Keep `min_span` wide enough that ticks remain legible.
/// * Test pinch and scroll on trackpads and touch devices separately.
///
/// ## Don'ts
///
/// * Do not enable zoom on sparklines or gauges — it applies to cartesian charts only.
/// * Do not assume keyboard range selection; gesture zoom is the supported path today.
///
/// # Accessibility
///
/// - Chart root: `role="img"`, zoom-aware `aria-label`, `Escape` dismisses tooltips.
/// - Reference lines: `aria-hidden` with SVG `<title>` for labels.
/// - Zoom is gesture-only; keyboard range selection deferred per CH-24.
/// - Gauge meter roles unchanged from Phase 4.
///
/// # Examples
///
/// ## Zoom and pan on line and bar charts
/// Scroll wheel zooms the monthly x-axis; drag pans; Reset restores the full domain.
/// Controlled `zoom` signal is shared between line and bar so both stay in sync.
/// <!-- preview -->
/// ```rust,ignore
/// use leptos::prelude::*;
/// use orbital_core_components::Button;
/// use crate::preview::fixtures::{full_grid, monthly_revenue_series, monthly_y_axis};
/// use crate::{BarChart, ChartFeatures, LineChart, ZoomWindow};
/// let zoom = RwSignal::new(vec![ZoomWindow::full("x")]);
/// let x_axis = zoomable_monthly_x_axis();
/// view! {
/// <div data-testid="charts-zoom-pan-preview" style="display:flex;flex-direction:column;gap:1rem;">
/// <div style="display:flex;gap:0.5rem;align-items:center;">
/// <Button
/// on_click=Callback::new(move |_| {
/// zoom.set(vec![ZoomWindow::full("x")]);
/// })
/// >
/// "Reset zoom"
/// </Button>
/// </div>
/// {move || {
/// let windows = zoom.get();
/// view! {
/// <LineChart
/// series=vec![monthly_revenue_series()]
/// x_axis=vec![x_axis.clone()]
/// y_axis=vec![monthly_y_axis()]
/// grid=full_grid()
/// features=ChartFeatures::ZOOM_PAN | ChartFeatures::ANIMATION
/// zoom=windows.clone()
/// on_zoom_change=Callback::new(move |(w,)| zoom.set(w))
/// width=640.0
/// height=280.0
/// />
/// <BarChart
/// series=vec![monthly_revenue_series()]
/// x_axis=vec![x_axis.clone()]
/// y_axis=vec![monthly_y_axis()]
/// grid=full_grid()
/// features=ChartFeatures::ZOOM_PAN | ChartFeatures::ANIMATION
/// zoom=windows
/// on_zoom_change=Callback::new(move |(w,)| zoom.set(w))
/// width=640.0
/// height=280.0
/// />
/// }
/// }}
/// </div>
/// }
/// ```