leptos_chartistry/inner/
grid_line.rs

1use crate::{
2    colours::Colour, debug::DebugRect, projection::Projection, state::State, ticks::GeneratedTicks,
3    Tick, TickLabels,
4};
5use leptos::prelude::*;
6
7/// Default colour for grid lines.
8pub const GRID_LINE_COLOUR: Colour = Colour::from_rgb(0xEF, 0xF2, 0xFA);
9
10macro_rules! impl_grid_line {
11    ($name:ident) => {
12        /// Builds a tick-aligned grid line across the inner chart area.
13        #[derive(Clone, Debug, PartialEq)]
14        #[non_exhaustive]
15        pub struct $name<XY: Tick> {
16            /// Width of the grid line.
17            pub width: RwSignal<f64>,
18            /// Colour of the grid line.
19            pub colour: RwSignal<Colour>,
20            /// Ticks to align the grid line to.
21            pub ticks: TickLabels<XY>,
22        }
23
24        impl<XY: Tick> $name<XY> {
25            /// Creates a new grid line from a set of ticks.
26            pub fn from_ticks(ticks: impl Into<TickLabels<XY>>) -> Self {
27                Self {
28                    ticks: ticks.into(),
29                    ..Default::default()
30                }
31            }
32
33            /// Sets the colour of the grid line.
34            pub fn with_colour(self, colour: impl Into<Colour>) -> Self {
35                self.colour.set(colour.into());
36                self
37            }
38        }
39
40        impl<XY: Tick> Default for $name<XY> {
41            fn default() -> Self {
42                Self {
43                    width: RwSignal::new(1.0),
44                    colour: RwSignal::new(GRID_LINE_COLOUR),
45                    ticks: TickLabels::default(),
46                }
47            }
48        }
49    };
50}
51
52impl_grid_line!(XGridLine);
53impl_grid_line!(YGridLine);
54
55macro_rules! impl_use_grid_line {
56    ($name:ident) => {
57        pub struct $name<XY: Tick> {
58            width: RwSignal<f64>,
59            colour: RwSignal<Colour>,
60            ticks: Memo<GeneratedTicks<XY>>,
61        }
62
63        impl<XY: Tick> Clone for $name<XY> {
64            fn clone(&self) -> Self {
65                Self {
66                    width: self.width,
67                    colour: self.colour,
68                    ticks: self.ticks,
69                }
70            }
71        }
72    };
73}
74
75impl_use_grid_line!(UseXGridLine);
76impl_use_grid_line!(UseYGridLine);
77
78impl<X: Tick> XGridLine<X> {
79    pub(crate) fn use_horizontal<Y: Tick>(self, state: &State<X, Y>) -> UseXGridLine<X> {
80        let inner = state.layout.inner;
81        let avail_width = Signal::derive(move || inner.with(|inner| inner.width()));
82        UseXGridLine {
83            width: self.width,
84            colour: self.colour,
85            ticks: self.ticks.generate_x(&state.pre, avail_width),
86        }
87    }
88}
89
90impl<Y: Tick> YGridLine<Y> {
91    pub(crate) fn use_vertical<X: Tick>(self, state: &State<X, Y>) -> UseYGridLine<Y> {
92        let inner = state.layout.inner;
93        let avail_height = Signal::derive(move || inner.with(|inner| inner.height()));
94        UseYGridLine {
95            width: self.width,
96            colour: self.colour,
97            ticks: self.ticks.generate_y(&state.pre, avail_height),
98        }
99    }
100}
101
102#[component]
103pub(super) fn XGridLine<X: Tick, Y: Tick>(
104    line: UseXGridLine<X>,
105    state: State<X, Y>,
106) -> impl IntoView {
107    let debug = state.pre.debug;
108    let inner = state.layout.inner;
109    let proj = state.projection;
110    let colour = line.colour;
111
112    let lines = move || {
113        for_ticks(line.ticks, proj, true)
114            .into_iter()
115            .map(|(x, label)| {
116                view! {
117                    <DebugRect label=format!("grid_line_x/{}", label) debug=debug />
118                    <line
119                        x1=x
120                        y1=move || inner.get().top_y()
121                        x2=x
122                        y2=move || inner.get().bottom_y() />
123                }
124            })
125            .collect_view()
126    };
127
128    view! {
129        <g
130            class="_chartistry_grid_line_x"
131            stroke=move || colour.get().to_string()
132            stroke-width=line.width>
133            <DebugRect label="grid_line_x" debug=debug />
134            {lines}
135        </g>
136    }
137}
138
139#[component]
140pub(super) fn YGridLine<X: Tick, Y: Tick>(
141    line: UseYGridLine<Y>,
142    state: State<X, Y>,
143) -> impl IntoView {
144    let debug = state.pre.debug;
145    let inner = state.layout.inner;
146    let proj = state.projection;
147    let colour = line.colour;
148
149    let lines = move || {
150        for_ticks(line.ticks, proj, false)
151            .into_iter()
152            .map(|(y, label)| {
153                view! {
154                    <DebugRect label=format!("grid_line_y/{}", label) debug=debug />
155                    <line
156                        x1=move || inner.get().left_x()
157                        y1=y
158                        x2=move || inner.get().right_x()
159                        y2=y />
160                }
161            })
162            .collect_view()
163    };
164
165    view! {
166        <g
167            class="_chartistry_grid_line_y"
168            stroke=move || colour.get().to_string()
169            stroke-width=line.width>
170            <DebugRect label="grid_line_y" debug=debug />
171            {lines}
172        </g>
173    }
174}
175
176fn for_ticks<XY: Tick>(
177    ticks: Memo<GeneratedTicks<XY>>,
178    proj: Memo<Projection>,
179    is_x: bool,
180) -> Vec<(f64, String)> {
181    ticks.with(move |ticks| {
182        let proj = proj.get();
183        ticks
184            .ticks
185            .iter()
186            .map(|tick| {
187                let label = ticks.state.format(tick);
188                let tick = tick.position();
189                let tick = if is_x {
190                    proj.position_to_svg(tick, 0.0).0
191                } else {
192                    proj.position_to_svg(0.0, tick).1
193                };
194                (tick, label)
195            })
196            .collect::<Vec<_>>()
197    })
198}