fortplot_2d_plots.f90 Source File


Source Code

module fortplot_2d_plots
    !! 2D plot operations module
    !! 
    !! This module handles all 2D plot operations including line plots
    !! and their associated data initialization and property setting.

    use, intrinsic :: iso_fortran_env, only: wp => real64
    use fortplot_figure_core, only: figure_t
    use fortplot_figure_initialization, only: figure_state_t
    use fortplot_plot_data, only: plot_data_t, PLOT_TYPE_LINE
    use fortplot_colors, only: parse_color, color_t
    use fortplot_logging, only: log_warning, log_error
    use fortplot_format_parser, only: parse_format_string
    use fortplot_coordinate_validation, only: validate_coordinate_arrays, coordinate_validation_result_t

    implicit none

    private
    public :: add_plot_impl
    public :: add_line_plot_data
    public :: init_line_plot_data
    public :: set_line_plot_properties

    interface add_plot
        module procedure add_plot_impl
    end interface
    public :: add_plot

contains

    subroutine add_plot_impl(self, x, y, label, linestyle, color_rgb, color_str, marker, markercolor)
        !! Add 2D line plot to figure
        class(figure_t), intent(inout) :: self
        real(wp), intent(in) :: x(:), y(:)
        character(len=*), intent(in), optional :: label
        character(len=*), intent(in), optional :: linestyle
        real(wp), intent(in), optional :: color_rgb(3)
        character(len=*), intent(in), optional :: color_str
        character(len=*), intent(in), optional :: marker
        real(wp), intent(in), optional :: markercolor(3)
        real(wp) :: mc_dummy
        if (present(markercolor)) mc_dummy = markercolor(1)
        
        call add_line_plot_data(self, x, y, label, linestyle, color_rgb, color_str, marker)
    end subroutine add_plot_impl

    subroutine add_line_plot_data(self, x, y, label, linestyle, color_rgb, color_str, marker)
        !! Add line plot data with comprehensive validation
        !! Enhanced Issue #854: Added comprehensive parameter validation
        use fortplot_parameter_validation, only: validate_numeric_parameters, validate_color_values, &
                                               validate_array_bounds, parameter_validation_result_t
        class(figure_t), intent(inout) :: self
        real(wp), intent(in) :: x(:), y(:)
        character(len=*), intent(in), optional :: label, linestyle, color_str, marker
        real(wp), intent(in), optional :: color_rgb(3)
        
        integer :: plot_idx
        type(coordinate_validation_result_t) :: validation
        type(parameter_validation_result_t) :: param_validation
        
        ! Validate coordinate arrays (Issue #436 fix)
        validation = validate_coordinate_arrays(x, y, "add_line_plot_data")
        if (.not. validation%is_valid) then
            call log_error(trim(validation%message))
            return  ! Skip adding invalid data
        end if
        
        ! Issue #854: Validate numeric parameters for NaN/infinity
        param_validation = validate_numeric_parameters(x, "x_coordinates", "add_line_plot_data")
        if (.not. param_validation%is_valid) then
            call log_error("X coordinates: " // trim(param_validation%message))
            return
        end if
        
        param_validation = validate_numeric_parameters(y, "y_coordinates", "add_line_plot_data")  
        if (.not. param_validation%is_valid) then
            call log_error("Y coordinates: " // trim(param_validation%message))
            return
        end if
        
        ! Issue #854: Validate array bounds
        param_validation = validate_array_bounds(size(x), min_size=1, context="add_line_plot_data")
        if (.not. param_validation%is_valid) then
            call log_error("Array bounds: " // trim(param_validation%message))
            return
        end if
        
        ! Issue #854: Validate color values if provided
        if (present(color_rgb)) then
            param_validation = validate_color_values(color_rgb(1), color_rgb(2), color_rgb(3), &
                                                   context="add_line_plot_data")
            ! Don't return on color validation failure - just warn and continue with default colors
        end if
        
        ! For single points, suggest using markers for better visibility
        if (validation%is_single_point .and. .not. present(marker)) then
            call log_warning("Single point detected - consider adding markers for better visibility")
        end if
        
        ! Get current plot index
        self%plot_count = self%plot_count + 1
        plot_idx = self%plot_count
        
        ! Ensure plots array is allocated
        if (.not. allocated(self%plots)) then
            allocate(self%plots(self%state%max_plots))
        else if (plot_idx > size(self%plots)) then
            return
        end if
        
        ! Initialize plot data
        call init_line_plot_data(self%plots(plot_idx), x, y)
        
        ! Set optional properties
        call set_line_plot_properties(self%plots(plot_idx), plot_idx, &
                                     label, linestyle, marker, color_rgb, color_str, self%state)
    end subroutine add_line_plot_data
    
    subroutine init_line_plot_data(plot, x, y)
        !! Initialize basic line plot data
        type(plot_data_t), intent(inout) :: plot
        real(wp), intent(in) :: x(:), y(:)
        
        plot%plot_type = PLOT_TYPE_LINE
        
        allocate(plot%x(size(x)))
        allocate(plot%y(size(y)))
        plot%x = x
        plot%y = y
    end subroutine init_line_plot_data
    
    subroutine set_line_plot_properties(plot, plot_idx, label, linestyle, marker, &
                                       color_rgb, color_str, state)
        !! Set line plot properties (style, color, etc.)
        !! Extracted from add_line_plot_data for QADS compliance
        type(plot_data_t), intent(inout) :: plot
        integer, intent(in) :: plot_idx
        character(len=*), intent(in), optional :: label, linestyle, marker, color_str
        real(wp), intent(in), optional :: color_rgb(3)
        type(figure_state_t), intent(in) :: state
        
        character(len=20) :: parsed_marker, parsed_linestyle
        real(wp) :: rgb(3)
        logical :: success
        integer :: color_idx
        
        ! Set label
        if (present(label) .and. len_trim(label) > 0) then
            plot%label = label
        end if
        
        ! Parse linestyle to extract marker and line style components
        if (present(linestyle)) then
            call parse_format_string(linestyle, parsed_marker, parsed_linestyle)
            
            if (len_trim(parsed_marker) > 0) then
                plot%marker = parsed_marker
            end if
            
            if (len_trim(parsed_linestyle) > 0) then
                plot%linestyle = parsed_linestyle
            end if
        end if
        
        ! Handle explicit marker override
        if (present(marker)) then
            plot%marker = marker
        end if
        
        ! Set color
        if (present(color_rgb)) then
            plot%color = color_rgb
        else if (present(color_str)) then
            call parse_color(color_str, rgb, success)
            if (success) then
                plot%color = rgb
            else
                ! Use default color cycling
                color_idx = mod(plot_idx - 1, size(state%colors, 2)) + 1
                plot%color = state%colors(:, color_idx)
            end if
        else
            ! Use default color cycling
            color_idx = mod(plot_idx - 1, size(state%colors, 2)) + 1
            plot%color = state%colors(:, color_idx)
        end if
        
        ! Initialize linestyle if not already done
        if (.not. allocated(plot%linestyle)) then
            plot%linestyle = '-'  ! default solid line
        end if
    end subroutine set_line_plot_properties

end module fortplot_2d_plots