#![allow(dead_code)]
pub mod utils;
pub mod scale;
use drawille::{Canvas as BrailleCanvas};
use self::scale::Scale;
use std::cmp;
use std::default::Default;
pub struct Chart {
width: u32,
height: u32,
xmin: f32,
xmax: f32,
ymin: f32,
ymax: f32,
ylabel: Option<String>,
canvas: BrailleCanvas,
}
pub enum Shape<'a> {
Continuous(fn(f32) -> f32),
Lines(&'a [(f32, f32)]),
Steps(&'a [(f32, f32)]),
Bars(&'a [(f32, f32)]),
}
pub trait Plot {
fn lineplot(&mut self, shape: Shape) -> &mut Chart;
fn y_label(&mut self, label: &str) -> &mut Chart;
}
impl Default for Chart {
fn default() -> Self {
Self::new(120, 60, -10.0, 10.0)
}
}
impl Chart {
pub fn new(width: u32, height: u32, xmin: f32, xmax: f32) -> Self {
if width < 32 {
panic!("width should be more then 32, {} is provided", width);
}
if height < 32 {
panic!("height should be more then 32, {} is provided", height);
}
Self {
xmin,
xmax,
ymin: 10.0,
ymax: -10.0,
width,
height,
canvas: BrailleCanvas::new(width, height),
ylabel: None
}
}
fn borders(&mut self) {
let w = self.width;
let h = self.height;
self.vline(0);
self.vline(w);
self.hline(0);
self.hline(h);
}
fn vline(&mut self, i: u32) {
if i <= self.width {
for j in 0..=self.height {
if j % 3 == 0 {
self.canvas.set(i, j);
}
}
}
}
fn hline(&mut self, j: u32) {
if j <= self.height {
for i in 0..=self.width {
if i % 3 == 0 {
self.canvas.set(i, self.height - j);
}
}
}
}
pub fn display(&self) {
let frame = self.canvas.frame();
let rows = frame.split('\n').into_iter().count();
let mid = rows / 2;
for (i, row) in frame.split('\n').into_iter().enumerate() {
if i == 0 {
println!("{0} {1:.1}", row, self.ymax);
} else if i == (rows - 1) {
println!("{0} {1:.1}", row, self.ymin);
} else if i == mid {
match self.ylabel {
Some(ref label) => println!("{0} {1:}", row, label),
None => println!("{}", row)
};
} else {
println!("{}", row);
}
}
println!("{0: <width$.1}{1:.1}", self.xmin, self.xmax, width=(self.width as usize) / 2 - 3);
}
pub fn nice(&mut self) {
self.borders();
self.display();
}
}
impl Plot for Chart {
fn y_label(&mut self, label: &str) -> &mut Chart {
self.ylabel = Some(label.into());
self
}
fn lineplot(&mut self, shape: Shape) -> &mut Chart {
let x_scale = Scale::new(self.xmin..self.xmax, 0.0..self.width as f32);
let ys: Vec<_> = match shape {
Shape::Continuous(f) => {
(0..self.width)
.into_iter()
.filter_map(|i| {
let x = x_scale.inv_linear(i as f32);
let y = f(x);
if y.is_normal() {
Some(y)
} else {
None
}
})
.collect()
},
| Shape::Lines(dt)
| Shape::Steps(dt)
| Shape::Bars(dt) => {
dt.iter()
.filter_map(|(x, y)| {
if *x >= self.xmin && *x <= self.xmax {
Some(*y)
} else {
None
}
}).collect()
},
};
let ymax = *ys.iter().max_by( |x, y| x.partial_cmp(y).unwrap_or(cmp::Ordering::Equal) ).unwrap_or(&0.0);
let ymin = *ys.iter().min_by( |x, y| x.partial_cmp(y).unwrap_or(cmp::Ordering::Equal) ).unwrap_or(&0.0);
self.ymin = f32::min(self.ymin, ymin);
self.ymax = f32::max(self.ymax, ymax);
let y_scale = Scale::new(self.ymin..self.ymax, 0.0..self.height as f32);
self.vline(x_scale.linear(0.0) as u32);
self.hline(y_scale.linear(0.0) as u32);
let points: Vec<_> = match shape {
Shape::Continuous(f) => {
(0..self.width)
.into_iter()
.filter_map(|i| {
let x = x_scale.inv_linear(i as f32);
let y = f(x);
if y.is_normal() {
let j = y_scale.linear(y).round();
Some((i, self.height - j as u32))
} else {
None
}
})
.collect()
},
| Shape::Lines(dt)
| Shape::Steps(dt)
| Shape::Bars(dt) => {
dt
.into_iter()
.filter_map(|(x, y)| {
let i = x_scale.linear(*x).round() as u32;
let j = y_scale.linear(*y).round() as u32;
if i <= self.width && j <= self.height {
Some( (i, self.height - j) )
} else {
None
}
})
.collect()
},
};
for pair in points.windows(2) {
let (x1, y1) = pair[0];
let (x2, y2) = pair[1];
match shape {
Shape::Continuous(_) => {
self.canvas.line(x1, y1, x2, y2);
},
Shape::Lines(_) => {
self.canvas.line(x1, y1, x2, y2);
},
Shape::Steps(_) => {
self.canvas.line(x1, y2, x2, y2);
self.canvas.line(x1, y1, x1, y2);
},
Shape::Bars(_) => {
self.canvas.line(x1, y2, x2, y2);
self.canvas.line(x1, y1, x1, y2);
self.canvas.line(x1, self.height, x1, y1);
self.canvas.line(x2, self.height, x2, y2);
},
}
}
self
}
}