Skip to main content

orbital_charts/context/
pointer.rs

1//! Pointer tracking over the plot area.
2
3use leptos::ev::MouseEvent;
4use leptos::prelude::*;
5
6use crate::context::{
7    set_hovered_item, use_chart_context, use_x_scale, use_y_scale, ChartInteractionContext,
8    ChartScale,
9};
10use crate::engine::{category_index_at_x, find_bar_at_pointer, find_nearest_marker};
11use crate::{ChartItemId, ChartOrientation, TooltipTrigger};
12
13/// Transparent overlay capturing pointer events for axis tooltips and crosshair.
14#[component]
15pub fn ChartPointerLayer(
16    #[prop(default = TooltipTrigger::Item)] trigger: TooltipTrigger,
17) -> impl IntoView {
18    let ctx = use_chart_context();
19    let plot_w = ctx.drawing_area.plot_width;
20    let plot_h = ctx.drawing_area.plot_height;
21    let inset_left = ctx.drawing_area.left;
22    let inset_top = ctx.drawing_area.top;
23    let orientation = ctx.orientation;
24
25    let category_scale = match orientation {
26        ChartOrientation::Vertical => use_x_scale("x".to_string()),
27        ChartOrientation::Horizontal => use_y_scale("y".to_string()),
28    };
29
30    let interaction = expect_context::<ChartInteractionContext>();
31    let pointer_events = if matches!(trigger, TooltipTrigger::None) {
32        "none"
33    } else {
34        "all"
35    };
36
37    view! {
38        <rect
39            class="orb-pointer-layer"
40            x=0
41            y=0
42            width=plot_w
43            height=plot_h
44            fill="transparent"
45            style=format!("pointer-events: {pointer_events}; cursor: crosshair;")
46            on:mousemove=move |ev: MouseEvent| {
47                let px = ev.offset_x() as f64;
48                let py = ev.offset_y() as f64;
49                interaction
50                    .pointer_plot
51                    .set(Some((inset_left + px, inset_top + py)));
52
53                let bars = interaction.plot_bars.get_untracked();
54                let markers = interaction.plot_line_markers.get_untracked();
55
56                if matches!(trigger, TooltipTrigger::Axis) {
57                    if let ChartScale::Band(band) = category_scale.clone() {
58                        interaction
59                            .axis_data_index
60                            .set(category_index_at_x(&band, px));
61                    }
62                    if let Some(hit) = find_bar_at_pointer(&bars, px, py) {
63                        set_hovered_item(Some(ChartItemId {
64                            series_id: hit.series_id,
65                            data_index: hit.data_index,
66                        }));
67                    } else if let Some(hit) = find_nearest_marker(px, py, &markers, 12.0) {
68                        set_hovered_item(Some(ChartItemId {
69                            series_id: hit.series_id,
70                            data_index: hit.data_index,
71                        }));
72                    } else {
73                        set_hovered_item(None);
74                    }
75                    return;
76                }
77
78                if let Some(hit) = find_bar_at_pointer(&bars, px, py) {
79                    set_hovered_item(Some(ChartItemId {
80                        series_id: hit.series_id,
81                        data_index: hit.data_index,
82                    }));
83                } else if let Some(hit) = find_nearest_marker(px, py, &markers, 12.0) {
84                    set_hovered_item(Some(ChartItemId {
85                        series_id: hit.series_id,
86                        data_index: hit.data_index,
87                    }));
88                } else {
89                    set_hovered_item(None);
90                }
91                interaction.axis_data_index.set(None);
92            }
93            on:mouseleave=move |_| {
94                interaction.pointer_plot.set(None);
95                interaction.axis_data_index.set(None);
96                set_hovered_item(None);
97            }
98        />
99    }
100}
101
102/// Read-only pointer position in chart shell coordinates.
103pub fn use_pointer_tracker() -> ReadSignal<Option<(f64, f64)>> {
104    expect_context::<ChartInteractionContext>()
105        .pointer_plot
106        .read_only()
107}