From 1c35763b6e5b11631ef86cf597274bf3ecc0f8ee Mon Sep 17 00:00:00 2001 From: Jacob Williams Date: Wed, 19 Jul 2017 19:25:22 -0500 Subject: [PATCH] removed error_stop statements. Fixes #14. added facecolor="white" as default. RGB vector can be used to specify line color in add_plot. Fixes #15. Replaced a FLUSH statement with CLOSE (better?) --- src/pyplot_module.f90 | 199 ++++++++++++++++++++++++++++++------------ src/tests/test.f90 | 32 +++---- 2 files changed, 158 insertions(+), 73 deletions(-) diff --git a/src/pyplot_module.f90 b/src/pyplot_module.f90 index 4d8a465..396d876 100644 --- a/src/pyplot_module.f90 +++ b/src/pyplot_module.f90 @@ -11,7 +11,7 @@ module pyplot_module - use, intrinsic :: iso_fortran_env, only : real64 + use, intrinsic :: iso_fortran_env, only : real64, error_unit implicit none @@ -204,9 +204,9 @@ subroutine initialize(me, grid, xlabel, ylabel, zlabel, title, legend, use_numpy call me%add_str('') if (present(figsize)) then !if specifying the figure size - call me%add_str('fig = plt.figure(figsize=('//trim(width_str)//','//trim(height_str)//'))') + call me%add_str('fig = plt.figure(figsize=('//trim(width_str)//','//trim(height_str)//'),facecolor="white")') else - call me%add_str('fig = plt.figure()') + call me%add_str('fig = plt.figure(facecolor="white")') end if if (me%mplot3d) then @@ -236,7 +236,7 @@ end subroutine initialize ! ! Add an x,y plot. - subroutine add_plot(me, x, y, label, linestyle, markersize, linewidth, xlim, ylim, xscale, yscale) + 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 @@ -249,11 +249,15 @@ subroutine add_plot(me, x, y, label, linestyle, markersize, linewidth, xlim, yli 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) :: 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 @@ -261,6 +265,8 @@ subroutine add_plot(me, x, y, label, linestyle, markersize, linewidth, xlim, yli if (allocated(me%str)) then + 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) @@ -278,14 +284,24 @@ subroutine add_plot(me, x, y, label, linestyle, markersize, linewidth, xlim, yli call me%add_str(trim(yname)//' = '//ystr) call me%add_str('') + !main arguments for plot: + arg_str = trim(xname)//','//& + trim(yname)//','//& + '"'//trim(linestyle)//'",'//& + 'linewidth='//trim(adjustl(iline))//','//& + 'markersize='//trim(adjustl(imark))//','//& + 'label="'//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('//& - trim(xname)//','//& - trim(yname)//','//& - '"'//trim(linestyle)//'",'//& - 'linewidth='//trim(adjustl(iline))//','//& - 'markersize='//trim(adjustl(imark))//','//& - 'label="'//trim(label)//'")') + call me%add_str('ax.plot('//arg_str//')') !axis limits: if (allocated(xlimstr)) call me%add_str('ax.set_xlim('//xlimstr//')') @@ -298,7 +314,8 @@ subroutine add_plot(me, x, y, label, linestyle, markersize, linewidth, xlim, yli call me%add_str('') else - error stop 'Error in add_plot: pyplot class not properly initialized.' + istat = -1 + write(error_unit,'(A)') 'Error in add_plot: pyplot class not properly initialized.' end if end subroutine add_plot @@ -309,7 +326,7 @@ end subroutine add_plot ! ! Add a histogram plot. - subroutine add_hist(me, x, label, xlim, ylim, xscale, yscale, bins, normed, cumulative) + 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 @@ -321,6 +338,7 @@ subroutine add_hist(me, x, label, xlim, ylim, xscale, yscale, bins, normed, cumu integer, intent (in), optional :: bins !! number of bins logical, intent (in), optional :: normed !! boolean flag that determines whether bin counts are normalized logical, intent (in), optional :: cumulative !! boolean flag that determines whether histogram represents the cumulative density of dataset + integer, intent (out) :: istat !! status output (0 means no problems) character(len=*), parameter :: xname = 'x' !! x variable name for script character(len=:), allocatable :: xstr !! x values stringified @@ -332,6 +350,8 @@ subroutine add_hist(me, x, label, xlim, ylim, xscale, yscale, bins, normed, cumu if (allocated(me%str)) then + 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) @@ -367,7 +387,8 @@ subroutine add_hist(me, x, label, xlim, ylim, xscale, yscale, bins, normed, cumu call me%add_str('') else - error stop 'Error in add_plot: pyplot class not properly initialized.' + istat = -1 + write(error_unit,'(A)') 'Error in add_plot: pyplot class not properly initialized.' end if end subroutine add_hist @@ -380,7 +401,7 @@ end subroutine add_hist ! !@note This requires `use_numpy` to be True. - subroutine add_contour(me, x, y, z, label, linestyle, linewidth, levels, color, filled, cmap) + subroutine add_contour(me, x, y, z, label, linestyle, linewidth, levels, color, filled, cmap, istat) class(pyplot), intent (inout) :: me !! pyplot handler real(wp),dimension(:), intent (in) :: x !! x values @@ -393,11 +414,12 @@ subroutine add_contour(me, x, y, z, label, linestyle, linewidth, levels, color, 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') + integer, intent (out) :: istat !! status output (0 means no problems) - character(len=:), allocatable :: xstr !! x values strinfied - character(len=:), allocatable :: ystr !! y values strinfied - character(len=:), allocatable :: zstr !! z values strinfied - character(len=:), allocatable :: levelstr !! levels vector strinfied + 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 @@ -410,6 +432,8 @@ subroutine add_contour(me, x, y, z, label, linestyle, linewidth, levels, color, if (allocated(me%str)) then + 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) @@ -452,7 +476,8 @@ subroutine add_contour(me, x, y, z, label, linestyle, linewidth, levels, color, call me%add_str('') else - error stop 'Error in add_plot: pyplot class not properly initialized.' + istat = -1 + write(error_unit,'(A)') 'Error in add_plot: pyplot class not properly initialized.' end if end subroutine add_contour @@ -465,7 +490,7 @@ end subroutine add_contour ! !@note Must initialize the class with ```mplot3d=.true.``` - subroutine add_3d_plot(me, x, y, z, label, linestyle, markersize, linewidth) + 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 @@ -475,10 +500,11 @@ subroutine add_3d_plot(me, x, y, z, label, linestyle, markersize, linewidth) 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) :: istat !! status output (0 means no problems) - character(len=:), allocatable :: xstr !! x values strinfied - character(len=:), allocatable :: ystr !! y values strinfied - character(len=:), allocatable :: zstr !! z values strinfied + 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 @@ -487,6 +513,8 @@ subroutine add_3d_plot(me, x, y, z, label, linestyle, markersize, linewidth) if (allocated(me%str)) then + 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) @@ -514,7 +542,8 @@ subroutine add_3d_plot(me, x, y, z, label, linestyle, markersize, linewidth) call me%add_str('') else - error stop 'Error in add_3d_plot: pyplot class not properly initialized.' + istat = -1 + write(error_unit,'(A)') 'Error in add_3d_plot: pyplot class not properly initialized.' end if end subroutine add_3d_plot @@ -526,7 +555,7 @@ end subroutine add_3d_plot ! Add a bar plot. subroutine add_bar(me, left, height, label, width, bottom, color, & - yerr, align, xlim, ylim, xscale, yscale) + yerr, align, xlim, ylim, xscale, yscale, istat) class(pyplot), intent(inout) :: me !! pyplot handler real(wp), dimension(:), intent(in) :: left !! left bar values @@ -541,6 +570,7 @@ subroutine add_bar(me, left, height, label, width, bottom, color, & 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) :: istat !! status output (0 means no problems) character(len=:), allocatable :: xstr !! x axis values stringified character(len=:), allocatable :: ystr !! y axis values stringified @@ -558,6 +588,8 @@ subroutine add_bar(me, left, height, label, width, bottom, color, & if (allocated(me%str)) then + 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) @@ -602,7 +634,8 @@ subroutine add_bar(me, left, height, label, width, bottom, color, & call me%add_str('') else - error stop 'Error in add_bar: pyplot class not properly initialized.' + istat = -1 + write(error_unit,'(A)') 'Error in add_bar: pyplot class not properly initialized.' end if end subroutine add_bar @@ -615,12 +648,13 @@ end subroutine add_bar !### Note ! * Based on code by Ricardo Torres, 4/2/2017. - subroutine add_imshow(me, x, xlim, ylim) + 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 + 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) :: istat !! status output (0 means no problems) character(len=:), allocatable :: xstr !! x values stringified character(len=*), parameter :: xname = 'x' !! x variable name for script @@ -631,6 +665,8 @@ subroutine add_imshow(me, x, xlim, ylim) if (allocated(me%str)) then + 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) @@ -650,7 +686,8 @@ subroutine add_imshow(me, x, xlim, ylim) if (allocated(ylimstr)) call me%add_str('ax.set_ylim('//ylimstr//')') else - error stop 'Error in add_imshow: pyplot class not properly initialized.' + istat = -1 + write(error_unit,'(A)') 'Error in add_imshow: pyplot class not properly initialized.' end if end subroutine add_imshow @@ -708,6 +745,7 @@ end subroutine optional_logical_to_string ! 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 @@ -715,7 +753,8 @@ subroutine integer_to_string(i, s) write(s, int_fmt, iostat=istat) i if (istat/=0) then - error stop 'Error converting integer to string' + write(error_unit,'(A)') 'Error converting integer to string' + s = '****' else s = adjustl(s) end if @@ -728,29 +767,51 @@ end subroutine integer_to_string ! ! Real vector to string. - subroutine vec_to_string(v, fmt, str, use_numpy) + 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 - str = '[' 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) error stop 'Error in vec_to_string' + if (istat/=0) then + write(error_unit,'(A)') 'Error in vec_to_string' + str = '****' + return + end if str = str//trim(adjustl(tmp)) if (i0) then + ! space in path, probably should enclose in quotes + call execute_command_line(python_exe//' "'//file//'"') + else + call execute_command_line(python_exe//' '//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=istat) + if (istat/=0) close(iunit, status='DELETE', iostat=istat) + end if + if (istat/=0) then + write(error_unit,'(A)') 'Error closing file.' + end if - !close the file: - if (scratch) then - close(iunit, status='DELETE', iostat=istat) - else - close(iunit, iostat=istat) end if - if (istat/=0) error stop 'Error closing file.' !cleanup: if (allocated(file)) deallocate(file) @@ -876,7 +953,7 @@ end subroutine finish_ops ! * modified: Johannes Rieke 6/16/2017 ! * modified: Jacob Williams 6/16/2017 - subroutine savefig(me, figfile, pyfile, dpi, transparent, facecolor, edgecolor, orientation) + subroutine savefig(me, figfile, pyfile, dpi, transparent, facecolor, edgecolor, orientation, istat) class(pyplot), intent(inout) :: me !! pyplot handler character(len=*), intent(in) :: figfile !! file name for the figure @@ -887,11 +964,14 @@ subroutine savefig(me, figfile, pyfile, dpi, transparent, facecolor, edgecolor, 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) :: istat !! status output (0 means no problems) character(len=:),allocatable :: tmp !! for building the `savefig` arguments. if (allocated(me%str)) then + istat = 0 + !finish up the string: call me%finish_ops() @@ -912,10 +992,11 @@ subroutine savefig(me, figfile, pyfile, dpi, transparent, facecolor, edgecolor, deallocate(tmp) !run it: - call me%execute(pyfile) + call me%execute(pyfile, istat=istat) else - error stop 'error in savefig: pyplot class not properly initialized.' + istat = -1 + write(error_unit,'(A)') 'error in savefig: pyplot class not properly initialized.' end if end subroutine savefig @@ -927,13 +1008,16 @@ end subroutine savefig ! ! Shows the figure. - subroutine showfig(me, pyfile) + subroutine showfig(me, pyfile, istat) class(pyplot), intent(inout) :: me !! pyplot handler character(len=*), intent(in), optional :: pyfile !! name of the Python script to generate + integer, intent (out) :: istat !! status output (0 means no problems) if (allocated(me%str)) then + istat = 0 + !finish up the string: call me%finish_ops() @@ -941,10 +1025,11 @@ subroutine showfig(me, pyfile) call me%add_str('plt.show()') !run it: - call me%execute(pyfile) + call me%execute(pyfile, istat=istat) else - error stop 'error in showfig: pyplot class not properly initialized.' + istat = -1 + write(error_unit,'(A)') 'error in showfig: pyplot class not properly initialized.' end if end subroutine showfig diff --git a/src/tests/test.f90 b/src/tests/test.f90 index fef3fe4..843101d 100644 --- a/src/tests/test.f90 +++ b/src/tests/test.f90 @@ -25,8 +25,8 @@ program test integer :: i !! counter integer :: j !! counter real(wp) :: r2 !! temp variable - - real(wp), dimension(n,n) :: mat !! image values + real(wp), dimension(n,n) :: mat !! image values + integer :: istat !! status code !generate some data: x = [(real(i,wp), i=0,size(x)-1)]/5.0_wp @@ -44,10 +44,10 @@ program test !2d line plot: call plt%initialize(grid=.true.,xlabel='angle (rad)',figsize=[20,10],& title='plot test',legend=.true.,axis_equal=.true.) - call plt%add_plot(x,sx,label='$\sin (x)$',linestyle='b-o',markersize=5,linewidth=2) - call plt%add_plot(x,cx,label='$\cos (x)$',linestyle='r-o',markersize=5,linewidth=2) - call plt%add_plot(x,tx,label='$\sin (x) \cos (x)$',linestyle='g-o',markersize=2,linewidth=1) - call plt%savefig('plottest.png', pyfile='plottest.py') + call plt%add_plot(x,sx,label='$\sin (x)$',linestyle='b-o',markersize=5,linewidth=2,istat=istat) + call plt%add_plot(x,cx,label='$\cos (x)$',linestyle='r-o',markersize=5,linewidth=2,istat=istat) + call plt%add_plot(x,tx,label='$\sin (x) \cos (x)$',linestyle='g-o',markersize=2,linewidth=1,istat=istat) + call plt%savefig('plottest.png', pyfile='plottest.py',istat=istat) !bar chart: tx = 0.1_wp !for bar width @@ -59,8 +59,8 @@ program test ytick_labelsize = 20,& legend_fontsize = 20 ) call plt%add_bar(left=x,height=sx,width=tx,label='$\sin (x)$',& - color='r',yerr=yerr,xlim=[0.0_wp, 20.0_wp],align='center') - call plt%savefig('bartest.png', pyfile='bartest.py') + color='r',yerr=yerr,xlim=[0.0_wp, 20.0_wp],align='center',istat=istat) + call plt%savefig('bartest.png', pyfile='bartest.py',istat=istat) !contour plot: x = [(real(i,wp), i=0,n-1)]/100.0_wp @@ -75,15 +75,15 @@ program test ylabel='y angle (rad)',figsize=[10,10],& title='Contour plot test', real_fmt='*') call plt%add_contour(x, y, z, label='contour', linestyle='-', & - linewidth=2, filled=.true., cmap='bone') - call plt%savefig('contour.png',pyfile='contour.py') + linewidth=2, filled=.true., cmap='bone',istat=istat) + call plt%savefig('contour.png',pyfile='contour.py',istat=istat) !image plot: call plt%initialize(grid=.true.,xlabel='x',ylabel='y',figsize=[20,20],& title='imshow test',& real_fmt='(F9.3)') - call plt%add_imshow(mat,xlim=[0.0_wp, 100.0_wp],ylim=[0.0_wp, 100.0_wp]) - call plt%savefig('imshow.png', pyfile='imshow.py') + call plt%add_imshow(mat,xlim=[0.0_wp, 100.0_wp],ylim=[0.0_wp, 100.0_wp],istat=istat) + call plt%savefig('imshow.png', pyfile='imshow.py',istat=istat) !histogram chart: x = [0.194,0.501,-1.241,1.425,-2.217,-0.342,-0.979,0.909,0.994,0.101, & @@ -106,8 +106,8 @@ program test ytick_labelsize = 20,& legend_fontsize = 20 ) - call plt%add_hist(x=x, label='x', normed=.true.) - call plt%savefig('histtest1.png', pyfile='histtest1.py') + call plt%add_hist(x=x, label='x', normed=.true.,istat=istat) + call plt%savefig('histtest1.png', pyfile='histtest1.py',istat=istat) call plt%initialize(grid=.true.,xlabel='x',& title='cumulative hist test',& @@ -118,11 +118,11 @@ program test ytick_labelsize = 20,& legend_fontsize = 20 ) - call plt%add_hist(x=x, label='x', bins=8, cumulative=.true.) + call plt%add_hist(x=x, label='x', bins=8, cumulative=.true.,istat=istat) call plt%savefig('histtest2.png', & pyfile='histtest2.py', & dpi='200', & - transparent=.true.) + transparent=.true.,istat=istat) end program test !*****************************************************************************************