Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to create y axis tick labels #53

Merged
merged 5 commits into from
Jan 18, 2024
Merged
Changes from 4 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
86 changes: 86 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ pub struct Chart<'a> {
x_label_format: LabelFormat,
/// Y-axis label format.
y_label_format: LabelFormat,
/// Y-axis tick label density
y_tick_labels: TickDisplay,
loony-bean marked this conversation as resolved.
Show resolved Hide resolved
}

/// Specifies different kinds of plotted data.
Expand Down Expand Up @@ -143,6 +145,15 @@ pub trait LabelBuilder<'a> {
fn y_label_format(&'a mut self, format: LabelFormat) -> &'a mut Chart<'a>;
}

/// Provides an interface for adding tick labels to the y-axis
pub trait TickDisplayBuilder<'a> {
// Horizontal labels don't allow for support of x-axis tick labels
/// Specifies the tick label density of y-axis.
/// YAxisTickLabels::Sparse will change the canvas height to the nearest multiple of 16
/// YAxisTickLabels::Dense will change the canvas height to the nearest multiple of 8
fn y_tick_display(&'a mut self, density: TickDisplay) -> &'a mut Chart<'a>;
}

impl<'a> Default for Chart<'a> {
fn default() -> Self {
Self::new(120, 60, -10.0, 10.0)
Expand Down Expand Up @@ -174,6 +185,26 @@ pub enum LabelFormat {
Custom(Box<dyn Fn(f32) -> String>),
}

/// Specifies density of labels on the Y axis between ymin and ymax.
/// Default value is `YAxisTickFormat::None`.
pub enum TickDisplay {
/// Tick labels are not displayed.
None,
/// Tick labels are sparsely shown (every 4th row)
Sparse,
/// Tick labels are densely shown (every 2nd row)
Dense,
}
impl TickDisplay {
fn get_row_spacing(&self) -> u32 {
match self {
TickDisplay::None => u32::MAX,
TickDisplay::Sparse => 4,
TickDisplay::Dense => 2,
}
}
}

impl<'a> Display for Chart<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
// get frame and replace space with U+2800 (BRAILLE PATTERN BLANK)
Expand All @@ -185,6 +216,31 @@ impl<'a> Display for Chart<'a> {

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

// Display y-axis ticks if requested
match self.y_tick_labels {
TickDisplay::None => {}
TickDisplay::Sparse | TickDisplay::Dense => {
let row_spacing: u32 = self.y_tick_labels.get_row_spacing(); // Rows between ticks
let num_steps: u32 = (self.height / 4) / row_spacing; // 4 dots per row of text
let step_size = (self.ymax - self.ymin) / (num_steps) as f32;
for i in 1..(num_steps) {
if let Some(index) = frame
.match_indices('\n')
.collect::<Vec<(usize, &str)>>()
.get((i * row_spacing) as usize)
{
frame.insert_str(
index.0,
&format!(
" {0}",
self.format_y_axis_tick(self.ymax - (step_size * i as f32))
),
);
}
}
}
}

frame.push_str(&format!(
" {0}\n{1: <width$}{2}\n",
self.format_y_axis_tick(self.ymin),
Expand Down Expand Up @@ -226,6 +282,7 @@ impl<'a> Chart<'a> {
y_style: LineStyle::Dotted,
x_label_format: LabelFormat::Value,
y_label_format: LabelFormat::Value,
y_tick_labels: TickDisplay::None,
}
}

Expand Down Expand Up @@ -264,6 +321,7 @@ impl<'a> Chart<'a> {
y_style: LineStyle::Dotted,
x_label_format: LabelFormat::Value,
y_label_format: LabelFormat::Value,
y_tick_labels: TickDisplay::None,
}
}

Expand Down Expand Up @@ -596,3 +654,31 @@ impl<'a> LabelBuilder<'a> for Chart<'a> {
self
}
}

impl<'a> TickDisplayBuilder<'a> for Chart<'a> {
/// Specifies the density of y-axis tick labels
fn y_tick_display(&mut self, format: TickDisplay) -> &mut Self {
// Round the height to the nearest multiple using integer division
match format {
TickDisplay::None => {}
TickDisplay::Sparse => {
// Round to the nearest 16
self.height = if self.height < 16 {
16
} else {
((self.height + 8) / 16) * 16
}
}
TickDisplay::Dense => {
// Round to the nearest 8
self.height = if self.height < 8 {
8
} else {
((self.height + 4) / 8) * 8
}
}
}
self.y_tick_labels = format;
self
}
}
Loading