1use 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#[derive(Debug, Clone)]
28pub struct Plot {
29 pub data: Vec<(f64, f64)>,
30 pub line_style: Option<LineStyle>,
32 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 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 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}