fortplot_matplotlib_plot_wrappers.f90 Source File


Source Code

module fortplot_matplotlib_plot_wrappers
    !! Matplotlib-style plot creation wrappers built on top of fortplot figures.
    !!
    !! Each wrapper exposes a matplotlib-compatible signature. Color kwargs
    !! accept either a character string (name, hex, single-letter) or an RGB
    !! triple through generic interfaces defined below. Parameters that have
    !! no visual effect in the current backend are accepted silently so that
    !! matplotlib-style code does not generate spurious runtime warnings.

    use, intrinsic :: iso_fortran_env, only: wp => real64
    use fortplot_global, only: fig => global_figure
    use fortplot_logging, only: log_error
    use fortplot_matplotlib_color_utils, only: resolve_color_string_or_rgb
    use fortplot_matplotlib_session, only: ensure_fig_init
    use fortplot_plotting_advanced, only: bar_impl, barh_impl
    use fortplot_3d_plots, only: add_3d_plot_impl => add_3d_plot
    use fortplot_matplotlib_scatter, only: &
        scatter, add_scatter
    use fortplot_matplotlib_errorbar, only: &
        errorbar, add_errorbar

    implicit none
    private

    public :: plot
    public :: scatter
    public :: errorbar
    public :: boxplot
    public :: bar
    public :: barh
    public :: bar_rgb_array
    public :: barh_rgb_array
    public :: add_plot
    public :: add_errorbar
    public :: add_scatter
    public :: add_3d_plot

    interface boxplot
        module procedure boxplot_string
        module procedure boxplot_rgb
    end interface boxplot

    interface add_3d_plot
        module procedure add_3d_plot_rgb
        module procedure add_3d_plot_string
    end interface add_3d_plot

    interface bar
        module procedure bar_rgb
        module procedure bar_string
        module procedure bar_rgb_edgecolor
    end interface bar

    interface barh
        module procedure barh_rgb
        module procedure barh_string
        module procedure barh_rgb_edgecolor
    end interface barh

    interface add_plot
        module procedure add_plot_rgb
        module procedure add_plot_string
    end interface add_plot

contains

    subroutine plot(x, y, label, linestyle, color, linewidth, marker, markersize)
        real(wp), contiguous, intent(in) :: x(:), y(:)
        character(len=*), intent(in), optional :: label, linestyle, marker
        real(wp), intent(in), optional :: color(3)
        real(wp), intent(in), optional :: linewidth, markersize
        integer :: idx, nrows, ncols, row, col

        call ensure_fig_init()

        nrows = fig%subplot_rows
        ncols = fig%subplot_cols
        idx = fig%current_subplot

        if (nrows > 0 .and. ncols > 0 .and. idx >= 1 .and. idx <= nrows*ncols) then
            row = (idx - 1)/ncols + 1
            col = mod(idx - 1, ncols) + 1
            call fig%subplot_plot(row, col, x, y, label=label, linestyle=linestyle, &
                                  color=color)
        else
            call fig%add_plot(x, y, label=label, linestyle=linestyle, color=color)
        end if

        call apply_line_style_overrides(linewidth, marker, markersize)
    end subroutine plot

    subroutine bar_rgb(x, height, width, bottom, label, color, edgecolor, align)
        real(wp), contiguous, intent(in) :: x(:), height(:)
        real(wp), intent(in), optional :: width
        real(wp), intent(in), optional :: bottom(:)
        character(len=*), intent(in), optional :: label, align
        real(wp), intent(in), optional :: color(3), edgecolor(3)

        real(wp) :: bar_width
        real(wp), allocatable :: bar_bottom(:)

        call ensure_fig_init()

        bar_width = 0.8_wp
        if (present(width)) bar_width = width

        call resolve_bar_bottom(size(x), bottom, bar_bottom, 'bar')
        if (.not. allocated(bar_bottom)) return

        call bar_impl(fig, x, height, width=bar_width, bottom=bar_bottom, &
                      label=label, color=color, edgecolor=edgecolor)
    end subroutine bar_rgb

    subroutine bar_string(x, height, color, width, bottom, label, edgecolor, align)
        real(wp), contiguous, intent(in) :: x(:), height(:)
        character(len=*), intent(in) :: color
        real(wp), intent(in), optional :: width
        real(wp), intent(in), optional :: bottom(:)
        character(len=*), intent(in), optional :: label, align, edgecolor

        real(wp) :: bar_width
        real(wp), allocatable :: bar_bottom(:)
        real(wp) :: color_rgb(3), edge_rgb(3)
        logical :: has_color, has_edge

        call ensure_fig_init()

        bar_width = 0.8_wp
        if (present(width)) bar_width = width

        call resolve_bar_bottom(size(x), bottom, bar_bottom, 'bar')
        if (.not. allocated(bar_bottom)) return

        call resolve_color_string_or_rgb(color_str=color, context='bar', &
                                         rgb_out=color_rgb, has_color=has_color)
        call resolve_color_string_or_rgb(color_str=edgecolor, context='bar', &
                                         rgb_out=edge_rgb, has_color=has_edge)

        if (has_color .and. has_edge) then
            call bar_impl(fig, x, height, width=bar_width, bottom=bar_bottom, &
                          label=label, color=color_rgb, edgecolor=edge_rgb)
        else if (has_color) then
            call bar_impl(fig, x, height, width=bar_width, bottom=bar_bottom, &
                          label=label, color=color_rgb)
        else if (has_edge) then
            call bar_impl(fig, x, height, width=bar_width, bottom=bar_bottom, &
                          label=label, edgecolor=edge_rgb)
        else
            call bar_impl(fig, x, height, width=bar_width, bottom=bar_bottom, &
                          label=label)
        end if
    end subroutine bar_string

    subroutine barh_rgb(y, width, height, left, label, color, edgecolor, align)
        real(wp), contiguous, intent(in) :: y(:), width(:)
        real(wp), intent(in), optional :: height
        real(wp), intent(in), optional :: left(:)
        character(len=*), intent(in), optional :: label, align
        real(wp), intent(in), optional :: color(3), edgecolor(3)

        real(wp) :: bar_height
        real(wp), allocatable :: bar_left(:)

        call ensure_fig_init()

        bar_height = 0.8_wp
        if (present(height)) bar_height = height

        call resolve_bar_bottom(size(y), left, bar_left, 'barh')
        if (.not. allocated(bar_left)) return

        call barh_impl(fig, y, width, height=bar_height, left=bar_left, &
                       label=label, color=color, edgecolor=edgecolor)
    end subroutine barh_rgb

    subroutine barh_string(y, width, color, height, left, label, edgecolor, align)
        real(wp), contiguous, intent(in) :: y(:), width(:)
        character(len=*), intent(in) :: color
        real(wp), intent(in), optional :: height
        real(wp), intent(in), optional :: left(:)
        character(len=*), intent(in), optional :: label, align, edgecolor

        real(wp) :: bar_height
        real(wp), allocatable :: bar_left(:)
        real(wp) :: color_rgb(3), edge_rgb(3)
        logical :: has_color, has_edge

        call ensure_fig_init()

        bar_height = 0.8_wp
        if (present(height)) bar_height = height

        call resolve_bar_bottom(size(y), left, bar_left, 'barh')
        if (.not. allocated(bar_left)) return

        call resolve_color_string_or_rgb(color_str=color, context='barh', &
                                         rgb_out=color_rgb, has_color=has_color)
        call resolve_color_string_or_rgb(color_str=edgecolor, context='barh', &
                                         rgb_out=edge_rgb, has_color=has_edge)

        if (has_color .and. has_edge) then
            call barh_impl(fig, y, width, height=bar_height, left=bar_left, &
                           label=label, color=color_rgb, edgecolor=edge_rgb)
        else if (has_color) then
            call barh_impl(fig, y, width, height=bar_height, left=bar_left, &
                           label=label, color=color_rgb)
        else if (has_edge) then
            call barh_impl(fig, y, width, height=bar_height, left=bar_left, &
                           label=label, edgecolor=edge_rgb)
        else
            call barh_impl(fig, y, width, height=bar_height, left=bar_left, &
                           label=label)
        end if
    end subroutine barh_string

    subroutine bar_rgb_edgecolor(x, height, color, edgecolor, width, bottom, label, align)
        !! Bar with RGB-triple color and named-color edgecolor
        real(wp), contiguous, intent(in) :: x(:), height(:)
        real(wp), intent(in) :: color(3)
        character(len=*), intent(in) :: edgecolor
        real(wp), intent(in), optional :: width
        real(wp), intent(in), optional :: bottom(:)
        character(len=*), intent(in), optional :: label, align

        real(wp) :: bar_width
        real(wp), allocatable :: bar_bottom(:)
        real(wp) :: edge_rgb(3)
        logical :: has_edge

        call ensure_fig_init()

        bar_width = 0.8_wp
        if (present(width)) bar_width = width

        call resolve_bar_bottom(size(x), bottom, bar_bottom, 'bar')
        if (.not. allocated(bar_bottom)) return

        call resolve_color_string_or_rgb(color_str=edgecolor, context='bar', &
                                         rgb_out=edge_rgb, has_color=has_edge)

        if (has_edge) then
            call bar_impl(fig, x, height, width=bar_width, bottom=bar_bottom, &
                          label=label, color=color, edgecolor=edge_rgb)
        else
            call bar_impl(fig, x, height, width=bar_width, bottom=bar_bottom, &
                          label=label, color=color)
        end if
    end subroutine bar_rgb_edgecolor

    subroutine barh_rgb_edgecolor(y, width, color, edgecolor, height, left, label, align)
        !! Barh with RGB-triple color and named-color edgecolor
        real(wp), contiguous, intent(in) :: y(:), width(:)
        real(wp), intent(in) :: color(3)
        character(len=*), intent(in) :: edgecolor
        real(wp), intent(in), optional :: height
        real(wp), intent(in), optional :: left(:)
        character(len=*), intent(in), optional :: label, align

        real(wp) :: bar_height
        real(wp), allocatable :: bar_left(:)
        real(wp) :: edge_rgb(3)
        logical :: has_edge

        call ensure_fig_init()

        bar_height = 0.8_wp
        if (present(height)) bar_height = height

        call resolve_bar_bottom(size(y), left, bar_left, 'barh')
        if (.not. allocated(bar_left)) return

        call resolve_color_string_or_rgb(color_str=edgecolor, context='barh', &
                                         rgb_out=edge_rgb, has_color=has_edge)

        if (has_edge) then
            call barh_impl(fig, y, width, height=bar_height, left=bar_left, &
                           label=label, color=color, edgecolor=edge_rgb)
        else
            call barh_impl(fig, y, width, height=bar_height, left=bar_left, &
                           label=label, color=color)
        end if
    end subroutine barh_rgb_edgecolor

    subroutine bar_rgb_array(x, height, color_per_bar, edgecolor_per_bar, width, bottom, label, align)
        !! Bar with per-bar RGB color arrays
        real(wp), contiguous, intent(in) :: x(:), height(:)
        real(wp), intent(in), optional :: color_per_bar(3, *)
        real(wp), intent(in), optional :: edgecolor_per_bar(3, *)
        real(wp), intent(in), optional :: width
        real(wp), intent(in), optional :: bottom(:)
        character(len=*), intent(in), optional :: label, align

        real(wp) :: bar_width
        real(wp), allocatable :: bar_bottom(:)

        call ensure_fig_init()

        bar_width = 0.8_wp
        if (present(width)) bar_width = width

        call resolve_bar_bottom(size(x), bottom, bar_bottom, 'bar')
        if (.not. allocated(bar_bottom)) return

        if (present(edgecolor_per_bar)) then
            call bar_impl(fig, x, height, width=bar_width, bottom=bar_bottom, &
                          label=label, color_per_bar=color_per_bar, edgecolor_per_bar=edgecolor_per_bar)
        else
            call bar_impl(fig, x, height, width=bar_width, bottom=bar_bottom, &
                          label=label, color_per_bar=color_per_bar)
        end if
    end subroutine bar_rgb_array

    subroutine barh_rgb_array(y, width, color_per_bar, edgecolor_per_bar, height, left, label, align)
        !! Barh with per-bar RGB color arrays
        real(wp), contiguous, intent(in) :: y(:), width(:)
        real(wp), intent(in), optional :: color_per_bar(3, *)
        real(wp), intent(in), optional :: edgecolor_per_bar(3, *)
        real(wp), intent(in), optional :: height
        real(wp), intent(in), optional :: left(:)
        character(len=*), intent(in), optional :: label, align

        real(wp) :: bar_height
        real(wp), allocatable :: bar_left(:)

        call ensure_fig_init()

        bar_height = 0.8_wp
        if (present(height)) bar_height = height

        call resolve_bar_bottom(size(y), left, bar_left, 'barh')
        if (.not. allocated(bar_left)) return

        if (present(edgecolor_per_bar)) then
            call barh_impl(fig, y, width, height=bar_height, left=bar_left, &
                           label=label, color_per_bar=color_per_bar, edgecolor_per_bar=edgecolor_per_bar)
        else
            call barh_impl(fig, y, width, height=bar_height, left=bar_left, &
                           label=label, color_per_bar=color_per_bar)
        end if
    end subroutine barh_rgb_array

    subroutine resolve_bar_bottom(n, bottom, bar_bottom, context)
        integer, intent(in) :: n
        real(wp), intent(in), optional :: bottom(:)
        real(wp), allocatable, intent(out) :: bar_bottom(:)
        character(len=*), intent(in) :: context

        allocate (bar_bottom(n))
        bar_bottom = 0.0_wp
        if (present(bottom)) then
            if (size(bottom) == n) then
                bar_bottom = bottom
            else if (size(bottom) == 1) then
                bar_bottom = bottom(1)
            else
                call log_error(context // ": bottom/left size must match or be scalar")
            end if
        end if
    end subroutine resolve_bar_bottom

    subroutine boxplot_string(data, position, width, label, show_outliers, horizontal, color)
        !! Boxplot with named-color string (matplotlib-compatible).
        !! Converts string color to RGB before delegating to the figure.
        real(wp), contiguous, intent(in) :: data(:)
        real(wp), intent(in), optional :: position
        real(wp), intent(in), optional :: width
        character(len=*), intent(in), optional :: label
        logical, intent(in), optional :: show_outliers, horizontal
        character(len=*), intent(in) :: color

        real(wp) :: color_rgb(3)
        logical :: has_color

        call ensure_fig_init()
        call resolve_color_string_or_rgb(color_str=color, context='boxplot', &
                                         rgb_out=color_rgb, has_color=has_color)
        if (has_color) then
            call fig%boxplot(data, position=position, width=width, label=label, &
                             show_outliers=show_outliers, horizontal=horizontal, &
                             color=color_rgb)
        else
            call fig%boxplot(data, position=position, width=width, label=label, &
                             show_outliers=show_outliers, horizontal=horizontal)
        end if
    end subroutine boxplot_string

    subroutine boxplot_rgb(data, position, width, label, show_outliers, horizontal, color)
        !! Boxplot with RGB-triple color (matplotlib-compatible).
        real(wp), contiguous, intent(in) :: data(:)
        real(wp), intent(in), optional :: position
        real(wp), intent(in), optional :: width
        character(len=*), intent(in), optional :: label
        logical, intent(in), optional :: show_outliers, horizontal
        real(wp), intent(in), optional :: color(3)

        call ensure_fig_init()
        call fig%boxplot(data, position=position, width=width, label=label, &
                         show_outliers=show_outliers, horizontal=horizontal, &
                         color=color)
    end subroutine boxplot_rgb

    subroutine add_plot_rgb(x, y, color, label, linestyle)
        real(wp), contiguous, intent(in) :: x(:), y(:)
        real(wp), intent(in), optional :: color(3)
        character(len=*), intent(in), optional :: label, linestyle

        call ensure_fig_init()
        call fig%add_plot(x, y, label=label, linestyle=linestyle, color=color)
    end subroutine add_plot_rgb

    subroutine add_plot_string(x, y, color, label, linestyle)
        real(wp), contiguous, intent(in) :: x(:), y(:)
        character(len=*), intent(in) :: color
        character(len=*), intent(in), optional :: label, linestyle

        real(wp) :: color_rgb(3)
        logical :: has_color

        call ensure_fig_init()
        call resolve_color_string_or_rgb(color_str=color, context='add_plot', &
                                         rgb_out=color_rgb, has_color=has_color)
        if (has_color) then
            call fig%add_plot(x, y, label=label, linestyle=linestyle, color=color_rgb)
        else
            call fig%add_plot(x, y, label=label, linestyle=linestyle)
        end if
    end subroutine add_plot_string

    subroutine add_3d_plot_rgb(x, y, z, label, linestyle, color, linewidth, marker, &
                                 markersize)
        !! 3D plot wrapper with RGB-triple color (matplotlib-compatible).
        real(wp), contiguous, intent(in) :: x(:), y(:), z(:)
        character(len=*), intent(in), optional :: label, linestyle, marker
        real(wp), intent(in), optional :: color(3)
        real(wp), intent(in), optional :: linewidth, markersize

        call ensure_fig_init()
        call add_3d_plot_impl(fig, x, y, z, label=label, linestyle=linestyle, &
                              marker=marker, markersize=markersize, linewidth=linewidth, &
                              color=color)
    end subroutine add_3d_plot_rgb

    subroutine add_3d_plot_string(x, y, z, label, linestyle, color, linewidth, marker, &
                                   markersize)
        !! 3D plot wrapper with named-color string (matplotlib-compatible).
        !! Converts string color to RGB before delegating to the figure.
        real(wp), contiguous, intent(in) :: x(:), y(:), z(:)
        character(len=*), intent(in), optional :: label, linestyle, marker
        character(len=*), intent(in) :: color
        real(wp), intent(in), optional :: linewidth, markersize

        real(wp) :: color_rgb(3)
        logical :: has_color

        call ensure_fig_init()
        call resolve_color_string_or_rgb(color_str=color, context='add_3d_plot', &
                                         rgb_out=color_rgb, has_color=has_color)
        if (has_color) then
            call add_3d_plot_impl(fig, x, y, z, label=label, linestyle=linestyle, &
                                  marker=marker, markersize=markersize, linewidth=linewidth, &
                                  color=color_rgb)
        else
            call add_3d_plot_impl(fig, x, y, z, label=label, linestyle=linestyle, &
                                  marker=marker, markersize=markersize, linewidth=linewidth)
        end if
    end subroutine add_3d_plot_string

    subroutine apply_line_style_overrides(linewidth, marker, markersize)
        real(wp), intent(in), optional :: linewidth, markersize
        character(len=*), intent(in), optional :: marker
        integer :: idx

        if (.not. allocated(fig%plots)) return
        idx = fig%plot_count
        if (idx < 1 .or. idx > size(fig%plots)) return

        if (present(linewidth)) fig%plots(idx)%line_width = linewidth
        if (present(marker)) fig%plots(idx)%marker = marker
        if (present(markersize)) fig%plots(idx)%scatter_size_default = markersize
    end subroutine apply_line_style_overrides

end module fortplot_matplotlib_plot_wrappers