mirror of
https://git.alemi.dev/dashboard.git
synced 2024-11-14 11:59:18 +01:00
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:
parent
e8df50f010
commit
7c6f765455
5 changed files with 121 additions and 34 deletions
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue