Skip to content

Commit ab9766c

Browse files
authored
fix: restore PDF axes visibility and prevent plot stretching/shifting #338 (#339)
Squash merge PDF axes rendering fix
1 parent fe67b40 commit ab9766c

File tree

8 files changed

+236
-1
lines changed

8 files changed

+236
-1
lines changed

BACKLOG.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,25 @@
33
## CURRENT SPRINT (Critical PNG/PDF Rendering Issues)
44

55
**🚨 CRITICAL: User-visible PNG/PDF rendering completely broken**
6+
- [x] #338: Fix - No axes visible and plots strangely stretched and shifted in PDF (moved to DOING)
7+
- [ ] #337: Fix - Title too far right in PNGs - check matplotlib placement
8+
- [ ] #335: Fix - Axes wrong and no labels visible on scale_examples.html
9+
- [ ] #334: Fix - No output visible on pcolormesh_demo.html
10+
- [ ] #333: Fix - Circles seem not centered with line plot in marker_demo.html
11+
- [ ] #332: Fix - Dashed and dash-dotted look funny on line_styles.html
12+
- [ ] #331: Fix - No legend visible in format_string_demo.html
13+
- [ ] #330: Fix - Old plot not cleared in second figure (figure() call) in contour_demo.html
14+
- [ ] #329: Fix - No output visible on colored_contours.html
15+
- [ ] #328: Fix - One legend entry too much in basic_plots.html second plot
16+
- [ ] #327: Fix - MP4 link not showing on animation.html
17+
- [ ] #336: Fix - Streamplot and stateful streamplot example redundant
618

719
**Infrastructure & Documentation Issues (Lower Priority)**
820
- [ ] #323: Test - add edge case tests for PDF heatmap color validation
921
- [ ] #324: Refactor - define epsilon constant for numerical comparisons
1022

1123
## DOING (Current Work)
12-
- [x] #321: Refactor - apply consistent validation pattern to other PDF write functions (branch: refactor-consistent-validation-321)
24+
- [ ] #338: Fix - No axes visible and plots strangely stretched and shifted in PDF
1325

1426
## BLOCKED (Infrastructure Issues)
1527

@@ -36,6 +48,7 @@
3648
- Performance impact validation
3749

3850
## DONE (Completed)
51+
- [x] #321: Refactor - apply consistent validation pattern to other PDF write functions - FIXED: Extended robust validation pattern to pdf_write_move, pdf_write_line, and pdf_write_line_width with comprehensive test coverage and zero performance impact (PR #326 merged)
3952
- [x] #320: Feature - add debug logging for invalid color value corrections - FIXED: Implemented debug logging for RGB color corrections in PDF output with zero performance impact and comprehensive test coverage (PR #325 merged)
4053
- [x] #319: Refactor - investigate source of invalid RGB values in contour color mapping - FIXED: Replaced direct color writing with validated pdf_write_color method, added proper edge case handling for division by zero scenarios (PR #322 merged)
4154
- [x] #317: Fix GitHub Pages deployment failure - colored_contours PDF runtime crash in pdf_write_color - FIXED: Added robust RGB validation to prevent PDF color crashes, restored visual showcase (PR #318 merged)

src/fortplot_ascii.f90

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ module fortplot_ascii
6262
procedure :: draw_axes_and_labels_backend => ascii_draw_axes_and_labels
6363
procedure :: save_coordinates => ascii_save_coordinates
6464
procedure :: set_coordinates => ascii_set_coordinates
65+
procedure :: render_axes => ascii_render_axes
6566
end type ascii_context
6667

6768
! ASCII plotting constants
@@ -927,4 +928,13 @@ subroutine ascii_set_coordinates(this, x_min, x_max, y_min, y_max)
927928
this%y_max = y_max
928929
end subroutine ascii_set_coordinates
929930

931+
subroutine ascii_render_axes(this, title_text, xlabel_text, ylabel_text)
932+
!! Render axes for ASCII context (stub implementation)
933+
class(ascii_context), intent(inout) :: this
934+
character(len=*), intent(in), optional :: title_text, xlabel_text, ylabel_text
935+
936+
! ASCII axes are rendered as part of draw_axes_and_labels_backend
937+
! This is a stub to satisfy the interface
938+
end subroutine ascii_render_axes
939+
930940
end module fortplot_ascii

src/fortplot_context.f90

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ module fortplot_context
4444
procedure(draw_axes_and_labels_interface), deferred :: draw_axes_and_labels_backend
4545
procedure(save_coordinates_interface), deferred :: save_coordinates
4646
procedure(set_coordinates_interface), deferred :: set_coordinates
47+
procedure(render_axes_interface), deferred :: render_axes
4748
end type plot_context
4849

4950
abstract interface
@@ -190,6 +191,12 @@ subroutine set_coordinates_interface(this, x_min, x_max, y_min, y_max)
190191
class(plot_context), intent(inout) :: this
191192
real(wp), intent(in) :: x_min, x_max, y_min, y_max
192193
end subroutine set_coordinates_interface
194+
195+
subroutine render_axes_interface(this, title_text, xlabel_text, ylabel_text)
196+
import :: plot_context
197+
class(plot_context), intent(inout) :: this
198+
character(len=*), intent(in), optional :: title_text, xlabel_text, ylabel_text
199+
end subroutine render_axes_interface
193200
end interface
194201

195202
contains

src/fortplot_pdf.f90

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ module fortplot_pdf
3333
type(pdf_context_handle), private :: coord_ctx
3434
integer :: x_tick_count = 0
3535
integer :: y_tick_count = 0
36+
logical, private :: axes_rendered = .false.
3637
contains
3738
procedure :: line => draw_pdf_line
3839
procedure :: color => set_pdf_color
@@ -62,6 +63,7 @@ module fortplot_pdf
6263
procedure :: draw_axes_and_labels_backend => draw_axes_and_labels_backend_wrapper
6364
procedure :: save_coordinates => pdf_save_coordinates
6465
procedure :: set_coordinates => pdf_set_coordinates
66+
procedure :: render_axes => render_pdf_axes_wrapper
6567

6668
procedure, private :: update_coord_context
6769
procedure, private :: make_coord_context
@@ -133,6 +135,10 @@ subroutine write_pdf_file_facade(this, filename)
133135
class(pdf_context), intent(inout) :: this
134136
character(len=*), intent(in) :: filename
135137

138+
! Automatically render axes if they haven't been rendered yet
139+
! This provides better UX for low-level PDF API users
140+
call this%render_axes()
141+
136142
this%core_ctx%stream_data = this%stream_writer%content_stream
137143
call write_pdf_file(this%core_ctx, filename)
138144
end subroutine write_pdf_file_facade
@@ -413,6 +419,56 @@ subroutine pdf_set_coordinates(this, x_min, x_max, y_min, y_max)
413419
this%x_max = x_max
414420
this%y_min = y_min
415421
this%y_max = y_max
422+
423+
! Reset axes flag when coordinates change
424+
this%axes_rendered = .false.
416425
end subroutine pdf_set_coordinates
417426

427+
subroutine render_pdf_axes_wrapper(this, title_text, xlabel_text, ylabel_text)
428+
!! Explicitly render axes with optional labels
429+
!! This allows low-level PDF users to add proper axes to their plots
430+
class(pdf_context), intent(inout) :: this
431+
character(len=*), intent(in), optional :: title_text, xlabel_text, ylabel_text
432+
433+
character(len=256) :: title_str, xlabel_str, ylabel_str
434+
435+
! Only render axes once unless coordinates change
436+
if (this%axes_rendered) return
437+
438+
! Ensure coordinate system is set
439+
if (this%x_min == this%x_max .or. this%y_min == this%y_max) then
440+
! No valid coordinate system - skip axes
441+
return
442+
end if
443+
444+
! Set default empty strings for labels
445+
title_str = ""
446+
xlabel_str = ""
447+
ylabel_str = ""
448+
449+
! Use provided labels if present
450+
if (present(title_text)) title_str = title_text
451+
if (present(xlabel_text)) xlabel_str = xlabel_text
452+
if (present(ylabel_text)) ylabel_str = ylabel_text
453+
454+
! Clear any previous axes data in core context
455+
this%core_ctx%stream_data = ""
456+
457+
! Draw axes and labels with current coordinate system
458+
call draw_pdf_axes_and_labels(this%core_ctx, "linear", "linear", 1.0_wp, &
459+
this%x_min, this%x_max, this%y_min, this%y_max, &
460+
title_str, xlabel_str, ylabel_str, &
461+
real(this%plot_area%left, wp), &
462+
real(this%plot_area%bottom, wp), &
463+
real(this%plot_area%width, wp), &
464+
real(this%plot_area%height, wp), &
465+
real(this%height, wp))
466+
467+
! Add axes content to the stream
468+
call this%stream_writer%add_to_stream(this%core_ctx%stream_data)
469+
470+
! Mark axes as rendered
471+
this%axes_rendered = .true.
472+
end subroutine render_pdf_axes_wrapper
473+
418474
end module fortplot_pdf

src/fortplot_raster.f90

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ module fortplot_raster
8787
procedure :: draw_axes_and_labels_backend => raster_draw_axes_and_labels
8888
procedure :: save_coordinates => raster_save_coordinates
8989
procedure :: set_coordinates => raster_set_coordinates
90+
procedure :: render_axes => raster_render_axes
9091
end type raster_context
9192

9293
contains
@@ -1014,5 +1015,13 @@ subroutine raster_set_coordinates(this, x_min, x_max, y_min, y_max)
10141015
this%y_max = y_max
10151016
end subroutine raster_set_coordinates
10161017

1018+
subroutine raster_render_axes(this, title_text, xlabel_text, ylabel_text)
1019+
!! Render axes for raster context (stub implementation)
1020+
class(raster_context), intent(inout) :: this
1021+
character(len=*), intent(in), optional :: title_text, xlabel_text, ylabel_text
1022+
1023+
! Raster axes are rendered as part of draw_axes_and_labels_backend
1024+
! This is a stub to satisfy the interface
1025+
end subroutine raster_render_axes
10171026

10181027
end module fortplot_raster

test/test_axes_edge_cases_338.f90

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
program test_axes_edge_cases_338
2+
!! Test edge cases for PDF axes rendering fix (Issue #338)
3+
use iso_fortran_env, only: wp => real64
4+
use fortplot_pdf, only: pdf_context, create_pdf_canvas
5+
implicit none
6+
7+
type(pdf_context) :: ctx
8+
9+
write(*, '(A)') "Testing PDF axes edge cases for Issue #338"
10+
11+
! Test 1: Multiple coordinate changes
12+
ctx = create_pdf_canvas(600, 400)
13+
ctx%x_min = 0.0_wp
14+
ctx%x_max = 1.0_wp
15+
ctx%y_min = 0.0_wp
16+
ctx%y_max = 1.0_wp
17+
18+
call ctx%render_axes("Test 1", "X1", "Y1")
19+
20+
! Change coordinates - should reset axes_rendered
21+
ctx%x_min = -1.0_wp
22+
ctx%x_max = 2.0_wp
23+
ctx%y_min = -2.0_wp
24+
ctx%y_max = 3.0_wp
25+
26+
call ctx%render_axes("Test 1b", "X2", "Y2")
27+
call ctx%save("test_edge_case_1.pdf")
28+
29+
! Test 2: Degenerate coordinate system
30+
ctx = create_pdf_canvas(600, 400)
31+
ctx%x_min = 1.0_wp
32+
ctx%x_max = 1.0_wp ! Same values - degenerate
33+
ctx%y_min = -1.0_wp
34+
ctx%y_max = 2.0_wp
35+
36+
call ctx%render_axes("Degenerate X", "X", "Y")
37+
call ctx%save("test_edge_case_2.pdf")
38+
39+
! Test 3: Multiple explicit axes calls
40+
ctx = create_pdf_canvas(600, 400)
41+
ctx%x_min = 0.0_wp
42+
ctx%x_max = 10.0_wp
43+
ctx%y_min = -5.0_wp
44+
ctx%y_max = 5.0_wp
45+
46+
call ctx%render_axes("First", "X1", "Y1")
47+
call ctx%render_axes("Second", "X2", "Y2") ! Should be ignored
48+
call ctx%save("test_edge_case_3.pdf")
49+
50+
write(*, '(A)') "Edge case tests completed:"
51+
write(*, '(A)') "- test_edge_case_1.pdf: Multiple coordinate changes"
52+
write(*, '(A)') "- test_edge_case_2.pdf: Degenerate coordinates (should have no axes)"
53+
write(*, '(A)') "- test_edge_case_3.pdf: Multiple axes calls (should use first)"
54+
55+
end program test_axes_edge_cases_338

test/test_axes_order_338.f90

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
program test_axes_order_338
2+
use iso_fortran_env, only: wp => real64
3+
use fortplot_pdf, only: pdf_context, create_pdf_canvas
4+
implicit none
5+
6+
type(pdf_context) :: ctx
7+
integer :: i
8+
real(wp) :: x, y, prev_x, prev_y
9+
10+
write(*, '(A)') "Testing axes/content order in PDF"
11+
12+
ctx = create_pdf_canvas(600, 400)
13+
ctx%x_min = 0.0_wp
14+
ctx%x_max = 10.0_wp
15+
ctx%y_min = -2.0_wp
16+
ctx%y_max = 2.0_wp
17+
18+
! Draw content BEFORE explicit axes call
19+
call ctx%color(0.0_wp, 0.0_wp, 1.0_wp)
20+
do i = 1, 50
21+
x = real(i-1, wp) * 10.0_wp / 49.0_wp
22+
y = sin(x)
23+
if (i > 1) then
24+
call ctx%line(prev_x, prev_y, x, y)
25+
end if
26+
prev_x = x
27+
prev_y = y
28+
end do
29+
30+
! Explicitly render axes
31+
call ctx%render_axes("Test Plot", "X Axis", "Y Axis")
32+
33+
! Draw MORE content AFTER axes call
34+
call ctx%color(1.0_wp, 0.0_wp, 0.0_wp)
35+
do i = 1, 50
36+
x = real(i-1, wp) * 10.0_wp / 49.0_wp
37+
y = cos(x)
38+
if (i > 1) then
39+
call ctx%line(prev_x, prev_y, x, y)
40+
end if
41+
prev_x = x
42+
prev_y = y
43+
end do
44+
45+
call ctx%save("test_axes_order.pdf")
46+
write(*, '(A)') "Generated test_axes_order.pdf"
47+
write(*, '(A)') "Check: Should see both blue sine and red cosine curves with axes"
48+
49+
end program test_axes_order_338

test/test_pdf_axes_simple_338.f90

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
program test_pdf_axes_simple_338
2+
use iso_fortran_env, only: wp => real64
3+
use fortplot_pdf, only: pdf_context, create_pdf_canvas
4+
implicit none
5+
6+
type(pdf_context) :: ctx
7+
integer :: i
8+
real(wp) :: x, y, prev_x, prev_y
9+
10+
write(*, '(A)') "Testing PDF axes fix for Issue #338"
11+
12+
ctx = create_pdf_canvas(600, 400)
13+
ctx%x_min = 0.0_wp
14+
ctx%x_max = 10.0_wp
15+
ctx%y_min = -2.0_wp
16+
ctx%y_max = 2.0_wp
17+
18+
! Draw a sine wave
19+
call ctx%color(0.0_wp, 0.0_wp, 1.0_wp)
20+
do i = 1, 100
21+
x = real(i-1, wp) * 10.0_wp / 99.0_wp
22+
y = sin(x)
23+
24+
if (i > 1) then
25+
call ctx%line(prev_x, prev_y, x, y)
26+
end if
27+
prev_x = x
28+
prev_y = y
29+
end do
30+
31+
call ctx%save("test_axes_simple_338.pdf")
32+
write(*, '(A)') "Generated: test_axes_simple_338.pdf"
33+
write(*, '(A)') "Expected: PDF with visible axes, tick marks, and labels"
34+
write(*, '(A)') "If axes are visible, Issue #338 is fixed!"
35+
36+
end program test_pdf_axes_simple_338

0 commit comments

Comments
 (0)