Skip to content

Commit

Permalink
Merge pull request #47 from uandere/master
Browse files Browse the repository at this point in the history
Added axis labels formatting; Height and width limits changed; Axis are now rendered before the graph to prevent artifacts on colored diagrams;
  • Loading branch information
loony-bean committed Sep 11, 2023
2 parents 374c677 + 37cb61a commit a0ce929
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 23 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ rgb = "0.8.27"
[dev-dependencies]
ctrlc = "3"
console = "0.15.7"
chrono = "0.4.30"

17 changes: 17 additions & 0 deletions examples/label.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use chrono::{NaiveDate, Duration};
use textplots::{Chart, Shape, LabelBuilder, LabelFormat, ColorPlot};

fn main() {
// Specify how labels are displayed.
let start = NaiveDate::from_ymd_opt(2023, 6, 1).unwrap();

let end = NaiveDate::from_ymd_opt(2023, 9, 1).unwrap();

println!("My step count over 3 months: ");
Chart::new_with_y_range(200, 50, 0.0, (end - start).num_days() as f32, 0.0, 25_000.0)
.linecolorplot(&Shape::Continuous(Box::new(|x| 1000.0 * (5.0 * (0.5 * x).sin() + 0.05 * x) + 9000.0)), rgb::RGB { r: 10, g: 100, b: 200 })
.x_label_format(LabelFormat::Custom(Box::new(move |val| {format!("{}", start + Duration::days(val as i64))})))
.y_label_format(LabelFormat::Value)
.display();

}
110 changes: 88 additions & 22 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,14 @@ pub struct Chart<'a> {
shapes: Vec<(&'a Shape<'a>, Option<RGB8>)>,
/// Underlying canvas object.
canvas: BrailleCanvas,
/// x-axis style
/// X-axis style.
x_style: LineStyle,
/// y-axis style
/// Y-axis style.
y_style: LineStyle,
/// X-axis label format.
x_label_format: LabelFormat,
/// Y-axis label format.
y_label_format: LabelFormat,
}

/// Specifies different kinds of plotted data.
Expand Down Expand Up @@ -125,10 +129,18 @@ pub trait ColorPlot<'a> {
/// Provides a builder interface for styling axis.
pub trait AxisBuilder<'a> {
/// Specifies the style of x-axis.
fn x_style(&'a mut self, style: LineStyle) -> &'a mut Chart<'a>;
fn x_axis_style(&'a mut self, style: LineStyle) -> &'a mut Chart<'a>;

/// Specifies the style of y-axis.
fn y_style(&'a mut self, style: LineStyle) -> &'a mut Chart<'a>;
fn y_axis_style(&'a mut self, style: LineStyle) -> &'a mut Chart<'a>;
}

pub trait LabelBuilder<'a> {
/// Specifies the label format of x-axis.
fn x_label_format(&'a mut self, format: LabelFormat) -> &'a mut Chart<'a>;

/// Specifies the label format of y-axis.
fn y_label_format(&'a mut self, format: LabelFormat) -> &'a mut Chart<'a>;
}

impl<'a> Default for Chart<'a> {
Expand All @@ -151,20 +163,34 @@ pub enum LineStyle {
Dashed,
}

/// Specifies label format.
/// Default value is `LabelFormat::Value`.
pub enum LabelFormat {
/// Label is not displayed.
None,
/// Label is shown as a value.
Value,
/// Label is shown as a custom string.
Custom(Box<dyn Fn(f32) -> String>),
}

impl<'a> Display for Chart<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {

// get frame and replace space with U+2800 (BRAILLE PATTERN BLANK)
let mut frame = self.canvas.frame().replace(' ', "\u{2800}");

if let Some(idx) = frame.find('\n') {
frame.insert_str(idx, &format!(" {0:.1}", self.ymax));
let xmin = self.format_x_axis_tick(self.xmin);
let xmax = self.format_x_axis_tick(self.xmax);

frame.insert_str(idx, &format!(" {0}", self.format_y_axis_tick(self.ymax)));

frame.push_str(&format!(
" {0:.1}\n{1: <width$.1}{2:.1}\n",
self.ymin,
self.xmin,
self.xmax,
width = (self.width as usize) / 2 - 3
" {0}\n{1: <width$}{2}\n",
self.format_y_axis_tick(self.ymin),
xmin,
xmax,
width = (self.width as usize) / 2 - xmax.len()
));
}
write!(f, "{}", frame)
Expand All @@ -176,14 +202,14 @@ impl<'a> Chart<'a> {
///
/// # Panics
///
/// Panics if `width` or `height` is less than 32.
/// Panics if `width` is less than 32 or `height` is less than 3.
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);
panic!("width should be at least 32");
}

if height < 32 {
panic!("height should be more then 32, {} is provided", height);
if height < 3 {
panic!("height should be at least 3");
}

Self {
Expand All @@ -198,14 +224,16 @@ impl<'a> Chart<'a> {
canvas: BrailleCanvas::new(width, height),
x_style: LineStyle::Dotted,
y_style: LineStyle::Dotted,
x_label_format: LabelFormat::Value,
y_label_format: LabelFormat::Value,
}
}

/// Creates a new `Chart` object with fixed y axis range.
///
/// # Panics
///
/// Panics if `width` or `height` is less than 32.
/// Panics if `width` is less than 32 or `height` is less than 3.
pub fn new_with_y_range(
width: u32,
height: u32,
Expand All @@ -215,11 +243,11 @@ impl<'a> Chart<'a> {
ymax: f32,
) -> Self {
if width < 32 {
panic!("width should be more then 32, {} is provided", width);
panic!("width should be at least 32");
}

if height < 32 {
panic!("height should be more then 32, {} is provided", height);
if height < 3 {
panic!("height should be at least 3");
}

Self {
Expand All @@ -234,6 +262,8 @@ impl<'a> Chart<'a> {
canvas: BrailleCanvas::new(width, height),
x_style: LineStyle::Dotted,
y_style: LineStyle::Dotted,
x_label_format: LabelFormat::Value,
y_label_format: LabelFormat::Value,
}
}

Expand Down Expand Up @@ -316,8 +346,8 @@ impl<'a> Chart<'a> {

/// Prints canvas content.
pub fn display(&mut self) {
self.figures();
self.axis();
self.figures();

println!("{}", self);
}
Expand Down Expand Up @@ -352,6 +382,28 @@ impl<'a> Chart<'a> {
}
}

/// Performs formatting of the x axis.
fn format_x_axis_tick(&self, value: f32) -> String {
match &self.x_label_format {
LabelFormat::None => "".to_owned(),
LabelFormat::Value => format!("{:.1}", value),
LabelFormat::Custom(f) => {
f(value)
},
}
}

/// Performs formatting of the y axis.
fn format_y_axis_tick(&self, value: f32) -> String {
match &self.y_label_format {
LabelFormat::None => "".to_owned(),
LabelFormat::Value => format!("{:.1}", value),
LabelFormat::Custom(f) => {
f(value)
},
}
}

// Shows figures.
pub fn figures(&mut self) {
for (shape, color) in &self.shapes {
Expand Down Expand Up @@ -524,13 +576,27 @@ fn rgb_to_pixelcolor(rgb: &RGB8) -> PixelColor {
}

impl<'a> AxisBuilder<'a> for Chart<'a> {
fn x_style(&'a mut self, style: LineStyle) -> &'a mut Chart {
fn x_axis_style(&'a mut self, style: LineStyle) -> &'a mut Chart {
self.x_style = style;
self
}

fn y_style(&'a mut self, style: LineStyle) -> &'a mut Chart {
fn y_axis_style(&'a mut self, style: LineStyle) -> &'a mut Chart {
self.y_style = style;
self
}
}

impl<'a> LabelBuilder<'a> for Chart<'a> {
/// Specifies a formater for the x-axis label.
fn x_label_format(&mut self, format: LabelFormat) -> &mut Self {
self.x_label_format = format;
self
}

/// Specifies a formater for the y-axis label.
fn y_label_format(&mut self, format: LabelFormat) -> &mut Self {
self.y_label_format = format;
self
}
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::process::exit;
use structopt::StructOpt;
use textplots::{Chart, Plot, Shape, AxisBuilder};
use textplots::{Chart, Plot, Shape};

#[derive(StructOpt)]
struct Opt {
Expand Down

0 comments on commit a0ce929

Please sign in to comment.