leptos_chartistry/inner/
guide_line.rs1use crate::{bounds::Bounds, colours::Colour, debug::DebugRect, state::State, Tick};
2use leptos::prelude::*;
3use std::str::FromStr;
4
5pub const GUIDE_LINE_COLOUR: Colour = Colour::from_rgb(0x9A, 0x9A, 0x9A);
7
8macro_rules! impl_guide_line {
9 ($name:ident) => {
10 #[derive(Clone, Debug, PartialEq)]
12 #[non_exhaustive]
13 pub struct $name {
14 pub align: RwSignal<AlignOver>,
16 pub width: RwSignal<f64>,
18 pub colour: RwSignal<Colour>,
20 }
21
22 impl $name {
23 fn new(align: AlignOver) -> Self {
24 Self {
25 align: RwSignal::new(align.into()),
26 width: RwSignal::new(1.0),
27 colour: RwSignal::new(GUIDE_LINE_COLOUR),
28 }
29 }
30
31 pub fn over_mouse() -> Self {
33 Self::new(AlignOver::Mouse)
34 }
35
36 pub fn over_data() -> Self {
38 Self::new(AlignOver::Data)
39 }
40
41 pub fn with_colour(self, colour: impl Into<Colour>) -> Self {
43 self.colour.set(colour.into());
44 self
45 }
46 }
47
48 impl Default for $name {
49 fn default() -> Self {
50 Self::new(AlignOver::default())
51 }
52 }
53 };
54}
55
56impl_guide_line!(XGuideLine);
57impl_guide_line!(YGuideLine);
58
59#[derive(Copy, Clone, Debug, Default, PartialEq)]
61#[non_exhaustive]
62pub enum AlignOver {
63 #[default]
65 Mouse,
66 Data,
68}
69
70#[derive(Clone)]
71pub struct UseXGuideLine(XGuideLine);
72
73#[derive(Clone)]
74pub struct UseYGuideLine(YGuideLine);
75
76impl XGuideLine {
77 pub(crate) fn use_horizontal(self) -> UseXGuideLine {
78 UseXGuideLine(self)
79 }
80}
81
82impl YGuideLine {
83 pub(crate) fn use_vertical(self) -> UseYGuideLine {
84 UseYGuideLine(self)
85 }
86}
87
88impl std::fmt::Display for AlignOver {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 match self {
91 AlignOver::Mouse => write!(f, "mouse"),
92 AlignOver::Data => write!(f, "data"),
93 }
94 }
95}
96
97impl FromStr for AlignOver {
98 type Err = String;
99 fn from_str(s: &str) -> Result<Self, Self::Err> {
100 match s {
101 "mouse" => Ok(AlignOver::Mouse),
102 "data" => Ok(AlignOver::Data),
103 _ => Err(format!("invalid align over: `{}`", s)),
104 }
105 }
106}
107
108#[component]
109pub(super) fn XGuideLine<X: Tick, Y: Tick>(
110 line: UseXGuideLine,
111 state: State<X, Y>,
112) -> impl IntoView {
113 let line = line.0;
114 let inner = state.layout.inner;
115 let mouse_chart = state.mouse_chart;
116
117 let nearest_pos_x = state.pre.data.nearest_position_x(state.hover_position_x);
119 let nearest_svg_x = Memo::new(move |_| {
120 nearest_pos_x
121 .get()
122 .map(|pos_x| state.projection.get().position_to_svg(pos_x, 0.0).0)
123 });
124
125 let pos = Signal::derive(move || {
126 let (mouse_x, _) = mouse_chart.get();
127 let x = match line.align.get() {
128 AlignOver::Data => nearest_svg_x.get().unwrap_or(mouse_x),
129 AlignOver::Mouse => mouse_x,
130 };
131 let inner = inner.get();
132 Bounds::from_points(x, inner.top_y(), x, inner.bottom_y())
133 });
134
135 view! {
136 <GuideLine id="x" width=line.width colour=line.colour state=state pos=pos />
137 }
138}
139
140#[component]
141pub(super) fn YGuideLine<X: Tick, Y: Tick>(
142 line: UseYGuideLine,
143 state: State<X, Y>,
144) -> impl IntoView {
145 let line = line.0;
146 let inner = state.layout.inner;
147 let mouse_chart = state.mouse_chart;
148 let pos = Signal::derive(move || {
150 let (_, mouse_y) = mouse_chart.get();
151 let inner = inner.get();
152 Bounds::from_points(inner.left_x(), mouse_y, inner.right_x(), mouse_y)
153 });
154 view! {
155 <GuideLine id="y" width=line.width colour=line.colour state=state pos=pos />
156 }
157}
158
159#[component]
160fn GuideLine<X: Tick, Y: Tick>(
161 id: &'static str,
162 width: RwSignal<f64>,
163 colour: RwSignal<Colour>,
164 state: State<X, Y>,
165 pos: Signal<Bounds>,
166) -> impl IntoView {
167 let debug = state.pre.debug;
168 let hover_inner = state.hover_inner;
169
170 let x1 = Memo::new(move |_| pos.get().left_x());
171 let y1 = Memo::new(move |_| pos.get().top_y());
172 let x2 = Memo::new(move |_| pos.get().right_x());
173 let y2 = Memo::new(move |_| pos.get().bottom_y());
174
175 let have_data = Signal::derive(move || {
177 !(x1.get().is_nan() || y1.get().is_nan() || x2.get().is_nan() || y2.get().is_nan())
178 });
179
180 view! {
181 <g
182 class=format!("_chartistry_{}_guide_line", id)
183 stroke=move || colour.get().to_string()
184 stroke-width=width>
185 <Show when=move || hover_inner.get() && have_data.get() >
186 <DebugRect label=format!("{}_guide_line", id) debug=debug />
187 <line
188 x1=x1
189 y1=y1
190 x2=x2
191 y2=y2
192 />
193 </Show>
194 </g>
195 }
196}