Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,30 @@ required-features = ["tool"]
travis-ci = { repository = "loony-bean/textplots-rs", branch = "master" }

[features]
default = ["std"]

tool = [
"meval",
"structopt",
]

std = [
"drawille",
"rgb"
]

[dependencies]
drawille = "0.3.0"
# THESE CRATES ARE ENABLED BY DEFAULT
drawille-nostd = "0.1.2"
num-traits = { version = "0.2.16", default-features = false, features = ["libm"] }
# THESE CRATES ARE ENABLED ONLY BY THE CORRESPONDING FEATURES
drawille = { version = "0.3.0", optional = true }
structopt = { version = "0.3", optional = true }
meval = { version = "0.2", optional = true }
rgb = "0.8.27"
rgb = { version = "0.8.27", optional = true }


[dev-dependencies]
ctrlc = "3"
console = "0.15.7"
chrono = "0.4.30"

174 changes: 138 additions & 36 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,35 @@
//!
//! <img src="https://github.com/loony-bean/textplots-rs/blob/master/doc/demo3.png?raw=true"/>

// If std feature isn't enabled, don't load standard library
#![cfg_attr(not(feature = "std"), no_std)]

pub mod scale;
pub mod utils;

use drawille::Canvas as BrailleCanvas;
// These imports are mutual for std and no_std
extern crate alloc;

use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::cmp;
use core::default::Default;
use core::f32;
use core::fmt::{Display, Formatter};
#[allow(unused_imports, clippy::single_component_path_imports)]
use drawille_nostd;
#[allow(unused_imports)]
use num_traits::real::Real;
use scale::Scale;

// These imports are for std-version only
#[cfg(feature = "std")]
use drawille::PixelColor;
#[cfg(feature = "std")]
use rgb::RGB8;
use scale::Scale;
use std::cmp;
use std::default::Default;
use std::f32;
use std::fmt::{Display, Formatter, Result};

/// How the chart will do the ranging on axes
#[derive(PartialEq)]
Expand All @@ -70,6 +88,18 @@ enum ChartRangeMethod {
FixedRange,
}

#[cfg(not(feature = "std"))]
type BrailleCanvas = drawille_nostd::Canvas;

#[cfg(not(feature = "std"))]
type Shapes<'a> = Vec<&'a Shape<'a>>;

#[cfg(feature = "std")]
type BrailleCanvas = drawille::Canvas;

#[cfg(feature = "std")]
type Shapes<'a> = Vec<(&'a Shape<'a>, Option<RGB8>)>;

/// Controls the drawing.
pub struct Chart<'a> {
/// Canvas width in points.
Expand All @@ -87,7 +117,7 @@ pub struct Chart<'a> {
/// The type of y axis ranging we'll do
y_ranging: ChartRangeMethod,
/// Collection of shapes to be presented on the canvas.
shapes: Vec<(&'a Shape<'a>, Option<RGB8>)>,
shapes: Shapes<'a>,
/// Underlying canvas object.
canvas: BrailleCanvas,
/// X-axis style.
Expand Down Expand Up @@ -121,6 +151,8 @@ pub trait Plot<'a> {
}

/// Provides an interface for drawing colored plots.
/// Available with `std` feature.
#[cfg(feature = "std")]
pub trait ColorPlot<'a> {
/// Draws a [line chart](https://en.wikipedia.org/wiki/Line_chart) of points connected by straight line segments using the specified color
fn linecolorplot(&'a mut self, shape: &'a Shape, color: RGB8) -> &'a mut Chart;
Expand Down Expand Up @@ -175,7 +207,7 @@ pub enum LabelFormat {
}

impl<'a> Display for Chart<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
// get frame and replace space with U+2800 (BRAILLE PATTERN BLANK)
let mut frame = self.canvas.frame().replace(' ', "\u{2800}");

Expand Down Expand Up @@ -268,7 +300,7 @@ impl<'a> Chart<'a> {
}

/// Displays bounding rect.
fn borders(&mut self) {
pub fn borders(&mut self) {
let w = self.width;
let h = self.height;

Expand Down Expand Up @@ -345,6 +377,8 @@ impl<'a> Chart<'a> {
}

/// Prints canvas content.
/// Available with `std` feature.
#[cfg(feature = "std")]
pub fn display(&mut self) {
self.axis();
self.figures();
Expand All @@ -353,6 +387,8 @@ impl<'a> Chart<'a> {
}

/// Prints canvas content with some additional visual elements (like borders).
/// Available with `std` feature.
#[cfg(feature = "std")]
pub fn nice(&mut self) {
self.borders();
self.display();
Expand Down Expand Up @@ -400,39 +436,47 @@ impl<'a> Chart<'a> {
}
}

// Shows figures.
/// Translates (x, y) points into screen coordinates
fn translate(&self, shape: &Shape, x_scale: &Scale, y_scale: &Scale) -> Vec<(u32, u32)> {
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(),
};
points
}

// Shows colored figures.
// Available with `std` feature.
#[cfg(feature = "std")]
pub fn figures(&mut self) {
for (shape, color) 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);

// translate (x, y) points into screen coordinates
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(),
};
let points = self.translate(shape, &x_scale, &y_scale);

// display segments
match shape {
Expand Down Expand Up @@ -496,6 +540,52 @@ impl<'a> Chart<'a> {
}
}

// Shows figures.
#[cfg(not(feature = "std"))]
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);

// translate (x, y) points into screen coordinates
let points = self.translate(shape, &x_scale, &y_scale);

// display segments
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);
}
}
}
}
}

/// Returns the frame.
pub fn frame(&self) -> String {
self.canvas.frame()
Expand Down Expand Up @@ -543,6 +633,7 @@ impl<'a> Chart<'a> {
}
}

#[cfg(feature = "std")]
impl<'a> ColorPlot<'a> for Chart<'a> {
fn linecolorplot(&'a mut self, shape: &'a Shape, color: RGB8) -> &'a mut Chart {
self.shapes.push((shape, Some(color)));
Expand All @@ -554,15 +645,26 @@ impl<'a> ColorPlot<'a> for Chart<'a> {
}

impl<'a> Plot<'a> for Chart<'a> {
#[cfg(feature = "std")]
fn lineplot(&'a mut self, shape: &'a Shape) -> &'a mut Chart {
self.shapes.push((shape, None));
if self.y_ranging == ChartRangeMethod::AutoRange {
self.rescale(shape);
}
self
}

#[cfg(not(feature = "std"))]
fn lineplot(&'a mut self, shape: &'a Shape) -> &'a mut Chart {
self.shapes.push(shape);
if self.y_ranging == ChartRangeMethod::AutoRange {
self.rescale(shape);
}
self
}
}

#[cfg(feature = "std")]
fn rgb_to_pixelcolor(rgb: &RGB8) -> PixelColor {
PixelColor::TrueColor {
r: rgb.r,
Expand Down
2 changes: 1 addition & 1 deletion src/scale.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Transformations between domain and range.

use std::ops::Range;
use core::ops::Range;

/// Holds mapping between domain and range of the function.
pub struct Scale {
Expand Down
3 changes: 3 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
//!
//! Merely a bunch of functions hanging around while the library API is taking shape.

use alloc::vec;
use alloc::vec::Vec;

/// Transforms points into frequency distribution (for using in histograms).
/// Values outside of [`min`, `max`] interval are ignored, and everything that
/// falls into the specified interval is grouped into `bins` number of buckets of equal width.
Expand Down