use super::UseLine;
use crate::colours::Colour;
use leptos::prelude::*;
const WIDTH_TO_MARKER: f64 = 8.0;
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct Marker {
pub shape: RwSignal<MarkerShape>,
pub colour: RwSignal<Option<Colour>>,
pub scale: RwSignal<f64>,
pub border: RwSignal<Option<Colour>>,
pub border_width: RwSignal<f64>,
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
#[non_exhaustive]
pub enum MarkerShape {
#[default]
None,
Circle,
Square,
Diamond,
Triangle,
Plus,
Cross,
}
impl Default for Marker {
fn default() -> Self {
Self {
shape: RwSignal::default(),
colour: RwSignal::default(),
scale: RwSignal::new(1.0),
border: RwSignal::default(),
border_width: RwSignal::new(0.0),
}
}
}
impl From<MarkerShape> for Marker {
fn from(shape: MarkerShape) -> Self {
Self::from_shape(shape)
}
}
impl Marker {
pub fn from_shape(shape: impl Into<MarkerShape>) -> Self {
Self {
shape: RwSignal::new(shape.into()),
..Default::default()
}
}
pub fn with_colour(self, colour: impl Into<Option<Colour>>) -> Self {
self.colour.set(colour.into());
self
}
pub fn with_scale(self, scale: impl Into<f64>) -> Self {
self.scale.set(scale.into());
self
}
pub fn with_border(self, border: impl Into<Option<Colour>>) -> Self {
self.border.set(border.into());
self
}
pub fn with_border_width(self, border_width: impl Into<f64>) -> Self {
self.border_width.set(border_width.into());
self
}
}
#[component]
pub(super) fn LineMarkers(line: UseLine, positions: Signal<Vec<(f64, f64)>>) -> impl IntoView {
let marker = line.marker.clone();
let border_width = Signal::derive(move || {
if marker.shape.get() == MarkerShape::None {
0.0
} else {
marker.border_width.get()
}
});
let markers = move || {
let shape = marker.shape.get();
let line_width = line.width.get();
let diameter = line_width * WIDTH_TO_MARKER * marker.scale.get();
if shape == MarkerShape::None {
return ().into_any();
};
positions.with(|positions| {
positions
.iter()
.filter(|(x, y)| !(x.is_nan() || y.is_nan()))
.map(|&(x, y)| {
view! {
<MarkerShape
shape=shape
x=x
y=y
diameter=diameter
line_width=line_width />
}
})
.collect_view()
.into_any()
})
};
view! {
<g
fill=move || marker.colour.get().unwrap_or_else(|| line.colour.get()).to_string()
stroke=move || marker.border.get().unwrap_or_else(|| line.colour.get()).to_string()
stroke-width=move || border_width.get() * 2.0 class="_chartistry_line_markers">
{markers}
</g>
}
.into_any()
}
#[component]
fn MarkerShape(
shape: MarkerShape,
x: f64,
y: f64,
diameter: f64,
line_width: f64,
) -> impl IntoView {
let radius = diameter / 2.0;
match shape {
MarkerShape::None => ().into_any(),
MarkerShape::Circle => view! {
<circle
cx=x
cy=y
r=(45.0_f64).to_radians().sin() * radius
paint-order="stroke fill"
/>
}
.into_any(),
MarkerShape::Square => view! {
<Diamond x=x y=y radius=radius rotate=45 />
}
.into_any(),
MarkerShape::Diamond => view! {
<Diamond x=x y=y radius=radius />
}
.into_any(),
MarkerShape::Triangle => view! {
<polygon
points=format!("{},{} {},{} {},{}",
x, y - radius,
x - radius, y + radius,
x + radius, y + radius)
paint-order="stroke fill"/>
}
.into_any(),
MarkerShape::Plus => view! {
<PlusPath x=x y=y diameter=diameter leg=line_width />
}
.into_any(),
MarkerShape::Cross => view! {
<PlusPath x=x y=y diameter=diameter leg=line_width rotate=45 />
}
.into_any(),
}
}
#[component]
fn Diamond(x: f64, y: f64, radius: f64, #[prop(into, optional)] rotate: f64) -> impl IntoView {
view! {
<polygon
transform=format!("rotate({rotate} {x} {y})")
paint-order="stroke fill"
points=format!("{},{} {},{} {},{} {},{}",
x, y - radius,
x - radius, y,
x, y + radius,
x + radius, y) />
}
}
#[component]
fn PlusPath(
x: f64,
y: f64,
diameter: f64,
leg: f64,
#[prop(into, optional)] rotate: f64,
) -> impl IntoView {
let radius = diameter / 2.0;
let half_leg = leg / 2.0;
let to_inner = radius - half_leg;
view! {
<path
transform=format!("rotate({rotate} {x} {y})")
paint-order="stroke fill"
d=format!("M {} {} h {} v {} h {} v {} h {} v {} h {} v {} h {} v {} h {} Z",
x - half_leg, y - radius, leg, to_inner,
to_inner, leg, -to_inner,
to_inner, -leg, -to_inner,
-to_inner, -leg, to_inner) />
}
}
impl std::str::FromStr for MarkerShape {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"none" => Ok(MarkerShape::None),
"circle" => Ok(MarkerShape::Circle),
"triangle" => Ok(MarkerShape::Triangle),
"square" => Ok(MarkerShape::Square),
"diamond" => Ok(MarkerShape::Diamond),
"plus" => Ok(MarkerShape::Plus),
"cross" => Ok(MarkerShape::Cross),
_ => Err("unknown marker"),
}
}
}
impl std::fmt::Display for MarkerShape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MarkerShape::None => write!(f, "None"),
MarkerShape::Circle => write!(f, "Circle"),
MarkerShape::Triangle => write!(f, "Triangle"),
MarkerShape::Square => write!(f, "Square"),
MarkerShape::Diamond => write!(f, "Diamond"),
MarkerShape::Plus => write!(f, "Plus"),
MarkerShape::Cross => write!(f, "Cross"),
}
}
}