fortplot_errorbar_plots.f90 Source File


Source Code

module fortplot_errorbar_plots
    !! Error bar plot operations module
    !! 
    !! This module handles error bar plotting functionality including
    !! symmetric and asymmetric error bars for both X and Y directions.

    use, intrinsic :: iso_fortran_env, only: wp => real64
    use fortplot_figure_core, only: figure_t
    use fortplot_plot_data, only: plot_data_t, PLOT_TYPE_ERRORBAR
    use fortplot_format_parser, only: parse_format_string
    use fortplot_colors, only: parse_color

    implicit none

    private
    public :: errorbar_impl

    interface errorbar
        module procedure errorbar_impl
    end interface
    public :: errorbar

contains

    subroutine errorbar_impl(self, x, y, xerr, yerr, xerr_lower, xerr_upper, &
                            yerr_lower, yerr_upper, label, marker, markersize, &
                            ecolor, elinewidth, capsize, capthick, color, &
                            linestyle, fmt)
        !! Add error bar plot to figure
        class(figure_t), intent(inout) :: self
        real(wp), contiguous, intent(in) :: x(:), y(:)
        real(wp), intent(in), optional :: xerr(:), yerr(:)
        real(wp), intent(in), optional :: xerr_lower(:), xerr_upper(:)
        real(wp), intent(in), optional :: yerr_lower(:), yerr_upper(:)
        character(len=*), intent(in), optional :: label
        character(len=*), intent(in), optional :: marker
        real(wp), intent(in), optional :: markersize
        real(wp), intent(in), optional :: ecolor(3)
        real(wp), intent(in), optional :: elinewidth, capsize, capthick
        real(wp), intent(in), optional :: color(3)
        character(len=*), intent(in), optional :: linestyle, fmt
        
        integer :: plot_idx
        self%plot_count = self%plot_count + 1
        plot_idx = self%plot_count
        ! Keep the figure-state plot count in sync so legend setup (which reads
        ! state%plot_count) sees this errorbar entry. Mirrors plot()/scatter().
        self%state%plot_count = 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_ERRORBAR
        
        allocate(self%plots(plot_idx)%x(size(x)))
        allocate(self%plots(plot_idx)%y(size(y)))
        self%plots(plot_idx)%x = x
        self%plots(plot_idx)%y = y
        
        ! Handle symmetric error bars
        if (present(xerr)) then
            allocate(self%plots(plot_idx)%xerr(size(xerr)))
            self%plots(plot_idx)%xerr = xerr
            self%plots(plot_idx)%has_xerr = .true.
        end if
        
        if (present(yerr)) then
            allocate(self%plots(plot_idx)%yerr(size(yerr)))
            self%plots(plot_idx)%yerr = yerr
            self%plots(plot_idx)%has_yerr = .true.
        end if
        
        ! Handle asymmetric error bars
        if (present(xerr_lower) .and. present(xerr_upper)) then
            allocate(self%plots(plot_idx)%xerr_lower(size(xerr_lower)))
            allocate(self%plots(plot_idx)%xerr_upper(size(xerr_upper)))
            self%plots(plot_idx)%xerr_lower = xerr_lower
            self%plots(plot_idx)%xerr_upper = xerr_upper
            self%plots(plot_idx)%has_xerr = .true.
            self%plots(plot_idx)%asymmetric_xerr = .true.
        end if
        
        if (present(yerr_lower) .and. present(yerr_upper)) then
            allocate(self%plots(plot_idx)%yerr_lower(size(yerr_lower)))
            allocate(self%plots(plot_idx)%yerr_upper(size(yerr_upper)))
            self%plots(plot_idx)%yerr_lower = yerr_lower
            self%plots(plot_idx)%yerr_upper = yerr_upper
            self%plots(plot_idx)%has_yerr = .true.
            self%plots(plot_idx)%asymmetric_yerr = .true.
        end if
        
        call configure_errorbar_style(self%plots(plot_idx), &
                                      self%state%colors(:, &
                                      mod(plot_idx - 1, size(self%state%colors, 2)) + 1), &
                                      self%state%current_line_width, label, marker, &
                                      markersize, ecolor, elinewidth, capsize, &
                                      capthick, color, linestyle, fmt)
    end subroutine errorbar_impl

    subroutine configure_errorbar_style(plot, default_color, default_linewidth, &
                                        label, marker, markersize, ecolor, elinewidth, &
                                        capsize, capthick, color, linestyle, fmt)
        type(plot_data_t), intent(inout) :: plot
        real(wp), intent(in) :: default_color(3), default_linewidth
        character(len=*), intent(in), optional :: label, marker, linestyle, fmt
        real(wp), intent(in), optional :: markersize, ecolor(3), elinewidth
        real(wp), intent(in), optional :: capsize, capthick, color(3)
        character(len=20) :: parsed_marker, parsed_linestyle, parsed_color
        real(wp) :: format_color(3)
        logical :: valid_color

        plot%capsize = 0.0_wp
        if (present(capsize)) plot%capsize = max(0.0_wp, capsize)
        plot%elinewidth = default_linewidth
        if (present(elinewidth)) plot%elinewidth = max(0.0_wp, elinewidth)
        ! Cap thickness follows lines.markeredgewidth, independently of elinewidth.
        plot%capthick = 1.0_wp
        if (present(capthick)) plot%capthick = max(0.0_wp, capthick)

        plot%linestyle = '-'
        plot%color = default_color
        if (present(fmt)) then
            if (trim(fmt) == 'none' .or. trim(fmt) == 'None') then
                plot%linestyle = 'none'
            else
                call parse_format_string(fmt, parsed_marker, parsed_linestyle, &
                                         parsed_color)
                if (len_trim(parsed_marker) > 0) plot%marker = parsed_marker
                if (len_trim(parsed_linestyle) > 0) plot%linestyle = parsed_linestyle
                if (len_trim(parsed_color) > 0) then
                    call parse_color(parsed_color, format_color, valid_color)
                    if (valid_color) plot%color = format_color
                end if
            end if
        end if
        if (present(marker)) plot%marker = marker
        if (present(markersize)) then
            plot%scatter_size_default = max(0.0_wp, markersize)**2
        end if
        if (present(linestyle)) plot%linestyle = linestyle
        if (present(color)) plot%color = color
        if (present(ecolor)) then
            plot%errorbar_color = ecolor
            plot%errorbar_color_set = .true.
        end if
        if (present(label)) then
            if (len_trim(label) > 0) plot%label = label
        end if
    end subroutine configure_errorbar_style

end module fortplot_errorbar_plots