I want to plot points, where the x-coordinate is a Date and the y-coordinate is a Time (like a NaiveTime
from Chrono).
I think this will be easiest using the Plotters crate.
Unfortunately, it doesn't support NaiveTime on the y-axis by default, as shown by this GitHub issue. However, the documentation at https://plotters-rs.github.io/book/basic/basic_data_plotting.html?highlight=date#time-series-chart states that
In theory Plotters supports any data type to be axis. The only requirement is to implement the axis mapping traits.
So that sounds good.
Therefore, I adapted their Stock.rs example, and got to the following.
use chrono::{Date, Duration, ParseError, DateTime, Utc, NaiveTime};
use chrono::offset::{Local, TimeZone};
use plotters::prelude::*;
fn parse_datetime(t: &str) -> Date<Local> {
Local
.datetime_from_str(&format!("{} 0:0", t), "%Y-%m-%d %H:%M")
.unwrap()
.date()
}
/// Workaround attempt: use a mock Date in order to get a DateTime instead of a NaiveTime
fn parse_time_as_datetime(t: &str) -> Result<DateTime<Local>, ParseError> {
return match Local.datetime_from_str(&format!("2020-01-01 {}", t), "%Y-%m-%d %H:%M.%S") {
Ok(date) => Ok(date),
Err(e) => { println!("{}", e); Err(e) },
};
}
fn parse_time(t: &str) -> Result<NaiveTime, ParseError> {
return match Local.datetime_from_str(t, "%M:%S%.f") {
Ok(date) => Ok(date.time()),
Err(e) => { println!("{}", e); Err(e) },
};
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let data = get_data();
let root = BitMapBackend::new("stock-example.png", (1024, 768)).into_drawing_area();
root.fill(&WHITE)?;
let (from_date, to_date) = (
parse_datetime(&data[0].0) + Duration::days(1),
parse_datetime(&data[4].0) - Duration::days(1),
);
let y_min = parse_time("9:30.0").unwrap();
let y_max = parse_time("13:00.0").unwrap();
// Workaround attempt: use DateTime instead of NaiveTime
// let y_min = Local.datetime_from_str("2020-01-01 0:0", "%Y-%m-%d %H:%M").unwrap().date();
// let y_max = Local.datetime_from_str("2020-01-02 0:1", "%Y-%m-%d %H:%M").unwrap().date();
let mut chart = ChartBuilder::on(&root)
.x_label_area_size(40)
.y_label_area_size(40)
.caption("Time", ("sans-serif", 30.0).into_font())
.build_cartesian_2d(from_date..to_date, y_min..y_max)?;
chart.configure_mesh().light_line_style(&WHITE).draw()?;
chart.draw_series(
data.iter()
.map(|x| Circle::new((parse_datetime(x.0), parse_time(x.1).unwrap()), 5, BLUE.filled())),
)?;
Ok(())
}
fn get_data() -> Vec<(&'static str, &'static str, f32, f32, f32)> {
return vec![
("2019-04-18", "10:11.5", 16.0, 121.3018, 123.3700),
("2019-04-22", "10:52.2", 15.0, 122.5700, 123.7600),
("2019-04-23", "12:23.5", 14.0, 123.8300, 125.4400),
("2019-04-24", "10:15.0", 13.0, 124.5200, 125.0100),
("2019-04-25", "10:43.9", 12.0, 128.8300, 129.1500),
];
}
Result:
error[E0277]: the trait bound `std::ops::Range<NaiveTime>: plotters::prelude::Ranged` is not satisfied
--> src/main.rs:47:49
|
47 | .build_cartesian_2d(from_date..to_date, y_min..y_max)?;
| ^^^^^^^^^^^^ the trait `plotters::prelude::Ranged` is not implemented for `std::ops::Range<NaiveTime>`
Now, how to implement the Trait.
... except that in https://docs.rs/plotters/0.3.0/src/plotters/coord/ranged1d/types/datetime.rs.html#600 it seems to be already implemented.
So, we can do
.build_cartesian_2d(from_date..to_date, RangedDateTime(y_min, y_max))?;
which fails with
52 | .build_cartesian_2d(from_date..to_date, RangedDateTime(y_min, y_max))?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use struct literal syntax instead: `RangedDateTime { 0: val, 1: val }`
So we change https://docs.rs/plotters/0.3.0/src/plotters/coord/ranged1d/types/datetime.rs.html#581 to make the struct fields public: pub struct RangedDateTime<DT: Datelike + Timelike + TimeValue>(pub DT, pub DT);
.
It fails with
52 | .build_cartesian_2d(from_date..to_date, RangedDateTime(y_min, y_max))?;
| ^^^^^^^^^^^^^^ the trait `ranged1d::types::datetime::TimeValue` is not implemented for `NaiveTime`
However, we cannot implement TimeValue
for NaiveTime
, because this is the current definition:
pub trait TimeValue: Eq {
type DateType: Datelike + PartialOrd;
// ...
}
and NaiveTime
is not DateLike
.
I don't know how to go from here, I considered the following options:
- Add
TimeLike
to the restriction of the associated type of TimeValue
,
- Remove
DateLike
from the asscociated type of TimeValue
,
- Add a separate trait
DateTimeValue
which can have TimeLike
as its DateType
.
question from:
https://stackoverflow.com/questions/65937447/how-to-plot-a-series-with-a-date-on-the-x-axis-and-time-on-the-y-axis