use std::io::{Write, stderr, stdin, stdout}; use std::process::exit; use chrono::{Datelike, NaiveDate}; use clap::{Parser, ValueEnum}; use timechart::heatmap::{CalendarProjection, Heatmap, IsoProjection, Projection}; use timechart::{Data, Result}; /// Render a calendar-like heatmap from a list of dates and values. /// /// Values are read from stdin. Output is written to stdout. #[derive(Parser, Debug)] struct Args { /// The size of each square in the heat map (in px) #[arg(long, default_value = "10")] size: u32, /// The padding between the squares (in px) #[arg(long, default_value = "2")] padding: u32, /// The color scale #[arg( long, default_values = ["#e7ebef","#deebf7","#c6dbef","#9ecae1","#6baed6","#2171b5"] )] colors: Vec, /// The unit description to use in the captions #[arg(long)] unit: Option, /// Use a log scale instead of a linear scale #[arg(long, default_value = "true")] log_scale: bool, #[arg( long("mode"), value_enum, default_value_t = Mode::Calendar )] mode: Mode, } #[derive(Debug, Clone, ValueEnum)] enum Mode { Calendar, Iso, } fn main() { match run() { Ok(_) => {} Err(e) => { let _ = writeln!(stderr(), "Error: {e}"); exit(1); } } } fn run() -> Result<()> { let args = Args::parse(); // Read data from stdin. let mut data = Data::new(); for line in stdin().lines() { if let Some((ts, value)) = line?.split_once(' ') { let date: NaiveDate = ts.parse()?; let value: f64 = value.parse()?; data.entry(date) .and_modify(|v| *v += value) .or_insert(value); } } // Find the range of dates that we will render. let (start, end) = data .keys() .copied() .fold((NaiveDate::MAX, NaiveDate::MIN), |acc, v| { let min = if acc.0 < v { acc.0 } else { NaiveDate::from_ymd_opt(v.year(), 1, 1).unwrap() }; let max = if acc.1 > v { acc.1 } else { NaiveDate::from_ymd_opt(v.year(), 12, 31).unwrap() }; (min, max) }); // Choose a drawing style. let projection: Box = match args.mode { Mode::Calendar => Box::new(CalendarProjection::new(start, end, args.size, args.padding)), Mode::Iso => Box::new(IsoProjection::new(start, end, args.size, args.padding)), }; // Render the heatmap. let heatmap = Heatmap { projection, colors: args.colors, unit: args.unit, log_scale: args.log_scale, }; heatmap.render(&data, stdout()) }