summaryrefslogtreecommitdiffstats
path: root/src/canvas
diff options
context:
space:
mode:
authorClement Tsang <34804052+ClementTsang@users.noreply.github.com>2021-04-04 05:38:57 -0400
committerGitHub <noreply@github.com>2021-04-04 05:38:57 -0400
commiteb6a737d3430920061cd5e54bf4dc40da21f1fc5 (patch)
treeb329c4b729f31de80405b3a2d015c1525d80d618 /src/canvas
parent40f4c796f8d1832e7ef9db7c87db558a1ce12b62 (diff)
feature: Rework network y-axis, linear interpolation for off-screen data (#437)
Rewrite of the y-axis labeling and scaling for the network widget, along with more customization. This still has one step to be optimized (cache results so we don't have to recalculate the legend each time), but will be done in another PR for sake of this one being too large already. Furthermore, this change adds linear interpolation at the 0 point in the case a data point shoots too far back - this seems to have lead to ugly gaps to the left of graphs in some cases, because the left hand limit was not big enough for the data point. We address this by grabbing values just outside the time range and linearly interpolating at the leftmost limit. This affects all graph widgets (CPU, mem, network). This can be optimized, and will hopefully be prior to release in a separate change.
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),
+ ],
+ )
+