fortplot_plot_bars.f90 Source File


Source Code

module fortplot_plot_bars
    !! Bar chart plotting functionality
    !! 
    !! Provides:
    !! - Vertical bar charts (bar)
    !! - Horizontal bar charts (barh)
    !! - Automatic bar width calculation
    !! - Color management for bar plots
    
    use, intrinsic :: iso_fortran_env, only: wp => real64
    use fortplot_figure_core, only: figure_t
    use fortplot_figure_initialization, only: figure_state_t
    use fortplot_plot_data, only: plot_data_t, PLOT_TYPE_BAR
    use fortplot_figure_core_ranges, only: update_data_ranges_figure
    implicit none
    
    private
    public :: bar_impl, barh_impl
    
contains
    
    subroutine bar_impl(self, x, heights, width, label, color)
        !! Add vertical bar plot
        class(figure_t), intent(inout) :: self
        real(wp), intent(in) :: x(:), heights(:)
        real(wp), intent(in), optional :: width
        character(len=*), intent(in), optional :: label
        real(wp), intent(in), optional :: color(3)
        
        call add_bar_plot_data(self, x, heights, width, label, color, horizontal=.false.)
    end subroutine bar_impl
    
    subroutine barh_impl(self, y, widths, height, label, color)
        !! Add horizontal bar plot
        class(figure_t), intent(inout) :: self
        real(wp), intent(in) :: y(:), widths(:)
        real(wp), intent(in), optional :: height
        character(len=*), intent(in), optional :: label
        real(wp), intent(in), optional :: color(3)
        
        call add_bar_plot_data(self, y, widths, height, label, color, horizontal=.true.)
    end subroutine barh_impl
    
    ! Private helper subroutines
    
    subroutine add_bar_plot_data(self, positions, values, bar_size, label, color, horizontal)
        !! Add bar plot data (handles both vertical and horizontal)
        class(figure_t), intent(inout) :: self
        real(wp), intent(in) :: positions(:), values(:)
        real(wp), intent(in), optional :: bar_size
        character(len=*), intent(in), optional :: label
        real(wp), intent(in), optional :: color(3)
        logical, intent(in) :: horizontal
        
        integer :: plot_idx, color_idx, i
        real(wp), parameter :: DEFAULT_BAR_WIDTH = 0.8_wp
        real(wp), parameter :: HUGE_SPACING = huge(1.0_wp)
        real(wp) :: min_spacing
        
        self%plot_count = self%plot_count + 1
        plot_idx = self%plot_count
        
        ! Ensure plots array is allocated
        if (.not. allocated(self%plots)) then
            allocate(self%plots(self%state%max_plots))
        else if (plot_idx > size(self%plots)) then
            return
        end if
        
        self%plots(plot_idx)%plot_type = PLOT_TYPE_BAR
        
        allocate(self%plots(plot_idx)%bar_x(size(positions)))
        allocate(self%plots(plot_idx)%bar_heights(size(values)))
        
        self%plots(plot_idx)%bar_x = positions
        self%plots(plot_idx)%bar_heights = values
        
        if (present(bar_size)) then
            self%plots(plot_idx)%bar_width = abs(bar_size)
            if (self%plots(plot_idx)%bar_width <= 0.0_wp) then
                self%plots(plot_idx)%bar_width = DEFAULT_BAR_WIDTH
            end if
        else
            ! Default bar width calculation
            if (size(positions) > 1) then
                min_spacing = HUGE_SPACING
                do i = 1, size(positions) - 1
                    min_spacing = min(min_spacing, abs(positions(i + 1) - positions(i)))
                end do
                if (min_spacing <= 0.0_wp .or. min_spacing == HUGE_SPACING) then
                    self%plots(plot_idx)%bar_width = DEFAULT_BAR_WIDTH
                else
                    self%plots(plot_idx)%bar_width = 0.8_wp * min_spacing
                end if
            else
                self%plots(plot_idx)%bar_width = DEFAULT_BAR_WIDTH
            end if
        end if

        self%plots(plot_idx)%bar_horizontal = horizontal
        
        if (present(color)) then
            self%plots(plot_idx)%color = color
        else
            ! Default color cycling
            color_idx = mod(plot_idx - 1, 6) + 1
            self%plots(plot_idx)%color = self%state%colors(:, color_idx)
        end if
        
        if (present(label) .and. len_trim(label) > 0) then
            self%plots(plot_idx)%label = label
        end if

        self%state%plot_count = self%plot_count
        call update_data_ranges_figure(self%plots, self%state, self%plot_count)
    end subroutine add_bar_plot_data

end module fortplot_plot_bars