342 lines
12 KiB
Rust
342 lines
12 KiB
Rust
use crate::config::Config;
|
|
use crate::database::{Brew, Database};
|
|
use crate::flow_profile::FlowProfile;
|
|
use crate::sheets::load_data;
|
|
use crate::time::{unix_to_human_date, unix_to_human_date_time, unix_to_machine_date};
|
|
|
|
use dialoguer::{theme::ColorfulTheme, MultiSelect};
|
|
|
|
use egui::widgets::plot::{PlotPoint, PlotPoints};
|
|
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
|
|
|
use notify_rust::Notification;
|
|
|
|
use plotly::{
|
|
common::{Mode, Title},
|
|
Layout, Plot, Scatter,
|
|
};
|
|
|
|
use std::collections::HashMap;
|
|
use std::fs;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
pub type RawPlotPoints = Vec<PlotPoint>;
|
|
pub type PlotEntry = (String, RawPlotPoints);
|
|
|
|
#[derive(Default)]
|
|
pub struct LoadingProgress {
|
|
pub curr_name: String,
|
|
pub current: usize,
|
|
pub total: usize,
|
|
pub percentage: f32,
|
|
}
|
|
|
|
pub fn generate_plots() -> Vec<(String, Plot)> {
|
|
let config_file = fs::read_to_string("config.toml").expect("Can't read config.toml");
|
|
let config: Config = toml::from_str(&config_file).expect("Can't deserialize config.toml");
|
|
|
|
let mut result: Vec<(String, Plot)> = Vec::with_capacity(config.charts.len());
|
|
|
|
for chart in config.charts {
|
|
// println!("Chart: {}\n", chart.1.title);
|
|
|
|
let filename = format!("{}/{}.html", &config.output_dir, &chart.1.title);
|
|
let mut plot = Plot::new();
|
|
|
|
let _shot_count = chart.1.shots.len();
|
|
|
|
for shot_nr in chart.1.shots {
|
|
if let Some(shot) = config.shots.get(&shot_nr.to_string()) {
|
|
// println!("\tShot: {}n", shot.title);
|
|
|
|
if let Some(shot_json) = &shot.json {
|
|
let brew = FlowProfile::from_file(
|
|
&format!("{}/{}_flow_profile.json", config.brew_dir, shot_json),
|
|
shot.cutoff,
|
|
)
|
|
.preprocess_json();
|
|
|
|
let (x, y): (Vec<_>, Vec<_>) = brew
|
|
.data_collection
|
|
.unwrap_or_else(|| {
|
|
panic!("No data_collection present for shot_json: {}", shot_json)
|
|
})
|
|
.weight
|
|
.iter()
|
|
.cloned()
|
|
.unzip();
|
|
let trace = Scatter::new(x, y).name(&shot.title).mode(Mode::Lines);
|
|
plot.add_trace(trace);
|
|
} else if let Some(shot_filename) = &shot.filename {
|
|
if let Some(data) = load_data(
|
|
&format!("{}/{}", config.shot_dir, shot_filename),
|
|
shot.cutoff,
|
|
) {
|
|
if let Some(disable) = shot.disable {
|
|
if disable {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
let (x, y): (Vec<_>, Vec<_>) = data.weight.into_iter().unzip();
|
|
let trace = Scatter::new(x, y).name(&shot.title).mode(Mode::Lines);
|
|
plot.add_trace(trace);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let layout = Layout::new().title(Title::new(&chart.1.title));
|
|
plot.set_layout(layout);
|
|
|
|
plot.use_local_plotly();
|
|
plot.write_html(filename);
|
|
result.push((chart.1.title, plot));
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
pub fn plot_points_to_owned(plot_points: &RawPlotPoints) -> PlotPoints {
|
|
PlotPoints::Owned(plot_points.to_owned())
|
|
}
|
|
|
|
pub fn database_plot_entry(brew: &Brew, config: &Config, database: &Database) -> Option<PlotEntry> {
|
|
let mut plot_points: Option<(String, RawPlotPoints)> = None;
|
|
if let Some(flow_profile) = &brew.flow_profile {
|
|
if !&flow_profile.is_empty() {
|
|
let brew_title = brew.date_time_with_bean(&database);
|
|
|
|
let flow_profile =
|
|
FlowProfile::from_file(&format!("{}/{}", &config.data_dir, &flow_profile), None)
|
|
.preprocess_json();
|
|
|
|
if let Some(data_collection) = &flow_profile.data_collection {
|
|
let point_vec: Vec<[f64; 2]> = data_collection
|
|
.weight
|
|
.iter()
|
|
.map(|(x, y)| [x.to_owned(), y.to_owned()])
|
|
.collect();
|
|
|
|
plot_points = Some((brew_title, PlotPoints::from(point_vec).points().to_vec()));
|
|
}
|
|
}
|
|
}
|
|
|
|
plot_points
|
|
}
|
|
|
|
pub fn database_plot_entries(
|
|
brews: Vec<&Brew>,
|
|
config: &Config,
|
|
database: &Database,
|
|
progress: Option<Arc<Mutex<LoadingProgress>>>,
|
|
) -> HashMap<String, PlotEntry> {
|
|
let mut plot_entries: HashMap<String, PlotEntry> = HashMap::new();
|
|
let step_size = 1.0 / brews.len() as f32;
|
|
|
|
for (i, brew) in brews.iter().enumerate() {
|
|
if let Some(progress) = &progress {
|
|
let mut progress_lock = progress.lock().unwrap();
|
|
(*progress_lock).curr_name = brew.date_time_with_bean(&database);
|
|
(*progress_lock).current = i + 1;
|
|
(*progress_lock).total = brews.len();
|
|
(*progress_lock).percentage += step_size;
|
|
}
|
|
|
|
if let Some(plot_entry) = database_plot_entry(&brew, &config, &database) {
|
|
plot_entries.insert(brew.config.uuid.to_owned(), plot_entry);
|
|
}
|
|
}
|
|
|
|
plot_entries
|
|
}
|
|
|
|
pub fn database_plot_selected(uuids: Vec<String>, config: &Config) -> Vec<(String, Plot)> {
|
|
let mut result: Vec<(String, Plot)> = Vec::with_capacity(config.charts.len());
|
|
|
|
let database = Database::from_config(&config);
|
|
|
|
let bean_names = database.bean_names();
|
|
|
|
if !uuids.is_empty() {
|
|
for single_selection in uuids {
|
|
if let Some(bean) = &database.bean_for_uuid(&single_selection) {
|
|
let bean_timestamp = bean.config.unix_timestamp.to_owned();
|
|
|
|
let title = format!("{} from {}", &bean.name, unix_to_human_date(bean_timestamp));
|
|
let filename = format!(
|
|
"{}/{}_from_{}.html",
|
|
&config.output_dir,
|
|
&bean.name,
|
|
unix_to_machine_date(bean_timestamp)
|
|
)
|
|
.replace(" ", "_");
|
|
let mut plot = Plot::new();
|
|
|
|
let brews = &database.brews_for_bean(&bean);
|
|
|
|
for brew in brews {
|
|
if let Some(flow_profile) = &brew.flow_profile {
|
|
if !&flow_profile.is_empty() {
|
|
let brew_title =
|
|
unix_to_human_date_time(brew.config.unix_timestamp.to_owned());
|
|
|
|
let brew = FlowProfile::from_file(
|
|
&format!("{}/{}", &config.data_dir, &flow_profile),
|
|
None,
|
|
)
|
|
.preprocess_json();
|
|
|
|
let (x, y): (Vec<_>, Vec<_>) = brew
|
|
.data_collection
|
|
.unwrap_or_else(|| {
|
|
panic!(
|
|
"No data_collection present for flow_profile: {}",
|
|
&flow_profile
|
|
)
|
|
})
|
|
.weight
|
|
.iter()
|
|
.cloned()
|
|
.unzip();
|
|
let trace = Scatter::new(x, y).name(&brew_title).mode(Mode::Lines);
|
|
plot.add_trace(trace);
|
|
|
|
// progress_brews.inc(..);
|
|
}
|
|
}
|
|
}
|
|
|
|
let layout = Layout::new().title(Title::new(&title));
|
|
plot.set_layout(layout);
|
|
|
|
plot.use_local_plotly();
|
|
plot.write_html(filename);
|
|
|
|
result.push((title.to_owned(), plot));
|
|
}
|
|
|
|
// progress_beans.inc(..);
|
|
}
|
|
}
|
|
|
|
Notification::new()
|
|
.summary("RustyBeans finished")
|
|
.body("Successfully generated all selected bean charts automatically, according to the database.")
|
|
.timeout(5000)
|
|
.show()
|
|
.expect("Couldn't show desktop notification");
|
|
|
|
result
|
|
}
|
|
|
|
pub fn database_plot_selected_tui() -> Vec<(String, Plot)> {
|
|
let config = Config::from_default();
|
|
|
|
let mut result: Vec<(String, Plot)> = Vec::with_capacity(config.charts.len());
|
|
|
|
let database = Database::from_config(&config);
|
|
|
|
let bean_names = database.bean_names();
|
|
|
|
let selection = MultiSelect::with_theme(&ColorfulTheme::default())
|
|
.with_prompt("Select the Beans you want to automatically generate charts for:")
|
|
.items(&bean_names[..])
|
|
.interact()
|
|
.expect("You need to select at least one bean to proceed");
|
|
|
|
if !selection.is_empty() {
|
|
let multi_progress = MultiProgress::new();
|
|
multi_progress.println("Generating brew charts.\nCheck the specified output directory and open the corresponding .html files to view.").unwrap();
|
|
|
|
let progress_style = ProgressStyle::with_template(
|
|
"[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}",
|
|
)
|
|
.expect("Can't generate progress bar style");
|
|
let progress_beans = multi_progress.add(
|
|
ProgressBar::new(selection.len() as u64)
|
|
.with_style(progress_style.to_owned())
|
|
.with_message("Beans"),
|
|
);
|
|
let mut progress_brews: ProgressBar = ProgressBar::new(0);
|
|
|
|
for single_selection in selection {
|
|
if let Some(bean) = &database.beans.get(single_selection) {
|
|
let bean_timestamp = bean.config.unix_timestamp.to_owned();
|
|
|
|
let title = format!("{} from {}", &bean.name, unix_to_human_date(bean_timestamp));
|
|
let filename = format!(
|
|
"{}/{}_from_{}.html",
|
|
&config.output_dir,
|
|
&bean.name,
|
|
unix_to_machine_date(bean_timestamp)
|
|
)
|
|
.replace(" ", "_");
|
|
let mut plot = Plot::new();
|
|
|
|
let brews = &database.brews_for_bean(&bean);
|
|
|
|
progress_brews = multi_progress.insert_after(
|
|
&progress_beans,
|
|
ProgressBar::new(brews.len() as u64)
|
|
.with_style(progress_style.to_owned())
|
|
.with_message("Brews"),
|
|
);
|
|
|
|
for brew in brews {
|
|
if let Some(flow_profile) = &brew.flow_profile {
|
|
if !&flow_profile.is_empty() {
|
|
let brew_title =
|
|
unix_to_human_date_time(brew.config.unix_timestamp.to_owned());
|
|
|
|
let brew = FlowProfile::from_file(
|
|
&format!("{}/{}", &config.data_dir, &flow_profile),
|
|
None,
|
|
)
|
|
.preprocess_json();
|
|
|
|
let (x, y): (Vec<_>, Vec<_>) = brew
|
|
.data_collection
|
|
.unwrap_or_else(|| {
|
|
panic!(
|
|
"No data_collection present for flow_profile: {}",
|
|
&flow_profile
|
|
)
|
|
})
|
|
.weight
|
|
.iter()
|
|
.cloned()
|
|
.unzip();
|
|
let trace = Scatter::new(x, y).name(&brew_title).mode(Mode::Lines);
|
|
plot.add_trace(trace);
|
|
|
|
progress_brews.inc(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
let layout = Layout::new().title(Title::new(&title));
|
|
plot.set_layout(layout);
|
|
|
|
plot.use_local_plotly();
|
|
plot.write_html(filename);
|
|
|
|
result.push((title.to_owned(), plot));
|
|
}
|
|
|
|
progress_beans.inc(1);
|
|
multi_progress.remove(&progress_brews);
|
|
}
|
|
}
|
|
|
|
Notification::new()
|
|
.summary("RustyBeans finished")
|
|
.body("Successfully generated all selected bean charts automatically, according to the database.")
|
|
.timeout(5000)
|
|
.show()
|
|
.expect("Couldn't show desktop notification");
|
|
|
|
result
|
|
}
|