1use 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#[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 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 pub fn x_max_ticks(mut self, val: usize) -> Self {
59 self.x_max_ticks = val;
60 self
61 }
62 pub fn y_max_ticks(mut self, val: usize) -> Self {
64 self.y_max_ticks = val;
65 self
66 }
67
68 pub fn add<R: ContinuousRepresentation + 'static>(mut self, repr: R) -> Self {
70 self.representations.push(Box::new(repr));
71 self
72 }
73
74 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 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 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 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 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 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 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 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#[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 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 pub fn add<R: CategoricalRepresentation + 'static>(mut self, repr: R) -> Self {
285 self.representations.push(Box::new(repr));
286 self
287 }
288
289 pub fn x_ticks(mut self, ticks: &[String]) -> Self {
293 self.x_range = Some(ticks.into());
294 self
295 }
296
297 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 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 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 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 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