1
use std;
2

3
use svg::node;
4
use svg::Node;
5

6
use crate::axis;
7
use crate::grid::GridType;
8
use crate::repr;
9
use crate::style;
10
use crate::utils;
11
use crate::utils::PairWise;
12

13 1
fn value_to_face_offset(value: f64, axis: &axis::ContinuousAxis, face_size: f64) -> f64 {
14 1
    let range = axis.max() - axis.min();
15 1
    (face_size * (value - axis.min())) / range
16
}
17

18 1
fn vertical_line<S>(xpos: f64, ymin: f64, ymax: f64, color: S) -> node::element::Line
19
where
20
    S: AsRef<str>,
21
{
22 1
    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 1
fn horizontal_line<S>(ypos: f64, xmin: f64, xmax: f64, color: S) -> node::element::Line
32
where
33
    S: AsRef<str>,
34
{
35 1
    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

44 1
pub fn draw_x_axis(a: &axis::ContinuousAxis, face_width: f64) -> node::element::Group {
45 1
    let axis_line = horizontal_line(0.0, 0.0, face_width, "black");
46

47 1
    let mut ticks = node::element::Group::new();
48 1
    let mut labels = node::element::Group::new();
49

50 1
    for &tick in a.ticks().iter() {
51 1
        let tick_pos = value_to_face_offset(tick, a, face_width);
52 1
        let tick_mark = node::element::Line::new()
53 1
            .set("x1", tick_pos)
54
            .set("y1", 0)
55 1
            .set("x2", tick_pos)
56
            .set("y2", 10)
57
            .set("stroke", "black")
58
            .set("stroke-width", 1);
59 1
        ticks.append(tick_mark);
60

61 1
        let tick_label = node::element::Text::new()
62 1
            .set("x", tick_pos)
63
            .set("y", 20)
64
            .set("text-anchor", "middle")
65 1
            .set("font-size", 12)
66 1
            .add(node::Text::new(tick.to_string()));
67 1
        labels.append(tick_label);
68
    }
69

70 1
    let label = node::element::Text::new()
71 1
        .set("x", face_width / 2.)
72
        .set("y", 30)
73
        .set("text-anchor", "middle")
74 1
        .set("font-size", 12)
75 1
        .add(node::Text::new(a.get_label()));
76

77 1
    node::element::Group::new()
78
        .add(ticks)
79
        .add(axis_line)
80
        .add(labels)
81 1
        .add(label)
82
}
83

84 1
pub fn draw_y_axis(a: &axis::ContinuousAxis, face_height: f64) -> node::element::Group {
85 1
    let axis_line = vertical_line(0.0, 0.0, -face_height, "black");
86

87 1
    let mut ticks = node::element::Group::new();
88 1
    let mut labels = node::element::Group::new();
89

90 1
    let y_tick_font_size = 12;
91

92 1
    for &tick in a.ticks().iter() {
93 1
        let tick_pos = value_to_face_offset(tick, a, face_height);
94 1
        let tick_mark = node::element::Line::new()
95
            .set("x1", 0)
96 1
            .set("y1", -tick_pos)
97
            .set("x2", -10)
98 1
            .set("y2", -tick_pos)
99
            .set("stroke", "black")
100
            .set("stroke-width", 1);
101 1
        ticks.append(tick_mark);
102

103 1
        let tick_label = node::element::Text::new()
104
            .set("x", -15)
105 1
            .set("y", -tick_pos)
106
            .set("text-anchor", "end")
107
            .set("dominant-baseline", "middle")
108 1
            .set("font-size", y_tick_font_size)
109 1
            .add(node::Text::new(tick.to_string()));
110 1
        labels.append(tick_label);
111
    }
112

113 1
    let max_tick_length = a
114
        .ticks()
115
        .iter()
116 1
        .map(|&t| t.to_string().len())
117
        .max()
118
        .expect("Could not calculate max tick length");
119

120 1
    let x_offset = -(y_tick_font_size * max_tick_length as i32);
121 1
    let y_label_offset = -(face_height / 2.);
122 1
    let y_label_font_size = 12;
123 1
    let label = node::element::Text::new()
124 1
        .set("x", x_offset)
125 1
        .set("y", y_label_offset - f64::from(y_label_font_size))
126
        .set("text-anchor", "middle")
127 1
        .set("font-size", y_label_font_size)
128
        .set(
129
            "transform",
130 1
            format!("rotate(-90 {} {})", x_offset, y_label_offset),
131
        )
132 1
        .add(node::Text::new(a.get_label()));
133

134 1
    node::element::Group::new()
135
        .add(ticks)
136
        .add(axis_line)
137
        .add(labels)
138 1
        .add(label)
139
}
140

141 0
pub fn draw_categorical_x_axis(a: &axis::CategoricalAxis, face_width: f64) -> node::element::Group {
142 0
    let axis_line = node::element::Line::new()
143
        .set("x1", 0)
144
        .set("y1", 0)
145 0
        .set("x2", face_width)
146
        .set("y2", 0)
147
        .set("stroke", "black")
148 0
        .set("stroke-width", 1);
149

150 0
    let mut ticks = node::element::Group::new();
151 0
    let mut labels = node::element::Group::new();
152

153 0
    let space_per_tick = face_width / a.ticks().len() as f64;
154

155 0
    for (i, tick) in a.ticks().iter().enumerate() {
156 0
        let tick_pos = (i as f64 * space_per_tick) + (0.5 * space_per_tick);
157 0
        let tick_mark = node::element::Line::new()
158 0
            .set("x1", tick_pos)
159
            .set("y1", 0)
160 0
            .set("x2", tick_pos)
161
            .set("y2", 10)
162
            .set("stroke", "black")
163
            .set("stroke-width", 1);
164 0
        ticks.append(tick_mark);
165

166 0
        let tick_label = node::element::Text::new()
167 0
            .set("x", tick_pos)
168
            .set("y", 20)
169
            .set("text-anchor", "middle")
170 0
            .set("font-size", 12)
171 0
            .add(node::Text::new(tick.to_owned()));
172 0
        labels.append(tick_label);
173
    }
174

175 0
    let label = node::element::Text::new()
176 0
        .set("x", face_width / 2.)
177
        .set("y", 30)
178
        .set("text-anchor", "middle")
179 0
        .set("font-size", 12)
180 0
        .add(node::Text::new(a.get_label()));
181

182 0
    node::element::Group::new()
183
        .add(ticks)
184
        .add(axis_line)
185
        .add(labels)
186 0
        .add(label)
187
}
188

189 1
pub fn draw_face_points(
190
    s: &[(f64, f64)],
191
    x_axis: &axis::ContinuousAxis,
192
    y_axis: &axis::ContinuousAxis,
193
    face_width: f64,
194
    face_height: f64,
195
    style: &style::PointStyle,
196
) -> node::element::Group {
197 1
    let mut group = node::element::Group::new();
198

199 1
    for &(x, y) in s {
200 1
        let x_pos = value_to_face_offset(x, x_axis, face_width);
201 1
        let y_pos = -value_to_face_offset(y, y_axis, face_height);
202 1
        let radius = f64::from(style.get_size());
203 1
        match style.get_marker() {
204 1
            style::PointMarker::Circle => {
205 0
                group.append(
206 0
                    node::element::Circle::new()
207 0
                        .set("cx", x_pos)
208 0
                        .set("cy", y_pos)
209 0
                        .set("r", radius)
210 0
                        .set("fill", style.get_colour()),
211
                );
212
            }
213
            style::PointMarker::Square => {
214 1
                group.append(
215 1
                    node::element::Rectangle::new()
216 1
                        .set("x", x_pos - radius)
217 1
                        .set("y", y_pos - radius)
218 1
                        .set("width", 2. * radius)
219 1
                        .set("height", 2. * radius)
220 1
                        .set("fill", style.get_colour()),
221
                );
222
            }
223
            style::PointMarker::Cross => {
224 0
                let path = node::element::path::Data::new()
225 0
                    .move_to((x_pos - radius, y_pos - radius))
226 0
                    .line_by((radius * 2., radius * 2.))
227 0
                    .move_by((-radius * 2., 0))
228 0
                    .line_by((radius * 2., -radius * 2.))
229 0
                    .close();
230 0
                group.append(
231 0
                    node::element::Path::new()
232 0
                        .set("fill", "none")
233 0
                        .set("stroke", style.get_colour())
234
                        .set("stroke-width", 2)
235 0
                        .set("d", path),
236
                );
237
            }
238
        };
239
    }
240

241 1
    group
242
}
243

244 0
pub fn draw_face_bars(
245
    h: &repr::Histogram,
246
    x_axis: &axis::ContinuousAxis,
247
    y_axis: &axis::ContinuousAxis,
248
    face_width: f64,
249
    face_height: f64,
250
    style: &style::BoxStyle,
251
) -> node::element::Group {
252 0
    let mut group = node::element::Group::new();
253

254 0
    for ((&l, &u), &count) in h.bin_bounds.pairwise().zip(h.get_values()) {
255 0
        let l_pos = value_to_face_offset(l, x_axis, face_width);
256 0
        let u_pos = value_to_face_offset(u, x_axis, face_width);
257 0
        let width = u_pos - l_pos;
258 0
        let count_scaled = value_to_face_offset(count, y_axis, face_height);
259 0
        let rect = node::element::Rectangle::new()
260 0
            .set("x", l_pos)
261 0
            .set("y", -count_scaled)
262 0
            .set("width", width)
263 0
            .set("height", count_scaled)
264 0
            .set("fill", style.get_fill())
265
            .set("stroke", "black");
266 0
        group.append(rect);
267
    }
268

269 0
    group
270
}
271

272 0
pub fn draw_face_line(
273
    s: &[(f64, f64)],
274
    x_axis: &axis::ContinuousAxis,
275
    y_axis: &axis::ContinuousAxis,
276
    face_width: f64,
277
    face_height: f64,
278
    style: &style::LineStyle,
279
) -> node::element::Group {
280 0
    let mut group = node::element::Group::new();
281

282 0
    let mut d: Vec<node::element::path::Command> = vec![];
283 0
    let &(first_x, first_y) = s.first().unwrap();
284 0
    let first_x_pos = value_to_face_offset(first_x, x_axis, face_width);
285 0
    let first_y_pos = -value_to_face_offset(first_y, y_axis, face_height);
286 0
    d.push(node::element::path::Command::Move(
287 0
        node::element::path::Position::Absolute,
288 0
        (first_x_pos, first_y_pos).into(),
289
    ));
290 0
    for &(x, y) in s {
291 0
        let x_pos = value_to_face_offset(x, x_axis, face_width);
292 0
        let y_pos = -value_to_face_offset(y, y_axis, face_height);
293 0
        d.push(node::element::path::Command::Line(
294 0
            node::element::path::Position::Absolute,
295 0
            (x_pos, y_pos).into(),
296
        ));
297
    }
298

299 0
    let path = node::element::path::Data::from(d);
300

301 0
    group.append(
302 0
        node::element::Path::new()
303 0
            .set("fill", "none")
304 0
            .set("stroke", style.get_colour())
305 0
            .set("stroke-width", style.get_width())
306
            .set(
307
                "stroke-linejoin",
308 0
                match style.get_linejoin() {
309 0
                    style::LineJoin::Miter => "miter",
310 0
                    style::LineJoin::Round => "round",
311
                },
312
            )
313 0
            .set("d", path),
314
    );
315

316 0
    group
317
}
318

319 0
pub fn draw_face_boxplot<L>(
320
    d: &[f64],
321
    label: &L,
322
    x_axis: &axis::CategoricalAxis,
323
    y_axis: &axis::ContinuousAxis,
324
    face_width: f64,
325
    face_height: f64,
326
    style: &style::BoxStyle,
327
) -> node::element::Group
328
where
329
    L: Into<String>,
330
    String: std::cmp::PartialEq<L>,
331
{
332 0
    let mut group = node::element::Group::new();
333

334 0
    let tick_index = x_axis.ticks().iter().position(|t| t == label).unwrap(); // TODO this should raise an error
335 0
    let space_per_tick = face_width / x_axis.ticks().len() as f64;
336 0
    let tick_pos = (tick_index as f64 * space_per_tick) + (0.5 * space_per_tick);
337

338 0
    let box_width = space_per_tick / 2.;
339

340 0
    let (q1, median, q3) = utils::quartiles(d);
341

342 0
    let box_start = -value_to_face_offset(q3, y_axis, face_height);
343 0
    let box_end = -value_to_face_offset(q1, y_axis, face_height);
344

345 0
    group.append(
346 0
        node::element::Rectangle::new()
347 0
            .set("x", tick_pos - (box_width / 2.))
348 0
            .set("y", box_start)
349 0
            .set("width", box_width)
350 0
            .set("height", box_end - box_start)
351 0
            .set("fill", style.get_fill())
352
            .set("stroke", "black"),
353
    );
354

355 0
    let mid_line = -value_to_face_offset(median, y_axis, face_height);
356

357 0
    group.append(
358 0
        node::element::Line::new()
359 0
            .set("x1", tick_pos - (box_width / 2.))
360 0
            .set("y1", mid_line)
361 0
            .set("x2", tick_pos + (box_width / 2.))
362 0
            .set("y2", mid_line)
363
            .set("stroke", "black"),
364
    );
365

366 0
    let (min, max) = utils::range(d);
367

368 0
    let whisker_bottom = -value_to_face_offset(min, y_axis, face_height);
369 0
    let whisker_top = -value_to_face_offset(max, y_axis, face_height);
370

371 0
    group.append(
372 0
        node::element::Line::new()
373 0
            .set("x1", tick_pos)
374 0
            .set("y1", whisker_bottom)
375 0
            .set("x2", tick_pos)
376 0
            .set("y2", box_end)
377
            .set("stroke", "black"),
378
    );
379

380 0
    group.append(
381 0
        node::element::Line::new()
382 0
            .set("x1", tick_pos)
383 0
            .set("y1", whisker_top)
384 0
            .set("x2", tick_pos)
385 0
            .set("y2", box_start)
386
            .set("stroke", "black"),
387
    );
388

389 0
    group
390
}
391

392 0
pub fn draw_face_barchart<L>(
393
    d: f64,
394
    label: &L,
395
    x_axis: &axis::CategoricalAxis,
396
    y_axis: &axis::ContinuousAxis,
397
    face_width: f64,
398
    face_height: f64,
399
    style: &style::BoxStyle,
400
) -> node::element::Group
401
where
402
    L: Into<String>,
403
    String: std::cmp::PartialEq<L>,
404
{
405 0
    let mut group = node::element::Group::new();
406

407 0
    let tick_index = x_axis.ticks().iter().position(|t| t == label).unwrap(); // TODO this should raise an error
408 0
    let space_per_tick = face_width / x_axis.ticks().len() as f64;
409 0
    let tick_pos = (tick_index as f64 * space_per_tick) + (0.5 * space_per_tick);
410

411 0
    let box_width = space_per_tick / 2.;
412

413 0
    let box_start = -value_to_face_offset(d, y_axis, face_height);
414 0
    let box_end = -value_to_face_offset(0.0, y_axis, face_height);
415

416 0
    group.append(
417 0
        node::element::Rectangle::new()
418 0
            .set("x", tick_pos - (box_width / 2.))
419 0
            .set("y", box_start)
420 0
            .set("width", box_width)
421 0
            .set("height", box_end - box_start)
422 0
            .set("fill", style.get_fill())
423
            .set("stroke", "black"),
424
    );
425

426 0
    group
427
}
428

429 0
pub(crate) fn draw_grid(grid: GridType, face_width: f64, face_height: f64) -> node::element::Group {
430 0
    match grid {
431 0
        GridType::HorizontalOnly(grid) => {
432 0
            let (ymin, ymax) = (0f64, face_height);
433 0
            let y_step = (ymax - ymin) / f64::from(grid.ny);
434 0
            let mut lines = node::element::Group::new();
435

436 0
            for iy in 0..=grid.ny {
437 0
                let y = f64::from(iy) * y_step + ymin;
438 0
                let line = horizontal_line(-y, 0.0, face_width, grid.color.as_str());
439 0
                lines = lines.add(line);
440
            }
441

442 0
            lines
443
        }
444 0
        GridType::Both(grid) => {
445 0
            let (xmin, xmax) = (0f64, face_width);
446 0
            let (ymin, ymax) = (0f64, face_height);
447

448 0
            let x_step = (xmax - xmin) / f64::from(grid.nx);
449 0
            let y_step = (ymax - ymin) / f64::from(grid.ny);
450

451 0
            let mut lines = node::element::Group::new();
452

453 0
            for iy in 0..=grid.ny {
454 0
                let y = f64::from(iy) * y_step + ymin;
455 0
                let line = horizontal_line(-y, 0.0, face_width, grid.color.as_str());
456 0
                lines = lines.add(line);
457
            }
458

459 0
            for ix in 0..=grid.nx {
460 0
                let x = f64::from(ix) * x_step + xmin;
461 0
                let line = vertical_line(x, 0.0, -face_height, grid.color.as_str());
462 0
                lines = lines.add(line);
463
            }
464

465 0
            lines
466
        }
467
    }
468
}
469

470
#[cfg(test)]
471
mod tests {
472
    use super::*;
473

474
    #[test]
475 1
    fn test_value_to_face_offset() {
476 1
        let axis = axis::ContinuousAxis::new(-2., 5., 6);
477 1
        assert_eq!(value_to_face_offset(-2.0, &axis, 14.0), 0.0);
478 1
        assert_eq!(value_to_face_offset(5.0, &axis, 14.0), 14.0);
479 1
        assert_eq!(value_to_face_offset(0.0, &axis, 14.0), 4.0);
480 1
        assert_eq!(value_to_face_offset(-4.0, &axis, 14.0), -4.0);
481 1
        assert_eq!(value_to_face_offset(7.0, &axis, 14.0), 18.0);
482
    }
483
}

Read our documentation on viewing source code .

Loading