use std::collections::HashMap;
use std::marker::PhantomData;
use std::sync::Arc;
use crate::color::Color;
use crate::geometry::Rect;
use crate::plot::scale::Scale;
use crate::plot::value::{DataColumn, Date, DateTime, Duration, Time, Value};
use crate::scales::geometry::Geometry;
use crate::scene::SceneBuilder;
use crate::shape::ShapeRegistry;
pub mod bspline;
pub(crate) mod bspline_eval;
pub mod ellipse;
pub mod geometry;
pub mod line;
pub(crate) mod marks;
pub(crate) mod outline;
pub mod point;
pub mod polygon;
pub(crate) mod project;
pub mod rect;
pub mod resolve;
pub mod ribbon;
pub mod ribbon_bspline;
pub mod segment;
pub mod state;
pub mod text;
pub mod text_fit;
pub mod text_path;
pub mod wedge;
pub use bspline::BSplineGeom;
pub use ellipse::EllipseGeom;
pub use geometry::GeometryGeom;
pub use line::LineGeom;
pub use point::PointGeom;
pub use polygon::PolygonGeom;
pub use rect::RectGeom;
pub use ribbon::RibbonGeom;
pub use ribbon_bspline::RibbonBSplineGeom;
pub use segment::SegmentGeom;
pub use state::{GeomState, KeysStrategy};
pub use text::TextGeom;
pub use text_fit::TextFitGeom;
pub use text_path::TextPathGeom;
pub use wedge::WedgeGeom;
#[derive(Clone, Debug)]
pub enum Channel {
Constant(Value),
Data(DataColumn),
RawConstant(Value),
RawData(DataColumn),
}
impl Channel {
pub fn is_data(&self) -> bool {
matches!(self, Channel::Data(_) | Channel::RawData(_))
}
pub fn data_len(&self) -> Option<usize> {
match self {
Channel::Constant(_) | Channel::RawConstant(_) => None,
Channel::Data(c) | Channel::RawData(c) => Some(c.len()),
}
}
}
#[derive(Clone, Debug)]
pub struct Raw<T>(pub T);
impl From<DataColumn> for Channel {
fn from(col: DataColumn) -> Self {
Channel::Data(col)
}
}
impl From<Value> for Channel {
fn from(v: Value) -> Self {
Channel::Constant(v)
}
}
macro_rules! impl_channel_from_vec {
($t:ty) => {
impl From<Vec<$t>> for Channel {
fn from(v: Vec<$t>) -> Self {
Channel::Data(v.into())
}
}
};
}
impl_channel_from_vec!(f64);
impl_channel_from_vec!(f32);
impl_channel_from_vec!(i32);
impl_channel_from_vec!(i64);
impl_channel_from_vec!(bool);
impl_channel_from_vec!(&'static str);
impl_channel_from_vec!(String);
impl_channel_from_vec!(Arc<str>);
impl_channel_from_vec!(Color);
impl_channel_from_vec!(Date);
impl_channel_from_vec!(DateTime);
impl_channel_from_vec!(Time);
impl_channel_from_vec!(Duration);
impl_channel_from_vec!(Geometry);
impl_channel_from_vec!(Arc<Geometry>);
impl From<std::ops::Range<i64>> for Channel {
fn from(r: std::ops::Range<i64>) -> Self {
Channel::Data(r.into())
}
}
macro_rules! impl_channel_from_scalar {
($t:ty) => {
impl From<$t> for Channel {
fn from(v: $t) -> Self {
Channel::Constant(Value::from(v))
}
}
};
}
impl_channel_from_scalar!(f64);
impl_channel_from_scalar!(f32);
impl_channel_from_scalar!(i32);
impl_channel_from_scalar!(i64);
impl_channel_from_scalar!(bool);
impl_channel_from_scalar!(&'static str);
impl_channel_from_scalar!(String);
impl_channel_from_scalar!(Arc<str>);
impl_channel_from_scalar!(Color);
impl_channel_from_scalar!(Date);
impl_channel_from_scalar!(DateTime);
impl_channel_from_scalar!(Time);
impl_channel_from_scalar!(Duration);
impl_channel_from_scalar!(Geometry);
impl_channel_from_scalar!(Arc<Geometry>);
impl From<Raw<DataColumn>> for Channel {
fn from(r: Raw<DataColumn>) -> Self {
Channel::RawData(r.0)
}
}
impl From<Raw<Value>> for Channel {
fn from(r: Raw<Value>) -> Self {
Channel::RawConstant(r.0)
}
}
macro_rules! impl_channel_from_raw_vec {
($t:ty) => {
impl From<Raw<Vec<$t>>> for Channel {
fn from(r: Raw<Vec<$t>>) -> Self {
Channel::RawData(r.0.into())
}
}
};
}
impl_channel_from_raw_vec!(f64);
impl_channel_from_raw_vec!(f32);
impl_channel_from_raw_vec!(i32);
impl_channel_from_raw_vec!(i64);
impl_channel_from_raw_vec!(bool);
impl_channel_from_raw_vec!(&'static str);
impl_channel_from_raw_vec!(String);
impl_channel_from_raw_vec!(Arc<str>);
impl_channel_from_raw_vec!(Color);
impl_channel_from_raw_vec!(Date);
impl_channel_from_raw_vec!(DateTime);
impl_channel_from_raw_vec!(Time);
impl_channel_from_raw_vec!(Duration);
impl From<Raw<std::ops::Range<i64>>> for Channel {
fn from(r: Raw<std::ops::Range<i64>>) -> Self {
Channel::RawData(r.0.into())
}
}
macro_rules! impl_channel_from_raw_scalar {
($t:ty) => {
impl From<Raw<$t>> for Channel {
fn from(r: Raw<$t>) -> Self {
Channel::RawConstant(Value::from(r.0))
}
}
};
}
impl_channel_from_raw_scalar!(f64);
impl_channel_from_raw_scalar!(f32);
impl_channel_from_raw_scalar!(i32);
impl_channel_from_raw_scalar!(i64);
impl_channel_from_raw_scalar!(bool);
impl_channel_from_raw_scalar!(&'static str);
impl_channel_from_raw_scalar!(String);
impl_channel_from_raw_scalar!(Arc<str>);
impl_channel_from_raw_scalar!(Color);
impl_channel_from_raw_scalar!(Date);
impl_channel_from_raw_scalar!(DateTime);
impl_channel_from_raw_scalar!(Time);
impl_channel_from_raw_scalar!(Duration);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ChannelDecl {
pub name: &'static str,
pub data_bound: bool,
pub expected_output: ExpectedOutput,
}
pub mod linetype;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ExpectedOutput {
Numbers,
Colors,
Strings,
Linetypes,
Any,
}
pub trait ScaleResolver {
fn scale_for(&self, channel: &str) -> Option<&Scale>;
}
pub struct DirectScaleResolver<'a> {
scales: HashMap<&'static str, &'a Scale>,
}
impl<'a> DirectScaleResolver<'a> {
pub fn new() -> Self {
Self {
scales: HashMap::new(),
}
}
pub fn with(mut self, channel: &'static str, scale: &'a Scale) -> Self {
self.scales.insert(channel, scale);
self
}
}
impl<'a> Default for DirectScaleResolver<'a> {
fn default() -> Self {
Self::new()
}
}
impl<'a> ScaleResolver for DirectScaleResolver<'a> {
fn scale_for(&self, channel: &str) -> Option<&Scale> {
self.scales.get(channel).copied()
}
}
pub struct GeomContext<'a> {
pub panel_rect: Rect,
pub dpi: f64,
pub shapes: &'a ShapeRegistry,
pub scales: &'a dyn ScaleResolver,
pub projection: &'a crate::plot::projection::Projection,
pub theme: &'a crate::plot::theme::Theme,
}
impl<'a> GeomContext<'a> {
pub fn new(
panel_rect: Rect,
dpi: f64,
shapes: &'a ShapeRegistry,
scales: &'a dyn ScaleResolver,
) -> Self {
Self {
panel_rect,
dpi,
shapes,
scales,
projection: &crate::plot::projection::Projection::Cartesian,
theme: default_theme_ref(),
}
}
pub fn with_projection(
panel_rect: Rect,
dpi: f64,
shapes: &'a ShapeRegistry,
scales: &'a dyn ScaleResolver,
projection: &'a crate::plot::projection::Projection,
) -> Self {
Self {
panel_rect,
dpi,
shapes,
scales,
projection,
theme: default_theme_ref(),
}
}
pub fn with_theme(mut self, theme: &'a crate::plot::theme::Theme) -> Self {
self.theme = theme;
self
}
pub fn scale_for(&self, channel: &str) -> Option<&Scale> {
self.scales.scale_for(channel)
}
}
fn default_theme_ref() -> &'static crate::plot::theme::Theme {
use std::sync::OnceLock;
static DEFAULT_THEME: OnceLock<crate::plot::theme::Theme> = OnceLock::new();
DEFAULT_THEME.get_or_init(crate::plot::theme::Theme::default)
}
#[derive(Clone, Debug)]
pub enum Keys {
Positional(usize),
Explicit(DataColumn),
}
impl Keys {
pub fn len(&self) -> usize {
match self {
Keys::Positional(n) => *n,
Keys::Explicit(col) => col.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn is_explicit(&self) -> bool {
matches!(self, Keys::Explicit(_))
}
pub fn empty_like(&self) -> Keys {
match self {
Keys::Positional(_) => Keys::Positional(0),
Keys::Explicit(col) => Keys::Explicit(empty_datacolumn_like(col)),
}
}
}
pub(crate) fn empty_datacolumn_like(col: &DataColumn) -> DataColumn {
match col {
DataColumn::F64(_) => DataColumn::F64(Vec::new()),
DataColumn::F32(_) => DataColumn::F32(Vec::new()),
DataColumn::I32(_) => DataColumn::I32(Vec::new()),
DataColumn::I64(_) => DataColumn::I64(Vec::new()),
DataColumn::Bool(_) => DataColumn::Bool(Vec::new()),
DataColumn::String(_) => DataColumn::String(Vec::new()),
DataColumn::Color(_) => DataColumn::Color(Vec::new()),
DataColumn::Date(_) => DataColumn::Date(Vec::new()),
DataColumn::DateTime(_) => DataColumn::DateTime(Vec::new()),
DataColumn::Time(_) => DataColumn::Time(Vec::new()),
DataColumn::Duration(_) => DataColumn::Duration(Vec::new()),
DataColumn::Linetype(_) => DataColumn::Linetype(Vec::new()),
DataColumn::Geometry(_) => DataColumn::Geometry(Vec::new()),
}
}
pub struct GeomBuilder<G: BuildableGeom> {
keys: Option<DataColumn>,
channels: HashMap<String, Channel>,
_phantom: PhantomData<fn() -> G>,
}
impl<G: BuildableGeom> Default for GeomBuilder<G> {
fn default() -> Self {
Self {
keys: None,
channels: HashMap::new(),
_phantom: PhantomData,
}
}
}
impl<G: BuildableGeom> GeomBuilder<G> {
pub fn new() -> Self {
Self::default()
}
pub(crate) fn from_parts(keys: Option<DataColumn>, channels: HashMap<String, Channel>) -> Self {
Self {
keys,
channels,
_phantom: PhantomData,
}
}
pub fn keys(&mut self, keys: impl Into<DataColumn>) -> &mut Self {
self.keys = Some(keys.into());
self
}
pub fn set(&mut self, channel: impl Into<String>, value: impl Into<Channel>) -> &mut Self {
self.channels.insert(channel.into(), value.into());
self
}
pub fn build(&mut self) -> G {
G::build_from(std::mem::take(self))
}
pub fn into_parts(self) -> (Option<DataColumn>, HashMap<String, Channel>) {
(self.keys, self.channels)
}
}
pub trait BuildableGeom: Geom + Sized {
fn build_from(builder: GeomBuilder<Self>) -> Self;
}
pub trait Geom: 'static {
fn state(&self) -> &GeomState;
fn state_mut(&mut self) -> &mut GeomState;
fn draw(&self, scene: &mut dyn SceneBuilder, ctx: &GeomContext<'_>);
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
fn declared_channels(&self) -> &[ChannelDecl] {
&self.state().declared
}
fn len(&self) -> usize {
self.state().len()
}
fn is_empty(&self) -> bool {
self.state().is_empty()
}
fn mark_count(&self) -> usize {
self.len()
}
fn rebuild_diff_against_previous(&mut self) {
self.state_mut().rebuild_diff_against_previous();
}
fn invalidate_caches(&mut self) {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::plot::scale;
#[test]
fn direct_resolver_returns_bound_scale() {
let s = scale::continuous(0.0..=1.0);
let resolver = DirectScaleResolver::new().with("x", &s);
assert!(resolver.scale_for("x").is_some());
assert!(resolver.scale_for("y").is_none());
}
#[test]
fn channel_constant_is_not_data() {
let c = Channel::Constant(Value::Number(1.0));
assert!(!c.is_data());
assert!(c.data_len().is_none());
}
#[test]
fn channel_data_len() {
let c = Channel::Data(vec![1.0_f64, 2.0, 3.0].into());
assert!(c.is_data());
assert_eq!(c.data_len(), Some(3));
}
#[test]
fn vec_f64_into_channel_is_data() {
let c: Channel = vec![1.0_f64, 2.0].into();
assert!(matches!(c, Channel::Data(_)));
}
#[test]
fn scalar_into_channel_is_constant() {
let c: Channel = 5.0_f64.into();
assert!(matches!(c, Channel::Constant(_)));
}
#[test]
fn static_str_into_channel_is_constant() {
let c: Channel = "circle".into();
assert!(matches!(c, Channel::Constant(_)));
}
#[test]
fn vec_str_into_channel_is_data() {
let c: Channel = vec!["a", "b"].into();
assert!(matches!(c, Channel::Data(_)));
}
#[test]
fn color_into_channel_is_constant() {
let c: Channel = Color::new([1.0, 0.0, 0.0, 1.0]).into();
assert!(matches!(c, Channel::Constant(_)));
}
#[test]
fn vec_color_into_channel_is_data() {
let c: Channel = vec![Color::new([1.0, 0.0, 0.0, 1.0])].into();
assert!(matches!(c, Channel::Data(_)));
}
#[test]
fn raw_scalar_into_channel_is_raw_constant() {
let c: Channel = Raw(5.0_f64).into();
assert!(matches!(c, Channel::RawConstant(_)));
assert!(!c.is_data());
assert!(c.data_len().is_none());
}
#[test]
fn raw_vec_into_channel_is_raw_data() {
let c: Channel = Raw(vec![0.1_f64, 0.5, 0.9]).into();
assert!(matches!(c, Channel::RawData(_)));
assert!(c.is_data());
assert_eq!(c.data_len(), Some(3));
}
#[test]
fn raw_color_into_channel_is_raw_constant() {
let c: Channel = Raw(Color::new([1.0, 0.0, 0.0, 1.0])).into();
assert!(matches!(c, Channel::RawConstant(_)));
}
#[test]
fn raw_str_vec_into_channel_is_raw_data() {
let c: Channel = Raw(vec!["a", "b"]).into();
assert!(matches!(c, Channel::RawData(_)));
assert_eq!(c.data_len(), Some(2));
}
}