【问题标题】:How to plot a series with a date on the x-axis and time on the y-axis?如何绘制 x 轴上的日期和 y 轴上的时间的系列?
【发布时间】: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&lt;DT: Datelike + Timelike + TimeValue&gt;(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

【问题讨论】:

    标签: plot rust


    【解决方案1】:

    在这种情况下,使用 Chrono 中的 Duration 会更容易,因为 Plotters 也实现了 RangedDuration,只是有点隐藏。

    请注意,您可以使用自定义轴标签格式化程序,因此您仍然可以根据需要将其格式化为一天中的时间。

    固定示例:

    #![feature(allocator_api)]
    
    use chrono::{Date, Duration, ParseError, 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()
    }
    
    fn parse_time(t: &str) -> Result<Duration, ParseError> {
        return match Local.datetime_from_str(&format!("2020-01-01 0:{}", t), "%Y-%m-%d %H:%M:%S%.f") {
            Ok(date) => Ok(date.time().signed_duration_since(NaiveTime::from_hms(0, 0, 0))),
            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[data.len() - 1].0) + Duration::days(1),
        );
    
        let y_min = parse_time("9:30.0").unwrap();
        let y_max = parse_time("13:00.0").unwrap();
    
        let mut chart = ChartBuilder::on(&root)
            .x_label_area_size(40)
            .y_label_area_size(50)
            .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)
            .y_label_formatter(&|y| format!("{:02}:{:02}", y.num_minutes(), y.num_seconds() % 60))
            .x_label_formatter(&|x| x.naive_local().to_string())
            .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),
        ];
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多