Skip to content

Commit

Permalink
WIP: Try alternative graph layouts
Browse files Browse the repository at this point in the history
  • Loading branch information
senier committed Nov 10, 2024
1 parent 3a4cd35 commit 1d1bba5
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 246 deletions.
258 changes: 150 additions & 108 deletions frontend/src/ui/common.rs

Large diffs are not rendered by default.

59 changes: 19 additions & 40 deletions frontend/src/ui/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ pub fn init(url: Url, _orders: &mut impl Orders<Msg>) -> Model {
training_stats: TrainingStats {
short_term_load: Vec::new(),
long_term_load: Vec::new(),
stimulus_per_muscle: BTreeMap::new(),
},
settings,
ongoing_training_session,
Expand Down Expand Up @@ -270,19 +269,24 @@ pub struct CycleStats {
}

pub struct TrainingStats {
pub short_term_load: Vec<(NaiveDate, f32)>,
pub long_term_load: Vec<(NaiveDate, f32)>,
pub stimulus_per_muscle: BTreeMap<u8, Vec<(NaiveDate, f32)>>,
pub short_term_load: Vec<(NaiveDate, Option<f32>)>,
pub long_term_load: Vec<(NaiveDate, Option<f32>)>,
}

impl TrainingStats {
pub const LOAD_RATIO_LOW: f32 = 0.8;
pub const LOAD_RATIO_HIGH: f32 = 1.5;

pub fn load_ratio(&self) -> Option<f32> {
let long_term_load = self.long_term_load.last().map_or(0., |(_, l)| *l);
let long_term_load = self
.long_term_load
.last()
.map_or(0., |(_, l)| l.map_or(0., |v| v));
if long_term_load > 0. {
let short_term_load = self.short_term_load.last().map_or(0., |(_, l)| *l);
let short_term_load = self
.short_term_load
.last()
.map_or(0., |(_, l)| l.map_or(0., |v| v));
Some(short_term_load / long_term_load)
} else {
None
Expand Down Expand Up @@ -878,14 +882,13 @@ fn calculate_training_stats(
TrainingStats {
short_term_load,
long_term_load,
stimulus_per_muscle: calculate_daily_stimulus_per_muscle(training_sessions, exercises),
}
}

fn calculate_weighted_sum_of_load(
training_sessions: &[&TrainingSession],
window_size: usize,
) -> Vec<(NaiveDate, f32)> {
) -> Vec<(NaiveDate, Option<f32>)> {
let mut result: BTreeMap<NaiveDate, f32> = BTreeMap::new();

let today = Local::now().date_naive();
Expand Down Expand Up @@ -916,56 +919,32 @@ fn calculate_weighted_sum_of_load(
window[0] = load;
(
date,
zip(&window, &weighting)
.map(|(load, weight)| load * weight)
.sum(),
Some(
zip(&window, &weighting)
.map(|(load, weight)| load * weight)
.sum(),
),
)
})
.collect()
}

fn calculate_average_weighted_sum_of_load(
weighted_sum_of_load: &[(NaiveDate, f32)],
weighted_sum_of_load: &[(NaiveDate, Option<f32>)],
window_size: usize,
) -> Vec<(NaiveDate, f32)> {
) -> Vec<(NaiveDate, Option<f32>)> {
#[allow(clippy::cast_precision_loss)]
weighted_sum_of_load
.windows(window_size)
.map(|window| {
(
window.last().unwrap().0,
window.iter().map(|(_, l)| l).sum::<f32>() / window_size as f32,
Some(window.iter().filter_map(|(_, l)| *l).sum::<f32>() / window_size as f32),
)
})
.collect::<Vec<_>>()
}

fn calculate_daily_stimulus_per_muscle(
training_sessions: &[&TrainingSession],
exercises: &BTreeMap<u32, Exercise>,
) -> BTreeMap<u8, Vec<(NaiveDate, f32)>> {
let mut result: BTreeMap<u8, Vec<(NaiveDate, f32)>> = BTreeMap::new();

for m in domain::Muscle::iter() {
#[allow(clippy::cast_precision_loss)]
result.insert(
domain::Muscle::id(*m),
training_sessions
.iter()
.map(|s| {
(
s.date,
*s.stimulus_per_muscle(exercises)
.get(&domain::Muscle::id(*m))
.unwrap_or(&0) as f32,
)
})
.collect(),
);
}
result
}

// ------ ------
// Update
// ------ ------
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/ui/page/body_fat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -747,26 +747,27 @@ fn view_chart(model: &Model, data_model: &data::Model) -> Node<Msg> {
(
body_fat
.iter()
.filter_map(|bf| bf.jp3(sex).map(|jp3| (bf.date, jp3)))
.filter_map(|bf| bf.jp3(sex).map(|jp3| (bf.date, Some(jp3))))
.collect::<Vec<_>>(),
common::COLOR_BODY_FAT_JP3,
),
(
body_fat
.iter()
.filter_map(|bf| bf.jp7(sex).map(|jp7| (bf.date, jp7)))
.filter_map(|bf| bf.jp7(sex).map(|jp7| (bf.date, Some(jp7))))
.collect::<Vec<_>>(),
common::COLOR_BODY_FAT_JP7,
),
],
&[(
body_weight
.iter()
.map(|bw| (bw.date, bw.weight))
.map(|bw| (bw.date, Some(bw.weight)))
.collect::<Vec<_>>(),
common::COLOR_BODY_WEIGHT,
)],
&model.interval,
false,
data_model.theme(),
),
true,
Expand Down
44 changes: 19 additions & 25 deletions frontend/src/ui/page/body_weight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,40 +355,34 @@ fn view_body_weight_dialog(dialog: &Dialog, loading: bool) -> Node<Msg> {
}

fn view_chart(model: &Model, data_model: &data::Model) -> Node<Msg> {
let body_weight = data_model
.body_weight
.values()
.filter(|bw| bw.date >= model.interval.first && bw.date <= model.interval.last)
.map(|bw| (bw.date, Some(bw.weight)))
.collect::<Vec<_>>();
let body_weight_7day_avg = common::centered_moving_average(
&data_model
.body_weight
.values()
.map(|bw| (bw.date, Some(bw.weight)))
.collect::<Vec<_>>(),
&model.interval,
3,
);
common::view_chart(
vec![
("Weight (kg)", common::COLOR_BODY_WEIGHT),
("Avg. weight (kg)", common::COLOR_AVG_BODY_WEIGHT),
]
.as_slice(),
common::plot_line_chart(
&[
(
data_model
.body_weight
.values()
.filter(|bw| {
bw.date >= model.interval.first && bw.date <= model.interval.last
})
.map(|bw| (bw.date, bw.weight))
.collect::<Vec<_>>(),
common::COLOR_BODY_WEIGHT,
),
(
data_model
.body_weight_stats
.values()
.filter(|bws| {
bws.date >= model.interval.first && bws.date <= model.interval.last
})
.filter_map(|bws| bws.avg_weight.map(|avg_weight| (bws.date, avg_weight)))
.collect::<Vec<_>>(),
common::COLOR_AVG_BODY_WEIGHT,
),
],
common::plot_bar_chart(
&[(body_weight, common::COLOR_BODY_WEIGHT)],
&[(body_weight_7day_avg, common::COLOR_AVG_BODY_WEIGHT)],
&model.interval,
None,
None,
true,
data_model.theme(),
),
true,
Expand Down
52 changes: 31 additions & 21 deletions frontend/src/ui/page/exercise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,27 +454,10 @@ pub fn view_charts<Ms>(
show_rpe: bool,
show_tut: bool,
) -> Vec<Node<Ms>> {
let mut set_volume: BTreeMap<NaiveDate, f32> = BTreeMap::new();
let mut volume_load: BTreeMap<NaiveDate, f32> = BTreeMap::new();
let mut tut: BTreeMap<NaiveDate, f32> = BTreeMap::new();
let mut reps_rpe: BTreeMap<NaiveDate, (Vec<f32>, Vec<f32>)> = BTreeMap::new();
let mut weight: BTreeMap<NaiveDate, Vec<f32>> = BTreeMap::new();
let mut time: BTreeMap<NaiveDate, Vec<f32>> = BTreeMap::new();
for training_session in training_sessions {
#[allow(clippy::cast_precision_loss)]
set_volume
.entry(training_session.date)
.and_modify(|e| *e += training_session.set_volume() as f32)
.or_insert(training_session.set_volume() as f32);
#[allow(clippy::cast_precision_loss)]
volume_load
.entry(training_session.date)
.and_modify(|e| *e += training_session.volume_load() as f32)
.or_insert(training_session.volume_load() as f32);
#[allow(clippy::cast_precision_loss)]
tut.entry(training_session.date)
.and_modify(|e| *e += training_session.tut().unwrap_or(0) as f32)
.or_insert(training_session.tut().unwrap_or(0) as f32);
if let Some(avg_reps) = training_session.avg_reps() {
reps_rpe
.entry(training_session.date)
Expand All @@ -498,11 +481,38 @@ pub fn view_charts<Ms>(
.or_insert(vec![avg_time]);
}
}
#[allow(clippy::cast_precision_loss)]
let tut = common::centered_moving_total(
&training_sessions
.iter()
.map(|s| (s.date, s.tut().map(|v| v as f32)))
.collect::<Vec<_>>(),
interval,
0,
);
#[allow(clippy::cast_precision_loss)]
let set_volume = common::centered_moving_total(
&training_sessions
.iter()
.map(|s| (s.date, Some(s.set_volume() as f32)))
.collect::<Vec<_>>(),
interval,
0,
);
let set_volume_7day_total = common::centered_moving_total(
&set_volume.clone().into_iter().collect::<Vec<_>>(),
interval,
3,
);
#[allow(clippy::cast_precision_loss)]
let volume_load = common::centered_moving_total(
&training_sessions
.iter()
.map(|s| (s.date, Some(s.volume_load() as f32)))
.collect::<Vec<_>>(),
interval,
0,
);
let volume_load_7day_avg = common::centered_moving_average(
&volume_load.clone().into_iter().collect::<Vec<_>>(),
interval,
Expand All @@ -516,7 +526,7 @@ pub fn view_charts<Ms>(
.iter()
.map(|(date, (avg_reps, _))| {
#[allow(clippy::cast_precision_loss)]
(*date, avg_reps.iter().sum::<f32>() / avg_reps.len() as f32)
(*date, Some(avg_reps.iter().sum::<f32>() / avg_reps.len() as f32))
})
.collect::<Vec<_>>(),
common::COLOR_REPS,
Expand All @@ -536,7 +546,7 @@ pub fn view_charts<Ms>(
if avg_rpe_values.is_empty() {
None
} else {
Some((date, avg_reps + 10.0 - avg_rpe))
Some((date, Some(avg_reps + 10.0 - avg_rpe)))
}
})
.collect::<Vec<_>>(),
Expand Down Expand Up @@ -615,7 +625,7 @@ pub fn view_charts<Ms>(
.into_iter()
.map(|(date, values)| {
#[allow(clippy::cast_precision_loss)]
(date, values.iter().sum::<f32>() / values.len() as f32)
(date, Some(values.iter().sum::<f32>() / values.len() as f32))
})
.collect::<Vec<_>>(),
common::COLOR_WEIGHT,
Expand All @@ -635,7 +645,7 @@ pub fn view_charts<Ms>(
time.into_iter()
.map(|(date, values)| {
#[allow(clippy::cast_precision_loss)]
(date, values.iter().sum::<f32>() / values.len() as f32)
(date, Some(values.iter().sum::<f32>() / values.len() as f32))
})
.collect::<Vec<_>>(),
common::COLOR_TIME,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/ui/page/menstrual_cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ fn view_chart(model: &Model, data_model: &data::Model) -> Node<Msg> {
&[(
period
.iter()
.map(|p| (p.date, f32::from(p.intensity)))
.map(|p| (p.date, Some(f32::from(p.intensity))))
.collect::<Vec<_>>(),
common::COLOR_PERIOD_INTENSITY,
)],
Expand Down
38 changes: 15 additions & 23 deletions frontend/src/ui/page/muscles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,30 +69,22 @@ pub fn view(model: &Model, data_model: &data::Model) -> Node<Msg> {
Msg::ChangeInterval
),
domain::Muscle::iter().map(|m| {
let set_volume = data_model
.training_stats
.stimulus_per_muscle
.get(&domain::Muscle::id(*m))
.map_or(Vec::new(), std::clone::Clone::clone)
.into_iter()
.filter(|(date, _)| {
*date >= model.interval.first && *date <= model.interval.last
#[allow(clippy::cast_precision_loss)]
let set_volume_data = &data_model
.training_sessions
.values()
.map(|s| {
(
s.date,
s.stimulus_per_muscle(&data_model.exercises)
.get(&domain::Muscle::id(*m))
.map(|v| (*v as f32) / 100.0),
)
})
.map(|(date, stimulus)| (date, stimulus / 100.0))
.collect();

let set_volume_7day_total = common::centered_moving_total(
&data_model
.training_stats
.stimulus_per_muscle
.get(&domain::Muscle::id(*m))
.unwrap_or(&Vec::new())
.iter()
.map(|(date, stimulus)| (*date, *stimulus / 100.0))
.collect(),
&model.interval,
3,
);
.collect::<Vec<_>>();
let set_volume = common::centered_moving_total(set_volume_data, &model.interval, 0);
let set_volume_7day_total =
common::centered_moving_total(set_volume_data, &model.interval, 3);

div![
common::view_title(&span![domain::Muscle::name(*m)], 1),
Expand Down
Loading

0 comments on commit 1d1bba5

Please sign in to comment.