plotlib/
page.rs

1/*!
2The `page` module provides structures for laying out and rendering multiple views.
3*/
4
5use std::ffi::OsStr;
6use std::path::Path;
7
8use svg;
9use svg::Document;
10use svg::Node;
11
12use crate::errors::Result;
13use crate::view::View;
14
15use failure::ResultExt;
16
17/**
18A single page page laying out the views in a grid
19*/
20pub struct Page<'a> {
21    views: Vec<&'a dyn View>,
22    num_views: u32,
23    dimensions: (u32, u32),
24}
25
26impl<'a> Page<'a> {
27    /**
28    Creates an empty page container for plots to be added to
29    */
30    pub fn empty() -> Self {
31        Page {
32            views: Vec::new(),
33            num_views: 0,
34            dimensions: (600, 400),
35        }
36    }
37
38    /**
39    Creates a plot containing a single view
40    */
41    pub fn single(view: &'a dyn View) -> Self {
42        Page::empty().add_plot(view)
43    }
44
45    /// Set the dimensions of the plot.
46    pub fn dimensions(mut self, x: u32, y: u32) -> Self {
47        self.dimensions = (x, y);
48        self
49    }
50
51    /// Add a view to the plot
52    pub fn add_plot(mut self, view: &'a dyn View) -> Self {
53        self.views.push(view);
54        self.num_views += 1;
55        self
56    }
57
58    /**
59    Render the plot to an svg document
60    */
61    pub fn to_svg(&self) -> Result<svg::Document> {
62        let (width, height) = self.dimensions;
63        let mut document = Document::new().set("viewBox", (0, 0, width, height));
64
65        let x_margin = 120; // should actually depend on y-axis label font size
66        let y_margin = 60;
67        let x_offset = 0.6 * f64::from(x_margin);
68        let y_offset = 0.6 * f64::from(y_margin);
69
70        // TODO put multiple views in correct places
71        for &view in &self.views {
72            let view_group = view
73                .to_svg(f64::from(width - x_margin), f64::from(height - y_margin))?
74                .set(
75                    "transform",
76                    format!("translate({}, {})", x_offset, f64::from(height) - y_offset),
77                );
78            document.append(view_group);
79        }
80        Ok(document)
81    }
82
83    /**
84    Render the plot to an `String`
85    */
86    pub fn to_text(&self) -> Result<String> {
87        let (width, height) = self.dimensions;
88        // TODO compose multiple views into a page
89        let view = self.views[0];
90        view.to_text(width, height)
91    }
92
93    /**
94    Save the plot to a file.
95
96    The type of file will be based on the file extension.
97    */
98
99    pub fn save<P>(&self, path: P) -> Result<()>
100    where
101        P: AsRef<Path>,
102    {
103        match path.as_ref().extension().and_then(OsStr::to_str) {
104            Some("svg") => svg::save(path, &self.to_svg()?)
105                .context("saving svg")
106                .map_err(From::from),
107            _ => Ok(()),
108        }
109    }
110}