orbital_charts/shared/motion/
path_draw.rs1use leptos::prelude::*;
4use orbital_motion::{MotionCurve, MotionDuration};
5
6#[component]
8pub fn PathDrawMotion(
9 #[prop(into)]
11 d: Signal<String>,
12 #[prop(into)]
14 stroke: Signal<String>,
15 #[prop(into, default = "orb-line-stroke".into())]
17 class: String,
18 #[prop(default = false)]
20 skip_animation: bool,
21 #[prop(default = MotionDuration::Normal)]
23 duration: MotionDuration,
24 #[prop(default = MotionCurve::DecelerateMax)]
26 curve: MotionCurve,
27 #[prop(into)]
29 draw_key: Signal<String>,
30 #[prop(default = 2.0)]
32 stroke_width: f64,
33) -> impl IntoView {
34 let drawn = RwSignal::new(skip_animation);
35 let path_length = Memo::new(move |_| estimate_path_length(&d.get()));
36
37 Effect::new(move |_| {
38 let _ = draw_key.get();
39 if skip_animation {
40 drawn.set(true);
41 return;
42 }
43 drawn.set(false);
44 schedule_draw_complete(move || drawn.set(true));
45 });
46
47 let dash_style = move || {
48 let len = path_length.get().max(1.0);
49 let offset = if drawn.get() { 0.0 } else { len };
50 format!(
51 "stroke-dasharray: {len}; stroke-dashoffset: {offset}; transition: stroke-dashoffset {} {}; fill: none; stroke-width: {stroke_width}; stroke: {};",
52 duration.ms(),
53 curve.css_var(),
54 stroke.get(),
55 )
56 };
57
58 view! {
59 <path
60 class=class
61 d=move || d.get()
62 style=dash_style
63 fill="none"
64 />
65 }
66}
67
68fn schedule_draw_complete(f: impl FnOnce() + 'static) {
69 #[cfg(feature = "hydrate")]
70 {
71 use wasm_bindgen::closure::Closure;
72 use wasm_bindgen::JsCast;
73
74 if let Some(win) = web_sys::window() {
75 let cb = Closure::once(f);
76 let _ = win.set_timeout_with_callback_and_timeout_and_arguments_0(
77 cb.as_ref().unchecked_ref(),
78 16,
79 );
80 cb.forget();
81 }
82 }
83 #[cfg(not(feature = "hydrate"))]
84 let _ = f;
85 #[cfg(not(feature = "hydrate"))]
86 f();
87}
88
89fn estimate_path_length(d: &str) -> f64 {
90 let nums: Vec<f64> = d
91 .split(|c: char| !c.is_ascii_digit() && c != '.' && c != '-')
92 .filter_map(|s| s.parse().ok())
93 .collect();
94 if nums.len() < 4 {
95 return 100.0;
96 }
97 let mut len = 0.0;
98 for w in nums.windows(2) {
99 len += (w[1] - w[0]).abs();
100 }
101 len.max(50.0)
102}