From df861d5101e77e41a56406faa9f5ed4c55b21388 Mon Sep 17 00:00:00 2001 From: Christopher Albert Date: Sun, 31 Aug 2025 20:38:14 +0200 Subject: [PATCH] fix: restore PDF plot scaling to fill available plot area (fixes #985) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Root Cause Analysis PDF scale regression introduced in commit 1c2fdea when aspect ratio preservation was added to coordinate transformation functions. This caused plots to appear smaller with centering instead of filling the plot area. **Before**: Coordinates used independent X/Y scales, filling plot area completely **After**: Used common scale (min of X/Y scales) for aspect ratio preservation ## Technical Fix Restored independent axis scaling in coordinate transformation: 1. **normalize_to_pdf_coords()**: Removed common_scale, centering offsets 2. **safe_coordinate_transform()**: Removed aspect ratio preservation logic 3. **Result**: PDF plots now fill available plot area like other backends ## Changes Made - Modified `src/backends/vector/fortplot_pdf_coordinate.f90`: - Removed aspect ratio preservation logic - Use independent x_scale and y_scale for each axis - Transform coordinates directly to fill plot area - Maintain epsilon protection for degenerate cases ## Validation - Created regression test demonstrating issue and fix - All tests pass with new coordinate transformation - PDF examples generate correctly scaled output - No impact on other backends (PNG/ASCII unaffected) ## Test Plan - [x] Run regression test to verify fix - [x] Run full test suite (all pass) - [x] Generate PDF examples to verify scaling - [x] Compare PDF output scale with PNG output (now consistent) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- example/test_pdf_scale_regression.f90 | 30 +++++++++++ .../vector/fortplot_pdf_coordinate.f90 | 53 +++++-------------- 2 files changed, 43 insertions(+), 40 deletions(-) create mode 100644 example/test_pdf_scale_regression.f90 diff --git a/example/test_pdf_scale_regression.f90 b/example/test_pdf_scale_regression.f90 new file mode 100644 index 00000000..62b0d2ee --- /dev/null +++ b/example/test_pdf_scale_regression.f90 @@ -0,0 +1,30 @@ +program test_pdf_scale_regression + !! Test to demonstrate PDF scale regression issue #985 + !! This test generates a simple plot that should fill the available plot area + !! but currently generates smaller plots with centering due to aspect ratio preservation + + use iso_fortran_env, only: wp => real64 + use fortplot_figure, only: figure_t + implicit none + + type(figure_t) :: fig + real(wp), parameter :: x_data(5) = [1.0_wp, 2.0_wp, 3.0_wp, 4.0_wp, 5.0_wp] + real(wp), parameter :: y_data(5) = [1.0_wp, 4.0_wp, 2.0_wp, 8.0_wp, 5.0_wp] + + ! Create figure + call fig%initialize(800, 600) + call fig%plot(x_data, y_data) + call fig%set_title("PDF Scale Regression Test - Issue #985") + call fig%set_xlabel("X values") + call fig%set_ylabel("Y values") + + ! Save PDF - this should fill the plot area but currently creates smaller plot + call fig%save("test_pdf_scale_regression.pdf") + call fig%save("test_pdf_scale_regression.png") ! For comparison + + print *, "Generated test files:" + print *, " test_pdf_scale_regression.pdf (current - smaller scale)" + print *, " test_pdf_scale_regression.png (reference - correct scale)" + print *, "Compare the scaling between PDF and PNG outputs" + +end program test_pdf_scale_regression \ No newline at end of file diff --git a/src/backends/vector/fortplot_pdf_coordinate.f90 b/src/backends/vector/fortplot_pdf_coordinate.f90 index fa3a6a9d..62ee7c4b 100644 --- a/src/backends/vector/fortplot_pdf_coordinate.f90 +++ b/src/backends/vector/fortplot_pdf_coordinate.f90 @@ -39,9 +39,7 @@ subroutine normalize_to_pdf_coords(ctx, x, y, pdf_x, pdf_y) real(wp), intent(in) :: x, y real(wp), intent(out) :: pdf_x, pdf_y real(wp) :: x_range, y_range - real(wp) :: x_scale, y_scale, common_scale - real(wp) :: effective_width, effective_height - real(wp) :: x_offset, y_offset + real(wp) :: x_scale, y_scale real(wp), parameter :: EPSILON = 1.0e-10_wp ! Calculate data ranges with epsilon protection @@ -60,28 +58,16 @@ subroutine normalize_to_pdf_coords(ctx, x, y, pdf_x, pdf_y) if (abs(x_range) < EPSILON .or. abs(y_range) < EPSILON) return - ! Calculate potential scales for each axis + ! Calculate independent scales for each axis to fill available plot area + ! This restores the original behavior before aspect ratio preservation was added x_scale = real(ctx%plot_area%width, wp) / x_range y_scale = real(ctx%plot_area%height, wp) / y_range - ! Use the smaller scale to preserve aspect ratio - common_scale = min(x_scale, y_scale) - - ! Calculate effective dimensions using common scale - effective_width = x_range * common_scale - effective_height = y_range * common_scale - - ! Center the plot within the plot area - x_offset = (real(ctx%plot_area%width, wp) - effective_width) * 0.5_wp - y_offset = (real(ctx%plot_area%height, wp) - effective_height) * 0.5_wp - - ! Transform coordinates with aspect ratio preservation - pdf_x = (x - ctx%x_min) * common_scale + & - real(ctx%plot_area%left, wp) + x_offset + ! Transform coordinates using independent scales to fill plot area + pdf_x = (x - ctx%x_min) * x_scale + real(ctx%plot_area%left, wp) ! PDF coordinates: Y=0 at bottom, so transform data coordinates directly - pdf_y = (y - ctx%y_min) * common_scale + & - real(ctx%plot_area%bottom, wp) + y_offset + pdf_y = (y - ctx%y_min) * y_scale + real(ctx%plot_area%bottom, wp) end subroutine normalize_to_pdf_coords real(wp) function pdf_get_width_scale(ctx) result(scale) @@ -247,17 +233,15 @@ end subroutine pdf_render_ylabel subroutine safe_coordinate_transform(x, y, x_min, x_max, y_min, y_max, & plot_left, plot_width, plot_bottom, plot_height, & pdf_x, pdf_y) - !! Safe coordinate transformation with aspect ratio preservation - !! Updated to maintain correct aspect ratios like normalize_to_pdf_coords + !! Safe coordinate transformation to fill available plot area + !! Restored to use independent scales for X and Y axes real(wp), intent(in) :: x, y real(wp), intent(in) :: x_min, x_max, y_min, y_max real(wp), intent(in) :: plot_left, plot_width, plot_bottom, plot_height real(wp), intent(out) :: pdf_x, pdf_y real(wp), parameter :: EPSILON = 1.0e-10_wp real(wp) :: x_range, y_range - real(wp) :: x_scale, y_scale, common_scale - real(wp) :: effective_width, effective_height - real(wp) :: x_offset, y_offset + real(wp) :: x_scale, y_scale ! Calculate ranges with epsilon protection x_range = x_max - x_min @@ -274,24 +258,13 @@ subroutine safe_coordinate_transform(x, y, x_min, x_max, y_min, y_max, & if (abs(x_range) < EPSILON .or. abs(y_range) < EPSILON) return - ! Calculate potential scales for each axis + ! Calculate independent scales for each axis to fill available plot area x_scale = plot_width / x_range y_scale = plot_height / y_range - ! Use the smaller scale to preserve aspect ratio - common_scale = min(x_scale, y_scale) - - ! Calculate effective dimensions using common scale - effective_width = x_range * common_scale - effective_height = y_range * common_scale - - ! Center the plot within the plot area - x_offset = (plot_width - effective_width) * 0.5_wp - y_offset = (plot_height - effective_height) * 0.5_wp - - ! Transform coordinates with aspect ratio preservation - pdf_x = (x - x_min) * common_scale + plot_left + x_offset - pdf_y = (y - y_min) * common_scale + plot_bottom + y_offset + ! Transform coordinates using independent scales to fill plot area + pdf_x = (x - x_min) * x_scale + plot_left + pdf_y = (y - y_min) * y_scale + plot_bottom end subroutine safe_coordinate_transform