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
25pub(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 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}