Compare commits
2 Commits
de2588b60e
...
e6f47328a0
Author | SHA1 | Date |
---|---|---|
David Holland | e6f47328a0 | |
David Holland | 9124e147f2 |
|
@ -7,7 +7,9 @@
|
|||
!/config.toml
|
||||
!/index.html
|
||||
!/rust-toolchain.toml
|
||||
!/Trunk.toml
|
||||
|
||||
!/assets/
|
||||
!/beanconqueror/
|
||||
!/shots/
|
||||
!/src/
|
||||
|
|
14
Cargo.toml
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
authors = ["David Holland <info@dustvoice.de>"]
|
||||
name = "rustybeans"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
@ -12,26 +12,22 @@ chrono = { version = "0.4.23", features = ["wasmbind"] }
|
|||
console_error_panic_hook = "0.1.7"
|
||||
fast-float = "0.2.0"
|
||||
palette = "0.6.1"
|
||||
plotly = { version = "0.8.1", features = ["wasm"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
toml = "0.5.9"
|
||||
indicatif = "0.17.3"
|
||||
dialoguer = "0.10.3"
|
||||
notify-rust = "4.7.0"
|
||||
crossterm = "0.25.0"
|
||||
rfd = "0.10.0"
|
||||
egui = "0.22.0"
|
||||
eframe = "0.22.0"
|
||||
log = "0.4.20"
|
||||
async-std = "1.12.0"
|
||||
zip = "0.6.6"
|
||||
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
tracing-subscriber = "0.3"
|
||||
env_logger = "0.10"
|
||||
|
||||
# web:
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
console_error_panic_hook = "0.1.6"
|
||||
tracing-wasm = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
|
||||
|
||||
|
|
Before Width: | Height: | Size: 90 KiB |
161
Shots/Shots.py
|
@ -1,161 +0,0 @@
|
|||
import pandas as pd
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import numpy as np
|
||||
from numpy import diff
|
||||
|
||||
from scipy.interpolate import make_interp_spline
|
||||
from scipy.signal import savgol_filter
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
timestamp_start = None
|
||||
|
||||
def unix_to_datetime(unix_string):
|
||||
return datetime.strptime(unix_string, "%H:%M:%S.%f")
|
||||
|
||||
def deltatime(time):
|
||||
global timestamp_start
|
||||
return (time - timestamp_start).total_seconds()
|
||||
|
||||
def timestamp_converter(timestamp):
|
||||
time = unix_to_datetime(timestamp)
|
||||
|
||||
global timestamp_start
|
||||
if timestamp_start == None:
|
||||
timestamp_start = time
|
||||
|
||||
delta = deltatime(time)
|
||||
return delta
|
||||
|
||||
shots = [
|
||||
{
|
||||
'filename': 'Elisabeth/16_06_2022_1.xlsx',
|
||||
'title': 'Elisabeth 16.06.2022 #1',
|
||||
'cutoff': 38,
|
||||
}, # [0]
|
||||
{
|
||||
'filename': 'Elisabeth/19_06_2022_1.xlsx',
|
||||
'title': 'Elisabeth 19.06.2022 #1',
|
||||
}, # [1]
|
||||
{
|
||||
'filename': 'Laura/20_06_2022_1.xlsx',
|
||||
'title': 'Laura 20.06.2022 #1',
|
||||
'cutoff': 38.7,
|
||||
}, # [2]
|
||||
{
|
||||
'filename': 'Laura/20_06_2022_2.xlsx',
|
||||
'title': 'Laura 20.06.2022 #2',
|
||||
'cutoff': 32.6,
|
||||
}, # [3]
|
||||
]
|
||||
|
||||
comparison = {
|
||||
'Elisabeth 19.06.22 Shot #1 vs. #2': (0, 1), #[0]
|
||||
'Laura 20.06.22 Shot #1 vs #2': (2, 3), #[1]
|
||||
#'comparison': {
|
||||
# 'Elisabeth Shots 06/13/22 - 06/10/22': (1, 2), #[0]
|
||||
# 'Elisabeth Shots 06/15/22 - 06/10/22': (1, 3), #[1]
|
||||
# 'Elisabeth Shots 06/15/22 - 06/13/22': (2, 3), #[2]
|
||||
# }
|
||||
}
|
||||
|
||||
|
||||
curr_figure = 0
|
||||
|
||||
fig1 = plt.figure(curr_figure)
|
||||
ax1 = plt.subplot(2, 2, 1)
|
||||
# ax1.minorticks_on()
|
||||
# plt.title('Shotweight over time')
|
||||
plt.ylabel('Weight (g)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
ax2 = plt.subplot(2, 2, 3, sharex=ax1)
|
||||
# plt.title('Flow-rate over time')
|
||||
plt.ylabel('Flow-rate (g/s)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
ax3 = plt.subplot(2, 2, 2, sharex=ax1, sharey=ax1)
|
||||
# plt.title('Shotweight over time')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
ax4 = plt.subplot(2, 2, 4, sharex=ax2, sharey=ax2)
|
||||
# plt.title('Flow-rate over time')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
fig1.supxlabel('Time (s)')
|
||||
plt.tight_layout()
|
||||
|
||||
plt.subplot(2, 2, 1)
|
||||
|
||||
for shot in shots:
|
||||
timestamp_start = None
|
||||
|
||||
df_raw = pd.read_excel(shot['filename'], converters={
|
||||
0: lambda x: timestamp_converter(x)
|
||||
}, sheet_name=0)
|
||||
df_calc = pd.read_excel(shot['filename'], converters={
|
||||
0: lambda x: timestamp_converter(x)
|
||||
}, sheet_name=1)
|
||||
|
||||
time_col_raw = df_raw.keys()[0]
|
||||
profile_col_raw = df_raw.keys()[5]
|
||||
|
||||
time_col_calc = df_calc.keys()[0]
|
||||
profile_col_calc = df_calc.keys()[2]
|
||||
|
||||
if 'cutoff' in shot:
|
||||
if shot['cutoff'] != -1:
|
||||
df_raw = df_raw.loc[df_raw[time_col_raw] < shot['cutoff']]
|
||||
df_calc = df_calc.loc[df_calc[time_col_calc] < shot['cutoff']]
|
||||
|
||||
shot['data'] = {}
|
||||
|
||||
shot['data']['profile'] = df_raw
|
||||
shot['data']['flowrate'] = df_calc
|
||||
|
||||
time = df_raw[time_col_raw].tolist()
|
||||
weight = df_raw[profile_col_raw].tolist()
|
||||
# print("time: ", time)
|
||||
# print("weight: ", time)
|
||||
plt.plot(time, weight, label = shot['title'], linewidth=1)
|
||||
|
||||
plt.subplot(2, 2, 3)
|
||||
|
||||
flow_time = df_calc[time_col_calc].tolist()
|
||||
flowrate = df_calc[profile_col_calc].tolist()
|
||||
# print("flow_time: ", flow_time)
|
||||
# print("flowrate: ", flowrate)
|
||||
plt.plot(flow_time, flowrate, label = shot['title'], linewidth=1)
|
||||
|
||||
plt.subplot(2, 2, 1)
|
||||
|
||||
plt.subplot(2, 2, 4)
|
||||
|
||||
for key in comparison:
|
||||
calc1 = shots[comparison[key][0]]['data']['flowrate']
|
||||
calc2 = shots[comparison[key][1]]['data']['flowrate']
|
||||
|
||||
t1 = np.array(calc1[calc1.keys()[0]].tolist())
|
||||
r1 = np.array(calc1[calc1.keys()[2]].tolist())
|
||||
|
||||
t2 = np.array(calc2[calc2.keys()[0]].tolist())
|
||||
r2 = np.array(calc2[calc2.keys()[2]].tolist())
|
||||
|
||||
dt = None
|
||||
size_diff = t2.size - t1.size
|
||||
if size_diff > 0:
|
||||
dt = t2
|
||||
r1 = np.pad(r1, (0, size_diff), 'constant')
|
||||
elif size_diff < 0:
|
||||
dt = t1
|
||||
r2 = np.pad(r2, (0, -size_diff), 'constant')
|
||||
|
||||
dr = r2 - r1
|
||||
|
||||
plt.plot(dt, dr, label = key, linewidth=1)
|
||||
|
||||
plt.legend(loc='best')
|
||||
|
||||
plt.show()
|
|
@ -1,75 +0,0 @@
|
|||
import pandas as pd
|
||||
|
||||
import numpy as np
|
||||
from numpy import diff
|
||||
|
||||
from scipy.interpolate import make_interp_spline
|
||||
from scipy.signal import savgol_filter
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
shots = [
|
||||
{
|
||||
'filename': 'Elisabeth/16_06_2022_1.xlsx',
|
||||
'title': 'Elisabeth 16.06.2022 #1',
|
||||
'cutoff': 38,
|
||||
'data': {}
|
||||
}, # [0]
|
||||
]
|
||||
|
||||
curr_figure = 0
|
||||
|
||||
plt.figure(curr_figure)
|
||||
plt.subplot(2, 1, 1)
|
||||
# plt.title('Shotweight over time')
|
||||
# plt.xlabel('Time (s)')
|
||||
plt.ylabel('Weight (g)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
plt.subplot(2, 1, 2)
|
||||
# plt.title('Flow-rate over time')
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Flow-rate (g/s)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
plt.subplot(2, 1, 1)
|
||||
|
||||
for shot in shots:
|
||||
df_raw = pd.read_excel(shot['filename'], converters={
|
||||
1: lambda x: float(x) if not str(x).endswith('.10') else float(x) + 0.85
|
||||
}, sheet_name=0)
|
||||
df_calc = pd.read_excel(shot['filename'], converters={
|
||||
1: lambda x: float(x) if not str(x).endswith('.10') else float(x) + 0.85
|
||||
}, sheet_name=1)
|
||||
|
||||
time_col_raw = df_raw.keys()[1]
|
||||
profile_col_raw = df_raw.keys()[5]
|
||||
|
||||
time_col_calc = df_calc.keys()[1]
|
||||
profile_col_calc = df_calc.keys()[2]
|
||||
|
||||
df_raw_co = df_raw.loc[df_raw[time_col_raw] < shot['cutoff']]
|
||||
df_calc_co = df_calc.loc[df_calc[time_col_calc] < shot['cutoff']]
|
||||
|
||||
shot['data']['profile'] = df_raw_co
|
||||
shot['data']['flowrate'] = df_calc_co
|
||||
|
||||
time = df_raw_co[time_col_raw].tolist()
|
||||
weight = df_raw_co[profile_col_raw].tolist()
|
||||
print("time: ", time)
|
||||
print("weight: ", time)
|
||||
plt.plot(time, weight, label = shot['title'], linewidth=1)
|
||||
|
||||
plt.subplot(2, 1, 2)
|
||||
|
||||
flow_time = df_calc_co[time_col_calc].tolist()
|
||||
flowrate = df_calc_co[profile_col_calc].tolist()
|
||||
print("flow_time: ", flow_time)
|
||||
print("flowrate: ", flowrate)
|
||||
plt.plot(flow_time, flowrate, label = shot['title'], linewidth=1)
|
||||
|
||||
plt.subplot(2, 1, 1)
|
||||
|
||||
plt.legend(loc='best')
|
||||
|
||||
plt.show()
|
|
@ -1,160 +0,0 @@
|
|||
import csv
|
||||
|
||||
import numpy as np
|
||||
from numpy import diff
|
||||
|
||||
from scipy.interpolate import make_interp_spline
|
||||
from scipy.signal import savgol_filter
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
filenames = [
|
||||
'Laura/06_09-1.csv', # [0]
|
||||
'Laura/06_09-2.csv', # [1]
|
||||
'Elisabeth/06_10-1.csv', #[2]
|
||||
'Elisabeth/06_10-2.csv', #[3]
|
||||
# 'Elisabeth/06_12-1.csv',
|
||||
'Elisabeth/06_13-1.csv', #[4]
|
||||
'Elisabeth/06_13-2.csv', #[5]
|
||||
'Elisabeth/06_15-1.csv', #[6]
|
||||
'Elisabeth/06_15-2.csv', #[7]
|
||||
]
|
||||
|
||||
comparison = {
|
||||
'Laura Shot #1 vs #2': (0, 1), #[0]
|
||||
'Elisabeth 06/10/22 Shot #2 - #1': (2, 3), #[1]
|
||||
'Elisabeth 06/13/22 Shot #2 - #1': (4, 5), #[2]
|
||||
'Elisabeth 06/15/22 Shot #2 - #1': (6, 7), #[3]
|
||||
'comparison': {
|
||||
'Elisabeth Shots 06/13/22 - 06/10/22': (1, 2), #[0]
|
||||
'Elisabeth Shots 06/15/22 - 06/10/22': (1, 3), #[1]
|
||||
'Elisabeth Shots 06/15/22 - 06/13/22': (2, 3), #[2]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data = {}
|
||||
|
||||
curr_figure = 0
|
||||
|
||||
plt.figure(curr_figure)
|
||||
plt.subplot(2, 1, 1)
|
||||
# plt.title('Shotweight over time')
|
||||
# plt.xlabel('Time (s)')
|
||||
plt.ylabel('Weight (g)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
plt.subplot(2, 1, 2)
|
||||
# plt.title('Flow-rate over time')
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Flow-rate (g/s)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
plt.subplot(2, 1, 1)
|
||||
|
||||
for filename in filenames:
|
||||
data[filename] = {
|
||||
'shot_name': '',
|
||||
'rows': [],
|
||||
'time': [],
|
||||
'weight': []
|
||||
}
|
||||
|
||||
with open(filename, newline='') as file:
|
||||
reader = csv.DictReader(file, delimiter=',')
|
||||
|
||||
for row in reader:
|
||||
data[filename]['rows'].append(row)
|
||||
|
||||
if row['information_type'] == 'meta':
|
||||
if row['metatype'] == 'Name':
|
||||
data[filename]['shot_name'] = row['metadata']
|
||||
elif row['information_type'] == 'moment':
|
||||
try:
|
||||
elapsed = float(row['elapsed'])
|
||||
weight = float(row['current_total_shot_weight'])
|
||||
|
||||
data[filename]['time'].append(elapsed)
|
||||
data[filename]['weight'].append(weight)
|
||||
except ValueError:
|
||||
continue
|
||||
#print("Not a float!")
|
||||
|
||||
time = data[filename]['time']
|
||||
weight = data[filename]['weight']
|
||||
|
||||
first_zero = list(x > 0 for x in weight).index(True) - 1
|
||||
zero_time = time[first_zero]
|
||||
|
||||
weight = weight[first_zero:]
|
||||
time = time[first_zero:]
|
||||
time = list(map(lambda t: t - zero_time, time))
|
||||
|
||||
data[filename]['time'] = time
|
||||
data[filename]['weight'] = weight
|
||||
|
||||
weight_savgol = savgol_filter(weight, 30, 3)
|
||||
|
||||
plt.plot(time, weight_savgol, label = data[filename]['shot_name'], linewidth=1)
|
||||
plt.scatter(time, weight, label = '', s=0.25)
|
||||
|
||||
plt.subplot(2, 1, 2)
|
||||
|
||||
dweight_dt = diff(weight)/diff(time)
|
||||
dweight_dt_savgol = savgol_filter(dweight_dt, 60, 3)
|
||||
|
||||
plt.plot(time[:-1], dweight_dt_savgol, label = data[filename]['shot_name'], linewidth=1)
|
||||
plt.scatter(time[1:-1], dweight_dt[1:], label = '', s=0.25)
|
||||
|
||||
plt.subplot(2, 1, 1)
|
||||
|
||||
#print(filename, "\n", time, "\n", weight)
|
||||
|
||||
plt.legend(loc='best')
|
||||
|
||||
def comparison_plot(curr_figure, comparison_dict):
|
||||
curr_figure += 1
|
||||
plt.figure(curr_figure)
|
||||
|
||||
for key in comparison_dict:
|
||||
if key != 'comparison':
|
||||
item = comparison_dict[key]
|
||||
i0 = item[0]
|
||||
i1 = item[1]
|
||||
|
||||
fn0 = filenames[i0]
|
||||
fn1 = filenames[i1]
|
||||
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Weight difference (g)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
t0 = np.array(data[fn0]['time'])
|
||||
w0 = np.array(data[fn0]['weight'])
|
||||
|
||||
t1 = np.array(data[fn1]['time'])
|
||||
w1 = np.array(data[fn1]['weight'])
|
||||
|
||||
time_linspace = np.linspace(max(np.array(t0).min(), np.array(t1).min()), min(np.array(t0).max(), np.array(t1).max()), max(len(t0), len(t1)))
|
||||
|
||||
i0 = make_interp_spline(t0, w0, k=3)
|
||||
wi0 = i0(time_linspace)
|
||||
i1 = make_interp_spline(t1, w1, k=3)
|
||||
wi1 = i1(time_linspace)
|
||||
|
||||
dw = wi1 - wi0
|
||||
|
||||
dw_savgol = savgol_filter(dw, 30, 3)
|
||||
|
||||
plt.plot(time_linspace, dw_savgol, label = key, linewidth=1)
|
||||
plt.scatter(time_linspace, dw, label = '', s=0.25)
|
||||
|
||||
plt.legend(loc='best')
|
||||
else:
|
||||
curr_figure = comparison_plot(curr_figure, comparison_dict['comparison'])
|
||||
return curr_figure
|
||||
|
||||
curr_figure = comparison_plot(curr_figure, comparison)
|
||||
|
||||
|
||||
plt.show()
|
|
@ -0,0 +1 @@
|
|||
[build]
|
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 314 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 21 KiB |
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "egui Template PWA",
|
||||
"short_name": "egui-template-pwa",
|
||||
"icons": [
|
||||
{
|
||||
"src": "./icon-256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "./maskable_icon_x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "./icon-1024.png",
|
||||
"sizes": "1024x1024",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"lang": "en-US",
|
||||
"id": "/index.html",
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"background_color": "white",
|
||||
"theme_color": "white"
|
||||
}
|
After Width: | Height: | Size: 128 KiB |
|
@ -0,0 +1,25 @@
|
|||
var cacheName = 'egui-template-pwa';
|
||||
var filesToCache = [
|
||||
'./',
|
||||
'./index.html',
|
||||
'./eframe_template.js',
|
||||
'./eframe_template_bg.wasm',
|
||||
];
|
||||
|
||||
/* Start the service worker and cache all of the app's content */
|
||||
self.addEventListener('install', function (e) {
|
||||
e.waitUntil(
|
||||
caches.open(cacheName).then(function (cache) {
|
||||
return cache.addAll(filesToCache);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
/* Serve cached content when offline */
|
||||
self.addEventListener('fetch', function (e) {
|
||||
e.respondWith(
|
||||
caches.match(e.request).then(function (response) {
|
||||
return response || fetch(e.request);
|
||||
})
|
||||
);
|
||||
});
|
142
index.html
|
@ -1,16 +1,142 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
|
||||
<!-- Disable zooming: -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>RustyBeans</title>
|
||||
<meta name="viewport">
|
||||
<link data-trunk rel="copy-file" href="/config.toml">
|
||||
<link data-trunk rel="copy-dir" href="/Shots">
|
||||
<!-- change this to your project name -->
|
||||
<title>eframe template</title>
|
||||
|
||||
<!-- config for our rust wasm binary. go to https://trunkrs.dev/assets/#rust for more customization -->
|
||||
<link data-trunk rel="rust" data-wasm-opt="2" />
|
||||
<!-- this is the base url relative to which other urls will be constructed. trunk will insert this from the public-url option -->
|
||||
<base data-trunk-public-url />
|
||||
|
||||
<link data-trunk rel="icon" href="assets/favicon.ico">
|
||||
|
||||
|
||||
<link data-trunk rel="copy-file" href="assets/sw.js" />
|
||||
<link data-trunk rel="copy-file" href="assets/manifest.json" />
|
||||
<link data-trunk rel="copy-file" href="assets/icon-1024.png" />
|
||||
<link data-trunk rel="copy-file" href="assets/icon-256.png" />
|
||||
<link data-trunk rel="copy-file" href="assets/icon_ios_touch_192.png" />
|
||||
<link data-trunk rel="copy-file" href="assets/maskable_icon_x512.png" />
|
||||
|
||||
<link data-trunk rel="copy-dir" href="beanconqueror" data-target-path="beanconqueror" />
|
||||
|
||||
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<link rel="apple-touch-icon" href="icon_ios_touch_192.png">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="white">
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#404040">
|
||||
|
||||
<style>
|
||||
html {
|
||||
/* Remove touch delay: */
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
body {
|
||||
/* Light mode background color for what is not covered by the egui canvas,
|
||||
or where the egui canvas is translucent. */
|
||||
background: #909090;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
/* Dark mode background color for what is not covered by the egui canvas,
|
||||
or where the egui canvas is translucent. */
|
||||
background: #404040;
|
||||
}
|
||||
}
|
||||
|
||||
/* Allow canvas to fill entire web page: */
|
||||
html,
|
||||
body {
|
||||
overflow: hidden;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Position canvas in center-top: */
|
||||
canvas {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0%);
|
||||
}
|
||||
|
||||
.centered {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #f0f0f0;
|
||||
font-size: 24px;
|
||||
font-family: Ubuntu-Light, Helvetica, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------- */
|
||||
/* Loading animation from https://loading.io/css/ */
|
||||
.lds-dual-ring {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.lds-dual-ring:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #fff;
|
||||
border-color: #fff transparent #fff transparent;
|
||||
animation: lds-dual-ring 1.2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes lds-dual-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main"></div>
|
||||
<!-- The WASM code will resize the canvas dynamically -->
|
||||
<!-- the id is hardcoded in main.rs . so, make sure both match. -->
|
||||
<canvas id="RustyBeans"></canvas>
|
||||
|
||||
<!--Register Service Worker. this will cache the wasm / js scripts for offline use (for PWA functionality). -->
|
||||
<!-- Force refresh (Ctrl + F5) to load the latest files instead of cached files -->
|
||||
<script>
|
||||
// We disable caching during development so that we always view the latest version.
|
||||
if ('serviceWorker' in navigator && window.location.hash !== "#dev") {
|
||||
window.addEventListener('load', function () {
|
||||
navigator.serviceWorker.register('sw.js');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
<!-- Powered by egui: https://github.com/emilk/egui/ -->
|
||||
|
|
11430
shots/Beanconqueror.json
Before Width: | Height: | Size: 90 KiB |
161
shots/Shots.py
|
@ -1,161 +0,0 @@
|
|||
import pandas as pd
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import numpy as np
|
||||
from numpy import diff
|
||||
|
||||
from scipy.interpolate import make_interp_spline
|
||||
from scipy.signal import savgol_filter
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
timestamp_start = None
|
||||
|
||||
def unix_to_datetime(unix_string):
|
||||
return datetime.strptime(unix_string, "%H:%M:%S.%f")
|
||||
|
||||
def deltatime(time):
|
||||
global timestamp_start
|
||||
return (time - timestamp_start).total_seconds()
|
||||
|
||||
def timestamp_converter(timestamp):
|
||||
time = unix_to_datetime(timestamp)
|
||||
|
||||
global timestamp_start
|
||||
if timestamp_start == None:
|
||||
timestamp_start = time
|
||||
|
||||
delta = deltatime(time)
|
||||
return delta
|
||||
|
||||
shots = [
|
||||
{
|
||||
'filename': 'Elisabeth/16_06_2022_1.xlsx',
|
||||
'title': 'Elisabeth 16.06.2022 #1',
|
||||
'cutoff': 38,
|
||||
}, # [0]
|
||||
{
|
||||
'filename': 'Elisabeth/19_06_2022_1.xlsx',
|
||||
'title': 'Elisabeth 19.06.2022 #1',
|
||||
}, # [1]
|
||||
{
|
||||
'filename': 'Laura/20_06_2022_1.xlsx',
|
||||
'title': 'Laura 20.06.2022 #1',
|
||||
'cutoff': 38.7,
|
||||
}, # [2]
|
||||
{
|
||||
'filename': 'Laura/20_06_2022_2.xlsx',
|
||||
'title': 'Laura 20.06.2022 #2',
|
||||
'cutoff': 32.6,
|
||||
}, # [3]
|
||||
]
|
||||
|
||||
comparison = {
|
||||
'Elisabeth 19.06.22 Shot #1 vs. #2': (0, 1), #[0]
|
||||
'Laura 20.06.22 Shot #1 vs #2': (2, 3), #[1]
|
||||
#'comparison': {
|
||||
# 'Elisabeth Shots 06/13/22 - 06/10/22': (1, 2), #[0]
|
||||
# 'Elisabeth Shots 06/15/22 - 06/10/22': (1, 3), #[1]
|
||||
# 'Elisabeth Shots 06/15/22 - 06/13/22': (2, 3), #[2]
|
||||
# }
|
||||
}
|
||||
|
||||
|
||||
curr_figure = 0
|
||||
|
||||
fig1 = plt.figure(curr_figure)
|
||||
ax1 = plt.subplot(2, 2, 1)
|
||||
# ax1.minorticks_on()
|
||||
# plt.title('Shotweight over time')
|
||||
plt.ylabel('Weight (g)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
ax2 = plt.subplot(2, 2, 3, sharex=ax1)
|
||||
# plt.title('Flow-rate over time')
|
||||
plt.ylabel('Flow-rate (g/s)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
ax3 = plt.subplot(2, 2, 2, sharex=ax1, sharey=ax1)
|
||||
# plt.title('Shotweight over time')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
ax4 = plt.subplot(2, 2, 4, sharex=ax2, sharey=ax2)
|
||||
# plt.title('Flow-rate over time')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
fig1.supxlabel('Time (s)')
|
||||
plt.tight_layout()
|
||||
|
||||
plt.subplot(2, 2, 1)
|
||||
|
||||
for shot in shots:
|
||||
timestamp_start = None
|
||||
|
||||
df_raw = pd.read_excel(shot['filename'], converters={
|
||||
0: lambda x: timestamp_converter(x)
|
||||
}, sheet_name=0)
|
||||
df_calc = pd.read_excel(shot['filename'], converters={
|
||||
0: lambda x: timestamp_converter(x)
|
||||
}, sheet_name=1)
|
||||
|
||||
time_col_raw = df_raw.keys()[0]
|
||||
profile_col_raw = df_raw.keys()[5]
|
||||
|
||||
time_col_calc = df_calc.keys()[0]
|
||||
profile_col_calc = df_calc.keys()[2]
|
||||
|
||||
if 'cutoff' in shot:
|
||||
if shot['cutoff'] != -1:
|
||||
df_raw = df_raw.loc[df_raw[time_col_raw] < shot['cutoff']]
|
||||
df_calc = df_calc.loc[df_calc[time_col_calc] < shot['cutoff']]
|
||||
|
||||
shot['data'] = {}
|
||||
|
||||
shot['data']['profile'] = df_raw
|
||||
shot['data']['flowrate'] = df_calc
|
||||
|
||||
time = df_raw[time_col_raw].tolist()
|
||||
weight = df_raw[profile_col_raw].tolist()
|
||||
# print("time: ", time)
|
||||
# print("weight: ", time)
|
||||
plt.plot(time, weight, label = shot['title'], linewidth=1)
|
||||
|
||||
plt.subplot(2, 2, 3)
|
||||
|
||||
flow_time = df_calc[time_col_calc].tolist()
|
||||
flowrate = df_calc[profile_col_calc].tolist()
|
||||
# print("flow_time: ", flow_time)
|
||||
# print("flowrate: ", flowrate)
|
||||
plt.plot(flow_time, flowrate, label = shot['title'], linewidth=1)
|
||||
|
||||
plt.subplot(2, 2, 1)
|
||||
|
||||
plt.subplot(2, 2, 4)
|
||||
|
||||
for key in comparison:
|
||||
calc1 = shots[comparison[key][0]]['data']['flowrate']
|
||||
calc2 = shots[comparison[key][1]]['data']['flowrate']
|
||||
|
||||
t1 = np.array(calc1[calc1.keys()[0]].tolist())
|
||||
r1 = np.array(calc1[calc1.keys()[2]].tolist())
|
||||
|
||||
t2 = np.array(calc2[calc2.keys()[0]].tolist())
|
||||
r2 = np.array(calc2[calc2.keys()[2]].tolist())
|
||||
|
||||
dt = None
|
||||
size_diff = t2.size - t1.size
|
||||
if size_diff > 0:
|
||||
dt = t2
|
||||
r1 = np.pad(r1, (0, size_diff), 'constant')
|
||||
elif size_diff < 0:
|
||||
dt = t1
|
||||
r2 = np.pad(r2, (0, -size_diff), 'constant')
|
||||
|
||||
dr = r2 - r1
|
||||
|
||||
plt.plot(dt, dr, label = key, linewidth=1)
|
||||
|
||||
plt.legend(loc='best')
|
||||
|
||||
plt.show()
|
|
@ -1,75 +0,0 @@
|
|||
import pandas as pd
|
||||
|
||||
import numpy as np
|
||||
from numpy import diff
|
||||
|
||||
from scipy.interpolate import make_interp_spline
|
||||
from scipy.signal import savgol_filter
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
shots = [
|
||||
{
|
||||
'filename': 'Elisabeth/16_06_2022_1.xlsx',
|
||||
'title': 'Elisabeth 16.06.2022 #1',
|
||||
'cutoff': 38,
|
||||
'data': {}
|
||||
}, # [0]
|
||||
]
|
||||
|
||||
curr_figure = 0
|
||||
|
||||
plt.figure(curr_figure)
|
||||
plt.subplot(2, 1, 1)
|
||||
# plt.title('Shotweight over time')
|
||||
# plt.xlabel('Time (s)')
|
||||
plt.ylabel('Weight (g)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
plt.subplot(2, 1, 2)
|
||||
# plt.title('Flow-rate over time')
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Flow-rate (g/s)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
plt.subplot(2, 1, 1)
|
||||
|
||||
for shot in shots:
|
||||
df_raw = pd.read_excel(shot['filename'], converters={
|
||||
1: lambda x: float(x) if not str(x).endswith('.10') else float(x) + 0.85
|
||||
}, sheet_name=0)
|
||||
df_calc = pd.read_excel(shot['filename'], converters={
|
||||
1: lambda x: float(x) if not str(x).endswith('.10') else float(x) + 0.85
|
||||
}, sheet_name=1)
|
||||
|
||||
time_col_raw = df_raw.keys()[1]
|
||||
profile_col_raw = df_raw.keys()[5]
|
||||
|
||||
time_col_calc = df_calc.keys()[1]
|
||||
profile_col_calc = df_calc.keys()[2]
|
||||
|
||||
df_raw_co = df_raw.loc[df_raw[time_col_raw] < shot['cutoff']]
|
||||
df_calc_co = df_calc.loc[df_calc[time_col_calc] < shot['cutoff']]
|
||||
|
||||
shot['data']['profile'] = df_raw_co
|
||||
shot['data']['flowrate'] = df_calc_co
|
||||
|
||||
time = df_raw_co[time_col_raw].tolist()
|
||||
weight = df_raw_co[profile_col_raw].tolist()
|
||||
print("time: ", time)
|
||||
print("weight: ", time)
|
||||
plt.plot(time, weight, label = shot['title'], linewidth=1)
|
||||
|
||||
plt.subplot(2, 1, 2)
|
||||
|
||||
flow_time = df_calc_co[time_col_calc].tolist()
|
||||
flowrate = df_calc_co[profile_col_calc].tolist()
|
||||
print("flow_time: ", flow_time)
|
||||
print("flowrate: ", flowrate)
|
||||
plt.plot(flow_time, flowrate, label = shot['title'], linewidth=1)
|
||||
|
||||
plt.subplot(2, 1, 1)
|
||||
|
||||
plt.legend(loc='best')
|
||||
|
||||
plt.show()
|
|
@ -1,160 +0,0 @@
|
|||
import csv
|
||||
|
||||
import numpy as np
|
||||
from numpy import diff
|
||||
|
||||
from scipy.interpolate import make_interp_spline
|
||||
from scipy.signal import savgol_filter
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
filenames = [
|
||||
'Laura/06_09-1.csv', # [0]
|
||||
'Laura/06_09-2.csv', # [1]
|
||||
'Elisabeth/06_10-1.csv', #[2]
|
||||
'Elisabeth/06_10-2.csv', #[3]
|
||||
# 'Elisabeth/06_12-1.csv',
|
||||
'Elisabeth/06_13-1.csv', #[4]
|
||||
'Elisabeth/06_13-2.csv', #[5]
|
||||
'Elisabeth/06_15-1.csv', #[6]
|
||||
'Elisabeth/06_15-2.csv', #[7]
|
||||
]
|
||||
|
||||
comparison = {
|
||||
'Laura Shot #1 vs #2': (0, 1), #[0]
|
||||
'Elisabeth 06/10/22 Shot #2 - #1': (2, 3), #[1]
|
||||
'Elisabeth 06/13/22 Shot #2 - #1': (4, 5), #[2]
|
||||
'Elisabeth 06/15/22 Shot #2 - #1': (6, 7), #[3]
|
||||
'comparison': {
|
||||
'Elisabeth Shots 06/13/22 - 06/10/22': (1, 2), #[0]
|
||||
'Elisabeth Shots 06/15/22 - 06/10/22': (1, 3), #[1]
|
||||
'Elisabeth Shots 06/15/22 - 06/13/22': (2, 3), #[2]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data = {}
|
||||
|
||||
curr_figure = 0
|
||||
|
||||
plt.figure(curr_figure)
|
||||
plt.subplot(2, 1, 1)
|
||||
# plt.title('Shotweight over time')
|
||||
# plt.xlabel('Time (s)')
|
||||
plt.ylabel('Weight (g)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
plt.subplot(2, 1, 2)
|
||||
# plt.title('Flow-rate over time')
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Flow-rate (g/s)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
plt.subplot(2, 1, 1)
|
||||
|
||||
for filename in filenames:
|
||||
data[filename] = {
|
||||
'shot_name': '',
|
||||
'rows': [],
|
||||
'time': [],
|
||||
'weight': []
|
||||
}
|
||||
|
||||
with open(filename, newline='') as file:
|
||||
reader = csv.DictReader(file, delimiter=',')
|
||||
|
||||
for row in reader:
|
||||
data[filename]['rows'].append(row)
|
||||
|
||||
if row['information_type'] == 'meta':
|
||||
if row['metatype'] == 'Name':
|
||||
data[filename]['shot_name'] = row['metadata']
|
||||
elif row['information_type'] == 'moment':
|
||||
try:
|
||||
elapsed = float(row['elapsed'])
|
||||
weight = float(row['current_total_shot_weight'])
|
||||
|
||||
data[filename]['time'].append(elapsed)
|
||||
data[filename]['weight'].append(weight)
|
||||
except ValueError:
|
||||
continue
|
||||
#print("Not a float!")
|
||||
|
||||
time = data[filename]['time']
|
||||
weight = data[filename]['weight']
|
||||
|
||||
first_zero = list(x > 0 for x in weight).index(True) - 1
|
||||
zero_time = time[first_zero]
|
||||
|
||||
weight = weight[first_zero:]
|
||||
time = time[first_zero:]
|
||||
time = list(map(lambda t: t - zero_time, time))
|
||||
|
||||
data[filename]['time'] = time
|
||||
data[filename]['weight'] = weight
|
||||
|
||||
weight_savgol = savgol_filter(weight, 30, 3)
|
||||
|
||||
plt.plot(time, weight_savgol, label = data[filename]['shot_name'], linewidth=1)
|
||||
plt.scatter(time, weight, label = '', s=0.25)
|
||||
|
||||
plt.subplot(2, 1, 2)
|
||||
|
||||
dweight_dt = diff(weight)/diff(time)
|
||||
dweight_dt_savgol = savgol_filter(dweight_dt, 60, 3)
|
||||
|
||||
plt.plot(time[:-1], dweight_dt_savgol, label = data[filename]['shot_name'], linewidth=1)
|
||||
plt.scatter(time[1:-1], dweight_dt[1:], label = '', s=0.25)
|
||||
|
||||
plt.subplot(2, 1, 1)
|
||||
|
||||
#print(filename, "\n", time, "\n", weight)
|
||||
|
||||
plt.legend(loc='best')
|
||||
|
||||
def comparison_plot(curr_figure, comparison_dict):
|
||||
curr_figure += 1
|
||||
plt.figure(curr_figure)
|
||||
|
||||
for key in comparison_dict:
|
||||
if key != 'comparison':
|
||||
item = comparison_dict[key]
|
||||
i0 = item[0]
|
||||
i1 = item[1]
|
||||
|
||||
fn0 = filenames[i0]
|
||||
fn1 = filenames[i1]
|
||||
|
||||
plt.xlabel('Time (s)')
|
||||
plt.ylabel('Weight difference (g)')
|
||||
plt.grid(visible=True, which='both', axis='both', linewidth=0.5)
|
||||
|
||||
t0 = np.array(data[fn0]['time'])
|
||||
w0 = np.array(data[fn0]['weight'])
|
||||
|
||||
t1 = np.array(data[fn1]['time'])
|
||||
w1 = np.array(data[fn1]['weight'])
|
||||
|
||||
time_linspace = np.linspace(max(np.array(t0).min(), np.array(t1).min()), min(np.array(t0).max(), np.array(t1).max()), max(len(t0), len(t1)))
|
||||
|
||||
i0 = make_interp_spline(t0, w0, k=3)
|
||||
wi0 = i0(time_linspace)
|
||||
i1 = make_interp_spline(t1, w1, k=3)
|
||||
wi1 = i1(time_linspace)
|
||||
|
||||
dw = wi1 - wi0
|
||||
|
||||
dw_savgol = savgol_filter(dw, 30, 3)
|
||||
|
||||
plt.plot(time_linspace, dw_savgol, label = key, linewidth=1)
|
||||
plt.scatter(time_linspace, dw, label = '', s=0.25)
|
||||
|
||||
plt.legend(loc='best')
|
||||
else:
|
||||
curr_figure = comparison_plot(curr_figure, comparison_dict['comparison'])
|
||||
return curr_figure
|
||||
|
||||
curr_figure = comparison_plot(curr_figure, comparison)
|
||||
|
||||
|
||||
plt.show()
|
|
@ -55,6 +55,11 @@ impl Config {
|
|||
toml::from_str(&config_file).expect("Can't deserialize config.toml")
|
||||
}
|
||||
|
||||
pub fn from_raw(bytes: &Vec<u8>) -> Self {
|
||||
let config_file = core::str::from_utf8(&bytes).expect("Not valid UTF-8");
|
||||
toml::from_str(&config_file).expect("Can't deserialize config.toml")
|
||||
}
|
||||
|
||||
pub fn from_default() -> Self {
|
||||
Self::from_file("config.toml")
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ impl Database {
|
|||
}
|
||||
|
||||
pub fn from_config(config: &config::Config) -> Self {
|
||||
Database::from_file(&format!("{}/{}", &config.data_dir, &config.main_json))
|
||||
Database::from_file(&format!("./{}/{}", &config.data_dir, &config.main_json))
|
||||
}
|
||||
|
||||
pub fn bean_names(&self) -> Vec<String> {
|
||||
|
|
30
src/main.rs
|
@ -8,20 +8,14 @@ mod ui;
|
|||
|
||||
use ui::Ui;
|
||||
|
||||
use crate::plot::database_plot_selected_tui;
|
||||
use crate::plot::generate_plots;
|
||||
|
||||
extern crate console_error_panic_hook;
|
||||
use std::panic;
|
||||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() -> Result<(), eframe::Error> {
|
||||
panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() -> eframe::Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
// generate_plots();
|
||||
|
||||
// database_plot_selected_tui();
|
||||
let options = eframe::NativeOptions {
|
||||
drag_and_drop_support: true,
|
||||
min_window_size: Some(egui::vec2(640.0, 360.0)),
|
||||
|
@ -34,3 +28,21 @@ fn main() -> Result<(), eframe::Error> {
|
|||
Box::new(|_cc| Box::new(Ui::default())),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn main() {
|
||||
eframe::WebLogger::init(log::LevelFilter::Debug).ok();
|
||||
|
||||
let options = eframe::WebOptions::default();
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async {
|
||||
eframe::WebRunner::new()
|
||||
.start(
|
||||
"RustyBeans",
|
||||
options,
|
||||
Box::new(|cc| Box::new(Ui::default())),
|
||||
)
|
||||
.await
|
||||
.expect("Failed to start eframe");
|
||||
});
|
||||
}
|
||||
|
|
279
src/plot.rs
|
@ -4,17 +4,7 @@ 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 egui::plot::{PlotPoint, PlotPoints};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
|
@ -29,72 +19,7 @@ pub struct LoadingProgress {
|
|||
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 finished: bool,
|
||||
}
|
||||
|
||||
pub fn plot_points_to_owned(plot_points: &RawPlotPoints) -> PlotPoints {
|
||||
|
@ -135,12 +60,17 @@ pub fn database_plot_entries(
|
|||
let mut plot_entries: HashMap<String, PlotEntry> = HashMap::new();
|
||||
let step_size = 1.0 / brews.len() as f32;
|
||||
|
||||
if let Some(progress) = &progress {
|
||||
let mut progress_lock = progress.lock().unwrap();
|
||||
(*progress_lock).total = brews.len();
|
||||
(*progress_lock).finished = false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -149,193 +79,10 @@ pub fn database_plot_entries(
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(progress) = &progress {
|
||||
let mut progress_lock = progress.lock().unwrap();
|
||||
(*progress_lock).finished = true;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
151
src/ui.rs
|
@ -2,12 +2,8 @@
|
|||
|
||||
use eframe::egui;
|
||||
|
||||
use egui::{
|
||||
widgets::plot::{Legend, Line, Plot},
|
||||
Align, Layout, ProgressBar,
|
||||
};
|
||||
use plotly::layout::Center;
|
||||
use rfd::FileDialog;
|
||||
use egui::{plot::{Legend, Line, Plot}, Align, Layout, ProgressBar, Modifiers};
|
||||
use rfd::{FileHandle, AsyncFileDialog};
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
|
@ -24,6 +20,8 @@ use std::{
|
|||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use async_std::task;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct LoadingData {
|
||||
config: Config,
|
||||
|
@ -40,9 +38,9 @@ pub struct Ui {
|
|||
side_panel_expanded: bool,
|
||||
|
||||
loading_progress: Arc<Mutex<LoadingProgress>>,
|
||||
loader_thread: Option<JoinHandle<()>>,
|
||||
loading_data: Arc<Mutex<Option<LoadingData>>>,
|
||||
data_loaded: bool,
|
||||
loading_file: Arc<Mutex<Option<Vec<u8>>>>,
|
||||
data_transfered: bool,
|
||||
|
||||
config: Config,
|
||||
database: Database,
|
||||
|
@ -53,8 +51,6 @@ pub struct Ui {
|
|||
select_all: bool,
|
||||
clear_all: bool,
|
||||
|
||||
picked_path: Option<String>,
|
||||
|
||||
plot_entries: HashMap<String, PlotEntry>,
|
||||
|
||||
continuous_mode: bool,
|
||||
|
@ -79,16 +75,12 @@ impl Ui {
|
|||
.brews_for_uuids(&self.get_selected_brew_uuids())
|
||||
}
|
||||
|
||||
pub fn reload(&mut self, ctx: &egui::Context) {
|
||||
if let Some(loader_thread) = &self.loader_thread {
|
||||
if loader_thread.is_finished() {
|
||||
self.data_loaded = false;
|
||||
self.loader_thread = None;
|
||||
pub fn reload(&mut self, _ctx: &egui::Context) {
|
||||
if self.data_transfered {
|
||||
self.data_transfered = false;
|
||||
|
||||
let loading_progress_arc = self.loading_progress.clone();
|
||||
let mut loading_progress_lock = loading_progress_arc.lock().unwrap();
|
||||
*loading_progress_lock = LoadingProgress::default();
|
||||
}
|
||||
let mut loading_progress_lock = self.loading_progress.lock().unwrap();
|
||||
*loading_progress_lock = LoadingProgress::default();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -113,15 +105,15 @@ impl eframe::App for Ui {
|
|||
self.reload(ctx);
|
||||
}
|
||||
|
||||
if reload_button.hovered() {
|
||||
if let Some(loader_thread) = &self.loader_thread {
|
||||
if !loader_thread.is_finished() {
|
||||
egui::show_tooltip(ctx, egui::Id::new("reload_tooltip"), |ui| {
|
||||
ui.label("Loading is still in progress.\nTo reload, please wait until previous loading has finished!");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// if reload_button.hovered() {
|
||||
// if let Some(loader_thread) = &self.loader_thread {
|
||||
// if !loader_thread.is_finished() {
|
||||
// egui::show_tooltip(ctx, egui::Id::new("reload_tooltip"), |ui| {
|
||||
// ui.label("Loading is still in progress.\nTo reload, please wait until previous loading has finished!");
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
ui.with_layout(Layout::right_to_left(Align::Min), |ui| {
|
||||
egui::widgets::global_dark_light_mode_buttons(ui);
|
||||
|
@ -135,8 +127,7 @@ impl eframe::App for Ui {
|
|||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ui.centered_and_justified(|ui| {
|
||||
if self.show_loading_screen {
|
||||
let loading_progress_arc = self.loading_progress.clone();
|
||||
let loading_progress_lock = loading_progress_arc.lock().unwrap();
|
||||
let loading_progress_lock = self.loading_progress.lock().unwrap();
|
||||
|
||||
ui.add(
|
||||
ProgressBar::new(loading_progress_lock.percentage.to_owned()).text(
|
||||
|
@ -149,15 +140,14 @@ impl eframe::App for Ui {
|
|||
),
|
||||
);
|
||||
|
||||
if let Some(loader_thread) = &self.loader_thread {
|
||||
if loader_thread.is_finished() {
|
||||
self.continuous_mode = false;
|
||||
self.modal = false;
|
||||
self.show_loading_screen = false;
|
||||
}
|
||||
if loading_progress_lock.finished {
|
||||
self.continuous_mode = false;
|
||||
self.modal = false;
|
||||
self.show_loading_screen = false;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if self.show_confirmation_dialog {
|
||||
// Show confirmation dialog:
|
||||
ui.separator();
|
||||
|
@ -187,7 +177,7 @@ impl eframe::App for Ui {
|
|||
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
if self.data_loaded {
|
||||
if self.data_transfered {
|
||||
ui.heading("Beans");
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
|
@ -204,7 +194,7 @@ impl eframe::App for Ui {
|
|||
)
|
||||
.clicked_by(egui::PointerButton::Primary)
|
||||
{
|
||||
if ctx.input(|i| i.modifiers.ctrl) {
|
||||
if ctx.input(|i| i.modifiers.command) {
|
||||
self.select_all = true;
|
||||
}
|
||||
}
|
||||
|
@ -215,7 +205,7 @@ impl eframe::App for Ui {
|
|||
});
|
||||
|
||||
ui.vertical(|ui| {
|
||||
if self.data_loaded {
|
||||
if self.data_transfered {
|
||||
if let Some(bean) =
|
||||
&self.database.bean_for_uuid(&self.selected_bean)
|
||||
{
|
||||
|
@ -253,7 +243,7 @@ impl eframe::App for Ui {
|
|||
|
||||
ui.horizontal_centered(|ui| {
|
||||
ui.vertical(|ui| {
|
||||
if self.data_loaded {
|
||||
if self.data_transfered {
|
||||
ui.heading("Selected Brews");
|
||||
|
||||
egui::ScrollArea::vertical()
|
||||
|
@ -286,7 +276,7 @@ impl eframe::App for Ui {
|
|||
egui::PointerButton::Primary,
|
||||
)
|
||||
{
|
||||
if ctx.input(|i| i.modifiers.ctrl) {
|
||||
if ctx.input(|i| i.modifiers.matches(Modifiers::CTRL)) {
|
||||
self.clear_all = true;
|
||||
}
|
||||
}
|
||||
|
@ -322,25 +312,34 @@ impl eframe::App for Ui {
|
|||
);
|
||||
|
||||
ui.with_layout(
|
||||
Layout::top_down(Align::Center).with_main_align(egui::Align::Center),
|
||||
Layout::top_down(Align::Center).with_main_align(Align::Center),
|
||||
|ui| {
|
||||
if let Some(picked_path) = &self.picked_path {
|
||||
let thread_handle = self.loader_thread.get_or_insert_with(|| {
|
||||
let file_loaded: bool;
|
||||
{
|
||||
file_loaded = self.loading_file.lock().unwrap().is_some();
|
||||
}
|
||||
if file_loaded {
|
||||
let loading_finished: bool;
|
||||
{
|
||||
loading_finished = self.loading_progress.lock().unwrap().finished;
|
||||
}
|
||||
if !loading_finished {
|
||||
self.continuous_mode = true;
|
||||
self.modal = true;
|
||||
self.show_loading_screen = true;
|
||||
|
||||
let loading_file_arc = self.loading_file.clone();
|
||||
let loading_data_arc = self.loading_data.clone();
|
||||
let loading_progress_arc = self.loading_progress.clone();
|
||||
let picked_path_owned = picked_path.to_owned();
|
||||
let ctx_clone = ctx.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let data_loader = async move {
|
||||
let ctx = ctx_clone;
|
||||
|
||||
let mut loading_data = LoadingData::default();
|
||||
|
||||
loading_data.config = Config::from_file(&picked_path_owned);
|
||||
let loading_file_lock = loading_file_arc.lock().unwrap();
|
||||
loading_data.config = Config::from_raw(loading_file_lock.as_ref().expect("No file loaded"));
|
||||
|
||||
loading_data.database =
|
||||
Database::from_config(&loading_data.config);
|
||||
|
@ -378,11 +377,15 @@ impl eframe::App for Ui {
|
|||
*loading_data_lock = Some(loading_data);
|
||||
|
||||
ctx.request_repaint();
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
if thread_handle.is_finished() {
|
||||
if !self.data_loaded {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
task::spawn(data_loader);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_bindgen_futures::spawn_local(data_loader);
|
||||
} else {
|
||||
if !self.data_transfered {
|
||||
self.side_panel_expanded = true;
|
||||
|
||||
// println!("loading_data: {:?}", &self.loading_data);
|
||||
|
@ -397,7 +400,7 @@ impl eframe::App for Ui {
|
|||
self.selected_bean = loading_data.selected_bean;
|
||||
self.plot_entries = loading_data.plot_entries;
|
||||
|
||||
self.data_loaded = true;
|
||||
self.data_transfered = true;
|
||||
} else {
|
||||
Plot::new("Combined Chart of selected brews")
|
||||
.legend(Legend::default())
|
||||
|
@ -420,26 +423,33 @@ impl eframe::App for Ui {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
ui.label("Select the config.toml to start");
|
||||
ui.label("Select the config file (.toml) to start");
|
||||
|
||||
if ui.button("Open file").clicked() {
|
||||
if let Some(path) = FileDialog::new()
|
||||
.add_filter("toml", &["toml"])
|
||||
.set_directory(
|
||||
match &env::current_dir() {
|
||||
Ok(path) => path.to_str(),
|
||||
Err(_) => None,
|
||||
}
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.pick_file()
|
||||
{
|
||||
self.picked_path = Some(path.display().to_string());
|
||||
}
|
||||
}
|
||||
if ui.button("Load file").clicked() {
|
||||
let loading_file_arc = self.loading_file.clone();
|
||||
|
||||
if ui.button("Open default config.toml").clicked() {
|
||||
self.picked_path = Some(String::from("config.toml"));
|
||||
let file_dialog = async move {
|
||||
let file = AsyncFileDialog::new()
|
||||
.add_filter("toml", &["toml"])
|
||||
.set_directory(
|
||||
match &env::current_dir() {
|
||||
Ok(path) => path.to_str(),
|
||||
Err(_) => None,
|
||||
}.unwrap_or_default()
|
||||
)
|
||||
.pick_file().await;
|
||||
|
||||
let data = file.unwrap().read().await;
|
||||
|
||||
let mut loading_file_lock = loading_file_arc.lock().unwrap();
|
||||
*loading_file_lock = Some(data);
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
task::spawn(file_dialog);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_bindgen_futures::spawn_local(file_dialog);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -449,6 +459,7 @@ impl eframe::App for Ui {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn on_close_event(&mut self) -> bool {
|
||||
self.modal = true;
|
||||
self.show_confirmation_dialog = true;
|
||||
|
|