fortplot_figure_core_fill_between.f90 Source File


Source Code

submodule(fortplot_figure_core) fortplot_figure_core_fill_between

    implicit none

contains

    module subroutine add_fill_between(self, x, y1, y2, where, color, alpha, &
                                       interpolate)
        class(figure_t), intent(inout) :: self
        real(wp), contiguous, intent(in) :: x(:), y1(:)
        real(wp), intent(in), optional :: y2(:)
        logical, intent(in), optional :: where (:)
        character(len=*), intent(in), optional :: color
        real(wp), intent(in), optional :: alpha
        logical, intent(in), optional :: interpolate

        integer :: n
        real(wp), allocatable :: upper_vals(:), lower_vals(:)
        logical, allocatable :: mask_vals(:)
        logical :: has_mask

        n = size(x)
        if (.not. prepare_fill_between_values(n, y1, y2, where, upper_vals, &
                                              lower_vals, mask_vals, has_mask)) then
            call cleanup_fill_between_values(upper_vals, lower_vals, mask_vals)
            return
        end if

        if (present(interpolate)) then
            call log_warning('fill_between: interpolate option ignored')
        end if

        if (has_mask) then
            call add_prepared_fill_between(self, x, upper_vals, lower_vals, &
                                           mask=mask_vals, color=color, alpha=alpha)
        else
            call add_prepared_fill_between(self, x, upper_vals, lower_vals, &
                                           color=color, alpha=alpha)
        end if

        self%plot_count = self%state%plot_count

    end subroutine add_fill_between

    subroutine cleanup_fill_between_values(upper_vals, lower_vals, mask_vals)
        real(wp), allocatable, intent(inout) :: upper_vals(:), lower_vals(:)
        logical, allocatable, intent(inout) :: mask_vals(:)

        if (allocated(upper_vals)) deallocate (upper_vals)
        if (allocated(lower_vals)) deallocate (lower_vals)
        if (allocated(mask_vals)) deallocate (mask_vals)
    end subroutine cleanup_fill_between_values

    logical function prepare_fill_between_values(n, y1, y2, where, upper_vals, &
                                                 lower_vals, mask_vals, has_mask) &
        result(ok)
        integer, intent(in) :: n
        real(wp), contiguous, intent(in) :: y1(:)
        real(wp), intent(in), optional :: y2(:)
        logical, intent(in), optional :: where(:)
        real(wp), allocatable, intent(out) :: upper_vals(:), lower_vals(:)
        logical, allocatable, intent(out) :: mask_vals(:)
        logical, intent(out) :: has_mask

        ok = .false.
        has_mask = .false.
        if (n < 2) then
            call log_error('fill_between: need at least two points to form area')
            return
        end if
        if (size(y1) /= n) then
            call log_error('fill_between: y1 size mismatch')
            return
        end if

        allocate (upper_vals(n), lower_vals(n))
        upper_vals = y1
        if (.not. assign_fill_between_bound(n, y2, lower_vals, 'y2')) return
        if (.not. assign_fill_between_mask(n, where, mask_vals, has_mask)) return
        ok = .true.
    end function prepare_fill_between_values

    logical function assign_fill_between_bound(n, source, target, name) result(ok)
        integer, intent(in) :: n
        real(wp), intent(in), optional :: source(:)
        real(wp), intent(out) :: target(:)
        character(len=*), intent(in) :: name

        ok = .false.
        if (present(source)) then
            if (size(source) /= n) then
                call log_error('fill_between: '//name//' size mismatch')
                return
            end if
            target = source
        else
            target = 0.0_wp
        end if
        ok = .true.
    end function assign_fill_between_bound

    logical function assign_fill_between_mask(n, where, mask_vals, has_mask) &
        result(ok)
        integer, intent(in) :: n
        logical, intent(in), optional :: where(:)
        logical, allocatable, intent(out) :: mask_vals(:)
        logical, intent(out) :: has_mask

        ok = .false.
        has_mask = .false.
        if (.not. present(where)) then
            ok = .true.
            return
        end if
        if (size(where) /= n) then
            call log_error('fill_between: where mask size mismatch')
            return
        end if
        allocate (mask_vals(n))
        mask_vals = where
        if (.not. any(mask_vals)) then
            call log_warning('fill_between: mask excludes all data points')
            return
        end if
        has_mask = .true.
        ok = .true.
    end function assign_fill_between_mask

    subroutine add_prepared_fill_between(self, x, upper_vals, lower_vals, mask, &
                                         color, alpha)
        class(figure_t), intent(inout) :: self
        real(wp), contiguous, intent(in) :: x(:), upper_vals(:), lower_vals(:)
        logical, intent(in), optional :: mask(:)
        character(len=*), intent(in), optional :: color
        real(wp), intent(in), optional :: alpha

        integer :: row, col
        type(figure_state_t) :: panel_state

        if (self%subplot_rows > 0 .and. self%subplot_cols > 0) then
            row = (self%current_subplot - 1)/self%subplot_cols + 1
            col = mod(self%current_subplot - 1, self%subplot_cols) + 1
            associate (panel => self%subplots_array(row, col))
                panel_state%colors = self%state%colors
                panel_state%plot_count = panel%plot_count
                panel_state%max_plots = panel%max_plots
                call core_add_fill_between(panel%plots, panel_state, x, upper_vals, &
                    lower_vals, mask=mask, color_string=color, alpha=alpha, &
                    plot_count=panel%plot_count)
            end associate
            self%state%rendered = .false.
            return
        end if
        call core_add_fill_between(self%plots, self%state, x, upper_vals, &
            lower_vals, mask=mask, color_string=color, alpha=alpha, &
            plot_count=self%plot_count)

    end subroutine add_prepared_fill_between

end submodule fortplot_figure_core_fill_between