首页 > 解决方案 > 如何绘制 x 轴上的日期和 y 轴上的时间的系列?

问题描述

我想绘制点,其中 x 坐标是日期,y 坐标是时间(如NaiveTime来自 Chrono)。

我认为这将是使用Plotters板条箱最简单的方法。不幸的是,默认情况下它不支持 y 轴上的 NaiveTime,如GitHub 问题所示。但是,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`

但是,我们不能实现TimeValuefor NaiveTime,因为这是当前的定义:

pub trait TimeValue: Eq {
    type DateType: Datelike + PartialOrd;
    // ...
}

NaiveTime不是DateLike。我不知道如何从这里开始,我考虑了以下选项:

标签: plotrust

解决方案


在这种情况下,它更容易Duration从 Chrono 中使用,因为 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),
    ];
}

在此处输入图像描述


推荐阅读