diff options
author | jakobst1n <jakob.stendahl@outlook.com> | 2024-06-04 05:00:57 +0200 |
---|---|---|
committer | jakobst1n <jakob.stendahl@outlook.com> | 2024-06-04 05:00:57 +0200 |
commit | 6e9628e2d20c09eaeb7eedfe2f7278de0b65a09f (patch) | |
tree | 745677d456a80d6e3d3d80d66eea1bf5d0c4b636 | |
download | textgraph-6e9628e2d20c09eaeb7eedfe2f7278de0b65a09f.tar.gz textgraph-6e9628e2d20c09eaeb7eedfe2f7278de0b65a09f.zip |
Initial commit
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 7 | ||||
-rw-r--r-- | Cargo.toml | 8 | ||||
-rw-r--r-- | src/graph.rs | 142 | ||||
-rw-r--r-- | src/lib.rs | 1 | ||||
-rw-r--r-- | src/main.rs | 31 |
6 files changed, 190 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..53dcf10 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "textgraph" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..eaeeca4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "textgraph" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/graph.rs b/src/graph.rs new file mode 100644 index 0000000..f837f68 --- /dev/null +++ b/src/graph.rs @@ -0,0 +1,142 @@ +const _BRAILLE_1: char = '⣿'; + +const ASCII_0: char = '─'; +const ASCII_1: char = '│'; +const ASCII_2: char = '╭'; +const ASCII_3: char = '╰'; +const ASCII_4: char = '╮'; +const ASCII_7: 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 = '⡇'; + + +/* + ╭────────╮ +╭─╯ │ +╰ ╰╮ + ╰──────── +*/ + +#[derive(Debug)] +pub struct GraphOptions { + pub width: f64, + pub height: f64, +} + +#[derive(Debug)] +pub struct SeriesAspects<T> { + max: T, + min: T, +} + +pub trait SeriesTraits: std::cmp::PartialOrd + Clone + std::ops::Div + std::ops::Sub {} +impl<T: std::cmp::PartialOrd + Clone + std::ops::Div + std::ops::Sub> SeriesTraits for T {} + +impl<T: SeriesTraits> From<&Vec<T>> for SeriesAspects<T> { + fn from(series: &Vec<T>) -> SeriesAspects<T> { + let mut it = series.iter(); + let first = it.next(); + let mut min = first.expect("TG2"); + let mut max = first.expect("TG3"); + while let Some(i) = it.next() { + if i < min { + min = i; + } + if i > max { + max = i; + } + } + SeriesAspects { + max: max.clone(), + min: min.clone(), + } + } +} + + +pub fn downsample(series: &[f64], column_count: usize) -> Vec<f64> { + let factor = series.len() as f64 / column_count as f64; + (0..column_count).map(|i| series[(i as f64 * factor) as usize]).collect() +} + +pub fn interpolate(series: &[f64], marks: &[f64], column_count: usize) -> Vec<f64> { + let min_mark = marks.iter().cloned().fold(f64::INFINITY, f64::min); + let max_mark = marks.iter().cloned().fold(f64::NEG_INFINITY, f64::max); + let step = (max_mark - min_mark) / (column_count as f64 - 1.0); + let mut interpolated_data = Vec::new(); + + for i in 0..column_count { + let target_mark = min_mark + i as f64 * step; + let mut j = 0; + while j < marks.len() - 1 && marks[j + 1] < target_mark { + j += 1; + } + let t0 = marks[j]; + let t1 = marks[j + 1]; + let d0 = series[j]; + let d1 = series[j + 1]; + let value = d0 + (d1 - d0) * (target_mark - t0) / (t1 - t0); + interpolated_data.push(value); + } + + interpolated_data +} + +fn scale(series: &[f64], row_count: usize) -> Vec<usize> { + let min_value = series.iter().cloned().fold(f64::INFINITY, f64::min); + let max_value = series.iter().cloned().fold(f64::NEG_INFINITY, f64::max); + let scale_factor = (row_count - 1) as f64 / (max_value - min_value); + series.iter().map(|&y| ((y - min_value) * scale_factor).round() as usize).collect() +} + +pub fn star(series: &[f64], options: &GraphOptions) -> String { + let scaled_data = scale(series, options.height as usize); + let mut graph = vec![vec![' '; options.width as usize]; options.height as usize]; + + for (i, &value) in scaled_data.iter().enumerate() { + let y = options.height as usize - value - 1; // Invert y-axis for ASCII graph + graph[y][i] = '*'; + } + + graph.iter().map(|row| row.iter().collect::<String>()).collect::<Vec<String>>().join("\n") +} + +pub fn ascii_trailing(series: &[f64], options: &GraphOptions) -> String { + let scaled_data = scale(series, options.height as usize); + let mut graph = vec![vec![' '; options.width as usize]; options.height as usize]; + + for (i, &value) in scaled_data.iter().enumerate() { + let y = options.height as usize - value - 1; // Invert y-axis for ASCII graph + graph[y][i] = '*'; + } + + graph.iter().map(|row| row.iter().collect::<String>()).collect::<Vec<String>>().join("\n") +} + +pub fn braille(series: &Vec<f64>, options: &GraphOptions) -> String { + let aspects = SeriesAspects::from(series); + 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 series.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/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6f94350 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod graph; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..4cae5be --- /dev/null +++ b/src/main.rs @@ -0,0 +1,31 @@ +use textgraph::graph; + +fn main() { + let mut line: Vec<f64> = Vec::new(); + let mut marks: Vec<f64> = Vec::new(); + for i in 0..500 { + line.push((i as f64 * std::f64::consts::PI / 120.0).sin()); + marks.push(i as f64); + } + + // Choose one of the methods based on sample speed: + //let downsampled_data = graph::downsample(&line, 100); + let interpolated_data = graph::interpolate(&line, &marks, 100); + + + //let processed_data = if marks.windows(2).all(|w| w[1] - w[0] == w[0] - w[1]) { + // downsample(&series, options.width) + //} else { + // interpolate(&series, &marks, options.width) + //}; + + + let g = graph::ascii_trailing( + &interpolated_data, + &graph::GraphOptions { + width: 100.0, + height: 30.0, + }, + ); + println!("{}", g); +} |