milliams / plotlib
 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 13 ```use std::f64; ``` 14 15 ```use svg; ``` 16 ```use svg::node; ``` 17 ```use svg::Node; ``` 18 19 ```use crate::axis; ``` 20 ```use crate::repr::ContinuousRepresentation; ``` 21 ```use crate::style::*; ``` 22 ```use crate::svg_render; ``` 23 ```use 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)] ``` 28 ```pub struct Plot { ``` 29 ``` pub data: Vec<(f64, f64)>, ``` 30 ``` /// None if no lines should be displayed ``` 31 ``` pub line_style: Option, ``` 32 ``` /// None if no points should be displayed ``` 33 ``` pub point_style: Option, ``` 34 ``` pub legend: Option, ``` 35 ```} ``` 36 37 ```impl Plot { ``` 38 1 ``` 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 0 ``` pub fn from_function f64>(f: F, lower: f64, upper: f64) -> Self { ``` 48 0 ``` let sampling = (upper - lower) / 200.; ``` 49 0 ``` let samples = (0..) ``` 50 0 ``` .map(|x| lower + (f64::from(x) * sampling)) ``` 51 0 ``` .take_while(|&x| x <= upper); ``` 52 0 ``` 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 0 ``` pub fn line_style(mut self, other: LineStyle) -> Self { ``` 62 0 ``` if let Some(ref mut self_style) = self.line_style { ``` 63 0 ``` self_style.overlay(&other); ``` 64 ``` } else { ``` 65 0 ``` self.line_style = Some(other); ``` 66 ``` } ``` 67 0 ``` self ``` 68 ``` } ``` 69 1 ``` pub fn point_style(mut self, other: PointStyle) -> Self { ``` 70 1 ``` if let Some(ref mut self_style) = self.point_style { ``` 71 0 ``` self_style.overlay(&other); ``` 72 ``` } else { ``` 73 1 ``` self.point_style = Some(other); ``` 74 ``` } ``` 75 1 ``` self ``` 76 ``` } ``` 77 0 ``` pub fn legend(mut self, legend: String) -> Self { ``` 78 0 ``` self.legend = Some(legend); ``` 79 0 ``` self ``` 80 ``` } ``` 81 82 1 ``` fn x_range(&self) -> (f64, f64) { ``` 83 1 ``` let mut min = f64::INFINITY; ``` 84 1 ``` let mut max = f64::NEG_INFINITY; ``` 85 1 ``` for &(x, _) in &self.data { ``` 86 1 ``` min = min.min(x); ``` 87 1 ``` max = max.max(x); ``` 88 ``` } ``` 89 1 ``` (min, max) ``` 90 ``` } ``` 91 92 1 ``` fn y_range(&self) -> (f64, f64) { ``` 93 1 ``` let mut min = f64::INFINITY; ``` 94 1 ``` let mut max = f64::NEG_INFINITY; ``` 95 1 ``` for &(_, y) in &self.data { ``` 96 1 ``` min = min.min(y); ``` 97 1 ``` max = max.max(y); ``` 98 ``` } ``` 99 1 ``` (min, max) ``` 100 ``` } ``` 101 ```} ``` 102 103 ```impl ContinuousRepresentation for Plot { ``` 104 1 ``` fn range(&self, dim: u32) -> (f64, f64) { ``` 105 1 ``` match dim { ``` 106 1 ``` 0 => self.x_range(), ``` 107 1 ``` 1 => self.y_range(), ``` 108 0 ``` _ => panic!("Axis out of range"), ``` 109 ``` } ``` 110 ``` } ``` 111 112 1 ``` 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 1 ``` let mut group = node::element::Group::new(); ``` 120 1 ``` if let Some(ref line_style) = self.line_style { ``` 121 0 ``` group.append(svg_render::draw_face_line( ``` 122 0 ``` &self.data, ``` 123 0 ``` x_axis, ``` 124 0 ``` y_axis, ``` 125 0 ``` face_width, ``` 126 0 ``` face_height, ``` 127 0 ``` line_style, ``` 128 ``` )) ``` 129 ``` } ``` 130 1 ``` if let Some(ref point_style) = self.point_style { ``` 131 1 ``` group.append(svg_render::draw_face_points( ``` 132 1 ``` &self.data, ``` 133 1 ``` x_axis, ``` 134 1 ``` y_axis, ``` 135 1 ``` face_width, ``` 136 1 ``` face_height, ``` 137 1 ``` point_style, ``` 138 ``` )) ``` 139 ``` } ``` 140 1 ``` group ``` 141 ``` } ``` 142 1 ``` fn legend_svg(&self) -> Option { ``` 143 ``` // TODO: add points ``` 144 ``` // TODO: can we use common functionality with svg_render? ``` 145 1 ``` self.legend.as_ref().map(|legend| { ``` 146 0 ``` let legend = legend.clone(); ``` 147 148 0 ``` let mut group = node::element::Group::new(); ``` 149 0 ``` const FONT_SIZE: f32 = 12.0; ``` 150 151 ``` // Draw legend text ``` 152 0 ``` let legend_text = node::element::Text::new() ``` 153 0 ``` .set("x", 0) ``` 154 0 ``` .set("y", 0) ``` 155 0 ``` .set("text-anchor", "start") ``` 156 0 ``` .set("font-size", FONT_SIZE) ``` 157 0 ``` .add(node::Text::new(legend)); ``` 158 0 ``` group.append(legend_text); ``` 159 160 0 ``` if let Some(ref style) = self.line_style { ``` 161 0 ``` let line = node::element::Line::new() ``` 162 0 ``` .set("x1", -10) ``` 163 0 ``` .set("y1", -FONT_SIZE / 2. + 2.) ``` 164 0 ``` .set("x2", -3) ``` 165 0 ``` .set("y2", -FONT_SIZE / 2. + 2.) ``` 166 0 ``` .set("stroke-width", style.get_width()) ``` 167 0 ``` .set("stroke", style.get_colour()); ``` 168 0 ``` group.append(line); ``` 169 ``` } ``` 170 171 0 ``` group ``` 172 ``` }) ``` 173 ``` } ``` 174 175 0 ``` 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 0 ``` let face_lines = if let Some(line_style) = &self.line_style { ``` 183 0 ``` unimplemented!("Text rendering does not yet support line plots") ``` 184 ``` } else { ``` 185 0 ``` (0..face_height) ``` 186 0 ``` .map(|_| " ".repeat(face_width as usize)) ``` 187 0 ``` .collect::>() ``` 188 0 ``` .join("\n") ``` 189 ``` }; ``` 190 0 ``` let face_points = if let Some(point_style) = &self.point_style { ``` 191 0 ``` text_render::render_face_points( ``` 192 0 ``` &self.data, ``` 193 0 ``` x_axis, ``` 194 0 ``` y_axis, ``` 195 0 ``` face_width, ``` 196 0 ``` face_height, ``` 197 0 ``` &point_style, ``` 198 ``` ) ``` 199 ``` } else { ``` 200 0 ``` (0..face_height) ``` 201 0 ``` .map(|_| " ".repeat(face_width as usize)) ``` 202 0 ``` .collect::>() ``` 203 0 ``` .join("\n") ``` 204 ``` }; ``` 205 0 ``` text_render::overlay(&face_lines, &face_points, 0, 0) ``` 206 ``` } ``` 207 ```} ```

Read our documentation on viewing source code .