diff options
author | jakobst1n <jakob.stendahl@outlook.com> | 2024-06-12 13:40:31 +0200 |
---|---|---|
committer | jakobst1n <jakob.stendahl@outlook.com> | 2024-06-12 13:40:31 +0200 |
commit | d696de49c85544be76b9ca132c86f640b5e44bc1 (patch) | |
tree | 1eb9ab1b359c6b68b671fcecb516467df9741a9d | |
parent | 861bb7ddfbf3056c75523908e05d7ac29787aaaa (diff) | |
download | textgraph-d696de49c85544be76b9ca132c86f640b5e44bc1.tar.gz textgraph-d696de49c85544be76b9ca132c86f640b5e44bc1.zip |
Add options to toggle colors, prepare for allowing multiple lines
-rw-r--r-- | src/graph.rs | 135 | ||||
-rw-r--r-- | src/main.rs | 1 | ||||
-rw-r--r-- | src/parseopts.rs | 25 | ||||
-rw-r--r-- | tg.1 | 6 |
4 files changed, 111 insertions, 56 deletions
diff --git a/src/graph.rs b/src/graph.rs index 69a271d..92e16aa 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -1,6 +1,3 @@ -//use std::io::IsTerminal; -//dbg!(std::io::stdout().is_terminal()); - const ASCII_0: char = '─'; const ASCII_1: char = '│'; const ASCII_2: char = '╭'; @@ -77,13 +74,15 @@ pub struct GraphBuilder { /// The values of the x-axis of the graph x_values: Vec<f64>, /// The values of the y-axis of the graph - y_values: Vec<f64>, + y_values: Vec<Vec<f64>>, /// Decides whether axis will be drawn on the resulting graph enable_axis: bool, /// Which GraphType to use when the graph is drawn graph_type: GraphType, /// Special case of running keep_tail once cut_overflow: bool, + /// Whether or not to use color pixels + enable_color: bool, } impl GraphBuilder { @@ -103,10 +102,11 @@ impl GraphBuilder { col_offset: 0, row_offset: 0, x_values: x_values.to_vec(), - y_values: y_values.to_vec(), + y_values: vec![y_values.to_vec()], enable_axis: false, graph_type: GraphType::default(), cut_overflow: false, + enable_color: true, } } @@ -122,6 +122,12 @@ impl GraphBuilder { self } + /// Enable or disable color + pub fn color(&mut self, enable_color: bool) -> &Self { + self.enable_color = enable_color; + self + } + /// Delete all saved samples before the last n /// Assumes that y_values and x_values has the same length /// @@ -129,9 +135,11 @@ impl GraphBuilder { /// /// * `n` - Number of samples to keep pub fn keep_tail(&mut self, n: usize) -> &Self { - if self.y_values.len() > n { - self.y_values = self.y_values[self.y_values.len() - n..].to_vec(); - self.x_values = self.x_values[self.x_values.len() - n..].to_vec(); + for i in 0..self.y_values.len() { + if self.y_values[i].len() > n { + self.y_values[i] = self.y_values[i][self.y_values.len() - n..].to_vec(); + self.x_values = self.x_values[self.x_values.len() - n..].to_vec(); + } } self } @@ -166,9 +174,11 @@ impl GraphBuilder { // .iter() // .cloned() // .fold(f64::NEG_INFINITY, f64::max); - let min_y = self.y_values.iter().cloned().fold(f64::INFINITY, f64::min); - let max_y = self - .y_values + let min_y = self.y_values[0] + .iter() + .cloned() + .fold(f64::INFINITY, f64::min); + let max_y = self.y_values[0] .iter() .cloned() .fold(f64::NEG_INFINITY, f64::max); @@ -197,7 +207,7 @@ impl GraphBuilder { if true { // && x_values.windows(2).all(|w| w[1] - w[0] == w[0] - w[1]) { - if self.y_values.len() >= self.draw_width { + if self.y_values[0].len() >= self.draw_width { self.downsample(); } } else { @@ -212,14 +222,14 @@ impl GraphBuilder { scale_height = self.draw_height * 3; } let scale_factor = (scale_height - 1) as f64 / (max_y - min_y); - for i in 0..self.y_values.len() { - self.y_values[i] = ((self.y_values[i] - min_y) * scale_factor).round(); + for i in 0..self.y_values[0].len() { + self.y_values[0][i] = ((self.y_values[0][i] - min_y) * scale_factor).round(); } match self.graph_type { - GraphType::Star => self.draw_star(), - GraphType::Ascii => self.draw_ascii(), - GraphType::Braille => self.draw_braille(), + GraphType::Star => self.draw_star(0), + GraphType::Ascii => self.draw_ascii(0), + GraphType::Braille => self.draw_braille(0), } self.to_string() @@ -229,22 +239,24 @@ impl GraphBuilder { // with the x values. // Make sure to only use one downsampling-algorithm fn downsample(&mut self) { - if self.graph_type == GraphType::Braille { - let factor = self.y_values.len() as f64 / (self.draw_width as f64 * 2.0); - let mut new_values = Vec::with_capacity(self.draw_width * 2); - for i in 0..self.draw_width * 2 { - let new_value = self.y_values[(i as f64 * factor) as usize]; - new_values.push(new_value); - } - self.y_values = new_values; - } else { - let factor = self.y_values.len() as f64 / self.draw_width as f64; - let mut new_values = Vec::with_capacity(self.draw_width); - for i in 0..self.draw_width { - let new_value = self.y_values[(i as f64 * factor) as usize]; - new_values.push(new_value); + for g in 0..self.y_values.len() { + if self.graph_type == GraphType::Braille { + let factor = self.y_values[g].len() as f64 / (self.draw_width as f64 * 2.0); + let mut new_values = Vec::with_capacity(self.draw_width * 2); + for i in 0..self.draw_width * 2 { + let new_value = self.y_values[g][(i as f64 * factor) as usize]; + new_values.push(new_value); + } + self.y_values[g] = new_values; + } else { + let factor = self.y_values[g].len() as f64 / self.draw_width as f64; + let mut new_values = Vec::with_capacity(self.draw_width); + for i in 0..self.draw_width { + let new_value = self.y_values[g][(i as f64 * factor) as usize]; + new_values.push(new_value); + } + self.y_values[g] = new_values; } - self.y_values = new_values; } } @@ -260,6 +272,19 @@ impl GraphBuilder { out } + // Method that takes a closure to decide which GraphPixel variant to create + // A more customizable variant of the color! macro + fn color_pixel<F>(&self, px: char, creator: F) -> GraphPixel<char> + where + F: FnOnce(char) -> GraphPixel<char>, + { + if self.enable_color { + creator(px) + } else { + GraphPixel::Normal(px) + } + } + /// Set a pixel at a absolute position in the canvas /// /// # Argument @@ -343,60 +368,60 @@ impl GraphBuilder { } /// Draw a graph using * for the pixels of the graph - fn draw_star(&mut self) { - for i in 0..self.y_values.len() { - let y = self.draw_height - (self.y_values[i] as usize) - 1; + fn draw_star(&mut self, g: usize) { + for i in 0..self.y_values[g].len() { + let y = self.draw_height - (self.y_values[g][i] as usize) - 1; self.draw(i, y, GraphPixel::Normal('*')); } } /// Draw a graph using somewhat pretty ascii characters for pixels of the graph - pub fn draw_ascii(&mut self) { + pub fn draw_ascii(&mut self, g: usize) { if self.enable_axis { self.draw_exact( self.col_offset - 1, - self.draw_height - self.y_values[0] as usize, - GraphPixel::Green('├'), + self.draw_height - self.y_values[g][0] as usize, + self.color_pixel('├', |px| GraphPixel::Green(px)), ); self.draw_exact( self.width - 1, - self.draw_height - self.y_values[self.y_values.len() - 1] as usize, - GraphPixel::Green('┤'), + self.draw_height - self.y_values[g][self.y_values.len() - 1] as usize, + self.color_pixel('┤', |px| GraphPixel::Green(px)), ); } - for i in 0..self.y_values.len() { - let y1 = self.draw_height - (self.y_values[i] as usize) - 1; - let y2 = if i < self.y_values.len() - 1 { - self.draw_height - (self.y_values[i + 1] as usize) - 1 + for i in 0..self.y_values[g].len() { + let y1 = self.draw_height - (self.y_values[g][i] as usize) - 1; + let y2 = if i < self.y_values[g].len() - 1 { + self.draw_height - (self.y_values[g][i + 1] as usize) - 1 } else { y1 }; if y1 == y2 { - self.draw(i, y1, GraphPixel::Green(ASCII_0)); + self.draw(i, y1, self.color_pixel(ASCII_0, |px| GraphPixel::Green(px))); } else if y1 > y2 { - self.draw(i, y1, GraphPixel::Green(ASCII_7)); - self.draw(i, y2, GraphPixel::Green(ASCII_2)); + self.draw(i, y1, self.color_pixel(ASCII_7, |px| GraphPixel::Green(px))); + self.draw(i, y2, self.color_pixel(ASCII_2, |px| GraphPixel::Green(px))); for j in (y2 + 1)..y1 { - self.draw(i, j, GraphPixel::Green(ASCII_1)); + self.draw(i, j, self.color_pixel(ASCII_1, |px| GraphPixel::Green(px))); } } else { - self.draw(i, y1, GraphPixel::Green(ASCII_4)); - self.draw(i, y2, GraphPixel::Green(ASCII_3)); + self.draw(i, y1, self.color_pixel(ASCII_4, |px| GraphPixel::Green(px))); + self.draw(i, y2, self.color_pixel(ASCII_3, |px| GraphPixel::Green(px))); for j in (y1 + 1)..y2 { - self.draw(i, j, GraphPixel::Green(ASCII_1)); + self.draw(i, j, self.color_pixel(ASCII_1, |px| GraphPixel::Green(px))); } } } } /// Draw a graph using * for the pixels of the graph - fn draw_braille(&mut self) { + fn draw_braille(&mut self, g: usize) { let mut i = 0; - while i < self.y_values.len() - 1 { - let y1 = (self.draw_height * 3) - (self.y_values[i] as usize) - 1; + while i < self.y_values[g].len() - 1 { + let y1 = (self.draw_height * 3) - (self.y_values[g][i] as usize) - 1; let y1_abs = y1 / 3; - let y2 = (self.draw_height * 3) - (self.y_values[i + 1] as usize) - 1; + let y2 = (self.draw_height * 3) - (self.y_values[g][i + 1] as usize) - 1; let y2_abs = y2 / 3; if y1_abs == y2_abs { diff --git a/src/main.rs b/src/main.rs index 3e827d9..16de4cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,7 @@ fn build_graph(x_values: &Vec<f64>, y_values: &Vec<f64>, opts: &OptsBuilder) -> let opts = opts.clone().build(); let mut gb = GraphBuilder::new(&x_values, &y_values, opts.width, opts.height); + gb.color(opts.color); gb.axis(!opts.silent); gb.graph_type(opts.graph_type.clone()); if opts.cut { diff --git a/src/parseopts.rs b/src/parseopts.rs index 7d5d760..18b314c 100644 --- a/src/parseopts.rs +++ b/src/parseopts.rs @@ -1,6 +1,8 @@ use crate::graph::GraphType; use std::str::FromStr; +use std::io::IsTerminal; + /// Struct containing command line options pub struct Opts { /// Desired width of graph, if None, it should be automatically determined @@ -17,6 +19,8 @@ pub struct Opts { pub cut: bool, /// Read from the specified file, instead of reading continously from stdin pub in_file: Option<String>, + /// Enable color + pub color: bool, } /// Struct containing command line options @@ -29,6 +33,7 @@ pub struct OptsBuilder { pub last_n: Option<u64>, pub cut: bool, pub in_file: Option<String>, + pub color: Option<bool>, } impl OptsBuilder { @@ -55,6 +60,9 @@ impl OptsBuilder { last_n: self.last_n, in_file: self.in_file, cut: self.cut, + color: self + .color + .unwrap_or_else(|| std::io::stdout().is_terminal()), } } } @@ -141,6 +149,20 @@ pub fn parseopt(opts: &mut OptsBuilder, arg: &str, value: Option<String>, progna "c" | "cut" => { opts.cut = true; } + "color" => { + let Some(color) = value else { + println!("Missing value for {}", arg); + parseopts_panic!(progname); + }; + opts.color = match color.as_str() { + "yes" => Some(true), + "no" => Some(false), + t => { + println!("Unknown type \"{}\", valid options are \"yes\", \"no\".", t); + parseopts_panic!(progname); + } + } + } "w" | "width" => { let Some(width) = value else { println!("Missing value for {}", arg); @@ -173,6 +195,7 @@ pub fn parseopts() -> OptsBuilder { last_n: None, cut: false, in_file: None, + color: None, }; let mut it = std::env::args(); @@ -194,7 +217,7 @@ pub fn parseopts() -> OptsBuilder { } else { arg_name = arg.clone(); match arg_name.as_str() { - "widht" | "height" | "last-n" => { + "widht" | "height" | "last-n" | "color" => { arg_value = it.next(); } _ => (), @@ -48,6 +48,12 @@ Specify a height for the output. If not specified, it will attempt to determine the TTY height and use that. If it cannot be automatically determined, it will fail. +.IP "\fB--color\fR \fIyes\fR|\fIno\fR" +Enable or disable colors, by default color will be enabled if it looks like a tty is connected. + +It can therefore be nice to use \fB--color yes\fR +if you are piping the output into another program that supports colors. + .SH EXAMPLES The simplest version is if you have a text file of values |