mod interpolation;
mod marker;
pub use interpolation::{Interpolation, Step};
pub use marker::{Marker, MarkerShape};
use super::{ApplyUseSeries, IntoUseLine, SeriesAcc, UseData, UseY};
use crate::{
colours::{Colour, DivergingGradient, LinearGradientSvg, SequentialGradient, BERLIN, LIPARI},
series::GetYValue,
ColourScheme, Tick,
};
use leptos::prelude::*;
use std::sync::Arc;
pub const LINEAR_GRADIENT: SequentialGradient = LIPARI;
pub const DIVERGING_GRADIENT: DivergingGradient = BERLIN;
#[non_exhaustive]
pub struct Line<T, Y> {
get_y: Arc<dyn GetYValue<T, Y>>,
pub name: RwSignal<String>,
pub colour: RwSignal<Option<Colour>>,
pub gradient: RwSignal<Option<ColourScheme>>,
pub width: RwSignal<f64>,
pub interpolation: RwSignal<Interpolation>,
pub marker: Marker,
}
#[derive(Clone, Debug, PartialEq)]
pub struct UseLine {
colour: Signal<Colour>,
gradient: RwSignal<Option<ColourScheme>>,
width: RwSignal<f64>,
interpolation: RwSignal<Interpolation>,
marker: Marker,
}
impl<T, Y> Line<T, Y> {
pub fn new(get_y: impl Fn(&T) -> Y + Send + Sync + 'static) -> Self
where
Y: Tick,
{
Self {
get_y: Arc::new(get_y),
name: RwSignal::default(),
colour: RwSignal::default(),
gradient: RwSignal::default(),
width: RwSignal::new(1.0),
interpolation: RwSignal::default(),
marker: Marker::default(),
}
}
pub fn with_name(self, name: impl Into<String>) -> Self {
self.name.set(name.into());
self
}
pub fn with_colour(self, colour: impl Into<Option<Colour>>) -> Self {
self.colour.set(colour.into());
self
}
pub fn with_gradient(self, scheme: impl Into<ColourScheme>) -> Self {
self.gradient.set(Some(scheme.into()));
self
}
pub fn with_width(self, width: impl Into<f64>) -> Self {
self.width.set(width.into());
self
}
pub fn with_interpolation(self, interpolation: impl Into<Interpolation>) -> Self {
self.interpolation.set(interpolation.into());
self
}
pub fn with_marker(mut self, marker: impl Into<Marker>) -> Self {
self.marker = marker.into();
self
}
}
impl<T, Y> Clone for Line<T, Y> {
fn clone(&self) -> Self {
Self {
get_y: self.get_y.clone(),
name: self.name,
colour: self.colour,
gradient: self.gradient,
width: self.width,
interpolation: self.interpolation,
marker: self.marker.clone(),
}
}
}
impl<T, Y: Tick, F: Fn(&T) -> Y + Send + Sync + 'static> From<F> for Line<T, Y> {
fn from(f: F) -> Self {
Self::new(f)
}
}
impl<T, Y: Tick, U: Fn(&T) -> Y + Send + Sync> GetYValue<T, Y> for U {
fn value(&self, t: &T) -> Y {
self(t)
}
fn stacked_value(&self, t: &T) -> Y {
self(t)
}
}
impl<T, Y> ApplyUseSeries<T, Y> for Line<T, Y> {
fn apply_use_series(self: Arc<Self>, series: &mut SeriesAcc<T, Y>) {
let colour = series.next_colour();
_ = series.push_line(colour, (*self).clone());
}
}
impl<T, Y> IntoUseLine<T, Y> for Line<T, Y> {
fn into_use_line(self, id: usize, colour: Memo<Colour>) -> (UseY, Arc<dyn GetYValue<T, Y>>) {
let override_colour = self.colour;
let colour = Signal::derive(move || override_colour.get().unwrap_or(colour.get()));
let line = UseY::new_line(
id,
self.name,
UseLine {
colour,
gradient: self.gradient,
width: self.width,
interpolation: self.interpolation,
marker: self.marker.clone(),
},
);
(line, self.get_y.clone())
}
}
#[component]
pub fn RenderLine<X: Tick, Y: Tick>(
use_y: UseY,
line: UseLine,
data: UseData<X, Y>,
positions: Signal<Vec<(f64, f64)>>,
markers: Signal<Vec<(f64, f64)>>,
) -> impl IntoView {
let path = move || positions.with(|positions| line.interpolation.get().path(positions));
let gradient_id = format!("line_{}_gradient", use_y.id);
let stroke = {
let colour = line.colour;
let gradient_id = gradient_id.clone();
Signal::derive(move || {
if line.gradient.get().is_some() {
format!("url(#{gradient_id})")
} else {
colour.get().to_string()
}
})
};
let gradient = Signal::derive(move || {
line.gradient
.get()
.unwrap_or_else(|| LINEAR_GRADIENT.into())
});
let range_y = Signal::derive(move || data.range_y.read().positions());
let width = line.width;
view! {
<g
class="_chartistry_line"
stroke=stroke
stroke-linecap="round"
stroke-linejoin="bevel"
stroke-width=width>
<defs>
<Show when=move || line.gradient.get().is_some()>
<LinearGradientSvg
id=gradient_id.clone()
scheme=gradient
range_y=range_y />
</Show>
</defs>
<path d=path fill="none" />
<marker::LineMarkers line=line positions=markers />
</g>
}
.into_any()
}