fortplot_bar_rendering.f90 Source File


Source Code

module fortplot_bar_rendering
    !! Bar plot rendering for memory/vector backends
    !!
    !! Renders vertical and horizontal bars using backend quadrilateral fills.

    use, intrinsic :: iso_fortran_env, only: wp => real64
    use fortplot_context
    use fortplot_plot_data, only: plot_data_t
    use fortplot_scales, only: apply_scale_transform
    implicit none

    private
    public :: render_bar_plot, render_histogram_plot

contains

    subroutine render_bar_plot(backend, plot_data, xscale, yscale, symlog_threshold)
        !! Render bar plots (vertical or horizontal) for the active backend
        class(plot_context), intent(inout) :: backend
        type(plot_data_t), intent(in) :: plot_data
        character(len=*), intent(in) :: xscale, yscale
        real(wp), intent(in) :: symlog_threshold

        call render_rectangular_bars(backend, plot_data, xscale, yscale, &
                                     symlog_threshold, draw_edges=.true.)
    end subroutine render_bar_plot

    subroutine render_histogram_plot(backend, plot_data, xscale, yscale, symlog_threshold)
        !! Render histogram bins as solid quads without outline seams between bins
        class(plot_context), intent(inout) :: backend
        type(plot_data_t), intent(in) :: plot_data
        character(len=*), intent(in) :: xscale, yscale
        real(wp), intent(in) :: symlog_threshold

        call render_rectangular_bars(backend, plot_data, xscale, yscale, &
                                     symlog_threshold, draw_edges=.false.)
    end subroutine render_histogram_plot

    subroutine render_rectangular_bars(backend, plot_data, xscale, yscale, &
                                       symlog_threshold, draw_edges)
        class(plot_context), intent(inout) :: backend
        type(plot_data_t), intent(in) :: plot_data
        character(len=*), intent(in) :: xscale, yscale
        real(wp), intent(in) :: symlog_threshold
        logical, intent(in) :: draw_edges

        integer :: i, j, n
        real(wp), parameter :: DEFAULT_BAR_WIDTH = 0.8_wp
        real(wp) :: half_width
        real(wp) :: x_data(4), y_data(4)
        real(wp) :: x_screen(4), y_screen(4)
        real(wp) :: effective_width
        real(wp) :: base_val, top_val
        real(wp) :: fill_color(3), edge_color(3)
        real(wp) :: alpha

        if (.not. allocated(plot_data%bar_x)) return
        if (.not. allocated(plot_data%bar_heights)) return

        n = min(size(plot_data%bar_x), size(plot_data%bar_heights))
        if (n <= 0) return

        effective_width = abs(plot_data%bar_width)
        if (effective_width <= 0.0_wp) effective_width = DEFAULT_BAR_WIDTH
        half_width = 0.5_wp * effective_width

        if (plot_data%bar_color_per_bar_set) then
            fill_color = plot_data%bar_color_per_bar(:, 1)
        else
            fill_color = plot_data%color
        end if
        if (draw_edges .and. plot_data%bar_edgecolor_set) then
            edge_color = plot_data%bar_edgecolor
        else
            edge_color = fill_color
        end if
        alpha = max(0.0_wp, min(1.0_wp, plot_data%fill_alpha))
        if (alpha <= 1.0e-6_wp) return

        do i = 1, n
            fill_color = plot_data%color
            if (plot_data%bar_color_per_bar_set) then
                fill_color = plot_data%bar_color_per_bar(:, i)
            end if
            if (draw_edges .and. plot_data%bar_edgecolor_per_bar_set) then
                edge_color = plot_data%bar_edgecolor_per_bar(:, i)
            else if (draw_edges .and. plot_data%bar_edgecolor_set) then
                edge_color = plot_data%bar_edgecolor
            else
                edge_color = fill_color
            end if
            fill_color = blend_color(fill_color, alpha)
            edge_color = blend_color(edge_color, alpha)
            ! Get base offset (bottom for vertical, left for horizontal)
            if (allocated(plot_data%bar_bottom) .and. i <= size(plot_data%bar_bottom)) then
                base_val = plot_data%bar_bottom(i)
            else
                base_val = 0.0_wp
            end if
            top_val = base_val + plot_data%bar_heights(i)

            if (plot_data%bar_horizontal) then
                ! Horizontal bars: base_val is left edge, top_val is right edge
                x_data = [base_val, top_val, top_val, base_val]
                y_data = [plot_data%bar_x(i) - half_width, &
                          plot_data%bar_x(i) - half_width, &
                          plot_data%bar_x(i) + half_width, &
                          plot_data%bar_x(i) + half_width]
            else
                ! Vertical bars: base_val is bottom, top_val is top
                x_data = [plot_data%bar_x(i) - half_width, &
                          plot_data%bar_x(i) + half_width, &
                          plot_data%bar_x(i) + half_width, &
                          plot_data%bar_x(i) - half_width]
                y_data = [base_val, base_val, top_val, top_val]
            end if

            do j = 1, 4
                x_screen(j) = apply_scale_transform(x_data(j), xscale, symlog_threshold)
                y_screen(j) = apply_scale_transform(y_data(j), yscale, symlog_threshold)
            end do

            call backend%color(fill_color(1), fill_color(2), fill_color(3))
            call backend%fill_quad(x_screen, y_screen)
            if (draw_edges) then
                call backend%color(edge_color(1), edge_color(2), edge_color(3))
                call backend%line(x_screen(1), y_screen(1), x_screen(2), y_screen(2))
                call backend%line(x_screen(2), y_screen(2), x_screen(3), y_screen(3))
                call backend%line(x_screen(3), y_screen(3), x_screen(4), y_screen(4))
                call backend%line(x_screen(4), y_screen(4), x_screen(1), y_screen(1))
            end if
        end do
    end subroutine render_rectangular_bars

    pure function blend_color(color, alpha) result(blended)
        real(wp), intent(in) :: color(3)
        real(wp), intent(in) :: alpha
        real(wp) :: blended(3)
        real(wp) :: alpha_clamped

        alpha_clamped = max(0.0_wp, min(1.0_wp, alpha))
        blended = alpha_clamped*color + (1.0_wp - alpha_clamped)
    end function blend_color

end module fortplot_bar_rendering