plotlib/repr/
plot.rs

1//! Plot line charts
2
3//! # Examples
4
5//! ```
6//! # use plotlib::repr::Plot;
7//! # use plotlib::view::ContinuousView;
8//! // y=x^2 between 0 and 10
9//! let l = Plot::new(vec![(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)]);
10//! let v = ContinuousView::new().add(l);
11//! ```
12
13use std::f64;
14
15use svg;
16use svg::node;
17use svg::Node;
18
19use crate::axis;
20use crate::repr::ContinuousRepresentation;
21use crate::style::*;
22use crate::svg_render;
23use crate::text_render;
24
25/// Representation of any plot with points in the XY plane, visualized as points and/or with lines
26/// in-between.
27#[derive(Debug, Clone)]
28pub struct Plot {
29    pub data: Vec<(f64, f64)>,
30    /// None if no lines should be displayed
31    pub line_style: Option<LineStyle>,
32    /// None if no points should be displayed
33    pub point_style: Option<PointStyle>,
34    pub legend: Option<String>,
35}
36
37impl Plot {
38    pub fn new(data: Vec<(f64, f64)>) -> Self {
39        Plot {
40            data,
41            line_style: None,
42            point_style: None,
43            legend: None,
44        }
45    }
46
47    pub fn from_function<F: Fn(f64) -> f64>(f: F, lower: f64, upper: f64) -> Self {
48        let sampling = (upper - lower) / 200.;
49        let samples = (0..)
50            .map(|x| lower + (f64::from(x) * sampling))
51            .take_while(|&x| x <= upper);
52        let values = samples.map(|s| (s, f(s))).collect();
53        Plot {
54            data: values,
55            line_style: None,
56            point_style: None,
57            legend: None,
58        }
59    }
60
61    pub fn line_style(mut self, other: LineStyle) -> Self {
62        if let Some(ref mut self_style) = self.line_style {
63            self_style.overlay(&other);
64        } else {
65            self.line_style = Some(other);
66        }
67        self
68    }
69    pub fn point_style(mut self, other: PointStyle) -> Self {
70        if let Some(ref mut self_style) = self.point_style {
71            self_style.overlay(&other);
72        } else {
73            self.point_style = Some(other);
74        }
75        self
76    }
77    pub fn legend(mut self, legend: String) -> Self {
78        self.legend = Some(legend);
79        self
80    }
81
82    fn x_range(&self) -> (f64, f64) {
83        let mut min = f64::INFINITY;
84        let mut max = f64::NEG_INFINITY;
85        for &(x, _) in &self.data {
86            min = min.min(x);
87            max = max.max(x);
88        }
89        (min, max)
90    }
91
92    fn y_range(&self) -> (f64, f64) {
93        let mut min = f64::INFINITY;
94        let mut max = f64::NEG_INFINITY;
95        for &(_, y) in &self.data {
96            min = min.min(y);
97            max = max.max(y);
98        }
99        (min, max)
100    }
101}
102
103impl ContinuousRepresentation for Plot {
104    fn range(&self, dim: u32) -> (f64, f64) {
105        match dim {
106            0 => self.x_range(),
107            1 => self.y_range(),
108            _ => panic!("Axis out of range"),
109        }
110    }
111
112    fn to_svg(
113        &self,
114        x_axis: &axis::ContinuousAxis,
115        y_axis: &axis::ContinuousAxis,
116        face_width: f64,
117        face_height: f64,
118    ) -> svg::node::element::Group {
119        let mut group = node::element::Group::new();
120        if let Some(ref line_style) = self.line_style {
121            group.append(svg_render::draw_face_line(
122                &self.data,
123                x_axis,
124                y_axis,
125                face_width,
126                face_height,
127                line_style,
128            ))
129        }
130        if let Some(ref point_style) = self.point_style {
131            group.append(svg_render::draw_face_points(
132                &self.data,
133                x_axis,
134                y_axis,
135                face_width,
136                face_height,
137                point_style,
138            ))
139        }
140        group
141    }
142    fn legend_svg(&self) -> Option<svg::node::element::Group> {
143        // TODO: add points
144        // TODO: can we use common functionality with svg_render?
145        self.legend.as_ref().map(|legend| {
146            let legend = legend.clone();
147
148            let mut group = node::element::Group::new();
149            const FONT_SIZE: f32 = 12.0;
150
151            // Draw legend text
152            let legend_text = node::element::Text::new()
153                .set("x", 0)
154                .set("y", 0)
155                .set("text-anchor", "start")
156                .set("font-size", FONT_SIZE)
157                .add(node::Text::new(legend));
158            group.append(legend_text);
159
160            if let Some(ref style) = self.line_style {
161                let line = node::element::Line::new()
162                    .set("x1", -10)
163                    .set("y1", -FONT_SIZE / 2. + 2.)
164                    .set("x2", -3)
165                    .set("y2", -FONT_SIZE / 2. + 2.)
166                    .set("stroke-width", style.get_width())
167                    .set("stroke", style.get_colour());
168                group.append(line);
169            }
170
171            group
172        })
173    }
174
175    fn to_text(
176        &self,
177        x_axis: &axis::ContinuousAxis,
178        y_axis: &axis::ContinuousAxis,
179        face_width: u32,
180        face_height: u32,
181    ) -> String {
182        let face_lines = if let Some(line_style) = &self.line_style {
183            unimplemented!("Text rendering does not yet support line plots")
184        } else {
185            text_render::empty_face(face_width, face_height)
186        };
187        let face_points = if let Some(point_style) = &self.point_style {
188            text_render::render_face_points(
189                &self.data,
190                x_axis,
191                y_axis,
192                face_width,
193                face_height,
194                &point_style,
195            )
196        } else {
197            text_render::empty_face(face_width, face_height)
198        };
199        text_render::overlay(&face_lines, &face_points, 0, 0)
200    }
201}