Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 128 additions & 10 deletions src/pyplot_module.F90
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ module pyplot_module
private

character(len=:), allocatable :: str !! string buffer
integer :: str_len = 0 !! current length of `str`

character(len=1) :: raw_str_token = ' ' !! will be 'r' if using raw strings

Expand Down Expand Up @@ -91,6 +92,7 @@ module pyplot_module
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 :: add_text !! add text annotation 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
Expand All @@ -114,6 +116,7 @@ subroutine destroy(me)

class(pyplot),intent(inout) :: me !! pyplot handler

me%str_len = 0
if (allocated(me%str)) deallocate(me%str)
if (allocated(me%real_fmt)) deallocate(me%real_fmt)

Expand All @@ -136,22 +139,36 @@ subroutine add_str(me,str)
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

! original
!me%str = me%str//str//new_line(' ')
! 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)

! if there is room in the current string, then
! insert str into it. otherwise, allocate a new string
! with enough room and move the old string into it before adding str.

if (me%str_len + len(str) + 1 <= len(me%str)) then
me%str(me%str_len+1:me%str_len+len(str)) = str
me%str_len = me%str_len + len(str)
me%str(me%str_len+1:me%str_len+1) = new_line(' ')
me%str_len = me%str_len + 1
else
n_old = me%str_len
n_str = len(str)
! double size to avoid too many allocations
allocate(character(len=2*(n_old+n_str)) :: tmp)
tmp(1:n_old) = me%str
tmp(n_old+1:) = str//new_line(' ')
call move_alloc(tmp, me%str)
me%str_len = n_old + n_str + 1
end if
else
allocate(me%str, source = str//new_line(' '))
me%str_len = len(me%str)
end if

end subroutine add_str
Expand Down Expand Up @@ -1099,6 +1116,107 @@ subroutine add_imshow(me, x, xlim, ylim, istat)
end subroutine add_imshow
!*****************************************************************************************

!*****************************************************************************************
!> author: Jacob Williams
!
! Add a text annotation to the plot.
!
!### Example
!```fortran
! call plt%add_text(5.0_wp, 10.0_wp, 'Peak', fontsize=12, color='red', &
! horizontalalignment='center', verticalalignment='bottom')
!```

subroutine add_text(me, x, y, txt, fontsize, color, horizontalalignment, &
verticalalignment, rotation, alpha, fontweight, &
fontstyle, istat)

class(pyplot), intent(inout) :: me !! pyplot handler
real(wp), intent(in) :: x !! x position in data coordinates
real(wp), intent(in) :: y !! y position in data coordinates
character(len=*), intent(in) :: txt !! text string to display
integer, intent(in), optional :: fontsize !! font size
character(len=*), intent(in), optional :: color !! text color
character(len=*), intent(in), optional :: horizontalalignment !! horizontal alignment: 'left', 'center', 'right'
character(len=*), intent(in), optional :: verticalalignment !! vertical alignment: 'top', 'center', 'bottom', 'baseline'
real(wp), intent(in), optional :: rotation !! rotation angle in degrees
real(wp), intent(in), optional :: alpha !! transparency (0.0 to 1.0)
character(len=*), intent(in), optional :: fontweight !! 'normal', 'bold', 'heavy', 'light', 'ultrabold', 'ultralight'
character(len=*), intent(in), optional :: fontstyle !! 'normal', 'italic', 'oblique'
integer, intent(out),optional :: istat !! status output (0 means no problems)

character(len=:), allocatable :: xstr !! x value stringified
character(len=:), allocatable :: ystr !! y value stringified
character(len=:), allocatable :: txt_str !! plot command string
character(len=:), allocatable :: rotation_str !! rotation value stringified
character(len=:), allocatable :: alpha_str !! alpha value stringified
character(len=max_int_len) :: fontsize_str !! fontsize value stringified

if (allocated(me%str)) then

if (present(istat)) istat = 0

! convert coordinates to strings
call real_to_string(x, me%real_fmt, xstr)
call real_to_string(y, me%real_fmt, ystr)

! build the text command
txt_str = 'ax.text('//xstr//', '//ystr//', '//&
trim(me%raw_str_token)//'"'//trim(txt)//'"'

! add optional parameters
if (present(fontsize)) then
call optional_int_to_string(fontsize, fontsize_str, '10')
txt_str = txt_str//', fontsize='//trim(fontsize_str)
end if

if (present(color)) then
txt_str = txt_str//', color='//trim(me%raw_str_token)//'"'//trim(color)//'"'
end if

if (present(horizontalalignment)) then
txt_str = txt_str//', horizontalalignment='//trim(me%raw_str_token)//'"'//&
trim(horizontalalignment)//'"'
end if

if (present(verticalalignment)) then
txt_str = txt_str//', verticalalignment='//trim(me%raw_str_token)//'"'//&
trim(verticalalignment)//'"'
end if

if (present(rotation)) then
call real_to_string(rotation, me%real_fmt, rotation_str)
txt_str = txt_str//', rotation='//rotation_str
end if

if (present(alpha)) then
call real_to_string(alpha, me%real_fmt, alpha_str)
txt_str = txt_str//', alpha='//alpha_str
end if

if (present(fontweight)) then
txt_str = txt_str//', fontweight='//trim(me%raw_str_token)//'"'//&
trim(fontweight)//'"'
end if

if (present(fontstyle)) then
txt_str = txt_str//', style='//trim(me%raw_str_token)//'"'//&
trim(fontstyle)//'"'
end if

txt_str = txt_str//')'

! add the text command
call me%add_str(txt_str)

else
if (present(istat)) istat = -1
write(error_unit,'(A)') 'Error in add_text: pyplot class not properly initialized.'
end if

end subroutine add_text
!*****************************************************************************************

!*****************************************************************************************
!> author: Alexander Sandrock
!
Expand Down Expand Up @@ -1451,7 +1569,7 @@ subroutine execute(me, pyfile, istat, python, wait)
end if

!write to the file:
write(iunit, '(A)') me%str
write(iunit, '(A)') me%str(1:me%str_len)

!to ensure that the file is there for the next
!command line call, we have to close it here.
Expand Down
12 changes: 9 additions & 3 deletions test/color_test.f90
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ program color_test
implicit none

type(pyplot) :: plt !! pytplot handler
integer :: istat
integer :: istat, i
real(wp), parameter :: F(3) = [0.4510d0, 0.3098d0, 0.5882d0] ! Fortran-lang color
real(wp), parameter :: Y(3) = [0.9608d0, 0.8157d0, 0.0118d0] ! Yellow

Expand All @@ -22,8 +22,14 @@ program color_test

call plt%initialize(figsize=[20,10],title='color test')

call plt%add_plot(Ax,Ay,label='',linestyle='o',markersize=5,color=F)
call plt%add_plot(Bx,By,label='',linestyle='o',markersize=5,color=Y)
call plt%add_plot(Ax,Ay,label='',linestyle='o',markersize=50,color=F)
call plt%add_plot(Bx,By,label='',linestyle='o',markersize=50,color=Y)

do i = 1, 3
call plt%add_text(Ax(i), Ay(i), "F", fontsize=40, color="white", &
horizontalalignment="center", verticalalignment="center", &
istat=istat)
end do

call plt%savefig(testdir//'color_test.png',&
pyfile=testdir//'color_test.py',istat=istat)
Expand Down
86 changes: 86 additions & 0 deletions test/text_test.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
!*****************************************************************************************
!> author: Jacob Williams
! license: BSD
!
! Test of the add_text routine.

program text_test

use pyplot_module
use iso_fortran_env, only: wp => real64

implicit none

type(pyplot) :: plt
real(wp), dimension(100) :: x, y
real(wp) :: pi
integer :: i
integer :: istat

pi = acos(-1.0_wp)

! Generate some data
do i = 1, 100
x(i) = real(i-1, wp) * 2.0_wp * pi / 99.0_wp
y(i) = sin(x(i))
end do

! Initialize the plot
call plt%initialize(grid=.true., xlabel='x', ylabel='sin(x)', &
title='Text Annotation Demo', &
legend=.false., figsize=[10,6])

! Add the main plot
call plt%add_plot(x, y, label='sin(x)', linestyle='b-', linewidth=2)

! Add various text annotations with different styles
call plt%add_text(pi/2.0_wp, 1.0_wp, 'Peak at π/2', &
fontsize=12, &
color='red', &
horizontalalignment='center', &
verticalalignment='bottom', &
istat=istat)

call plt%add_text(3.0_wp*pi/2.0_wp, -1.0_wp, 'Minimum at 3π/2', &
fontsize=12, &
color='blue', &
horizontalalignment='center', &
verticalalignment='top', &
istat=istat)

call plt%add_text(pi, 0.0_wp, 'Zero crossing', &
fontsize=10, &
color='green', &
rotation=45.0_wp, &
horizontalalignment='left', &
verticalalignment='bottom', &
istat=istat)

call plt%add_text(0.5_wp, -0.8_wp, 'Bold text', &
fontsize=14, &
color='purple', &
fontweight='bold', &
istat=istat)

call plt%add_text(5.0_wp, 0.5_wp, 'Italic text', &
fontsize=11, &
color='orange', &
fontstyle='italic', &
istat=istat)

call plt%add_text(4.0_wp, -0.5_wp, 'Semi-transparent', &
fontsize=10, &
color='black', &
alpha=0.5_wp, &
istat=istat)

! Save the figure
call plt%savefig('test/text_test.png', istat=istat)

if (istat /= 0) then
write(*,*) 'Error saving figure'
else
write(*,*) 'Figure saved successfully: test/text_test.png'
end if

end program text_test