Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1484,6 +1484,22 @@ impl From<Vec<String>> for VecColumn {
}
}

#[cfg(feature = "time")]
impl From<Vec<DateTime>> for VecColumn {
fn from(v: Vec<DateTime>) -> Self {
let v: Vec<Option<DateTime>> = v.into_iter().map(Some).collect();
VecColumn::Time(v)
}
}

#[cfg(feature = "time")]
impl From<Vec<TimeDelta>> for VecColumn {
fn from(v: Vec<TimeDelta>) -> Self {
let v: Vec<Option<TimeDelta>> = v.into_iter().map(Some).collect();
VecColumn::TimeDelta(v)
}
}

impl Column for VecColumn {
fn len(&self) -> usize {
match self {
Expand Down
16 changes: 16 additions & 0 deletions src/des/series.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -59,6 +61,20 @@ impl From<Vec<String>> for DataCol {
}
}

#[cfg(feature = "time")]
impl From<Vec<time::DateTime>> for DataCol {
fn from(col: Vec<time::DateTime>) -> Self {
DataCol::Inline(col.into())
}
}

#[cfg(feature = "time")]
impl From<Vec<time::TimeDelta>> for DataCol {
fn from(col: Vec<time::TimeDelta>) -> 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.
Expand Down
35 changes: 26 additions & 9 deletions src/drawing/ticks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
Expand All @@ -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<dyn LabelFormatter> {
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)
Expand All @@ -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
),
}
}

Expand Down Expand Up @@ -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"),
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file added tests/refs/axes/datetime-locator.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions tests/refs/axes/datetime-locator.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions tests/src/tests/axes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<_>>();
let y = (0..10).map(|i| 1.0 / (i as f64 + 1.0)).collect::<Vec<_>>();

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::<Vec<_>>();
let y = (0..10).map(|i| 1.0 / (i as f64 + 1.0)).collect::<Vec<_>>();

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");
}
Loading