Skip to content

Commit 569f025

Browse files
Merge pull request #17 from jacobwilliams/sphere
Sphere
2 parents a7ab913 + 780f6f2 commit 569f025

File tree

2 files changed

+158
-1
lines changed

2 files changed

+158
-1
lines changed

src/pyplot_module.f90

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ module pyplot_module
5151

5252
procedure, public :: add_plot !! add a 2d plot to pyplot instance
5353
procedure, public :: add_3d_plot !! add a 3d plot to pyplot instance
54+
procedure, public :: add_sphere !! add a 3d sphere to pyplot instance
5455
procedure, public :: add_contour !! add a contour plot to pyplot instance
5556
procedure, public :: add_bar !! add a barplot to pyplot instance
5657
procedure, public :: add_imshow !! add an image plot (using `imshow`)
@@ -549,6 +550,83 @@ subroutine add_3d_plot(me, x, y, z, label, linestyle, markersize, linewidth, ist
549550
end subroutine add_3d_plot
550551
!*****************************************************************************************
551552

553+
!*****************************************************************************************
554+
!> author: Jacob Williams
555+
!
556+
! Add a sphere to a 3D x,y,z plot.
557+
!
558+
!@note Must initialize the class with `mplot3d=.true.` and `use_numpy=.true.`.
559+
560+
subroutine add_sphere(me, r, xc, yc, zc, n_facets, linewidth, antialiased, color, istat)
561+
562+
implicit none
563+
564+
class(pyplot), intent (inout) :: me !! pyplot handler
565+
real(wp), intent (in) :: r !! radius of the sphere
566+
real(wp), intent (in) :: xc !! x value of sphere center
567+
real(wp), intent (in) :: yc !! y value of sphere center
568+
real(wp), intent (in) :: zc !! z value of sphere center
569+
integer, intent (in), optional :: n_facets !! [default is 100]
570+
integer, intent (in), optional :: linewidth !! line width
571+
logical, intent (in), optional :: antialiased !! enabled anti-aliasing
572+
character(len=*), intent (in), optional :: color !! color of the contour line
573+
integer, intent (out) :: istat !! status output (0 means no problems)
574+
575+
character(len=:), allocatable :: rstr !! `r` value stringified
576+
character(len=:), allocatable :: xcstr !! `xc` value stringified
577+
character(len=:), allocatable :: ycstr !! `yc` value stringified
578+
character(len=:), allocatable :: zcstr !! `zc` value stringified
579+
character(len=*), parameter :: xname = 'x' !! `x` variable name for script
580+
character(len=*), parameter :: yname = 'y' !! `y` variable name for script
581+
character(len=*), parameter :: zname = 'z' !! `z` variable name for script
582+
583+
character(len=max_int_len) :: linewidth_str !! `linewidth` input stringified
584+
character(len=:), allocatable :: antialiased_str !! `antialised` input stringified
585+
character(len=max_int_len) :: n_facets_str !! `n_facets` input stringified
586+
character(len=:), allocatable :: extras !! optional stuff string
587+
588+
if (allocated(me%str)) then
589+
590+
!get optional inputs (if not present, set default value):
591+
call optional_int_to_string(n_facets, n_facets_str, '100')
592+
extras = ''
593+
if (present(linewidth)) then
594+
call optional_int_to_string(linewidth, linewidth_str, '1')
595+
extras = extras//','//'linewidth='//linewidth_str
596+
end if
597+
if (present(antialiased)) then
598+
call optional_logical_to_string(antialiased, antialiased_str, 'False')
599+
extras = extras//','//'antialiased='//antialiased_str
600+
end if
601+
if (present(color)) then
602+
extras = extras//','//'color="'//trim(color)//'"'
603+
end if
604+
605+
istat = 0
606+
607+
!convert the arrays to strings:
608+
call real_to_string(r , me%real_fmt, rstr)
609+
call real_to_string(xc, me%real_fmt, xcstr)
610+
call real_to_string(yc, me%real_fmt, ycstr)
611+
call real_to_string(zc, me%real_fmt, zcstr)
612+
613+
! sphere code:
614+
call me%add_str('u = np.linspace(0, 2 * np.pi, '//n_facets_str//')')
615+
call me%add_str('v = np.linspace(0, np.pi, '//n_facets_str//')')
616+
call me%add_str(xname//' = '//xcstr//' + '//rstr//' * np.outer(np.cos(u), np.sin(v))')
617+
call me%add_str(yname//' = '//ycstr//' + '//rstr//' * np.outer(np.sin(u), np.sin(v))')
618+
call me%add_str(zname//' = '//zcstr//' + '//rstr//' * np.outer(np.ones(np.size(u)), np.cos(v))')
619+
call me%add_str('ax.plot_surface('//xname//', '//yname//', '//zname//extras//')')
620+
call me%add_str('')
621+
622+
else
623+
istat = -1
624+
write(error_unit,'(A)') 'Error in add_sphere: pyplot class not properly initialized.'
625+
end if
626+
627+
end subroutine add_sphere
628+
!*****************************************************************************************
629+
552630
!*****************************************************************************************
553631
!> author: Jacob Williams
554632
!
@@ -762,6 +840,35 @@ subroutine integer_to_string(i, s)
762840
end subroutine integer_to_string
763841
!*****************************************************************************************
764842

843+
!*****************************************************************************************
844+
!> author: Jacob Williams
845+
!
846+
! Real scalar to string.
847+
848+
subroutine real_to_string(v, fmt, str)
849+
850+
real(wp), intent(in) :: v !! real values
851+
character(len=*), intent(in) :: fmt !! real format string
852+
character(len=:), allocatable, intent(out) :: str !! real values stringified
853+
854+
integer :: istat !! IO status
855+
character(len=max_real_len) :: tmp !! dummy string
856+
857+
if (fmt=='*') then
858+
write(tmp, *, iostat=istat) v
859+
else
860+
write(tmp, fmt, iostat=istat) v
861+
end if
862+
if (istat/=0) then
863+
write(error_unit,'(A)') 'Error in real_to_string'
864+
str = '****'
865+
else
866+
str = trim(adjustl(tmp))
867+
end if
868+
869+
end subroutine real_to_string
870+
!*****************************************************************************************
871+
765872
!*****************************************************************************************
766873
!> author: Jacob Williams
767874
!
@@ -937,7 +1044,29 @@ subroutine finish_ops(me)
9371044
call me%add_str('')
9381045
end if
9391046
if (me%axis_equal) then
940-
call me%add_str('ax.axis("equal")')
1047+
if (me%mplot3d) then
1048+
call me%add_str('ax.set_aspect("equal")')
1049+
call me%add_str('')
1050+
1051+
call me%add_str('def set_axes_equal(ax):')
1052+
call me%add_str(' x_limits = ax.get_xlim3d()')
1053+
call me%add_str(' y_limits = ax.get_ylim3d()')
1054+
call me%add_str(' z_limits = ax.get_zlim3d()')
1055+
call me%add_str(' x_range = abs(x_limits[1] - x_limits[0])')
1056+
call me%add_str(' x_middle = np.mean(x_limits)')
1057+
call me%add_str(' y_range = abs(y_limits[1] - y_limits[0])')
1058+
call me%add_str(' y_middle = np.mean(y_limits)')
1059+
call me%add_str(' z_range = abs(z_limits[1] - z_limits[0])')
1060+
call me%add_str(' z_middle = np.mean(z_limits)')
1061+
call me%add_str(' plot_radius = 0.5*max([x_range, y_range, z_range])')
1062+
call me%add_str(' ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])')
1063+
call me%add_str(' ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])')
1064+
call me%add_str(' ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])')
1065+
call me%add_str('set_axes_equal(ax)')
1066+
1067+
else
1068+
call me%add_str('ax.axis("equal")')
1069+
end if
9411070
call me%add_str('')
9421071
end if
9431072

src/tests/test.f90

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ program test
1919
real(wp), dimension(n) :: yerr !! error values for bar chart
2020
real(wp), dimension(n) :: sx !! sin(x) values
2121
real(wp), dimension(n) :: cx !! cos(x) values
22+
real(wp), dimension(n) :: zx !!
2223
real(wp), dimension(n) :: tx !! sin(x)*cos(x) values
2324
real(wp), dimension(n,n) :: z !! z matrix for contour plot
2425
type(pyplot) :: plt !! pytplot handler
@@ -28,11 +29,15 @@ program test
2829
real(wp), dimension(n,n) :: mat !! image values
2930
integer :: istat !! status code
3031

32+
real(wp),parameter :: pi = acos(-1.0_wp)
33+
real(wp),parameter :: deg2rad = pi/180.0_wp
34+
3135
!generate some data:
3236
x = [(real(i,wp), i=0,size(x)-1)]/5.0_wp
3337
sx = sin(x)
3438
cx = cos(x)
3539
tx = sx * cx
40+
zx = 0.0_wp
3641
yerr = abs(sx*.25_wp)
3742

3843
do i=1,n
@@ -124,5 +129,28 @@ program test
124129
dpi='200', &
125130
transparent=.true.,istat=istat)
126131

132+
!3D plot:
133+
call plt%initialize(grid=.true.,&
134+
xlabel='x (km)',ylabel='y (km)',zlabel='z (km)',&
135+
title='Orbit',&
136+
axis_equal=.true.,&
137+
mplot3d=.true.,&
138+
figsize=[20,10])
139+
140+
x = [(real(i,wp), i=0,size(x)-1)]/5.0_wp
141+
sx = 7000.0_wp * cos(x * deg2rad)
142+
cx = 7000.0_wp * sin(x * deg2rad)
143+
zx = 0.0_wp
144+
call plt%add_3d_plot(sx,cx,zx,&
145+
label='orbit',linestyle='r-',&
146+
linewidth=2,istat=istat)
147+
call plt%add_sphere(6378.0_wp,0.0_wp,0.0_wp,0.0_wp,&
148+
color='Blue',n_facets=500,&
149+
antialiased=.true.,istat=istat)
150+
call plt%savefig('orbit.png', &
151+
pyfile='orbit.py', &
152+
dpi='200', &
153+
transparent=.true.,istat=istat)
154+
127155
end program test
128156
!*****************************************************************************************

0 commit comments

Comments
 (0)