Plot weight loss
This commit is contained in:
parent
58df3b82d4
commit
a4578c004f
@ -21,6 +21,7 @@ mod math;
|
|||||||
mod my_structs;
|
mod my_structs;
|
||||||
use my_structs::MyError;
|
use my_structs::MyError;
|
||||||
use my_structs::Purchase;
|
use my_structs::Purchase;
|
||||||
|
use my_structs::Weight;
|
||||||
mod plotting;
|
mod plotting;
|
||||||
// use crate::plotting::Plotter;
|
// use crate::plotting::Plotter;
|
||||||
use std::{thread, time};
|
use std::{thread, time};
|
||||||
@ -68,6 +69,7 @@ async fn main() {
|
|||||||
.route("/image.png", get(png_image))
|
.route("/image.png", get(png_image))
|
||||||
.route("/image.svg", get(svg_image))
|
.route("/image.svg", get(svg_image))
|
||||||
.route("/calorie_intake.svg", get(svg_calorie_intake))
|
.route("/calorie_intake.svg", get(svg_calorie_intake))
|
||||||
|
.route("/weight_loss.svg", get(svg_weight_loss))
|
||||||
.route("/calorie_intake", get(calorie_intake))
|
.route("/calorie_intake", get(calorie_intake))
|
||||||
.route("/wasm_test", get(wasm_test))
|
.route("/wasm_test", get(wasm_test))
|
||||||
.route("/camera_test", get(camera_test))
|
.route("/camera_test", get(camera_test))
|
||||||
@ -139,6 +141,21 @@ async fn svg_calorie_intake(
|
|||||||
Ok(plotting::SVGPlotter::plot(640, 480, plotfun))
|
Ok(plotting::SVGPlotter::plot(640, 480, plotfun))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn svg_weight_loss(State(state): State<Arc<AppState>>) -> Result<impl IntoResponse, MyError> {
|
||||||
|
println!("Serving: svg_weight_loss");
|
||||||
|
|
||||||
|
let nutrition_val_promise = Nutrition::get_nutrition_hashmap(state.sql_pool.clone());
|
||||||
|
let purchases_val_promise = Purchase::get_purchase_rows(state.sql_pool.clone());
|
||||||
|
let weight_val_promise = Weight::get_weight_rows(state.sql_pool.clone());
|
||||||
|
|
||||||
|
let nutrition_val = nutrition_val_promise.await?;
|
||||||
|
let purchases_val = purchases_val_promise.await?;
|
||||||
|
let weight_val = weight_val_promise.await?;
|
||||||
|
|
||||||
|
let plotfun = math::plot_weight_loss(nutrition_val, purchases_val, weight_val);
|
||||||
|
Ok(plotting::SVGPlotter::plot(640, 480, plotfun))
|
||||||
|
}
|
||||||
|
|
||||||
async fn png_image(State(_state): State<Arc<AppState>>) -> impl IntoResponse {
|
async fn png_image(State(_state): State<Arc<AppState>>) -> impl IntoResponse {
|
||||||
println!("Serving: png_image");
|
println!("Serving: png_image");
|
||||||
let fun = plotting::BitMapPlotter::examplefun();
|
let fun = plotting::BitMapPlotter::examplefun();
|
||||||
@ -154,7 +171,10 @@ async fn html_plotter(State(_state): State<Arc<AppState>>) -> Html<String> {
|
|||||||
async fn calorie_intake(State(_state): State<Arc<AppState>>) -> Html<String> {
|
async fn calorie_intake(State(_state): State<Arc<AppState>>) -> Html<String> {
|
||||||
println!("Serving: calorie_intake");
|
println!("Serving: calorie_intake");
|
||||||
|
|
||||||
Html("<body><img src=/calorie_intake.svg></img></body>".to_string())
|
Html(
|
||||||
|
"<body><img src=/calorie_intake.svg><br><img src=/weight_loss.svg></img></body>"
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn construct_js(path: &str) -> Result<String, MyError> {
|
fn construct_js(path: &str) -> Result<String, MyError> {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
// pub use self::calculations::calculate_calories_per_day;
|
// pub use self::calculations::calculate_calories_per_day;
|
||||||
pub use self::calculations::plot_calories_per_day;
|
pub use self::calculations::plot_calories_per_day;
|
||||||
|
pub use self::calculations::plot_weight_loss;
|
||||||
mod calculations;
|
mod calculations;
|
||||||
|
@ -153,3 +153,119 @@ pub fn plot_calories_per_day(
|
|||||||
});
|
});
|
||||||
plotfun
|
plotfun
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn plot_weight_loss(
|
||||||
|
nutrition_map: HashMap<u32, Nutrition>,
|
||||||
|
purchases: Vec<my_structs::Purchase>,
|
||||||
|
weight: Vec<my_structs::Weight>,
|
||||||
|
) -> plotting::SVGPlotFun {
|
||||||
|
let (mindate, calorie_days) = calculate_calories_per_day(nutrition_map, purchases);
|
||||||
|
let calories_burned_per_day: f32 = 2200.;
|
||||||
|
let deficit_days: Vec<f32> = calorie_days
|
||||||
|
.iter()
|
||||||
|
.scan(0., |deficit, &calories| {
|
||||||
|
*deficit += calories - calories_burned_per_day;
|
||||||
|
Some(*deficit)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let maxdate = mindate
|
||||||
|
.checked_add(Duration::days(deficit_days.len().try_into().unwrap()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mindate_chrono = time_date_to_chrono_naive_date(mindate);
|
||||||
|
let maxdate_chrono = time_date_to_chrono_naive_date(maxdate);
|
||||||
|
let today_chrono = DateTime::<Utc>::from(SystemTime::now()).date_naive();
|
||||||
|
|
||||||
|
let max_weight = weight
|
||||||
|
.iter()
|
||||||
|
.map(|w| w.weight)
|
||||||
|
.reduce(|a, b| a.max(b))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let min_weight = weight
|
||||||
|
.iter()
|
||||||
|
.map(|w| w.weight)
|
||||||
|
.reduce(|a, b| a.min(b))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let plotfun = Box::new(move |root: plotting::SVGPlotRoot| {
|
||||||
|
root.fill(&WHITE).unwrap();
|
||||||
|
let mut chart = ChartBuilder::on(&root)
|
||||||
|
.caption("Weight loss", ("sans-serif", 50).into_font())
|
||||||
|
// .margin(20)
|
||||||
|
.x_label_area_size(30)
|
||||||
|
.y_label_area_size(50)
|
||||||
|
.build_cartesian_2d(
|
||||||
|
mindate_chrono..maxdate_chrono,
|
||||||
|
(min_weight - 2.)..(max_weight + 2.),
|
||||||
|
// 70_f32..90_f32,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
chart
|
||||||
|
.configure_mesh()
|
||||||
|
.y_desc("kg fat burned")
|
||||||
|
.y_max_light_lines(4)
|
||||||
|
.x_max_light_lines(6)
|
||||||
|
.draw()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
chart
|
||||||
|
.draw_series(LineSeries::new(
|
||||||
|
vec![
|
||||||
|
(today_chrono, min_weight - 2.),
|
||||||
|
(today_chrono, max_weight + 2.),
|
||||||
|
],
|
||||||
|
BLACK.stroke_width(2),
|
||||||
|
))
|
||||||
|
.unwrap()
|
||||||
|
.label("today")
|
||||||
|
.legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], BLACK));
|
||||||
|
|
||||||
|
chart
|
||||||
|
.draw_series(LineSeries::new(
|
||||||
|
deficit_days.iter().enumerate().map(|(i, cal)| {
|
||||||
|
(
|
||||||
|
mindate_chrono
|
||||||
|
.checked_add_days(chrono::Days::new(i.try_into().unwrap()))
|
||||||
|
.unwrap(),
|
||||||
|
*cal / 7000. + weight[0].weight + 1.,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
RED.stroke_width(3),
|
||||||
|
))
|
||||||
|
.unwrap()
|
||||||
|
.label("kg burned")
|
||||||
|
.legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], RED));
|
||||||
|
|
||||||
|
chart
|
||||||
|
.draw_series(LineSeries::new(
|
||||||
|
weight.iter().map(|w| {
|
||||||
|
(
|
||||||
|
time_date_to_chrono_naive_date(w.datetime.date()),
|
||||||
|
// mindate_chrono
|
||||||
|
// .checked_add_days(chrono::Days::new(i.try_into().unwrap()))
|
||||||
|
// .unwrap(),
|
||||||
|
w.weight,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
BLUE.stroke_width(3),
|
||||||
|
))
|
||||||
|
.unwrap()
|
||||||
|
.label("weight")
|
||||||
|
.legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], BLUE));
|
||||||
|
|
||||||
|
// Show labels
|
||||||
|
chart
|
||||||
|
.configure_series_labels()
|
||||||
|
.background_style(WHITE.mix(0.8))
|
||||||
|
.border_style(BLACK)
|
||||||
|
.position(SeriesLabelPosition::LowerRight)
|
||||||
|
.draw()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
root.present().unwrap();
|
||||||
|
});
|
||||||
|
plotfun
|
||||||
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
pub use self::myerror::MyError;
|
pub use self::myerror::MyError;
|
||||||
pub use self::purchases::Purchase;
|
pub use self::purchases::Purchase;
|
||||||
|
pub use self::weight::Weight;
|
||||||
mod myerror;
|
mod myerror;
|
||||||
mod purchases;
|
mod purchases;
|
||||||
|
mod weight;
|
||||||
|
54
server/src/my_structs/weight.rs
Normal file
54
server/src/my_structs/weight.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
use mysql::prelude::*;
|
||||||
|
use mysql::*;
|
||||||
|
use mysql_common::frunk::{hlist_pat, HList};
|
||||||
|
use struct_field_names_as_array::FieldNamesAsArray;
|
||||||
|
use time::PrimitiveDateTime;
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[derive(Debug, PartialEq, FieldNamesAsArray)]
|
||||||
|
pub struct Weight {
|
||||||
|
id: u32,
|
||||||
|
pub weight: f32,
|
||||||
|
pub datetime: PrimitiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
type RowType = HList!(u32, f32, PrimitiveDateTime);
|
||||||
|
|
||||||
|
impl Weight {
|
||||||
|
fn get_sql_fields() -> String {
|
||||||
|
Weight::FIELD_NAMES_AS_ARRAY
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.intersperse(", ")
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_map_helper() -> (String, impl Fn(RowType) -> Weight) {
|
||||||
|
let sql_query = format!("SELECT {} from weight", Self::get_sql_fields());
|
||||||
|
println!("{}", sql_query);
|
||||||
|
|
||||||
|
let construction_closure = |row: RowType| {
|
||||||
|
let hlist_pat![id, weight, datetime] = row;
|
||||||
|
Weight {
|
||||||
|
id,
|
||||||
|
weight,
|
||||||
|
datetime,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(sql_query, construction_closure)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_weight_rows(sql_pool: Pool) -> Result<Vec<Weight>> {
|
||||||
|
let mut conn = sql_pool
|
||||||
|
.get_conn()
|
||||||
|
.expect("Cannot establish database connection");
|
||||||
|
|
||||||
|
let (weight_query, weight_closure) = Self::query_map_helper();
|
||||||
|
let weight_val: Vec<Weight> = conn
|
||||||
|
.query_map(weight_query, weight_closure)
|
||||||
|
.expect("Data in database doesn't match the Weight class");
|
||||||
|
|
||||||
|
Ok(weight_val)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user