use crate::{colours::Colour, debug::DebugRect, state::State, Tick};
use leptos::prelude::*;
use std::str::FromStr;
pub const AXIS_MARKER_COLOUR: Colour = Colour::from_rgb(0xD2, 0xD2, 0xD2);
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct AxisMarker {
pub placement: RwSignal<AxisPlacement>,
pub colour: RwSignal<Colour>,
pub arrow: RwSignal<bool>,
pub width: RwSignal<f64>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum AxisPlacement {
Top,
Right,
Bottom,
Left,
HorizontalZero,
VerticalZero,
}
impl AxisMarker {
fn new(placement: AxisPlacement) -> Self {
Self {
placement: RwSignal::new(placement),
colour: RwSignal::new(AXIS_MARKER_COLOUR),
arrow: RwSignal::new(true),
width: RwSignal::new(1.0),
}
}
pub fn top_edge() -> Self {
Self::new(AxisPlacement::Top)
}
pub fn right_edge() -> Self {
Self::new(AxisPlacement::Right)
}
pub fn bottom_edge() -> Self {
Self::new(AxisPlacement::Bottom)
}
pub fn left_edge() -> Self {
Self::new(AxisPlacement::Left)
}
pub fn horizontal_zero() -> Self {
Self::new(AxisPlacement::HorizontalZero)
}
pub fn vertical_zero() -> Self {
Self::new(AxisPlacement::VerticalZero)
}
pub fn with_arrow(self, arrow: impl Into<bool>) -> Self {
self.arrow.set(arrow.into());
self
}
pub fn with_colour(self, colour: impl Into<Colour>) -> Self {
self.colour.set(colour.into());
self
}
}
impl std::fmt::Display for AxisPlacement {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use AxisPlacement as P;
match self {
P::Top => write!(f, "top"),
P::Right => write!(f, "right"),
P::Bottom => write!(f, "bottom"),
P::Left => write!(f, "left"),
P::HorizontalZero => write!(f, "horizontal zero"),
P::VerticalZero => write!(f, "vertical zero"),
}
}
}
impl FromStr for AxisPlacement {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use AxisPlacement::*;
match s.to_lowercase().as_str() {
"top" => Ok(Top),
"right" => Ok(Right),
"bottom" => Ok(Bottom),
"left" => Ok(Left),
"horizontal zero" => Ok(HorizontalZero),
"vertical zero" => Ok(VerticalZero),
_ => Err(format!("unknown axis placement: `{}`", s)),
}
}
}
#[component]
pub(super) fn AxisMarker<X: Tick, Y: Tick>(
marker: AxisMarker,
state: State<X, Y>,
) -> impl IntoView {
let debug = state.pre.debug;
let zero = state.svg_zero;
let inner = state.layout.inner;
let pos = Memo::new(move |_| {
let inner = inner.get();
let (top, right, bottom, left) = (
inner.top_y(),
inner.right_x(),
inner.bottom_y(),
inner.left_x(),
);
let (zero_x, zero_y) = zero.get();
let coords @ (x1, y1, x2, y2) = match marker.placement.get() {
AxisPlacement::Top => (left, top, right, top),
AxisPlacement::Bottom => (left, bottom, right, bottom),
AxisPlacement::Left => (left, bottom, left, top),
AxisPlacement::Right => (right, bottom, right, top),
AxisPlacement::HorizontalZero => (left, zero_y, right, zero_y),
AxisPlacement::VerticalZero => (zero_x, bottom, zero_x, top),
};
let in_bounds = inner.contains(x1, y1) && inner.contains(x2, y2);
(in_bounds, coords)
});
let in_bounds = Memo::new(move |_| pos.get().0);
let x1 = Memo::new(move |_| pos.get().1 .0);
let y1 = Memo::new(move |_| pos.get().1 .1);
let x2 = Memo::new(move |_| pos.get().1 .2);
let y2 = Memo::new(move |_| pos.get().1 .3);
let arrow = move || {
if marker.arrow.get() {
"url(#marker_axis_arrow)"
} else {
""
}
};
let colour = marker.colour;
let colour = move || colour.get().to_string();
view! {
<g
class="_chartistry_axis_marker"
stroke=colour
stroke-width=marker.width>
<Show when=move || in_bounds.get() >
<DebugRect label="axis_marker" debug=debug />
<line
x1=x1
y1=y1
x2=x2
y2=y2
marker-end=arrow
/>
</Show>
</g>
}
}