Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot plot series with zero or one values #7

Closed
DouglasLivingstone opened this issue Jul 15, 2018 · 2 comments
Closed

Cannot plot series with zero or one values #7

DouglasLivingstone opened this issue Jul 15, 2018 · 2 comments
Labels
Milestone

Comments

@DouglasLivingstone
Copy link

Thanks for putting this together, outputting a SVG is a great fit for my CLI app, so looking forward to trying this out some more.

I've been experimenting using this package to plot some "real" data, and some of it is a little boring, including a series with only a single value, but unfortunately this panics on the 0.3.0 tag since the default axis calculation needs at least two different values in the series.

With

[dependencies]
plotlib= { git = "https://github.com/milliams/plotlib", tag = "0.3.0" }

a cut down version of the example in the readme, with a single value in data1:

extern crate plotlib;
use plotlib::scatter::Scatter;
use plotlib::scatter;
use plotlib::style::{Marker, Point};
use plotlib::view::View;
use plotlib::page::Page;

fn main() {
    // Scatter plots expect a list of pairs
    let data1 = [(-3.0, 2.3)];

    // We create our scatter plot from the data
    let s1 = Scatter::from_vec(&data1)
        .style(scatter::Style::new()
            .marker(Marker::Square) // setting the marker to be a square
            .colour("#DD3355")); // and a custom colour

    // The 'view' describes what set of data is drawn
    let v = View::new()
        .add(&s1)
        .x_range(-5., 10.)
        .y_range(-2., 6.)
        .x_label("Some varying variable")
        .y_label("The response of something");

    // A page with a single view is then saved to an SVG file
    Page::single(&v).save("scatter.svg");
}

produces this error at runtime:

thread 'main' panicked at 'assertion failed: lower < upper', D:\rust\cargo\git\checkouts\plotlib-407c626431dc1ac2\8ed7480\src\axis.rs:15:9
stack backtrace:
   0: std::sys::windows::backtrace::unwind_backtrace
             at C:\projects\rust\src\libstd\sys\windows\backtrace\mod.rs:65
   1: std::sys_common::backtrace::_print
             at C:\projects\rust\src\libstd\sys_common\backtrace.rs:71
   2: std::sys_common::backtrace::print
             at C:\projects\rust\src\libstd\sys_common\backtrace.rs:59
   3: std::panicking::default_hook::{{closure}}
             at C:\projects\rust\src\libstd\panicking.rs:211
   4: std::panicking::default_hook
             at C:\projects\rust\src\libstd\panicking.rs:227
   5: std::panicking::rust_panic_with_hook
             at C:\projects\rust\src\libstd\panicking.rs:463
   6: std::panicking::begin_panic<str*>
             at C:\projects\rust\src\libstd\panicking.rs:397
   7: plotlib::axis::Range::new
             at D:\rust\cargo\git\checkouts\plotlib-407c626431dc1ac2\8ed7480\src\axis.rs:15
   8: plotlib::view::View::default_x_range
             at D:\rust\cargo\git\checkouts\plotlib-407c626431dc1ac2\8ed7480\src\view.rs:88
   9: plotlib::view::View::create_axes
             at D:\rust\cargo\git\checkouts\plotlib-407c626431dc1ac2\8ed7480\src\view.rs:103
  10: plotlib::view::View::to_svg
             at D:\rust\cargo\git\checkouts\plotlib-407c626431dc1ac2\8ed7480\src\view.rs:127
  11: plotlib::page::Page::to_svg
             at D:\rust\cargo\git\checkouts\plotlib-407c626431dc1ac2\8ed7480\src\page.rs:40
  12: plotlib::page::Page::save<str*>
             at D:\rust\cargo\git\checkouts\plotlib-407c626431dc1ac2\8ed7480\src\page.rs:68
  13: plot_test::main
             at .\src\main.rs:27
  14: std::rt::lang_start::{{closure}}<()>
             at C:\projects\rust\src\libstd\rt.rs:74
  15: std::rt::lang_start_internal::{{closure}}
             at C:\projects\rust\src\libstd\rt.rs:59
  16: std::panicking::try::do_call<closure,i32>
             at C:\projects\rust\src\libstd\panicking.rs:310
  17: panic_unwind::__rust_maybe_catch_panic
             at C:\projects\rust\src\libpanic_unwind\lib.rs:105
  18: std::panicking::try
             at C:\projects\rust\src\libstd\panicking.rs:289
  19: std::panic::catch_unwind
             at C:\projects\rust\src\libstd\panic.rs:374
  20: std::rt::lang_start_internal
             at C:\projects\rust\src\libstd\rt.rs:58
  21: std::rt::lang_start<()>
             at C:\projects\rust\src\libstd\rt.rs:74
  22: main
  23: invoke_main
             at f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl:78
  24: __scrt_common_main_seh
             at f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl:283
  25: BaseThreadInitThunk
  26: RtlUserThreadStart
error: process didn't exit successfully: `target\debug\plot_test.exe` (exit code: 101)

It looks like the problem is that when there is a single data point, x_range in scatter.ts,

    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)
    }

returns the same value for min and max, so the assert!(lower < upper); fails.

It also fails if there are no entries in self.data, since it will leave lower as f64::INFINITY and upper as f64::NEG_INFINITY.

I'm just getting started with Rust, but a pattern that I've seen in other code is to use a fold to produce values when the collection may be empty:

    fn x_range(&self) -> Option<(f64, f64)> {
        self.data.iter().fold(None, |result, &(x, _)| match result {
            None => Some((x, x)),
            Some((min, max)) => Some((min.min(x), max.max(x))),
        })
    }

That could be a fix for the no entries situation, for a single entry the assert probably needs to be relaxed, or the caller could define a default range of say min to min + 1 if min == max.

@DouglasLivingstone DouglasLivingstone changed the title Cannot plot series with a zero or one value Cannot plot series with zero or one values Jul 15, 2018
@milliams milliams added the bug label Jul 16, 2018
@milliams
Copy link
Owner

Indeed that is a case that is not handled very well yet. I general I need to develop a proper error handling solution for plotlib. Likely this will use something like failure.

In the short term, I think that in the case you showed above, if you explicitly specified the x and y range then it shouldn't need to check the ranges of the data set and so shouldn't matter about the number of data points.

Also, it should definitely work for a single-valued data set as we can special-case it to set the min and max to the same value.

For the zero-length data set, we could filter those out at the view level and ignore them when plotting.

simonrw added a commit to simonrw/plotlib that referenced this issue Jul 19, 2018
This is only a partial implementation, driven by milliams#7. Instead of
asserting, we return an error with a (hopefully) helpful error message.

The error types are then propagated through the call stack, using
`context` where possible to add more information.
This was referenced Jul 19, 2018
@milliams
Copy link
Owner

This is now closed thanks to @mindriot101's work in #10. It will be fixed in the next release.

@milliams milliams added this to the 0.4.0 milestone Sep 16, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants