aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/graph.rs91
-rw-r--r--src/main.rs49
-rw-r--r--src/parseopts.rs31
3 files changed, 101 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;
}