module fortplot_figure_plot_management !! Figure plot data management module !! !! Single Responsibility: Manage plot data storage and operations !! Extracted from fortplot_figure_core to improve modularity use, intrinsic :: iso_fortran_env, only: wp => real64 use fortplot_plot_data, only: plot_data_t, PLOT_TYPE_LINE, PLOT_TYPE_CONTOUR, PLOT_TYPE_PCOLORMESH use fortplot_legend, only: legend_t implicit none private public :: add_line_plot_data, add_contour_plot_data, add_colored_contour_plot_data public :: add_pcolormesh_plot_data, generate_default_contour_levels public :: setup_figure_legend, update_plot_ydata, validate_plot_data contains subroutine validate_plot_data(x, y, label) !! Validate plot data and provide informative warnings for edge cases !! Added for Issue #432: Better user feedback for problematic data real(wp), intent(in) :: x(:), y(:) character(len=*), intent(in), optional :: label character(len=100) :: label_str ! Prepare label for messages if (present(label)) then label_str = "'" // trim(label) // "'" else label_str = "(unlabeled plot)" end if ! Check for zero-size arrays if (size(x) == 0 .or. size(y) == 0) then print *, "Warning: Plot data ", trim(label_str), " contains zero-size arrays." print *, " The plot will show axes and labels but no data points." return end if ! Check for mismatched array sizes if (size(x) /= size(y)) then print *, "Warning: Plot data ", trim(label_str), " has mismatched array sizes:" print *, " x has ", size(x), " elements, y has ", size(y), " elements." print *, " Only the common size will be plotted." end if ! Check for single point case if (size(x) == 1 .and. size(y) == 1) then print *, "Info: Plot data ", trim(label_str), " contains a single point." print *, " Automatic scaling will add margins for visibility." end if ! Check for constant values (might be hard to see) if (size(x) > 1 .and. abs(maxval(x) - minval(x)) < 1.0e-10_wp) then print *, "Warning: All x values in plot ", trim(label_str), " are identical." print *, " This may result in a vertical line or poor visualization." end if if (size(y) > 1 .and. abs(maxval(y) - minval(y)) < 1.0e-10_wp) then print *, "Warning: All y values in plot ", trim(label_str), " are identical." print *, " This may result in a horizontal line or poor visualization." end if end subroutine validate_plot_data subroutine add_line_plot_data(plots, plot_count, max_plots, colors, & x, y, label, linestyle, color, marker) !! Add line plot data to internal storage with edge case validation !! Fixed Issue #432: Added data validation for better user feedback type(plot_data_t), intent(inout) :: plots(:) integer, intent(inout) :: plot_count integer, intent(in) :: max_plots real(wp), intent(in) :: colors(:,:) real(wp), intent(in) :: x(:), y(:) character(len=*), intent(in), optional :: label, linestyle, marker real(wp), intent(in) :: color(3) if (plot_count >= max_plots) then print *, "Warning: Maximum number of plots reached" return end if ! Validate input data and provide warnings call validate_plot_data(x, y, label) plot_count = plot_count + 1 ! Store plot data plots(plot_count)%plot_type = PLOT_TYPE_LINE allocate(plots(plot_count)%x(size(x))) allocate(plots(plot_count)%y(size(y))) plots(plot_count)%x = x plots(plot_count)%y = y plots(plot_count)%color = color ! Process optional arguments if (present(label)) then plots(plot_count)%label = label end if if (present(linestyle)) then plots(plot_count)%linestyle = linestyle else plots(plot_count)%linestyle = '-' end if if (present(marker)) then if (len_trim(marker) > 0) then plots(plot_count)%marker = marker end if end if end subroutine add_line_plot_data subroutine add_contour_plot_data(plots, plot_count, max_plots, colors, & x_grid, y_grid, z_grid, levels, label) !! Add contour plot data to internal storage type(plot_data_t), intent(inout) :: plots(:) integer, intent(inout) :: plot_count integer, intent(in) :: max_plots real(wp), intent(in) :: colors(:,:) real(wp), intent(in) :: x_grid(:), y_grid(:), z_grid(:,:) real(wp), intent(in), optional :: levels(:) character(len=*), intent(in), optional :: label if (plot_count >= max_plots) then print *, "Warning: Maximum number of plots reached" return end if plot_count = plot_count + 1 ! Store plot data plots(plot_count)%plot_type = PLOT_TYPE_CONTOUR allocate(plots(plot_count)%x_grid(size(x_grid))) allocate(plots(plot_count)%y_grid(size(y_grid))) allocate(plots(plot_count)%z_grid(size(z_grid,1), size(z_grid,2))) plots(plot_count)%x_grid = x_grid plots(plot_count)%y_grid = y_grid plots(plot_count)%z_grid = z_grid if (present(levels)) then allocate(plots(plot_count)%contour_levels(size(levels))) plots(plot_count)%contour_levels = levels else call generate_default_contour_levels(plots(plot_count)) end if if (present(label)) then plots(plot_count)%label = label end if ! Set default color plots(plot_count)%color = colors(:, mod(plot_count-1, 6) + 1) plots(plot_count)%use_color_levels = .false. end subroutine add_contour_plot_data subroutine add_colored_contour_plot_data(plots, plot_count, max_plots, & x_grid, y_grid, z_grid, levels, & colormap, show_colorbar, label) !! Add colored contour plot data type(plot_data_t), intent(inout) :: plots(:) integer, intent(inout) :: plot_count integer, intent(in) :: max_plots real(wp), intent(in) :: x_grid(:), y_grid(:), z_grid(:,:) real(wp), intent(in), optional :: levels(:) character(len=*), intent(in), optional :: colormap, label logical, intent(in), optional :: show_colorbar if (plot_count >= max_plots) then print *, "Warning: Maximum number of plots reached" return end if plot_count = plot_count + 1 ! Store plot data plots(plot_count)%plot_type = PLOT_TYPE_CONTOUR allocate(plots(plot_count)%x_grid(size(x_grid))) allocate(plots(plot_count)%y_grid(size(y_grid))) allocate(plots(plot_count)%z_grid(size(z_grid,1), size(z_grid,2))) plots(plot_count)%x_grid = x_grid plots(plot_count)%y_grid = y_grid plots(plot_count)%z_grid = z_grid if (present(levels)) then allocate(plots(plot_count)%contour_levels(size(levels))) plots(plot_count)%contour_levels = levels else call generate_default_contour_levels(plots(plot_count)) end if if (present(colormap)) then plots(plot_count)%colormap = colormap else plots(plot_count)%colormap = 'crest' end if if (present(show_colorbar)) then plots(plot_count)%show_colorbar = show_colorbar else plots(plot_count)%show_colorbar = .true. end if if (present(label)) then plots(plot_count)%label = label end if plots(plot_count)%use_color_levels = .true. end subroutine add_colored_contour_plot_data subroutine add_pcolormesh_plot_data(plots, plot_count, max_plots, & x, y, c, colormap, vmin, vmax, & edgecolors, linewidths) !! Add pcolormesh plot data type(plot_data_t), intent(inout) :: plots(:) integer, intent(inout) :: plot_count integer, intent(in) :: max_plots real(wp), intent(in) :: x(:), y(:), c(:,:) character(len=*), intent(in), optional :: colormap real(wp), intent(in), optional :: vmin, vmax real(wp), intent(in), optional :: edgecolors(3) real(wp), intent(in), optional :: linewidths if (plot_count >= max_plots) then print *, "Warning: Maximum number of plots reached" return end if plot_count = plot_count + 1 ! Store plot data plots(plot_count)%plot_type = PLOT_TYPE_PCOLORMESH ! Initialize pcolormesh data using proper method call plots(plot_count)%pcolormesh_data%initialize_regular_grid(x, y, c, colormap) if (present(vmin)) then plots(plot_count)%pcolormesh_data%vmin = vmin plots(plot_count)%pcolormesh_data%vmin_set = .true. end if if (present(vmax)) then plots(plot_count)%pcolormesh_data%vmax = vmax plots(plot_count)%pcolormesh_data%vmax_set = .true. end if if (present(edgecolors)) then plots(plot_count)%pcolormesh_data%show_edges = .true. plots(plot_count)%pcolormesh_data%edge_color = edgecolors end if if (present(linewidths)) then plots(plot_count)%pcolormesh_data%edge_width = linewidths end if end subroutine add_pcolormesh_plot_data subroutine generate_default_contour_levels(plot_data) !! Generate default contour levels type(plot_data_t), intent(inout) :: plot_data real(wp) :: z_min, z_max integer :: num_levels integer :: i z_min = minval(plot_data%z_grid) z_max = maxval(plot_data%z_grid) num_levels = 7 allocate(plot_data%contour_levels(num_levels)) do i = 1, num_levels plot_data%contour_levels(i) = z_min + (i-1) * (z_max - z_min) / (num_levels - 1) end do end subroutine generate_default_contour_levels subroutine setup_figure_legend(legend_data, show_legend, plots, plot_count, location) !! Setup figure legend type(legend_t), intent(inout) :: legend_data logical, intent(inout) :: show_legend type(plot_data_t), intent(in) :: plots(:) integer, intent(in) :: plot_count character(len=*), intent(in), optional :: location character(len=:), allocatable :: loc integer :: i loc = 'upper right' if (present(location)) loc = location show_legend = .true. ! Clear existing legend entries to prevent duplication call legend_data%clear() ! Set legend position based on location string call legend_data%set_position(loc) ! Add legend entries from plots do i = 1, plot_count ! Only add legend entry if label is allocated and not empty if (allocated(plots(i)%label)) then if (len_trim(plots(i)%label) > 0) then if (allocated(plots(i)%linestyle)) then if (allocated(plots(i)%marker)) then call legend_data%add_entry(plots(i)%label, & plots(i)%color, & plots(i)%linestyle, & plots(i)%marker) else call legend_data%add_entry(plots(i)%label, & plots(i)%color, & plots(i)%linestyle) end if else call legend_data%add_entry(plots(i)%label, & plots(i)%color) end if end if end if end do end subroutine setup_figure_legend subroutine update_plot_ydata(plots, plot_count, plot_index, y_new) !! Update y data for an existing plot type(plot_data_t), intent(inout) :: plots(:) integer, intent(in) :: plot_count integer, intent(in) :: plot_index real(wp), intent(in) :: y_new(:) if (plot_index < 1 .or. plot_index > plot_count) then print *, "Warning: Invalid plot index", plot_index return end if if (.not. allocated(plots(plot_index)%y)) then print *, "Warning: Plot", plot_index, "has no y data to update" return end if if (size(y_new) /= size(plots(plot_index)%y)) then print *, "Warning: New y data size", size(y_new), & "does not match existing size", size(plots(plot_index)%y) return end if plots(plot_index)%y = y_new end subroutine update_plot_ydata end module fortplot_figure_plot_management