plotlib/
view.rs

1/*!
2*Views* are plotlib's way of combining multiple representations into a single plot.
3It is analogous to a *subplot* in other plotting libraries.
4
5In essence, a view is a collection of representations along with some metadata describing the
6extent to plot and information about the axes. It knows how to render itself.
7*/
8
9use std;
10use std::f64;
11
12use failure::format_err;
13use svg::Node;
14
15use crate::axis;
16use crate::errors::Result;
17use crate::grid::{Grid, GridType};
18use crate::repr::{CategoricalRepresentation, ContinuousRepresentation};
19use crate::svg_render;
20use crate::text_render;
21use crate::utils;
22
23pub trait View {
24    fn to_svg(&self, face_width: f64, face_height: f64) -> Result<svg::node::element::Group>;
25    fn to_text(&self, face_width: u32, face_height: u32) -> Result<String>;
26    fn add_grid(&mut self, grid: Grid);
27    fn grid(&self) -> &Option<Grid>;
28}
29
30/// Standard 1-dimensional view with a continuous x-axis
31#[derive(Default)]
32pub struct ContinuousView {
33    representations: Vec<Box<dyn ContinuousRepresentation>>,
34    x_range: Option<axis::Range>,
35    y_range: Option<axis::Range>,
36    x_max_ticks: usize,
37    y_max_ticks: usize,
38    x_label: Option<String>,
39    y_label: Option<String>,
40    grid: Option<Grid>,
41}
42
43impl ContinuousView {
44    /// Create an empty view
45    pub fn new() -> ContinuousView {
46        ContinuousView {
47            representations: vec![],
48            x_range: None,
49            y_range: None,
50            x_max_ticks: 6,
51            y_max_ticks: 6,
52            x_label: None,
53            y_label: None,
54            grid: None,
55        }
56    }
57    /// Set the maximum number of ticks along the x axis.
58    pub fn x_max_ticks(mut self, val: usize) -> Self {
59        self.x_max_ticks = val;
60        self
61    }
62    /// Set the maximum number of ticks along the y axis.
63    pub fn y_max_ticks(mut self, val: usize) -> Self {
64        self.y_max_ticks = val;
65        self
66    }
67
68    /// Add a representation to the view
69    pub fn add<R: ContinuousRepresentation + 'static>(mut self, repr: R) -> Self {
70        self.representations.push(Box::new(repr));
71        self
72    }
73
74    /// Set the x range for the view
75    pub fn x_range(mut self, min: f64, max: f64) -> Self {
76        self.x_range = Some(axis::Range::new(min, max));
77        self
78    }
79
80    /// Set the y range for the view
81    pub fn y_range(mut self, min: f64, max: f64) -> Self {
82        self.y_range = Some(axis::Range::new(min, max));
83        self
84    }
85
86    /// Set the label for the x-axis
87    pub fn x_label<T>(mut self, value: T) -> Self
88    where
89        T: Into<String>,
90    {
91        self.x_label = Some(value.into());
92        self
93    }
94
95    /// Set the label for the y-axis
96    pub fn y_label<T>(mut self, value: T) -> Self
97    where
98        T: Into<String>,
99    {
100        self.y_label = Some(value.into());
101        self
102    }
103
104    fn default_x_range(&self) -> axis::Range {
105        let mut x_min = f64::INFINITY;
106        let mut x_max = f64::NEG_INFINITY;
107        for repr in &self.representations {
108            let (this_x_min, this_x_max) = repr.range(0);
109            x_min = x_min.min(this_x_min);
110            x_max = x_max.max(this_x_max);
111        }
112        let (x_min, x_max) = utils::pad_range_to_zero(x_min, x_max);
113        axis::Range::new(x_min, x_max)
114    }
115
116    fn default_y_range(&self) -> axis::Range {
117        let mut y_min = f64::INFINITY;
118        let mut y_max = f64::NEG_INFINITY;
119        for repr in &self.representations {
120            let (this_y_min, this_y_max) = repr.range(1);
121            y_min = y_min.min(this_y_min);
122            y_max = y_max.max(this_y_max);
123        }
124        let (y_min, y_max) = utils::pad_range_to_zero(y_min, y_max);
125        axis::Range::new(y_min, y_max)
126    }
127
128    fn create_axes(&self) -> Result<(axis::ContinuousAxis, axis::ContinuousAxis)> {
129        let default_x_range = self.default_x_range();
130        let x_range = self.x_range.as_ref().unwrap_or(&default_x_range);
131        if !x_range.is_valid() {
132            return Err(format_err!(
133                "Invalid x_range: {} >= {}. Please specify the x_range manually.",
134                x_range.lower,
135                x_range.upper
136            ));
137        }
138
139        let default_y_range = self.default_y_range();
140        let y_range = self.y_range.as_ref().unwrap_or(&default_y_range);
141        if !y_range.is_valid() {
142            return Err(format_err!(
143                "Invalid y_range: {} >= {}. Please specify the y_range manually.",
144                y_range.lower,
145                y_range.upper
146            ));
147        }
148
149        let x_label: String = self.x_label.clone().unwrap_or_else(|| "".to_string());
150        let y_label: String = self.y_label.clone().unwrap_or_else(|| "".to_string());
151
152        let x_axis = axis::ContinuousAxis::new(x_range.lower, x_range.upper, self.x_max_ticks)
153            .label(x_label);
154        let y_axis = axis::ContinuousAxis::new(y_range.lower, y_range.upper, self.y_max_ticks)
155            .label(y_label);
156
157        Ok((x_axis, y_axis))
158    }
159}
160
161impl View for ContinuousView {
162    /**
163    Create an SVG rendering of the view
164    */
165    fn to_svg(&self, face_width: f64, face_height: f64) -> Result<svg::node::element::Group> {
166        let mut view_group = svg::node::element::Group::new();
167
168        let (x_axis, y_axis) = self.create_axes()?;
169
170        let (legend_x, mut legend_y) = (face_width - 100., -face_height);
171        if let Some(grid) = &self.grid {
172            view_group.append(svg_render::draw_grid(
173                GridType::Both(grid),
174                face_width,
175                face_height,
176            ));
177        }
178
179        // Then, based on those ranges, draw each repr as an SVG
180        for repr in &self.representations {
181            let repr_group = repr.to_svg(&x_axis, &y_axis, face_width, face_height);
182            view_group.append(repr_group);
183
184            if let Some(legend_group) = repr.legend_svg() {
185                view_group.append(legend_group.set(
186                    "transform",
187                    format!("translate({}, {})", legend_x, legend_y),
188                ));
189                legend_y += 18.;
190            }
191        }
192
193        // Add in the axes
194        view_group.append(svg_render::draw_x_axis(&x_axis, face_width));
195        view_group.append(svg_render::draw_y_axis(&y_axis, face_height));
196
197        Ok(view_group)
198    }
199
200    /**
201    Create a text rendering of the view
202    */
203    fn to_text(&self, face_width: u32, face_height: u32) -> Result<String> {
204        let (x_axis, y_axis) = self.create_axes()?;
205
206        let (y_axis_string, longest_y_label_width) =
207            text_render::render_y_axis_strings(&y_axis, face_height);
208
209        let (x_axis_string, start_offset) = text_render::render_x_axis_strings(&x_axis, face_width);
210
211        let left_gutter_width = std::cmp::max(
212            longest_y_label_width as i32 + 3,
213            start_offset.wrapping_neg(),
214        ) as u32;
215
216        let view_width = face_width + 1 + left_gutter_width + 1;
217        let view_height = face_height + 4;
218
219        let blank: Vec<String> = (0..view_height)
220            .map(|_| (0..view_width).map(|_| ' ').collect())
221            .collect();
222        let mut view_string = blank.join("\n");
223
224        for repr in &self.representations {
225            let face_string = repr.to_text(&x_axis, &y_axis, face_width, face_height);
226            view_string =
227                text_render::overlay(&view_string, &face_string, left_gutter_width as i32 + 1, 0);
228        }
229
230        let view_string = text_render::overlay(
231            &view_string,
232            &y_axis_string,
233            left_gutter_width as i32 - 2 - longest_y_label_width,
234            0,
235        );
236        let view_string = text_render::overlay(
237            &view_string,
238            &x_axis_string,
239            left_gutter_width as i32,
240            face_height as i32,
241        );
242
243        Ok(view_string)
244    }
245
246    fn add_grid(&mut self, grid: Grid) {
247        self.grid = Some(grid)
248    }
249
250    fn grid(&self) -> &Option<Grid> {
251        &self.grid
252    }
253}
254
255/// A view with categorical entries along the x-axis and continuous values along the y-axis
256#[derive(Default)]
257pub struct CategoricalView {
258    representations: Vec<Box<dyn CategoricalRepresentation>>,
259    x_range: Option<Vec<String>>,
260    y_range: Option<axis::Range>,
261    x_label: Option<String>,
262    y_label: Option<String>,
263    grid: Option<Grid>,
264}
265
266impl CategoricalView {
267    /**
268    Create an empty view
269    */
270    pub fn new() -> CategoricalView {
271        CategoricalView {
272            representations: vec![],
273            x_range: None,
274            y_range: None,
275            x_label: None,
276            y_label: None,
277            grid: None,
278        }
279    }
280
281    /**
282    Add a representation to the view
283    */
284    pub fn add<R: CategoricalRepresentation + 'static>(mut self, repr: R) -> Self {
285        self.representations.push(Box::new(repr));
286        self
287    }
288
289    /**
290    Set the x range for the view
291    */
292    pub fn x_ticks(mut self, ticks: &[String]) -> Self {
293        self.x_range = Some(ticks.into());
294        self
295    }
296
297    /**
298    Set the y range for the view
299    */
300    pub fn y_range(mut self, min: f64, max: f64) -> Self {
301        self.y_range = Some(axis::Range::new(min, max));
302        self
303    }
304
305    /**
306    Set the label for the x-axis
307    */
308    pub fn x_label<T>(mut self, value: T) -> Self
309    where
310        T: Into<String>,
311    {
312        self.x_label = Some(value.into());
313        self
314    }
315
316    /**
317    Set the label for the y-axis
318    */
319    pub fn y_label<T>(mut self, value: T) -> Self
320    where
321        T: Into<String>,
322    {
323        self.y_label = Some(value.into());
324        self
325    }
326
327    fn default_x_ticks(&self) -> Vec<String> {
328        let mut v = vec![];
329        for repr in &self.representations {
330            for l in repr.ticks() {
331                if !v.contains(&l) {
332                    v.push(l.clone());
333                }
334            }
335        }
336        v
337    }
338
339    fn default_y_range(&self) -> axis::Range {
340        let mut y_min = f64::INFINITY;
341        let mut y_max = f64::NEG_INFINITY;
342        for repr in &self.representations {
343            let (this_y_min, this_y_max) = repr.range();
344            y_min = y_min.min(this_y_min);
345            y_max = y_max.max(this_y_max);
346        }
347        let buffer = (y_max - y_min) / 10.;
348        let y_min = if y_min == 0.0 { y_min } else { y_min - buffer };
349        let y_max = y_max + buffer;
350        axis::Range::new(y_min, y_max)
351    }
352
353    fn create_axes(&self) -> Result<(axis::CategoricalAxis, axis::ContinuousAxis)> {
354        let default_x_ticks = self.default_x_ticks();
355        let x_range = self.x_range.as_ref().unwrap_or(&default_x_ticks);
356
357        let default_y_range = self.default_y_range();
358        let y_range = self.y_range.as_ref().unwrap_or(&default_y_range);
359
360        if !y_range.is_valid() {
361            return Err(format_err!("invalid y_range: {:?}", y_range));
362        }
363
364        let default_x_label = "".to_string();
365        let x_label: String = self.x_label.clone().unwrap_or(default_x_label);
366
367        let default_y_label = "".to_string();
368        let y_label: String = self.y_label.clone().unwrap_or(default_y_label);
369
370        let x_axis = axis::CategoricalAxis::new(x_range).label(x_label);
371        let y_axis = axis::ContinuousAxis::new(y_range.lower, y_range.upper, 6).label(y_label);
372
373        Ok((x_axis, y_axis))
374    }
375}
376
377impl View for CategoricalView {
378    fn to_svg(&self, face_width: f64, face_height: f64) -> Result<svg::node::element::Group> {
379        let mut view_group = svg::node::element::Group::new();
380
381        let (x_axis, y_axis) = self.create_axes()?;
382
383        if let Some(grid) = &self.grid {
384            view_group.append(svg_render::draw_grid(
385                GridType::HorizontalOnly(grid),
386                face_width,
387                face_height,
388            ));
389        }
390
391        // Then, based on those ranges, draw each repr as an SVG
392        for repr in &self.representations {
393            let repr_group = repr.to_svg(&x_axis, &y_axis, face_width, face_height);
394            view_group.append(repr_group);
395        }
396
397        // Add in the axes
398        view_group.append(svg_render::draw_categorical_x_axis(&x_axis, face_width));
399        view_group.append(svg_render::draw_y_axis(&y_axis, face_height));
400
401        Ok(view_group)
402    }
403
404    fn to_text(&self, _face_width: u32, _face_height: u32) -> Result<String> {
405        Ok("".into())
406    }
407
408    fn add_grid(&mut self, grid: Grid) {
409        self.grid = Some(grid);
410    }
411
412    fn grid(&self) -> &Option<Grid> {
413        &self.grid
414    }
415}
416
417/*pub struct AnyView<'a> {
418    representations: Vec<&'a Representation>,
419    axes: Vec<>,
420    x_range: Option<axis::Range>,
421    y_range: Option<axis::Range>,
422    x_label: Option<String>,
423    y_label: Option<String>,
424}*/