diff --git a/frontend/src/ui/common.rs b/frontend/src/ui/common.rs index ec8fde6..f00e625 100644 --- a/frontend/src/ui/common.rs +++ b/frontend/src/ui/common.rs @@ -1,4 +1,7 @@ -use std::collections::{BTreeMap, HashMap}; +use std::{ + borrow::BorrowMut, + collections::{BTreeMap, HashMap}, +}; use chrono::{prelude::*, Duration}; use plotters::prelude::*; @@ -25,6 +28,89 @@ pub const COLOR_REPS_RIR: usize = 4; pub const COLOR_WEIGHT: usize = 8; pub const COLOR_TIME: usize = 5; +#[derive(Clone)] +pub enum PlotType { + Circle(usize, u32), + Line(usize, u32), + Histogram(usize), +} + +pub fn plot_line_with_dots(color: usize) -> Vec { + [PlotType::Line(color, 2), PlotType::Circle(color, 2)].to_vec() +} + +#[derive(Default)] +pub struct PlotParams { + pub y_min_opt: Option, + pub y_max_opt: Option, + pub secondary: bool, +} + +impl PlotParams { + pub fn default() -> Self { + Self { + y_min_opt: None, + y_max_opt: None, + secondary: false, + } + } + + pub fn primary_range(min: f32, max: f32) -> Self { + Self { + y_min_opt: Some(min), + y_max_opt: Some(max), + secondary: false, + } + } + + pub const SECONDARY: Self = Self { + y_max_opt: None, + y_min_opt: None, + secondary: true, + }; +} + +pub struct PlotData { + pub values: Vec<(NaiveDate, f32)>, + pub plots: Vec, + pub params: PlotParams, +} + +#[derive(Clone, Copy, Default)] +pub struct Bounds { + min: f32, + max: f32, +} + +impl Bounds { + fn min_with_margin(self) -> f32 { + assert!(0. <= self.min); + assert!(self.min <= self.max); + + if self.min <= f32::EPSILON { + return self.min; + } + self.min - self.margin() + } + + fn max_with_margin(self) -> f32 { + assert!(0. <= self.min); + assert!(self.min <= self.max); + + self.max + self.margin() + } + + fn margin(self) -> f32 { + assert!(0. <= self.min); + assert!(self.min <= self.max); + + if (self.max - self.min).abs() > f32::EPSILON { + return (self.max - self.min) * 0.1; + } + 0.1 + } +} + pub struct Interval { pub first: NaiveDate, pub last: NaiveDate, @@ -647,25 +733,19 @@ pub fn view_chart( } } -pub fn plot_line_chart( - data: &[(Vec<(NaiveDate, f32)>, usize)], +pub fn plot_chart( + data: &[PlotData], x_min: NaiveDate, x_max: NaiveDate, - y_min_opt: Option, - y_max_opt: Option, theme: &data::Theme, ) -> Result, Box> { if all_zeros(data) { return Ok(None); } - let (y_min, y_max, y_margin) = determine_y_bounds( - data.iter() - .flat_map(|(s, _)| s.iter().map(|(_, y)| *y)) - .collect::>(), - y_min_opt, - y_max_opt, - ); + let (Some(primary_bounds), secondary_bounds) = determine_y_bounds(data) else { + return Ok(None); + }; let mut result = String::new(); @@ -681,196 +761,21 @@ pub fn plot_line_chart( .x_label_area_size(30f32) .y_label_area_size(40f32); - let mut chart = chart_builder.build_cartesian_2d( - x_min..x_max, - f32::max(0., y_min - y_margin)..y_max + y_margin, - )?; - - chart - .configure_mesh() - .disable_x_mesh() - .set_all_tick_mark_size(3u32) - .axis_style(color.mix(0.3)) - .bold_line_style(color.mix(0.05)) - .light_line_style(color.mix(0.0)) - .label_style(&color) - .x_labels(2) - .y_labels(6) - .draw()?; - - for (series, color_idx) in data { - let mut series = series.iter().collect::>(); - series.sort_by_key(|e| e.0); - let color = Palette99::pick(*color_idx).mix(0.9); - - chart.draw_series(LineSeries::new( - series.iter().map(|(x, y)| (*x, *y)), - color.stroke_width(2), - ))?; - - chart.draw_series( - series - .iter() - .map(|(x, y)| Circle::new((*x, *y), 2, color.filled())), - )?; - } - - root.present()?; - } - - Ok(Some(result)) -} - -pub fn plot_dual_line_chart( - data: &[(Vec<(NaiveDate, f32)>, usize)], - secondary_data: &[(Vec<(NaiveDate, f32)>, usize)], - x_min: NaiveDate, - x_max: NaiveDate, - theme: &data::Theme, -) -> Result, Box> { - if all_zeros(data) && all_zeros(secondary_data) { - return Ok(None); - } - - let (y1_min, y1_max, y1_margin) = determine_y_bounds( - data.iter() - .flat_map(|(s, _)| s.iter().map(|(_, y)| *y)) - .collect::>(), - None, - None, - ); - let (y2_min, y2_max, y2_margin) = determine_y_bounds( - secondary_data - .iter() - .flat_map(|(s, _)| s.iter().map(|(_, y)| *y)) - .collect::>(), - None, - None, - ); - - let mut result = String::new(); - - { - let root = SVGBackend::with_string(&mut result, (chart_width(), 200)).into_drawing_area(); - let (color, background_color) = colors(theme); - - root.fill(&background_color)?; - let mut chart = ChartBuilder::on(&root) .margin(10f32) .x_label_area_size(30f32) .y_label_area_size(40f32) - .right_y_label_area_size(40f32) - .build_cartesian_2d(x_min..x_max, y1_min - y1_margin..y1_max + y1_margin)? - .set_secondary_coord(x_min..x_max, y2_min - y2_margin..y2_max + y2_margin); - - chart - .configure_mesh() - .disable_x_mesh() - .set_all_tick_mark_size(3u32) - .axis_style(color.mix(0.3)) - .bold_line_style(color.mix(0.05)) - .light_line_style(color.mix(0.0)) - .label_style(&color) - .x_labels(2) - .y_labels(6) - .draw()?; - - chart - .configure_secondary_axes() - .set_all_tick_mark_size(3u32) - .axis_style(color.mix(0.3)) - .label_style(&color) - .draw()?; - - for (series, color_idx) in secondary_data { - let mut series = series.iter().collect::>(); - series.sort_by_key(|e| e.0); - let color = Palette99::pick(*color_idx).mix(0.9); - - chart.draw_secondary_series(LineSeries::new( - series.iter().map(|(x, y)| (*x, *y)), - color.stroke_width(2), - ))?; - - chart.draw_secondary_series( - series - .iter() - .map(|(x, y)| Circle::new((*x, *y), 2, color.filled())), - )?; - } - - for (series, color_idx) in data { - let mut series = series.iter().collect::>(); - series.sort_by_key(|e| e.0); - let color = Palette99::pick(*color_idx).mix(0.9); - - chart.draw_series(LineSeries::new( - series.iter().map(|(x, y)| (*x, *y)), - color.stroke_width(2), - ))?; - - chart.draw_series( - series - .iter() - .map(|(x, y)| Circle::new((*x, *y), 2, color.filled())), - )?; - } - - root.present()?; - } - - Ok(Some(result)) -} - -pub fn plot_bar_chart( - data: &[(Vec<(NaiveDate, f32)>, usize)], - secondary_data: &[(Vec<(NaiveDate, f32)>, usize)], - x_min: NaiveDate, - x_max: NaiveDate, - y_min_opt: Option, - y_max_opt: Option, - theme: &data::Theme, -) -> Result, Box> { - if all_zeros(data) && all_zeros(secondary_data) { - return Ok(None); - } - - let (y1_min, y1_max, _) = determine_y_bounds( - data.iter() - .flat_map(|(s, _)| s.iter().map(|(_, y)| *y)) - .collect::>(), - y_min_opt, - y_max_opt, - ); - let y1_margin = 0.; - let (y2_min, y2_max, y2_margin) = determine_y_bounds( - secondary_data - .iter() - .flat_map(|(s, _)| s.iter().map(|(_, y)| *y)) - .collect::>(), - None, - None, - ); - - let mut result = String::new(); - - { - let root = SVGBackend::with_string(&mut result, (chart_width(), 200)).into_drawing_area(); - let (color, background_color) = colors(theme); - - root.fill(&background_color)?; - - let mut chart = ChartBuilder::on(&root) - .margin(10f32) - .x_label_area_size(30f32) - .y_label_area_size(40f32) - .right_y_label_area_size(30f32) + .right_y_label_area_size(secondary_bounds.map_or_else(|| 0f32, |_| 40f32)) .build_cartesian_2d( - (x_min..x_max).into_segmented(), - y1_min - y1_margin..y1_max + y1_margin, + x_min..x_max, + primary_bounds.min_with_margin()..primary_bounds.max_with_margin(), )? - .set_secondary_coord(x_min..x_max, y2_min - y2_margin..y2_max + y2_margin); + .set_secondary_coord( + x_min..x_max, + secondary_bounds + .as_ref() + .map_or(0.0..0.0, |b| b.min_with_margin()..b.max_with_margin()), + ); chart .configure_mesh() @@ -884,40 +789,63 @@ pub fn plot_bar_chart( .y_labels(6) .draw()?; - chart - .configure_secondary_axes() - .set_all_tick_mark_size(3u32) - .axis_style(color.mix(0.3)) - .label_style(&color) - .draw()?; - - for (series, color_idx) in data { - let mut series = series.iter().collect::>(); - series.sort_by_key(|e| e.0); - let color = Palette99::pick(*color_idx).mix(0.9).filled(); - let histogram = Histogram::vertical(&chart) - .style(color) - .margin(0) // https://github.com/plotters-rs/plotters/issues/300 - .data(series.iter().map(|(x, y)| (*x, *y))); - - chart.draw_series(histogram)?; + if secondary_bounds.is_some() { + chart + .configure_secondary_axes() + .set_all_tick_mark_size(3u32) + .axis_style(color.mix(0.3)) + .label_style(&color) + .draw()?; } - for (series, color_idx) in secondary_data { - let mut series = series.iter().collect::>(); + for plot_data in data { + let mut series = plot_data.values.iter().collect::>(); series.sort_by_key(|e| e.0); - let color = Palette99::pick(*color_idx).mix(0.9); - - chart.draw_secondary_series(LineSeries::new( - series.iter().map(|(x, y)| (*x, *y)), - color.stroke_width(2), - ))?; - - chart.draw_secondary_series( - series - .iter() - .map(|(x, y)| Circle::new((*x, *y), 2, color.filled())), - )?; + + for plot in &plot_data.plots { + match *plot { + PlotType::Circle(color, size) => { + let data = series + .iter() + .map(|(x, y)| { + Circle::new( + (*x, *y), + size, + Palette99::pick(color).mix(0.9).filled(), + ) + }) + .collect::>(); + if plot_data.params.secondary { + chart.draw_secondary_series(data)? + } else { + chart.draw_series(data)? + } + } + PlotType::Line(color, size) => { + let data = LineSeries::new( + series.iter().map(|(x, y)| (*x, *y)), + Palette99::pick(color).mix(0.9).stroke_width(size), + ); + if plot_data.params.secondary { + chart.draw_secondary_series(data)? + } else { + chart.draw_series(data)? + } + } + PlotType::Histogram(color) => { + let data = Histogram::vertical(&chart) + .style(Palette99::pick(color).mix(0.9).filled()) + .margin(0) // https://github.com/plotters-rs/plotters/issues/300 + .data(series.iter().map(|(x, y)| (*x, *y))); + + if plot_data.params.secondary { + chart.draw_secondary_series(data)? + } else { + chart.draw_series(data)? + } + } + }; + } } root.present()?; @@ -926,8 +854,11 @@ pub fn plot_bar_chart( Ok(Some(result)) } -fn all_zeros(data: &[(Vec<(NaiveDate, f32)>, usize)]) -> bool { - return data.iter().all(|p| p.0.iter().all(|s| s.1 == 0.0)); +fn all_zeros(data: &[PlotData]) -> bool { + data.iter() + .map(|v| v.values.iter().all(|(_, v)| *v == 0.0)) + .reduce(|l, r| l && r) + .unwrap_or(true) } fn colors(theme: &data::Theme) -> (RGBColor, RGBColor) { @@ -938,26 +869,36 @@ fn colors(theme: &data::Theme) -> (RGBColor, RGBColor) { } } -fn determine_y_bounds( - y: Vec, - y_min_opt: Option, - y_max_opt: Option, -) -> (f32, f32, f32) { - let y_min = f32::min( - y_min_opt.unwrap_or(f32::MAX), - y.clone().into_iter().reduce(f32::min).unwrap_or(0.), - ); - let y_max = f32::max( - y_max_opt.unwrap_or(0.), - y.into_iter().reduce(f32::max).unwrap_or(0.), - ); - let y_margin = if (y_max - y_min).abs() > f32::EPSILON || y_min == 0. { - (y_max - y_min) * 0.1 - } else { - 0.1 - }; +fn determine_y_bounds(data: &[PlotData]) -> (Option, Option) { + let mut primary_bounds: Option = None; + let mut secondary_bounds: Option = None; + + for plot in data.iter().filter(|plot| !plot.values.is_empty()) { + let min = plot + .values + .iter() + .map(|(_, v)| *v) + .fold(plot.params.y_min_opt.unwrap_or(f32::MAX), f32::min); + let max = plot + .values + .iter() + .map(|(_, v)| *v) + .fold(plot.params.y_max_opt.unwrap_or(0.), f32::max); + + assert!(min <= max, "min={min}, max={max}"); + + let b = if plot.params.secondary { + secondary_bounds.borrow_mut() + } else { + primary_bounds.borrow_mut() + } + .get_or_insert(Bounds { min, max }); + + b.min = f32::min(b.min, min); + b.max = f32::max(b.max, max); + } - (y_min, y_max, y_margin) + (primary_bounds, secondary_bounds) } fn chart_width() -> u32 { diff --git a/frontend/src/ui/page/body_fat.rs b/frontend/src/ui/page/body_fat.rs index 5df8bdb..7061d86 100644 --- a/frontend/src/ui/page/body_fat.rs +++ b/frontend/src/ui/page/body_fat.rs @@ -742,30 +742,33 @@ fn view_chart(model: &Model, data_model: &data::Model) -> Node { ("Weight (kg)", common::COLOR_BODY_WEIGHT), ] .as_slice(), - common::plot_dual_line_chart( + common::plot_chart( &[ - ( - body_fat + common::PlotData { + values: body_weight + .iter() + .map(|bw| (bw.date, bw.weight)) + .collect::>(), + plots: common::plot_line_with_dots(common::COLOR_BODY_WEIGHT), + params: common::PlotParams::SECONDARY, + }, + common::PlotData { + values: body_fat .iter() .filter_map(|bf| bf.jp3(sex).map(|jp3| (bf.date, jp3))) .collect::>(), - common::COLOR_BODY_FAT_JP3, - ), - ( - body_fat + plots: common::plot_line_with_dots(common::COLOR_BODY_FAT_JP3), + params: common::PlotParams::default(), + }, + common::PlotData { + values: body_fat .iter() .filter_map(|bf| bf.jp7(sex).map(|jp7| (bf.date, jp7))) .collect::>(), - common::COLOR_BODY_FAT_JP7, - ), + plots: common::plot_line_with_dots(common::COLOR_BODY_FAT_JP7), + params: common::PlotParams::default(), + }, ], - &[( - body_weight - .iter() - .map(|bw| (bw.date, bw.weight)) - .collect::>(), - common::COLOR_BODY_WEIGHT, - )], model.interval.first, model.interval.last, data_model.theme(), diff --git a/frontend/src/ui/page/body_weight.rs b/frontend/src/ui/page/body_weight.rs index a043f73..67182c5 100644 --- a/frontend/src/ui/page/body_weight.rs +++ b/frontend/src/ui/page/body_weight.rs @@ -361,10 +361,10 @@ fn view_chart(model: &Model, data_model: &data::Model) -> Node { ("Avg. weight (kg)", common::COLOR_AVG_BODY_WEIGHT), ] .as_slice(), - common::plot_line_chart( + common::plot_chart( &[ - ( - data_model + common::PlotData { + values: data_model .body_weight .values() .filter(|bw| { @@ -372,10 +372,11 @@ fn view_chart(model: &Model, data_model: &data::Model) -> Node { }) .map(|bw| (bw.date, bw.weight)) .collect::>(), - common::COLOR_BODY_WEIGHT, - ), - ( - data_model + plots: common::plot_line_with_dots(common::COLOR_BODY_WEIGHT), + params: common::PlotParams::default(), + }, + common::PlotData { + values: data_model .body_weight_stats .values() .filter(|bws| { @@ -383,13 +384,12 @@ fn view_chart(model: &Model, data_model: &data::Model) -> Node { }) .filter_map(|bws| bws.avg_weight.map(|avg_weight| (bws.date, avg_weight))) .collect::>(), - common::COLOR_AVG_BODY_WEIGHT, - ), + plots: common::plot_line_with_dots(common::COLOR_AVG_BODY_WEIGHT), + params: common::PlotParams::default(), + }, ], model.interval.first, model.interval.last, - None, - None, data_model.theme(), ), true, diff --git a/frontend/src/ui/page/exercise.rs b/frontend/src/ui/page/exercise.rs index d2d256c..b15cbaa 100644 --- a/frontend/src/ui/page/exercise.rs +++ b/frontend/src/ui/page/exercise.rs @@ -501,21 +501,22 @@ pub fn view_charts( let mut labels = vec![("Repetitions", common::COLOR_REPS)]; - let mut data = vec![( - reps_rpe + let mut data = vec![common::PlotData { + values: reps_rpe .iter() .map(|(date, (avg_reps, _))| { #[allow(clippy::cast_precision_loss)] (*date, avg_reps.iter().sum::() / avg_reps.len() as f32) }) .collect::>(), - common::COLOR_REPS, - )]; + plots: common::plot_line_with_dots(common::COLOR_REPS), + params: common::PlotParams::primary_range(0., 10.), + }]; if show_rpe { labels.push(("+ Repetitions in reserve", common::COLOR_REPS_RIR)); - data.push(( - reps_rpe + data.push(common::PlotData { + values: reps_rpe .into_iter() .filter_map(|(date, (avg_reps_values, avg_rpe_values))| { #[allow(clippy::cast_precision_loss)] @@ -530,37 +531,36 @@ pub fn view_charts( } }) .collect::>(), - common::COLOR_REPS_RIR, - )); + plots: common::plot_line_with_dots(common::COLOR_REPS_RIR), + params: common::PlotParams::primary_range(0., 10.), + }); } nodes![ common::view_chart( &[("Set volume", common::COLOR_SET_VOLUME)], - common::plot_line_chart( - &[( - set_volume.into_iter().collect::>(), - common::COLOR_SET_VOLUME, - )], + common::plot_chart( + &[common::PlotData { + values: set_volume.into_iter().collect::>(), + plots: common::plot_line_with_dots(common::COLOR_SET_VOLUME), + params: common::PlotParams::primary_range(0., 10.), + }], interval.first, interval.last, - Some(0.), - Some(10.), theme, ), false, ), common::view_chart( &[("Volume load", common::COLOR_VOLUME_LOAD)], - common::plot_line_chart( - &[( - volume_load.into_iter().collect::>(), - common::COLOR_VOLUME_LOAD, - )], + common::plot_chart( + &[common::PlotData { + values: volume_load.into_iter().collect::>(), + plots: common::plot_line_with_dots(common::COLOR_VOLUME_LOAD), + params: common::PlotParams::primary_range(0., 10.), + }], interval.first, interval.last, - Some(0.), - Some(10.), theme, ), false, @@ -568,12 +568,14 @@ pub fn view_charts( IF![show_tut => common::view_chart( &[("Time under tension (s)", common::COLOR_TUT)], - common::plot_line_chart( - &[(tut.into_iter().collect::>(), common::COLOR_TUT,)], + common::plot_chart( + &[common::PlotData { + values: tut.into_iter().collect::>(), + plots: common::plot_line_with_dots(common::COLOR_TUT), + params: common::PlotParams::primary_range(0., 10.), + }], interval.first, interval.last, - Some(0.), - Some(10.), theme, ), false, @@ -581,33 +583,25 @@ pub fn view_charts( ], common::view_chart( &labels, - common::plot_line_chart( - &data, - interval.first, - interval.last, - Some(0.), - Some(10.), - theme, - ), + common::plot_chart(&data, interval.first, interval.last, theme), false, ), common::view_chart( &[("Weight (kg)", common::COLOR_WEIGHT)], - common::plot_line_chart( - &[( - weight + common::plot_chart( + &[common::PlotData { + values: weight .into_iter() .map(|(date, values)| { #[allow(clippy::cast_precision_loss)] (date, values.iter().sum::() / values.len() as f32) }) .collect::>(), - common::COLOR_WEIGHT, - )], + plots: common::plot_line_with_dots(common::COLOR_WEIGHT), + params: common::PlotParams::primary_range(0., 10.), + }], interval.first, interval.last, - Some(0.), - Some(10.), theme, ), false, @@ -615,20 +609,19 @@ pub fn view_charts( IF![show_tut => common::view_chart( &[("Time (s)", common::COLOR_TIME)], - common::plot_line_chart( - &[( - time.into_iter() + common::plot_chart( + &[common::PlotData{ + values: time.into_iter() .map(|(date, values)| { #[allow(clippy::cast_precision_loss)] (date, values.iter().sum::() / values.len() as f32) }) .collect::>(), - common::COLOR_TIME, - )], + plots: common::plot_line_with_dots(common::COLOR_TIME), + params: common::PlotParams::primary_range(0., 10.) + }], interval.first, interval.last, - Some(0.), - Some(10.), theme, ), false, diff --git a/frontend/src/ui/page/menstrual_cycle.rs b/frontend/src/ui/page/menstrual_cycle.rs index f66e29c..79681fb 100644 --- a/frontend/src/ui/page/menstrual_cycle.rs +++ b/frontend/src/ui/page/menstrual_cycle.rs @@ -345,19 +345,17 @@ fn view_chart(model: &Model, data_model: &data::Model) -> Node { common::view_chart( vec![("Intensity", common::COLOR_PERIOD_INTENSITY)].as_slice(), - common::plot_bar_chart( - &[( - period + common::plot_chart( + &[common::PlotData { + values: period .iter() .map(|p| (p.date, f32::from(p.intensity))) .collect::>(), - common::COLOR_PERIOD_INTENSITY, - )], - &[], + plots: [common::PlotType::Histogram(common::COLOR_PERIOD_INTENSITY)].to_vec(), + params: common::PlotParams::primary_range(0., 4.), + }], model.interval.first, model.interval.last, - Some(0.), - Some(4.), data_model.theme(), ), true, diff --git a/frontend/src/ui/page/muscles.rs b/frontend/src/ui/page/muscles.rs index 4ca5e34..b88feec 100644 --- a/frontend/src/ui/page/muscles.rs +++ b/frontend/src/ui/page/muscles.rs @@ -97,12 +97,14 @@ pub fn view(model: &Model, data_model: &data::Model) -> Node { ], common::view_chart( &[("Set volume (weekly total)", common::COLOR_SET_VOLUME)], - common::plot_line_chart( - &[(set_volume, common::COLOR_SET_VOLUME)], + common::plot_chart( + &[common::PlotData { + values: set_volume, + plots: common::plot_line_with_dots(common::COLOR_SET_VOLUME), + params: common::PlotParams::primary_range(0., 10.), + }], model.interval.first, model.interval.last, - Some(0.), - Some(10.), data_model.theme() ), true, diff --git a/frontend/src/ui/page/routine.rs b/frontend/src/ui/page/routine.rs index 0bbfc04..93570fc 100644 --- a/frontend/src/ui/page/routine.rs +++ b/frontend/src/ui/page/routine.rs @@ -1338,27 +1338,28 @@ pub fn view_charts( nodes![ common::view_chart( &[("Load", common::COLOR_LOAD)], - common::plot_line_chart( - &[(load.into_iter().collect::>(), common::COLOR_LOAD)], + common::plot_chart( + &[common::PlotData { + values: load.into_iter().collect::>(), + plots: common::plot_line_with_dots(common::COLOR_LOAD), + params: common::PlotParams::primary_range(0., 10.), + }], interval.first, interval.last, - Some(0.), - Some(10.), theme, ), false, ), common::view_chart( &[("Set volume", common::COLOR_SET_VOLUME)], - common::plot_line_chart( - &[( - set_volume.into_iter().collect::>(), - common::COLOR_SET_VOLUME, - )], + common::plot_chart( + &[common::PlotData { + values: set_volume.into_iter().collect::>(), + plots: common::plot_line_with_dots(common::COLOR_SET_VOLUME), + params: common::PlotParams::primary_range(0., 10.), + }], interval.first, interval.last, - Some(0.), - Some(10.), theme, ), false, @@ -1367,9 +1368,9 @@ pub fn view_charts( show_rpe => common::view_chart( &[("RPE", common::COLOR_RPE)], - common::plot_line_chart( - &[( - rpe.into_iter() + common::plot_chart( + &[common::PlotData{ + values: rpe.into_iter() .map(|(date, values)| { #[allow(clippy::cast_precision_loss)] ( @@ -1382,12 +1383,11 @@ pub fn view_charts( ) }) .collect::>(), - common::COLOR_RPE, - )], + plots: common::plot_line_with_dots(common::COLOR_RPE), + params: common::PlotParams::primary_range(5., 10.) + }], interval.first, interval.last, - Some(5.), - Some(10.), theme, ), false, diff --git a/frontend/src/ui/page/training.rs b/frontend/src/ui/page/training.rs index 8049451..43b0806 100644 --- a/frontend/src/ui/page/training.rs +++ b/frontend/src/ui/page/training.rs @@ -528,29 +528,45 @@ pub fn view_charts( ("Short-term load", common::COLOR_LOAD), ("Long-term load", common::COLOR_LONG_TERM_LOAD) ], - common::plot_line_chart( + common::plot_chart( &[ - (long_term_load_low, common::COLOR_LONG_TERM_LOAD_BOUNDS), - (long_term_load_high, common::COLOR_LONG_TERM_LOAD_BOUNDS), - (long_term_load, common::COLOR_LONG_TERM_LOAD), - (short_term_load, common::COLOR_LOAD) + common::PlotData { + values: long_term_load_low, + plots: common::plot_line_with_dots(common::COLOR_LONG_TERM_LOAD_BOUNDS), + params: common::PlotParams::primary_range(0., 10.), + }, + common::PlotData { + values: long_term_load_high, + plots: common::plot_line_with_dots(common::COLOR_LONG_TERM_LOAD_BOUNDS), + params: common::PlotParams::primary_range(0., 10.), + }, + common::PlotData { + values: long_term_load, + plots: common::plot_line_with_dots(common::COLOR_LONG_TERM_LOAD), + params: common::PlotParams::primary_range(0., 10.), + }, + common::PlotData { + values: short_term_load, + plots: common::plot_line_with_dots(common::COLOR_LOAD), + params: common::PlotParams::primary_range(0., 10.), + } ], interval.first, interval.last, - Some(0.), - Some(10.), theme, ), false, ), common::view_chart( &[("Set volume (weekly total)", common::COLOR_SET_VOLUME)], - common::plot_line_chart( - &[(total_set_volume_per_week, common::COLOR_SET_VOLUME)], + common::plot_chart( + &[common::PlotData { + values: total_set_volume_per_week, + plots: common::plot_line_with_dots(common::COLOR_SET_VOLUME), + params: common::PlotParams::primary_range(0., 10.), + }], interval.first, interval.last, - Some(0.), - Some(10.), theme, ), false, @@ -559,12 +575,13 @@ pub fn view_charts( show_rpe => common::view_chart( &[("RPE (weekly average)", common::COLOR_RPE)], - common::plot_line_chart( - &[(avg_rpe_per_week, common::COLOR_RPE)], + common::plot_chart( + &[common::PlotData{values: avg_rpe_per_week, + plots: common::plot_line_with_dots(common::COLOR_RPE), + params: common::PlotParams::primary_range(5., 10.) + }], interval.first, interval.last, - Some(5.), - Some(10.), theme, ), false,