diff --git a/src/app/data/mod.rs b/src/app/data/mod.rs index ed0806f..31dba5d 100644 --- a/src/app/data/mod.rs +++ b/src/app/data/mod.rs @@ -79,9 +79,16 @@ impl ApplicationState { .expect("Storage Mutex poisoned") .new_panel( panel.name.as_str(), + false, panel.view_size, + 5, + 0, + true, panel.width, panel.height, + false, + false, + false, self.panels.read().expect("Panels RwLock poisoned").len() as i32, // todo can this be made more compact and without acquisition? )?; // TODO make values customizable and useful self.panels diff --git a/src/app/data/source.rs b/src/app/data/source.rs index f040de1..9fdaf86 100644 --- a/src/app/data/source.rs +++ b/src/app/data/source.rs @@ -4,15 +4,20 @@ use eframe::egui::plot::{Value, Values}; use eframe::epaint::Color32; use std::sync::RwLock; +#[derive(Debug)] pub struct Panel { pub(crate) id: i32, pub name: String, pub view_scroll: bool, - pub view_size: i32, + pub view_size: u32, + pub view_chunks: u32, + pub view_offset: u32, pub timeserie: bool, pub(crate) width: i32, pub(crate) height: i32, pub limit: bool, + pub reduce: bool, + pub shift: bool, } impl Default for Panel { @@ -22,14 +27,19 @@ impl Default for Panel { name: "".to_string(), view_scroll: true, view_size: 300, + view_chunks: 5, + view_offset: 0, timeserie: true, width: 100, height: 200, limit: false, + reduce: false, + shift: false, } } } +#[derive(Debug)] pub struct Source { pub(crate) id: i32, pub name: String, @@ -64,19 +74,37 @@ impl Default for Source { } } +fn avg_value(values: &[Value]) -> Value { + let mut x = 0.0; + let mut y = 0.0; + for v in values { + x += v.x; + y += v.y; + } + return Value { x: x / values.len() as f64, y: y / values.len() as f64 }; +} + impl Source { pub fn valid(&self) -> bool { let last_fetch = self.last_fetch.read().expect("LastFetch RwLock poisoned"); return (Utc::now() - *last_fetch).num_seconds() < self.interval as i64; } - pub fn values(&self) -> Values { - Values::from_values(self.data.read().expect("Values RwLock poisoned").clone()) - } - - pub fn values_filter(&self, min_x: f64) -> Values { + // TODO optimize this with caching! + pub fn values(&self, min_x: Option, max_x: Option, chunk_size: Option) -> Values { let mut values = self.data.read().expect("Values RwLock poisoned").clone(); - values.retain(|x| x.x > min_x); + if let Some(min_x) = min_x { + values.retain(|x| x.x > min_x); + } + if let Some(max_x) = max_x { + values.retain(|x| x.x < max_x); + } + if let Some(chunk_size) = chunk_size { + if chunk_size > 0 { // TODO make this nested if prettier + let iter = values.chunks(chunk_size as usize); + values = iter.map(|x| avg_value(x) ).collect(); + } + } Values::from_values(values) } } diff --git a/src/app/data/store.rs b/src/app/data/store.rs index 2dcd365..8bc171a 100644 --- a/src/app/data/store.rs +++ b/src/app/data/store.rs @@ -30,7 +30,11 @@ impl SQLiteDataStore { width INT NOT NULL, height INT NOT NULL, limit_view BOOL NOT NULL, - position INT NOT NULL + position INT NOT NULL, + reduce_view BOOL NOT NULL, + view_chunks INT NOT NULL, + shift_view BOOL NOT NULL, + view_offset INT NOT NULL );", [], )?; @@ -212,6 +216,11 @@ impl SQLiteDataStore { width: row.get(5)?, height: row.get(6)?, limit: row.get(7)?, + // position: row.get(8)?, + reduce: row.get(9)?, + view_chunks: row.get(10)?, + shift: row.get(11)?, + view_offset: row.get(12)?, }) })?; @@ -228,14 +237,21 @@ impl SQLiteDataStore { pub fn new_panel( &self, name: &str, - view_size: i32, + view_scroll: bool, + view_size: u32, + view_chunks: u32, + view_offset: u32, + timeserie: bool, width: i32, height: i32, + limit: bool, + reduce: bool, + shift: bool, position: i32, ) -> rusqlite::Result { self.conn.execute( - "INSERT INTO panels (name, view_scroll, view_size, timeserie, width, height, limit_view, position) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", - params![name, true, view_size, true, width, height, false, position] + "INSERT INTO panels (name, view_scroll, view_size, timeserie, width, height, limit_view, position, reduce_view, view_chunks, shift_view, view_offset) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + params![name, view_scroll, view_size, timeserie, width, height, limit, position, reduce, view_chunks, shift, view_offset] )?; let mut statement = self.conn.prepare("SELECT * FROM panels WHERE name = ?")?; for panel in statement.query_map(params![name], |row| { @@ -248,6 +264,11 @@ impl SQLiteDataStore { width: row.get(5)?, height: row.get(6)?, limit: row.get(7)?, + // position: row.get(8)?, + reduce: row.get(9)?, + view_chunks: row.get(10)?, + shift: row.get(11)?, + view_offset: row.get(12)?, }) })? { if let Ok(p) = panel { @@ -262,16 +283,20 @@ impl SQLiteDataStore { id: i32, name: &str, view_scroll: bool, - view_size: i32, + view_size: u32, + view_chunks: u32, + view_offset: u32, timeserie: bool, width: i32, height: i32, limit: bool, + reduce: bool, + shift: bool, position: i32, ) -> rusqlite::Result { self.conn.execute( - "UPDATE panels SET name = ?, view_scroll = ?, view_size = ?, timeserie = ?, width = ?, height = ?, limit_view = ?, position = ? WHERE id = ?", - params![name, view_scroll, view_size, timeserie, width, height, limit, position, id], + "UPDATE panels SET name = ?, view_scroll = ?, view_size = ?, timeserie = ?, width = ?, height = ?, limit_view = ?, position = ?, reduce_view = ?, view_chunks = ?, shift_view = ?, view_offset = ? WHERE id = ?", + params![name, view_scroll, view_size, timeserie, width, height, limit, position, reduce, view_chunks, shift, view_offset, id], ) } diff --git a/src/app/gui/panel.rs b/src/app/gui/panel.rs index 5146105..a13177d 100644 --- a/src/app/gui/panel.rs +++ b/src/app/gui/panel.rs @@ -21,15 +21,37 @@ pub fn panel_title_ui(ui: &mut Ui, panel: &mut Panel, extra: bool) { ui.heading(panel.name.as_str()); ui.with_layout(Layout::right_to_left(), |ui| { ui.horizontal(|ui| { - ui.toggle_value(&mut panel.view_scroll, " • "); + ui.toggle_value(&mut panel.view_scroll, "🔒"); ui.separator(); - ui.label("m"); - ui.add( - DragValue::new(&mut panel.view_size) - .speed(10) - .clamp_range(0..=2147483647i32), - ); - ui.checkbox(&mut panel.limit, "limit"); + if panel.limit { + ui.label("min"); // TODO makes no sense if it's not a timeserie + ui.add( + DragValue::new(&mut panel.view_size) + .speed(10) + .clamp_range(0..=2147483647i32), + ); + } + ui.toggle_value(&mut panel.limit, "limit"); + ui.separator(); + if panel.shift { + ui.label("min"); + ui.add( + DragValue::new(&mut panel.view_offset) + .speed(10) + .clamp_range(0..=2147483647i32), + ); + } + ui.toggle_value(&mut panel.shift, "offset"); + ui.separator(); + if panel.reduce { + ui.label("x"); + ui.add( + DragValue::new(&mut panel.view_chunks) + .speed(1) + .clamp_range(1..=1000), // TODO allow to average larger spans maybe? + ); + } + ui.toggle_value(&mut panel.reduce, "reduce"); if extra { ui.separator(); ui.checkbox(&mut panel.timeserie, "timeserie"); @@ -52,11 +74,12 @@ pub fn panel_body_ui(ui: &mut Ui, panel: &mut Panel, sources: &Vec) { } if panel.view_scroll { - p = p.include_x(Utc::now().timestamp() as f64); + let _now = (Utc::now().timestamp() as f64) - (60.0 * panel.view_offset as f64); + p = p.include_x(_now); if panel.limit { p = p - .include_x((Utc::now().timestamp() + (panel.view_size as i64 * 3)) as f64) - .include_x((Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64); // ??? TODO + .include_x(_now + (panel.view_size as f64 * 3.0)) + .include_x(_now - (panel.view_size as f64 * 60.0)); // ??? TODO } } @@ -106,16 +129,16 @@ pub fn panel_body_ui(ui: &mut Ui, panel: &mut Panel, sources: &Vec) { } p.show(ui, |plot_ui| { - for source in &*sources { + for source in sources { if source.panel_id == panel.id { - let line = if panel.limit { - Line::new(source.values_filter( - (Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64, - )) - .name(source.name.as_str()) - } else { - Line::new(source.values()).name(source.name.as_str()) - }; + let _now = Utc::now().timestamp() as f64; + let _off = (panel.view_offset as f64) * 60.0; // TODO multiplying x60 makes sense only for timeseries + let _size = (panel.view_size as f64) * 60.0; // TODO multiplying x60 makes sense only for timeseries + let min_x = if panel.limit { Some(_now - _size - _off) } else { None }; + let max_x = if panel.shift { Some(_now - _off) } else { None }; + let chunk_size = if panel.reduce { Some(panel.view_chunks) } else { None }; + // let chunks = None; + let line = Line::new(source.values(min_x, max_x, chunk_size)).name(source.name.as_str()); plot_ui.line(line.color(source.color)); } } diff --git a/src/app/worker.rs b/src/app/worker.rs index fc28b49..753c5b7 100644 --- a/src/app/worker.rs +++ b/src/app/worker.rs @@ -14,10 +14,14 @@ pub fn native_save(state: Arc) { panel.name.as_str(), panel.view_scroll, panel.view_size, + panel.view_chunks, + panel.view_offset, panel.timeserie, panel.width, panel.height, panel.limit, + panel.reduce, + panel.shift, index as i32, ) { warn!(target: "native-save", "Could not update panel #{} : {:?}", panel.id, e);