use crate::types::ColorV;
use anyhow::{bail, Result};
use arcstr::ArcStr;
use chrono::{DateTime, Utc};
use netidx::publisher::{FromValue, Value};
use plotters::prelude::SeriesLabelPosition;
use poolshark::local::LPooled;
#[derive(Clone, Copy, Debug)]
pub struct ChartColor(pub f32, pub f32, pub f32, pub f32);
impl ChartColor {
pub fn to_plotters_rgb(self) -> plotters::style::RGBColor {
plotters::style::RGBColor(
(self.0 * 255.0) as u8,
(self.1 * 255.0) as u8,
(self.2 * 255.0) as u8,
)
}
}
impl From<iced_core::Color> for ChartColor {
fn from(c: iced_core::Color) -> Self {
Self(c.r, c.g, c.b, c.a)
}
}
impl From<ChartColor> for iced_core::Color {
fn from(c: ChartColor) -> Self {
iced_core::Color::from_rgba(c.0, c.1, c.2, c.3)
}
}
pub enum XYData {
Numeric(LPooled<Vec<(f64, f64)>>),
DateTime(LPooled<Vec<(DateTime<Utc>, f64)>>),
}
impl FromValue for XYData {
fn from_value(v: Value) -> Result<Self> {
let a = match v {
Value::Array(a) => a,
_ => bail!("chart dataset data: expected array"),
};
if a.is_empty() {
return Ok(Self::Numeric(LPooled::take()));
}
let is_datetime = matches!(&a[0], Value::Array(tup) if !tup.is_empty() && matches!(&tup[0], Value::DateTime(_)));
if is_datetime {
Ok(Self::DateTime(
a.iter()
.map(|v| v.clone().cast_to::<(DateTime<Utc>, f64)>())
.collect::<Result<_>>()?,
))
} else {
Ok(Self::Numeric(
a.iter()
.map(|v| v.clone().cast_to::<(f64, f64)>())
.collect::<Result<_>>()?,
))
}
}
}
pub struct BarData(pub LPooled<Vec<(String, f64)>>);
impl FromValue for BarData {
fn from_value(v: Value) -> Result<Self> {
let a = match v {
Value::Array(a) => a,
_ => bail!("chart bar data: expected array"),
};
Ok(Self(
a.iter()
.map(|v| v.clone().cast_to::<(String, f64)>())
.collect::<Result<_>>()?,
))
}
}
#[derive(Clone, Copy, Debug)]
pub struct OHLCPoint {
pub x: f64,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
}
impl FromValue for OHLCPoint {
fn from_value(v: Value) -> Result<Self> {
let [(_, close), (_, high), (_, low), (_, open), (_, x)] =
v.cast_to::<[(ArcStr, f64); 5]>()?;
Ok(Self { x, open, high, low, close })
}
}
#[derive(Clone, Copy, Debug)]
pub struct TimeOHLCPoint {
pub x: DateTime<Utc>,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
}
impl FromValue for TimeOHLCPoint {
fn from_value(v: Value) -> Result<Self> {
let [(_, close), (_, high), (_, low), (_, open), (_, x)] =
v.cast_to::<[(ArcStr, Value); 5]>()?;
Ok(Self {
x: x.cast_to::<DateTime<Utc>>()?,
open: open.cast_to::<f64>()?,
high: high.cast_to::<f64>()?,
low: low.cast_to::<f64>()?,
close: close.cast_to::<f64>()?,
})
}
}
pub enum OHLCData {
Numeric(LPooled<Vec<OHLCPoint>>),
DateTime(LPooled<Vec<TimeOHLCPoint>>),
}
impl FromValue for OHLCData {
fn from_value(v: Value) -> Result<Self> {
let a = match v {
Value::Array(a) => a,
_ => bail!("chart ohlc data: expected array"),
};
if a.is_empty() {
return Ok(Self::Numeric(LPooled::take()));
}
let first_fields = a[0].clone().cast_to::<[(ArcStr, Value); 5]>()?;
let x_val = &first_fields[4].1;
if matches!(x_val, Value::DateTime(_)) {
Ok(Self::DateTime(
a.iter()
.map(|v| TimeOHLCPoint::from_value(v.clone()))
.collect::<Result<_>>()?,
))
} else {
Ok(Self::Numeric(
a.iter()
.map(|v| OHLCPoint::from_value(v.clone()))
.collect::<Result<_>>()?,
))
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct EBPoint {
pub x: f64,
pub min: f64,
pub avg: f64,
pub max: f64,
}
impl FromValue for EBPoint {
fn from_value(v: Value) -> Result<Self> {
let [(_, avg), (_, max), (_, min), (_, x)] = v.cast_to::<[(ArcStr, f64); 4]>()?;
Ok(Self { x, min, avg, max })
}
}
#[derive(Clone, Copy, Debug)]
pub struct TimeEBPoint {
pub x: DateTime<Utc>,
pub min: f64,
pub avg: f64,
pub max: f64,
}
impl FromValue for TimeEBPoint {
fn from_value(v: Value) -> Result<Self> {
let [(_, avg), (_, max), (_, min), (_, x)] =
v.cast_to::<[(ArcStr, Value); 4]>()?;
Ok(Self {
x: x.cast_to::<DateTime<Utc>>()?,
min: min.cast_to::<f64>()?,
avg: avg.cast_to::<f64>()?,
max: max.cast_to::<f64>()?,
})
}
}
pub enum EBData {
Numeric(LPooled<Vec<EBPoint>>),
DateTime(LPooled<Vec<TimeEBPoint>>),
}
impl FromValue for EBData {
fn from_value(v: Value) -> Result<Self> {
let a = match v {
Value::Array(a) => a,
_ => bail!("chart error bar data: expected array"),
};
if a.is_empty() {
return Ok(Self::Numeric(LPooled::take()));
}
let first_fields = a[0].clone().cast_to::<[(ArcStr, Value); 4]>()?;
let x_val = &first_fields[3].1;
if matches!(x_val, Value::DateTime(_)) {
Ok(Self::DateTime(
a.iter()
.map(|v| TimeEBPoint::from_value(v.clone()))
.collect::<Result<_>>()?,
))
} else {
Ok(Self::Numeric(
a.iter()
.map(|v| EBPoint::from_value(v.clone()))
.collect::<Result<_>>()?,
))
}
}
}
pub struct XYZData(pub LPooled<Vec<(f64, f64, f64)>>);
impl FromValue for XYZData {
fn from_value(v: Value) -> Result<Self> {
let a = match v {
Value::Array(a) => a,
_ => bail!("chart xyz data: expected array"),
};
Ok(Self(
a.iter()
.map(|v| v.clone().cast_to::<(f64, f64, f64)>())
.collect::<Result<_>>()?,
))
}
}
pub struct SurfaceData(pub Vec<Vec<(f64, f64, f64)>>);
impl FromValue for SurfaceData {
fn from_value(v: Value) -> Result<Self> {
let a = match v {
Value::Array(a) => a,
_ => bail!("chart surface data: expected array of arrays"),
};
let mut rows = Vec::with_capacity(a.len());
for row_v in a.iter() {
let row_a = match row_v {
Value::Array(a) => a,
_ => bail!("chart surface data: expected inner array"),
};
let row: Vec<(f64, f64, f64)> = row_a
.iter()
.map(|v| v.clone().cast_to::<(f64, f64, f64)>())
.collect::<Result<_>>()?;
rows.push(row);
}
Ok(Self(rows))
}
}
pub struct SeriesStyleV {
pub color: Option<ChartColor>,
pub label: Option<String>,
pub stroke_width: Option<f64>,
pub point_size: Option<f64>,
}
impl FromValue for SeriesStyleV {
fn from_value(v: Value) -> Result<Self> {
let [(_, color), (_, label), (_, point_size), (_, stroke_width)] =
v.cast_to::<[(ArcStr, Value); 4]>()?;
Ok(Self {
color: if color == Value::Null {
None
} else {
Some(ColorV::from_value(color)?.0.into())
},
label: if label == Value::Null {
None
} else {
Some(label.cast_to::<String>()?)
},
stroke_width: if stroke_width == Value::Null {
None
} else {
Some(stroke_width.cast_to::<f64>()?)
},
point_size: if point_size == Value::Null {
None
} else {
Some(point_size.cast_to::<f64>()?)
},
})
}
}
pub struct BarStyleV {
pub color: Option<ChartColor>,
pub label: Option<String>,
pub margin: Option<f64>,
}
impl FromValue for BarStyleV {
fn from_value(v: Value) -> Result<Self> {
let [(_, color), (_, label), (_, margin)] =
v.cast_to::<[(ArcStr, Value); 3]>()?;
Ok(Self {
color: if color == Value::Null {
None
} else {
Some(ColorV::from_value(color)?.0.into())
},
label: if label == Value::Null {
None
} else {
Some(label.cast_to::<String>()?)
},
margin: if margin == Value::Null {
None
} else {
Some(margin.cast_to::<f64>()?)
},
})
}
}
pub struct CandlestickStyleV {
pub gain_color: Option<ChartColor>,
pub loss_color: Option<ChartColor>,
pub bar_width: Option<f64>,
pub label: Option<String>,
}
impl FromValue for CandlestickStyleV {
fn from_value(v: Value) -> Result<Self> {
let [(_, bar_width), (_, gain_color), (_, label), (_, loss_color)] =
v.cast_to::<[(ArcStr, Value); 4]>()?;
Ok(Self {
gain_color: if gain_color == Value::Null {
None
} else {
Some(ColorV::from_value(gain_color)?.0.into())
},
loss_color: if loss_color == Value::Null {
None
} else {
Some(ColorV::from_value(loss_color)?.0.into())
},
bar_width: if bar_width == Value::Null {
None
} else {
Some(bar_width.cast_to::<f64>()?)
},
label: if label == Value::Null {
None
} else {
Some(label.cast_to::<String>()?)
},
})
}
}
pub struct PieStyleV {
pub colors: Option<Vec<ChartColor>>,
pub donut: Option<f64>,
pub label_offset: Option<f64>,
pub show_percentages: Option<bool>,
pub start_angle: Option<f64>,
}
impl FromValue for PieStyleV {
fn from_value(v: Value) -> Result<Self> {
let [(_, colors), (_, donut), (_, label_offset), (_, show_percentages), (_, start_angle)] =
v.cast_to::<[(ArcStr, Value); 5]>()?;
Ok(Self {
colors: if colors == Value::Null {
None
} else {
let arr = match colors {
Value::Array(a) => a,
_ => bail!("pie colors: expected array"),
};
Some(
arr.iter()
.map(|v| Ok(ChartColor::from(ColorV::from_value(v.clone())?.0)))
.collect::<Result<_>>()?,
)
},
donut: if donut == Value::Null {
None
} else {
Some(donut.cast_to::<f64>()?)
},
label_offset: if label_offset == Value::Null {
None
} else {
Some(label_offset.cast_to::<f64>()?)
},
show_percentages: if show_percentages == Value::Null {
None
} else {
Some(show_percentages.cast_to::<bool>()?)
},
start_angle: if start_angle == Value::Null {
None
} else {
Some(start_angle.cast_to::<f64>()?)
},
})
}
}
pub struct SurfaceStyleV {
pub color: Option<ChartColor>,
pub color_by_z: Option<bool>,
pub label: Option<String>,
}
impl FromValue for SurfaceStyleV {
fn from_value(v: Value) -> Result<Self> {
let [(_, color), (_, color_by_z), (_, label)] =
v.cast_to::<[(ArcStr, Value); 3]>()?;
Ok(Self {
color: if color == Value::Null {
None
} else {
Some(ColorV::from_value(color)?.0.into())
},
color_by_z: if color_by_z == Value::Null {
None
} else {
Some(color_by_z.cast_to::<bool>()?)
},
label: if label == Value::Null {
None
} else {
Some(label.cast_to::<String>()?)
},
})
}
}
pub struct MeshStyleV {
pub show_x_grid: Option<bool>,
pub show_y_grid: Option<bool>,
pub grid_color: Option<ChartColor>,
pub axis_color: Option<ChartColor>,
pub label_color: Option<ChartColor>,
pub label_size: Option<f64>,
pub x_label_area_size: Option<f64>,
pub x_labels: Option<i64>,
pub y_label_area_size: Option<f64>,
pub y_labels: Option<i64>,
}
impl FromValue for MeshStyleV {
fn from_value(v: Value) -> Result<Self> {
let [(_, axis_color), (_, grid_color), (_, label_color), (_, label_size), (_, show_x_grid), (_, show_y_grid), (_, x_label_area_size), (_, x_labels), (_, y_label_area_size), (_, y_labels)] =
v.cast_to::<[(ArcStr, Value); 10]>()?;
Ok(Self {
show_x_grid: if show_x_grid == Value::Null {
None
} else {
Some(show_x_grid.cast_to::<bool>()?)
},
show_y_grid: if show_y_grid == Value::Null {
None
} else {
Some(show_y_grid.cast_to::<bool>()?)
},
grid_color: if grid_color == Value::Null {
None
} else {
Some(ColorV::from_value(grid_color)?.0.into())
},
axis_color: if axis_color == Value::Null {
None
} else {
Some(ColorV::from_value(axis_color)?.0.into())
},
label_color: if label_color == Value::Null {
None
} else {
Some(ColorV::from_value(label_color)?.0.into())
},
label_size: if label_size == Value::Null {
None
} else {
Some(label_size.cast_to::<f64>()?)
},
x_label_area_size: if x_label_area_size == Value::Null {
None
} else {
Some(x_label_area_size.cast_to::<f64>()?)
},
x_labels: if x_labels == Value::Null {
None
} else {
Some(x_labels.cast_to::<i64>()?)
},
y_label_area_size: if y_label_area_size == Value::Null {
None
} else {
Some(y_label_area_size.cast_to::<f64>()?)
},
y_labels: if y_labels == Value::Null {
None
} else {
Some(y_labels.cast_to::<i64>()?)
},
})
}
}
pub struct OptMeshStyle(pub Option<MeshStyleV>);
impl FromValue for OptMeshStyle {
fn from_value(v: Value) -> Result<Self> {
if v == Value::Null {
Ok(Self(None))
} else {
Ok(Self(Some(MeshStyleV::from_value(v)?)))
}
}
}
pub struct LegendStyleV {
pub background: Option<ChartColor>,
pub border: Option<ChartColor>,
pub label_color: Option<ChartColor>,
pub label_size: Option<f64>,
}
impl FromValue for LegendStyleV {
fn from_value(v: Value) -> Result<Self> {
let [(_, background), (_, border), (_, label_color), (_, label_size)] =
v.cast_to::<[(ArcStr, Value); 4]>()?;
Ok(Self {
background: if background == Value::Null {
None
} else {
Some(ColorV::from_value(background)?.0.into())
},
border: if border == Value::Null {
None
} else {
Some(ColorV::from_value(border)?.0.into())
},
label_color: if label_color == Value::Null {
None
} else {
Some(ColorV::from_value(label_color)?.0.into())
},
label_size: if label_size == Value::Null {
None
} else {
Some(label_size.cast_to::<f64>()?)
},
})
}
}
pub struct OptLegendStyle(pub Option<LegendStyleV>);
impl FromValue for OptLegendStyle {
fn from_value(v: Value) -> Result<Self> {
if v == Value::Null {
Ok(Self(None))
} else {
Ok(Self(Some(LegendStyleV::from_value(v)?)))
}
}
}
#[derive(Clone)]
pub struct LegendPositionV(pub SeriesLabelPosition);
impl FromValue for LegendPositionV {
fn from_value(v: Value) -> Result<Self> {
match &*v.cast_to::<ArcStr>()? {
"UpperLeft" => Ok(Self(SeriesLabelPosition::UpperLeft)),
"UpperRight" => Ok(Self(SeriesLabelPosition::UpperRight)),
"LowerLeft" => Ok(Self(SeriesLabelPosition::LowerLeft)),
"LowerRight" => Ok(Self(SeriesLabelPosition::LowerRight)),
"MiddleLeft" => Ok(Self(SeriesLabelPosition::MiddleLeft)),
"MiddleRight" => Ok(Self(SeriesLabelPosition::MiddleRight)),
"UpperMiddle" => Ok(Self(SeriesLabelPosition::UpperMiddle)),
"LowerMiddle" => Ok(Self(SeriesLabelPosition::LowerMiddle)),
s => bail!("invalid legend position: {s}"),
}
}
}
pub struct OptLegendPosition(pub Option<LegendPositionV>);
impl FromValue for OptLegendPosition {
fn from_value(v: Value) -> Result<Self> {
if v == Value::Null {
Ok(Self(None))
} else {
Ok(Self(Some(LegendPositionV::from_value(v)?)))
}
}
}
pub struct OptF64(pub Option<f64>);
impl FromValue for OptF64 {
fn from_value(v: Value) -> Result<Self> {
if v == Value::Null {
Ok(Self(None))
} else {
Ok(Self(Some(v.cast_to::<f64>()?)))
}
}
}
pub struct OptColor(pub Option<ChartColor>);
impl FromValue for OptColor {
fn from_value(v: Value) -> Result<Self> {
if v == Value::Null {
Ok(Self(None))
} else {
Ok(Self(Some(ColorV::from_value(v)?.0.into())))
}
}
}
pub struct Projection3DV {
pub pitch: Option<f64>,
pub scale: Option<f64>,
pub yaw: Option<f64>,
}
impl FromValue for Projection3DV {
fn from_value(v: Value) -> Result<Self> {
let [(_, pitch), (_, scale), (_, yaw)] = v.cast_to::<[(ArcStr, Value); 3]>()?;
Ok(Self {
pitch: if pitch == Value::Null {
None
} else {
Some(pitch.cast_to::<f64>()?)
},
scale: if scale == Value::Null {
None
} else {
Some(scale.cast_to::<f64>()?)
},
yaw: if yaw == Value::Null { None } else { Some(yaw.cast_to::<f64>()?) },
})
}
}
pub struct OptProjection3D(pub Option<Projection3DV>);
impl FromValue for OptProjection3D {
fn from_value(v: Value) -> Result<Self> {
if v == Value::Null {
Ok(Self(None))
} else {
Ok(Self(Some(Projection3DV::from_value(v)?)))
}
}
}
#[derive(Clone, Debug)]
pub struct AxisRange {
pub min: f64,
pub max: f64,
}
impl FromValue for AxisRange {
fn from_value(v: Value) -> Result<Self> {
let [(_, max), (_, min)] = v.cast_to::<[(ArcStr, f64); 2]>()?;
Ok(AxisRange { min, max })
}
}
#[derive(Clone, Debug)]
pub struct OptAxisRange(pub Option<AxisRange>);
impl FromValue for OptAxisRange {
fn from_value(v: Value) -> Result<Self> {
if v == Value::Null {
Ok(Self(None))
} else {
Ok(Self(Some(AxisRange::from_value(v)?)))
}
}
}
pub enum XAxisRange {
Numeric { min: f64, max: f64 },
DateTime { min: DateTime<Utc>, max: DateTime<Utc> },
}
pub struct OptXAxisRange(pub Option<XAxisRange>);
impl FromValue for OptXAxisRange {
fn from_value(v: Value) -> Result<Self> {
if v == Value::Null {
return Ok(Self(None));
}
if let Ok([(_, max), (_, min)]) = v.clone().cast_to::<[(ArcStr, f64); 2]>() {
return Ok(Self(Some(XAxisRange::Numeric { min, max })));
}
let [(_, max), (_, min)] = v.cast_to::<[(ArcStr, Value); 2]>()?;
Ok(Self(Some(XAxisRange::DateTime {
min: min.cast_to::<DateTime<Utc>>()?,
max: max.cast_to::<DateTime<Utc>>()?,
})))
}
}