120 lines
3.6 KiB
Rust
120 lines
3.6 KiB
Rust
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<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
|
|
where P: AsRef<Path>, {
|
|
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<dyn Error>> {
|
|
|
|
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::<i32>().unwrap();
|
|
let hr = split.nth(0).unwrap().parse::<i32>().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<(u8,i32,i32)>> = 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(())
|
|
}
|