Histogram of floating point data with plotters-rs (blank graph?)

I think I may have run into a bug in the plotters crate, but it is such a common task on a widely used crate that I think I must be doing something wrong. I'm unable to draw a histogram of floating point data; indeed, all the documentation and online samples I've been able to find use integers for the domain.

Here's my code. I've trimmed it down as much as I can to a standalone sample. It runs fine, but displays only the axes and labels, no data. If I cast the domain values to i32, it displays the bars (see the 2 commented out lines).

Should I file a bug report on plotters-rs/plotters? Or can you spot the error in my code?!

By the way, these bugs are possibly related (closest I could find): #470 histograms fail silently, #300 histogram drawn with negative width rects

Thank you!

Tim

use anyhow::{bail, Result};
use ndarray::prelude::*;
use ndarray_rand::{rand_distr::StandardNormal, RandomExt};
use ndarray_stats::histogram::{strategies::Auto, Grid, GridBuilder, HistogramExt};
use noisy_float::types::{r64, R64};
use plotters::{prelude::*, style::full_palette as palette};
use std::{fmt, ops::Range};

fn main() -> Result<()> {
    // Histogram wants a 2D array, and it's fine for the second dimension to be 1.
    let a = Array::<f64, _>::random((1_000, 1), StandardNormal).mapv(r64);
    let a = a * 5.3 + 1.65;

    let grid = GridBuilder::<Auto<R64>>::from_array(&a).unwrap().build();
    println!("grid: shape {:?}, {grid:?}", grid.shape());

    // FIXME: Is this really necessary? How do people normally do this?
    let ranges = extract_grid_ranges(&grid)?;
    println!("\nranges: {} {ranges:?}", ranges.len());

    let hist = a.histogram(grid);
    println!("\nhist: counts {:?}", hist.counts());

    let root_area = BitMapBackend::new("histogram.png", (800, 600)).into_drawing_area();
    root_area.fill(&palette::GREY_300).unwrap();

    let mut ctx = ChartBuilder::on(&root_area)
        .set_label_area_size(LabelAreaPosition::Left, 40)
        .set_label_area_size(LabelAreaPosition::Right, 40)
        .set_label_area_size(LabelAreaPosition::Bottom, 40)
        .caption("Histogram from Grid", ("sans-serif", 40))
        .build_cartesian_2d((-20.0..20.0).step(1.0), 0..130)
        // INTEGER WORKS: .build_cartesian_2d(-20..20, 0..130)
        .unwrap();

    ctx.configure_mesh().draw().unwrap();

    let counts = hist.counts();
    let data = ranges
        .iter()
        .zip(counts.iter())
        .map(|(r, c)| (r.start, *c as i32))
        // INTEGER WORKS: .map(|(r, c)| (r.start.raw() as i32, *c as i32))
        .collect::<Vec<_>>();
    println!("\nCharting data: {data:?}");
    ctx.draw_series(
        Histogram::vertical(&ctx)
            .style(BLUE.filled())
            .margin(2)
            .data(data),
    )
    .unwrap();

    root_area.present()?;

    Ok(())
}

fn grid_size<A>(grid: &Grid<A>) -> Result<usize>
where
    A: Ord,
{
    if grid.ndim() != 1 {
        bail!("Only implemented for 1-dimensional grids, sorry");
    }
    Ok(*grid.shape().first().unwrap())
}

fn extract_grid_ranges<A>(grid: &Grid<A>) -> Result<Vec<Range<A>>>
where
    A: Ord + Clone + fmt::Debug,
{
    let size = grid_size(grid)?;
    Ok((0..size)
        .flat_map(|i| grid.index(&[i]))
        .collect::<Vec<Range<A>>>())
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.