This document describes the matplotlib-compatible basic plotting implementation in fortplot, covering line plots, scatter plots, and bar charts. The implementation follows matplotlib's API and behavior exactly to ensure drop-in compatibility and familiar user experience.
The current basic plotting implementation has several limitations:
1. Limited format string support - Only basic line styles and markers
2. No scatter plot functionality - Missing dedicated scatter implementation
3. No bar plot support - No bar chart capabilities
4. Incomplete color handling - No color parsing from format strings
5. Missing line style variants - No dotted (:) or dash-dot (-.) styles
6. Single dataset limitation - Cannot handle plot(x1, y1, x2, y2) syntax
The solution consists of four main components:
Following matplotlib's _process_plot_format() in axes/_base.py (lines 3134-3200):
type :: plot_format_t
    character(len=20) :: linestyle = '-'     ! '-', '--', '-.', ':', 'None'
    character(len=20) :: marker = 'None'     ! 'o', 's', '^', etc.
    character(len=20) :: color = 'auto'      ! 'r', 'g', 'b', 'C0'-'C9', etc.
end type
subroutine parse_format_string(fmt_str, format_spec)
    ! Implements matplotlib's exact parsing logic:
    ! 1. Check if entire string is color specification
    ! 2. Parse two-character line styles first ('--', '-.')
    ! 3. Parse single-character elements (markers, colors, '-', ':')
    ! 4. Apply matplotlib defaults (linestyle='-' if no marker specified)
end subroutine
Matplotlib Format String Examples:
- 'ko' → black circles (color + marker)
- 'r--' → red dashed line (color + linestyle)
- 'C2-.' → third color cycle with dash-dot line
- 'o' → circles only (marker, no line)
Following matplotlib's lineStyles dictionary in lines.py:
! Supported line styles matching matplotlib exactly
character(len=*), parameter :: LINESTYLE_SOLID = '-'
character(len=*), parameter :: LINESTYLE_DASHED = '--'
character(len=*), parameter :: LINESTYLE_DOTTED = ':'
character(len=*), parameter :: LINESTYLE_DASHDOT = '-.'
character(len=*), parameter :: LINESTYLE_NONE = 'None'
subroutine draw_line_with_style(backend, x, y, linestyle)
    select case (trim(linestyle))
    case ('-')
        call backend%draw_solid_line(x, y)
    case ('--')
        call backend%draw_dashed_line(x, y, dash_pattern=[5.0, 3.0])
    case (':')
        call backend%draw_dotted_line(x, y, dot_pattern=[1.5, 2.0])
    case ('-.')
        call backend%draw_dash_dot_line(x, y, pattern=[5.0, 2.0, 1.5, 2.0])
    end select
end subroutine
Following matplotlib's MarkerStyle.markers in markers.py:
type :: marker_spec_t
    character(len=10) :: symbol
    real(wp) :: size = 6.0
    character(len=20) :: color = 'auto'
    character(len=20) :: edgecolor = 'auto'
    real(wp) :: linewidth = 1.0
end type
! Matplotlib-compatible marker symbols
character(len=*), parameter :: SUPPORTED_MARKERS(*) = [ &
    'o  ', 's  ', '^  ', 'v  ', '<  ', '>  ', &  ! basic shapes
    '+  ', 'x  ', '*  ', 'D  ', 'd  ', 'p  ', &  ! symbols
    'h  ', 'H  ', '1  ', '2  ', '3  ', '4  ', &  ! specialized
    '.  ', ',  ', '|  ', '_  '                  ] ! points and lines
Following matplotlib's scatter() in axes/_axes.py (lines 4500+):
subroutine scatter(fig, x, y, s, c, marker, alpha, linewidths, edgecolors)
    real(wp), intent(in) :: x(:), y(:)
    real(wp), intent(in), optional :: s(:)        ! sizes in points²
    character(*), intent(in), optional :: c(:)     ! colors
    character(*), intent(in), optional :: marker   ! marker symbol
    real(wp), intent(in), optional :: alpha        ! transparency
    real(wp), intent(in), optional :: linewidths(:) ! edge widths
    character(*), intent(in), optional :: edgecolors(:) ! edge colors
    ! Size handling (matplotlib default: rcParams['lines.markersize'] ** 2)
    if (present(s)) then
        sizes = s
    else
        sizes = [(36.0, i=1, size(x))]  ! 6.0² default marker size
    end if
    ! Render each point with individual size/color
    do i = 1, size(x)
        call draw_marker(fig%backend, x(i), y(i), marker, &
                        sqrt(sizes(i)), colors(i), edges(i))
    end do
end subroutine
Following matplotlib's bar() in axes/_axes.py (lines 2300+):
subroutine bar(fig, x, height, width, bottom, align, color, edgecolor)
    real(wp), intent(in) :: x(:), height(:)
    real(wp), intent(in), optional :: width(:)     ! bar widths (default 0.8)
    real(wp), intent(in), optional :: bottom(:)    ! baseline (default 0)
    character(*), intent(in), optional :: align    ! 'center', 'left', 'right'
    ! Alignment calculation (matplotlib default: 'center')
    select case (trim(align_type))
    case ('center')
        x_left = x - width * 0.5
    case ('left') 
        x_left = x
    case ('right')
        x_left = x - width
    end select
    ! Draw rectangular bars
    do i = 1, size(x)
        call draw_rectangle(fig%backend, x_left(i), bottom_val(i), &
                           width_val(i), height(i), color_val(i))
    end do
end subroutine
Following matplotlib's plot() signature and multi-dataset support:
subroutine plot(fig, varargin)
    ! Support matplotlib calling patterns:
    ! plot(y)                    - y vs index
    ! plot(x, y)                 - basic plot
    ! plot(x, y, fmt)            - with format string
    ! plot(x1, y1, x2, y2)       - multiple datasets
    ! plot(x1, y1, fmt1, x2, y2, fmt2) - multiple with formats
    call parse_plot_arguments(varargin, datasets, formats)
    do i = 1, n_datasets
        format_spec = parse_format_string(formats(i))
        if (format_spec%marker /= 'None' .and. format_spec%linestyle == 'None') then
            ! Marker-only plot
            call scatter(fig, datasets(i)%x, datasets(i)%y, &
                        marker=format_spec%marker, c=format_spec%color)
        else
            ! Line plot (with optional markers)
            call draw_line_with_style(fig%backend, datasets(i)%x, datasets(i)%y, &
                                    format_spec%linestyle)
            if (format_spec%marker /= 'None') then
                call scatter(fig, datasets(i)%x, datasets(i)%y, &
                            marker=format_spec%marker, c=format_spec%color)
            end if
        end if
    end do
end subroutine
Following matplotlib's color cycle system:
! Matplotlib default color cycle (C0-C9)
character(len=*), parameter :: DEFAULT_COLORS(*) = [ &
    '#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', &
    '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf'  ]
integer :: current_color_index = 1
function get_next_color() result(color)
    color = DEFAULT_COLORS(current_color_index)
    current_color_index = modulo(current_color_index, size(DEFAULT_COLORS)) + 1
end function
Before (current implementation): - Limited format strings ('o', 'x', '-', '--') - No scatter or bar plot support - Basic color handling - Single dataset per plot call
After (matplotlib-compatible): - Complete format string support matching matplotlib - Dedicated scatter() and bar() functions - Full color cycle and named color support - Multi-dataset plotting with automatic color cycling - Professional-quality rendering matching matplotlib output
thirdparty/matplotlib/lib/matplotlib/axes/_axes.py (lines 1591-1600)thirdparty/matplotlib/lib/matplotlib/axes/_base.py (lines 3134-3200)thirdparty/matplotlib/lib/matplotlib/lines.pythirdparty/matplotlib/lib/matplotlib/axes/_axes.py (lines 4500+)thirdparty/matplotlib/lib/matplotlib/axes/_axes.py (lines 2300+)thirdparty/pyplot-fortran/src/pyplot_module.F90 (plotting patterns)src/fortplot_basic_plots.f90 - Enhanced plot(), scatter(), bar() functionssrc/fortplot_format_parser.f90 - Complete matplotlib format string parsersrc/fortplot_markers.f90 - Extended marker support and renderingsrc/fortplot_linestyles.f90 - Complete line style implementationssrc/fortplot_colors.f90 - Color cycle and named color supportpython/fortplot/fortplot.py - Updated Python interfacetest/test_basic_plots.f90 - Comprehensive plotting teststest/test_format_parser.f90 - Format string parsing verificationThis implementation ensures fortplot provides matplotlib-compatible basic plotting functionality with professional rendering quality and complete API compatibility for seamless migration from matplotlib-based workflows.