diff options
Diffstat (limited to 'src/canvas')
-rw-r--r-- | src/canvas/dialogs/dd_dialog.rs | 8 | ||||
-rw-r--r-- | src/canvas/drawing_utils.rs | 17 | ||||
-rw-r--r-- | src/canvas/widgets/cpu_graph.rs | 81 | ||||
-rw-r--r-- | src/canvas/widgets/mem_graph.rs | 106 | ||||
-rw-r--r-- | src/canvas/widgets/network_graph.rs | 577 |
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() |