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(())
}