use chrono::{TimeZone, Utc}; use plotters::prelude::*; use std::fs::File; use std::i32; use std::io::{self, BufRead}; use std::path::Path; use std::error::Error; fn read_lines

(filename: P) -> io::Result>> where P: AsRef, { let file = File::open(filename)?; Ok(io::BufReader::new(file).lines()) } const ZONES: [(u8,i32,i32,&str, u32); 6] = [ (0,0,100, "Chill", 0x24d0e5), (1,100,120, "Light", 0x2498e5), (2,120,140, "Intensive", 0x19b854), (3,140,160, "Aerobix", 0xefab00), (4,160,180, "Anaerobic", 0xf58201), (5,180,255, "VO2 Max", 0xf03e3e), ]; fn hr_to_zone(hr: i32) -> u8 { for z in ZONES { if hr < z.2 { return z.0; } } ZONES[0].0 } const OUT_FILE_NAME: &str = "plotters-doc-data/slc-temp.png"; fn main() -> Result<(), Box> { let mut raw_data: Vec<(i32, i32)> = vec![]; let mut min = i32::MAX; let mut max = 0; if let Ok(lines) = read_lines("hr.csv") { // Consumes the iterator, returns an (Optional) String for line in lines.map_while(Result::ok) { let mut split = line.split(", "); let timestp = split.nth(0).unwrap().parse::().unwrap(); let hr = split.nth(0).unwrap().parse::().unwrap(); raw_data.push((timestp, hr)); if hr < min { min = hr; } if hr > max { max = hr; } } } println!("min: {}, max: {}", min, max); let mut zones: Vec> = vec![]; let mut prev = 255; for data in &raw_data { let zone = hr_to_zone(data.1); if zone != prev { if prev != 255 { zones.last_mut().unwrap().push((zone, data.0 , data.1)); } zones.push(vec![(zone, data.0 , data.1)]) } zones.last_mut().unwrap().push((zone, data.0 , data.1)); prev = zone; } let root = BitMapBackend::new(OUT_FILE_NAME, (raw_data.len()as u32*2 , 1024*3)).into_drawing_area(); root.fill(&BLACK)?; let mut chart = ChartBuilder::on(&root) .margin(30) .caption( "Heartrate", ("Ubuntu", 140, &WHITE), ) .set_label_area_size(LabelAreaPosition::Left, 60) .set_label_area_size(LabelAreaPosition::Right, 60) .set_label_area_size(LabelAreaPosition::Bottom, 40) .build_cartesian_2d( zones[0][0].1..zones.last().unwrap().last().unwrap().1, ((min/10)*10-10)..((max/10)*10 + 10), )?; chart .configure_mesh() .disable_x_mesh() //.disable_y_mesh() .x_labels((((max/10)+ 1) - ((min/10)-1))as usize*2 ) .max_light_lines(2) .y_desc("BPM") .axis_style(&WHITE) .light_line_style(&WHITE) .bold_line_style(&WHITE) .label_style(("Ubuntu", 50, &WHITE)) .draw()?; for zone in zones { let r: u8 = (ZONES[zone[0].0 as usize].4 >> 16 & 0xFF) as u8; let g: u8 = (ZONES[zone[0].0 as usize].4 >> 8 & 0xFF) as u8; let b: u8 = (ZONES[zone[0].0 as usize].4 & 0xFF) as u8; chart.draw_series(LineSeries::new( zone.iter().map(|x| (x.1 as i32, x.2)), ShapeStyle{ color: RGBColor(r,g,b).into(), filled: true, stroke_width: 4 } , ))?; } // To avoid the IO failure being ignored silently, we manually call the present function root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir"); println!("Result has been saved to {}", OUT_FILE_NAME); Ok(()) }