feat!: added view offset and reduce, ui tweaks

view reduce averages chunks of X values, view offset shifts the "now"
back. Improved panel options UI
This commit is contained in:
əlemi 2022-06-18 19:37:56 +02:00
parent e8df50f010
commit 7c6f765455
Signed by: alemi
GPG key ID: A4895B84D311642C
5 changed files with 121 additions and 34 deletions

View file

@ -79,9 +79,16 @@ impl ApplicationState {
.expect("Storage Mutex poisoned") .expect("Storage Mutex poisoned")
.new_panel( .new_panel(
panel.name.as_str(), panel.name.as_str(),
false,
panel.view_size, panel.view_size,
5,
0,
true,
panel.width, panel.width,
panel.height, 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? 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 )?; // TODO make values customizable and useful
self.panels self.panels

View file

@ -4,15 +4,20 @@ use eframe::egui::plot::{Value, Values};
use eframe::epaint::Color32; use eframe::epaint::Color32;
use std::sync::RwLock; use std::sync::RwLock;
#[derive(Debug)]
pub struct Panel { pub struct Panel {
pub(crate) id: i32, pub(crate) id: i32,
pub name: String, pub name: String,
pub view_scroll: bool, pub view_scroll: bool,
pub view_size: i32, pub view_size: u32,
pub view_chunks: u32,
pub view_offset: u32,
pub timeserie: bool, pub timeserie: bool,
pub(crate) width: i32, pub(crate) width: i32,
pub(crate) height: i32, pub(crate) height: i32,
pub limit: bool, pub limit: bool,
pub reduce: bool,
pub shift: bool,
} }
impl Default for Panel { impl Default for Panel {
@ -22,14 +27,19 @@ impl Default for Panel {
name: "".to_string(), name: "".to_string(),
view_scroll: true, view_scroll: true,
view_size: 300, view_size: 300,
view_chunks: 5,
view_offset: 0,
timeserie: true, timeserie: true,
width: 100, width: 100,
height: 200, height: 200,
limit: false, limit: false,
reduce: false,
shift: false,
} }
} }
} }
#[derive(Debug)]
pub struct Source { pub struct Source {
pub(crate) id: i32, pub(crate) id: i32,
pub name: String, 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 { impl Source {
pub fn valid(&self) -> bool { pub fn valid(&self) -> bool {
let last_fetch = self.last_fetch.read().expect("LastFetch RwLock poisoned"); let last_fetch = self.last_fetch.read().expect("LastFetch RwLock poisoned");
return (Utc::now() - *last_fetch).num_seconds() < self.interval as i64; return (Utc::now() - *last_fetch).num_seconds() < self.interval as i64;
} }
pub fn values(&self) -> Values { // TODO optimize this with caching!
Values::from_values(self.data.read().expect("Values RwLock poisoned").clone()) pub fn values(&self, min_x: Option<f64>, max_x: Option<f64>, chunk_size: Option<u32>) -> Values {
}
pub fn values_filter(&self, min_x: f64) -> Values {
let mut values = self.data.read().expect("Values RwLock poisoned").clone(); 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) Values::from_values(values)
} }
} }

View file

@ -30,7 +30,11 @@ impl SQLiteDataStore {
width INT NOT NULL, width INT NOT NULL,
height INT NOT NULL, height INT NOT NULL,
limit_view BOOL 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)?, width: row.get(5)?,
height: row.get(6)?, height: row.get(6)?,
limit: row.get(7)?, 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( pub fn new_panel(
&self, &self,
name: &str, name: &str,
view_size: i32, view_scroll: bool,
view_size: u32,
view_chunks: u32,
view_offset: u32,
timeserie: bool,
width: i32, width: i32,
height: i32, height: i32,
limit: bool,
reduce: bool,
shift: bool,
position: i32, position: i32,
) -> rusqlite::Result<Panel> { ) -> rusqlite::Result<Panel> {
self.conn.execute( self.conn.execute(
"INSERT INTO panels (name, view_scroll, view_size, timeserie, width, height, limit_view, position) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", "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, true, view_size, true, width, height, false, position] 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 = ?")?; let mut statement = self.conn.prepare("SELECT * FROM panels WHERE name = ?")?;
for panel in statement.query_map(params![name], |row| { for panel in statement.query_map(params![name], |row| {
@ -248,6 +264,11 @@ impl SQLiteDataStore {
width: row.get(5)?, width: row.get(5)?,
height: row.get(6)?, height: row.get(6)?,
limit: row.get(7)?, 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 { if let Ok(p) = panel {
@ -262,16 +283,20 @@ impl SQLiteDataStore {
id: i32, id: i32,
name: &str, name: &str,
view_scroll: bool, view_scroll: bool,
view_size: i32, view_size: u32,
view_chunks: u32,
view_offset: u32,
timeserie: bool, timeserie: bool,
width: i32, width: i32,
height: i32, height: i32,
limit: bool, limit: bool,
reduce: bool,
shift: bool,
position: i32, position: i32,
) -> rusqlite::Result<usize> { ) -> rusqlite::Result<usize> {
self.conn.execute( self.conn.execute(
"UPDATE panels SET name = ?, view_scroll = ?, view_size = ?, timeserie = ?, width = ?, height = ?, limit_view = ?, position = ? WHERE 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, id], params![name, view_scroll, view_size, timeserie, width, height, limit, position, reduce, view_chunks, shift, view_offset, id],
) )
} }

View file

@ -21,15 +21,37 @@ pub fn panel_title_ui(ui: &mut Ui, panel: &mut Panel, extra: bool) {
ui.heading(panel.name.as_str()); ui.heading(panel.name.as_str());
ui.with_layout(Layout::right_to_left(), |ui| { ui.with_layout(Layout::right_to_left(), |ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.toggle_value(&mut panel.view_scroll, ""); ui.toggle_value(&mut panel.view_scroll, "🔒");
ui.separator(); ui.separator();
ui.label("m"); if panel.limit {
ui.add( ui.label("min"); // TODO makes no sense if it's not a timeserie
DragValue::new(&mut panel.view_size) ui.add(
.speed(10) DragValue::new(&mut panel.view_size)
.clamp_range(0..=2147483647i32), .speed(10)
); .clamp_range(0..=2147483647i32),
ui.checkbox(&mut panel.limit, "limit"); );
}
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 { if extra {
ui.separator(); ui.separator();
ui.checkbox(&mut panel.timeserie, "timeserie"); ui.checkbox(&mut panel.timeserie, "timeserie");
@ -52,11 +74,12 @@ pub fn panel_body_ui(ui: &mut Ui, panel: &mut Panel, sources: &Vec<Source>) {
} }
if panel.view_scroll { 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 { if panel.limit {
p = p p = p
.include_x((Utc::now().timestamp() + (panel.view_size as i64 * 3)) as f64) .include_x(_now + (panel.view_size as f64 * 3.0))
.include_x((Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64); // ??? TODO .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<Source>) {
} }
p.show(ui, |plot_ui| { p.show(ui, |plot_ui| {
for source in &*sources { for source in sources {
if source.panel_id == panel.id { if source.panel_id == panel.id {
let line = if panel.limit { let _now = Utc::now().timestamp() as f64;
Line::new(source.values_filter( let _off = (panel.view_offset as f64) * 60.0; // TODO multiplying x60 makes sense only for timeseries
(Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64, 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 };
.name(source.name.as_str()) let max_x = if panel.shift { Some(_now - _off) } else { None };
} else { let chunk_size = if panel.reduce { Some(panel.view_chunks) } else { None };
Line::new(source.values()).name(source.name.as_str()) // 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)); plot_ui.line(line.color(source.color));
} }
} }

View file

@ -14,10 +14,14 @@ pub fn native_save(state: Arc<ApplicationState>) {
panel.name.as_str(), panel.name.as_str(),
panel.view_scroll, panel.view_scroll,
panel.view_size, panel.view_size,
panel.view_chunks,
panel.view_offset,
panel.timeserie, panel.timeserie,
panel.width, panel.width,
panel.height, panel.height,
panel.limit, panel.limit,
panel.reduce,
panel.shift,
index as i32, index as i32,
) { ) {
warn!(target: "native-save", "Could not update panel #{} : {:?}", panel.id, e); warn!(target: "native-save", "Could not update panel #{} : {:?}", panel.id, e);