如何在Plotters中动态使用SegmentValue绘制Band矩形?
解决方案
以下是针对你的Plotters绘图问题的完整可运行实现,同时解决字符串类型约束与生命周期问题:
依赖配置
确保Cargo.toml包含所需依赖:
[dependencies] plotters = "0.3" chrono = { version = "0.4", features = ["naive", "serde"] }
完整代码实现
use chrono::NaiveDateTime; use plotters::prelude::*; pub struct Band { pub name: String, pub start_dt: NaiveDateTime, pub end_dt: NaiveDateTime, pub stage: String, pub selected: bool, } fn main() -> Result<(), Box<dyn std::error::Error>> { // 示例演出数据 let bands = vec![ Band { name: "Storm Riders".into(), start_dt: NaiveDateTime::parse_from_str("2024-06-01 18:30:00", "%Y-%m-%d %H:%M:%S")?, end_dt: NaiveDateTime::parse_from_str("2024-06-01 20:00:00", "%Y-%m-%d %H:%M:%S")?, stage: "主舞台".into(), selected: true, }, Band { name: "Acoustic Dream".into(), start_dt: NaiveDateTime::parse_from_str("2024-06-01 19:15:00", "%Y-%m-%d %H:%M:%S")?, end_dt: NaiveDateTime::parse_from_str("2024-06-01 20:45:00", "%Y-%m-%d %H:%M:%S")?, stage: "副舞台".into(), selected: false, }, Band { name: "Electronic Pulse".into(), start_dt: NaiveDateTime::parse_from_str("2024-06-01 20:30:00", "%Y-%m-%d %H:%M:%S")?, end_dt: NaiveDateTime::parse_from_str("2024-06-01 22:00:00", "%Y-%m-%d %H:%M:%S")?, stage: "主舞台".into(), selected: true, }, ]; // 收集并去重舞台名称,用于构建X分类轴 let mut stage_names: Vec<String> = bands.iter().map(|b| b.stage.clone()).collect(); stage_names.dedup(); let stage_count = stage_names.len(); // 确定Y轴时间范围,转换为Unix时间戳以便数值映射 let min_dt = bands.iter().map(|b| b.start_dt).min().unwrap(); let max_dt = bands.iter().map(|b| b.end_dt).max().unwrap(); let min_ts = min_dt.timestamp(); let max_ts = max_dt.timestamp(); // 创建绘图画布 let root = BitMapBackend::new("band_schedule.png", (800, 600)).into_drawing_area(); root.fill(&WHITE)?; // 构建笛卡尔坐标系 let mut chart = ChartBuilder::on(&root) .caption("音乐节演出时间表", ("sans-serif", 20).into_font()) .x_label_area_size(40) .y_label_area_size(80) .build_cartesian_2d(0..stage_count, min_ts..max_ts)?; // 配置网格与轴标签 chart.configure_mesh() .x_labels(3) .x_label_formatter(&|idx| stage_names[*idx].clone()) .y_labels(5) .y_label_formatter(&|ts| { NaiveDateTime::from_timestamp(*ts, 0).format("%H:%M").to_string() }) .draw()?; // 计算X轴每个分段的宽度 let x_unit_width = (chart.x_range().end - chart.x_range().start) as f64 / stage_count as f64; // 绘制每个乐队对应的演出时段矩形 for band in &bands { // 找到当前舞台在分类轴中的索引 let stage_idx = stage_names.iter().position(|s| s == &band.stage).unwrap(); let x_start = stage_idx as f64; let x_end = x_start + x_unit_width; // 转换演出时间为Y轴坐标 let y_start = band.start_dt.timestamp(); let y_end = band.end_dt.timestamp(); // 根据选中状态选择填充颜色 let fill_color = if band.selected { BLUE.mix(0.6) } else { GREY.mix(0.6) }; // 绘制矩形 chart.draw_rectangle( (x_start, y_start)..(x_end, y_end), fill_color.filled(), )?; // 在矩形中心绘制乐队名称 let text_pos = (x_start + x_unit_width / 2.0, (y_start + y_end) / 2.0); chart.draw_text( &band.name, ("sans-serif", 12).into_font(), text_pos, &TextStyle::from(("sans-serif", 12)).color(&BLACK).alignment(Alignment::Center), )?; } root.present()?; Ok(()) }
核心问题解决说明
字符串生命周期处理:
- 提前收集所有舞台名称到
stage_names向量并去重,确保字符串生命周期覆盖整个绘图流程。 - 通过
x_label_formatter将轴索引映射为对应舞台名称,避免临时字符串导致的生命周期不匹配。
- 提前收集所有舞台名称到
DateTime轴适配:
- 将
NaiveDateTime转换为Unix时间戳(秒),利用Plotters的LinearAxis进行数值映射。 - 通过
y_label_formatter将时间戳转换回可读的时间格式,兼顾绘图兼容性与可读性。
- 将
矩形位置计算:
- 根据舞台在分类轴中的索引计算X轴起始/结束位置,每个分段宽度由总轴长除以舞台数量得出。
- Y轴直接使用时间戳数值范围,保证演出时段的时长与顺序映射准确。
内容的提问来源于stack exchange,提问作者Tare
相关产品推荐
相关产品推荐

