leptos_chartistry/inner/
grid_line.rs1use crate::{
2 colours::Colour, debug::DebugRect, projection::Projection, state::State, ticks::GeneratedTicks,
3 Tick, TickLabels,
4};
5use leptos::prelude::*;
6
7pub const GRID_LINE_COLOUR: Colour = Colour::from_rgb(0xEF, 0xF2, 0xFA);
9
10macro_rules! impl_grid_line {
11 ($name:ident) => {
12 #[derive(Clone, Debug, PartialEq)]
14 #[non_exhaustive]
15 pub struct $name<XY: Tick> {
16 pub width: RwSignal<f64>,
18 pub colour: RwSignal<Colour>,
20 pub ticks: TickLabels<XY>,
22 }
23
24 impl<XY: Tick> $name<XY> {
25 pub fn from_ticks(ticks: impl Into<TickLabels<XY>>) -> Self {
27 Self {
28 ticks: ticks.into(),
29 ..Default::default()
30 }
31 }
32
33 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}