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
Binary file added ._.DS_Store
Binary file not shown.
63 changes: 63 additions & 0 deletions example/disconnected_lines.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
program disconnected_lines
use fortplot
use fortplot_figure, only: figure_t
use, intrinsic :: ieee_arithmetic, only: ieee_value, ieee_quiet_nan
implicit none

type(figure_t) :: fig
real(8) :: x(11), y(11), nan
integer :: i

! Get NaN value
nan = ieee_value(nan, ieee_quiet_nan)

call fig%initialize(800, 600)

! Create data with three disconnected segments using NaN as separator
! First segment: sine wave from 0 to pi
x(1:4) = [0.0_8, 1.047_8, 2.094_8, 3.142_8] ! 0, pi/3, 2pi/3, pi
do i = 1, 4
y(i) = sin(x(i))
end do

! NaN separator
x(5) = nan
y(5) = nan

! Second segment: cosine wave from pi to 2pi
x(6:9) = [3.142_8, 4.189_8, 5.236_8, 6.283_8] ! pi, 4pi/3, 5pi/3, 2pi
do i = 6, 9
y(i) = cos(x(i))
end do

! NaN separator
x(10) = nan
y(10) = nan

! Third segment: horizontal line at y=0.5
x(11) = 7.0_8
y(11) = 0.5_8

! Plot disconnected segments with markers and lines
call fig%add_plot(x, y, label='Disconnected segments', linestyle='o-')

! Add single point (will be disconnected from the line)
call fig%add_plot([8.0_8], [-0.5_8], linestyle='rs', label='Single point')

! Configure plot
call fig%set_title('Disconnected Line Segments Example')
call fig%set_xlabel('x')
call fig%set_ylabel('y')
call fig%legend()

! Save in multiple formats
call fig%savefig('build/example/disconnected_lines.png')
call fig%savefig('build/example/disconnected_lines.pdf')
call fig%savefig('build/example/disconnected_lines.txt')

print *, "Disconnected lines example saved to:"
print *, " - build/example/disconnected_lines.png"
print *, " - build/example/disconnected_lines.pdf"
print *, " - build/example/disconnected_lines.txt"

end program disconnected_lines
55 changes: 46 additions & 9 deletions src/fortplot_figure_core.f90
Original file line number Diff line number Diff line change
Expand Up @@ -1028,7 +1028,8 @@ subroutine render_line_plot(self, plot_idx)
end subroutine render_line_plot

subroutine render_markers(self, plot_idx)
!! Render markers at each data point
!! Render markers at each data point, skipping NaN values
use, intrinsic :: ieee_arithmetic, only: ieee_is_nan
class(figure_t), intent(inout) :: self
integer, intent(in) :: plot_idx
character(len=:), allocatable :: marker
Expand All @@ -1042,6 +1043,9 @@ subroutine render_markers(self, plot_idx)
if (marker == 'None') return

do i = 1, size(self%plots(plot_idx)%x)
! Skip points with NaN values
if (ieee_is_nan(self%plots(plot_idx)%x(i)) .or. ieee_is_nan(self%plots(plot_idx)%y(i))) cycle

x_trans = apply_scale_transform(self%plots(plot_idx)%x(i), self%xscale, self%symlog_threshold)
y_trans = apply_scale_transform(self%plots(plot_idx)%y(i), self%yscale, self%symlog_threshold)
call self%backend%draw_marker(x_trans, y_trans, marker)
Expand Down Expand Up @@ -1411,13 +1415,20 @@ subroutine draw_line_with_style(self, plot_idx, linestyle)
end subroutine draw_line_with_style

subroutine render_solid_line(self, plot_idx)
!! Render solid line by drawing all segments
!! Render solid line by drawing all segments, breaking on NaN values
use, intrinsic :: ieee_arithmetic, only: ieee_is_nan
class(figure_t), intent(inout) :: self
integer, intent(in) :: plot_idx
integer :: i
real(wp) :: x1_screen, y1_screen, x2_screen, y2_screen

do i = 1, size(self%plots(plot_idx)%x) - 1
! Skip segment if either point contains NaN
if (ieee_is_nan(self%plots(plot_idx)%x(i)) .or. ieee_is_nan(self%plots(plot_idx)%y(i)) .or. &
ieee_is_nan(self%plots(plot_idx)%x(i+1)) .or. ieee_is_nan(self%plots(plot_idx)%y(i+1))) then
cycle
end if

! Apply scale transformations
x1_screen = apply_scale_transform(self%plots(plot_idx)%x(i), self%xscale, self%symlog_threshold)
y1_screen = apply_scale_transform(self%plots(plot_idx)%y(i), self%yscale, self%symlog_threshold)
Expand All @@ -1430,6 +1441,7 @@ end subroutine render_solid_line

subroutine render_patterned_line(self, plot_idx, linestyle)
!! Render line with continuous pattern across segments (matplotlib-style)
use, intrinsic :: ieee_arithmetic, only: ieee_is_nan
class(figure_t), intent(inout) :: self
integer, intent(in) :: plot_idx
character(len=*), intent(in) :: linestyle
Expand All @@ -1439,25 +1451,41 @@ subroutine render_patterned_line(self, plot_idx, linestyle)
real(wp) :: pattern(20), pattern_length
integer :: pattern_size, pattern_index
logical :: drawing
integer :: i
integer :: i, valid_count
real(wp) :: x1_screen, y1_screen, x2_screen, y2_screen, dx, dy

! Get transformed data range for proper pattern scaling
real(wp) :: x_range, y_range, plot_scale
real(wp), allocatable :: x_trans(:), y_trans(:)
logical, allocatable :: valid_points(:)

! Transform all data points to get proper scaling
allocate(x_trans(size(self%plots(plot_idx)%x)))
allocate(y_trans(size(self%plots(plot_idx)%y)))
allocate(valid_points(size(self%plots(plot_idx)%x)))

valid_count = 0
do i = 1, size(self%plots(plot_idx)%x)
x_trans(i) = apply_scale_transform(self%plots(plot_idx)%x(i), self%xscale, self%symlog_threshold)
y_trans(i) = apply_scale_transform(self%plots(plot_idx)%y(i), self%yscale, self%symlog_threshold)
valid_points(i) = .not. (ieee_is_nan(self%plots(plot_idx)%x(i)) .or. ieee_is_nan(self%plots(plot_idx)%y(i)))
if (valid_points(i)) then
x_trans(i) = apply_scale_transform(self%plots(plot_idx)%x(i), self%xscale, self%symlog_threshold)
y_trans(i) = apply_scale_transform(self%plots(plot_idx)%y(i), self%yscale, self%symlog_threshold)
valid_count = valid_count + 1
else
x_trans(i) = 0.0_wp
y_trans(i) = 0.0_wp
end if
end do

x_range = maxval(x_trans) - minval(x_trans)
y_range = maxval(y_trans) - minval(y_trans)
plot_scale = max(x_range, y_range)
! Handle case where all points are NaN
if (valid_count > 0) then
x_range = maxval(x_trans, mask=valid_points) - minval(x_trans, mask=valid_points)
y_range = maxval(y_trans, mask=valid_points) - minval(y_trans, mask=valid_points)
plot_scale = max(x_range, y_range)
else
! All points are NaN, use default scale
plot_scale = 1.0_wp
end if

! Define pattern lengths (matplotlib-like)
dash_len = plot_scale * 0.03_wp ! 3% of range
Expand Down Expand Up @@ -1502,6 +1530,15 @@ subroutine render_patterned_line(self, plot_idx, linestyle)
drawing = .true. ! Start drawing

do i = 1, size(self%plots(plot_idx)%x) - 1
! Skip segment if either point is invalid (NaN)
if (.not. valid_points(i) .or. .not. valid_points(i+1)) then
! Reset pattern state when encountering NaN
current_distance = 0.0_wp
pattern_index = 1
drawing = .true.
cycle
end if

x1_screen = x_trans(i)
y1_screen = y_trans(i)
x2_screen = x_trans(i+1)
Expand All @@ -1519,7 +1556,7 @@ subroutine render_patterned_line(self, plot_idx, linestyle)
end do

! Clean up
deallocate(x_trans, y_trans)
deallocate(x_trans, y_trans, valid_points)
end subroutine render_patterned_line

subroutine render_segment_with_pattern(self, x1, y1, x2, y2, segment_length, &
Expand Down
Loading