summaryrefslogtreecommitdiffstats
path: root/src/canvas
diff options
context:
space:
mode:
Diffstat (limited to 'src/canvas')
-rw-r--r--src/canvas/dialogs/dd_dialog.rs8
-rw-r--r--src/canvas/drawing_utils.rs17
-rw-r--r--src/canvas/widgets/cpu_graph.rs81
-rw-r--r--src/canvas/widgets/mem_graph.rs106
-rw-r--r--src/canvas/widgets/network_graph.rs577
5 files changed, 655 insertions, 134 deletions
diff --git a/src/canvas/dialogs/dd_dialog.rs b/src/canvas/dialogs/dd_dialog.rs
index cd9029c8..0415aa2d 100644
--- a/src/canvas/dialogs/dd_dialog.rs
+++ b/src/canvas/dialogs/dd_dialog.rs
@@ -72,11 +72,11 @@ impl KillDialog for Painter {
) {
if cfg!(target_os = "windows") || !app_state.app_config_fields.is_advanced_kill {
let (yes_button, no_button) = match app_state.delete_dialog_state.selected_signal {
- KillSignal::KILL(_) => (
+ KillSignal::Kill(_) => (
Span::styled("Yes", self.colours.currently_selected_text_style),
Span::raw("No"),
),
- KillSignal::CANCEL => (
+ KillSignal::Cancel => (
Span::raw("Yes"),
Span::styled("No", self.colours.currently_selected_text_style),
),
@@ -249,8 +249,8 @@ impl KillDialog for Painter {
.split(*button_draw_loc)[1];
let mut selected = match app_state.delete_dialog_state.selected_signal {
- KillSignal::CANCEL => 0,
- KillSignal::KILL(signal) => signal,
+ KillSignal::Cancel => 0,
+ KillSignal::Kill(signal) => signal,
};
// 32+33 are skipped
if selected > 31 {
diff --git a/src/canvas/drawing_utils.rs b/src/canvas/drawing_utils.rs
index 222ca852..6aee1aa7 100644
--- a/src/canvas/drawing_utils.rs
+++ b/src/canvas/drawing_utils.rs
@@ -117,6 +117,12 @@ pub fn get_column_widths(
filtered_column_widths
}
+/// FIXME: [command move] This is a greedy method of determining column widths. This is reserved for columns where we are okay with
+/// shoving information as far right as required.
+// pub fn greedy_get_column_widths() -> Vec<u16> {
+// vec![]
+// }
+
pub fn get_search_start_position(
num_columns: usize, cursor_direction: &app::CursorDirection, cursor_bar: &mut usize,
current_cursor_position: usize, is_force_redraw: bool,
@@ -205,3 +211,14 @@ pub fn calculate_basic_use_bars(use_percentage: f64, num_bars_available: usize)
num_bars_available,
)
}
+
+/// Interpolates between two points. Mainly used to help fill in tui-rs blanks in certain situations.
+/// It is expected point_one is "further left" compared to point_two.
+/// A point is two floats, in (x, y) form. x is time, y is value.
+pub fn interpolate_points(point_one: &(f64, f64), point_two: &(f64, f64), time: f64) -> f64 {
+ let delta_x = point_two.0 - point_one.0;
+ let delta_y = point_two.1 - point_one.1;
+ let slope = delta_y / delta_x;
+
+ (point_one.1 + (time - point_one.0) * slope).max(0.0)
+}
diff --git a/src/canvas/widgets/cpu_graph.rs b/src/canvas/widgets/cpu_graph.rs
index 1b79482f..ff6838f2 100644
--- a/src/canvas/widgets/cpu_graph.rs
+++ b/src/canvas/widgets/cpu_graph.rs
@@ -4,7 +4,7 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::{
app::{layout_manager::WidgetDirection, App},
canvas::{
- drawing_utils::{get_column_widths, get_start_position},
+ drawing_utils::{get_column_widths, get_start_position, interpolate_points},
Painter,
},
constants::*,
@@ -146,32 +146,34 @@ impl CpuGraphWidget for Painter {
];
let y_axis_labels = vec![
- Span::styled("0%", self.colours.graph_style),
+ Span::styled(" 0%", self.colours.graph_style),
Span::styled("100%", self.colours.graph_style),
];
+ let time_start = -(cpu_widget_state.current_display_time as f64);
+
let x_axis = if app_state.app_config_fields.hide_time
|| (app_state.app_config_fields.autohide_time
&& cpu_widget_state.autohide_timer.is_none())
{
- Axis::default().bounds([-(cpu_widget_state.current_display_time as f64), 0.0])
+ Axis::default().bounds([time_start, 0.0])
} else if let Some(time) = cpu_widget_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
{
Axis::default()
- .bounds([-(cpu_widget_state.current_display_time as f64), 0.0])
+ .bounds([time_start, 0.0])
.style(self.colours.graph_style)
.labels(display_time_labels)
} else {
cpu_widget_state.autohide_timer = None;
- Axis::default().bounds([-(cpu_widget_state.current_display_time as f64), 0.0])
+ Axis::default().bounds([time_start, 0.0])
}
} else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT {
- Axis::default().bounds([-(cpu_widget_state.current_display_time as f64), 0.0])
+ Axis::default().bounds([time_start, 0.0])
} else {
Axis::default()
- .bounds([-(cpu_widget_state.current_display_time as f64), 0.0])
+ .bounds([time_start, 0.0])
.style(self.colours.graph_style)
.labels(display_time_labels)
};
@@ -184,6 +186,59 @@ impl CpuGraphWidget for Painter {
let use_dot = app_state.app_config_fields.use_dot;
let show_avg_cpu = app_state.app_config_fields.show_average_cpu;
let current_scroll_position = cpu_widget_state.scroll_state.current_scroll_position;
+
+ let interpolated_cpu_points = cpu_data
+ .iter_mut()
+ .enumerate()
+ .map(|(itx, cpu)| {
+ let to_show = if current_scroll_position == ALL_POSITION {
+ true
+ } else {
+ itx == current_scroll_position
+ };
+
+ if to_show {
+ if let Some(end_pos) = cpu
+ .cpu_data
+ .iter()
+ .position(|(time, _data)| *time >= time_start)
+ {
+ if end_pos > 1 {
+ let start_pos = end_pos - 1;
+ let outside_point = cpu.cpu_data.get(start_pos);
+ let inside_point = cpu.cpu_data.get(end_pos);
+
+ if let (Some(outside_point), Some(inside_point)) =
+ (outside_point, inside_point)
+ {
+ let old = *outside_point;
+
+ let new_point = (
+ time_start,
+ interpolate_points(outside_point, inside_point, time_start),
+ );
+
+ if let Some(to_replace) = cpu.cpu_data.get_mut(start_pos) {
+ *to_replace = new_point;
+ Some((start_pos, old))
+ } else {
+ None // Failed to get mutable reference.
+ }
+ } else {
+ None // Point somehow doesn't exist in our data
+ }
+ } else {
+ None // Point is already "leftmost", no need to interpolate.
+ }
+ } else {
+ None // There is no point.
+ }
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+
let dataset_vector: Vec<Dataset<'_>> = if current_scroll_position == ALL_POSITION {
cpu_data
.iter()
@@ -311,6 +366,18 @@ impl CpuGraphWidget for Painter {
.y_axis(y_axis),
draw_loc,
);
+
+ // Reset interpolated points
+ cpu_data
+ .iter_mut()
+ .zip(interpolated_cpu_points)
+ .for_each(|(cpu, interpolation)| {
+ if let Some((index, old_value)) = interpolation {
+ if let Some(to_replace) = cpu.cpu_data.get_mut(index) {
+ *to_replace = old_value;
+ }
+ }
+ });
}
}
diff --git a/src/canvas/widgets/mem_graph.rs b/src/canvas/widgets/mem_graph.rs
index 9136b1ba..6b5bc5ab 100644
--- a/src/canvas/widgets/mem_graph.rs
+++ b/src/canvas/widgets/mem_graph.rs
@@ -1,4 +1,8 @@
-use crate::{app::App, canvas::Painter, constants::*};
+use crate::{
+ app::App,
+ canvas::{drawing_utils::interpolate_points, Painter},
+ constants::*,
+};
use tui::{
backend::Backend,
@@ -22,8 +26,10 @@ impl MemGraphWidget for Painter {
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
if let Some(mem_widget_state) = app_state.mem_state.widget_states.get_mut(&widget_id) {
- let mem_data: &[(f64, f64)] = &app_state.canvas_data.mem_data;
- let swap_data: &[(f64, f64)] = &app_state.canvas_data.swap_data;
+ let mem_data: &mut [(f64, f64)] = &mut app_state.canvas_data.mem_data;
+ let swap_data: &mut [(f64, f64)] = &mut app_state.canvas_data.swap_data;
+
+ let time_start = -(mem_widget_state.current_display_time as f64);
let display_time_labels = vec![
Span::styled(
@@ -33,7 +39,7 @@ impl MemGraphWidget for Painter {
Span::styled("0s".to_string(), self.colours.graph_style),
];
let y_axis_label = vec![
- Span::styled("0%", self.colours.graph_style),
+ Span::styled(" 0%", self.colours.graph_style),
Span::styled("100%", self.colours.graph_style),
];
@@ -41,24 +47,24 @@ impl MemGraphWidget for Painter {
|| (app_state.app_config_fields.autohide_time
&& mem_widget_state.autohide_timer.is_none())
{
- Axis::default().bounds([-(mem_widget_state.current_display_time as f64), 0.0])
+ Axis::default().bounds([time_start, 0.0])
} else if let Some(time) = mem_widget_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()
< AUTOHIDE_TIMEOUT_MILLISECONDS as u128
{
Axis::default()
- .bounds([-(mem_widget_state.current_display_time as f64), 0.0])
+ .bounds([time_start, 0.0])
.style(self.colours.graph_style)
.labels(display_time_labels)
} else {
mem_widget_state.autohide_timer = None;
- Axis::default().bounds([-(mem_widget_state.current_display_time as f64), 0.0])
+ Axis::default().bounds([time_start, 0.0])
}
} else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT {
- Axis::default().bounds([-(mem_widget_state.current_display_time as f64), 0.0])
+ Axis::default().bounds([time_start, 0.0])
} else {
Axis::default()
- .bounds([-(mem_widget_state.current_display_time as f64), 0.0])
+ .bounds([time_start, 0.0])
.style(self.colours.graph_style)
.labels(display_time_labels)
};
@@ -68,6 +74,75 @@ impl MemGraphWidget for Painter {
.bounds([0.0, 100.5])
.labels(y_axis_label);
+ // Interpolate values to avoid ugly gaps
+ let interpolated_mem_point = if let Some(end_pos) = mem_data
+ .iter()
+ .position(|(time, _data)| *time >= time_start)
+ {
+ if end_pos > 1 {
+ let start_pos = end_pos - 1;
+ let outside_point = mem_data.get(start_pos);
+ let inside_point = mem_data.get(end_pos);
+
+ if let (Some(outside_point), Some(inside_point)) = (outside_point, inside_point)
+ {
+ let old = *outside_point;
+
+ let new_point = (
+ time_start,
+ interpolate_points(outside_point, inside_point, time_start),
+ );
+
+ if let Some(to_replace) = mem_data.get_mut(start_pos) {
+ *to_replace = new_point;
+ Some((start_pos, old))
+ } else {
+ None // Failed to get mutable reference.
+ }
+ } else {
+ None // Point somehow doesn't exist in our data
+ }
+ } else {
+ None // Point is already "leftmost", no need to interpolate.
+ }
+ } else {
+ None // There is no point.
+ };
+
+ let interpolated_swap_point = if let Some(end_pos) = swap_data
+ .iter()
+ .position(|(time, _data)| *time >= time_start)
+ {
+ if end_pos > 1 {
+ let start_pos = end_pos - 1;
+ let outside_point = swap_data.get(start_pos);
+ let inside_point = swap_data.get(end_pos);
+
+ if let (Some(outside_point), Some(inside_point)) = (outside_point, inside_point)
+ {
+ let old = *outside_point;
+
+ let new_point = (
+ time_start,
+ interpolate_points(outside_point, inside_point, time_start),
+ );
+
+ if let Some(to_replace) = swap_data.get_mut(start_pos) {
+ *to_replace = new_point;
+ Some((start_pos, old))
+ } else {
+ None // Failed to get mutable reference.
+ }
+ } else {
+ None // Point somehow doesn't exist in our data
+ }
+ } else {
+ None // Point is already "leftmost", no need to interpolate.
+ }
+ } else {
+ None // There is no point.
+ };
+
let mut mem_canvas_vec: Vec<Dataset<'_>> = vec![];
if let Some((label_percent, label_frac)) = &app_state.canvas_data.mem_labels {
@@ -147,6 +222,19 @@ impl MemGraphWidget for Painter {
.hidden_legend_constraints((Constraint::Ratio(3, 4), Constraint::Ratio(3, 4))),
draw_loc,
);
+
+ // Now if you're done, reset any interpolated points!
+ if let Some((index, old_value)) = interpolated_mem_point {
+ if let Some(to_replace) = mem_data.get_mut(index) {
+ *to_replace = old_value;
+ }
+ }
+
+ if let Some((index, old_value)) = interpolated_swap_point {
+ if let Some(to_replace) = swap_data.get_mut(index) {
+ *to_replace = old_value;
+ }
+ }
}
if app_state.should_get_widget_bounds() {
diff --git a/src/canvas/widgets/network_graph.rs b/src/canvas/widgets/network_graph.rs
index b6ee080b..74509224 100644
--- a/src/canvas/widgets/network_graph.rs
+++ b/src/canvas/widgets/network_graph.rs
@@ -3,9 +3,13 @@ use std::cmp::max;
use unicode_segmentation::UnicodeSegmentation;
use crate::{
- app::App,
- canvas::{drawing_utils::get_column_widths, Painter},
+ app::{App, AxisScaling},
+ canvas::{
+ drawing_utils::{get_column_widths, interpolate_points},
+ Painter,
+ },
constants::*,
+ units::data_units::DataUnit,
utils::gen_util::*,
};
@@ -82,103 +86,344 @@ impl NetworkGraphWidget for Painter {
/// Point is of time, data
type Point = (f64, f64);
- /// Returns the required max data point and labels.
- fn adjust_network_data_point(
- rx: &[Point], tx: &[Point], time_start: f64, time_end: f64,
- ) -> (f64, Vec<String>) {
- // First, filter and find the maximal rx or tx so we know how to scale
- let mut max_val_bytes = 0.0;
- let filtered_rx = rx
- .iter()
- .cloned()
- .filter(|(time, _data)| *time >= time_start && *time <= time_end);
-
- let filtered_tx = tx
- .iter()
- .cloned()
- .filter(|(time, _data)| *time >= time_start && *time <= time_end);
-
- for (_time, data) in filtered_rx.clone().chain(filtered_tx.clone()) {
- if data > max_val_bytes {
- max_val_bytes = data;
+ /// Returns the max data point and time given a time.
+ fn get_max_entry(
+ rx: &[Point], tx: &[Point], time_start: f64, network_scale_type: &AxisScaling,
+ network_use_binary_prefix: bool,
+ ) -> (f64, f64) {
+ /// Determines a "fake" max value in circumstances where we couldn't find one from the data.
+ fn calculate_missing_max(
+ network_scale_type: &AxisScaling, network_use_binary_prefix: bool,
+ ) -> f64 {
+ match network_scale_type {
+ AxisScaling::Log => {
+ if network_use_binary_prefix {
+ LOG_KIBI_LIMIT
+ } else {
+ LOG_KILO_LIMIT
+ }
+ }
+ AxisScaling::Linear => {
+ if network_use_binary_prefix {
+ KIBI_LIMIT_F64
+ } else {
+ KILO_LIMIT_F64
+ }
+ }
}
}
- // FIXME [NETWORKING]: Granularity. Just scale up the values.
- // FIXME [NETWORKING]: Ability to set fixed scale in config.
- // Currently we do 32 -> 33... which skips some gigabit values
- let true_max_val: f64;
- let mut labels = vec![];
- if max_val_bytes < LOG_KIBI_LIMIT {
- true_max_val = LOG_KIBI_LIMIT;
- labels = vec!["0B".to_string(), "1KiB".to_string()];
- } else if max_val_bytes < LOG_MEBI_LIMIT {
- true_max_val = LOG_MEBI_LIMIT;
- labels = vec!["0B".to_string(), "1KiB".to_string(), "1MiB".to_string()];
- } else if max_val_bytes < LOG_GIBI_LIMIT {
- true_max_val = LOG_GIBI_LIMIT;
- labels = vec![
- "0B".to_string(),
- "1KiB".to_string(),
- "1MiB".to_string(),
- "1GiB".to_string(),
- ];
- } else if max_val_bytes < LOG_TEBI_LIMIT {
- true_max_val = max_val_bytes.ceil() + 1.0;
- let cap_u32 = true_max_val as u32;
-
- for i in 0..=cap_u32 {
- match i {
- 0 => labels.push("0B".to_string()),
- LOG_KIBI_LIMIT_U32 => labels.push("1KiB".to_string()),
- LOG_MEBI_LIMIT_U32 => labels.push("1MiB".to_string()),
- LOG_GIBI_LIMIT_U32 => labels.push("1GiB".to_string()),
- _ if i == cap_u32 => {
- labels.push(format!("{}GiB", 2_u64.pow(cap_u32 - LOG_GIBI_LIMIT_U32)))
+ // First, let's shorten our ranges to actually look. We can abuse the fact that our rx and tx arrays
+ // are sorted, so we can short-circuit our search to filter out only the relevant data points...
+ let filtered_rx = if let (Some(rx_start), Some(rx_end)) = (
+ rx.iter().position(|(time, _data)| *time >= time_start),
+ rx.iter().rposition(|(time, _data)| *time <= 0.0),
+ ) {
+ Some(&rx[rx_start..=rx_end])
+ } else {
+ None
+ };
+
+ let filtered_tx = if let (Some(tx_start), Some(tx_end)) = (
+ tx.iter().position(|(time, _data)| *time >= time_start),
+ tx.iter().rposition(|(time, _data)| *time <= 0.0),
+ ) {
+ Some(&tx[tx_start..=tx_end])
+ } else {
+ None
+ };
+
+ // Then, find the maximal rx/tx so we know how to scale, and return it.
+ match (filtered_rx, filtered_tx) {
+ (None, None) => (
+ time_start,
+ calculate_missing_max(network_scale_type, network_use_binary_prefix),
+ ),
+ (None, Some(filtered_tx)) => {
+ match filtered_tx
+ .iter()
+ .max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false))
+ {
+ Some((best_time, max_val)) => {
+ if *max_val == 0.0 {
+ (
+ time_start,
+ calculate_missing_max(
+ network_scale_type,
+ network_use_binary_prefix,
+ ),
+ )
+ } else {
+ (*best_time, *max_val)
+ }
}
- _ if i == (LOG_GIBI_LIMIT_U32 + cap_u32) / 2 => labels.push(format!(
- "{}GiB",
- 2_u64.pow(cap_u32 - ((LOG_GIBI_LIMIT_U32 + cap_u32) / 2))
- )), // ~Halfway point
- _ => labels.push(String::default()),
+ None => (
+ time_start,
+ calculate_missing_max(network_scale_type, network_use_binary_prefix),
+ ),
}
}
- } else {
- true_max_val = max_val_bytes.ceil() + 1.0;
- let cap_u32 = true_max_val as u32;
-
- for i in 0..=cap_u32 {
- match i {
- 0 => labels.push("0B".to_string()),
- LOG_KIBI_LIMIT_U32 => labels.push("1KiB".to_string()),
- LOG_MEBI_LIMIT_U32 => labels.push("1MiB".to_string()),
- LOG_GIBI_LIMIT_U32 => labels.push("1GiB".to_string()),
- LOG_TEBI_LIMIT_U32 => labels.push("1TiB".to_string()),
- _ if i == cap_u32 => {
- labels.push(format!("{}GiB", 2_u64.pow(cap_u32 - LOG_TEBI_LIMIT_U32)))
+ (Some(filtered_rx), None) => {
+ match filtered_rx
+ .iter()
+ .max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false))
+ {
+ Some((best_time, max_val)) => {
+ if *max_val == 0.0 {
+ (
+ time_start,
+ calculate_missing_max(
+ network_scale_type,
+ network_use_binary_prefix,
+ ),
+ )
+ } else {
+ (*best_time, *max_val)
+ }
}
- _ if i == (LOG_TEBI_LIMIT_U32 + cap_u32) / 2 => labels.push(format!(
- "{}TiB",
- 2_u64.pow(cap_u32 - ((LOG_TEBI_LIMIT_U32 + cap_u32) / 2))
- )), // ~Halfway point
- _ => labels.push(String::default()),
+ None => (
+ time_start,
+ calculate_missing_max(network_scale_type, network_use_binary_prefix),
+ ),
+ }
+ }
+ (Some(filtered_rx), Some(filtered_tx)) => {
+ match filtered_rx
+ .iter()
+ .chain(filtered_tx)
+ .max_by(|(_, data_a), (_, data_b)| get_ordering(data_a, data_b, false))
+ {
+ Some((best_time, max_val)) => {
+ if *max_val == 0.0 {
+ (
+ *best_time,
+ calculate_missing_max(
+ network_scale_type,
+ network_use_binary_prefix,
+ ),
+ )
+ } else {
+ (*best_time, *max_val)
+ }
+ }
+ None => (
+ time_start,
+ calculate_missing_max(network_scale_type, network_use_binary_prefix),
+ ),
}
}
}
+ }
+
+ /// Returns the required max data point and labels.
+ fn adjust_network_data_point(
+ max_entry: f64, network_scale_type: &AxisScaling, network_unit_type: &DataUnit,
+ network_use_binary_prefix: bool,
+ ) -> (f64, Vec<String>) {
+ // So, we're going with an approach like this for linear data:
+ // - Main goal is to maximize the amount of information displayed given a specific height.
+ // We don't want to drown out some data if the ranges are too far though! Nor do we want to filter
+ // out too much data...
+ // - Change the y-axis unit (kilo/kibi, mega/mebi...) dynamically based on max load.
+ //
+ // The idea is we take the top value, build our scale such that each "point" is a scaled version of that.
+ // So for example, let's say I use 390 Mb/s. If I drew 4 segments, it would be 97.5, 195, 292.5, 390, and
+ // probably something like 438.75?
+ //
+ // So, how do we do this in tui-rs? Well, if we are using intervals that tie in perfectly to the max
+ // value we want... then it's actually not that hard. Since tui-rs accepts a vector as labels and will
+ // properly space them all out... we just work with that and space it out properly.
+ //
+ // Dynamic chart idea based off of FreeNAS's chart design.
+ //
+ // ===
+ //
+ // For log data, we just use the old method of log intervals (kilo/mega/giga/etc.). Keep it nice and simple.
+
+ // Now just check the largest unit we correspond to... then proceed to build some entries from there!
+
+ let unit_char = match network_unit_type {
+ DataUnit::Byte => "B",
+ DataUnit::Bit => "b",
+ };
+
+ match network_scale_type {
+ AxisScaling::Linear => {
+ let (k_limit, m_limit, g_limit, t_limit) = if network_use_binary_prefix {
+ (
+ KIBI_LIMIT_F64,
+ MEBI_LIMIT_F64,
+ GIBI_LIMIT_F64,
+ TEBI_LIMIT_F64,
+ )
+ } else {
+ (
+ KILO_LIMIT_F64,
+ MEGA_LIMIT_F64,
+ GIGA_LIMIT_F64,
+ TERA_LIMIT_F64,
+ )
+ };
+
+ let bumped_max_entry = max_entry * 1.5; // We use the bumped up version to calculate our unit type.
+ let (max_value_scaled, unit_prefix, unit_type): (f64, &str, &str) =
+ if bumped_max_entry < k_limit {
+ (max_entry, "", unit_char)
+ } else if bumped_max_entry < m_limit {
+ (
+ max_entry / k_limit,
+ if network_use_binary_prefix { "Ki" } else { "K" },
+ unit_char,
+ )
+ } else if bumped_max_entry < g_limit {
+ (
+ max_entry / m_limit,
+ if network_use_binary_prefix { "Mi" } else { "M" },
+ unit_char,
+ )
+ } else if bumped_max_entry < t_limit {
+ (
+ max_entry / g_limit,
+ if network_use_binary_prefix { "Gi" } else { "G" },
+ unit_char,
+ )
+ } else {
+ (
+ max_entry / t_limit,
+ if network_use_binary_prefix { "Ti" } else { "T" },
+ unit_char,
+ )
+ };
+
+ // Finally, build an acceptable range starting from there, using the given height!
+ // Note we try to put more of a weight on the bottom section vs. the top, since the top has less data.
+
+ let base_unit = max_value_scaled;
+ let labels: Vec<String> = vec![
+ format!("0{}{}", unit_prefix, unit_type),
+ format!("{:.1}", base_unit * 0.5),
+ format!("{:.1}", base_unit),
+ format!("{:.1}", base_unit * 1.5),
+ ]
+ .into_iter()
+ .map(|s| format!("{:>5}", s)) // Pull 5 as the longest legend value is generally going to be 5 digits (if they somehow hit over 5 terabits per second)
+ .collect();
+
+ (bumped_max_entry, labels)
+ }
+ AxisScaling::Log => {
+ let (m_limit, g_limit, t_limit) = if network_use_binary_prefix {
+ (LOG_MEBI_LIMIT, LOG_GIBI_LIMIT, LOG_TEBI_LIMIT)
+ } else {
+ (LOG_MEGA_LIMIT, LOG_GIGA_LIMIT, LOG_TERA_LIMIT)
+ };
+
+ fn get_zero(network_use_binary_prefix: bool, unit_char: &str) -> String {
+ format!(
+ "{}0{}",
+ if network_use_binary_prefix { " " } else { " " },
+ unit_char
+ )
+ }
+
+ fn get_k(network_use_binary_prefix: bool, unit_char: &str) -> String {
+ format!(
+ "1{}{}",
+ if network_use_binary_prefix { "Ki" } else { "K" },
+ unit_char
+ )
+ }
+
+ fn get_m(network_use_binary_prefix: bool, unit_char: &str) -> String {
+ format!(
+ "1{}{}",
+ if network_use_binary_prefix { "Mi" } else { "M" },
+ unit_char
+ )
+ }
+
+ fn get_g(network_use_binary_prefix: bool, unit_char: &str) -> String {
+ format!(
+ "1{}{}",
+ if network_use_binary_prefix { "Gi" } else { "G" },
+ unit_char
+ )
+ }
+
+ fn get_t(network_use_binary_prefix: bool, unit_char: &str) -> String {
+ format!(
+ "1{}{}",
+ if network_use_binary_prefix { "Ti" } else { "T" },
+ unit_char
+ )
+ }
- (true_max_val, labels)
+ fn get_p(network_use_binary_prefix: bool, unit_char: &str) -> String {
+ format!(
+ "1{}{}",
+ if network_use_binary_prefix { "Pi" } else { "P" },
+ unit_char
+ )
+ }
+
+ if max_entry < m_limit {
+ (
+ m_limit,
+ vec![
+ get_zero(network_use_binary_prefix, unit_char),
+ get_k(network_use_binary_prefix, unit_char),
+ get_m(network_use_binary_prefix, unit_char),
+ ],
+ )
+ } else if max_entry < g_limit {
+ (
+ g_limit,
+ vec![
+ get_zero(network_use_binary_prefix, unit_char),
+ get_k(network_use_binary_prefix, unit_char),
+ get_m(network_use_binary_prefix, unit_char),
+ get_g(network_use_binary_prefix, unit_char),
+ ],
+ )
+ } else if max_entry < t_limit {
+ (
+ t_limit,
+ vec![
+ get_zero(network_use_binary_prefix, unit_char),
+ get_k(network_use_binary_prefix, unit_char),
+ get_m(network_use_binary_prefix, unit_char),
+ get_g(network_use_binary_prefix, unit_char),
+ get_t(network_use_binary_prefix, unit_char),
+ ],
+ )
+ } else {
+ // I really doubt anyone's transferring beyond petabyte speeds...
+ (
+ if network_use_binary_prefix {
+ LOG_PEBI_LIMIT
+ } else {
+ LOG_PETA_LIMIT
+ },
+ vec![
+ get_zero(network_use_binary_prefix, unit_char),
+ get_k(network_use_binary_prefix, unit_char),
+ get_m(network_use_binary_prefix, unit_char),
+ get_g(network_use_binary_prefix, unit_char),
+ get_t(network_use_binary_prefix, unit_char),
+ get_p(network_use_binary_prefix, unit_char),
+ ],
+ )
+ }
+ }
+ }
}
if let Some(network_widget_state) = app_state.net_state.widget_states.get_mut(&widget_id) {
- let network_data_rx: &[(f64, f64)] = &app_state.canvas_data.network_data_rx;
- let network_data_tx: &[(f64, f64)] = &app_state.canvas_data.network_data_tx;
+ let network_data_rx: &mut [(f64, f64)] = &mut app_state.canvas_data.network_data_rx;
+ let network_data_tx: &mut [(f64, f64)] = &mut app_state.canvas_data.network_data_tx;
+
+ let time_start = -(network_widget_state.current_display_time as f64);
- let (max_range, labels) = adjust_network_data_point(
- network_data_rx,
- network_data_tx,
- -(network_widget_state.current_display_time as f64),
- 0.0,
- );
let display_time_labels = vec![
Span::styled(
format!("{}s", network_widget_state.current_display_time / 1000),
@@ -190,29 +435,138 @@ impl NetworkGraphWidget for Painter {
|| (app_state.app_config_fields.autohide_time
&& network_widget_state.autohide_timer.is_none())
{
- Axis::default().bounds([-(network_widget_state.current_display_time as f64), 0.0])
+ Axis::default().bounds([time_start, 0.0])
} else if let Some(time) = network_widget_state.autohide_timer {
if std::time::Instant::now().duration_since(time).as_millis()