Skip to main content

graphix_package_gui/widgets/chart/
mod.rs

1mod dataset;
2mod draw;
3mod interact;
4mod plotters_backend;
5mod ranges;
6mod types;
7
8use crate::types::LengthV;
9use crate::widgets::{GuiW, GuiWidget, IcedElement};
10use anyhow::{Context, Result};
11use arcstr::ArcStr;
12use dataset::{compile_datasets, DatasetEntry};
13use graphix_compiler::expr::ExprId;
14use graphix_rt::{GXExt, GXHandle, Ref, TRef};
15use iced_widget::canvas as iced_canvas;
16use netidx::publisher::Value;
17use poolshark::local::LPooled;
18use std::cell::Cell;
19use tokio::try_join;
20use types::*;
21
22#[cfg(test)]
23pub(crate) use ranges::pad_range;
24
25// ── ChartW ──────────────────────────────────────────────────────────
26
27pub(crate) struct ChartW<X: GXExt> {
28    gx: GXHandle<X>,
29    datasets_ref: Ref<X>,
30    datasets: LPooled<Vec<DatasetEntry<X>>>,
31    title: TRef<X, Option<String>>,
32    x_label: TRef<X, Option<String>>,
33    y_label: TRef<X, Option<String>>,
34    z_label: TRef<X, Option<String>>,
35    x_range: TRef<X, OptXAxisRange>,
36    y_range: TRef<X, OptAxisRange>,
37    z_range: TRef<X, OptAxisRange>,
38    projection: TRef<X, OptProjection3D>,
39    width: TRef<X, LengthV>,
40    height: TRef<X, LengthV>,
41    background: TRef<X, OptColor>,
42    margin: TRef<X, OptF64>,
43    title_color: TRef<X, OptColor>,
44    title_size: TRef<X, OptF64>,
45    legend_position: TRef<X, OptLegendPosition>,
46    legend_style: TRef<X, OptLegendStyle>,
47    mesh: TRef<X, OptMeshStyle>,
48    /// Set to true when data changes; draw() clears the cache and resets.
49    dirty: Cell<bool>,
50}
51
52impl<X: GXExt> ChartW<X> {
53    pub(crate) async fn compile(gx: GXHandle<X>, source: Value) -> Result<GuiW<X>> {
54        let [(_, background), (_, datasets), (_, height), (_, legend_position), (_, legend_style), (_, margin), (_, mesh), (_, projection), (_, title), (_, title_color), (_, title_size), (_, width), (_, x_label), (_, x_range), (_, y_label), (_, y_range), (_, z_label), (_, z_range)] =
55            source.cast_to::<[(ArcStr, u64); 18]>().context("chart flds")?;
56        let (
57            background_ref,
58            datasets_ref,
59            height_ref,
60            legend_position_ref,
61            legend_style_ref,
62            margin_ref,
63            mesh_ref,
64            projection_ref,
65            title_ref,
66            title_color_ref,
67            title_size_ref,
68            width_ref,
69            x_label_ref,
70            x_range_ref,
71            y_label_ref,
72            y_range_ref,
73            z_label_ref,
74            z_range_ref,
75        ) = try_join! {
76            gx.compile_ref(background),
77            gx.compile_ref(datasets),
78            gx.compile_ref(height),
79            gx.compile_ref(legend_position),
80            gx.compile_ref(legend_style),
81            gx.compile_ref(margin),
82            gx.compile_ref(mesh),
83            gx.compile_ref(projection),
84            gx.compile_ref(title),
85            gx.compile_ref(title_color),
86            gx.compile_ref(title_size),
87            gx.compile_ref(width),
88            gx.compile_ref(x_label),
89            gx.compile_ref(x_range),
90            gx.compile_ref(y_label),
91            gx.compile_ref(y_range),
92            gx.compile_ref(z_label),
93            gx.compile_ref(z_range),
94        }?;
95        let entries = match datasets_ref.last.as_ref() {
96            Some(v) => compile_datasets(&gx, v.clone()).await?,
97            None => LPooled::take(),
98        };
99        Ok(Box::new(Self {
100            gx: gx.clone(),
101            datasets_ref,
102            datasets: entries,
103            title: TRef::new(title_ref).context("chart tref title")?,
104            x_label: TRef::new(x_label_ref).context("chart tref x_label")?,
105            y_label: TRef::new(y_label_ref).context("chart tref y_label")?,
106            z_label: TRef::new(z_label_ref).context("chart tref z_label")?,
107            x_range: TRef::new(x_range_ref).context("chart tref x_range")?,
108            y_range: TRef::new(y_range_ref).context("chart tref y_range")?,
109            z_range: TRef::new(z_range_ref).context("chart tref z_range")?,
110            projection: TRef::new(projection_ref).context("chart tref projection")?,
111            width: TRef::new(width_ref).context("chart tref width")?,
112            height: TRef::new(height_ref).context("chart tref height")?,
113            background: TRef::new(background_ref).context("chart tref background")?,
114            margin: TRef::new(margin_ref).context("chart tref margin")?,
115            title_color: TRef::new(title_color_ref).context("chart tref title_color")?,
116            title_size: TRef::new(title_size_ref).context("chart tref title_size")?,
117            legend_position: TRef::new(legend_position_ref)
118                .context("chart tref legend_position")?,
119            legend_style: TRef::new(legend_style_ref)
120                .context("chart tref legend_style")?,
121            mesh: TRef::new(mesh_ref).context("chart tref mesh")?,
122            dirty: Cell::new(false),
123        }))
124    }
125}
126
127impl<X: GXExt> GuiWidget<X> for ChartW<X> {
128    fn handle_update(
129        &mut self,
130        rt: &tokio::runtime::Handle,
131        id: ExprId,
132        v: &Value,
133    ) -> Result<bool> {
134        let mut changed = false;
135        if id == self.datasets_ref.id {
136            self.datasets_ref.last = Some(v.clone());
137            self.datasets = rt
138                .block_on(compile_datasets(&self.gx, v.clone()))
139                .context("chart datasets recompile")?;
140            self.dirty.set(true);
141            changed = true;
142        }
143        for ds in self.datasets.iter_mut() {
144            let updated = match ds {
145                DatasetEntry::XY { data, .. } | DatasetEntry::DashedLine { data, .. } => {
146                    data.update(id, v).context("chart update xy data")?.is_some()
147                }
148                DatasetEntry::Bar { data, .. } => {
149                    data.update(id, v).context("chart update bar data")?.is_some()
150                }
151                DatasetEntry::Candlestick { data, .. } => {
152                    data.update(id, v).context("chart update ohlc data")?.is_some()
153                }
154                DatasetEntry::ErrorBar { data, .. } => {
155                    data.update(id, v).context("chart update errorbar data")?.is_some()
156                }
157                DatasetEntry::Pie { data, .. } => {
158                    data.update(id, v).context("chart update pie data")?.is_some()
159                }
160                DatasetEntry::Scatter3D { data, .. }
161                | DatasetEntry::Line3D { data, .. } => {
162                    data.update(id, v).context("chart update 3d data")?.is_some()
163                }
164                DatasetEntry::Surface { data, .. } => {
165                    data.update(id, v).context("chart update surface data")?.is_some()
166                }
167            };
168            if updated {
169                self.dirty.set(true);
170                changed = true;
171            }
172        }
173        macro_rules! up {
174            ($f:ident) => {
175                if self
176                    .$f
177                    .update(id, v)
178                    .context(concat!("chart update ", stringify!($f)))?
179                    .is_some()
180                {
181                    self.dirty.set(true);
182                    changed = true;
183                }
184            };
185        }
186        up!(title);
187        up!(x_label);
188        up!(y_label);
189        up!(z_label);
190        up!(x_range);
191        up!(y_range);
192        up!(z_range);
193        up!(projection);
194        up!(background);
195        up!(margin);
196        up!(title_color);
197        up!(title_size);
198        up!(legend_position);
199        up!(legend_style);
200        up!(mesh);
201        changed |= self.width.update(id, v).context("chart update width")?.is_some();
202        changed |= self.height.update(id, v).context("chart update height")?.is_some();
203        Ok(changed)
204    }
205
206    fn view(&self) -> IcedElement<'_> {
207        let mut c = iced_canvas::Canvas::new(self);
208        if let Some(w) = self.width.t.as_ref() {
209            c = c.width(w.0);
210        }
211        if let Some(h) = self.height.t.as_ref() {
212            c = c.height(h.0);
213        }
214        c.into()
215    }
216}