Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Introduce categorical representation, views and axes.
- Add ability to set dimensions of plot (PR #8)
- Added ability to display a histogram as densities
- Add ability to display grids (PR #23)

### Changed
- Change `create_axes`, `save`, `to_svg` and `to_text` to return `Result` indicating an error.
Expand Down
42 changes: 42 additions & 0 deletions examples/with_grid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
extern crate plotlib;

use plotlib::grid::Grid;
use plotlib::style::BarChart;
use plotlib::style::Line;
use plotlib::view::View;

fn main() {
render_line_chart("line_with_grid.svg");
render_barchart("barchart_with_grid.svg");
}

fn render_line_chart<S>(filename: S)
where
S: AsRef<str>,
{
let l1 = plotlib::line::Line::new(&[(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)])
.style(plotlib::line::Style::new().colour("burlywood"));
let mut v = plotlib::view::ContinuousView::new().add(&l1);
v.add_grid(Grid::new(3, 8));
plotlib::page::Page::single(&v)
.save(filename.as_ref())
.expect("saving svg");
}

fn render_barchart<S>(filename: S)
where
S: AsRef<str>,
{
let b1 = plotlib::barchart::BarChart::new(5.3).label("1");
let b2 = plotlib::barchart::BarChart::new(2.6)
.label("2")
.style(plotlib::barchart::Style::new().fill("darkolivegreen"));
let mut v = plotlib::view::CategoricalView::new()
.add(&b1)
.add(&b2)
.x_label("Experiment");
v.add_grid(Grid::new(3, 8));
plotlib::page::Page::single(&v)
.save(filename.as_ref())
.expect("saving svg");
}
69 changes: 69 additions & 0 deletions src/grid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#![deny(missing_docs)]

//! Configure a grid on a plot.
//!
//! Grids allow for easier estimating of data values. This module allows the configuration of grids
//! on plots.
//!
//! Grids are created by creating a `Grid` definition, and adding it to a plot:
//!
//! The grid lines for `plotlib` are rendered
//! _underneath_ the data so as to not detract from the data.
//!
//! # Examples
//!
//! ```rust
//! # use plotlib::view::ContinuousView;
//! use plotlib::grid::Grid;
//! # use plotlib::style::Line;
//! # use plotlib::view::View;
//!
//! # let l1 = plotlib::line::Line::new(&[(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)])
//! # .style(plotlib::line::Style::new().colour("burlywood"));
//! // let l1 = Line::new() ...
//! let mut v = ContinuousView::new().add(&l1);
//!
//! // 3 vertical lines and 8 horizontal lines
//! v.add_grid(Grid::new(3, 8));
//!
//! // Render plot
//! ```

// Internal type representing the logic of when do we render only horizontal lines, and when do we
// render a full grid
pub(crate) enum GridType<'a> {
HorizontalOnly(&'a Grid),
Both(&'a Grid),
}

/// Configuration for the grid on a plot
///
/// Supports changing the number of grid lines for the x and y dimensions.
/// **Note:** for categorical plots, only horizontal lines will be shown.
pub struct Grid {
/// Number of vertical grid lines (defaults to 3)
pub nx: u32,
/// Number of horizontal grid lines (defaults to 3)
pub ny: u32,
/// Color of the grid lines (defaults to "darkgrey")
pub color: String,
}

impl Default for Grid {
fn default() -> Self {
Grid::new(3, 3)
}
}

impl Grid {
/// Create a new grid with `nx` vertical and `ny` horizontal grid lines
///
/// The default colour is "darkgrey".
pub fn new(nx: u32, ny: u32) -> Grid {
Grid {
nx,
ny,
color: "darkgrey".to_owned(),
}
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ pub mod barchart;
pub mod boxplot;
mod errors;
pub mod function;
pub mod grid;
pub mod histogram;
pub mod line;
pub mod scatter;
Expand Down
15 changes: 11 additions & 4 deletions src/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,23 @@ pub struct Page<'a> {

impl<'a> Page<'a> {
/**
Creates a plot containing a single view
Creates an empty page container for plots to be added to
*/
pub fn single(view: &'a View) -> Self {
pub fn empty() -> Self {
Page {
views: vec![view],
num_views: 1,
views: Vec::new(),
num_views: 0,
dimensions: (600, 400),
}
}

/**
Creates a plot containing a single view
*/
pub fn single(view: &'a View) -> Self {
Page::empty().add_plot(view)
}

/// Set the dimensions of the plot.
pub fn dimensions(mut self, x: u32, y: u32) -> Self {
self.dimensions = (x, y);
Expand Down
102 changes: 82 additions & 20 deletions src/svg_render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use svg::node;
use svg::Node;

use crate::axis;
use crate::grid::GridType;
use crate::histogram;
use crate::style;
use crate::utils;
Expand All @@ -14,14 +15,34 @@ fn value_to_face_offset(value: f64, axis: &axis::ContinuousAxis, face_size: f64)
(face_size * (value - axis.min())) / range
}

fn vertical_line<S>(xpos: f64, ymin: f64, ymax: f64, color: S) -> node::element::Line
where
S: AsRef<str>,
{
node::element::Line::new()
.set("x1", xpos)
.set("x2", xpos)
.set("y1", ymin)
.set("y2", ymax)
.set("stroke", color.as_ref())
.set("stroke-width", 1)
}

fn horizontal_line<S>(ypos: f64, xmin: f64, xmax: f64, color: S) -> node::element::Line
where
S: AsRef<str>,
{
node::element::Line::new()
.set("x1", xmin)
.set("x2", xmax)
.set("y1", ypos)
.set("y2", ypos)
.set("stroke", color.as_ref())
.set("stroke-width", 1)
}

pub fn draw_x_axis(a: &axis::ContinuousAxis, face_width: f64) -> node::element::Group {
let axis_line = node::element::Line::new()
.set("x1", 0)
.set("y1", 0)
.set("x2", face_width)
.set("y2", 0)
.set("stroke", "black")
.set("stroke-width", 1);
let axis_line = horizontal_line(0.0, 0.0, face_width, "black");

let mut ticks = node::element::Group::new();
let mut labels = node::element::Group::new();
Expand Down Expand Up @@ -61,13 +82,7 @@ pub fn draw_x_axis(a: &axis::ContinuousAxis, face_width: f64) -> node::element::
}

pub fn draw_y_axis(a: &axis::ContinuousAxis, face_height: f64) -> node::element::Group {
let axis_line = node::element::Line::new()
.set("x1", 0)
.set("y1", 0)
.set("x2", 0)
.set("y2", -face_height)
.set("stroke", "black")
.set("stroke-0", 1);
let axis_line = vertical_line(0.0, 0.0, -face_height, "black");

let mut ticks = node::element::Group::new();
let mut labels = node::element::Group::new();
Expand Down Expand Up @@ -101,7 +116,8 @@ pub fn draw_y_axis(a: &axis::ContinuousAxis, face_height: f64) -> node::element:
.set(
"transform",
format!("rotate(-90 {} {})", -30, -(face_height / 2.)),
).add(node::Text::new(a.get_label()));
)
.add(node::Text::new(a.get_label()));

node::element::Group::new()
.add(ticks)
Expand Down Expand Up @@ -214,7 +230,8 @@ where
.set(
"stroke",
style.get_colour().clone().unwrap_or_else(|| "".into()),
).set("stroke-width", 2)
)
.set("stroke-width", 2)
.set("d", path),
);
}
Expand Down Expand Up @@ -253,7 +270,8 @@ where
.get_fill()
.clone()
.unwrap_or_else(|| "burlywood".into()),
).set("stroke", "black");
)
.set("stroke", "black");
group.append(rect);
}

Expand Down Expand Up @@ -298,7 +316,8 @@ where
.set(
"stroke",
style.get_colour().clone().unwrap_or_else(|| "".into()),
).set("stroke-width", style.get_width().clone().unwrap_or(2.))
)
.set("stroke-width", style.get_width().clone().unwrap_or(2.))
.set("d", path),
);

Expand Down Expand Up @@ -344,7 +363,8 @@ where
.get_fill()
.clone()
.unwrap_or_else(|| "burlywood".into()),
).set("stroke", "black"),
)
.set("stroke", "black"),
);

let mid_line = -value_to_face_offset(median, y_axis, face_height);
Expand Down Expand Up @@ -421,12 +441,54 @@ where
.get_fill()
.clone()
.unwrap_or_else(|| "burlywood".into()),
).set("stroke", "black"),
)
.set("stroke", "black"),
);

group
}

pub(crate) fn draw_grid(grid: GridType, face_width: f64, face_height: f64) -> node::element::Group {
match grid {
GridType::HorizontalOnly(grid) => {
let (ymin, ymax) = (0f64, face_height);
let y_step = (ymax - ymin) / f64::from(grid.ny);
let mut lines = node::element::Group::new();

for iy in 0..=grid.ny {
let y = f64::from(iy) * y_step + ymin;
let line = horizontal_line(-y, 0.0, face_width, grid.color.as_str());
lines = lines.add(line);
}

lines
}
GridType::Both(grid) => {
let (xmin, xmax) = (0f64, face_width);
let (ymin, ymax) = (0f64, face_height);

let x_step = (xmax - xmin) / f64::from(grid.nx);
let y_step = (ymax - ymin) / f64::from(grid.ny);

let mut lines = node::element::Group::new();

for iy in 0..=grid.ny {
let y = f64::from(iy) * y_step + ymin;
let line = horizontal_line(-y, 0.0, face_width, grid.color.as_str());
lines = lines.add(line);
}

for ix in 0..=grid.nx {
let x = f64::from(ix) * x_step + xmin;
let line = vertical_line(x, 0.0, -face_height, grid.color.as_str());
lines = lines.add(line);
}

lines
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading