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
3 changes: 3 additions & 0 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
**Infrastructure & Documentation Issues (Lower Priority)**
- [ ] #323: Test - add edge case tests for PDF heatmap color validation
- [ ] #324: Refactor - define epsilon constant for numerical comparisons
- [ ] #342: Refactor - complete symlog tick generation implementation
- [ ] #343: Refactor - extract label positioning constants
- [ ] #344: Refactor - add format threshold constants in axes module

## DOING (Current Work)
- [ ] #335: Fix - Axes wrong and no labels visible on scale_examples.html
Expand Down
33 changes: 33 additions & 0 deletions src/fortplot_ascii.f90
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,11 @@ subroutine output_to_terminal(this)
if (allocated(this%xlabel_text)) then
call print_centered_title(this%xlabel_text, this%plot_width)
end if

! Print ylabel (simple horizontal placement for now)
if (allocated(this%ylabel_text)) then
print '(A)', this%ylabel_text
end if
end subroutine output_to_terminal

subroutine output_to_file(this, unit)
Expand Down Expand Up @@ -339,6 +344,11 @@ subroutine output_to_file(this, unit)
if (allocated(this%xlabel_text)) then
call write_centered_title(unit, this%xlabel_text, this%plot_width)
end if

! Write ylabel to the left side of the plot if present
if (allocated(this%ylabel_text)) then
write(unit, '(A)') this%ylabel_text
end if
end subroutine output_to_file

integer function get_char_density(char)
Expand Down Expand Up @@ -871,6 +881,7 @@ subroutine ascii_draw_axes_and_labels(this, xscale, yscale, symlog_threshold, &
title, xlabel, ylabel, &
z_min, z_max, has_3d_plots)
!! Draw axes and labels for ASCII backend
use fortplot_axes, only: compute_scale_ticks, format_tick_label, MAX_TICKS
class(ascii_context), intent(inout) :: this
character(len=*), intent(in) :: xscale, yscale
real(wp), intent(in) :: symlog_threshold
Expand All @@ -880,6 +891,10 @@ subroutine ascii_draw_axes_and_labels(this, xscale, yscale, symlog_threshold, &
logical, intent(in) :: has_3d_plots

real(wp) :: label_x, label_y
real(wp) :: x_tick_positions(MAX_TICKS), y_tick_positions(MAX_TICKS)
integer :: num_x_ticks, num_y_ticks, i
character(len=50) :: tick_label
real(wp) :: tick_x, tick_y

! ASCII backend: explicitly set title and draw simple axes
if (present(title)) then
Expand All @@ -892,6 +907,24 @@ subroutine ascii_draw_axes_and_labels(this, xscale, yscale, symlog_threshold, &
call this%line(x_min, y_min, x_max, y_min)
call this%line(x_min, y_min, x_min, y_max)

! Generate tick marks and labels for ASCII
! X-axis ticks (drawn as characters along bottom axis)
call compute_scale_ticks(xscale, x_min, x_max, symlog_threshold, x_tick_positions, num_x_ticks)
do i = 1, num_x_ticks
tick_x = x_tick_positions(i)
! For ASCII, draw tick marks as characters in the text output
tick_label = format_tick_label(tick_x, xscale)
call this%text(tick_x, y_min - 0.05_wp * (y_max - y_min), trim(tick_label))
end do

! Y-axis ticks (drawn as characters along left axis)
call compute_scale_ticks(yscale, y_min, y_max, symlog_threshold, y_tick_positions, num_y_ticks)
do i = 1, num_y_ticks
tick_y = y_tick_positions(i)
tick_label = format_tick_label(tick_y, yscale)
call this%text(x_min - 0.1_wp * (x_max - x_min), tick_y, trim(tick_label))
end do

! Store xlabel and ylabel for rendering outside the plot frame
if (present(xlabel)) then
if (allocated(xlabel)) then
Expand Down
25 changes: 22 additions & 3 deletions src/fortplot_axes.f90
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module fortplot_axes
implicit none

private
public :: compute_scale_ticks, format_tick_label
public :: compute_scale_ticks, format_tick_label, MAX_TICKS

integer, parameter :: MAX_TICKS = 20

Expand Down Expand Up @@ -141,14 +141,33 @@ function format_tick_label(value, scale_type) result(label)
real(wp), intent(in) :: value
character(len=*), intent(in) :: scale_type
character(len=20) :: label
real(wp) :: abs_value

if (abs(value) < 1.0e-10_wp) then
abs_value = abs(value)

if (abs_value < 1.0e-10_wp) then
label = '0'
else if (trim(scale_type) == 'log' .and. is_power_of_ten(value)) then
label = format_power_of_ten(value)
else if (abs_value >= 1000.0_wp .or. abs_value < 0.01_wp) then
! Use scientific notation for very large or very small values
write(label, '(ES10.2)') value
label = adjustl(label)
else if (abs_value >= 100.0_wp) then
! No decimal places for values >= 100
write(label, '(F0.0)') value
else if (abs_value >= 10.0_wp) then
! One decimal place for values >= 10
write(label, '(F0.1)') value
else if (abs_value >= 1.0_wp) then
! Two decimal places for values >= 1
write(label, '(F0.2)') value
else
write(label, '(G0)') value
! Three decimal places for small values
write(label, '(F0.3)') value
end if

label = adjustl(label)
end function format_tick_label


Expand Down
10 changes: 4 additions & 6 deletions src/fortplot_figure_core.f90
Original file line number Diff line number Diff line change
Expand Up @@ -934,12 +934,10 @@ subroutine render_figure_axes(self)
self%title, self%xlabel, self%ylabel, &
0.0_wp, 0.0_wp, .false.)
type is (ascii_context)
! ASCII backend: explicitly set title and draw simple axes
if (allocated(self%title)) then
call backend%set_title(self%title)
end if
call self%backend%line(self%x_min, self%y_min, self%x_max, self%y_min)
call self%backend%line(self%x_min, self%y_min, self%x_min, self%y_max)
call backend%draw_axes_and_labels_backend(self%xscale, self%yscale, self%symlog_threshold, &
self%x_min, self%x_max, self%y_min, self%y_max, &
self%title, self%xlabel, self%ylabel, &
0.0_wp, 0.0_wp, .false.)
class default
! For other backends, use simple axes
call self%backend%line(self%x_min, self%y_min, self%x_max, self%y_min)
Expand Down
101 changes: 91 additions & 10 deletions src/fortplot_raster.f90
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,8 @@ subroutine raster_draw_axes_and_labels(this, xscale, yscale, symlog_threshold, &
title, xlabel, ylabel, &
z_min, z_max, has_3d_plots)
!! Draw axes and labels for raster backends
use fortplot_axes, only: compute_scale_ticks, format_tick_label, MAX_TICKS
use fortplot_text, only: calculate_text_width, calculate_text_height
class(raster_context), intent(inout) :: this
character(len=*), intent(in) :: xscale, yscale
real(wp), intent(in) :: symlog_threshold
Expand All @@ -955,14 +957,84 @@ subroutine raster_draw_axes_and_labels(this, xscale, yscale, symlog_threshold, &
logical, intent(in) :: has_3d_plots

real(wp) :: label_x, label_y
real(wp) :: x_tick_positions(MAX_TICKS), y_tick_positions(MAX_TICKS)
integer :: num_x_ticks, num_y_ticks, i
character(len=50) :: tick_label
real(wp) :: tick_x, tick_y
integer :: tick_length ! Tick length in pixels
integer :: px, py, text_width, text_height
real(wp) :: line_r, line_g, line_b
integer(1) :: text_r, text_g, text_b
real(wp) :: dummy_pattern(1) ! Dummy pattern for solid lines
real(wp) :: pattern_dist ! Pattern distance (mutable)
character(len=500) :: processed_text, escaped_text
integer :: processed_len

! Set color to black for axes and text
call this%color(0.0_wp, 0.0_wp, 0.0_wp)
line_r = 0.0_wp; line_g = 0.0_wp; line_b = 0.0_wp ! Black color for lines
text_r = 0; text_g = 0; text_b = 0 ! Black color for text

! Draw axes
call this%line(x_min, y_min, x_max, y_min)
call this%line(x_min, y_min, x_min, y_max)

! Generate and draw tick marks and labels
tick_length = 5 ! Tick length in pixels

! X-axis ticks
call compute_scale_ticks(xscale, x_min, x_max, symlog_threshold, x_tick_positions, num_x_ticks)
do i = 1, num_x_ticks
tick_x = x_tick_positions(i)
! Transform tick position to pixel coordinates
px = int((tick_x - x_min) / (x_max - x_min) * real(this%plot_area%width, wp) + real(this%plot_area%left, wp))
py = this%plot_area%bottom + this%plot_area%height ! Bottom of plot area

! Draw tick mark down from axis
dummy_pattern = 0.0_wp
pattern_dist = 0.0_wp
call draw_styled_line(this%raster%image_data, this%width, this%height, &
real(px, wp), real(py, wp), real(px, wp), real(py + tick_length, wp), &
line_r, line_g, line_b, 1.0_wp, 'solid', dummy_pattern, 0, 0.0_wp, pattern_dist)

! Draw tick label below tick mark
tick_label = format_tick_label(tick_x, xscale)
call process_latex_in_text(trim(tick_label), processed_text, processed_len)
call escape_unicode_for_raster(processed_text(1:processed_len), escaped_text)
text_width = calculate_text_width(trim(escaped_text))
! Center the label horizontally under the tick
call render_text_to_image(this%raster%image_data, this%width, this%height, &
px - text_width/2, py + tick_length + 5, trim(escaped_text), text_r, text_g, text_b)
end do

! Y-axis ticks
call compute_scale_ticks(yscale, y_min, y_max, symlog_threshold, y_tick_positions, num_y_ticks)
do i = 1, num_y_ticks
tick_y = y_tick_positions(i)
! Transform tick position to pixel coordinates
px = this%plot_area%left ! Left edge of plot area
py = int(real(this%plot_area%bottom + this%plot_area%height, wp) - &
(tick_y - y_min) / (y_max - y_min) * real(this%plot_area%height, wp))

! Draw tick mark to the left from axis
dummy_pattern = 0.0_wp
pattern_dist = 0.0_wp
call draw_styled_line(this%raster%image_data, this%width, this%height, &
real(px - tick_length, wp), real(py, wp), real(px, wp), real(py, wp), &
line_r, line_g, line_b, 1.0_wp, 'solid', dummy_pattern, 0, 0.0_wp, pattern_dist)

! Draw tick label to the left of tick mark
tick_label = format_tick_label(tick_y, yscale)
call process_latex_in_text(trim(tick_label), processed_text, processed_len)
call escape_unicode_for_raster(processed_text(1:processed_len), escaped_text)
text_width = calculate_text_width(trim(escaped_text))
text_height = calculate_text_height(trim(escaped_text))
! Right-align the label to the left of the tick
call render_text_to_image(this%raster%image_data, this%width, this%height, &
px - tick_length - text_width - 5, py - text_height/2, &
trim(escaped_text), text_r, text_g, text_b)
end do

! Draw title at top if present
if (present(title)) then
if (allocated(title)) then
Expand All @@ -972,23 +1044,32 @@ subroutine raster_draw_axes_and_labels(this, xscale, yscale, symlog_threshold, &
end if
end if

! Draw xlabel centered below x-axis
! Draw xlabel centered below x-axis (below tick labels)
if (present(xlabel)) then
if (allocated(xlabel)) then
label_x = (x_min + x_max) / 2.0_wp
label_y = y_min - 0.05_wp * (y_max - y_min)
call this%text(label_x, label_y, xlabel)
call process_latex_in_text(xlabel, processed_text, processed_len)
call escape_unicode_for_raster(processed_text(1:processed_len), escaped_text)
text_width = calculate_text_width(trim(escaped_text))
! Center horizontally in plot area, position below tick labels
px = this%plot_area%left + this%plot_area%width / 2 - text_width / 2
py = this%plot_area%bottom + this%plot_area%height + 30 ! 30 pixels below plot area
call render_text_to_image(this%raster%image_data, this%width, this%height, &
px, py, trim(escaped_text), text_r, text_g, text_b)
end if
end if

! Draw ylabel to the left of y-axis
! Draw ylabel to the left of y-axis (rotated would be better, but for now horizontal)
if (present(ylabel)) then
if (allocated(ylabel)) then
! For raster, ylabel needs special handling for rotation
! For now, just place it horizontally
label_x = x_min - 0.1_wp * (x_max - x_min)
label_y = (y_min + y_max) / 2.0_wp
call this%text(label_x, label_y, ylabel)
call process_latex_in_text(ylabel, processed_text, processed_len)
call escape_unicode_for_raster(processed_text(1:processed_len), escaped_text)
text_width = calculate_text_width(trim(escaped_text))
text_height = calculate_text_height(trim(escaped_text))
! Position to the left of plot area, centered vertically
px = 10 ! 10 pixels from left edge
py = this%plot_area%bottom + this%plot_area%height / 2 - text_height / 2
call render_text_to_image(this%raster%image_data, this%width, this%height, &
px, py, trim(escaped_text), text_r, text_g, text_b)
end if
end if
end subroutine raster_draw_axes_and_labels
Expand Down
54 changes: 54 additions & 0 deletions test/test_axes_labels_comprehensive.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
program test_axes_labels_comprehensive
!! Comprehensive test for Issue #335: Missing axis labels and tick marks
!! Tests that all axis text elements are properly rendered in both PNG and ASCII

use fortplot
implicit none

real(wp), dimension(6) :: x_data, y_linear, y_log
integer :: i

print *, "=== Comprehensive Axes Labels Test ==="

! Generate test data
x_data = [(real(i, wp), i = 1, 6)]
y_linear = x_data * 2.0_wp + 1.0_wp ! Linear: 3, 5, 7, 9, 11, 13
y_log = exp(x_data * 0.5_wp) ! Exponential for log scale

! Test 1: Linear scale with all axis elements
print *, "Testing linear scale with comprehensive axis labeling..."
call figure()
call plot(x_data, y_linear)
call title('Linear Scale Test - All Elements')
call xlabel('Input Values (x)')
call ylabel('Linear Response (2x+1)')
call savefig('test_linear_axes_comprehensive.png')
call savefig('test_linear_axes_comprehensive.txt')

! Test 2: Log scale with proper formatting
print *, "Testing log scale with scientific notation..."
call figure()
call plot(x_data, y_log)
call set_yscale('log')
call title('Log Scale Test - Scientific Labels')
call xlabel('Input Index')
call ylabel('Exponential Growth')
call savefig('test_log_axes_comprehensive.png')
call savefig('test_log_axes_comprehensive.txt')

print *, ""
print *, "VERIFICATION CHECKLIST:"
print *, "========================"
print *, "For each output file, verify:"
print *, "1. Title text is visible at top"
print *, "2. X-axis label appears below plot"
print *, "3. Y-axis label appears left of plot"
print *, "4. Tick marks are visible on axes"
print *, "5. Tick labels show numerical values"
print *, "6. Log scale uses appropriate notation"
print *, ""
print *, "Files created:"
print *, "- test_linear_axes_comprehensive.png/txt"
print *, "- test_log_axes_comprehensive.png/txt"

end program test_axes_labels_comprehensive
Loading
Loading