Repository: milliams/plotlib (language: rust)

Issue:
svg: Rounding errors in Y labels
![n](https://i.imgur.com/Iz3CzkZ.png)

The only thing that is visible of the top-most number os `0000000005`,  but it's actually `5.60000000005`.

Here is the svg file: http://sprunge.us/QdSTRO

Here is the input data to plotlib:
```
[(10.0, 5.554613094409367), (15.0, 5.571373799732846), (20.0, 5.580786175835321), (25.0, 5.594389457136185), (30.0, 5.588413866453277), (35.0, 5.581090728388583), (40.0, 5.564882932873482), (45.0, 5.5655230910515785), (50.0, 5.575694697314061), (55.0, 5.563091468885199), (60.0, 5.505139387304649), (65.0, 5.435932668847201), (70.0, 5.392844844207445), (75.0, 5.375070392542875), (80.0, 5.370750095478286), (85.0, 5.355376048261111), (90.0, 5.348519533974179), (95.0, 5.344626082423696), (100.0, 5.3274562508487024), (105.0, 5.329445069896911), (110.0, 5.330588028449412), (115.0, 5.335541125490482), (120.0, 5.32486693110636), (125.0, 5.326145859928657), (130.0, 5.3267627195905245), (135.0, 5.324000652981454), (140.0, 5.327175540775914), (145.0, 5.326936653662645), (150.0, 5.326319101633424), (155.0, 5.3195335123314775), (160.0, 5.318547022604354), (165.0, 5.318984347429394), (170.0, 5.317016756493654), (175.0, 5.320569820967218), (180.0, 5.321774086633875), (185.0, 5.3192970755289), (190.0, 5.308293371083831), (195.0, 5.286882630232821), (200.0, 5.264732108073603), (205.0, 5.254215082718998), (210.0, 5.242928735217475), (215.0, 5.229751139000503), (220.0, 5.202672283250928), (225.0, 5.180185883522078), (230.0, 5.156744670958561), (235.0, 5.134800944115094), (240.0, 5.127834870263292), (245.0, 5.121960601961146), (250.0, 5.115420107250173), (255.0, 5.1122765198698215), (260.0, 5.107215561118251), (265.0, 5.09911659020747), (270.0, 5.088330837894482), (275.0, 5.085926681262303), (280.0, 5.084239392471663), (285.0, 5.083673161411417), (290.0, 5.084211846974766), (295.0, 5.081707528453846), (300.0, 5.078245582248812)]
[(10.0, 5.554613094409367), (15.0, 5.571373799732846), (20.0, 5.580786175835321), (25.0, 5.594389457136185), (30.0, 5.588413866453277), (35.0, 5.585615659886031), (40.0, 5.587206724645493), (45.0, 5.571298729005933), (50.0, 5.558131825575247), (55.0, 5.544393316154019), (60.0, 5.53813614296059), (65.0, 5.523535700145619), (70.0, 5.4996913881052105), (75.0, 5.472378149492847), (80.0, 5.45374886101448), (85.0, 5.4324287369128195), (90.0, 5.410797420874291), (95.0, 5.393886671682631), (100.0, 5.361498295387429), (105.0, 5.320841464177755), (110.0, 5.277655355217769), (115.0, 5.242304398506202), (120.0, 5.220685015560172), (125.0, 5.197350800910618), (130.0, 5.180128027249545), (135.0, 5.165155622245212), (140.0, 5.157291822432492), (145.0, 5.160809304386804), (150.0, 5.1621050182918875), (155.0, 5.164784237269466), (160.0, 5.170044410029799), (165.0, 5.176610149099485), (170.0, 5.178943153694296), (175.0, 5.180969369533339), (180.0, 5.181202553209083), (185.0, 5.180421437704337), (190.0, 5.183487260078211), (195.0, 5.190838759095146), (200.0, 5.19805536865893), (205.0, 5.205331656977181), (210.0, 5.211848936948826), (215.0, 5.215886880659793), (220.0, 5.2191099964160355), (225.0, 5.214903056232922), (230.0, 5.209356626646803), (235.0, 5.210631952871428), (240.0, 5.212151991487046), (245.0, 5.214216204923996), (250.0, 5.206883052633969), (255.0, 5.207739240362648), (260.0, 5.207163350519838), (265.0, 5.205506780136554), (270.0, 5.207203657870471), (275.0, 5.206951561696365), (280.0, 5.208947475530317), (285.0, 5.20313975184542), (290.0, 5.204589733890532), (295.0, 5.205217982298412), (300.0, 5.202972315747025)]
[(10.0, 5.656477554152827), (15.0, 5.518753423352734), (20.0, 5.538369232714198), (25.0, 5.523798152568706), (30.0, 5.53725576641077), (35.0, 5.5218183424410014), (40.0, 5.523350461669418), (45.0, 5.531653410599423), (50.0, 5.519886110360921), (55.0, 5.495504075954746), (60.0, 5.483291143318121), (65.0, 5.485041358928483), (70.0, 5.49360138294122), (75.0, 5.4993546868179255), (80.0, 5.495793588413891), (85.0, 5.499689269619098), (90.0, 5.502345293847687), (95.0, 5.499380450456559), (100.0, 5.4860808187812), (105.0, 5.474888395868257), (110.0, 5.469030439040476), (115.0, 5.459149366952276), (120.0, 5.448213297843009), (125.0, 5.433420015075955), (130.0, 5.42028426873691), (135.0, 5.4039370730625), (140.0, 5.389449304230735), (145.0, 5.374739029115512), (150.0, 5.357460575330139), (155.0, 5.340662553581878), (160.0, 5.313241383144196), (165.0, 5.269444679770821), (170.0, 5.238487323826094), (175.0, 5.2086113937648335), (180.0, 5.189242228426556), (185.0, 5.1744945041051), (190.0, 5.159483304652246), (195.0, 5.14430017185943), (200.0, 5.117318556745862), (205.0, 5.10072786906999), (210.0, 5.084061503647151), (215.0, 5.075762136466544), (220.0, 5.068552343275285), (225.0, 5.062863726182678), (230.0, 5.051815330935621), (235.0, 5.031358091612719), (240.0, 5.021260071788471), (245.0, 5.010255285851625), (250.0, 5.003961940532494), (255.0, 4.99636579001612), (260.0, 4.987814326450532), (265.0, 4.980644666607665), (270.0, 4.963432971258708), (275.0, 4.956729825460665), (280.0, 4.948738350016256), (285.0, 4.943941669083361), (290.0, 4.93958132651051), (295.0, 4.933664938830087), (300.0, 4.928992049266768)]
```


Discussion / hints:
A fix for this: https://github.com/Ploppz/plotlib/commit/8c3670b4d107c72276ede793e714ceca5e437eee But it builds on @superfunc's work which has yet to be merged.
This is indeed a bug I've seen creep in in a few situations. Your fix looks sane so I'd invite you to put a PR up for review.

In the future, we'd want to avoid hardcoding the precision and support scientific notation but that can come as a separate feature.

Fix patch:
diff --git a/CHANGELOG b/CHANGELOG
index 6ac0f4c..94d35f3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,7 @@
 - Group all representations under a `repr` module.
 - Add `linejoin` option to line style.
 - More Box and less & in the interface
+- Replace Line, Scatter and Function with Plot
 
 ## 0.4.0 - 2019-03-02
 ### Added
diff --git a/examples/barchart_svg.rs b/examples/barchart_svg.rs
index 6fbb60a..a2fff43 100644
--- a/examples/barchart_svg.rs
+++ b/examples/barchart_svg.rs
@@ -7,7 +7,7 @@ fn main() {
     let b1 = BarChart::new(5.3).label("1");
     let b2 = BarChart::new(2.6)
         .label("2")
-        .style(BoxStyle::new().fill("darkolivegreen"));
+        .style(&BoxStyle::new().fill("darkolivegreen"));
 
     let v = CategoricalView::new()
         .add(b1)
diff --git a/examples/boxplot_svg.rs b/examples/boxplot_svg.rs
index 61f6dda..4a5f339 100644
--- a/examples/boxplot_svg.rs
+++ b/examples/boxplot_svg.rs
@@ -7,7 +7,7 @@ fn main() {
     let b1 = BoxPlot::from_slice(&[1.0, 4.0, 2.0, 3.5, 6.4, 2.5, 7.5, 1.8, 9.6]).label("1");
     let b2 = BoxPlot::from_slice(&[3.0, 4.3, 2.0, 3.5, 6.9, 4.5, 7.5, 1.8, 10.6])
         .label("2")
-        .style(BoxStyle::new().fill("darkolivegreen"));
+        .style(&BoxStyle::new().fill("darkolivegreen"));
 
     let v = CategoricalView::new()
         .add(b1)
diff --git a/examples/function_svg.rs b/examples/function_svg.rs
index 3bbd03d..593b564 100644
--- a/examples/function_svg.rs
+++ b/examples/function_svg.rs
@@ -1,14 +1,14 @@
-use plotlib::repr::Function;
 use plotlib::page::Page;
+use plotlib::repr::Plot;
 use plotlib::style::LineStyle;
 use plotlib::view::ContinuousView;
 
 fn main() {
-    let f1 = Function::new(|x| x * 5., 0., 10.).style(LineStyle::new().colour("burlywood"));
-    let f2 = Function::new(|x| x.powi(2), 0., 10.)
-        .style(LineStyle::new().colour("darkolivegreen").width(2.));
-    let f3 = Function::new(|x| x.sqrt() * 20., 0., 10.)
-        .style(LineStyle::new().colour("brown").width(1.));
+    let f1 = Plot::from_function(|x| x * 5., 0., 10.).line_style(LineStyle::new().colour("burlywood"));
+    let f2 = Plot::from_function(|x| x.powi(2), 0., 10.)
+        .line_style(LineStyle::new().colour("darkolivegreen").width(2.));
+    let f3 = Plot::from_function(|x| x.sqrt() * 20., 0., 10.)
+        .line_style(LineStyle::new().colour("brown").width(1.));
 
     let v = ContinuousView::new().add(f1).add(f2).add(f3);
 
diff --git a/examples/histogram_svg.rs b/examples/histogram_svg.rs
index d707783..7e70119 100644
--- a/examples/histogram_svg.rs
+++ b/examples/histogram_svg.rs
@@ -6,7 +6,7 @@ use plotlib::view::ContinuousView;
 fn main() {
     let data = [0.3, 0.5, 6.4, 5.3, 3.6, 3.6, 3.5, 7.5, 4.0];
     let h = Histogram::from_slice(&data, HistogramBins::Count(10))
-        .style(BoxStyle::new().fill("burlywood"));
+        .style(&BoxStyle::new().fill("burlywood"));
 
     let v = ContinuousView::new().add(h);
 
diff --git a/examples/line_and_point_svg.rs b/examples/line_and_point_svg.rs
new file mode 100644
index 0000000..59feec7
--- /dev/null
+++ b/examples/line_and_point_svg.rs
@@ -0,0 +1,13 @@
+use plotlib::page::Page;
+use plotlib::repr::Plot;
+use plotlib::style::*;
+use plotlib::view::ContinuousView;
+
+fn main() {
+    let l1 = Plot::new(vec![(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)])
+        .line_style(LineStyle::new().colour("burlywood").linejoin(LineJoin::Round))
+        .point_style(PointStyle::new());
+        
+    let v = ContinuousView::new().add(l1);
+    Page::single(&v).save("line_and_point.svg").expect("saving svg");
+}
diff --git a/examples/line_svg.rs b/examples/line_svg.rs
index 7055c62..7bf78a5 100644
--- a/examples/line_svg.rs
+++ b/examples/line_svg.rs
@@ -1,10 +1,10 @@
 use plotlib::page::Page;
-use plotlib::repr::Line;
+use plotlib::repr::Plot;
 use plotlib::style::{LineJoin, LineStyle};
 use plotlib::view::ContinuousView;
 
 fn main() {
-    let l1 = Line::new(vec![(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)]).style(
+    let l1 = Plot::new(vec![(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)]).line_style(
         LineStyle::new()
             .colour("burlywood")
             .linejoin(LineJoin::Round),
diff --git a/examples/scatter_svg.rs b/examples/scatter_svg.rs
index ab7fd9f..074ae8b 100644
--- a/examples/scatter_svg.rs
+++ b/examples/scatter_svg.rs
@@ -1,10 +1,10 @@
 use plotlib::page::Page;
-use plotlib::repr::Scatter;
+use plotlib::repr::Plot;
 use plotlib::style::{PointMarker, PointStyle};
 use plotlib::view::ContinuousView;
 
 fn main() {
-    let data = [
+    let data = vec![
         (-3.0, 2.3),
         (-1.6, 5.3),
         (0.3, 0.7),
@@ -12,14 +12,14 @@ fn main() {
         (6.4, 4.3),
         (8.5, 3.7),
     ];
-    let s1 = Scatter::from_slice(&data).style(
+    let s1 = Plot::new(data).point_style(
         PointStyle::new()
             .marker(PointMarker::Square)
             .colour("burlywood")
             .size(2.),
     );
-    let s2 = Scatter::from_slice(&[(-1.4, 2.5), (7.2, -0.3)])
-        .style(PointStyle::new().colour("darkseagreen"));
+    let s2 = Plot::new(vec![(-1.4, 2.5), (7.2, -0.3)])
+        .point_style(PointStyle::new().colour("darkseagreen"));
 
     let v = ContinuousView::new()
         .add(s1)
diff --git a/examples/scatter_text.rs b/examples/scatter_text.rs
index f892eec..d439143 100644
--- a/examples/scatter_text.rs
+++ b/examples/scatter_text.rs
@@ -1,10 +1,10 @@
 use plotlib::page::Page;
-use plotlib::repr::Scatter;
+use plotlib::repr::Plot;
 use plotlib::style::{PointMarker, PointStyle};
 use plotlib::view::ContinuousView;
 
 fn main() {
-    let data = [
+    let data = vec![
         (-3.0, 2.3),
         (-1.6, 5.3),
         (0.3, 0.7),
@@ -12,9 +12,9 @@ fn main() {
         (6.4, 4.3),
         (8.5, 3.7),
     ];
-    let s1 = Scatter::from_slice(&data);
-    let s2 = Scatter::from_slice(&[(-1.4, 2.5), (7.2, -0.3)])
-        .style(PointStyle::new().marker(PointMarker::Square));
+    let s1 = Plot::new(data);
+    let s2 = Plot::new(vec![(-1.4, 2.5), (7.2, -0.3)])
+        .point_style(PointStyle::new().marker(PointMarker::Square));
 
     let v = ContinuousView::new()
         .add(s1)
diff --git a/examples/with_grid.rs b/examples/with_grid.rs
index 972e9b3..172610a 100644
--- a/examples/with_grid.rs
+++ b/examples/with_grid.rs
@@ -1,4 +1,4 @@
-use plotlib::repr::{BarChart, Line};
+use plotlib::repr::{BarChart, Plot};
 use plotlib::grid::Grid;
 use plotlib::page::Page;
 use plotlib::style::{BoxStyle, LineStyle};
@@ -13,8 +13,8 @@ fn render_line_chart<S>(filename: S)
 where
     S: AsRef<str>,
 {
-    let l1 = Line::new(vec![(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)])
-        .style(LineStyle::new().colour("burlywood"));
+    let l1 = Plot::new(vec![(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)])
+        .line_style(LineStyle::new().colour("burlywood"));
     let mut v = ContinuousView::new().add(l1);
     v.add_grid(Grid::new(3, 8));
     Page::single(&v)
@@ -29,7 +29,7 @@ where
     let b1 = BarChart::new(5.3).label("1");
     let b2 = BarChart::new(2.6)
         .label("2")
-        .style(BoxStyle::new().fill("darkolivegreen"));
+        .style(&BoxStyle::new().fill("darkolivegreen"));
     let mut v = CategoricalView::new()
         .add(b1)
         .add(b2)
diff --git a/src/axis.rs b/src/axis.rs
index 31e193b..3cfa043 100644
--- a/src/axis.rs
+++ b/src/axis.rs
@@ -146,13 +146,19 @@ impl Iterator for TickSteps {
 }
 
 fn generate_ticks(min: f64, max: f64, step_size: f64) -> Vec<f64> {
+    // "fix" just makes sure there are no floating-point errors
+    fn fix(x: f64) -> f64 {
+        const PRECISION: f64 = 100000_f64;
+        (x * PRECISION).round() / PRECISION
+    }
+
     let mut ticks: Vec<f64> = vec![];
     if min <= 0.0 {
         if max >= 0.0 {
             // standard spanning axis
             ticks.extend(
                 (1..)
-                    .map(|n| -1.0 * f64::from(n) * step_size)
+                    .map(|n| -1.0 * fix(f64::from(n) * step_size))
                     .take_while(|&v| v >= min)
                     .collect::<Vec<f64>>()
                     .iter()
@@ -161,14 +167,14 @@ fn generate_ticks(min: f64, max: f64, step_size: f64) -> Vec<f64> {
             ticks.push(0.0);
             ticks.extend(
                 (1..)
-                    .map(|n| f64::from(n) * step_size)
+                    .map(|n| fix(f64::from(n) * step_size))
                     .take_while(|&v| v <= max),
             );
         } else {
             // entirely negative axis
             ticks.extend(
                 (1..)
-                    .map(|n| -1.0 * f64::from(n) * step_size)
+                    .map(|n| -1.0 * fix(f64::from(n) * step_size))
                     .skip_while(|&v| v > max)
                     .take_while(|&v| v >= min)
                     .collect::<Vec<f64>>()
@@ -180,7 +186,7 @@ fn generate_ticks(min: f64, max: f64, step_size: f64) -> Vec<f64> {
         // entirely positive axis
         ticks.extend(
             (1..)
-                .map(|n| f64::from(n) * step_size)
+                .map(|n| fix(f64::from(n) * step_size))
                 .skip_while(|&v| v < min)
                 .take_while(|&v| v <= max),
         );
@@ -386,12 +392,7 @@ mod tests {
     }
 
     #[test]
-    #[should_panic(expected = "assertion failed")]
-    fn test_todo() {
-        // This should work but there is currently a bug in how the rounding is performed.
-        // generate_ticks() does `(3 as f64) * step_size` which when step_size is 0.1 gives
-        // 0.30000000000000004 rather than 1.3
-        // Maybe we just want to fix this at display in which case this test can be removed.
+    fn test_no_rounding_errors() {
         assert_eq!(calculate_ticks(1.0, 1.5, 6), [1.0, 1.1, 1.2, 1.3, 1.4, 1.5]);
     }
 }
diff --git a/src/grid.rs b/src/grid.rs
index c1db159..23b957d 100644
--- a/src/grid.rs
+++ b/src/grid.rs
@@ -18,8 +18,8 @@
 //! # use plotlib::style::LineStyle;
 //! # use plotlib::view::View;
 //!
-//! # let l1 = plotlib::repr::Line::new(vec![(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)])
-//! #    .style(LineStyle::new().colour("burlywood"));
+//! # let l1 = plotlib::repr::Plot::new(vec![(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)])
+//! #    .line_style(LineStyle::new().colour("burlywood"));
 //! // let l1 = Line::new() ...
 //! let mut v = ContinuousView::new().add(l1);
 //!
diff --git a/src/repr/function.rs b/src/repr/function.rs
deleted file mode 100644
index 6249ab2..0000000
--- a/src/repr/function.rs
+++ /dev/null
@@ -1,116 +0,0 @@
-/*!
-
-Plot arbitrary functions
-
-# Examples
-
-```
-# use plotlib::repr::Function;
-# use plotlib::view::ContinuousView;
-// y=x^2 between 0 and 10
-let f = Function::new(|x| x*x, 0., 10.);
-let v = ContinuousView::new().add(f);
-```
-*/
-
-use std::f64;
-
-use svg;
-
-use crate::axis;
-use crate::repr::ContinuousRepresentation;
-use crate::style::LineStyle;
-use crate::svg_render;
-
-pub struct Function {
-    pub data: Vec<(f64, f64)>,
-    style: LineStyle,
-}
-
-impl Function {
-    pub fn new<F>(f: F, lower: f64, upper: f64) -> Self
-    where
-        F: Fn(f64) -> f64,
-    {
-        let sampling = (upper - lower) / 200.;
-        let samples = (0..)
-            .map(|x| lower + (f64::from(x) * sampling))
-            .take_while(|&x| x <= upper);
-        let values = samples.map(|s| (s, f(s))).collect();
-        Function {
-            data: values,
-            style: LineStyle::new(),
-        }
-    }
-
-    pub fn style(mut self, style: &LineStyle) -> Self {
-        self.style.overlay(style);
-        self
-    }
-
-    pub fn get_style(&self) -> &LineStyle {
-        &self.style
-    }
-
-    fn x_range(&self) -> (f64, f64) {
-        let mut min = f64::INFINITY;
-        let mut max = f64::NEG_INFINITY;
-        for &(x, _) in &self.data {
-            min = min.min(x);
-            max = max.max(x);
-        }
-        (min, max)
-    }
-
-    fn y_range(&self) -> (f64, f64) {
-        let mut min = f64::INFINITY;
-        let mut max = f64::NEG_INFINITY;
-        for &(_, y) in &self.data {
-            min = min.min(y);
-            max = max.max(y);
-        }
-        (min, max)
-    }
-}
-
-impl ContinuousRepresentation for Function {
-    fn range(&self, dim: u32) -> (f64, f64) {
-        match dim {
-            0 => self.x_range(),
-            1 => self.y_range(),
-            _ => panic!("Axis out of range"),
-        }
-    }
-
-    fn to_svg(
-        &self,
-        x_axis: &axis::ContinuousAxis,
-        y_axis: &axis::ContinuousAxis,
-        face_width: f64,
-        face_height: f64,
-    ) -> svg::node::element::Group {
-        svg_render::draw_face_line(
-            &self.data,
-            x_axis,
-            y_axis,
-            face_width,
-            face_height,
-            &self.style,
-        )
-    }
-
-    fn legend_svg(&self) -> Option<svg::node::element::Group> {
-        // TODO implement
-        None
-    }
-
-    fn to_text(
-        &self,
-        _x_axis: &axis::ContinuousAxis,
-        _y_axis: &axis::ContinuousAxis,
-        _face_width: u32,
-        _face_height: u32,
-    ) -> String {
-        "".into()
-    }
-}
diff --git a/src/repr/line.rs b/src/repr/line.rs
deleted file mode 100644
index f3c4934..0000000
--- a/src/repr/line.rs
+++ /dev/null
@@ -1,139 +0,0 @@
-/*!
-
-Plot line charts
-
-# Examples
-
-```
-# use plotlib::repr::Line;
-# use plotlib::view::ContinuousView;
-// y=x^2 between 0 and 10
-let l = Line::new(vec![(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)]);
-let v = ContinuousView::new().add(l);
-```
-*/
-
-use std::f64;
-
-use svg;
-
-use crate::axis;
-use crate::repr::ContinuousRepresentation;
-use crate::style::LineStyle;
-use crate::svg_render;
-
-pub struct Line {
-    pub data: Vec<(f64, f64)>,
-    style: LineStyle,
-    legend: Option<String>,
-}
-
-impl Line {
-    pub fn new(data: Vec<(f64, f64)>) -> Self {
-        Line {
-            data,
-            style: LineStyle::new(),
-            legend: None,
-        }
-    }
-
-    pub fn style(mut self, style: &LineStyle) -> Self {
-        self.style.overlay(style);
-        self
-    }
-    pub fn legend(mut self, legend: String) -> Self {
-        self.legend = Some(legend);
-        self
-    }
-
-    pub fn get_style(&self) -> &LineStyle {
-        &self.style
-    }
-
-    fn x_range(&self) -> (f64, f64) {
-        let mut min = f64::INFINITY;
-        let mut max = f64::NEG_INFINITY;
-        for &(x, _) in &self.data {
-            min = min.min(x);
-            max = max.max(x);
-        }
-        (min, max)
-    }
-
-    fn y_range(&self) -> (f64, f64) {
-        let mut min = f64::INFINITY;
-        let mut max = f64::NEG_INFINITY;
-        for &(_, y) in &self.data {
-            min = min.min(y);
-            max = max.max(y);
-        }
-        (min, max)
-    }
-}
-
-impl ContinuousRepresentation for Line {
-    fn range(&self, dim: u32) -> (f64, f64) {
-        match dim {
-            0 => self.x_range(),
-            1 => self.y_range(),
-            _ => panic!("Axis out of range"),
-        }
-    }
-
-    fn to_svg(
-        &self,
-        x_axis: &axis::ContinuousAxis,
-        y_axis: &axis::ContinuousAxis,
-        face_width: f64,
-        face_height: f64,
-    ) -> svg::node::element::Group {
-        svg_render::draw_face_line(
-            &self.data,
-            x_axis,
-            y_axis,
-            face_width,
-            face_height,
-            &self.style,
-        )
-    }
-    fn legend_svg(&self) -> Option<svg::node::element::Group> {
-        self.legend.as_ref().map(|legend| {
-            let legend = legend.clone();
-            use svg::{node, Node};
-
-            let mut group = node::element::Group::new();
-            const FONT_SIZE: f32 = 12.0;
-
-            // Draw legend text
-            let legend_text = node::element::Text::new()
-                .set("x", 0)
-                .set("y", 0)
-                .set("text-anchor", "start")
-                .set("font-size", FONT_SIZE)
-                .add(node::Text::new(legend));
-            group.append(legend_text);
-
-            // Draw sample line
-            let mut line = node::element::Line::new()
-                .set("x1", -10)
-                .set("y1", -FONT_SIZE/2. +2.)
-                .set("x2", -3)
-                .set("y2", -FONT_SIZE/2. +2.)
-                .set("stroke-width", self.style.get_width().unwrap_or(2.))
-                .set("stroke", self.style.get_colour().clone().unwrap_or_else(|| "".into()));
-            group.append(line);
-
-            group
-        })
-    }
-
-    fn to_text(
-        &self,
-        _x_axis: &axis::ContinuousAxis,
-        _y_axis: &axis::ContinuousAxis,
-        _face_width: u32,
-        _face_height: u32,
-    ) -> String {
-        "".into()
-    }
-}
diff --git a/src/repr/mod.rs b/src/repr/mod.rs
index 817269a..e8c4f6e 100644
--- a/src/repr/mod.rs
+++ b/src/repr/mod.rs
@@ -14,18 +14,14 @@ These points may then be layered with other SVG elements from other representati
 
 use crate::axis;
 
-mod line;
-mod function;
 mod barchart;
 mod boxplot;
 mod histogram;
-mod scatter;
-pub use line::*;
-pub use function::*;
+mod plot;
 pub use barchart::*;
 pub use boxplot::*;
 pub use histogram::*;
-pub use scatter::*;
+pub use plot::*;
 
 /**
 A representation of data that is continuous in two dimensions.
diff --git a/src/repr/plot.rs b/src/repr/plot.rs
new file mode 100644
index 0000000..87a7b1e
--- /dev/null
+++ b/src/repr/plot.rs
@@ -0,0 +1,186 @@
+//! Plot line charts
+
+//! # Examples
+
+//! ```
+//! # use plotlib::repr::Plot;
+//! # use plotlib::view::ContinuousView;
+//! // y=x^2 between 0 and 10
+//! let l = Plot::new(vec![(0., 1.), (2., 1.5), (3., 1.2), (4., 1.1)]);
+//! let v = ContinuousView::new().add(l);
+//! ```
+
+use std::f64;
+
+use svg;
+use svg::node;
+use svg::Node;
+
+use crate::axis;
+use crate::repr::ContinuousRepresentation;
+use crate::style::*;
+use crate::svg_render;
+
+/// Representation of any plot with points in the XY plane, visualized as points and/or with lines
+/// in-between.
+#[derive(Debug, Clone)]
+pub struct Plot {
+    pub data: Vec<(f64, f64)>,
+    /// None if no lines should be displayed
+    pub line_style: Option<LineStyle>,
+    /// None if no points should be displayed
+    pub point_style: Option<PointStyle>,
+    pub legend: Option<String>,
+}
+
+impl Plot {
+    pub fn new(data: Vec<(f64, f64)>) -> Self {
+        Plot {
+            data,
+            line_style: None,
+            point_style: None,
+            legend: None,
+        }
+    }
+
+    pub fn from_function<F: Fn(f64) -> f64>(f: F, lower: f64, upper: f64) -> Self {
+        let sampling = (upper - lower) / 200.;
+        let samples = (0..)
+            .map(|x| lower + (f64::from(x) * sampling))
+            .take_while(|&x| x <= upper);
+        let values = samples.map(|s| (s, f(s))).collect();
+        Plot {
+            data: values,
+            line_style: None,
+            point_style: None,
+            legend: None,
+        }
+    }
+
+    pub fn line_style(mut self, other: LineStyle) -> Self {
+        if let Some(ref mut self_style) = self.line_style {
+            self_style.overlay(&other);
+        } else {
+            self.line_style = Some(other);
+        }
+        self
+    }
+    pub fn point_style(mut self, other: PointStyle) -> Self {
+        if let Some(ref mut self_style) = self.point_style {
+            self_style.overlay(&other);
+        } else {
+            self.point_style = Some(other);
+        }
+        self
+    }
+    pub fn legend(mut self, legend: String) -> Self {
+        self.legend = Some(legend);
+        self
+    }
+
+
+    fn x_range(&self) -> (f64, f64) {
+        let mut min = f64::INFINITY;
+        let mut max = f64::NEG_INFINITY;
+        for &(x, _) in &self.data {
+            min = min.min(x);
+            max = max.max(x);
+        }
+        (min, max)
+    }
+
+    fn y_range(&self) -> (f64, f64) {
+        let mut min = f64::INFINITY;
+        let mut max = f64::NEG_INFINITY;
+        for &(_, y) in &self.data {
+            min = min.min(y);
+            max = max.max(y);
+        }
+        (min, max)
+    }
+}
+
+impl ContinuousRepresentation for Plot {
+    fn range(&self, dim: u32) -> (f64, f64) {
+        match dim {
+            0 => self.x_range(),
+            1 => self.y_range(),
+            _ => panic!("Axis out of range"),
+        }
+    }
+
+    fn to_svg(
+        &self,
+        x_axis: &axis::ContinuousAxis,
+        y_axis: &axis::ContinuousAxis,
+        face_width: f64,
+        face_height: f64,
+    ) -> svg::node::element::Group {
+        let mut group = node::element::Group::new();
+        if let Some(ref line_style) = self.line_style {
+            group.append(
+                svg_render::draw_face_line(
+                    &self.data,
+                    x_axis,
+                    y_axis,
+                    face_width,
+                    face_height,
+                    line_style,
+                ))
+        }
+        if let Some(ref point_style) = self.point_style {
+            group.append(
+                svg_render::draw_face_points(
+                    &self.data,
+                    x_axis,
+                    y_axis,
+                    face_width,
+                    face_height,
+                    point_style,
+                ))
+        }
+        group
+    }
+    fn legend_svg(&self) -> Option<svg::node::element::Group> {
+        // TODO: add points
+        // TODO: can we use common functionality with svg_render?
+        self.legend.as_ref().map(|legend| {
+            let legend = legend.clone();
+
+            let mut group = node::element::Group::new();
+            const FONT_SIZE: f32 = 12.0;
+
+            // Draw legend text
+            let legend_text = node::element::Text::new()
+                .set("x", 0)
+                .set("y", 0)
+                .set("text-anchor", "start")
+                .set("font-size", FONT_SIZE)
+                .add(node::Text::new(legend));
+            group.append(legend_text);
+
+            if let Some(ref style) = self.line_style {
+                let line = node::element::Line::new()
+                    .set("x1", -10)
+                    .set("y1", -FONT_SIZE/2. +2.)
+                    .set("x2", -3)
+                    .set("y2", -FONT_SIZE/2. +2.)
+                    .set("stroke-width", style.get_width())
+                    .set("stroke", style.get_colour());
+                group.append(line);
+            }
+
+            group
+        })
+    }
+
+    fn to_text(
+        &self,
+        _x_axis: &axis::ContinuousAxis,
+        _y_axis: &axis::ContinuousAxis,
+        _face_width: u32,
+        _face_height: u32,
+    ) -> String {
+        "".into()
+    }
+}
diff --git a/src/repr/scatter.rs b/src/repr/scatter.rs
deleted file mode 100644
index 501536c..0000000
--- a/src/repr/scatter.rs
+++ /dev/null
@@ -1,108 +0,0 @@
-use std::f64;
-
-use svg;
-
-use crate::axis;
-use crate::repr::ContinuousRepresentation;
-use crate::style::PointStyle;
-use crate::svg_render;
-use crate::text_render;
-
-/// The scatter *representation*.
-/// It knows its data as well how to style itself
-#[derive(Debug)]
-pub struct Scatter {
-    pub data: Vec<(f64, f64)>,
-    style: PointStyle,
-}
-
-impl Scatter {
-    pub fn from_slice(v: &[(f64, f64)]) -> Self {
-        let mut data: Vec<(f64, f64)> = vec![];
-        for &(x, y) in v {
-            data.push((x, y));
-        }
-
-        Scatter {
-            data,
-            style: PointStyle::new(),
-        }
-    }
-
-    pub fn style(mut self, style: &PointStyle) -> Self {
-        self.style.overlay(style);
-        self
-    }
-
-    pub fn get_style(&self) -> &PointStyle {
-        &self.style
-    }
-
-    fn x_range(&self) -> (f64, f64) {
-        let mut min = f64::INFINITY;
-        let mut max = f64::NEG_INFINITY;
-        for &(x, _) in &self.data {
-            min = min.min(x);
-            max = max.max(x);
-        }
-        (min, max)
-    }
-
-    fn y_range(&self) -> (f64, f64) {
-        let mut min = f64::INFINITY;
-        let mut max = f64::NEG_INFINITY;
-        for &(_, y) in &self.data {
-            min = min.min(y);
-            max = max.max(y);
-        }
-        (min, max)
-    }
-}
-
-impl ContinuousRepresentation for Scatter {
-    fn range(&self, dim: u32) -> (f64, f64) {
-        match dim {
-            0 => self.x_range(),
-            1 => self.y_range(),
-            _ => panic!("Axis out of range"),
-        }
-    }
-
-    fn to_svg(
-        &self,
-        x_axis: &axis::ContinuousAxis,
-        y_axis: &axis::ContinuousAxis,
-        face_width: f64,
-        face_height: f64,
-    ) -> svg::node::element::Group {
-        svg_render::draw_face_points(
-            &self.data,
-            x_axis,
-            y_axis,
-            face_width,
-            face_height,
-            &self.style,
-        )
-    }
-    fn legend_svg(&self) -> Option<svg::node::element::Group> {
-        // TODO implement
-        None
-    }
-
-    fn to_text(
-        &self,
-        x_axis: &axis::ContinuousAxis,
-        y_axis: &axis::ContinuousAxis,
-        face_width: u32,
-        face_height: u32,
-    ) -> String {
-        text_render::render_face_points(
-            &self.data,
-            x_axis,
-            y_axis,
-            face_width,
-            face_height,
-            &self.style,
-        )
-    }
-}
diff --git a/src/style.rs b/src/style.rs
index 2658dcc..9d6fe40 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -6,20 +6,18 @@
 //! can be overlaid with another one to set many at once.
 //! Settings will be cloned in and out of it.
 
-/**
-The style that line corners should use
-*/
-#[derive(Debug, Clone)]
+/// The style that line corners should use
+#[derive(Debug, Clone, Copy)]
 pub enum LineJoin {
     Miter,
     Round,
 }
 
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Clone)]
 pub struct LineStyle {
-    colour: Option<String>,
-    width: Option<f32>,
-    linejoin: Option<LineJoin>,
+    pub colour: Option<String>,
+    pub width: Option<f32>,
+    pub linejoin: Option<LineJoin>,
 }
 impl LineStyle {
     pub fn new() -> Self {
@@ -43,47 +41,42 @@ impl LineStyle {
             self.linejoin = Some(v.clone())
         }
     }
-    pub fn colour<T>(&mut self, value: T) -> &mut Self
+    pub fn colour<T>(mut self, value: T) -> Self
     where
         T: Into<String>,
     {
         self.colour = Some(value.into());
         self
     }
-
-    pub fn get_colour(&self) -> &Option<String> {
-        &self.colour
+    pub fn get_colour(&self) -> String {
+        self.colour.clone().unwrap_or_else(|| "black".into())
     }
 
-    pub fn width<T>(&mut self, value: T) -> &mut Self
+    pub fn width<T>(mut self, value: T) -> Self
     where
         T: Into<f32>,
     {
         self.width = Some(value.into());
         self
     }
-
-    pub fn get_width(&self) -> &Option<f32> {
-        &self.width
+    pub fn get_width(&self) -> f32 {
+        self.width.unwrap_or_else(|| 2.0)
     }
 
-    pub fn linejoin<T>(&mut self, value: T) -> &mut Self
+    pub fn linejoin<T>(mut self, value: T) -> Self
     where
         T: Into<LineJoin>,
     {
         self.linejoin = Some(value.into());
         self
     }
-
-    pub fn get_linejoin(&self) -> &Option<LineJoin> {
-        &self.linejoin
+    pub fn get_linejoin(&self) -> LineJoin {
+        self.linejoin.unwrap_or_else(|| LineJoin::Round)
     }
 }
 
-/**
-The marker that should be used for the points of the scatter plot
-*/
-#[derive(Debug, Clone)]
+/// The marker that should be used for the points of the scatter plot
+#[derive(Debug, Clone, Copy)]
 pub enum PointMarker {
     Circle,
     Square,
@@ -91,7 +84,7 @@ pub enum PointMarker {
 }
 
 
-#[derive(Debug, Default)]
+#[derive(Debug, Default, Clone)]
 pub struct PointStyle {
     marker: Option<PointMarker>,
     colour: Option<String>,
@@ -119,40 +112,37 @@ impl PointStyle {
             self.size = Some(v)
         }
     }
-    pub fn marker<T>(&mut self, value: T) -> &mut Self
+    pub fn marker<T>(mut self, value: T) -> Self
     where
         T: Into<PointMarker>,
     {
         self.marker = Some(value.into());
         self
     }
-
-    pub fn get_marker(&self) -> &Option<PointMarker> {
-        &self.marker
+    pub fn get_marker(&self) -> PointMarker {
+        self.marker.unwrap_or(PointMarker::Circle)
     }
 
-    pub fn colour<T>(&mut self, value: T) -> &mut Self
+    pub fn colour<T>(mut self, value: T) -> Self
     where
         T: Into<String>,
     {
         self.colour = Some(value.into());
         self
     }
-
-    pub fn get_colour(&self) -> &Option<String> {
-        &self.colour
+    pub fn get_colour(&self) -> String {
+        self.colour.clone().unwrap_or_else(|| "".into())
     }
 
-    pub fn size<T>(&mut self, value: T) -> &mut Self
+    pub fn size<T>(mut self, value: T) -> Self
     where
         T: Into<f32>,
     {
         self.size = Some(value.into());
         self
     }
-
-    pub fn get_size(&self) -> &Option<f32> {
-        &self.size
+    pub fn get_size(&self) -> f32 {
+        self.size.unwrap_or(5.0)
     }
 }
 
@@ -172,43 +162,32 @@ impl BoxStyle {
         }
     }
 
-    pub fn fill<T>(&mut self, value: T) -> &mut Self
+    pub fn fill<T>(mut self, value: T) -> Self
     where
         T: Into<String>,
     {
         self.fill = Some(value.into());
         self
     }
-
-    pub fn get_fill(&self) -> &Option<String> {
-        &self.fill
+    pub fn get_fill(&self) -> String {
+        self.fill.clone().unwrap_or_else(|| "".into())
     }
+
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
 
-    #[test]
-    fn test_linestyle_simple() {
-        let s = LineStyle::new();
-        assert_eq!(*s.get_colour(), None);
-        assert_eq!(*s.get_width(), None);
-        assert!(match s.get_linejoin() {
-            None => true,
-            _ => false,
-        });
-    }
-
     #[test]
     fn test_linestyle_plain_overlay() {
         let mut p = LineStyle::new();
-        p.overlay(LineStyle::new().colour("red").linejoin(LineJoin::Miter).width(1.));
-        assert_eq!(*p.get_colour(), Some("red".into()));
-        assert_eq!(*p.get_width(), Some(1.));
-        assert!(match p.get_linejoin() {
-            Some(LineJoin::Miter) => true,
-            _ => false,
-        });
+        p.overlay(&LineStyle::new().colour("red").linejoin(LineJoin::Miter).width(1.));
+        assert_eq!(p.get_colour(), "red".to_string());
+        assert_eq!(p.get_width(), 1.);
+        if let LineJoin::Miter = p.get_linejoin() {
+        } else {
+            panic!()
+        }
     }
 }
diff --git a/src/svg_render.rs b/src/svg_render.rs
index 56dce31..f3dbe72 100644
--- a/src/svg_render.rs
+++ b/src/svg_render.rs
@@ -108,14 +108,15 @@ pub fn draw_y_axis(a: &axis::ContinuousAxis, face_height: f64) -> node::element:
         labels.append(tick_label);
     }
 
+    const X_OFFSET: i32 = - 50;
     let label = node::element::Text::new()
-        .set("x", -30)
+        .set("x", X_OFFSET)
         .set("y", -(face_height / 2.))
         .set("text-anchor", "middle")
         .set("font-size", 12)
         .set(
             "transform",
-            format!("rotate(-90 {} {})", -30, -(face_height / 2.)),
+            format!("rotate(-90 {} {})", X_OFFSET, -(face_height / 2.)),
         )
         .add(node::Text::new(a.get_label()));
 
@@ -188,8 +189,8 @@ pub fn draw_face_points(
     for &(x, y) in s {
         let x_pos = value_to_face_offset(x, x_axis, face_width);
         let y_pos = -value_to_face_offset(y, y_axis, face_height);
-        let radius = f64::from(style.get_size().clone().unwrap_or(5.));
-        match style.get_marker().clone().unwrap_or(style::PointMarker::Circle) {
+        let radius = f64::from(style.get_size());
+        match style.get_marker() {
             style::PointMarker::Circle => {
                 group.append(
                     node::element::Circle::new()
@@ -198,7 +199,7 @@ pub fn draw_face_points(
                         .set("r", radius)
                         .set(
                             "fill",
-                            style.get_colour().clone().unwrap_or_else(|| "".into()),
+                            style.get_colour(),
                         ),
                 );
             }
@@ -211,7 +212,7 @@ pub fn draw_face_points(
                         .set("height", 2. * radius)
                         .set(
                             "fill",
-                            style.get_colour().clone().unwrap_or_else(|| "".into()),
+                            style.get_colour(),
                         ),
                 );
             }
@@ -227,7 +228,7 @@ pub fn draw_face_points(
                         .set("fill", "none")
                         .set(
                             "stroke",
-                            style.get_colour().clone().unwrap_or_else(|| "".into()),
+                            style.get_colour(),
                         )
                         .set("stroke-width", 2)
                         .set("d", path),
@@ -264,8 +265,6 @@ pub fn draw_face_bars(
                 "fill",
                 style
                     .get_fill()
-                    .clone()
-                    .unwrap_or_else(|| "burlywood".into()),
             )
             .set("stroke", "black");
         group.append(rect);
@@ -307,12 +306,9 @@ pub fn draw_face_line(
     group.append(
         node::element::Path::new()
             .set("fill", "none")
-            .set(
-                "stroke",
-                style.get_colour().clone().unwrap_or_else(|| "".into()),
-            )
-            .set("stroke-width", style.get_width().clone().unwrap_or(2.))
-            .set("stroke-linejoin", match style.get_linejoin().clone().unwrap_or(style::LineJoin::Round) {
+            .set("stroke", style.get_colour())
+            .set("stroke-width", style.get_width())
+            .set("stroke-linejoin", match style.get_linejoin() {
                 style::LineJoin::Miter => "miter",
                 style::LineJoin::Round => "round",
             })
@@ -354,13 +350,7 @@ where
             .set("y", box_start)
             .set("width", box_width)
             .set("height", box_end - box_start)
-            .set(
-                "fill",
-                style
-                    .get_fill()
-                    .clone()
-                    .unwrap_or_else(|| "burlywood".into()),
-            )
+            .set("fill", style.get_fill())
             .set("stroke", "black"),
     );
 
@@ -431,13 +421,7 @@ where
             .set("y", box_start)
             .set("width", box_width)
             .set("height", box_end - box_start)
-            .set(
-                "fill",
-                style
-                    .get_fill()
-                    .clone()
-                    .unwrap_or_else(|| "burlywood".into()),
-            )
+            .set("fill", style .get_fill())
             .set("stroke", "black"),
     );
 
diff --git a/src/text_render.rs b/src/text_render.rs
index bf7be31..91fd61f 100644
--- a/src/text_render.rs
+++ b/src/text_render.rs
@@ -361,7 +361,7 @@ pub fn render_face_points(
             )
         }).collect();
 
-    let marker = match style.get_marker().clone().unwrap_or(style::PointMarker::Circle) {
+    let marker = match style.get_marker() {
         style::PointMarker::Circle => '●',
         style::PointMarker::Square => '■',
         style::PointMarker::Cross => '×',
@@ -626,11 +626,11 @@ mod tests {
             (6.4, 4.3),
             (8.5, 3.7),
         ];
-        let s = repr::Scatter::from_slice(&data);
         let x_axis = axis::ContinuousAxis::new(-3.575, 9.075, 6);
         let y_axis = axis::ContinuousAxis::new(-1.735, 5.635, 6);
         let style = PointStyle::new();
-        let strings = render_face_points(&s.data, &x_axis, &y_axis, 20, 10, &style);
+        //TODO NEXT
+        let strings = render_face_points(&data, &x_axis, &y_axis, 20, 10, &style);
         assert_eq!(strings.lines().count(), 10);
         assert!(strings.lines().all(|s| s.chars().count() == 20));
 


Test patch:
diff --git a/tests/test_no_data.rs b/tests/test_no_data.rs
index 9f6086e..18faaba 100644
--- a/tests/test_no_data.rs
+++ b/tests/test_no_data.rs
@@ -1,15 +1,15 @@
 use plotlib::page::Page;
-use plotlib::repr::Scatter;
+use plotlib::repr::Plot;
 use plotlib::style::{PointMarker, PointStyle};
 use plotlib::view::ContinuousView;
 
 #[test]
 fn test_data_with_one_length() {
     // Scatter plots expect a list of pairs
-    let data1 = [(-3.0, 2.3)];
+    let data1 = vec![(-3.0, 2.3)];
 
     // We create our scatter plot from the data
-    let s1 = Scatter::from_slice(&data1).style(
+    let s1 = Plot::new(data1).point_style(
         PointStyle::new()
             .marker(PointMarker::Square) // setting the marker to be a square
             .colour("#DD3355"),
@@ -32,10 +32,10 @@ fn test_data_with_one_length() {
 #[test]
 fn test_data_with_no_length() {
     // Scatter plots expect a list of pairs
-    let data1 = [];
+    let data1 = vec![];
 
     // We create our scatter plot from the data
-    let s1 = Scatter::from_slice(&data1).style(
+    let s1 = Plot::new(data1).point_style(
         PointStyle::new()
             .marker(PointMarker::Square) // setting the marker to be a square
             .colour("#DD3355"),
@@ -59,10 +59,10 @@ fn test_data_with_no_length() {
 #[should_panic(expected = "Invalid x_range")]
 fn test_data_with_one_length_and_autoscaling_axes_limits() {
     // Scatter plots expect a list of pairs
-    let data1 = [(-3.0, 2.3)];
+    let data1 = vec![(-3.0, 2.3)];
 
     // We create our scatter plot from the data
-    let s1 = Scatter::from_slice(&data1).style(
+    let s1 = Plot::new(data1).point_style(
         PointStyle::new()
             .marker(PointMarker::Square) // setting the marker to be a square
             .colour("#DD3355"),