diff options
-rw-r--r-- | src/graph.rs | 91 | ||||
-rw-r--r-- | src/main.rs | 49 | ||||
-rw-r--r-- | src/parseopts.rs | 31 | ||||
-rw-r--r-- | tg.1 | 13 |
4 files changed, 114 insertions, 70 deletions
diff --git a/src/graph.rs b/src/graph.rs index 85a0838..861478c 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -47,6 +47,8 @@ pub enum GraphType { Star, /// Use pretty characters from the ascii range Ascii, + /// Draw using braille unicode characters + Braille, } impl std::default::Default for GraphType { @@ -80,6 +82,8 @@ pub struct GraphBuilder { 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, } impl GraphBuilder { @@ -102,6 +106,7 @@ impl GraphBuilder { y_values: y_values.to_vec(), enable_axis: false, graph_type: GraphType::default(), + cut_overflow: false, } } @@ -131,10 +136,26 @@ impl GraphBuilder { self } + /// Enable cutting overflow, this works differently to keep_tail directly, + /// as the axis-calculations must be performed first. + /// So keep_tail is run once first, so we can keep a approximate window, + /// and then another time to get it exactly right. + /// + /// # Arguments + /// + pub fn cut_overflow(&mut self, enable: bool) -> &Self { + self.cut_overflow = enable; + self + } + /// Build the actual graph, /// this is potentially a heavy operation, and it will mutate &self! /// If you want to only see the "current state", you should clone first! pub fn build(&mut self) -> String { + if self.cut_overflow { + self.keep_tail(self.draw_width); + } + //let min_x = self.x_values.iter().cloned().fold(f64::INFINITY, f64::min); //let max_x = self // .x_values @@ -161,6 +182,11 @@ impl GraphBuilder { ); } + // Run a second time after axis has been calculated properly + if self.cut_overflow { + self.keep_tail(self.draw_width); + } + if true { // && x_values.windows(2).all(|w| w[1] - w[0] == w[0] - w[1]) { if self.y_values.len() >= self.draw_width { @@ -181,6 +207,7 @@ impl GraphBuilder { match self.graph_type { GraphType::Star => self.draw_star(), GraphType::Ascii => self.draw_ascii(), + GraphType::Braille => self.draw_braille(), } self.to_string() @@ -256,28 +283,40 @@ impl GraphBuilder { c5: GraphPixel<char>, c6: GraphPixel<char>, ) { - if self.height < 2 || self.width < 2 { - return; + let mut y_ticks: Vec<String> = Vec::with_capacity(self.height); + let mut x_offset: usize = 0; + for i in 0..self.height { + let n = (min_y + (((max_y - min_y) / (self.height as f64 - 1.0)) * i as f64)) + .round() + .to_string(); + if n.len() > x_offset { + x_offset = n.len(); + } + y_ticks.insert(0, n); } + for i in 0..self.height { - self.elements[i * self.width] = c1.clone(); + self.elements[i * self.width + x_offset] = c1.clone(); self.elements[i * self.width + self.width - 1] = c1.clone(); + for (j, c) in y_ticks[i].chars().enumerate() { + self.elements[i * self.width + j] = GraphPixel::Normal(c); + } } - for i in 1..self.width - 1 { + for i in 1 + x_offset..self.width - 1 { self.elements[i] = c2.clone(); self.elements[(self.height - 1) * self.width + i] = c2.clone(); } - self.elements[0] = c4.clone(); + self.elements[x_offset] = c4.clone(); self.elements[self.width - 1] = c6.clone(); - self.elements[(self.height - 1) * self.width] = c3.clone(); + self.elements[(self.height - 1) * self.width + x_offset] = c3.clone(); self.elements[self.height * self.width - 1] = c5.clone(); if self.draw_height > 2 { self.draw_height = self.height - 2; } if self.draw_width > 2 { - self.draw_width = self.width - 2; + self.draw_width = self.width - 2 - x_offset; } - self.col_offset = 1; + self.col_offset = x_offset + 1; self.row_offset = 1; } @@ -293,7 +332,7 @@ impl GraphBuilder { pub fn draw_ascii(&mut self) { if self.enable_axis { self.draw_exact( - 0, + self.col_offset - 1, self.draw_height - self.y_values[0] as usize, GraphPixel::Green('├'), ); @@ -328,6 +367,11 @@ impl GraphBuilder { } } } + + /// Draw a graph using * for the pixels of the graph + fn draw_braille(&mut self) { + unimplemented!("The braille mode is not implemented"); + } } // /// A better way to downsize, heavier and more complex, but should be used when sample speed is uneven. @@ -359,32 +403,3 @@ impl GraphBuilder { // // interpolated_data // } - -//const _BRAILLE_1: char = '⣿'; -//const BRAILLE_1_0: char = '⡀'; -//const BRAILLE_1_1: char = '⣀'; -//const BRAILLE_1_2: char = '⣀'; -//const BRAILLE_2_0: char = '⡄'; -//const BRAILLE_3_0: char = '⡆'; -//const BRAILLE_4_0: char = '⡇'; -// pub fn braille(y_values: &Vec<f64>, options: &GraphOptions) -> String { -// let aspects = SeriesAspects::from(y_values); -// let canvas = String::with_capacity((options.width * options.height) as usize); -// -// /* -// r = (max - min) -// r' = (max' - min') -// y' = (((y - min) * r') / r) + min' -// */ -// let r = aspects.max - aspects.min; -// let r_marked = options.height; -// -// let norm_after = options.height; -// -// //for (x, y) in y_values.iter().enumerate() { -// // let y = norm(y.clone(), 0.0, options.height); -// // let x = norm(x.clone(), 0.0, options.width); -// //} -// -// String::from("") -// } diff --git a/src/main.rs b/src/main.rs index 728f42a..07434db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,35 @@ use std::io::{self, BufRead, Write}; use std::str::FromStr; -use textgraph::graph; -use textgraph::parseopts::{parseopts, Opts}; +use textgraph::graph::GraphBuilder; +use textgraph::parseopts::{parseopts, OptsBuilder}; + +/// Build a graph text string, based on values and a OptsBuilder +/// +/// # Arguments +/// +/// * `opts` - textgraph::parseopts::OptBuilder +fn build_graph(x_values: &Vec<f64>, y_values: &Vec<f64>, opts: &OptsBuilder) -> String { + let opts = opts.clone().build(); + + let mut gb = GraphBuilder::new(&x_values, &y_values, opts.width, opts.height); + gb.axis(!opts.silent); + gb.graph_type(opts.graph_type.clone()); + if opts.cut { + gb.cut_overflow(true); + } else if let Some(n) = opts.last_n { + gb.keep_tail(n as usize); + } + + gb.build() +} /// Will graph what comes in through stdin, /// For each new line, the graph will be re-drawn. /// /// # Arguments /// -/// * `opts` - textgraph::parseopts::Opts -fn filter(opts: Opts) { +/// * `opts` - textgraph::parseopts::OptBuilder +fn filter(opts: OptsBuilder) { //print!("\x1b[?1049h"); let mut x_values: Vec<f64> = Vec::new(); @@ -25,15 +45,8 @@ fn filter(opts: Opts) { y_values.push(y); x_values.push(i); - let mut gb = graph::GraphBuilder::new(&x_values, &y_values, opts.width, opts.height); - gb.axis(!opts.silent); - gb.graph_type(opts.graph_type.clone()); - if let Some(n) = opts.last_n { - gb.keep_tail(n as usize); - } - //print!("\x1B[2J\x1B[H"); - println!("{}", gb.build()); + println!("{}", build_graph(&x_values, &y_values, &opts)); } //print!("\x1B[?1049l"); @@ -46,8 +59,8 @@ fn filter(opts: Opts) { /// /// # Arguments /// -/// * `opts` - textgraph::parseopts::Opts -fn graph_file(opts: Opts) { +/// * `opts` - textgraph::parseopts::OptBuilder +fn graph_file(opts: OptsBuilder) { let raw_y_values = std::fs::read_to_string(opts.in_file.clone().unwrap()).expect("TG6"); let mut y_values: Vec<f64> = Vec::new(); @@ -57,13 +70,7 @@ fn graph_file(opts: Opts) { x_values.push(i as f64); } - let mut gb = graph::GraphBuilder::new(&x_values, &y_values, opts.width, opts.height); - gb.axis(!opts.silent); - gb.graph_type(opts.graph_type); - if let Some(n) = opts.last_n { - gb.keep_tail(n as usize); - } - println!("{}", gb.build()); + println!("{}", build_graph(&x_values, &y_values, &opts)); } /// Main entry point for the binary of textgraph diff --git a/src/parseopts.rs b/src/parseopts.rs index 1bae188..9cb1c7e 100644 --- a/src/parseopts.rs +++ b/src/parseopts.rs @@ -13,28 +13,26 @@ pub struct Opts { pub silent: bool, /// Specify if it is used as a filter, and you only want to look at the last N samples pub last_n: Option<u64>, + /// Special case of last_n, which will use window_with as a target. + pub cut: bool, /// Read from the specified file, instead of reading continously from stdin pub in_file: Option<String>, } /// Struct containing command line options +#[derive(Clone)] pub struct OptsBuilder { - /// Desired width of graph, if None, it should be automatically determined pub width: Option<usize>, - /// Desired height of graph, if None, it should be automatically determined pub height: Option<usize>, - /// Which type of graph it should be, ascii, star pub graph_type: GraphType, - /// This will disable distracting elements, such as axis pub silent: bool, - /// Specify if it is used as a filter, and you only want to look at the last N samples pub last_n: Option<u64>, - /// Read from the specified file, instead of reading continously from stdin + pub cut: bool, pub in_file: Option<String>, } impl OptsBuilder { - fn build(self) -> Opts { + pub fn build(self) -> Opts { Opts { width: self.width.unwrap_or_else(|| { if let Ok((width, _)) = crate::term::get_terminal_size() { @@ -56,6 +54,7 @@ impl OptsBuilder { silent: self.silent, last_n: self.last_n, in_file: self.in_file, + cut: self.cut, } } } @@ -96,6 +95,9 @@ pub fn parseopt(opts: &mut OptsBuilder, arg: &str, value: Option<String>, progna "ascii" => { opts.graph_type = GraphType::Ascii; } + "braille" => { + opts.graph_type = GraphType::Braille; + } t => { println!( "Unknown type \"{}\", valid options are \"star\", \"ascii\".", @@ -116,7 +118,7 @@ pub fn parseopt(opts: &mut OptsBuilder, arg: &str, value: Option<String>, progna }; opts.height = Some(height); } - "l" | "last-n" => { + "n" | "last-n" => { let Some(last_n) = value else { println!("Missing value for {}", arg); parseopts_panic!(progname); @@ -130,6 +132,12 @@ pub fn parseopt(opts: &mut OptsBuilder, arg: &str, value: Option<String>, progna "s" | "silent" => { opts.silent = true; } + "a" | "ascii" => { + opts.graph_type = GraphType::Ascii; + } + "c" | "cut" => { + opts.cut = true; + } "w" | "width" => { let Some(width) = value else { println!("Missing value for {}", arg); @@ -153,13 +161,14 @@ pub fn parseopt(opts: &mut OptsBuilder, arg: &str, value: Option<String>, progna /// of unix style command line arguments. /// This function is specialised for the TextGraph program, /// but is easily adaptable for other programs as well. -pub fn parseopts() -> Opts { +pub fn parseopts() -> OptsBuilder { let mut opts = OptsBuilder { width: None, height: None, graph_type: GraphType::Star, silent: false, last_n: None, + cut: false, in_file: None, }; @@ -193,7 +202,7 @@ pub fn parseopts() -> Opts { arg.remove(0); for arg_name in arg.chars() { match arg_name { - 'h' | 't' | 'w' | 'l' => { + 'h' | 't' | 'w' | 'n' => { parseopt(&mut opts, &arg_name.to_string(), it.next(), &progname); } _ => { @@ -218,5 +227,5 @@ pub fn parseopts() -> Opts { } } - return opts.build(); + return opts; } @@ -16,6 +16,19 @@ Display help information. .IP "\fB-s, --silent\fR" Disable distracting elements, such as axis and non-graph text. +.IP "\fB-a, --ascii\fR" +Shorthand for -t ascii, if multiple options setting mode is specified, +the last will likely be respected. + +.IP "\fB-n, --last-n\fR \fIcount\fR" +If specified, only the newest \fIcount\fR samples will be plotted. +This can be useful if you want to follow the latest state of a graph that is piped in. + + +.IP "\fB-c, --cut\fR" +This is a special case of \fB--last-n\fR. Where the number of columns +\fB--width\fI will be used for the count. + .IP "\fB-t\fR \fItype\fR" The type of graph to draw, it defaults to \fBstar\fR, which is the fastest one. |