fortplot_figure_histogram.f90 Source File


Source Code

module fortplot_figure_histogram
    !! Figure histogram functionality module
    !! 
    !! Single Responsibility: Handle histogram calculation and visualization
    !! Extracted from fortplot_figure_core to improve modularity
    
    use, intrinsic :: iso_fortran_env, only: wp => real64
    use fortplot_plot_data, only: plot_data_t
    use fortplot_figure_initialization, only: figure_state_t
    use fortplot_figure_plots, only: figure_add_plot
    implicit none
    
    private
    public :: calculate_histogram_bins, create_histogram_line_data, hist_figure
    
contains
    
    subroutine calculate_histogram_bins(data, n_bins, normalize_density, &
                                       bin_edges, bin_counts)
        !! Calculate histogram bin edges and counts from data
        real(wp), intent(in) :: data(:)
        integer, intent(in) :: n_bins
        logical, intent(in) :: normalize_density
        real(wp), allocatable, intent(out) :: bin_edges(:), bin_counts(:)
        
        integer :: i, bin_index, n_data
        real(wp) :: data_min, data_max, bin_width
        real(wp) :: total_area
        
        n_data = size(data)
        
        ! Find data range
        data_min = minval(data)
        data_max = maxval(data)
        
        ! Handle case where all data points are the same
        if (abs(data_max - data_min) < epsilon(1.0_wp)) then
            data_min = data_min - 0.5_wp
            data_max = data_max + 0.5_wp
        end if
        
        ! Create bin edges
        allocate(bin_edges(n_bins + 1))
        allocate(bin_counts(n_bins))
        
        bin_width = (data_max - data_min) / real(n_bins, wp)
        
        do i = 1, n_bins + 1
            bin_edges(i) = data_min + real(i - 1, wp) * bin_width
        end do
        
        ! Count data points in each bin
        bin_counts = 0.0_wp
        do i = 1, n_data
            bin_index = min(n_bins, max(1, int((data(i) - data_min) / bin_width) + 1))
            bin_counts(bin_index) = bin_counts(bin_index) + 1.0_wp
        end do
        
        ! Normalize for density if requested
        if (normalize_density) then
            total_area = real(n_data, wp) * bin_width
            bin_counts = bin_counts / total_area
        end if
        
    end subroutine calculate_histogram_bins
    
    subroutine create_histogram_line_data(bin_edges, bin_counts, x_data, y_data)
        !! Create line data for histogram visualization as connected rectangles
        real(wp), intent(in) :: bin_edges(:), bin_counts(:)
        real(wp), allocatable, intent(out) :: x_data(:), y_data(:)
        
        integer :: i, n_bins
        
        n_bins = size(bin_counts)
        allocate(x_data(4 * n_bins + 1), y_data(4 * n_bins + 1))
        
        ! Create line segments for each bar
        do i = 1, n_bins
            ! Bottom left corner
            x_data(4*(i-1) + 1) = bin_edges(i)
            y_data(4*(i-1) + 1) = 0.0_wp
            
            ! Top left corner
            x_data(4*(i-1) + 2) = bin_edges(i)
            y_data(4*(i-1) + 2) = bin_counts(i)
            
            ! Top right corner
            x_data(4*(i-1) + 3) = bin_edges(i + 1)
            y_data(4*(i-1) + 3) = bin_counts(i)
            
            ! Bottom right corner
            x_data(4*(i-1) + 4) = bin_edges(i + 1)
            y_data(4*(i-1) + 4) = 0.0_wp
        end do
        
        ! Close the path back to origin
        x_data(4 * n_bins + 1) = bin_edges(1)
        y_data(4 * n_bins + 1) = 0.0_wp
        
    end subroutine create_histogram_line_data

    subroutine hist_figure(plots, state, plot_count, data, bins, density, label, color)
        !! Add histogram to figure plots array
        type(plot_data_t), intent(inout) :: plots(:)
        type(figure_state_t), intent(inout) :: state
        integer, intent(inout) :: plot_count
        real(wp), intent(in) :: data(:)
        integer, intent(in), optional :: bins
        logical, intent(in), optional :: density
        character(len=*), intent(in), optional :: label
        real(wp), intent(in), optional :: color(3)
        
        integer :: n_bins
        logical :: normalize_density
        real(wp), allocatable :: bin_edges(:), bin_counts(:), x_data(:), y_data(:)
        
        ! Set defaults
        n_bins = 10
        if (present(bins)) n_bins = bins

        ! Guard against invalid inputs (graceful no-op)
        if (size(data) == 0) then
            return
        end if
        if (n_bins <= 0) then
            return
        end if

        normalize_density = .false.
        if (present(density)) normalize_density = density

        ! Calculate histogram (validated inputs)
        call calculate_histogram_bins(data, n_bins, normalize_density, bin_edges, bin_counts)
        
        ! Create line data for visualization
        call create_histogram_line_data(bin_edges, bin_counts, x_data, y_data)
        
        ! Add as line plot
        call figure_add_plot(plots, state, x_data, y_data, label, color=color)
        plot_count = plot_count + 1
    end subroutine hist_figure

end module fortplot_figure_histogram