leptos_chartistry/series/line/
mod.rs1mod interpolation;
2mod marker;
3pub use interpolation::{Interpolation, Step};
4pub use marker::{Marker, MarkerShape};
5
6use super::{ApplyUseSeries, IntoUseLine, SeriesAcc, UseData, UseY};
7use crate::{
8 colours::{Colour, DivergingGradient, LinearGradientSvg, SequentialGradient, BERLIN, LIPARI},
9 series::GetYValue,
10 ColourScheme, Tick,
11};
12use leptos::prelude::*;
13use std::sync::Arc;
14
15pub const LINEAR_GRADIENT: SequentialGradient = LIPARI;
17
18pub const DIVERGING_GRADIENT: DivergingGradient = BERLIN;
20
21#[non_exhaustive]
45pub struct Line<T, Y> {
46 get_y: Arc<dyn GetYValue<T, Y>>,
47 pub name: RwSignal<String>,
49 pub colour: RwSignal<Option<Colour>>,
51 pub gradient: RwSignal<Option<ColourScheme>>,
53 pub width: RwSignal<f64>,
55 pub interpolation: RwSignal<Interpolation>,
57 pub marker: Marker,
59}
60
61#[derive(Clone, Debug, PartialEq)]
62pub struct UseLine {
63 colour: Signal<Colour>,
64 gradient: RwSignal<Option<ColourScheme>>,
65 width: RwSignal<f64>,
66 interpolation: RwSignal<Interpolation>,
67 marker: Marker,
68}
69
70impl<T, Y> Line<T, Y> {
71 pub fn new(get_y: impl Fn(&T) -> Y + Send + Sync + 'static) -> Self
75 where
76 Y: Tick,
77 {
78 Self {
79 get_y: Arc::new(get_y),
80 name: RwSignal::default(),
81 colour: RwSignal::default(),
82 gradient: RwSignal::default(),
83 width: RwSignal::new(1.0),
84 interpolation: RwSignal::default(),
85 marker: Marker::default(),
86 }
87 }
88
89 pub fn with_name(self, name: impl Into<String>) -> Self {
91 self.name.set(name.into());
92 self
93 }
94
95 pub fn with_colour(self, colour: impl Into<Option<Colour>>) -> Self {
97 self.colour.set(colour.into());
98 self
99 }
100
101 pub fn with_gradient(self, scheme: impl Into<ColourScheme>) -> Self {
105 self.gradient.set(Some(scheme.into()));
106 self
107 }
108
109 pub fn with_width(self, width: impl Into<f64>) -> Self {
111 self.width.set(width.into());
112 self
113 }
114
115 pub fn with_interpolation(self, interpolation: impl Into<Interpolation>) -> Self {
117 self.interpolation.set(interpolation.into());
118 self
119 }
120
121 pub fn with_marker(mut self, marker: impl Into<Marker>) -> Self {
123 self.marker = marker.into();
124 self
125 }
126}
127
128impl<T, Y> Clone for Line<T, Y> {
129 fn clone(&self) -> Self {
130 Self {
131 get_y: self.get_y.clone(),
132 name: self.name,
133 colour: self.colour,
134 gradient: self.gradient,
135 width: self.width,
136 interpolation: self.interpolation,
137 marker: self.marker.clone(),
138 }
139 }
140}
141
142impl<T, Y: Tick, F: Fn(&T) -> Y + Send + Sync + 'static> From<F> for Line<T, Y> {
143 fn from(f: F) -> Self {
144 Self::new(f)
145 }
146}
147
148impl<T, Y: Tick, U: Fn(&T) -> Y + Send + Sync> GetYValue<T, Y> for U {
149 fn value(&self, t: &T) -> Y {
150 self(t)
151 }
152
153 fn stacked_value(&self, t: &T) -> Y {
154 self(t)
155 }
156}
157
158impl<T, Y> ApplyUseSeries<T, Y> for Line<T, Y> {
159 fn apply_use_series(self: Arc<Self>, series: &mut SeriesAcc<T, Y>) {
160 let colour = series.next_colour();
161 _ = series.push_line(colour, (*self).clone());
162 }
163}
164
165impl<T, Y> IntoUseLine<T, Y> for Line<T, Y> {
166 fn into_use_line(self, id: usize, colour: Memo<Colour>) -> (UseY, Arc<dyn GetYValue<T, Y>>) {
167 let override_colour = self.colour;
168 let colour = Signal::derive(move || override_colour.get().unwrap_or(colour.get()));
169 let line = UseY::new_line(
170 id,
171 self.name,
172 UseLine {
173 colour,
174 gradient: self.gradient,
175 width: self.width,
176 interpolation: self.interpolation,
177 marker: self.marker.clone(),
178 },
179 );
180 (line, self.get_y.clone())
181 }
182}
183
184#[component]
185pub fn RenderLine<X: Tick, Y: Tick>(
186 use_y: UseY,
187 line: UseLine,
188 data: UseData<X, Y>,
189 positions: Signal<Vec<(f64, f64)>>,
190 markers: Signal<Vec<(f64, f64)>>,
191) -> impl IntoView {
192 let path = move || positions.with(|positions| line.interpolation.get().path(positions));
193
194 let gradient_id = format!("line_{}_gradient", use_y.id);
196 let stroke = {
197 let colour = line.colour;
198 let gradient_id = gradient_id.clone();
199 Signal::derive(move || {
200 if line.gradient.get().is_some() {
202 format!("url(#{gradient_id})")
203 } else {
204 colour.get().to_string()
205 }
206 })
207 };
208 let gradient = Signal::derive(move || {
209 line.gradient
210 .get()
211 .unwrap_or_else(|| LINEAR_GRADIENT.into())
212 });
213 let range_y = Signal::derive(move || data.range_y.read().positions());
214
215 let width = line.width;
216 view! {
217 <g
218 class="_chartistry_line"
219 stroke=stroke
220 stroke-linecap="round"
221 stroke-linejoin="bevel"
222 stroke-width=width>
223 <defs>
224 <Show when=move || line.gradient.get().is_some()>
225 <LinearGradientSvg
226 id=gradient_id.clone()
227 scheme=gradient
228 range_y=range_y />
229 </Show>
230 </defs>
231 <path d=path fill="none" />
232 <marker::LineMarkers line=line positions=markers />
233 </g>
234 }
235 .into_any()
236}