1use super::types::*;
2use anyhow::{bail, Context, Result};
3use arcstr::ArcStr;
4use graphix_rt::{GXExt, GXHandle, TRef};
5use log::error;
6use netidx::publisher::{FromValue, Value};
7use poolshark::local::LPooled;
8
9#[derive(Clone, Copy)]
12pub enum XYKind {
13 Line,
14 Scatter,
15 Area,
16}
17
18pub enum DatasetEntry<X: GXExt> {
20 XY { kind: XYKind, data: TRef<X, XYData>, style: SeriesStyleV },
21 DashedLine { data: TRef<X, XYData>, dash: f64, gap: f64, style: SeriesStyleV },
22 Bar { data: TRef<X, BarData>, style: BarStyleV },
23 Candlestick { data: TRef<X, OHLCData>, style: CandlestickStyleV },
24 ErrorBar { data: TRef<X, EBData>, style: SeriesStyleV },
25 Pie { data: TRef<X, BarData>, style: PieStyleV },
26 Scatter3D { data: TRef<X, XYZData>, style: SeriesStyleV },
27 Line3D { data: TRef<X, XYZData>, style: SeriesStyleV },
28 Surface { data: TRef<X, SurfaceData>, style: SurfaceStyleV },
29}
30
31impl<X: GXExt> DatasetEntry<X> {
32 pub fn label(&self) -> Option<&str> {
33 match self {
34 Self::XY { style, .. }
35 | Self::DashedLine { style, .. }
36 | Self::ErrorBar { style, .. }
37 | Self::Scatter3D { style, .. }
38 | Self::Line3D { style, .. } => style.label.as_deref(),
39 Self::Bar { style, .. } => style.label.as_deref(),
40 Self::Candlestick { style, .. } => style.label.as_deref(),
41 Self::Pie { .. } => None,
42 Self::Surface { style, .. } => style.label.as_deref(),
43 }
44 }
45}
46
47enum DatasetMeta {
49 XY { kind: XYKind, data_id: u64, style: SeriesStyleV },
50 DashedLine { data_id: u64, dash: f64, gap: f64, style: SeriesStyleV },
51 Bar { data_id: u64, style: BarStyleV },
52 Candlestick { data_id: u64, style: CandlestickStyleV },
53 ErrorBar { data_id: u64, style: SeriesStyleV },
54 Pie { data_id: u64, style: PieStyleV },
55 Scatter3D { data_id: u64, style: SeriesStyleV },
56 Line3D { data_id: u64, style: SeriesStyleV },
57 Surface { data_id: u64, style: SurfaceStyleV },
58}
59
60impl FromValue for DatasetMeta {
61 fn from_value(v: Value) -> Result<Self> {
62 let (tag, inner) = v.cast_to::<(ArcStr, Value)>()?;
63 match &*tag {
64 "Line" => {
65 let [(_, data), (_, style)] = inner.cast_to::<[(ArcStr, Value); 2]>()?;
66 let data_id = data.cast_to::<u64>()?;
67 let style = SeriesStyleV::from_value(style)?;
68 Ok(Self::XY { kind: XYKind::Line, data_id, style })
69 }
70 "Scatter" => {
71 let [(_, data), (_, style)] = inner.cast_to::<[(ArcStr, Value); 2]>()?;
72 let data_id = data.cast_to::<u64>()?;
73 let style = SeriesStyleV::from_value(style)?;
74 Ok(Self::XY { kind: XYKind::Scatter, data_id, style })
75 }
76 "Bar" => {
77 let [(_, data), (_, style)] = inner.cast_to::<[(ArcStr, Value); 2]>()?;
78 let data_id = data.cast_to::<u64>()?;
79 let style = BarStyleV::from_value(style)?;
80 Ok(Self::Bar { data_id, style })
81 }
82 "Area" => {
83 let [(_, data), (_, style)] = inner.cast_to::<[(ArcStr, Value); 2]>()?;
84 let data_id = data.cast_to::<u64>()?;
85 let style = SeriesStyleV::from_value(style)?;
86 Ok(Self::XY { kind: XYKind::Area, data_id, style })
87 }
88 "DashedLine" => {
89 let [(_, dash), (_, data), (_, gap), (_, style)] =
90 inner.cast_to::<[(ArcStr, Value); 4]>()?;
91 let data_id = data.cast_to::<u64>()?;
92 let dash = dash.cast_to::<f64>()?;
93 let gap = gap.cast_to::<f64>()?;
94 let style = SeriesStyleV::from_value(style)?;
95 Ok(Self::DashedLine { data_id, dash, gap, style })
96 }
97 "Candlestick" => {
98 let [(_, data), (_, style)] = inner.cast_to::<[(ArcStr, Value); 2]>()?;
99 let data_id = data.cast_to::<u64>()?;
100 let style = CandlestickStyleV::from_value(style)?;
101 Ok(Self::Candlestick { data_id, style })
102 }
103 "ErrorBar" => {
104 let [(_, data), (_, style)] = inner.cast_to::<[(ArcStr, Value); 2]>()?;
105 let data_id = data.cast_to::<u64>()?;
106 let style = SeriesStyleV::from_value(style)?;
107 Ok(Self::ErrorBar { data_id, style })
108 }
109 "Pie" => {
110 let [(_, data), (_, style)] = inner.cast_to::<[(ArcStr, Value); 2]>()?;
111 let data_id = data.cast_to::<u64>()?;
112 let style = PieStyleV::from_value(style)?;
113 Ok(Self::Pie { data_id, style })
114 }
115 "Scatter3D" => {
116 let [(_, data), (_, style)] = inner.cast_to::<[(ArcStr, Value); 2]>()?;
117 let data_id = data.cast_to::<u64>()?;
118 let style = SeriesStyleV::from_value(style)?;
119 Ok(Self::Scatter3D { data_id, style })
120 }
121 "Line3D" => {
122 let [(_, data), (_, style)] = inner.cast_to::<[(ArcStr, Value); 2]>()?;
123 let data_id = data.cast_to::<u64>()?;
124 let style = SeriesStyleV::from_value(style)?;
125 Ok(Self::Line3D { data_id, style })
126 }
127 "Surface" => {
128 let [(_, data), (_, style)] = inner.cast_to::<[(ArcStr, Value); 2]>()?;
129 let data_id = data.cast_to::<u64>()?;
130 let style = SurfaceStyleV::from_value(style)?;
131 Ok(Self::Surface { data_id, style })
132 }
133 s => bail!("invalid dataset variant: {s}"),
134 }
135 }
136}
137
138pub async fn compile_datasets<X: GXExt>(
140 gx: &GXHandle<X>,
141 v: Value,
142) -> Result<LPooled<Vec<DatasetEntry<X>>>> {
143 let metas: Vec<DatasetMeta> = v
144 .cast_to::<Vec<Value>>()?
145 .into_iter()
146 .map(DatasetMeta::from_value)
147 .collect::<Result<_>>()?;
148 let mut entries: LPooled<Vec<DatasetEntry<X>>> = LPooled::take();
149 entries.reserve(metas.len());
150 for meta in metas {
151 match meta {
152 DatasetMeta::XY { kind, data_id, style } => {
153 let data_ref = gx.compile_ref(data_id).await?;
154 let data = TRef::new(data_ref).context("chart xy data")?;
155 entries.push(DatasetEntry::XY { kind, data, style });
156 }
157 DatasetMeta::DashedLine { data_id, dash, gap, style } => {
158 let data_ref = gx.compile_ref(data_id).await?;
159 let data = TRef::new(data_ref).context("chart dashed data")?;
160 entries.push(DatasetEntry::DashedLine { data, dash, gap, style });
161 }
162 DatasetMeta::Bar { data_id, style } => {
163 let data_ref = gx.compile_ref(data_id).await?;
164 let data = TRef::new(data_ref).context("chart bar data")?;
165 entries.push(DatasetEntry::Bar { data, style });
166 }
167 DatasetMeta::Candlestick { data_id, style } => {
168 let data_ref = gx.compile_ref(data_id).await?;
169 let data = TRef::new(data_ref).context("chart ohlc data")?;
170 entries.push(DatasetEntry::Candlestick { data, style });
171 }
172 DatasetMeta::ErrorBar { data_id, style } => {
173 let data_ref = gx.compile_ref(data_id).await?;
174 let data = TRef::new(data_ref).context("chart errorbar data")?;
175 entries.push(DatasetEntry::ErrorBar { data, style });
176 }
177 DatasetMeta::Pie { data_id, style } => {
178 let data_ref = gx.compile_ref(data_id).await?;
179 let data = TRef::new(data_ref).context("chart pie data")?;
180 entries.push(DatasetEntry::Pie { data, style });
181 }
182 DatasetMeta::Scatter3D { data_id, style } => {
183 let data_ref = gx.compile_ref(data_id).await?;
184 let data = TRef::new(data_ref).context("chart scatter3d data")?;
185 entries.push(DatasetEntry::Scatter3D { data, style });
186 }
187 DatasetMeta::Line3D { data_id, style } => {
188 let data_ref = gx.compile_ref(data_id).await?;
189 let data = TRef::new(data_ref).context("chart line3d data")?;
190 entries.push(DatasetEntry::Line3D { data, style });
191 }
192 DatasetMeta::Surface { data_id, style } => {
193 let data_ref = gx.compile_ref(data_id).await?;
194 let data = TRef::new(data_ref).context("chart surface data")?;
195 entries.push(DatasetEntry::Surface { data, style });
196 }
197 }
198 }
199 Ok(entries)
200}
201
202#[derive(Clone, Copy, PartialEq, Eq)]
205pub enum ChartMode {
206 Numeric,
207 TimeSeries,
208 Bar,
209 Pie,
210 ThreeD,
211 Empty,
212}
213
214pub fn chart_mode<X: GXExt>(datasets: &[DatasetEntry<X>]) -> ChartMode {
215 let mut has_bar = false;
216 let mut has_pie = false;
217 let mut has_3d = false;
218 let mut has_other = false;
219 for ds in datasets {
220 match ds {
221 DatasetEntry::XY { data, .. } | DatasetEntry::DashedLine { data, .. } => {
222 if let Some(d) = data.t.as_ref() {
223 match d {
224 XYData::DateTime(v) if !v.is_empty() => has_other = true,
225 XYData::Numeric(v) if !v.is_empty() => has_other = true,
226 _ => {}
227 }
228 }
229 }
230 DatasetEntry::Bar { data, .. } => {
231 if let Some(d) = data.t.as_ref() {
232 if !d.0.is_empty() {
233 has_bar = true;
234 }
235 }
236 }
237 DatasetEntry::Candlestick { data, .. } => {
238 if let Some(d) = data.t.as_ref() {
239 match d {
240 OHLCData::DateTime(v) if !v.is_empty() => has_other = true,
241 OHLCData::Numeric(v) if !v.is_empty() => has_other = true,
242 _ => {}
243 }
244 }
245 }
246 DatasetEntry::ErrorBar { data, .. } => {
247 if let Some(d) = data.t.as_ref() {
248 match d {
249 EBData::DateTime(v) if !v.is_empty() => has_other = true,
250 EBData::Numeric(v) if !v.is_empty() => has_other = true,
251 _ => {}
252 }
253 }
254 }
255 DatasetEntry::Pie { data, .. } => {
256 if let Some(d) = data.t.as_ref() {
257 if !d.0.is_empty() {
258 has_pie = true;
259 }
260 }
261 }
262 DatasetEntry::Scatter3D { data, .. } | DatasetEntry::Line3D { data, .. } => {
263 if let Some(d) = data.t.as_ref() {
264 if !d.0.is_empty() {
265 has_3d = true;
266 }
267 }
268 }
269 DatasetEntry::Surface { data, .. } => {
270 if let Some(d) = data.t.as_ref() {
271 if !d.0.is_empty() {
272 has_3d = true;
273 }
274 }
275 }
276 }
277 }
278 let mode_count = has_bar as u8 + has_pie as u8 + has_3d as u8 + has_other as u8;
279 if mode_count > 1 {
280 error!("chart: cannot mix bar, pie, 3D, and XY/timeseries datasets");
281 return ChartMode::Empty;
282 }
283 if has_pie {
284 return ChartMode::Pie;
285 }
286 if has_bar {
287 return ChartMode::Bar;
288 }
289 if has_3d {
290 return ChartMode::ThreeD;
291 }
292 for ds in datasets {
294 match ds {
295 DatasetEntry::XY { data, .. } | DatasetEntry::DashedLine { data, .. } => {
296 if let Some(d) = data.t.as_ref() {
297 match d {
298 XYData::DateTime(v) if !v.is_empty() => {
299 return ChartMode::TimeSeries
300 }
301 XYData::Numeric(v) if !v.is_empty() => return ChartMode::Numeric,
302 _ => {}
303 }
304 }
305 }
306 DatasetEntry::Bar { .. }
307 | DatasetEntry::Pie { .. }
308 | DatasetEntry::Scatter3D { .. }
309 | DatasetEntry::Line3D { .. }
310 | DatasetEntry::Surface { .. } => {}
311 DatasetEntry::Candlestick { data, .. } => {
312 if let Some(d) = data.t.as_ref() {
313 match d {
314 OHLCData::DateTime(v) if !v.is_empty() => {
315 return ChartMode::TimeSeries;
316 }
317 OHLCData::Numeric(v) if !v.is_empty() => {
318 return ChartMode::Numeric
319 }
320 _ => {}
321 }
322 }
323 }
324 DatasetEntry::ErrorBar { data, .. } => {
325 if let Some(d) = data.t.as_ref() {
326 match d {
327 EBData::DateTime(v) if !v.is_empty() => {
328 return ChartMode::TimeSeries;
329 }
330 EBData::Numeric(v) if !v.is_empty() => return ChartMode::Numeric,
331 _ => {}
332 }
333 }
334 }
335 }
336 }
337 ChartMode::Empty
338}