extern crate open;
use std::fs::File;
use std::io::Write;
use crate::Result;
use crate::err::PlotError;
use crate::style::PlotStyle;
use super::{
draw_line,
draw_text
};
pub struct Bar {
width: usize,
height: usize,
axis_pad: usize,
bar_pad: usize,
y_label: String,
y_limit: Option<f32>,
title: String,
anno_style: PlotStyle,
bar_labels: Vec<String>,
y_dataset: Vec<f32>,
plot_style: PlotStyle,
n_ticks: usize
}
impl Default for Bar {
fn default() -> Bar {
Bar {
width: 1000,
height: 750,
axis_pad: 50,
bar_pad: 10,
y_label: "".to_string(),
y_limit: None,
title: "".to_string(),
anno_style: PlotStyle::default(),
bar_labels: Vec::default(),
y_dataset: Vec::default(),
plot_style: PlotStyle::default(),
n_ticks: 11
}
}
}
impl Bar {
pub fn label_y(&mut self, new_label: &str) {
self.y_label = new_label.to_string();
}
pub fn title(&mut self, new_title: &str) {
self.title = new_title.to_string();
}
pub fn add_data(&mut self, bar_labels: &Vec<String>, new_data: &Vec<f32>, plot_style: &PlotStyle) -> Result<()> {
if bar_labels.len() != new_data.len() {
return Err(Box::new(PlotError::DataLengthError))
}
if new_data.into_iter()
.any(|&value| value < 0.0)
{
return Err(Box::new(PlotError::BoundsError))
}
self.bar_labels = bar_labels.clone();
self.y_dataset = new_data.clone();
self.plot_style = plot_style.clone();
Ok(())
}
pub fn set_ymax(&mut self, upper_lim: f32) {
self.y_limit = Some(upper_lim);
}
pub fn render(&self, file_name: &str) -> Result<()> {
let mut render_string: String = format!(r#"<!DOCTYPE svg><svg xmlns="http://www.w3.org/2000/svg" viewBox="-50 -50 {} {}" width="{}" height="{}">"#,
self.width + 50,
self.height + 50,
self.width,
self.height
);
draw_text(&mut render_string, self.width / 2, self.axis_pad / 2, 0, &self.title, &self.anno_style, "xx-large");
let y_data_max: f32 = (&self.y_dataset).into_iter()
.fold(f32::NEG_INFINITY, |left, &right| left.max(right));
let y_abs_min: f32 = 0.0;
let y_abs_max: f32 = self.y_limit.unwrap_or(y_data_max);
self.draw_axes(&mut render_string, y_abs_max);
render_string.push_str(&format!(r#"<svg width="{}" height="{}" x="{}" y="{}">"#, self.width - 2 * self.axis_pad, self.height - 2 * self.axis_pad, self.axis_pad, self.axis_pad));
let mapped_y: Vec<usize> = (0..self.y_dataset.len()).into_iter()
.map(|idx|
(self.height - self.axis_pad) - (self.axis_pad as f32 + (self.height - 2 * self.axis_pad) as f32 * (self.y_dataset[idx] - y_abs_min) / (y_abs_max - y_abs_min)) as usize
).collect::<Vec<usize>>();
let n_bars: usize = self.bar_labels.len();
let bar_width: usize = ((self.width - 2 * self.axis_pad) / (n_bars + 2)) - self.bar_pad;
for bar_idx in 0..n_bars {
let progression: f32 = (bar_idx + 1) as f32 / (n_bars + 1) as f32;
let bar_center_loc: usize = (progression * (self.width - 2 * self.axis_pad) as f32) as usize;
render_string.push_str(&format!(r#"<rect x="{}" y="{}" width="{}" height="{}" fill="{}" stroke="{}" stroke-width="{}"><title>{}</title></rect>"#,
bar_center_loc - (bar_width / 2),
mapped_y[bar_idx],
bar_width,
(self.height - self.axis_pad) - mapped_y[bar_idx],
self.plot_style.fill_color,
self.plot_style.stroke_color,
self.plot_style.stroke_width,
self.y_dataset[bar_idx]
));
}
render_string.push_str(&format!("</svg></svg>"));
{
let mut output_svg: File = File::create(format!("{}.svg", file_name))?;
output_svg.write_all(render_string.as_bytes())?;
}
open::that(format!("./{}.svg", file_name))?;
Ok(())
}
fn draw_axes(&self, mut render_string: &mut String, y_end: f32) {
let tick_r: usize = 6;
draw_line(&mut render_string, self.axis_pad, self.height - self.axis_pad, self.width - self.axis_pad, self.height - self.axis_pad, &self.anno_style);
draw_line(&mut render_string, self.axis_pad, self.height - self.axis_pad, self.axis_pad, self.axis_pad, &self.anno_style);
let y_label_y: usize = self.axis_pad + (self.height - 2 * self.axis_pad) / 2;
draw_text(&mut render_string, 10, y_label_y, 270, &self.y_label, &self.anno_style, "large");
let n_bars: usize = self.bar_labels.len();
for bar_idx in 0..n_bars {
let progression: f32 = (bar_idx + 1) as f32 / (n_bars + 1) as f32;
let x_tick_loc: usize = self.axis_pad + (progression * (self.width - 2 * self.axis_pad) as f32) as usize;
draw_line(&mut render_string, x_tick_loc, self.height - self.axis_pad + tick_r, x_tick_loc, self.height - self.axis_pad - tick_r, &self.anno_style);
draw_text(&mut render_string, x_tick_loc, self.height - self.axis_pad + 3 * tick_r, 0, &self.bar_labels[bar_idx], &self.anno_style, "medium");
}
for y_tick in 0..self.n_ticks {
let progression: f32 = y_tick as f32 / (self.n_ticks - 1) as f32;
let y_tick_loc: usize = self.height - (self.axis_pad + (progression * (self.height - 2 * self.axis_pad) as f32) as usize);
let y_tick_val: f32 = progression * y_end;
draw_line(&mut render_string, self.axis_pad - tick_r, y_tick_loc, self.axis_pad + tick_r, y_tick_loc, &self.anno_style);
draw_text(&mut render_string, self.axis_pad - 3 * tick_r, y_tick_loc, 270, &format!("{:.2}", y_tick_val), &self.anno_style, "medium");
}
}
}