diff options
Diffstat (limited to 'src/canvas/widgets/network_graph.rs')
-rw-r--r-- | src/canvas/widgets/network_graph.rs | 972 |
1 files changed, 390 insertions, 582 deletions
diff --git a/src/canvas/widgets/network_graph.rs b/src/canvas/widgets/network_graph.rs index 3c0aaadb..2c3256f0 100644 --- a/src/canvas/widgets/network_graph.rs +++ b/src/canvas/widgets/network_graph.rs @@ -1,12 +1,12 @@ use once_cell::sync::Lazy; use std::cmp::max; -use unicode_segmentation::UnicodeSegmentation; use crate::{ app::{App, AxisScaling}, canvas::{ - drawing_utils::{get_column_widths, interpolate_points}, - Painter, + components::{GraphData, TimeGraph}, + drawing_utils::{get_column_widths, should_hide_x_label}, + Painter, Point, }, constants::*, units::data_units::DataUnit, @@ -16,11 +16,9 @@ use crate::{ use tui::{ backend::Backend, layout::{Constraint, Direction, Layout, Rect}, - symbols::Marker, terminal::Frame, - text::Span, - text::{Spans, Text}, - widgets::{Axis, Block, Borders, Chart, Dataset, Row, Table}, + text::Text, + widgets::{Block, Borders, Row, Table}, }; const NETWORK_HEADERS: [&str; 4] = ["RX", "TX", "Total RX", "Total TX"]; @@ -68,462 +66,18 @@ impl Painter { &self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64, hide_legend: bool, ) { - /// Point is of time, data - type Point = (f64, f64); - - /// 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 - } - } - } - } - - // 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) - } - } - None => ( - time_start, - calculate_missing_max(network_scale_type, network_use_binary_prefix), - ), - } - } - (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) - } - } - 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 - ) - } - - 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: &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 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 time_start = -(network_widget_state.current_display_time as f64); - - let display_time_labels = vec![ - Span::styled( - format!("{}s", network_widget_state.current_display_time / 1000), - self.colours.graph_style, - ), - Span::styled("0s".to_string(), self.colours.graph_style), - ]; - let x_axis = if app_state.app_config_fields.hide_time - || (app_state.app_config_fields.autohide_time - && network_widget_state.autohide_timer.is_none()) - { - 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() - < AUTOHIDE_TIMEOUT_MILLISECONDS.into() - { - Axis::default() - .bounds([time_start, 0.0]) - .style(self.colours.graph_style) - .labels(display_time_labels) - } else { - network_widget_state.autohide_timer = None; - Axis::default().bounds([time_start, 0.0]) - } - } else if draw_loc.height < TIME_LABEL_HEIGHT_LIMIT { - Axis::default().bounds([time_start, 0.0]) - } else { - Axis::default() - .bounds([time_start, 0.0]) - .style(self.colours.graph_style) - .labels(display_time_labels) - }; - - // Interpolate a point for rx and tx between the last value outside of the left bounds and the first value - // inside it. - // Because we assume it is all in order for... basically all our code, we can't just append it, - // and insertion in the middle seems. So instead, we swap *out* the value that is outside with our - // interpolated point, draw and do whatever calculations, then swap back in the old value! - // - // Note there is some re-used work here! For potential optimizations, we could re-use some work here in/from - // get_max_entry... - let interpolated_rx_point = if let Some(rx_end_pos) = network_data_rx - .iter() - .position(|(time, _data)| *time >= time_start) - { - if rx_end_pos > 1 { - let rx_start_pos = rx_end_pos - 1; - let outside_rx_point = network_data_rx.get(rx_start_pos); - let inside_rx_point = network_data_rx.get(rx_end_pos); - - if let (Some(outside_rx_point), Some(inside_rx_point)) = - (outside_rx_point, inside_rx_point) - { - let old = *outside_rx_point; - - let new_point = ( - time_start, - interpolate_points(outside_rx_point, inside_rx_point, time_start), - ); - - // debug!( - // "Interpolated between {:?} and {:?}, got rx for time {:?}: {:?}", - // outside_rx_point, inside_rx_point, time_start, new_point - // ); - - if let Some(to_replace) = network_data_rx.get_mut(rx_start_pos) { - *to_replace = new_point; - Some((rx_start_pos, old)) - } else { - None // Failed to get mutable reference. - } - } else { - None // Point somehow doesn't exist in our network_data_rx - } - } else { - None // Point is already "leftmost", no need to interpolate. - } - } else { - None // There is no point. - }; - - let interpolated_tx_point = if let Some(tx_end_pos) = network_data_tx - .iter() - .position(|(time, _data)| *time >= time_start) - { - if tx_end_pos > 1 { - let tx_start_pos = tx_end_pos - 1; - let outside_tx_point = network_data_tx.get(tx_start_pos); - let inside_tx_point = network_data_tx.get(tx_end_pos); - - if let (Some(outside_tx_point), Some(inside_tx_point)) = - (outside_tx_point, inside_tx_point) - { - let old = *outside_tx_point; - - let new_point = ( - time_start, - interpolate_points(outside_tx_point, inside_tx_point, time_start), - ); - - if let Some(to_replace) = network_data_tx.get_mut(tx_start_pos) { - *to_replace = new_point; - Some((tx_start_pos, old)) - } else { - None // Failed to get mutable reference. - } - } else { - None // Point somehow doesn't exist in our network_data_tx - } - } else { - None // Point is already "leftmost", no need to interpolate. - } - } else { - None // There is no point. - }; + let border_style = self.get_border_style(widget_id, app_state.current_widget.widget_id); + let x_bounds = [0, network_widget_state.current_display_time]; + let hide_x_labels = should_hide_x_label( + app_state.app_config_fields.hide_time, + app_state.app_config_fields.autohide_time, + &mut network_widget_state.autohide_timer, + draw_loc, + ); // TODO: Cache network results: Only update if: // - Force update (includes time interval change) @@ -531,7 +85,6 @@ impl Painter { // - A new time interval is better and does not fit (check from end of vector to last checked; we only want to update if it is TOO big!) // Find the maximal rx/tx so we know how to scale, and return it. - let (_best_time, max_entry) = get_max_entry( network_data_rx, network_data_tx, @@ -547,44 +100,8 @@ impl Painter { app_state.app_config_fields.network_use_binary_prefix, ); - // Cache results. - // network_widget_state.draw_max_range_cache = max_range; - // network_widget_state.draw_time_start_cache = best_time; - // network_widget_state.draw_labels_cache = labels; - - let y_axis_labels = labels - .iter() - .map(|label| Span::styled(label, self.colours.graph_style)) - .collect::<Vec<_>>(); - let y_axis = Axis::default() - .style(self.colours.graph_style) - .bounds([0.0, max_range]) - .labels(y_axis_labels); - - let is_on_widget = widget_id == app_state.current_widget.widget_id; - let border_style = if is_on_widget { - self.colours.highlighted_border_style - } else { - self.colours.border_style - }; - - let title = if app_state.is_expanded { - const TITLE_BASE: &str = " Network ── Esc to go back "; - Spans::from(vec![ - Span::styled(" Network ", self.colours.widget_title_style), - Span::styled( - format!( - "─{}─ Esc to go back ", - "─".repeat(usize::from(draw_loc.width).saturating_sub( - UnicodeSegmentation::graphemes(TITLE_BASE, true).count() + 2 - )) - ), - border_style, - ), - ]) - } else { - Spans::from(Span::styled(" Network ", self.colours.widget_title_style)) - }; + let y_labels = labels.iter().map(|label| label.into()).collect::<Vec<_>>(); + let y_bounds = [0.0, max_range]; let legend_constraints = if hide_legend { (Constraint::Ratio(0, 1), Constraint::Ratio(0, 1)) @@ -593,96 +110,64 @@ impl Painter { }; // TODO: Add support for clicking on legend to only show that value on chart. - let dataset = if app_state.app_config_fields.use_old_network_legend && !hide_legend { + let points = if app_state.app_config_fields.use_old_network_legend && !hide_legend { vec![ - Dataset::default() - .name(format!("RX: {:7}", app_state.canvas_data.rx_display)) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.rx_style) - .data(network_data_rx) - .graph_type(tui::widgets::GraphType::Line), - Dataset::default() - .name(format!("TX: {:7}", app_state.canvas_data.tx_display)) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.tx_style) - .data(network_data_tx) - .graph_type(tui::widgets::GraphType::Line), - Dataset::default() - .name(format!( - "Total RX: {:7}", - app_state.canvas_data.total_rx_display - )) - .style(self.colours.total_rx_style), - Dataset::default() - .name(format!( - "Total TX: {:7}", - app_state.canvas_data.total_tx_display - )) - .style(self.colours.total_tx_style), + GraphData { + points: network_data_rx, + style: self.colours.rx_style, + name: Some(format!("RX: {:7}", app_state.canvas_data.rx_display).into()), + }, + GraphData { + points: network_data_tx, + style: self.colours.tx_style, + name: Some(format!("TX: {:7}", app_state.canvas_data.tx_display).into()), + }, + GraphData { + points: &[], + style: self.colours.total_rx_style, + name: Some( + format!("Total RX: {:7}", app_state.canvas_data.total_rx_display) + .into(), + ), + }, + GraphData { + points: &[], + style: self.colours.total_tx_style, + name: Some( + format!("Total TX: {:7}", app_state.canvas_data.total_tx_display) + .into(), + ), + }, ] } else { vec![ - Dataset::default() - .name(&app_state.canvas_data.rx_display) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.rx_style) - .data(network_data_rx) - .graph_type(tui::widgets::GraphType::Line), - Dataset::default() - .name(&app_state.canvas_data.tx_display) - .marker(if app_state.app_config_fields.use_dot { - Marker::Dot - } else { - Marker::Braille - }) - .style(self.colours.tx_style) - .data(network_data_tx) - .graph_type(tui::widgets::GraphType::Line), + GraphData { + points: network_data_rx, + style: self.colours.rx_style, + name: Some((&app_state.canvas_data.rx_display).into()), + }, + GraphData { + points: network_data_tx, + style: self.colours.tx_style, + name: Some((&app_state.canvas_data.tx_display).into()), + }, ] }; - f.render_widget( - Chart::new(dataset) - .block( - Block::default() - .title(title) - .borders(Borders::ALL) - .border_style(if app_state.current_widget.widget_id == widget_id { - self.colours.highlighted_border_style - } else { - self.colours.border_style - }), - ) - .x_axis(x_axis) - .y_axis(y_axis) - .hidden_legend_constraints(legend_constraints), - draw_loc, - ); - - // Now if you're done, reset any interpolated points! - if let Some((index, old_value)) = interpolated_rx_point { - if let Some(to_replace) = network_data_rx.get_mut(index) { - *to_replace = old_value; - } - } - - if let Some((index, old_value)) = interpolated_tx_point { - if let Some(to_replace) = network_data_tx.get_mut(index) { - *to_replace = old_value; - } + TimeGraph { + use_dot: app_state.app_config_fields.use_dot, + x_bounds, + hide_x_labels, + y_bounds, + y_labels: &y_labels, + graph_style: self.colours.graph_style, + border_style, + title: " Network ".into(), + is_expanded: app_state.is_expanded, + title_style: self.colours.widget_title_style, + legend_constraints: Some(legend_constraints), } + .draw_time_graph(f, draw_loc, &points); } } @@ -753,3 +238,326 @@ impl Painter { ); } } + +/// 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 + } + } + } + } + + // 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) + } + } + None => ( + time_start, + calculate_missing_max(network_scale_type, network_use_binary_prefix), + ), + } + } + (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) + } + } + 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", + }; |