summaryrefslogtreecommitdiffstats
path: root/src/canvas/drawing_utils.rs
diff options
context:
space:
mode:
authorClementTsang <cjhtsang@uwaterloo.ca>2022-04-27 02:13:48 -0400
committerClementTsang <cjhtsang@uwaterloo.ca>2022-04-28 23:36:53 -0400
commit2401e583fb3a6441c5d4d7483d5ce654b2f75b07 (patch)
treec22bbfe2ac448f2ec3c28e2307b759e507584929 /src/canvas/drawing_utils.rs
parent1f731358baa8e4802d8bc1f9f8171fe85132f3f1 (diff)
refactor: consolidate time graph components
This consolidates all the time graph drawing to one main location, as well as some small improvements. This is helpful in that I don't have to reimplement the same thing across three locations if I have to make one change that in theory should affect them all. In particular, the CPU graph, memory graph, and network graph are all now using the same, generic implementation for drawing, which we call (for now) a component. Note this only affects drawing - it accepts some parameters affecting style and labels, as well as data points, and draw similarly to how it used to before. Widget-specific actions, or things affecting widget state, should all be handled by the widget-specific code instead. For example, our current implementation of x-axis autohide is still controlled by the widget, not the component, even if some of the code is shared. Components are, again, only responsible for drawing (at least for now). For that matter, the graph component does not have mutable access to any form of state outside of tui-rs' `Frame`. Note this *might* change in the future, where we might give the component state. Note that while functionally, the graph behaviour for now is basically the same, a few changes were made internally other than the move to components. The big change is that rather than using tui-rs' `Chart` for the underlying drawing, we now use a tweaked custom `TimeChart` tui-rs widget, which also handles all interpolation steps and some extra customization. Personally, I don't like having to deviate from the library's implementation, but this gives us more flexibility and allows greater control. For example, this allows me to move away from the old hacks required to do interpolation (where I had to mutate the existing list to avoid having to reallocate an extra vector just to insert one extra interpolated point). I can also finally allow customizable legends (which will be added in the future).
Diffstat (limited to 'src/canvas/drawing_utils.rs')
-rw-r--r--src/canvas/drawing_utils.rs222
1 files changed, 208 insertions, 14 deletions
diff --git a/src/canvas/drawing_utils.rs b/src/canvas/drawing_utils.rs
index 7cdb3eb5..3b4fc3f6 100644
--- a/src/canvas/drawing_utils.rs
+++ b/src/canvas/drawing_utils.rs
@@ -1,5 +1,10 @@
+use tui::layout::Rect;
+
use crate::app;
-use std::cmp::{max, min};
+use std::{
+ cmp::{max, min},
+ time::Instant,
+};
/// Return a (hard)-width vector for column widths.
///
@@ -186,8 +191,7 @@ pub fn get_start_position(
}
}
-/// Calculate how many bars are to be
-/// drawn within basic mode's components.
+/// Calculate how many bars are to be drawn within basic mode's components.
pub fn calculate_basic_use_bars(use_percentage: f64, num_bars_available: usize) -> usize {
std::cmp::min(
(num_bars_available as f64 * use_percentage / 100.0).round() as usize,
@@ -195,22 +199,215 @@ pub fn calculate_basic_use_bars(use_percentage: f64, num_bars_available: usize)
)
}
-/// 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;
+/// Determine whether a graph x-label should be hidden.
+pub fn should_hide_x_label(
+ always_hide_time: bool, autohide_time: bool, timer: &mut Option<Instant>, draw_loc: Rect,
+) -> bool {
+ use crate::constants::*;
- (point_one.1 + (time - point_one.0) * slope).max(0.0)
+ if always_hide_time || (autohide_time && timer.is_none()) {
+ true
+ } else if let Some(time) = timer {
+ if Instant::now().duration_since(*time).as_millis() < AUTOHIDE_TIMEOUT_MILLISECONDS.into() {
+ false
+ } else {
+ *timer = None;
+ true
+ }
+ } else {
+ draw_loc.height < TIME_LABEL_HEIGHT_LIMIT
+ }
}
#[cfg(test)]
mod test {
+
use super::*;
#[test]
+ fn test_get_start_position() {
+ use crate::app::ScrollDirection;
+
+ // Scrolling down from start
+ {
+ let mut bar = 0;
+ assert_eq!(
+ get_start_position(10, &ScrollDirection::Down, &mut bar, 0, false),
+ 0
+ );
+ assert_eq!(bar, 0);
+ }
+
+ // Simple scrolling down
+ {
+ let mut bar = 0;
+ assert_eq!(
+ get_start_position(10, &ScrollDirection::Down, &mut bar, 1, false),
+ 0
+ );
+ assert_eq!(bar, 0);
+ }
+
+ // Scrolling down from the middle high up
+ {
+ let mut bar = 0;
+ assert_eq!(
+ get_start_position(10, &ScrollDirection::Down, &mut bar, 5, false),
+ 0
+ );
+ assert_eq!(bar, 0);
+ }
+
+ // Scrolling down into boundary
+ {
+ let mut bar = 0;
+ assert_eq!(
+ get_start_position(10, &ScrollDirection::Down, &mut bar, 11, false),
+ 1
+ );
+ assert_eq!(bar, 1);
+ }
+
+ // Scrolling down from the with non-zero bar
+ {
+ let mut bar = 5;
+ assert_eq!(
+ get_start_position(10, &ScrollDirection::Down, &mut bar, 15, false),
+ 5
+ );
+ assert_eq!(bar, 5);
+ }
+
+ // Force redraw scrolling down (e.g. resize)
+ {
+ let mut bar = 5;
+ assert_eq!(
+ get_start_position(15, &ScrollDirection::Down, &mut bar, 15, true),
+ 0
+ );
+ assert_eq!(bar, 0);
+ }
+
+ // Test jumping down
+ {
+ let mut bar = 1;
+ assert_eq!(
+ get_start_position(10, &ScrollDirection::Down, &mut bar, 20, true),
+ 10
+ );
+ assert_eq!(bar, 10);
+ }
+
+ // Scrolling up from bottom
+ {
+ let mut bar = 10;
+ assert_eq!(
+ get_start_position(10, &ScrollDirection::Up, &mut bar, 20, false),
+ 10
+ );
+ assert_eq!(bar, 10);
+ }
+
+ // Simple scrolling up
+ {
+ let mut bar = 10;
+ assert_eq!(
+ get_start_position(10, &ScrollDirection::Up, &mut bar, 19, false),
+ 10
+ );
+ assert_eq!(bar, 10);
+ }
+
+ // Scrolling up from the middle
+ {
+ let mut bar = 10;
+ assert_eq!(
+ get_start_position(10, &ScrollDirection::Up, &mut bar, 10, false),
+ 10
+ );
+ assert_eq!(bar, 10);
+ }
+
+ // Scrolling up into boundary
+ {
+ let mut bar = 10;
+ assert_eq!(
+ get_start_position(10, &ScrollDirection::Up, &mut bar, 9, false),
+ 9
+ );
+ assert_eq!(bar, 9);
+ }
+
+ // Force redraw scrolling up (e.g. resize)
+ {
+ let mut bar = 5;
+ assert_eq!(
+ get_start_position(10, &ScrollDirection::Up, &mut bar, 15, true),
+ 5
+ );
+ assert_eq!(bar, 5);
+ }
+
+ // Test jumping up
+ {
+ let mut bar = 10;
+ assert_eq!(
+ get_start_position(10, &ScrollDirection::Up, &mut bar, 0, false),
+ 0
+ );
+ assert_eq!(bar, 0);
+ }
+ }
+
+ #[test]
+ fn test_calculate_basic_use_bars() {
+ // Testing various breakpoints and edge cases.
+ assert_eq!(calculate_basic_use_bars(0.0, 15), 0);
+ assert_eq!(calculate_basic_use_bars(1.0, 15), 0);
+ assert_eq!(calculate_basic_use_bars(5.0, 15), 1);
+ assert_eq!(calculate_basic_use_bars(10.0, 15), 2);
+ assert_eq!(calculate_basic_use_bars(40.0, 15), 6);
+ assert_eq!(calculate_basic_use_bars(45.0, 15), 7);
+ assert_eq!(calculate_basic_use_bars(50.0, 15), 8);
+ assert_eq!(calculate_basic_use_bars(100.0, 15), 15);
+ assert_eq!(calculate_basic_use_bars(150.0, 15), 15);
+ }
+
+ #[test]
+ fn test_should_hide_x_label() {
+ use crate::constants::*;
+ use std::time::{Duration, Instant};
+ use tui::layout::Rect;
+
+ let rect = Rect::new(0, 0, 10, 10);
+ let small_rect = Rect::new(0, 0, 10, 6);
+
+ let mut under_timer = Some(Instant::now());
+ let mut over_timer =
+ Instant::now().checked_sub(Duration::from_millis(AUTOHIDE_TIMEOUT_MILLISECONDS + 100));
+
+ assert!(should_hide_x_label(true, false, &mut None, rect));
+ assert!(should_hide_x_label(false, true, &mut None, rect));
+ assert!(should_hide_x_label(false, false, &mut None, small_rect));
+
+ assert!(!should_hide_x_label(
+ false,
+ true,
+ &mut under_timer,
+ small_rect
+ ));
+ assert!(under_timer.is_some());
+
+ assert!(should_hide_x_label(
+ false,
+ true,
+ &mut over_timer,
+ small_rect
+ ));
+ assert!(over_timer.is_none());
+ }
+
+ #[test]
fn test_zero_width() {
assert_eq!(
get_column_widths(
@@ -222,7 +419,6 @@ mod test {
true
),
vec![],
- "vector should be empty"
);
}
@@ -238,7 +434,6 @@ mod test {
true
),
vec![],
- "vector should be empty"
);
}
@@ -254,7 +449,6 @@ mod test {
true
),
vec![2, 2, 7],
- "vector should not be empty"
);
}
}