1
/*!
2
*Views* are plotlib's way of combining multiple representations into a single plot.
3
It is analogous to a *subplot* in other plotting libraries.
4

5
In essence, a view is a collection of representations along with some metadata describing the
6
extent to plot and information about the axes. It knows how to render itself.
7
*/
8

9
use crate::errors;
10
use std;
11
use std::f64;
12

13
use svg::Node;
14

15
use crate::axis;
16
use crate::errors::Result;
17
use crate::grid::{Grid, GridType};
18
use crate::repr::{CategoricalRepresentation, ContinuousRepresentation};
19
use crate::svg_render;
20
use crate::text_render;
21
use crate::utils;
22

23
pub 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)]
32
pub 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

43
impl ContinuousView {
44
    /// Create an empty view
45 1
    pub fn new() -> ContinuousView {
46
        ContinuousView {
47 1
            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 0
    pub fn x_max_ticks(mut self, val: usize) -> Self {
59 0
        self.x_max_ticks = val;
60 0
        self
61
    }
62
    /// Set the maximum number of ticks along the y axis.
63 0
    pub fn y_max_ticks(mut self, val: usize) -> Self {
64 0
        self.y_max_ticks = val;
65 0
        self
66
    }
67

68
    /// Add a representation to the view
69 1
    pub fn add<R: ContinuousRepresentation + 'static>(mut self, repr: R) -> Self {
70 1
        self.representations.push(Box::new(repr));
71 1
        self
72
    }
73

74
    /// Set the x range for the view
75 1
    pub fn x_range(mut self, min: f64, max: f64) -> Self {
76 1
        self.x_range = Some(axis::Range::new(min, max));
77 1
        self
78
    }
79

80
    /// Set the y range for the view
81 1
    pub fn y_range(mut self, min: f64, max: f64) -> Self {
82 1
        self.y_range = Some(axis::Range::new(min, max));
83 1
        self
84
    }
85

86
    /// Set the label for the x-axis
87 1
    pub fn x_label<T>(mut self, value: T) -> Self
88
    where
89
        T: Into<String>,
90
    {
91 1
        self.x_label = Some(value.into());
92 1
        self
93
    }
94

95
    /// Set the label for the y-axis
96 1
    pub fn y_label<T>(mut self, value: T) -> Self
97
    where
98
        T: Into<String>,
99
    {
100 1
        self.y_label = Some(value.into());
101 1
        self
102
    }
103

104 1
    fn default_x_range(&self) -> axis::Range {
105 1
        let mut x_min = f64::INFINITY;
106 1
        let mut x_max = f64::NEG_INFINITY;
107 1
        for repr in &self.representations {
108 1
            let (this_x_min, this_x_max) = repr.range(0);
109 1
            x_min = x_min.min(this_x_min);
110 1
            x_max = x_max.max(this_x_max);
111
        }
112 1
        let (x_min, x_max) = utils::pad_range_to_zero(x_min, x_max);
113 1
        axis::Range::new(x_min, x_max)
114
    }
115

116 1
    fn default_y_range(&self) -> axis::Range {
117 1
        let mut y_min = f64::INFINITY;
118 1
        let mut y_max = f64::NEG_INFINITY;
119 1
        for repr in &self.representations {
120 1
            let (this_y_min, this_y_max) = repr.range(1);
121 1
            y_min = y_min.min(this_y_min);
122 1
            y_max = y_max.max(this_y_max);
123
        }
124 1
        let (y_min, y_max) = utils::pad_range_to_zero(y_min, y_max);
125 1
        axis::Range::new(y_min, y_max)
126
    }
127

128 1
    fn create_axes(&self) -> Result<(axis::ContinuousAxis, axis::ContinuousAxis)> {
129 1
        let default_x_range = self.default_x_range();
130 1
        let x_range = self.x_range.as_ref().unwrap_or(&default_x_range);
131 1
        if !x_range.is_valid() {
132 0
            return Err(errors::Error::InvalidRange {
133 0
                name: String::from("x"),
134 0
                lower: x_range.lower,
135 0
                upper: x_range.upper,
136
            });
137
        }
138

139 1
        let default_y_range = self.default_y_range();
140 1
        let y_range = self.y_range.as_ref().unwrap_or(&default_y_range);
141 1
        if !y_range.is_valid() {
142 0
            return Err(errors::Error::InvalidRange {
143 0
                name: String::from("y"),
144 0
                lower: x_range.lower,
145 0
                upper: x_range.upper,
146
            });
147
        }
148

149 1
        let x_label: String = self.x_label.clone().unwrap_or_else(|| "".to_string());
150 1
        let y_label: String = self.y_label.clone().unwrap_or_else(|| "".to_string());
151

152 1
        let x_axis = axis::ContinuousAxis::new(x_range.lower, x_range.upper, self.x_max_ticks)
153 1
            .label(x_label);
154 1
        let y_axis = axis::ContinuousAxis::new(y_range.lower, y_range.upper, self.y_max_ticks)
155 1
            .label(y_label);
156

157 1
        Ok((x_axis, y_axis))
158
    }
159
}
160

161
impl View for ContinuousView {
162
    /**
163
    Create an SVG rendering of the view
164
    */
165 1
    fn to_svg(&self, face_width: f64, face_height: f64) -> Result<svg::node::element::Group> {
166 1
        let mut view_group = svg::node::element::Group::new();
167

168 1
        let (x_axis, y_axis) = self.create_axes()?;
169

170 1
        let (legend_x, mut legend_y) = (face_width - 100., -face_height);
171 1
        if let Some(grid) = &self.grid {
172 0
            view_group.append(svg_render::draw_grid(
173 0
                GridType::Both(grid),
174 0
                face_width,
175 0
                face_height,
176
            ));
177
        }
178

179
        // Then, based on those ranges, draw each repr as an SVG
180 1
        for repr in &self.representations {
181 1
            let repr_group = repr.to_svg(&x_axis, &y_axis, face_width, face_height);
182 1
            view_group.append(repr_group);
183

184 1
            if let Some(legend_group) = repr.legend_svg() {
185 0
                view_group.append(legend_group.set(
186 0
                    "transform",
187 0
                    format!("translate({}, {})", legend_x, legend_y),
188
                ));
189 0
                legend_y += 18.;
190
            }
191
        }
192

193
        // Add in the axes
194 1
        view_group.append(svg_render::draw_x_axis(&x_axis, face_width));
195 1
        view_group.append(svg_render::draw_y_axis(&y_axis, face_height));
196

197 1
        Ok(view_group)
198
    }
199

200
    /**
201
    Create a text rendering of the view
202
    */
203 0
    fn to_text(&self, face_width: u32, face_height: u32) -> Result<String> {
204 0
        let (x_axis, y_axis) = self.create_axes()?;
205

206 0
        let (y_axis_string, longest_y_label_width) =
207 0
            text_render::render_y_axis_strings(&y_axis, face_height);
208

209 0
        let (x_axis_string, start_offset) = text_render::render_x_axis_strings(&x_axis, face_width);
210

211 0
        let left_gutter_width = std::cmp::max(
212 0
            longest_y_label_width as i32 + 3,
213 0
            start_offset.wrapping_neg(),
214 0
        ) as u32;
215

216 0
        let view_width = face_width + 1 + left_gutter_width + 1;
217 0
        let view_height = face_height + 4;
218

219 0
        let blank: Vec<String> = (0..view_height)
220 0
            .map(|_| (0..view_width).map(|_| ' ').collect())
221
            .collect();
222 0
        let mut view_string = blank.join("\n");
223

224 0
        for repr in &self.representations {
225 0
            let face_string = repr.to_text(&x_axis, &y_axis, face_width, face_height);
226 0
            view_string =
227 0
                text_render::overlay(&view_string, &face_string, left_gutter_width as i32 + 1, 0);
228
        }
229

230
        let view_string = text_render::overlay(
231 0
            &view_string,
232 0
            &y_axis_string,
233 0
            left_gutter_width as i32 - 2 - longest_y_label_width,
234
            0,
235
        );
236
        let view_string = text_render::overlay(
237 0
            &view_string,
238 0
            &x_axis_string,
239 0
            left_gutter_width as i32,
240 0
            face_height as i32,
241
        );
242

243 0
        Ok(view_string)
244
    }
245

246 0
    fn add_grid(&mut self, grid: Grid) {
247 0
        self.grid = Some(grid)
248
    }
249

250 0
    fn grid(&self) -> &Option<Grid> {
251 0
        &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)]
257
pub 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

266
impl CategoricalView {
267
    /**
268
    Create an empty view
269
    */
270 0
    pub fn new() -> CategoricalView {
271
        CategoricalView {
272 0
            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 0
    pub fn add<R: CategoricalRepresentation + 'static>(mut self, repr: R) -> Self {
285 0
        self.representations.push(Box::new(repr));
286 0
        self
287
    }
288

289
    /**
290
    Set the x range for the view
291
    */
292 0
    pub fn x_ticks(mut self, ticks: &[String]) -> Self {
293 0
        self.x_range = Some(ticks.into());
294 0
        self
295
    }
296

297
    /**
298
    Set the y range for the view
299
    */
300 0
    pub fn y_range(mut self, min: f64, max: f64) -> Self {
301 0
        self.y_range = Some(axis::Range::new(min, max));
302 0
        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 0
        self.x_label = Some(value.into());
313 0
        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 0
        self.y_label = Some(value.into());
324 0
        self
325
    }
326

327 0
    fn default_x_ticks(&self) -> Vec<String> {
328 0
        let mut v = vec![];
329 0
        for repr in &self.representations {
330 0
            for l in repr.ticks() {
331 0
                if !v.contains(&l) {
332 0
                    v.push(l.clone());
333
                }
334
            }
335
        }
336 0
        v
337
    }
338

339 0
    fn default_y_range(&self) -> axis::Range {
340 0
        let mut y_min = f64::INFINITY;
341 0
        let mut y_max = f64::NEG_INFINITY;
342 0
        for repr in &self.representations {
343 0
            let (this_y_min, this_y_max) = repr.range();
344 0
            y_min = y_min.min(this_y_min);
345 0
            y_max = y_max.max(this_y_max);
346
        }
347 0
        let buffer = (y_max - y_min) / 10.;
348 0
        let y_min = if y_min == 0.0 { y_min } else { y_min - buffer };
349 0
        let y_max = y_max + buffer;
350 0
        axis::Range::new(y_min, y_max)
351
    }
352

353 0
    fn create_axes(&self) -> Result<(axis::CategoricalAxis, axis::ContinuousAxis)> {
354 0
        let default_x_ticks = self.default_x_ticks();
355 0
        let x_range = self.x_range.as_ref().unwrap_or(&default_x_ticks);
356

357 0
        let default_y_range = self.default_y_range();
358 0
        let y_range = self.y_range.as_ref().unwrap_or(&default_y_range);
359

360 0
        if !y_range.is_valid() {
361 0
            return Err(errors::Error::InvalidRange {
362 0
                name: String::from("y"),
363 0
                lower: y_range.lower,
364 0
                upper: y_range.upper,
365
            });
366
        }
367

368 0
        let default_x_label = "".to_string();
369 0
        let x_label: String = self.x_label.clone().unwrap_or(default_x_label);
370

371 0
        let default_y_label = "".to_string();
372 0
        let y_label: String = self.y_label.clone().unwrap_or(default_y_label);
373

374 0
        let x_axis = axis::CategoricalAxis::new(x_range).label(x_label);
375 0
        let y_axis = axis::ContinuousAxis::new(y_range.lower, y_range.upper, 6).label(y_label);
376

377 0
        Ok((x_axis, y_axis))
378
    }
379
}
380

381
impl View for CategoricalView {
382 0
    fn to_svg(&self, face_width: f64, face_height: f64) -> Result<svg::node::element::Group> {
383 0
        let mut view_group = svg::node::element::Group::new();
384

385 0
        let (x_axis, y_axis) = self.create_axes()?;
386

387 0
        if let Some(grid) = &self.grid {
388 0
            view_group.append(svg_render::draw_grid(
389 0
                GridType::HorizontalOnly(grid),
390 0
                face_width,
391 0
                face_height,
392
            ));
393
        }
394

395
        // Then, based on those ranges, draw each repr as an SVG
396 0
        for repr in &self.representations {
397 0
            let repr_group = repr.to_svg(&x_axis, &y_axis, face_width, face_height);
398 0
            view_group.append(repr_group);
399
        }
400

401
        // Add in the axes
402 0
        view_group.append(svg_render::draw_categorical_x_axis(&x_axis, face_width));
403 0
        view_group.append(svg_render::draw_y_axis(&y_axis, face_height));
404

405 0
        Ok(view_group)
406
    }
407

408 0
    fn to_text(&self, _face_width: u32, _face_height: u32) -> Result<String> {
409 0
        Ok("".into())
410
    }
411

412 0
    fn add_grid(&mut self, grid: Grid) {
413 0
        self.grid = Some(grid);
414
    }
415

416 0
    fn grid(&self) -> &Option<Grid> {
417 0
        &self.grid
418
    }
419
}
420

421
/*pub struct AnyView<'a> {
422
    representations: Vec<&'a Representation>,
423
    axes: Vec<>,
424
    x_range: Option<axis::Range>,
425
    y_range: Option<axis::Range>,
426
    x_label: Option<String>,
427
    y_label: Option<String>,
428
}*/

Read our documentation on viewing source code .

Loading