Skip to content

Commit 6548495

Browse files
committed
Add support for grids
This support is basic so far, and only for continuous views for now. The user can choose how many grid points they wish to use for each axis (defaults to 3 each), and the colour of the grid lines (defaults to a grey colour).
1 parent 1231d83 commit 6548495

File tree

5 files changed

+131
-20
lines changed

5 files changed

+131
-20
lines changed

examples/with_grid.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
extern crate plotlib;
2+
3+
use plotlib::grid::Grid;
4+
use plotlib::style::Line;
5+
use plotlib::view::View;
6+
7+
fn main() {
8+
let l1 = plotlib::line::Line::new(&[(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)])
9+
.style(plotlib::line::Style::new().colour("burlywood"));
10+
let grid = Grid::new(3, 8);
11+
let mut v = plotlib::view::ContinuousView::new().add(&l1);
12+
v.add_grid(grid);
13+
plotlib::page::Page::single(&v)
14+
.save("line.svg")
15+
.expect("saving svg");
16+
}

src/grid.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
pub struct Grid {
2+
pub nx: u32,
3+
pub ny: u32,
4+
pub color: String,
5+
}
6+
7+
impl Default for Grid {
8+
fn default() -> Self {
9+
Grid::new(3, 3)
10+
}
11+
}
12+
13+
impl Grid {
14+
pub fn new(nx: u32, ny: u32) -> Grid {
15+
Grid {
16+
nx,
17+
ny,
18+
color: "darkgrey".to_owned(),
19+
}
20+
}
21+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ pub mod barchart;
8888
pub mod boxplot;
8989
mod errors;
9090
pub mod function;
91+
pub mod grid;
9192
pub mod histogram;
9293
pub mod line;
9394
pub mod scatter;

src/svg_render.rs

Lines changed: 65 additions & 20 deletions
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::Grid;
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,37 @@ 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: &Grid, face_width: f64, face_height: f64) -> node::element::Group {
452+
let (xmin, xmax) = (0f64, face_width);
453+
let (ymin, ymax) = (0f64, face_height);
454+
455+
let x_step = (xmax - xmin) / f64::from(grid.nx);
456+
let y_step = (ymax - ymin) / f64::from(grid.ny);
457+
458+
let mut lines = node::element::Group::new();
459+
460+
for iy in 0..=grid.ny {
461+
let y = f64::from(iy) * y_step + ymin;
462+
let line = horizontal_line(-y, 0.0, face_width, grid.color.as_str());
463+
lines = lines.add(line);
464+
}
465+
466+
for ix in 0..=grid.nx {
467+
let x = f64::from(ix) * x_step + xmin;
468+
let line = vertical_line(x, 0.0, -face_height, grid.color.as_str());
469+
lines = lines.add(line);
470+
}
471+
472+
lines
473+
}
474+
430475
#[cfg(test)]
431476
mod tests {
432477
use super::*;

src/view.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ use svg::Node;
1414

1515
use crate::axis;
1616
use crate::errors::Result;
17+
use crate::grid::Grid;
1718
use crate::representation::{CategoricalRepresentation, ContinuousRepresentation};
1819
use crate::svg_render;
1920
use crate::text_render;
2021

2122
pub trait View {
2223
fn to_svg(&self, face_width: f64, face_height: f64) -> Result<svg::node::element::Group>;
2324
fn to_text(&self, face_width: u32, face_height: u32) -> Result<String>;
25+
fn add_grid(&mut self, grid: Grid);
26+
fn grid<'a>(&'a self) -> &'a Option<Grid>;
2427
}
2528

2629
/// Standard 1-dimensional view with a continuous x-axis
@@ -31,6 +34,7 @@ pub struct ContinuousView<'a> {
3134
y_range: Option<axis::Range>,
3235
x_label: Option<String>,
3336
y_label: Option<String>,
37+
grid: Option<Grid>,
3438
}
3539

3640
impl<'a> ContinuousView<'a> {
@@ -44,6 +48,7 @@ impl<'a> ContinuousView<'a> {
4448
y_range: None,
4549
x_label: None,
4650
y_label: None,
51+
grid: None,
4752
}
4853
}
4954

@@ -164,6 +169,11 @@ impl<'a> View for ContinuousView<'a> {
164169
// Add in the axes
165170
view_group.append(svg_render::draw_x_axis(&x_axis, face_width));
166171
view_group.append(svg_render::draw_y_axis(&y_axis, face_height));
172+
173+
if let Some(grid) = &self.grid {
174+
view_group.append(svg_render::draw_grid(grid, face_width, face_height));
175+
}
176+
167177
Ok(view_group)
168178
}
169179

@@ -212,6 +222,14 @@ impl<'a> View for ContinuousView<'a> {
212222

213223
Ok(view_string)
214224
}
225+
226+
fn add_grid(&mut self, grid: Grid) {
227+
self.grid = Some(grid)
228+
}
229+
230+
fn grid(&self) -> &Option<Grid> {
231+
&self.grid
232+
}
215233
}
216234

217235
/// A view with categorical entries along the x-axis and continuous values along the y-axis
@@ -222,6 +240,7 @@ pub struct CategoricalView<'a> {
222240
y_range: Option<axis::Range>,
223241
x_label: Option<String>,
224242
y_label: Option<String>,
243+
grid: Option<Grid>,
225244
}
226245

227246
impl<'a> CategoricalView<'a> {
@@ -235,6 +254,7 @@ impl<'a> CategoricalView<'a> {
235254
y_range: None,
236255
x_label: None,
237256
y_label: None,
257+
grid: None,
238258
}
239259
}
240260

@@ -355,6 +375,14 @@ impl<'a> View for CategoricalView<'a> {
355375
fn to_text(&self, _face_width: u32, _face_height: u32) -> Result<String> {
356376
Ok("".into())
357377
}
378+
379+
fn add_grid(&mut self, grid: Grid) {
380+
self.grid = Some(grid);
381+
}
382+
383+
fn grid(&self) -> &Option<Grid> {
384+
&self.grid
385+
}
358386
}
359387

360388
/*pub struct AnyView<'a> {

0 commit comments

Comments
 (0)