module fortplot_matplotlib_axes !! Axes, scale, and labelling helpers for the matplotlib facade use, intrinsic :: iso_fortran_env, only: wp => real64 use fortplot_global, only: fig => global_figure use fortplot_logging, only: log_warning use fortplot_matplotlib_session, only: ensure_fig_init implicit none private public :: xlabel public :: ylabel public :: title public :: suptitle public :: legend public :: grid public :: xlim public :: ylim public :: set_xscale public :: set_yscale public :: xscale public :: yscale public :: set_line_width public :: set_ydata public :: use_axis public :: get_active_axis public :: minorticks_on public :: axis public :: tight_layout public :: axhline public :: axvline public :: hlines public :: vlines public :: set_xticks public :: set_yticks interface axis !! Set aspect ratio: axis('equal'), axis('auto'), or axis(2.0) module procedure axis_str module procedure axis_num end interface axis contains subroutine xlabel(label_text) !! Set the x-axis label text. Routes to subplot or figure level. character(len=*), intent(in) :: label_text 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_set_xlabel(row, col, label_text) else call fig%set_xlabel(label_text) end if end subroutine xlabel subroutine ylabel(label_text) !! Set the y-axis label text. Routes to subplot or figure level. character(len=*), intent(in) :: label_text 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_set_ylabel(row, col, label_text) else call fig%set_ylabel(label_text) end if end subroutine ylabel subroutine title(title_text) !! Set the title for the current axes. Routes to subplot or figure level. character(len=*), intent(in) :: title_text 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_set_title(row, col, title_text) else call fig%set_title(title_text) end if end subroutine title subroutine suptitle(title_text, fontsize) !! Set a centered figure-level title above all subplots character(len=*), intent(in) :: title_text real(wp), intent(in), optional :: fontsize call ensure_fig_init() call fig%suptitle(title_text, fontsize) end subroutine suptitle subroutine legend(loc, box, fontsize, position) !! Display figure legend (matplotlib-compatible) !! !! Arguments mirror matplotlib.pyplot.legend: !! loc - legend location string (e.g. 'upper right', 'best') !! box - accepted for compatibility; styling not yet implemented !! fontsize - accepted for compatibility; styling not yet implemented !! position - deprecated alias for loc, kept for backward compatibility character(len=*), intent(in), optional :: loc logical, intent(in), optional :: box integer, intent(in), optional :: fontsize character(len=*), intent(in), optional :: position call ensure_fig_init() if (present(position) .and. .not. present(loc)) then call log_warning( & "legend: 'position' is deprecated; use 'loc' for matplotlib parity") call fig%legend(location=position) else if (present(loc)) then call fig%legend(location=loc) else call fig%legend() end if ! box and fontsize are accepted silently for matplotlib parity; ! styling passthrough is tracked by follow-up work and is not ! reported as an error on every call. call ignore_unused_legend_kwargs(box, fontsize) end subroutine legend subroutine ignore_unused_legend_kwargs(box, fontsize) !! Explicit no-op to document silent acceptance of matplotlib kwargs logical, intent(in), optional :: box integer, intent(in), optional :: fontsize if (present(box) .or. present(fontsize)) return end subroutine ignore_unused_legend_kwargs subroutine grid(visible, which, axis, alpha, linestyle, enabled) !! Toggle or style grid lines (matplotlib-compatible) !! !! Arguments: !! visible - show grid when .true.; canonical matplotlib name !! which - 'major', 'minor', or 'both' !! axis - 'x', 'y', or 'both' !! alpha - grid line transparency !! linestyle - grid line style ('-', '--', ':', '-.') !! enabled - deprecated alias for visible, kept for backward compatibility !! !! Default grid state follows the active style: MPL mode disables !! grid by default; Vega-Lite mode enables it. !! !! When neither visible nor enabled is present and no styling kwargs !! are given, grid() is a no-op. When styling kwargs (which, axis, !! alpha, linestyle) are present without a visibility argument, the !! grid is implicitly enabled with the given styling. logical, intent(in), optional :: visible character(len=*), intent(in), optional :: which, axis, linestyle real(wp), intent(in), optional :: alpha logical, intent(in), optional :: enabled logical :: effective_visible logical :: has_visible has_visible = present(visible) .or. present(enabled) effective_visible = .true. if (present(visible)) then effective_visible = visible else if (present(enabled)) then call log_warning( & "grid: 'enabled' is deprecated; use 'visible' for matplotlib parity") effective_visible = enabled end if call ensure_fig_init() if (has_visible) then call fig%grid(enabled=effective_visible, which=which, axis=axis, & alpha=alpha, linestyle=linestyle) else call fig%grid(which=which, axis=axis, alpha=alpha, linestyle=linestyle) end if end subroutine grid subroutine xlim(xmin, xmax) !! Set the x-axis display limits. real(wp), intent(in) :: xmin, xmax call ensure_fig_init() call fig%set_xlim(xmin, xmax) end subroutine xlim subroutine ylim(ymin, ymax) !! Set the y-axis display limits. real(wp), intent(in) :: ymin, ymax call ensure_fig_init() call fig%set_ylim(ymin, ymax) end subroutine ylim subroutine set_xscale(scale, linthresh, threshold, base, linscale) !! Set x-axis scale (matplotlib-compatible) !! !! Arguments: !! scale - 'linear', 'log', 'symlog', 'logit' !! linthresh - symlog linear range threshold (matplotlib canonical) !! threshold - deprecated alias for linthresh, kept for compatibility !! base - symlog logarithm base (default 10) !! linscale - symlog linear region scaling factor (default 1) character(len=*), intent(in) :: scale real(wp), intent(in), optional :: linthresh, threshold, base, linscale real(wp) :: resolved_threshold logical :: has_threshold call ensure_fig_init() call resolve_scale_threshold(linthresh, threshold, resolved_threshold, & has_threshold) if (has_threshold) then call fig%set_xscale(scale, resolved_threshold, base=base, linscale=linscale) else call fig%set_xscale(scale, base=base, linscale=linscale) end if end subroutine set_xscale subroutine set_yscale(scale, linthresh, threshold, base, linscale) !! Set y-axis scale (matplotlib-compatible); see set_xscale for kwargs character(len=*), intent(in) :: scale real(wp), intent(in), optional :: linthresh, threshold, base, linscale real(wp) :: resolved_threshold logical :: has_threshold call ensure_fig_init() call resolve_scale_threshold(linthresh, threshold, resolved_threshold, & has_threshold) if (has_threshold) then call fig%set_yscale(scale, resolved_threshold, base=base, linscale=linscale) else call fig%set_yscale(scale, base=base, linscale=linscale) end if end subroutine set_yscale subroutine xscale(scale, linthresh, threshold, base, linscale) !! matplotlib pyplot alias for set_xscale character(len=*), intent(in) :: scale real(wp), intent(in), optional :: linthresh, threshold, base, linscale call set_xscale(scale, linthresh=linthresh, threshold=threshold, & base=base, linscale=linscale) end subroutine xscale subroutine yscale(scale, linthresh, threshold, base, linscale) !! matplotlib pyplot alias for set_yscale character(len=*), intent(in) :: scale real(wp), intent(in), optional :: linthresh, threshold, base, linscale call set_yscale(scale, linthresh=linthresh, threshold=threshold, & base=base, linscale=linscale) end subroutine yscale subroutine resolve_scale_threshold(linthresh, threshold, value, present_out) real(wp), intent(in), optional :: linthresh, threshold real(wp), intent(out) :: value logical, intent(out) :: present_out value = 1.0_wp present_out = .false. if (present(linthresh)) then value = linthresh present_out = .true. else if (present(threshold)) then call log_warning( & "set_xscale/set_yscale: 'threshold' is deprecated; use 'linthresh'") value = threshold present_out = .true. end if end subroutine resolve_scale_threshold subroutine set_line_width(width) !! Set the default line width for subsequent plots. real(wp), intent(in) :: width call ensure_fig_init() call fig%set_line_width(width) end subroutine set_line_width subroutine set_ydata(ydata) !! Replace the y-data of the first plot line. real(wp), contiguous, intent(in) :: ydata(:) call ensure_fig_init() call fig%set_ydata(1, ydata) end subroutine set_ydata subroutine use_axis(axis_name) !! Switch the active axes by name. character(len=*), intent(in) :: axis_name call ensure_fig_init() call fig%use_axis(axis_name) end subroutine use_axis function get_active_axis() result(axis_name) !! Return the name of the currently active axes. character(len=10) :: axis_name call ensure_fig_init() axis_name = fig%get_active_axis() end function get_active_axis subroutine minorticks_on() !! Enable minor ticks on both axes (matplotlib-compatible) call ensure_fig_init() call fig%minorticks_on() end subroutine minorticks_on subroutine axis_str(aspect) !! Set axis aspect ratio using string mode: 'equal' or 'auto' character(len=*), intent(in) :: aspect call ensure_fig_init() call fig%set_aspect(aspect) end subroutine axis_str subroutine axis_num(ratio) !! Set axis aspect ratio using a numeric value (y-scale = ratio * x-scale) real(wp), intent(in) :: ratio call ensure_fig_init() call fig%set_aspect(ratio) end subroutine axis_num subroutine tight_layout(pad, w_pad, h_pad) !! Automatically adjust subplot parameters to give specified padding !! !! Enables tight layout mode which optimizes the figure layout to !! minimize overlap between subplots, titles, and axis labels. real(wp), intent(in), optional :: pad real(wp), intent(in), optional :: w_pad real(wp), intent(in), optional :: h_pad call ensure_fig_init() call fig%tight_layout(pad, w_pad, h_pad) end subroutine tight_layout subroutine axhline(y, xmin, xmax, color, linestyle, linewidth, label) !! Draw a horizontal reference line at data value y (matplotlib-compatible) real(wp), intent(in) :: y real(wp), intent(in), optional :: xmin, xmax character(len=*), intent(in), optional :: color, linestyle, label real(wp), intent(in), optional :: linewidth call ensure_fig_init() call fig%axhline(y, xmin=xmin, xmax=xmax, color=color, & linestyle=linestyle, linewidth=linewidth, label=label) end subroutine axhline subroutine axvline(x, ymin, ymax, color, linestyle, linewidth, label) !! Draw a vertical reference line at data value x (matplotlib-compatible) real(wp), intent(in) :: x real(wp), intent(in), optional :: ymin, ymax character(len=*), intent(in), optional :: color, linestyle, label real(wp), intent(in), optional :: linewidth call ensure_fig_init() call fig%axvline(x, ymin=ymin, ymax=ymax, color=color, & linestyle=linestyle, linewidth=linewidth, label=label) end subroutine axvline subroutine hlines(y, xmin, xmax, colors, linestyles, linewidth, label) !! Draw one or more horizontal lines at y values between xmin and xmax real(wp), contiguous, intent(in) :: y(:) real(wp), intent(in) :: xmin, xmax character(len=*), intent(in), optional :: colors, linestyles, label real(wp), intent(in), optional :: linewidth call ensure_fig_init() call fig%hlines(y, xmin=xmin, xmax=xmax, colors=colors, & linestyles=linestyles, linewidth=linewidth, label=label) end subroutine hlines subroutine vlines(x, ymin, ymax, colors, linestyles, linewidth, label) !! Draw one or more vertical lines at x values between ymin and ymax real(wp), contiguous, intent(in) :: x(:) real(wp), intent(in) :: ymin, ymax character(len=*), intent(in), optional :: colors, linestyles, label real(wp), intent(in), optional :: linewidth call ensure_fig_init() call fig%vlines(x, ymin=ymin, ymax=ymax, colors=colors, & linestyles=linestyles, linewidth=linewidth, label=label) end subroutine vlines subroutine set_xticks(positions, labels) !! Set custom x-axis tick positions and optionally labels real(wp), contiguous, intent(in) :: positions(:) character(len=*), intent(in), optional :: labels(:) call ensure_fig_init() if (present(labels)) then call fig%set_xticks(positions, labels) else call fig%set_xticks(positions) end if end subroutine set_xticks subroutine set_yticks(positions, labels) !! Set custom y-axis tick positions and optionally labels real(wp), contiguous, intent(in) :: positions(:) character(len=*), intent(in), optional :: labels(:) call ensure_fig_init() if (present(labels)) then call fig%set_yticks(positions, labels) else call fig%set_yticks(positions) end if end subroutine set_yticks end module fortplot_matplotlib_axes