diff --git a/src/data.rs b/src/data.rs index 5a6bafc..ac426ed 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1484,6 +1484,22 @@ impl From> for VecColumn { } } +#[cfg(feature = "time")] +impl From> for VecColumn { + fn from(v: Vec) -> Self { + let v: Vec> = v.into_iter().map(Some).collect(); + VecColumn::Time(v) + } +} + +#[cfg(feature = "time")] +impl From> for VecColumn { + fn from(v: Vec) -> Self { + let v: Vec> = v.into_iter().map(Some).collect(); + VecColumn::TimeDelta(v) + } +} + impl Column for VecColumn { fn len(&self) -> usize { match self { diff --git a/src/des/series.rs b/src/des/series.rs index ab059e5..755ad6a 100644 --- a/src/des/series.rs +++ b/src/des/series.rs @@ -2,6 +2,8 @@ use crate::data; use crate::des::axis; use crate::style::{self, defaults}; +#[cfg(feature = "time")] +use crate::time; /// A data column, either inline or a reference to a data source. /// @@ -59,6 +61,20 @@ impl From> for DataCol { } } +#[cfg(feature = "time")] +impl From> for DataCol { + fn from(col: Vec) -> Self { + DataCol::Inline(col.into()) + } +} + +#[cfg(feature = "time")] +impl From> for DataCol { + fn from(col: Vec) -> Self { + DataCol::Inline(col.into()) + } +} + /// A data series to be plotted in a plot. /// /// This enum represents the different types of series that can be visualized. diff --git a/src/drawing/ticks.rs b/src/drawing/ticks.rs index 51f977d..f07776d 100644 --- a/src/drawing/ticks.rs +++ b/src/drawing/ticks.rs @@ -35,6 +35,13 @@ pub fn locate_num( Ok(LogLocator::new_major(*base).ticks(nb)) } #[cfg(feature = "time")] + (Locator::DateTime(_), Scale::Auto | Scale::Linear { .. }) => { + Ok(locate_datetime(&locator, nb.into())? + .into_iter() + .map(|dt| dt.timestamp()) + .collect()) + } + #[cfg(feature = "time")] (Locator::TimeDelta(loc), Scale::Auto | Scale::Linear { .. }) => { locate_timedelta_num(loc, nb) } @@ -492,9 +499,7 @@ pub fn num_label_formatter( match ticks.formatter() { None => Arc::new(NullFormat), Some(Formatter::Auto) if scale.is_shared() => Arc::new(NullFormat), - Some(Formatter::Auto | Formatter::SharedAuto) => { - auto_label_formatter(ticks.locator(), ab, scale) - } + Some(Formatter::Auto | Formatter::SharedAuto) => auto_label_formatter(ticks, ab, scale), Some(Formatter::Prec(prec)) => Arc::new(PrecLabelFormat(*prec)), Some(Formatter::Percent(fmt)) => { let prec = fmt @@ -505,16 +510,16 @@ pub fn num_label_formatter( #[cfg(feature = "time")] Some(Formatter::TimeDelta(tdfmt)) => timedelta_label_formatter(ab, tdfmt), #[cfg(feature = "time")] - _ => todo!(), + Some(Formatter::DateTime(_)) => datetime_label_formatter(ticks, ab.into(), scale).unwrap(), } } fn auto_label_formatter( - locator: &Locator, + ticks: &Ticks, ab: axis::NumBounds, scale: &Scale, ) -> Arc { - match (locator, scale) { + match (ticks.locator(), scale) { (Locator::PiMultiple { .. }, _) => Arc::new(PiMultipleLabelFormat { prec: 2 }), (Locator::Auto, Scale::Log(LogScale { base, .. })) if *base == 10.0 => { Arc::new(SciLabelFormat) @@ -531,7 +536,13 @@ fn auto_label_formatter( Arc::new(PrecLabelFormat(2)) } } - _ => todo!(), + #[cfg(feature = "time")] + (Locator::DateTime(_), _) => datetime_label_formatter(ticks, ab.into(), scale).unwrap(), + _ => todo!( + "auto label formatter for locator {:?} and scale {:?}", + ticks.locator(), + scale + ), } } @@ -694,8 +705,14 @@ struct DateTimeLabelFormat { #[cfg(feature = "time")] impl LabelFormatter for DateTimeLabelFormat { fn format_label(&self, data: data::SampleRef) -> String { - let dt = data.as_time().unwrap(); - format!("{}", dt.fmt_to_string(&self.fmt)) + match data { + data::SampleRef::Time(dt) => format!("{}", dt.fmt_to_string(&self.fmt)), + data::SampleRef::Num(num) => { + let dt = DateTime::from_timestamp(num).expect("Invalid timestamp"); + format!("{}", dt.fmt_to_string(&self.fmt)) + } + _ => panic!("data is not compatible with formatter"), + } } } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index bae5bca..e2475c5 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -10,7 +10,7 @@ keywords.workspace = true publish = false [dependencies] -plotive.workspace = true +plotive = { workspace = true, features=["time"] } plotive-pxl.workspace = true plotive-svg.workspace = true tiny-skia.workspace = true diff --git a/tests/refs/axes/datetime-locator.png b/tests/refs/axes/datetime-locator.png new file mode 100644 index 0000000..74f963d Binary files /dev/null and b/tests/refs/axes/datetime-locator.png differ diff --git a/tests/refs/axes/datetime-locator.svg b/tests/refs/axes/datetime-locator.svg new file mode 100644 index 0000000..70630bd --- /dev/null +++ b/tests/refs/axes/datetime-locator.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/src/tests/axes.rs b/tests/src/tests/axes.rs index 94d4161..df51671 100644 --- a/tests/src/tests/axes.rs +++ b/tests/src/tests/axes.rs @@ -364,3 +364,44 @@ fn axes_multiple_trbl_titles() { assert_fig_eq_ref!(&fig, "axes/multiple-trbl-titles"); } + +#[test] +fn axes_datetime_locator() { + use plotive::time; + + let start = time::DateTime::fmt_parse("2020-01-01", "%Y-%m-%d").unwrap(); + let x = (0..10) + .map(|i| start + time::TimeDelta::from_days(i as f64)) + .collect::>(); + let y = (0..10).map(|i| 1.0 / (i as f64 + 1.0)).collect::>(); + + let series = des::series::Line::new(x.into(), y.into()); + + let plot = des::Plot::new(vec![series.into()]).with_x_axis(des::Axis::new().with_ticks( + des::axis::Ticks::new().with_locator(des::axis::ticks::DateTimeLocator::Days(2).into()), + )); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "axes/datetime-locator"); +} + +#[test] +fn axes_num_datetime_locator() { + use plotive::time; + + let start = time::DateTime::fmt_parse("2020-01-01", "%Y-%m-%d").unwrap(); + let x = (0..10) + .map(|i| start + time::TimeDelta::from_days(i as f64)) + .map(|dt| dt.timestamp()) + .collect::>(); + let y = (0..10).map(|i| 1.0 / (i as f64 + 1.0)).collect::>(); + + let series = des::series::Line::new(x.into(), y.into()); + + let plot = des::Plot::new(vec![series.into()]).with_x_axis(des::Axis::new().with_ticks( + des::axis::Ticks::new().with_locator(des::axis::ticks::DateTimeLocator::Days(2).into()), + )); + let fig = fig_small(plot); + + assert_fig_eq_ref!(&fig, "axes/datetime-locator"); +}