rustybeans/src/plot.rs

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
}