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
    public :: bar_plot_state, barh_plot_state

contains

    subroutine bar_impl(self, x, heights, width, bottom, label, color, edgecolor, &
                        color_per_bar, edgecolor_per_bar)
        !! Add vertical bar plot
        class(figure_t), intent(inout) :: self
        real(wp), contiguous, intent(in) :: x(:), heights(:)
        real(wp), intent(in), optional :: width
        real(wp), intent(in), optional :: bottom(:)
        character(len=*), intent(in), optional :: label
        real(wp), intent(in), optional :: color(3)
        real(wp), intent(in), optional :: edgecolor(3)
        real(wp), intent(in), optional :: color_per_bar(3, *)
        real(wp), intent(in), optional :: edgecolor_per_bar(3, *)

        call bar_plot_state(self%plots, self%state, self%plot_count, x, heights, &
                            width, bottom, label, color, edgecolor, &
                            color_per_bar, edgecolor_per_bar)
    end subroutine bar_impl

    subroutine barh_impl(self, y, widths, height, left, label, color, edgecolor, &
                         color_per_bar, edgecolor_per_bar)
        !! Add horizontal bar plot
        class(figure_t), intent(inout) :: self
        real(wp), contiguous, intent(in) :: y(:), widths(:)
        real(wp), intent(in), optional :: height
        real(wp), intent(in), optional :: left(:)
        character(len=*), intent(in), optional :: label
        real(wp), intent(in), optional :: color(3)
        real(wp), intent(in), optional :: edgecolor(3)
        real(wp), intent(in), optional :: color_per_bar(3, *)
        real(wp), intent(in), optional :: edgecolor_per_bar(3, *)

        call barh_plot_state(self%plots, self%state, self%plot_count, y, widths, &
                             height, left, label, color, edgecolor, &
                             color_per_bar, edgecolor_per_bar)
    end subroutine barh_impl

  subroutine bar_plot_state(plots, state, plot_count, x, heights, width, bottom, &
                               label, color, edgecolor, color_per_bar, edgecolor_per_bar)
        type(plot_data_t), intent(inout) :: plots(:)
        type(figure_state_t), intent(inout) :: state
        integer, intent(inout) :: plot_count
        real(wp), contiguous, intent(in) :: x(:), heights(:)
        real(wp), intent(in), optional :: width
        real(wp), intent(in), optional :: bottom(:)
        character(len=*), intent(in), optional :: label
        real(wp), intent(in), optional :: color(3)
        real(wp), intent(in), optional :: edgecolor(3)
        real(wp), intent(in), optional :: color_per_bar(3, *)
        real(wp), intent(in), optional :: edgecolor_per_bar(3, *)

        call add_bar_plot_data(plots, state, plot_count, x, heights, width, &
                               bottom, label, color, edgecolor, &
                               color_per_bar, edgecolor_per_bar, &
                               horizontal=.false.)
    end subroutine bar_plot_state

    subroutine barh_plot_state(plots, state, plot_count, y, widths, height, left, &
                               label, color, edgecolor, color_per_bar, edgecolor_per_bar)
        type(plot_data_t), intent(inout) :: plots(:)
        type(figure_state_t), intent(inout) :: state
        integer, intent(inout) :: plot_count
        real(wp), contiguous, intent(in) :: y(:), widths(:)
        real(wp), intent(in), optional :: height
        real(wp), intent(in), optional :: left(:)
        character(len=*), intent(in), optional :: label
        real(wp), intent(in), optional :: color(3)
        real(wp), intent(in), optional :: edgecolor(3)
        real(wp), intent(in), optional :: color_per_bar(3, *)
        real(wp), intent(in), optional :: edgecolor_per_bar(3, *)

        call add_bar_plot_data(plots, state, plot_count, y, widths, height, left, &
                               label, color, edgecolor, &
                               color_per_bar, edgecolor_per_bar, &
                               horizontal=.true.)
    end subroutine barh_plot_state

    ! Private helper subroutines

  subroutine add_bar_plot_data(plots, state, plot_count, positions, values, &
                                  bar_size, bottom, label, color, edgecolor, &
                                  color_per_bar, edgecolor_per_bar, &
                                  horizontal)
        !! Add bar plot data (handles both vertical and horizontal)
        type(plot_data_t), intent(inout) :: plots(:)
        type(figure_state_t), intent(inout) :: state
        integer, intent(inout) :: plot_count
        real(wp), contiguous, intent(in) :: positions(:), values(:)
        real(wp), intent(in), optional :: bar_size
        real(wp), intent(in), optional :: bottom(:)
        character(len=*), intent(in), optional :: label
        real(wp), intent(in), optional :: color(3)
        real(wp), intent(in), optional :: edgecolor(3)
        real(wp), intent(in), optional :: color_per_bar(3, *)
        real(wp), intent(in), optional :: edgecolor_per_bar(3, *)
        logical, intent(in) :: horizontal

        integer :: plot_idx, color_idx, i, n
        real(wp), parameter :: DEFAULT_BAR_WIDTH = 0.8_wp
        real(wp), parameter :: HUGE_SPACING = huge(1.0_wp)
        real(wp) :: min_spacing

        plot_count = plot_count + 1
        plot_idx = plot_count

        if (plot_idx > size(plots)) then
            return
        end if

        plots(plot_idx)%plot_type = PLOT_TYPE_BAR
        n = size(positions)

        allocate(plots(plot_idx)%bar_x(n))
        allocate(plots(plot_idx)%bar_heights(size(values)))
        allocate(plots(plot_idx)%bar_bottom(n))

        plots(plot_idx)%bar_x = positions
        plots(plot_idx)%bar_heights = values

        ! Set bottom offset (for vertical bars) or left offset (for horizontal bars)
        if (present(bottom)) then
            if (size(bottom) == n) then
                plots(plot_idx)%bar_bottom = bottom
            else if (size(bottom) == 1) then
                plots(plot_idx)%bar_bottom = bottom(1)
            else
                plots(plot_idx)%bar_bottom = 0.0_wp
            end if
        else
            plots(plot_idx)%bar_bottom = 0.0_wp
        end if

        if (present(bar_size)) then
            plots(plot_idx)%bar_width = abs(bar_size)
            if (plots(plot_idx)%bar_width <= 0.0_wp) then
                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
                    plots(plot_idx)%bar_width = DEFAULT_BAR_WIDTH
                else
                    plots(plot_idx)%bar_width = 0.8_wp * min_spacing
                end if
            else
                plots(plot_idx)%bar_width = DEFAULT_BAR_WIDTH
            end if
        end if

        plots(plot_idx)%bar_horizontal = horizontal
        plots(plot_idx)%bar_edgecolor_set = .false.

        if (present(color)) then
            plots(plot_idx)%color = color
        else
            ! Default color cycling
            color_idx = mod(plot_idx - 1, size(state%colors, 2)) + 1
            plots(plot_idx)%color = state%colors(:, color_idx)
        end if

        if (present(edgecolor)) then
            plots(plot_idx)%bar_edgecolor = edgecolor
            plots(plot_idx)%bar_edgecolor_set = .true.
        end if

        if (present(color_per_bar)) then
            allocate(plots(plot_idx)%bar_color_per_bar(3, n))
            plots(plot_idx)%bar_color_per_bar = color_per_bar(:, 1:n)
            plots(plot_idx)%bar_color_per_bar_set = .true.
        end if

        if (present(edgecolor_per_bar)) then
            allocate(plots(plot_idx)%bar_edgecolor_per_bar(3, n))
            plots(plot_idx)%bar_edgecolor_per_bar = edgecolor_per_bar(:, 1:n)
            plots(plot_idx)%bar_edgecolor_per_bar_set = .true.
        end if

        if (present(label) .and. len_trim(label) > 0) then
            plots(plot_idx)%label = label
        end if

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

end module fortplot_plot_bars