feat: made lines color customizable

This commit is contained in:
əlemi 2022-06-08 14:05:18 +02:00
parent 203cff23f9
commit 4cc469f682
Signed by: alemi
GPG key ID: A4895B84D311642C
5 changed files with 99 additions and 41 deletions

View file

@ -6,6 +6,7 @@ use std::sync::{RwLock, Mutex};
use std::num::ParseFloatError; use std::num::ParseFloatError;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use eframe::egui::plot::{Values, Value}; use eframe::egui::plot::{Values, Value};
use eframe::epaint::Color32;
use self::store::SQLiteDataStore; use self::store::SQLiteDataStore;
@ -64,8 +65,9 @@ impl ApplicationState {
Ok(()) Ok(())
} }
pub fn add_source(&self, panel_id:i32, name:&str, url:&str, query_x:&str, query_y:&str) -> Result<(), FetchError> { pub fn add_source(&self, panel_id:i32, name:&str, url:&str, query_x:&str, query_y:&str, color:Color32, visible:bool) -> Result<(), FetchError> {
let source = self.storage.lock().expect("Storage Mutex poisoned").new_source(panel_id, name, url, query_x, query_y)?; let source = self.storage.lock().expect("Storage Mutex poisoned")
.new_source(panel_id, name, url, query_x, query_y, color, visible)?;
let panels = self.panels.read().expect("Panels RwLock poisoned"); let panels = self.panels.read().expect("Panels RwLock poisoned");
for panel in &*panels { for panel in &*panels {
if panel.id == panel_id { if panel.id == panel_id {
@ -97,6 +99,8 @@ pub struct Source {
pub name: String, pub name: String,
pub url: String, pub url: String,
pub interval: i32, pub interval: i32,
pub color: Color32,
pub visible: bool,
pub(crate) last_fetch: RwLock<DateTime<Utc>>, pub(crate) last_fetch: RwLock<DateTime<Utc>>,
pub query_x: String, pub query_x: String,
// pub(crate) compiled_query_x: Arc<Mutex<jq_rs::JqProgram>>, // pub(crate) compiled_query_x: Arc<Mutex<jq_rs::JqProgram>>,

View file

@ -1,8 +1,9 @@
use crate::app::data::{Panel, Source}; use crate::app::{data::{Panel, Source}, util::repack_color};
use chrono::{TimeZone, Utc}; use chrono::{TimeZone, Utc};
use eframe::egui::plot::Value; use eframe::egui::{Color32, plot::Value};
use rusqlite::{params, Connection}; use rusqlite::{params, Connection};
use std::sync::RwLock; use std::sync::RwLock;
use crate::app::util::unpack_color;
pub trait DataStorage { pub trait DataStorage {
fn add_panel(&self, name: &str); fn add_panel(&self, name: &str);
@ -19,12 +20,12 @@ impl SQLiteDataStore {
conn.execute( conn.execute(
"CREATE TABLE IF NOT EXISTS panels ( "CREATE TABLE IF NOT EXISTS panels (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
name TEXT UNIQUE, name TEXT UNIQUE NOT NULL,
view_scroll BOOL, view_scroll BOOL NOT NULL,
view_size INT, view_size INT NOT NULL,
timeserie BOOL, timeserie BOOL NOT NULL,
width INT, width INT NOT NULL,
height INT height INT NOT NULL
);", );",
[], [],
)?; )?;
@ -32,12 +33,14 @@ impl SQLiteDataStore {
conn.execute( conn.execute(
"CREATE TABLE IF NOT EXISTS sources ( "CREATE TABLE IF NOT EXISTS sources (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
name TEXT, name TEXT NOT NULL,
url TEXT, url TEXT NOT NULL,
interval INT, interval INT NOT NULL,
query_x TEXT, query_x TEXT NOT NULL,
query_y TEXT, query_y TEXT NOT NULL,
panel_id INT panel_id INT NOT NULL,
color INT NULL,
visible BOOL NOT NULL
);", );",
[], [],
)?; )?;
@ -45,10 +48,10 @@ impl SQLiteDataStore {
conn.execute( conn.execute(
"CREATE TABLE IF NOT EXISTS points ( "CREATE TABLE IF NOT EXISTS points (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
panel_id INT, panel_id INT NOT NULL,
source_id INT, source_id INT NOT NULL,
x FLOAT, x FLOAT NOT NULL,
y FLOAT y FLOAT NOT NULL
);", );",
[], [],
)?; )?;
@ -105,6 +108,8 @@ impl SQLiteDataStore {
query_y: row.get(5)?, query_y: row.get(5)?,
// compiled_query_y: Arc::new(Mutex::new(jq_rs::compile(row.get::<usize, String>(5)?.as_str()).unwrap())), // compiled_query_y: Arc::new(Mutex::new(jq_rs::compile(row.get::<usize, String>(5)?.as_str()).unwrap())),
// panel_id: row.get(6)?, // panel_id: row.get(6)?,
color: unpack_color(row.get(7).unwrap_or(0)),
visible: row.get(8)?,
data: RwLock::new(Vec::new()), data: RwLock::new(Vec::new()),
}) })
})?; })?;
@ -127,10 +132,13 @@ impl SQLiteDataStore {
url: &str, url: &str,
query_x: &str, query_x: &str,
query_y: &str, query_y: &str,
color: Color32,
visible: bool,
) -> rusqlite::Result<Source> { ) -> rusqlite::Result<Source> {
let color_u32 : Option<u32> = if color == Color32::TRANSPARENT { None } else { Some(repack_color(color)) };
self.conn.execute( self.conn.execute(
"INSERT INTO sources(name, url, interval, query_x, query_y, panel_id) VALUES (?, ?, ?, ?, ?, ?)", "INSERT INTO sources(name, url, interval, query_x, query_y, panel_id, color, visible) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
params![name, url, 60, query_x, query_y, panel_id], params![name, url, 60i32, query_x, query_y, panel_id, color_u32, visible],
)?; )?;
let mut statement = self let mut statement = self
.conn .conn
@ -141,17 +149,19 @@ impl SQLiteDataStore {
name: row.get(1)?, name: row.get(1)?,
url: row.get(2)?, url: row.get(2)?,
interval: row.get(3)?, interval: row.get(3)?,
query_x: row.get(4)?,
query_y: row.get(5)?,
// panel_id: row.get(6)?,
last_fetch: RwLock::new(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)), last_fetch: RwLock::new(Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)),
query_x: row.get(4)?,
// compiled_query_x: Arc::new(Mutex::new(jq_rs::compile(row.get::<usize, String>(4)?.as_str()).unwrap())),
query_y: row.get(5)?,
// compiled_query_y: Arc::new(Mutex::new(jq_rs::compile(row.get::<usize, String>(5)?.as_str()).unwrap())),
// panel_id: row.get(6)?,
color: unpack_color(row.get(7).unwrap_or(0)),
visible: row.get(8)?,
data: RwLock::new(Vec::new()), data: RwLock::new(Vec::new()),
}) })
})? { })? {
if let Ok(p) = panel { if let Ok(p) = panel {
return Ok(p); return Ok(p);
} else {
println!("WTF");
} }
} }
@ -166,10 +176,13 @@ impl SQLiteDataStore {
interval: i32, interval: i32,
query_x: &str, query_x: &str,
query_y: &str, query_y: &str,
color: Color32,
visible: bool,
) -> rusqlite::Result<usize> { ) -> rusqlite::Result<usize> {
let color_u32 : Option<u32> = if color == Color32::TRANSPARENT { None } else { Some(repack_color(color)) };
self.conn.execute( self.conn.execute(
"UPDATE sources SET name = ?, url = ?, interval = ?, query_x = ?, query_y = ? WHERE id = ?", "UPDATE sources SET name = ?, url = ?, interval = ?, query_x = ?, query_y = ?, color = ?, visible = ? WHERE id = ?",
params![name, url, interval, query_x, query_y, source_id], params![name, url, interval, query_x, query_y, color_u32, visible, source_id],
) )
} }

View file

@ -5,7 +5,7 @@ pub mod util;
use std::sync::Arc; use std::sync::Arc;
use chrono::Utc; use chrono::Utc;
use eframe::egui; use eframe::egui;
use eframe::egui::{plot::{Line, Plot}}; use eframe::egui::{RichText, plot::{Line, Plot}, Color32};
use self::data::ApplicationState; use self::data::ApplicationState;
use self::worker::native_save; use self::worker::native_save;
@ -18,6 +18,8 @@ struct InputBuffer {
interval: i32, interval: i32,
query_x: String, query_x: String,
query_y: String, query_y: String,
color: Color32,
visible: bool,
panel_id: i32, panel_id: i32,
} }
@ -30,6 +32,8 @@ impl Default for InputBuffer {
interval: 60, interval: 60,
query_x: "".to_string(), query_x: "".to_string(),
query_y: "".to_string(), query_y: "".to_string(),
color: Color32::TRANSPARENT,
visible: true,
panel_id: 0, panel_id: 0,
} }
} }
@ -84,7 +88,9 @@ impl eframe::App for App {
} }
} }
); );
ui.checkbox(&mut self.input.visible, "visible");
ui.add(egui::Slider::new(&mut self.input.interval, 1..=60)); ui.add(egui::Slider::new(&mut self.input.interval, 1..=60));
ui.color_edit_button_srgba(&mut self.input.color);
if ui.button("add").clicked() { if ui.button("add").clicked() {
self.data.add_source( self.data.add_source(
self.input.panel_id, self.input.panel_id,
@ -92,6 +98,8 @@ impl eframe::App for App {
self.input.url.as_str(), self.input.url.as_str(),
self.input.query_x.as_str(), self.input.query_x.as_str(),
self.input.query_y.as_str(), self.input.query_y.as_str(),
self.input.color,
self.input.visible,
).unwrap(); ).unwrap();
} }
ui.separator(); ui.separator();
@ -128,11 +136,19 @@ impl eframe::App for App {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.heading(panel.name.as_str()); ui.heading(panel.name.as_str());
ui.separator(); ui.separator();
if !self.edit { for source in &mut *sources {
for source in &*sources { if self.edit {
ui.label(source.name.as_str()); ui.checkbox(&mut source.visible, "");
ui.separator();
} }
if source.visible {
ui.label(
RichText::new(source.name.as_str())
.color(if source.color == Color32::TRANSPARENT { Color32::GRAY } else { source.color })
);
} else {
ui.label(RichText::new(source.name.as_str()).color(Color32::BLACK));
}
ui.separator();
} }
ui.checkbox(&mut panel.view_scroll, "autoscroll"); ui.checkbox(&mut panel.view_scroll, "autoscroll");
ui.checkbox(&mut panel.timeserie, "timeserie"); ui.checkbox(&mut panel.timeserie, "timeserie");
@ -150,11 +166,12 @@ impl eframe::App for App {
for source in &mut *sources { for source in &mut *sources {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add(egui::Slider::new(&mut source.interval, 1..=60)); ui.add(egui::Slider::new(&mut source.interval, 1..=60));
eframe::egui::TextEdit::singleline(&mut source.url).hint_text("url").desired_width(250.0).show(ui); eframe::egui::TextEdit::singleline(&mut source.url).hint_text("url").desired_width(300.0).show(ui);
if !panel.timeserie { if !panel.timeserie {
eframe::egui::TextEdit::singleline(&mut source.query_x).hint_text("x").desired_width(25.0).show(ui); eframe::egui::TextEdit::singleline(&mut source.query_x).hint_text("x").desired_width(50.0).show(ui);
} }
eframe::egui::TextEdit::singleline(&mut source.query_y).hint_text("y").desired_width(25.0).show(ui); eframe::egui::TextEdit::singleline(&mut source.query_y).hint_text("y").desired_width(50.0).show(ui);
ui.color_edit_button_srgba(&mut source.color);
ui.separator(); ui.separator();
ui.label(source.name.as_str()); ui.label(source.name.as_str());
}); });
@ -185,10 +202,13 @@ impl eframe::App for App {
p.show(ui, |plot_ui| { p.show(ui, |plot_ui| {
for source in &mut *sources { for source in &mut *sources {
if self.filter { if source.visible {
plot_ui.line(Line::new(source.values_filter((Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64)).name(source.name.as_str())); let line = if self.filter {
} else { Line::new(source.values_filter((Utc::now().timestamp() - (panel.view_size as i64 * 60)) as f64)).name(source.name.as_str())
plot_ui.line(Line::new(source.values()).name(source.name.as_str())); } else {
Line::new(source.values()).name(source.name.as_str())
};
plot_ui.line(line.color(source.color));
} }
} }
}); });

View file

@ -1,5 +1,6 @@
// if you're handling more than terabytes of data, it's the future and you ought to update this code! // if you're handling more than terabytes of data, it's the future and you ought to update this code!
use chrono::{DateTime, Utc, NaiveDateTime}; use chrono::{DateTime, Utc, NaiveDateTime};
use eframe::egui::Color32;
const PREFIXES: &'static [&'static str] = &["", "k", "M", "G", "T"]; const PREFIXES: &'static [&'static str] = &["", "k", "M", "G", "T"];
@ -20,4 +21,22 @@ pub fn timestamp_to_str(t:i64) -> String {
DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(t, 0), Utc) DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(t, 0), Utc)
.format("%Y/%m/%d %H:%M:%S") .format("%Y/%m/%d %H:%M:%S")
) )
}
pub fn unpack_color(c: u32) -> Color32 {
let r : u8 = (c >> 0) as u8;
let g : u8 = (c >> 8) as u8;
let b : u8 = (c >> 16) as u8;
let a : u8 = (c >> 24) as u8;
return Color32::from_rgba_unmultiplied(r, g, b, a);
}
pub fn repack_color(c: Color32) -> u32 {
let mut out : u32 = 0;
let mut offset = 0;
for el in c.to_array() {
out |= ((el & 0xFF) as u32) << offset;
offset += 8;
}
return out;
} }

View file

@ -26,6 +26,8 @@ pub fn native_save(state:Arc<ApplicationState>) {
source.interval, source.interval,
source.query_x.as_str(), source.query_x.as_str(),
source.query_y.as_str(), source.query_y.as_str(),
source.color,
source.visible,
).unwrap(); ).unwrap();
} }
} }