【发布时间】:2021-01-28 20:50:57
【问题描述】:
我想绘制点,其中 x 坐标是日期,y 坐标是时间(例如来自 Chrono 的 NaiveTime)。
我认为使用Plotters crate 是最简单的。 不幸的是,默认情况下它不支持 y 轴上的 NaiveTime,如 GitHub issue 所示。但是,https://plotters-rs.github.io/book/basic/basic_data_plotting.html?highlight=date#time-series-chart 的文档指出
理论上,绘图仪支持任何数据类型为轴。唯一的要求是实现轴映射特征。
听起来不错。
因此,我改编了他们的Stock.rs 示例,并得到了以下内容。
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),
];
}
结果:
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>`
现在,如何实现 Trait。
...除了https://docs.rs/plotters/0.3.0/src/plotters/coord/ranged1d/types/datetime.rs.html#600 似乎已经实现。
所以,我们可以做
.build_cartesian_2d(from_date..to_date, RangedDateTime(y_min, y_max))?;
失败了
52 | .build_cartesian_2d(from_date..to_date, RangedDateTime(y_min, y_max))?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use struct literal syntax instead: `RangedDateTime { 0: val, 1: val }`
所以我们更改https://docs.rs/plotters/0.3.0/src/plotters/coord/ranged1d/types/datetime.rs.html#581 以公开结构字段:pub struct RangedDateTime<DT: Datelike + Timelike + TimeValue>(pub DT, pub DT);。
它失败了
52 | .build_cartesian_2d(from_date..to_date, RangedDateTime(y_min, y_max))?;
| ^^^^^^^^^^^^^^ the trait `ranged1d::types::datetime::TimeValue` is not implemented for `NaiveTime`
但是,我们不能为NaiveTime 实现TimeValue,因为这是当前的定义:
pub trait TimeValue: Eq {
type DateType: Datelike + PartialOrd;
// ...
}
并且NaiveTime 不是DateLike。
我不知道如何从这里开始,我考虑了以下选项:
- 将
TimeLike添加到TimeValue的关联类型的限制中, - 从
TimeValue的关联类型中删除DateLike, - 添加一个单独的特征
DateTimeValue,它可以将TimeLike作为它的DateType。
【问题讨论】: