Skip to content

Commit 2c0e516

Browse files
authored
Merge pull request #23 from mindriot101/add-grid
Add support for grids
2 parents 6f396cb + a3460bc commit 2c0e516

File tree

7 files changed

+247
-24
lines changed

7 files changed

+247
-24
lines changed

CHANGELOG

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Introduce categorical representation, views and axes.
99
- Add ability to set dimensions of plot (PR #8)
1010
- Added ability to display a histogram as densities
11+
- Add ability to display grids (PR #23)
1112

1213
### Changed
1314
- Change `create_axes`, `save`, `to_svg` and `to_text` to return `Result` indicating an error.

examples/with_grid.rs

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
extern crate plotlib;
2+
3+
use plotlib::grid::Grid;
4+
use plotlib::style::BarChart;
5+
use plotlib::style::Line;
6+
use plotlib::view::View;
7+
8+
fn main() {
9+
render_line_chart("line_with_grid.svg");
10+
render_barchart("barchart_with_grid.svg");
11+
}
12+
13+
fn render_line_chart<S>(filename: S)
14+
where
15+
S: AsRef<str>,
16+
{
17+
let l1 = plotlib::line::Line::new(&[(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)])
18+
.style(plotlib::line::Style::new().colour("burlywood"));
19+
let mut v = plotlib::view::ContinuousView::new().add(&l1);
20+
v.add_grid(Grid::new(3, 8));
21+
plotlib::page::Page::single(&v)
22+
.save(filename.as_ref())
23+
.expect("saving svg");
24+
}
25+
26+
fn render_barchart<S>(filename: S)
27+
where
28+
S: AsRef<str>,
29+
{
30+
let b1 = plotlib::barchart::BarChart::new(5.3).label("1");
31+
let b2 = plotlib::barchart::BarChart::new(2.6)
32+
.label("2")
33+
.style(plotlib::barchart::Style::new().fill("darkolivegreen"));
34+
let mut v = plotlib::view::CategoricalView::new()
35+
.add(&b1)
36+
.add(&b2)
37+
.x_label("Experiment");
38+
v.add_grid(Grid::new(3, 8));
39+
plotlib::page::Page::single(&v)
40+
.save(filename.as_ref())
41+
.expect("saving svg");
42+
}

src/grid.rs

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#![deny(missing_docs)]
2+
3+
//! Configure a grid on a plot.
4+
//!
5+
//! Grids allow for easier estimating of data values. This module allows the configuration of grids
6+
//! on plots.
7+
//!
8+
//! Grids are created by creating a `Grid` definition, and adding it to a plot:
9+
//!
10+
//! The grid lines for `plotlib` are rendered
11+
//! _underneath_ the data so as to not detract from the data.
12+
//!
13+
//! # Examples
14+
//!
15+
//! ```rust
16+
//! # use plotlib::view::ContinuousView;
17+
//! use plotlib::grid::Grid;
18+
//! # use plotlib::style::Line;
19+
//! # use plotlib::view::View;
20+
//!
21+
//! # let l1 = plotlib::line::Line::new(&[(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)])
22+
//! # .style(plotlib::line::Style::new().colour("burlywood"));
23+
//! // let l1 = Line::new() ...
24+
//! let mut v = ContinuousView::new().add(&l1);
25+
//!
26+
//! // 3 vertical lines and 8 horizontal lines
27+
//! v.add_grid(Grid::new(3, 8));
28+
//!
29+
//! // Render plot
30+
//! ```
31+
32+
// Internal type representing the logic of when do we render only horizontal lines, and when do we
33+
// render a full grid
34+
pub(crate) enum GridType<'a> {
35+
HorizontalOnly(&'a Grid),
36+
Both(&'a Grid),
37+
}
38+
39+
/// Configuration for the grid on a plot
40+
///
41+
/// Supports changing the number of grid lines for the x and y dimensions.
42+
/// **Note:** for categorical plots, only horizontal lines will be shown.
43+
pub struct Grid {
44+
/// Number of vertical grid lines (defaults to 3)
45+
pub nx: u32,
46+
/// Number of horizontal grid lines (defaults to 3)
47+
pub ny: u32,
48+
/// Color of the grid lines (defaults to "darkgrey")
49+
pub color: String,
50+
}
51+
52+
impl Default for Grid {
53+
fn default() -> Self {
54+
Grid::new(3, 3)
55+
}
56+
}
57+
58+
impl Grid {
59+
/// Create a new grid with `nx` vertical and `ny` horizontal grid lines
60+
///
61+
/// The default colour is "darkgrey".
62+
pub fn new(nx: u32, ny: u32) -> Grid {
63+
Grid {
64+
nx,
65+
ny,
66+
color: "darkgrey".to_owned(),
67+
}
68+
}
69+
}

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub mod barchart;
8484
pub mod boxplot;
8585
mod errors;
8686
pub mod function;
87+
pub mod grid;
8788
pub mod histogram;
8889
pub mod line;
8990
pub mod scatter;

src/page.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,23 @@ pub struct Page<'a> {
2525

2626
impl<'a> Page<'a> {
2727
/**
28-
Creates a plot containing a single view
28+
Creates an empty page container for plots to be added to
2929
*/
30-
pub fn single(view: &'a View) -> Self {
30+
pub fn empty() -> Self {
3131
Page {
32-
views: vec![view],
33-
num_views: 1,
32+
views: Vec::new(),
33+
num_views: 0,
3434
dimensions: (600, 400),
3535
}
3636
}
3737

38+
/**
39+
Creates a plot containing a single view
40+
*/
41+
pub fn single(view: &'a View) -> Self {
42+
Page::empty().add_plot(view)
43+
}
44+
3845
/// Set the dimensions of the plot.
3946
pub fn dimensions(mut self, x: u32, y: u32) -> Self {
4047
self.dimensions = (x, y);

src/svg_render.rs

+82-20
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use svg::node;
44
use svg::Node;
55

66
use crate::axis;
7+
use crate::grid::GridType;
78
use crate::histogram;
89
use crate::style;
910
use crate::utils;
@@ -14,14 +15,34 @@ fn value_to_face_offset(value: f64, axis: &axis::ContinuousAxis, face_size: f64)
1415
(face_size * (value - axis.min())) / range
1516
}
1617

18+
fn vertical_line<S>(xpos: f64, ymin: f64, ymax: f64, color: S) -> node::element::Line
19+
where
20+
S: AsRef<str>,
21+
{
22+
node::element::Line::new()
23+
.set("x1", xpos)
24+
.set("x2", xpos)
25+
.set("y1", ymin)
26+
.set("y2", ymax)
27+
.set("stroke", color.as_ref())
28+
.set("stroke-width", 1)
29+
}
30+
31+
fn horizontal_line<S>(ypos: f64, xmin: f64, xmax: f64, color: S) -> node::element::Line
32+
where
33+
S: AsRef<str>,
34+
{
35+
node::element::Line::new()
36+
.set("x1", xmin)
37+
.set("x2", xmax)
38+
.set("y1", ypos)
39+
.set("y2", ypos)
40+
.set("stroke", color.as_ref())
41+
.set("stroke-width", 1)
42+
}
43+
1744
pub fn draw_x_axis(a: &axis::ContinuousAxis, face_width: f64) -> node::element::Group {
18-
let axis_line = node::element::Line::new()
19-
.set("x1", 0)
20-
.set("y1", 0)
21-
.set("x2", face_width)
22-
.set("y2", 0)
23-
.set("stroke", "black")
24-
.set("stroke-width", 1);
45+
let axis_line = horizontal_line(0.0, 0.0, face_width, "black");
2546

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

6384
pub fn draw_y_axis(a: &axis::ContinuousAxis, face_height: f64) -> node::element::Group {
64-
let axis_line = node::element::Line::new()
65-
.set("x1", 0)
66-
.set("y1", 0)
67-
.set("x2", 0)
68-
.set("y2", -face_height)
69-
.set("stroke", "black")
70-
.set("stroke-0", 1);
85+
let axis_line = vertical_line(0.0, 0.0, -face_height, "black");
7186

7287
let mut ticks = node::element::Group::new();
7388
let mut labels = node::element::Group::new();
@@ -101,7 +116,8 @@ pub fn draw_y_axis(a: &axis::ContinuousAxis, face_height: f64) -> node::element:
101116
.set(
102117
"transform",
103118
format!("rotate(-90 {} {})", -30, -(face_height / 2.)),
104-
).add(node::Text::new(a.get_label()));
119+
)
120+
.add(node::Text::new(a.get_label()));
105121

106122
node::element::Group::new()
107123
.add(ticks)
@@ -214,7 +230,8 @@ where
214230
.set(
215231
"stroke",
216232
style.get_colour().clone().unwrap_or_else(|| "".into()),
217-
).set("stroke-width", 2)
233+
)
234+
.set("stroke-width", 2)
218235
.set("d", path),
219236
);
220237
}
@@ -253,7 +270,8 @@ where
253270
.get_fill()
254271
.clone()
255272
.unwrap_or_else(|| "burlywood".into()),
256-
).set("stroke", "black");
273+
)
274+
.set("stroke", "black");
257275
group.append(rect);
258276
}
259277

@@ -298,7 +316,8 @@ where
298316
.set(
299317
"stroke",
300318
style.get_colour().clone().unwrap_or_else(|| "".into()),
301-
).set("stroke-width", style.get_width().clone().unwrap_or(2.))
319+
)
320+
.set("stroke-width", style.get_width().clone().unwrap_or(2.))
302321
.set("d", path),
303322
);
304323

@@ -344,7 +363,8 @@ where
344363
.get_fill()
345364
.clone()
346365
.unwrap_or_else(|| "burlywood".into()),
347-
).set("stroke", "black"),
366+
)
367+
.set("stroke", "black"),
348368
);
349369

350370
let mid_line = -value_to_face_offset(median, y_axis, face_height);
@@ -421,12 +441,54 @@ where
421441
.get_fill()
422442
.clone()
423443
.unwrap_or_else(|| "burlywood".into()),
424-
).set("stroke", "black"),
444+
)
445+
.set("stroke", "black"),
425446
);
426447

427448
group
428449
}
429450

451+
pub(crate) fn draw_grid(grid: GridType, face_width: f64, face_height: f64) -> node::element::Group {
452+
match grid {
453+
GridType::HorizontalOnly(grid) => {
454+
let (ymin, ymax) = (0f64, face_height);
455+
let y_step = (ymax - ymin) / f64::from(grid.ny);
456+
let mut lines = node::element::Group::new();
457+
458+
for iy in 0..=grid.ny {
459+
let y = f64::from(iy) * y_step + ymin;
460+
let line = horizontal_line(-y, 0.0, face_width, grid.color.as_str());
461+
lines = lines.add(line);
462+
}
463+
464+
lines
465+
}
466+
GridType::Both(grid) => {
467+
let (xmin, xmax) = (0f64, face_width);
468+
let (ymin, ymax) = (0f64, face_height);
469+
470+
let x_step = (xmax - xmin) / f64::from(grid.nx);
471+
let y_step = (ymax - ymin) / f64::from(grid.ny);
472+
473+
let mut lines = node::element::Group::new();
474+
475+
for iy in 0..=grid.ny {
476+
let y = f64::from(iy) * y_step + ymin;
477+
let line = horizontal_line(-y, 0.0, face_width, grid.color.as_str());
478+
lines = lines.add(line);
479+
}
480+
481+
for ix in 0..=grid.nx {
482+
let x = f64::from(ix) * x_step + xmin;
483+
let line = vertical_line(x, 0.0, -face_height, grid.color.as_str());
484+
lines = lines.add(line);
485+
}
486+
487+
lines
488+
}
489+
}
490+
}
491+
430492
#[cfg(test)]
431493
mod tests {
432494
use super::*;

0 commit comments

Comments
 (0)