!***************************************************************************************** !> author: Jacob Williams ! date: 6/16/2017 ! license: BSD ! ! For making simple x-y plots from Fortran. ! It works by generating a Python script and executing it. ! !# See also ! * Inspired by: [EasyPlot](https://pypi.python.org/pypi/EasyPlot) ! !@note The default real kind (`wp`) can be ! changed using optional preprocessor flags. ! This library was built with real kind: #ifdef REAL32 ! `real(kind=real32)` [4 bytes] #elif REAL64 ! `real(kind=real64)` [8 bytes] #elif REAL128 ! `real(kind=real128)` [16 bytes] #else ! `real(kind=real64)` [8 bytes] #endif module pyplot_module use, intrinsic :: iso_fortran_env implicit none private #ifdef REAL32 integer,parameter,public :: pyplot_wp = real32 !! real kind used by this module [4 bytes] #elif REAL64 integer,parameter,public :: pyplot_wp = real64 !! real kind used by this module [8 bytes] #elif REAL128 integer,parameter,public :: pyplot_wp = real128 !! real kind used by this module [16 bytes] #else integer,parameter,public :: pyplot_wp = real64 !! real kind used by this module [8 bytes] #endif integer,parameter :: wp = pyplot_wp !! local copy of `pyplot_wp` with a shorter name character(len=*), parameter :: tmp_file = 'pyplot_module_temp_1234567890.py' !! Default name of the temporary file !! (this can also be user-specified). character(len=*), parameter :: python_exe ='python' !! The python executable name. character(len=*), parameter :: int_fmt = '(I10)' !! integer format string integer, parameter :: max_int_len = 10 !! max string length for integers character(len=*), parameter :: real_fmt_default = '(E30.16)' !! default real number format string integer, parameter :: max_real_len = 60 !! max string length for reals type, public :: pyplot !! The main pyplot class. private character(len=:), allocatable :: str !! string buffer character(len=1) :: raw_str_token = ' ' !! will be 'r' if using raw strings logical :: show_legend = .false. !! show legend into plot logical :: use_numpy = .true. !! use numpy python module logical :: use_oo_api = .false. !! use OO interface of matplotlib (incopatible with showfig subroutine) logical :: mplot3d = .false. !! it is a 3d plot logical :: polar = .false. !! it is a polar plot logical :: axis_equal = .false. !! equal scale on each axis logical :: axisbelow = .true. !! axis below other chart elements logical :: tight_layout = .false. !! tight layout option logical :: usetex = .false. !! enable LaTeX character(len=:),allocatable :: xaxis_date_fmt !! date format for the x-axis. Example: `"%m/%d/%y %H:%M:%S"` character(len=:),allocatable :: yaxis_date_fmt !! date format for the y-axis. Example: `"%m/%d/%y %H:%M:%S"` character(len=:),allocatable :: real_fmt !! real number formatting contains ! public methods procedure, public :: initialize !! initialize pyplot instance procedure, public :: add_plot !! add a 2d plot to pyplot instance procedure, public :: add_errorbar !! add a 2d error bar plot to pyplot instance procedure, public :: add_3d_plot !! add a 3d plot to pyplot instance procedure, public :: add_sphere !! add a 3d sphere to pyplot instance procedure, public :: add_contour !! add a contour plot to pyplot instance procedure, public :: plot_wireframe!! add a wireframe plot to pyplot instance procedure, public :: plot_surface !! add a surface plot to pyplot instance procedure, public :: add_bar !! add a barplot to pyplot instance procedure, public :: add_imshow !! add an image plot (using `imshow`) procedure, public :: add_hist !! add a histogram plot to pyplot instance procedure, public :: savefig !! save plots of pyplot instance procedure, public :: showfig !! show plots of pyplot instance procedure, public :: destroy !! destroy pyplot instance ! private methods procedure :: execute !! execute pyplot commands procedure :: add_str !! add string to pytplot instance buffer procedure :: finish_ops !! some final ops before saving end type pyplot contains !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Destructor. subroutine destroy(me) class(pyplot),intent(inout) :: me !! pyplot handler if (allocated(me%str)) deallocate(me%str) if (allocated(me%real_fmt)) deallocate(me%real_fmt) me%raw_str_token = ' ' end subroutine destroy !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Add a string to the buffer. subroutine add_str(me,str) class(pyplot), intent(inout) :: me !! pyplot handler character(len=*), intent(in) :: str !! str to be added to pyplot handler buffer integer :: n_old !! current `me%str` length integer :: n_str !! length of input `str` character(len=:),allocatable :: tmp !! tmp string for building the result ! original !me%str = me%str//str//new_line(' ') if (len(str)==0) return ! the above can sometimes cause a stack overflow in the ! intel Fortran compiler, so we replace with this: if (allocated(me%str)) then n_old = len(me%str) n_str = len(str) allocate(character(len=n_old+n_str+1) :: tmp) tmp(1:n_old) = me%str tmp(n_old+1:) = str//new_line(' ') call move_alloc(tmp, me%str) else allocate(me%str, source = str//new_line(' ')) end if end subroutine add_str !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Initialize a plot subroutine initialize(me, grid, xlabel, ylabel, zlabel, title, legend, use_numpy, figsize, & font_size, axes_labelsize, xtick_labelsize, ytick_labelsize, ztick_labelsize, & legend_fontsize, mplot3d, axis_equal, polar, real_fmt, use_oo_api, axisbelow,& tight_layout, raw_strings, usetex, xaxis_date_fmt, yaxis_date_fmt) class(pyplot), intent(inout) :: me !! pyplot handler logical, intent(in), optional :: grid !! activate grid drawing character(len=*), intent(in), optional :: xlabel !! label of x axis character(len=*), intent(in), optional :: ylabel !! label of y axis character(len=*), intent(in), optional :: zlabel !! label of z axis character(len=*), intent(in), optional :: title !! plot title logical, intent(in), optional :: legend !! plot legend logical, intent(in), optional :: use_numpy !! activate usage of numpy python module integer, dimension(2), intent(in), optional :: figsize !! dimension of the figure integer, intent(in), optional :: font_size !! font size integer, intent(in), optional :: axes_labelsize !! size of axis labels integer, intent(in), optional :: xtick_labelsize !! size of x axis tick lables integer, intent(in), optional :: ytick_labelsize !! size of y axis tick lables integer, intent(in), optional :: ztick_labelsize !! size of z axis tick lables integer, intent(in), optional :: legend_fontsize !! size of legend font logical, intent(in), optional :: mplot3d !! set true for 3d plots (cannot use with polar) logical, intent(in), optional :: axis_equal !! set true for axis = 'equal' logical, intent(in), optional :: polar !! set true for polar plots (cannot use with mplot3d) character(len=*), intent(in), optional :: real_fmt !! format string for real numbers (examples: '(E30.16)' [default], '*') logical, intent(in), optional :: use_oo_api !! avoid matplotlib's GUI by using the OO interface (cannot use with showfig) logical, intent(in), optional :: axisbelow !! to put the grid lines below the other chart elements [default is true] logical, intent(in), optional :: tight_layout !! enable tight layout [default is false] logical, intent(in), optional :: raw_strings !! if True, all strings sent to Python are treated as !! raw strings (e.g., r'str'). Default is False. logical, intent(in), optional :: usetex !! if True, enable LaTeX. (default if false) character(len=*), intent(in), optional :: xaxis_date_fmt !! if present, used to set the date format for the x-axis character(len=*), intent(in), optional :: yaxis_date_fmt !! if present, used to set the date format for the y-axis character(len=max_int_len) :: width_str !! figure width dummy string character(len=max_int_len) :: height_str !! figure height dummy string character(len=max_int_len) :: font_size_str !! font size dummy string character(len=max_int_len) :: axes_labelsize_str !! size of axis labels dummy string character(len=max_int_len) :: xtick_labelsize_str !! size of x axis tick labels dummy string character(len=max_int_len) :: ytick_labelsize_str !! size of x axis tick labels dummy string character(len=max_int_len) :: ztick_labelsize_str !! size of z axis tick labels dummy string character(len=max_int_len) :: legend_fontsize_str !! size of legend font dummy string character(len=:),allocatable :: python_fig_func !! Python's function for creating a new Figure instance character(len=*), parameter :: default_font_size_str = '10' !! the default font size for plots call me%destroy() if (present(raw_strings)) then if (raw_strings) me%raw_str_token = 'r' end if if (present(legend)) then me%show_legend = legend else me%show_legend = .false. end if if (present(use_numpy)) then me%use_numpy = use_numpy else me%use_numpy = .true. end if if (present(use_oo_api)) then me%use_oo_api = use_oo_api else me%use_oo_api = .false. end if if (present(figsize)) then call integer_to_string(figsize(1), width_str) call integer_to_string(figsize(2), height_str) end if if (present(mplot3d)) then me%mplot3d = mplot3d else me%mplot3d = .false. end if if (present(polar)) then me%polar = polar else me%polar = .false. end if if (present(axis_equal)) then me%axis_equal = axis_equal else me%axis_equal = .false. end if if (present(real_fmt)) then me%real_fmt = trim(adjustl(real_fmt)) else me%real_fmt = real_fmt_default end if if (present(tight_layout)) then me%tight_layout = tight_layout else me%tight_layout = .false. end if if (present(usetex)) then me%usetex = usetex else me%usetex = .false. end if if (present(xaxis_date_fmt)) then me%xaxis_date_fmt = xaxis_date_fmt else if (allocated(me%xaxis_date_fmt)) deallocate(me%xaxis_date_fmt) end if if (present(yaxis_date_fmt)) then me%yaxis_date_fmt = yaxis_date_fmt else if (allocated(me%yaxis_date_fmt)) deallocate(me%yaxis_date_fmt) end if call optional_int_to_string(font_size, font_size_str, default_font_size_str) call optional_int_to_string(axes_labelsize, axes_labelsize_str, default_font_size_str) call optional_int_to_string(xtick_labelsize, xtick_labelsize_str, default_font_size_str) call optional_int_to_string(ytick_labelsize, ytick_labelsize_str, default_font_size_str) call optional_int_to_string(ztick_labelsize, ztick_labelsize_str, default_font_size_str) call optional_int_to_string(legend_fontsize, legend_fontsize_str, default_font_size_str) me%str = '' call me%add_str('#!/usr/bin/env python') call me%add_str('') call me%add_str('import matplotlib') if (me%use_oo_api) then call me%add_str('from matplotlib.figure import Figure') call me%add_str('from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas') else call me%add_str('import matplotlib.pyplot as plt') endif if (me%mplot3d) call me%add_str('from mpl_toolkits.mplot3d import Axes3D') if (me%use_numpy) call me%add_str('import numpy as np') call me%add_str('') call me%add_str('matplotlib.rcParams["font.family"] = "Serif"') call me%add_str('matplotlib.rcParams["font.size"] = '//trim(font_size_str)) call me%add_str('matplotlib.rcParams["axes.labelsize"] = '//trim(axes_labelsize_str)) call me%add_str('matplotlib.rcParams["xtick.labelsize"] = '//trim(xtick_labelsize_str)) call me%add_str('matplotlib.rcParams["ytick.labelsize"] = '//trim(ytick_labelsize_str)) call me%add_str('matplotlib.rcParams["legend.fontsize"] = '//trim(legend_fontsize_str)) if (me%usetex) call me%add_str('matplotlib.rcParams["text.usetex"] = True') call me%add_str('') if (me%use_oo_api) then python_fig_func = 'Figure' else python_fig_func = 'plt.figure' endif if (present(figsize)) then !if specifying the figure size call me%add_str('fig = '//python_fig_func//'(figsize=('//trim(width_str)//','//trim(height_str)//'),facecolor="white")') else call me%add_str('fig = '//python_fig_func//'(facecolor="white")') end if if (me%mplot3d) then call me%add_str('ax = fig.add_subplot(1, 1, 1, projection=''3d'')') elseif (me%polar) then call me%add_str('ax = fig.add_subplot(1, 1, 1, projection=''polar'')') else call me%add_str('ax = fig.add_subplot(1, 1, 1)') end if if (present(grid)) then if (grid) call me%add_str('ax.grid()') end if if (present(axisbelow)) then me%axisbelow = axisbelow else me%axisbelow = .true. ! default end if if (me%axisbelow) call me%add_str('ax.set_axisbelow(True)') if (present(xlabel)) call me%add_str('ax.set_xlabel('//trim(me%raw_str_token)//'"'//trim(xlabel)//'")') if (present(ylabel)) call me%add_str('ax.set_ylabel('//trim(me%raw_str_token)//'"'//trim(ylabel)//'")') if (present(zlabel)) call me%add_str('ax.set_zlabel('//trim(me%raw_str_token)//'"'//trim(zlabel)//'")') if (present(title)) call me%add_str('ax.set_title('//trim(me%raw_str_token)//'"' //trim(title) //'")') call me%add_str('') end subroutine initialize !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Add an x,y plot. subroutine add_plot(me, x, y, label, linestyle, markersize, linewidth, xlim, ylim, xscale, yscale, color, istat) class(pyplot), intent (inout) :: me !! pyplot handler real(wp), dimension(:), intent (in) :: x !! x values real(wp), dimension(:), intent (in) :: y !! y values character(len=*), intent (in) :: label !! plot label character(len=*), intent (in) :: linestyle !! style of the plot line integer, intent (in), optional :: markersize !! size of the plot markers integer, intent (in), optional :: linewidth !! width of the plot line real(wp),dimension(2), intent (in), optional :: xlim !! x-axis range real(wp),dimension(2), intent (in), optional :: ylim !! y-axis range character(len=*), intent (in), optional :: xscale !! example: 'linear' (default), 'log' character(len=*), intent (in), optional :: yscale !! example: 'linear' (default), 'log' real(wp),dimension(:), intent (in), optional :: color !! RGB color tuple [0-1,0-1,0-1] integer, intent (out), optional :: istat !! status output (0 means no problems) character(len=:), allocatable :: arg_str !! the arguments to pass to `plot` character(len=:), allocatable :: xstr !! x values stringified character(len=:), allocatable :: ystr !! y values stringified character(len=:), allocatable :: xlimstr !! xlim values stringified character(len=:), allocatable :: ylimstr !! ylim values stringified character(len=:), allocatable :: color_str !! color values stringified character(len=max_int_len) :: imark !! actual markers size character(len=max_int_len) :: iline !! actual line width character(len=*), parameter :: xname = 'x' !! x variable name for script character(len=*), parameter :: yname = 'y' !! y variable name for script if (allocated(me%str)) then if (present(istat)) istat = 0 !axis limits (optional): if (present(xlim)) call vec_to_string(xlim, me%real_fmt, xlimstr, me%use_numpy) if (present(ylim)) call vec_to_string(ylim, me%real_fmt, ylimstr, me%use_numpy) !convert the arrays to strings: call vec_to_string(x, me%real_fmt, xstr, me%use_numpy) call vec_to_string(y, me%real_fmt, ystr, me%use_numpy) !get optional inputs (if not present, set default value): call optional_int_to_string(markersize, imark, '3') call optional_int_to_string(linewidth, iline, '3') !write the arrays: call me%add_str(trim(xname)//' = '//xstr) call me%add_str(trim(yname)//' = '//ystr) call me%add_str('') !main arguments for plot: arg_str = trim(xname)//','//& trim(yname)//','//& trim(me%raw_str_token)//'"'//trim(linestyle)//'",'//& 'linewidth='//trim(adjustl(iline))//','//& 'markersize='//trim(adjustl(imark))//','//& 'label='//trim(me%raw_str_token)//'"'//trim(label)//'"' ! optional arguments: if (present(color)) then if (size(color)<=3) then call vec_to_string(color(1:3), '*', color_str, use_numpy=.false., is_tuple=.true.) arg_str = arg_str//',color='//trim(color_str) end if end if !write the plot statement: call me%add_str('ax.plot('//arg_str//')') !axis limits: if (allocated(xlimstr)) call me%add_str('ax.set_xlim('//xlimstr//')') if (allocated(ylimstr)) call me%add_str('ax.set_ylim('//ylimstr//')') !axis scales: if (present(xscale)) call me%add_str('ax.set_xscale('//trim(me%raw_str_token)//'"'//xscale//'")') if (present(yscale)) call me%add_str('ax.set_yscale('//trim(me%raw_str_token)//'"'//yscale//'")') call me%add_str('') else if (present(istat)) istat = -1 write(error_unit,'(A)') 'Error in add_plot: pyplot class not properly initialized.' end if end subroutine add_plot !***************************************************************************************** !***************************************************************************************** !> author: Jimmy Leta ! ! Add a histogram plot. subroutine add_hist(me, x, label, xlim, ylim, xscale, yscale, bins, normed, cumulative, istat) class(pyplot), intent (inout) :: me !! pyplot handler real(wp), dimension(:), intent (in) :: x !! array of data character(len=*), intent (in) :: label !! plot label real(wp),dimension(2), intent (in), optional :: xlim !! x-axis range real(wp),dimension(2), intent (in), optional :: ylim !! y-axis range character(len=*), intent (in), optional :: xscale !! example: 'linear' (default), 'log' character(len=*), intent (in), optional :: yscale !! example: 'linear' (default), 'log' integer, intent (in), optional :: bins !! number of bins logical, intent (in), optional :: normed !! boolean flag that determines whether bin counts are normalized [NO LONGER USED] logical, intent (in), optional :: cumulative !! boolean flag that determines whether histogram represents the cumulative density of dataset integer, intent (out),optional :: istat !! status output (0 means no problems) character(len=*), parameter :: xname = 'x' !! x variable name for script character(len=:), allocatable :: xstr !! x values stringified character(len=:), allocatable :: xlimstr !! xlim values stringified character(len=:), allocatable :: ylimstr !! ylim values stringified character(len=:), allocatable :: cumulativestr !! character(len=max_int_len) :: binsstr !! if (allocated(me%str)) then if (present(istat)) istat = 0 !axis limits (optional): if (present(xlim)) call vec_to_string(xlim, me%real_fmt, xlimstr, me%use_numpy) if (present(ylim)) call vec_to_string(ylim, me%real_fmt, ylimstr, me%use_numpy) !convert the arrays to strings: call vec_to_string(x, me%real_fmt, xstr, me%use_numpy) !write the arrays: call me%add_str(trim(xname)//' = '//xstr) call me%add_str('') !get optional inputs (if not present, set default value): call optional_int_to_string(bins, binsstr, '10') call optional_logical_to_string(cumulative, cumulativestr, 'False') !write the plot statement: call me%add_str('ax.hist('//& trim(xname)//','//& 'label='//trim(me%raw_str_token)//'"'//trim(label)//'",'//& 'bins='//trim(binsstr)//','//& 'cumulative='//trim(cumulativestr)//')') !axis limits: if (allocated(xlimstr)) call me%add_str('ax.set_xlim('//xlimstr//')') if (allocated(ylimstr)) call me%add_str('ax.set_ylim('//ylimstr//')') !axis scales: if (present(xscale)) call me%add_str('ax.set_xscale('//trim(me%raw_str_token)//'"'//xscale//'")') if (present(yscale)) call me%add_str('ax.set_yscale('//trim(me%raw_str_token)//'"'//yscale//'")') call me%add_str('') else if (present(istat)) istat = -1 write(error_unit,'(A)') 'Error in add_plot: pyplot class not properly initialized.' end if end subroutine add_hist !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Add a contour plot. ! !@note This requires `use_numpy` to be True. subroutine add_contour(me, x, y, z, linestyle, linewidth, levels, color, & filled, cmap, colorbar, istat) class(pyplot), intent (inout) :: me !! pyplot handler real(wp),dimension(:), intent (in) :: x !! x values real(wp),dimension(:), intent (in) :: y !! y values real(wp),dimension(:,:), intent (in) :: z !! z values (a matrix) character(len=*), intent (in) :: linestyle !! style of the plot line integer, intent (in), optional :: linewidth !! width of the plot line [only used when `filled=False`] real(wp),dimension(:), intent (in), optional :: levels !! contour levels to plot character(len=*), intent (in), optional :: color !! color of the contour line logical, intent (in), optional :: filled !! use filled control (default=False) character(len=*), intent (in), optional :: cmap !! colormap if filled=True (examples: 'jet', 'bone') logical, intent (in), optional :: colorbar !! add a colorbar (default=False) integer, intent (out),optional :: istat !! status output (0 means no problems) character(len=:), allocatable :: xstr !! x values stringified character(len=:), allocatable :: ystr !! y values stringified character(len=:), allocatable :: zstr !! z values stringified character(len=:), allocatable :: levelstr !! levels vector stringified character(len=max_int_len) :: iline !! actual line width character(len=*), parameter :: xname = 'x' !! x variable name for script character(len=*), parameter :: yname = 'y' !! y variable name for script character(len=*), parameter :: zname = 'z' !! z variable name for script character(len=*), parameter :: xname_ = 'X' !! X variable name for contour character(len=*), parameter :: yname_ = 'Y' !! Y variable name for contour character(len=*), parameter :: zname_ = 'Z' !! Z variable name for contour character(len=:), allocatable :: extras !! optional stuff character(len=:), allocatable :: contourfunc !! 'contour' or 'contourf' logical :: is_filled !! if it is a filled contour plot if (allocated(me%str)) then if (present(istat)) istat = 0 !convert the arrays to strings: call vec_to_string(x, me%real_fmt, xstr, me%use_numpy) call vec_to_string(y, me%real_fmt, ystr, me%use_numpy) call matrix_to_string(z, me%real_fmt, zstr, me%use_numpy) if (present(levels)) call vec_to_string(levels, me%real_fmt, levelstr, me%use_numpy) !get optional inputs (if not present, set default value): call optional_int_to_string(linewidth, iline, '3') !write the arrays: call me%add_str(trim(xname)//' = '//xstr) call me%add_str(trim(yname)//' = '//ystr) call me%add_str(trim(zname)//' = '//zstr) call me%add_str('') !convert inputs for contour plotting: call me%add_str(xname_//', '//yname_//' = np.meshgrid('//trim(xname)//', '//trim(yname)//')') call me%add_str(zname_//' = np.transpose('//zname//')') !optional arguments: extras = '' if (present(levels)) extras = extras//','//'levels='//levelstr if (present(color)) extras = extras//','//'colors='//trim(me%raw_str_token)//'"'//color//'"' if (present(linewidth)) extras = extras//','//'linewidths='//trim(adjustl(iline)) if (present(cmap)) extras = extras//','//'cmap='//trim(me%raw_str_token)//'"'//cmap//'"' !filled or regular: contourfunc = 'contour' !default is_filled = .false. if (present(filled)) then is_filled = filled if (filled) contourfunc = 'contourf' !filled contour end if !write the plot statement: call me%add_str('CS = ax.'//contourfunc//'('//xname_//','//yname_//','//zname_//','//& 'linestyles='//trim(me%raw_str_token)//'"'//trim(adjustl(linestyle))//'"'//& extras//')') if (present(colorbar)) then if (colorbar) call me%add_str('fig.colorbar(CS)') end if if (.not. is_filled) call me%add_str('ax.clabel(CS, fontsize=9, inline=1)') call me%add_str('') else if (present(istat)) istat = -1 write(error_unit,'(A)') 'Error in add_plot: pyplot class not properly initialized.' end if end subroutine add_contour !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Add a surface plot. ! !@note This requires `use_numpy` to be True. subroutine plot_surface(me, x, y, z, label, linestyle, linewidth, levels, color, & cmap, colorbar, antialiased, istat) class(pyplot), intent (inout) :: me !! pyplot handler real(wp),dimension(:), intent (in) :: x !! x values real(wp),dimension(:), intent (in) :: y !! y values real(wp),dimension(:,:), intent (in) :: z !! z values (a matrix) character(len=*), intent (in) :: label !! plot label character(len=*), intent (in) :: linestyle !! style of the plot line integer, intent (in), optional :: linewidth !! width of the plot line real(wp),dimension(:), intent (in), optional :: levels !! contour levels to plot character(len=*), intent (in), optional :: color !! Color of the surface patches character(len=*), intent (in), optional :: cmap !! colormap if filled=True (examples: 'jet', 'bone') logical, intent (in), optional :: colorbar !! add a colorbar (default=False) logical, intent (in), optional :: antialiased !! The surface is made opaque by using antialiased=False integer, intent (out), optional :: istat !! status output (0 means no problems) character(len=:), allocatable :: xstr !! x values stringified character(len=:), allocatable :: ystr !! y values stringified character(len=:), allocatable :: zstr !! z values stringified character(len=:), allocatable :: levelstr !! levels vector stringified character(len=:), allocatable :: antialiasedstr !! antialiased stringified character(len=max_int_len) :: iline !! actual line width character(len=*), parameter :: xname = 'x' !! x variable name for script character(len=*), parameter :: yname = 'y' !! y variable name for script character(len=*), parameter :: zname = 'z' !! z variable name for script character(len=*), parameter :: xname_ = 'X' !! X variable name for contour character(len=*), parameter :: yname_ = 'Y' !! Y variable name for contour character(len=*), parameter :: zname_ = 'Z' !! Z variable name for contour character(len=:), allocatable :: extras !! optional stuff if (allocated(me%str)) then if (present(istat)) istat = 0 !convert the arrays to strings: call vec_to_string(x, me%real_fmt, xstr, me%use_numpy) call vec_to_string(y, me%real_fmt, ystr, me%use_numpy) call matrix_to_string(z, me%real_fmt, zstr, me%use_numpy) if (present(levels)) call vec_to_string(levels, me%real_fmt, levelstr, me%use_numpy) !get optional inputs (if not present, set default value): call optional_int_to_string(linewidth, iline, '3') call optional_logical_to_string(antialiased, antialiasedstr, 'False') !write the arrays: call me%add_str(trim(xname)//' = '//xstr) call me%add_str(trim(yname)//' = '//ystr) call me%add_str(trim(zname)//' = '//zstr) call me%add_str('') !convert inputs for contour plotting: call me%add_str(xname_//', '//yname_//' = np.meshgrid('//trim(xname)//', '//trim(yname)//')') call me%add_str(zname_//' = np.transpose('//zname//')') !optional arguments: extras = '' if (present(levels)) extras = extras//','//'levels='//levelstr if (present(color)) extras = extras//','//'colors='//trim(me%raw_str_token)//'"'//color//'"' if (present(linewidth)) extras = extras//','//'linewidths='//trim(adjustl(iline)) if (present(cmap)) extras = extras//','//'cmap='//trim(me%raw_str_token)//'"'//cmap//'"' !write the plot statement: call me%add_str('CS = ax.plot_surface'//'('//xname_//','//yname_//','//zname_//','//& 'label='//trim(me%raw_str_token)//'"'//trim(label)//'",'//& 'antialiased='//trim(antialiasedstr)//','//& 'linestyles='//trim(me%raw_str_token)//'"'//trim(adjustl(linestyle))//'"'//& extras//')') if (present(colorbar)) then if (colorbar) call me%add_str('fig.colorbar(CS)') end if call me%add_str('') else if (present(istat)) istat = -1 write(error_unit,'(A)') 'Error in add_plot: pyplot class not properly initialized.' end if end subroutine plot_surface !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Add a wireframe plot. ! !@note This requires `use_numpy` to be True. subroutine plot_wireframe(me, x, y, z, label, linestyle, linewidth, levels, color, & cmap, colorbar, antialiased, istat) class(pyplot), intent (inout) :: me !! pyplot handler real(wp),dimension(:), intent (in) :: x !! x values real(wp),dimension(:), intent (in) :: y !! y values real(wp),dimension(:,:), intent (in) :: z !! z values (a matrix) character(len=*), intent (in) :: label !! plot label character(len=*), intent (in) :: linestyle !! style of the plot line integer, intent (in), optional :: linewidth !! width of the plot line real(wp),dimension(:), intent (in), optional :: levels !! contour levels to plot character(len=*), intent (in), optional :: color !! Color of the surface patches character(len=*), intent (in), optional :: cmap !! colormap if filled=True (examples: 'jet', 'bone') logical, intent (in), optional :: colorbar !! add a colorbar (default=False) logical, intent (in), optional :: antialiased !! The surface is made opaque by using antialiased=False integer, intent (out), optional :: istat !! status output (0 means no problems) character(len=:), allocatable :: xstr !! x values stringified character(len=:), allocatable :: ystr !! y values stringified character(len=:), allocatable :: zstr !! z values stringified character(len=:), allocatable :: levelstr !! levels vector stringified character(len=:), allocatable :: antialiasedstr !! antialiased stringified character(len=max_int_len) :: iline !! actual line width character(len=*), parameter :: xname = 'x' !! x variable name for script character(len=*), parameter :: yname = 'y' !! y variable name for script character(len=*), parameter :: zname = 'z' !! z variable name for script character(len=*), parameter :: xname_ = 'X' !! X variable name for contour character(len=*), parameter :: yname_ = 'Y' !! Y variable name for contour character(len=*), parameter :: zname_ = 'Z' !! Z variable name for contour character(len=:), allocatable :: extras !! optional stuff if (allocated(me%str)) then if (present(istat)) istat = 0 !convert the arrays to strings: call vec_to_string(x, me%real_fmt, xstr, me%use_numpy) call vec_to_string(y, me%real_fmt, ystr, me%use_numpy) call matrix_to_string(z, me%real_fmt, zstr, me%use_numpy) if (present(levels)) call vec_to_string(levels, me%real_fmt, levelstr, me%use_numpy) !get optional inputs (if not present, set default value): call optional_int_to_string(linewidth, iline, '3') call optional_logical_to_string(antialiased, antialiasedstr, 'False') !write the arrays: call me%add_str(trim(xname)//' = '//xstr) call me%add_str(trim(yname)//' = '//ystr) call me%add_str(trim(zname)//' = '//zstr) call me%add_str('') !convert inputs for contour plotting: call me%add_str(xname_//', '//yname_//' = np.meshgrid('//trim(xname)//', '//trim(yname)//')') call me%add_str(zname_//' = np.transpose('//zname//')') !optional arguments: extras = '' if (present(levels)) extras = extras//','//'levels='//levelstr if (present(color)) extras = extras//','//'colors='//trim(me%raw_str_token)//'"'//color//'"' if (present(linewidth)) extras = extras//','//'linewidths='//trim(adjustl(iline)) if (present(cmap)) extras = extras//','//'cmap='//trim(me%raw_str_token)//'"'//cmap//'"' !write the plot statement: call me%add_str('CS = ax.plot_wireframe'//'('//xname_//','//yname_//','//zname_//','//& 'label='//trim(me%raw_str_token)//'"'//trim(label)//'",'//& 'antialiased='//trim(antialiasedstr)//','//& 'linestyles='//trim(me%raw_str_token)//'"'//trim(adjustl(linestyle))//'"'//& extras//')') if (present(colorbar)) then if (colorbar) call me%add_str('fig.colorbar(CS)') end if call me%add_str('') else if (present(istat)) istat = -1 write(error_unit,'(A)') 'Error in add_plot: pyplot class not properly initialized.' end if end subroutine plot_wireframe !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Add a 3D x,y,z plot. ! !@note Must initialize the class with ```mplot3d=.true.``` subroutine add_3d_plot(me, x, y, z, label, linestyle, markersize, linewidth, istat) class(pyplot), intent (inout) :: me !! pyplot handler real(wp), dimension(:), intent (in) :: x !! x values real(wp), dimension(:), intent (in) :: y !! y values real(wp), dimension(:), intent (in) :: z !! z values character(len=*), intent (in) :: label !! plot label character(len=*), intent (in) :: linestyle !! style of the plot line integer, intent (in), optional :: markersize !! size of the plot markers integer, intent (in), optional :: linewidth !! width of the plot line integer, intent (out), optional :: istat !! status output (0 means no problems) character(len=:), allocatable :: xstr !! x values stringified character(len=:), allocatable :: ystr !! y values stringified character(len=:), allocatable :: zstr !! z values stringified character(len=max_int_len) :: imark !! actual markers size character(len=max_int_len) :: iline !! actual line width character(len=*), parameter :: xname = 'x' !! x variable name for script character(len=*), parameter :: yname = 'y' !! y variable name for script character(len=*), parameter :: zname = 'z' !! z variable name for script if (allocated(me%str)) then if (present(istat)) istat = 0 !convert the arrays to strings: call vec_to_string(x, me%real_fmt, xstr, me%use_numpy) call vec_to_string(y, me%real_fmt, ystr, me%use_numpy) call vec_to_string(z, me%real_fmt, zstr, me%use_numpy) !get optional inputs (if not present, set default value): call optional_int_to_string(markersize, imark, '3') call optional_int_to_string(linewidth, iline, '3') !write the arrays: call me%add_str(trim(xname)//' = '//xstr) call me%add_str(trim(yname)//' = '//ystr) call me%add_str(trim(zname)//' = '//zstr) call me%add_str('') !write the plot statement: call me%add_str('ax.plot('//& trim(xname)//','//& trim(yname)//','//& trim(zname)//','//& trim(me%raw_str_token)//'"'//trim(linestyle)//'",'//& 'linewidth='//trim(adjustl(iline))//','//& 'markersize='//trim(adjustl(imark))//','//& 'label='//trim(me%raw_str_token)//'"'//trim(label)//'")') call me%add_str('') else if (present(istat)) istat = -1 write(error_unit,'(A)') 'Error in add_3d_plot: pyplot class not properly initialized.' end if end subroutine add_3d_plot !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Add a sphere to a 3D x,y,z plot. ! !@note Must initialize the class with `mplot3d=.true.` and `use_numpy=.true.`. subroutine add_sphere(me, r, xc, yc, zc, n_facets, linewidth, antialiased, color, istat) implicit none class(pyplot), intent (inout) :: me !! pyplot handler real(wp), intent (in) :: r !! radius of the sphere real(wp), intent (in) :: xc !! x value of sphere center real(wp), intent (in) :: yc !! y value of sphere center real(wp), intent (in) :: zc !! z value of sphere center integer, intent (in), optional :: n_facets !! [default is 100] integer, intent (in), optional :: linewidth !! line width logical, intent (in), optional :: antialiased !! enabled anti-aliasing character(len=*), intent (in), optional :: color !! color of the contour line integer, intent (out), optional :: istat !! status output (0 means no problems) character(len=:), allocatable :: rstr !! `r` value stringified character(len=:), allocatable :: xcstr !! `xc` value stringified character(len=:), allocatable :: ycstr !! `yc` value stringified character(len=:), allocatable :: zcstr !! `zc` value stringified character(len=*), parameter :: xname = 'x' !! `x` variable name for script character(len=*), parameter :: yname = 'y' !! `y` variable name for script character(len=*), parameter :: zname = 'z' !! `z` variable name for script character(len=max_int_len) :: linewidth_str !! `linewidth` input stringified character(len=:), allocatable :: antialiased_str !! `antialised` input stringified character(len=max_int_len) :: n_facets_str !! `n_facets` input stringified character(len=:), allocatable :: extras !! optional stuff string if (allocated(me%str)) then !get optional inputs (if not present, set default value): call optional_int_to_string(n_facets, n_facets_str, '100') extras = '' if (present(linewidth)) then call optional_int_to_string(linewidth, linewidth_str, '1') extras = extras//','//'linewidth='//linewidth_str end if if (present(antialiased)) then call optional_logical_to_string(antialiased, antialiased_str, 'False') extras = extras//','//'antialiased='//antialiased_str end if if (present(color)) then extras = extras//','//'color='//trim(me%raw_str_token)//'"'//trim(color)//'"' end if if (present(istat)) istat = 0 !convert the arrays to strings: call real_to_string(r , me%real_fmt, rstr) call real_to_string(xc, me%real_fmt, xcstr) call real_to_string(yc, me%real_fmt, ycstr) call real_to_string(zc, me%real_fmt, zcstr) ! sphere code: call me%add_str('u = np.linspace(0, 2 * np.pi, '//n_facets_str//')') call me%add_str('v = np.linspace(0, np.pi, '//n_facets_str//')') call me%add_str(xname//' = '//xcstr//' + '//rstr//' * np.outer(np.cos(u), np.sin(v))') call me%add_str(yname//' = '//ycstr//' + '//rstr//' * np.outer(np.sin(u), np.sin(v))') call me%add_str(zname//' = '//zcstr//' + '//rstr//' * np.outer(np.ones(np.size(u)), np.cos(v))') call me%add_str('ax.plot_surface('//xname//', '//yname//', '//zname//extras//')') call me%add_str('') else if (present(istat)) istat = -1 write(error_unit,'(A)') 'Error in add_sphere: pyplot class not properly initialized.' end if end subroutine add_sphere !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Add a bar plot. subroutine add_bar(me, x, height, label, width, bottom, color, & yerr, align, xlim, ylim, xscale, yscale, istat) class(pyplot), intent(inout) :: me !! pyplot handler real(wp), dimension(:), intent(in) :: x !! x bar values real(wp), dimension(:), intent(in) :: height !! height bar values character(len=*), intent(in) :: label !! plot label real(wp), dimension(:), intent(in), optional :: width !! width values real(wp), dimension(:), intent(in), optional :: bottom !! bottom values character(len=*), intent(in), optional :: color !! plot color real(wp), dimension(:), intent(in), optional :: yerr !! yerr values character(len=*), intent(in), optional :: align !! default: 'center' real(wp),dimension(2), intent (in), optional :: xlim !! x-axis range real(wp),dimension(2), intent (in), optional :: ylim !! y-axis range character(len=*), intent (in), optional :: xscale !! example: 'linear' (default), 'log' character(len=*), intent (in), optional :: yscale !! example: 'linear' (default), 'log' integer, intent (out),optional :: istat !! status output (0 means no problems) character(len=:), allocatable :: xstr !! x axis values stringified character(len=:), allocatable :: ystr !! y axis values stringified character(len=:), allocatable :: xlimstr !! xlim values stringified character(len=:), allocatable :: ylimstr !! ylim values stringified character(len=:), allocatable :: wstr !! width values stringified character(len=:), allocatable :: bstr !! bottom values stringified character(len=:), allocatable :: plt_str !! plot string character(len=:), allocatable :: yerr_str !! yerr values stringified character(len=*), parameter :: xname = 'x' !! x axis name character(len=*), parameter :: yname = 'y' !! y axis name character(len=*), parameter :: wname = 'w' !! width name character(len=*), parameter :: bname = 'b' !! bottom name character(len=*), parameter :: yerrname = 'yerr' !! yerr name if (allocated(me%str)) then if (present(istat)) istat = 0 !axis limits (optional): if (present(xlim)) call vec_to_string(xlim, me%real_fmt, xlimstr, me%use_numpy) if (present(ylim)) call vec_to_string(ylim, me%real_fmt, ylimstr, me%use_numpy) !convert the arrays to strings: call vec_to_string(x, me%real_fmt, xstr, me%use_numpy) call vec_to_string(height, me%real_fmt, ystr, me%use_numpy) if (present(width)) call vec_to_string(width, me%real_fmt, wstr, me%use_numpy) if (present(bottom)) call vec_to_string(bottom, me%real_fmt, bstr, me%use_numpy) if (present(yerr)) call vec_to_string(yerr, me%real_fmt, yerr_str, me%use_numpy) !write the arrays: call me%add_str(trim(xname)//' = '//xstr) call me%add_str(trim(yname)//' = '//ystr) if (present(width)) call me%add_str(trim(wname)//' = '//wstr) if (present(bottom)) call me%add_str(trim(bname)//' = '//bstr) if (present(yerr)) call me%add_str(trim(yerrname)//' = '//yerr_str) call me%add_str('') !create the plot string: plt_str = 'ax.bar('//& 'x='//trim(xname)//','//& 'height='//trim(yname)//',' if (present(yerr)) plt_str=plt_str//'yerr='//trim(yerrname)//',' if (present(width)) plt_str=plt_str//'width='//trim(wname)//',' if (present(bottom)) plt_str=plt_str//'bottom='//trim(bstr)//',' if (present(color)) plt_str=plt_str//'color='//trim(me%raw_str_token)//'"'//trim(color)//'",' if (present(align)) plt_str=plt_str//'align='//trim(me%raw_str_token)//'"'//trim(align)//'",' plt_str=plt_str//'label='//trim(me%raw_str_token)//'"'//trim(label)//'")' !write the plot statement: call me%add_str(plt_str) !axis limits: if (allocated(xlimstr)) call me%add_str('ax.set_xlim('//xlimstr//')') if (allocated(ylimstr)) call me%add_str('ax.set_ylim('//ylimstr//')') !axis scales: if (present(xscale)) call me%add_str('ax.set_xscale('//trim(me%raw_str_token)//'"'//xscale//'")') if (present(yscale)) call me%add_str('ax.set_yscale('//trim(me%raw_str_token)//'"'//yscale//'")') call me%add_str('') else if (present(istat)) istat = -1 write(error_unit,'(A)') 'Error in add_bar: pyplot class not properly initialized.' end if end subroutine add_bar !***************************************************************************************** !***************************************************************************************** !> ! Add an image plot using `imshow`. ! !### Note ! * Based on code by Ricardo Torres, 4/2/2017. subroutine add_imshow(me, x, xlim, ylim, istat) class(pyplot), intent (inout) :: me !! pyplot handler real(wp),dimension(:,:),intent (in) :: x !! x values real(wp),dimension(2), intent (in), optional :: xlim !! x-axis range real(wp),dimension(2), intent (in), optional :: ylim !! y-axis range integer, intent (out),optional :: istat !! status output (0 means no problems) character(len=:), allocatable :: xstr !! x values stringified character(len=*), parameter :: xname = 'x' !! x variable name for script !axis limits (optional): character(len=:), allocatable :: xlimstr !! xlim values stringified character(len=:), allocatable :: ylimstr !! ylim values stringified if (allocated(me%str)) then if (present(istat)) istat = 0 if (present(xlim)) call vec_to_string(xlim, me%real_fmt, xlimstr, me%use_numpy) if (present(ylim)) call vec_to_string(ylim, me%real_fmt, ylimstr, me%use_numpy) !convert the arrays to strings: call matrix_to_string(x, me%real_fmt, xstr, me%use_numpy) !write the arrays: call me%add_str(trim(xname)//' = '//xstr) call me%add_str('') !write the plot statement: call me%add_str('ax.imshow('//trim(xname)//')') call me%add_str('') !axis limits: if (allocated(xlimstr)) call me%add_str('ax.set_xlim('//xlimstr//')') if (allocated(ylimstr)) call me%add_str('ax.set_ylim('//ylimstr//')') else if (present(istat)) istat = -1 write(error_unit,'(A)') 'Error in add_imshow: pyplot class not properly initialized.' end if end subroutine add_imshow !***************************************************************************************** !***************************************************************************************** !> author: Alexander Sandrock ! ! Add an x,y plot with errorbars. subroutine add_errorbar(me, x, y, label, linestyle, xerr, yerr, markersize, linewidth, xlim, ylim, xscale, yscale, color, istat) class(pyplot), intent (inout) :: me !! pyplot handler real(wp), dimension(:), intent (in) :: x !! x values real(wp), dimension(:), intent (in) :: y !! y values character(len=*), intent (in) :: label !! plot label character(len=*), intent (in) :: linestyle !! style of the plot line real(wp), dimension(:), intent (in), optional :: xerr !! x errorbar sizes real(wp), dimension(:), intent (in), optional :: yerr !! y errorbar sizes integer, intent (in), optional :: markersize !! size of the plot markers integer, intent (in), optional :: linewidth !! width of the plot line real(wp),dimension(2), intent (in), optional :: xlim !! x-axis range real(wp),dimension(2), intent (in), optional :: ylim !! y-axis range character(len=*), intent (in), optional :: xscale !! example: 'linear' (default), 'log' character(len=*), intent (in), optional :: yscale !! example: 'linear' (default), 'log' real(wp),dimension(:), intent (in), optional :: color !! RGB color tuple [0-1,0-1,0-1] integer, intent (out),optional :: istat !! status output (0 means no problems) character(len=:), allocatable :: arg_str !! the arguments to pass to `plot` character(len=:), allocatable :: xstr !! x values stringified character(len=:), allocatable :: ystr !! y values stringified character(len=:), allocatable :: xlimstr !! xlim values stringified character(len=:), allocatable :: ylimstr !! ylim values stringified character(len=:), allocatable :: xerrstr !! xerr values stringified character(len=:), allocatable :: yerrstr !! yerr values stringified character(len=:), allocatable :: color_str !! color values stringified character(len=max_int_len) :: imark !! actual markers size character(len=max_int_len) :: iline !! actual line width character(len=*), parameter :: xname = 'x' !! x variable name for script character(len=*), parameter :: yname = 'y' !! y variable name for script character(len=*), parameter :: xerrname = 'xerr' !! xerr variable name for script character(len=*), parameter :: yerrname = 'yerr' !! yerr variable name for script if (allocated(me%str)) then if (present(istat)) istat = 0 !axis limits (optional): if (present(xlim)) call vec_to_string(xlim, me%real_fmt, xlimstr, me%use_numpy) if (present(ylim)) call vec_to_string(ylim, me%real_fmt, ylimstr, me%use_numpy) !errorbar sizes (optional): if (present(xerr)) call vec_to_string(xerr, me%real_fmt, xerrstr, me%use_numpy) if (present(yerr)) call vec_to_string(yerr, me%real_fmt, yerrstr, me%use_numpy) !convert the arrays to strings: call vec_to_string(x, me%real_fmt, xstr, me%use_numpy) call vec_to_string(y, me%real_fmt, ystr, me%use_numpy) !get optional inputs (if not present, set default value): call optional_int_to_string(markersize, imark, '3') call optional_int_to_string(linewidth, iline, '3') !write the arrays: call me%add_str(trim(xname)//' = '//xstr) call me%add_str(trim(yname)//' = '//ystr) call me%add_str('') if (present(xerr)) call me%add_str(trim(xerrname)//' = '//xerrstr) if (present(yerr)) call me%add_str(trim(yerrname)//' = '//yerrstr) if (present(xerr) .or. present(yerr)) call me%add_str('') !main arguments for plot: arg_str = trim(xname)//','//& trim(yname)//','//& 'fmt='//trim(me%raw_str_token)//'"'//trim(linestyle)//'",'//& 'linewidth='//trim(adjustl(iline))//','//& 'markersize='//trim(adjustl(imark))//','//& 'label='//trim(me%raw_str_token)//'"'//trim(label)//'"' ! optional arguments: if (present(xerr)) then arg_str = arg_str//','//'xerr='//trim(xerrname) end if if (present(yerr)) then arg_str = arg_str//','//'yerr='//trim(yerrname) end if if (present(color)) then if (size(color)<=3) then call vec_to_string(color(1:3), '*', color_str, use_numpy=.false., is_tuple=.true.) arg_str = arg_str//',color='//trim(color_str) end if end if !write the plot statement: call me%add_str('ax.errorbar('//arg_str//')') !axis limits: if (allocated(xlimstr)) call me%add_str('ax.set_xlim('//xlimstr//')') if (allocated(ylimstr)) call me%add_str('ax.set_ylim('//ylimstr//')') !axis scales: if (present(xscale)) call me%add_str('ax.set_xscale('//trim(me%raw_str_token)//'"'//xscale//'")') if (present(yscale)) call me%add_str('ax.set_yscale('//trim(me%raw_str_token)//'"'//yscale//'")') call me%add_str('') else if (present(istat)) istat = -1 write(error_unit,'(A)') 'Error in add_errorbar: pyplot class not properly initialized.' end if end subroutine add_errorbar !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Integer to string, specifying the default value if ! the optional argument is not present. subroutine optional_int_to_string(int_value, string_value, default_value) integer, intent(in), optional :: int_value !! integer value character(len=*), intent(out) :: string_value !! integer value stringified character(len=*), intent(in) :: default_value !! default integer value if (present(int_value)) then call integer_to_string(int_value, string_value) else string_value = default_value end if end subroutine optional_int_to_string !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Logical to string, specifying the default value if ! the optional argument is not present. subroutine optional_logical_to_string(logical_value, string_value, default_value) logical,intent(in),optional :: logical_value character(len=:),allocatable,intent(out) :: string_value !! integer value stringified character(len=*),intent(in) :: default_value !! default integer value if (present(logical_value)) then if (logical_value) then string_value = 'True' else string_value = 'False' end if else string_value = default_value end if end subroutine optional_logical_to_string !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Integer to string conversion. subroutine integer_to_string(i, s) integer, intent(in), optional :: i !! integer value character(len=*), intent(out) :: s !! integer value stringified integer :: istat !! IO status write(s, int_fmt, iostat=istat) i if (istat/=0) then write(error_unit,'(A)') 'Error converting integer to string' s = '****' else s = adjustl(s) end if end subroutine integer_to_string !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Real scalar to string. subroutine real_to_string(v, fmt, str) real(wp), intent(in) :: v !! real values character(len=*), intent(in) :: fmt !! real format string character(len=:), allocatable, intent(out) :: str !! real values stringified integer :: istat !! IO status character(len=max_real_len) :: tmp !! dummy string if (fmt=='*') then write(tmp, *, iostat=istat) v else write(tmp, fmt, iostat=istat) v end if if (istat/=0) then write(error_unit,'(A)') 'Error in real_to_string' str = '****' else str = trim(adjustl(tmp)) end if end subroutine real_to_string !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Real vector to string. subroutine vec_to_string(v, fmt, str, use_numpy, is_tuple) real(wp), dimension(:), intent(in) :: v !! real values character(len=*), intent(in) :: fmt !! real format string character(len=:), allocatable, intent(out) :: str !! real values stringified logical, intent(in) :: use_numpy !! activate numpy python module usage logical,intent(in),optional :: is_tuple !! if true [default], use '()', if false use '[]' integer :: i !! counter integer :: istat !! IO status character(len=max_real_len) :: tmp !! dummy string logical :: tuple if (present(is_tuple)) then tuple = is_tuple else tuple = .false. end if if (tuple) then str = '(' else str = '[' end if do i=1, size(v) if (fmt=='*') then write(tmp, *, iostat=istat) v(i) else write(tmp, fmt, iostat=istat) v(i) end if if (istat/=0) then write(error_unit,'(A)') 'Error in vec_to_string' str = '****' return end if str = str//trim(adjustl(tmp)) if (i<size(v)) str = str // ',' end do if (tuple) then str = str // ')' else str = str // ']' end if !convert to numpy array if necessary: if (use_numpy) str = 'np.array('//str//')' end subroutine vec_to_string !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Real matrix (rank 2) to string. subroutine matrix_to_string(v, fmt, str, use_numpy) real(wp), dimension(:,:), intent(in) :: v !! real values character(len=*), intent(in) :: fmt !! real format string character(len=:), allocatable, intent(out) :: str !! real values stringified logical, intent(in) :: use_numpy !! activate numpy python module usage integer :: i !! counter character(len=:),allocatable :: tmp !! dummy string str = '[' do i=1, size(v,1) !rows call vec_to_string(v(i,:), fmt, tmp, use_numpy) !one row at a time str = str//trim(adjustl(tmp)) if (i<size(v,1)) str = str // ',' end do str = str // ']' !convert to numpy array if necessary: if (use_numpy) str = 'np.array('//str//')' end subroutine matrix_to_string !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! date: 8/16/2015 ! ! Write the buffer to a file, and then execute it with Python. ! ! If user specifies a Python file name, then the file is kept, otherwise ! a temporary filename is used, and the file is deleted after it is used. subroutine execute(me, pyfile, istat, python) class(pyplot), intent(inout) :: me !! pytplot handler character(len=*), intent(in), optional :: pyfile !! name of the python script to generate integer, intent (out),optional :: istat !! status output (0 means no problems) character(len=*), intent(in),optional :: python !! python executable to use. (by default, this is 'python') integer :: iunit !! IO unit character(len=:), allocatable :: file !! file name logical :: scratch !! if a scratch file is to be used integer :: iostat !! open/close status code character(len=:), allocatable :: python_ !! python executable to use if (allocated(me%str)) then if (present(istat)) istat = 0 scratch = (.not. present(pyfile)) !file name to use: if (scratch) then file = trim(tmp_file) !use the default else file = trim(pyfile) !use the user-specified name end if if (file == '') then if (present(istat)) istat = -1 write(error_unit,'(A)') 'Error: filename is blank.' return end if !open the file: open(newunit=iunit, file=file, status='REPLACE', iostat=iostat) if (iostat/=0) then if (present(istat)) istat = iostat write(error_unit,'(A)') 'Error opening file: '//trim(file) return end if !write to the file: write(iunit, '(A)') me%str !to ensure that the file is there for the next !command line call, we have to close it here. close(iunit, iostat=iostat) if (iostat/=0) then if (present(istat)) istat = iostat write(error_unit,'(A)') 'Error closing file: '//trim(file) else if (present(python)) then python_ = trim(python) else python_ = python_exe end if !run the file using python: if (file(1:1)/='"') then ! if not already in quotes, should enclose in quotes call execute_command_line(python_//' "'//file//'"') else call execute_command_line(python_//' '//file) end if if (scratch) then !delete the file (have to reopen it because !Fortran has no file delete function) open(newunit=iunit, file=file, status='OLD', iostat=iostat) if (iostat==0) close(iunit, status='DELETE', iostat=iostat) end if if (iostat/=0) then if (present(istat)) istat = iostat write(error_unit,'(A)') 'Error closing file.' end if end if !cleanup: if (allocated(file)) deallocate(file) else if (present(istat)) istat = -1 end if end subroutine execute !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Some final things to add before saving or showing the figure. subroutine finish_ops(me) class(pyplot),intent(inout) :: me !! pyplot handler if (me%show_legend) then call me%add_str('ax.legend(loc="best")') call me%add_str('') end if if (me%axis_equal) then if (me%mplot3d) then call me%add_str('ax.set_aspect("auto")') call me%add_str('') call me%add_str('def set_axes_equal(ax):') call me%add_str(' x_limits = ax.get_xlim3d()') call me%add_str(' y_limits = ax.get_ylim3d()') call me%add_str(' z_limits = ax.get_zlim3d()') call me%add_str(' x_range = abs(x_limits[1] - x_limits[0])') call me%add_str(' x_middle = np.mean(x_limits)') call me%add_str(' y_range = abs(y_limits[1] - y_limits[0])') call me%add_str(' y_middle = np.mean(y_limits)') call me%add_str(' z_range = abs(z_limits[1] - z_limits[0])') call me%add_str(' z_middle = np.mean(z_limits)') call me%add_str(' plot_radius = 0.5*max([x_range, y_range, z_range])') call me%add_str(' ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])') call me%add_str(' ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])') call me%add_str(' ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])') call me%add_str('set_axes_equal(ax)') else call me%add_str('ax.axis("equal")') end if call me%add_str('') end if if (allocated(me%xaxis_date_fmt) .or. allocated(me%yaxis_date_fmt)) then call me%add_str('from matplotlib.dates import DateFormatter') if (allocated(me%xaxis_date_fmt)) & call me%add_str('ax.xaxis.set_major_formatter(DateFormatter("'//trim(me%xaxis_date_fmt)//'"))') if (allocated(me%yaxis_date_fmt)) & call me%add_str('ax.yaxis.set_major_formatter(DateFormatter("'//trim(me%yaxis_date_fmt)//'"))') call me%add_str('') end if if (me%tight_layout) then call me%add_str('fig.tight_layout()') call me%add_str('') end if end subroutine finish_ops !***************************************************************************************** !***************************************************************************************** !> author: Jacob Williams ! ! Save the figure. ! !### History ! * modified: Johannes Rieke 6/16/2017 ! * modified: Jacob Williams 6/16/2017 subroutine savefig(me, figfile, pyfile, dpi, transparent, facecolor, edgecolor, orientation, istat, python) class(pyplot), intent(inout) :: me !! pyplot handler character(len=*), intent(in) :: figfile !! file name for the figure character(len=*), intent(in), optional :: pyfile !! name of the Python script to generate character(len=*), intent(in), optional :: dpi !! resolution of the figure for png !! [note this is a string] logical, intent(in), optional :: transparent !! transparent background (T/F) character(len=*), intent(in), optional :: facecolor !! the colors of the figure rectangle character(len=*), intent(in), optional :: edgecolor !! the colors of the figure rectangle character(len=*), intent(in), optional :: orientation !! 'landscape' or 'portrait' integer, intent (out), optional :: istat !! status output (0 means no problems) character(len=*), intent(in),optional :: python !! python executable to use. (by default, this is 'python') character(len=:),allocatable :: tmp !! for building the `savefig` arguments. if (allocated(me%str)) then if (present(istat)) istat = 0 !finish up the string: call me%finish_ops() !build the savefig arguments: tmp = trim(me%raw_str_token)//'"'//trim(figfile)//'"' if (present(dpi)) tmp = tmp//', dpi='//trim(dpi) if (present(transparent)) then if (transparent) then tmp = tmp//', transparent=True' else tmp = tmp//', transparent=False' end if end if if (present(facecolor)) tmp = tmp//', facecolor="'//trim(facecolor)//'"' if (present(edgecolor)) tmp = tmp//', edgecolor="'//trim(edgecolor)//'"' if (present(orientation)) tmp = tmp//', orientation="'//trim(orientation)//'"' if (me%use_oo_api) then call me%add_str('canvas = FigureCanvas(fig)') call me%add_str('canvas.print_figure('//tmp//')') else call me%add_str('plt.savefig('//tmp//')') endif deallocate(tmp) !run it: call me%execute(pyfile, istat=istat, python=python) else if (present(istat)) istat = -1 write(error_unit,'(A)') 'error in savefig: pyplot class not properly initialized.' end if end subroutine savefig !***************************************************************************************** !***************************************************************************************** !> author: Johannes Rieke ! date: 6/16/2017 ! ! Shows the figure. subroutine showfig(me, pyfile, istat, python) class(pyplot), intent(inout) :: me !! pyplot handler character(len=*), intent(in), optional :: pyfile !! name of the Python script to generate integer, intent (out), optional :: istat !! status output (0 means no problems) character(len=*), intent(in),optional :: python !! python executable to use. (by default, this is 'python') if (.not. allocated(me%str)) then if (present(istat)) istat = -1 write(error_unit,'(A)') 'error in showfig: pyplot class not properly initialized.' else if (me%use_oo_api) then if (present(istat)) istat = -2 write(error_unit,'(A)') 'error in showfig: not compatible with "use_oo_api" option' else if (present(istat)) istat = 0 !finish up the string: call me%finish_ops() !show figure: call me%add_str('plt.show()') !run it: call me%execute(pyfile, istat=istat, python=python) end if end subroutine showfig !***************************************************************************************** !***************************************************************************************** end module pyplot_module !*****************************************************************************************