pub mod scale;
pub mod utils;
use drawille::Canvas as BrailleCanvas;
use scale::Scale;
use std::cmp;
use std::default::Default;
use std::f32;
pub struct Chart<'a> {
width: u32,
height: u32,
xmin: f32,
xmax: f32,
ymin: f32,
ymax: f32,
shapes: Vec<&'a Shape<'a>>,
canvas: BrailleCanvas,
}
pub enum Shape<'a> {
Continuous(Box<dyn Fn(f32) -> f32 + 'a>),
Points(&'a [(f32, f32)]),
Lines(&'a [(f32, f32)]),
Steps(&'a [(f32, f32)]),
Bars(&'a [(f32, f32)]),
}
pub trait Plot<'a> {
fn lineplot(&'a mut self, shape: &'a Shape) -> &'a mut Chart;
}
impl<'a> Default for Chart<'a> {
fn default() -> Self {
Self::new(120, 60, -10.0, 10.0)
}
}
impl<'a> Chart<'a> {
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: f32::INFINITY,
ymax: f32::NEG_INFINITY,
width,
height,
shapes: Vec::new(),
canvas: BrailleCanvas::new(width, height),
}
}
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(&mut self) {
self.figures();
self.axis();
let frame = self.canvas.frame();
let rows = frame.split('\n').count();
for (i, row) in frame.split('\n').enumerate() {
if i == 0 {
println!("{0} {1:.1}", row, self.ymax);
} else if i == (rows - 1) {
println!("{0} {1:.1}", row, self.ymin);
} 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();
}
pub fn axis(&mut self) {
let x_scale = Scale::new(self.xmin..self.xmax, 0.0..self.width as f32);
let y_scale = Scale::new(self.ymin..self.ymax, 0.0..self.height as f32);
if self.xmin <= 0.0 && self.xmax >= 0.0 {
self.vline(x_scale.linear(0.0) as u32);
}
if self.ymin <= 0.0 && self.ymax >= 0.0 {
self.hline(y_scale.linear(0.0) as u32);
}
}
pub fn figures(&mut self) {
for shape in &self.shapes {
let x_scale = Scale::new(self.xmin..self.xmax, 0.0..self.width as f32);
let y_scale = Scale::new(self.ymin..self.ymax, 0.0..self.height as f32);
let points: Vec<_> = match shape {
Shape::Continuous(f) => (0..self.width)
.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::Points(dt) | Shape::Lines(dt) | Shape::Steps(dt) | Shape::Bars(dt) => dt
.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(),
};
match shape {
Shape::Continuous(_) | Shape::Lines(_) => {
for pair in points.windows(2) {
let (x1, y1) = pair[0];
let (x2, y2) = pair[1];
self.canvas.line(x1, y1, x2, y2);
}
}
Shape::Points(_) => {
for (x, y) in points {
self.canvas.set(x, y);
}
}
Shape::Steps(_) => {
for pair in points.windows(2) {
let (x1, y1) = pair[0];
let (x2, y2) = pair[1];
self.canvas.line(x1, y2, x2, y2);
self.canvas.line(x1, y1, x1, y2);
}
}
Shape::Bars(_) => {
for pair in points.windows(2) {
let (x1, y1) = pair[0];
let (x2, y2) = pair[1];
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);
}
}
}
}
}
pub fn frame(&self) -> String {
self.canvas.frame()
}
}
impl<'a> Plot<'a> for Chart<'a> {
fn lineplot(&'a mut self, shape: &'a Shape) -> &'a mut Chart {
self.shapes.push(shape);
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)
.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::Points(dt) | 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);
self
}
}