plotters-rs / plotters

A rust drawing library for high quality data plotting for both WASM and native, statically and realtimely 🦀 📈🚀
https://plotters-rs.github.io/home/
MIT License
3.87k stars 281 forks source link

[BUG] Histogram drawn with negative width rects using SVGBackend #300

Open ardouglas opened 3 years ago

ardouglas commented 3 years ago

Describe the bug When generating histogram plots with the SVG backend, the widths of the rect tags are sometimes negative. The same code with the Bitmap backend produces a valid image. svg with negative rects png with normal rects

To Reproduce This code snippet reproduces the issue:

use plotters::{prelude::{ChartBuilder, Histogram, IntoDrawingArea, IntoSegmentedCoord, SVGBackend}, style::{Color, RED, WHITE}};

const OUT_FILE_NAME: &'static str = "histogram.svg";
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let root = SVGBackend::new(OUT_FILE_NAME, (640, 480)).into_drawing_area();

    root.fill(&WHITE)?;
    let min = *DATA.iter().min().unwrap();
    let max = *DATA.iter().max().unwrap();
    let mut chart = ChartBuilder::on(&root)
        .x_label_area_size(35)
        .y_label_area_size(40)
        .margin(5)
        .caption("Histogram Test", ("sans-serif", 50.0))
        .build_cartesian_2d((min..max).into_segmented(), min..max)?;

    chart
        .configure_mesh()
        .disable_x_mesh()
        .bold_line_style(&WHITE.mix(0.3))
        .y_desc("Count")
        .x_desc("Bucket")
        .axis_desc_style(("sans-serif", 15))
        .draw()?;

    chart.draw_series(
        Histogram::vertical(&chart)
            .style(RED.mix(0.5).filled())
            .data(DATA.iter().map(|x: &u32| (*x, 1))),
    )?;

    // To avoid the IO failure being ignored silently, we manually call the present function
    root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir");
    println!("Result has been saved to {}", OUT_FILE_NAME);

    Ok(())
}

const DATA: &[u32] = &[
    17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
    17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
    17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 12, 5, 26, 21, 37, 5, 31, 31, 26,
    26, 5, 31, 26, 71, 15, 15, 15, 80, 42, 5, 37, 21, 15, 40, 15, 45, 56, 82, 26, 63, 66, 11, 28,
    66, 28, 54, 59, 27, 11, 82, 28, 27, 242, 32, 42, 68, 5, 94, 37, 68, 31, 38, 60, 10, 73, 48, 5,
    31, 37, 100, 31, 26, 22, 5, 52, 41, 26, 31, 5, 76, 31, 16, 15, 93, 54, 33, 21, 5, 42, 42, 31,
    37, 32, 11, 37, 37, 37, 32, 5, 58, 51, 21, 11, 5, 10, 78, 47, 32, 5, 21, 68, 73, 74, 32, 5, 37,
    32, 73, 5, 5, 27, 20, 15, 31, 77, 15, 83, 37, 63, 26, 5, 32, 26, 68, 37, 39, 11, 11, 55, 33,
    42, 37, 5, 26, 5, 31, 5, 16, 5, 71, 37, 108, 42, 5, 32, 5, 37, 37, 5, 68, 37, 32, 5, 60, 11,
    27, 17, 16, 11, 48, 5, 37, 5, 37, 37, 37, 16, 27, 5, 68, 31, 5, 11, 32, 5, 26, 11, 37, 31, 31,
    32, 5, 32, 5, 31, 31, 5, 47, 37, 37, 6, 22, 39, 39, 27, 27, 28, 37, 42, 31, 58, 37, 16, 42, 31,
    31, 37, 42, 37, 5, 37, 26, 31, 5, 26, 37, 37, 21, 37, 59, 94, 27, 42, 21, 11, 42, 26, 11, 37,
    37, 26, 5, 26, 21, 42, 26, 10, 37, 47, 42, 32, 5, 37, 37, 5, 37, 31, 5, 37, 31, 37, 37, 5, 21,
    5, 63, 5, 37, 43, 22, 39, 58, 26, 31, 5, 26, 42, 68, 5, 26, 69, 37, 37, 31, 31, 5, 26, 42, 31,
    31, 31, 37, 37, 27, 11, 28, 71, 120, 5, 37, 21, 21, 5, 42, 5, 32, 31, 63, 32, 31, 31, 68, 5,
    26, 5, 68, 31, 21, 31, 21, 5, 31, 43, 50, 38, 43, 5, 32, 11, 11, 5, 26, 48, 37, 73, 31, 42, 37,
    31, 42, 73, 31, 26, 42, 5, 5, 91, 11, 27, 28, 5, 42, 5, 43, 22, 42, 5, 11, 11, 32, 5, 37, 31,
    16, 79, 73, 5, 26, 37, 42, 37, 52, 31, 37, 44, 82, 5, 5, 5, 11, 27, 31, 32, 5, 42, 37, 53, 63,
    37, 31, 31, 68, 21, 11, 63, 26, 42, 31, 57, 26, 75, 75, 58, 42, 37, 48, 26, 5, 126, 42, 31, 32,
    5, 42, 42, 26, 31, 26, 31, 91, 95, 37, 37, 31, 16, 10, 32, 37, 42, 42, 31, 11, 73, 5, 42, 5,
    84, 31, 52, 42, 5, 44, 11, 37, 37, 37, 5, 37, 5, 32, 53, 37, 5, 31, 5, 32, 5, 26, 5, 37, 37,
    31, 31, 31, 26, 63, 26, 52, 10, 15, 57, 58, 38, 38, 26, 37, 42, 37, 31, 73, 10, 42, 5, 32, 74,
    42, 84, 26, 62, 5, 42, 41, 30, 21, 31, 107, 32, 63, 5, 32, 37, 47, 31, 16, 53, 31, 42, 53, 5,
    63, 37, 21, 26, 31, 31, 84, 87, 30, 130, 5, 54, 27, 57, 37, 26, 26, 5, 37, 37, 26, 26, 21, 73,
    5, 48, 63, 31, 31, 32, 5, 58, 5, 31, 47, 30, 15, 10, 26, 73, 26, 52, 26, 63, 31, 26, 26, 11, 5,
    37, 31, 58, 78, 31, 21, 31, 31, 94, 31, 78, 67, 56, 25, 86, 31, 26, 73, 37, 21, 5, 63, 5, 57,
    31, 5, 32, 21, 36, 26, 26, 47, 31, 74, 26, 26, 52, 51, 10, 25, 15, 15, 60, 15, 16, 26, 37, 21,
    11, 73, 26, 58, 31, 37, 31, 31, 21, 37, 5, 26, 21, 26, 31, 5, 31, 31, 21, 26, 37, 53, 42, 52,
    31, 37, 26, 11, 42, 26, 16, 31, 42, 26, 31, 37, 5, 32, 27, 48, 16, 21, 5, 37, 32, 32, 73, 37,
    5, 37, 31, 37, 79, 37, 26, 5, 37, 31, 31, 37, 31, 11, 27, 33, 16, 33, 17, 5, 48, 17, 48, 53,
    37, 26, 26, 11, 37, 42, 26, 36, 31, 5, 37, 5, 31, 37, 37, 26, 5, 37, 31, 31, 21, 16, 59, 43,
    64, 42, 85, 22, 5, 37, 69, 63, 31, 31, 26, 21, 26, 37, 21, 5, 5, 42, 31, 31, 31, 31, 5, 26, 64,
    11, 37, 69, 54, 68, 63, 104, 5, 58, 31, 5, 68, 31, 32, 63, 31, 11, 63, 5, 11, 32, 37, 5, 37,
    48, 11, 27, 16, 37, 37, 37, 31, 42, 32, 79, 26, 42, 37, 47, 31, 52, 37, 42, 31, 37, 5, 42, 5,
    5, 5, 5, 42, 10, 22, 31, 5, 31, 53, 42, 31, 10, 31, 37, 42, 61, 31, 5, 42, 68, 31, 42, 5, 32,
    5, 31, 42, 20, 104, 31, 27, 31, 5, 5, 47, 57, 11, 26, 42, 37, 37, 37, 26, 68, 40, 65, 26, 94,
    26, 15, 10, 15, 25, 40, 25, 15, 15, 15, 95, 15, 55, 18, 17, 17, 17,
];

Version Information plotters = "0.3.1"

shinmili commented 2 years ago

TL;DR: How about removing the bin margin? The PNG has overlapping bins and it's not really perfect either. Removing the margin gives you both histograms with the same and decent looks.

     chart.draw_series(
         Histogram::vertical(&chart)
             .style(RED.mix(0.5).filled())
+            .margin(0)
             .data(DATA.iter().map(|x: &u32| (*x, 1))),
     )?;

Generated PNG Generated SVG


So, here is the code where those negative width happen:

https://github.com/plotters-rs/plotters/blob/a72bfbced3523b17adc974ec7459f6c1c6f2d986/src/element/basic_shapes.rs#L163-L182

Histogram calculated bin widths (b.0 - a.0) to 2 or 3 px in this example. On the other hand, it also had a default left/right margin of 5 px (for each). Adding/Subtracting margin without testing caused left-right inversion (a.0 >= b.0) here. BitMapBackend did draw a rect as if x's are swapped, though SVGBackend generated a negative-width (-7 or -8 px) rect.

I think plotters should detect these kinds of invalid configurations and give a warning (or an error) at least. Auto-correcting can also be beneficial, but I'm not sure how this should be corrected...