fortplot_polar_rendering.f90 Source File


Source Code

module fortplot_polar_rendering
    !! Polar projection rendering for polygon backends
    !!
    !! Renders circular plot boundary, radial gridlines (spokes),
    !! angular gridlines (concentric circles), and angular tick labels.

    use, intrinsic :: iso_fortran_env, only: wp => real64
    use fortplot_context
    use fortplot_polar, only: polar_to_cartesian, compute_angular_ticks, &
                              compute_radial_ticks, PI, TWO_PI, RAD_TO_DEG
    implicit none

    private
    public :: render_polar_boundary
    public :: render_polar_radial_gridlines
    public :: render_polar_angular_gridlines
    public :: render_polar_angular_ticks
    public :: render_polar_data

    integer, parameter :: CIRCLE_SEGMENTS = 72  ! 5-degree resolution

contains

    subroutine render_polar_boundary(backend, center_x, center_y, radius, &
                                     line_width, color)
        !! Render circular boundary for polar plot
        class(plot_context), intent(inout) :: backend
        real(wp), intent(in) :: center_x, center_y, radius
        real(wp), intent(in), optional :: line_width
        real(wp), intent(in), optional :: color(3)

        real(wp) :: angle, x1, y1, x2, y2
        real(wp) :: lw, c(3)
        integer :: i

        lw = 1.0_wp
        if (present(line_width)) lw = line_width
        c = [0.0_wp, 0.0_wp, 0.0_wp]  ! Black default
        if (present(color)) c = color

        call backend%color(c(1), c(2), c(3))
        call backend%set_line_width(lw)
        call backend%set_line_style('-')

        do i = 1, CIRCLE_SEGMENTS
            angle = TWO_PI*real(i - 1, wp)/real(CIRCLE_SEGMENTS, wp)
            x1 = center_x + radius*cos(angle)
            y1 = center_y + radius*sin(angle)

            angle = TWO_PI*real(i, wp)/real(CIRCLE_SEGMENTS, wp)
            x2 = center_x + radius*cos(angle)
            y2 = center_y + radius*sin(angle)

            call backend%line(x1, y1, x2, y2)
        end do
    end subroutine render_polar_boundary

    subroutine render_polar_radial_gridlines(backend, center_x, center_y, &
                                             radius, n_spokes, &
                                             theta_offset, clockwise, &
                                             color, alpha)
        !! Render radial gridlines (spokes) from center to boundary
        class(plot_context), intent(inout) :: backend
        real(wp), intent(in) :: center_x, center_y, radius
        integer, intent(in) :: n_spokes
        real(wp), intent(in) :: theta_offset
        logical, intent(in) :: clockwise
        real(wp), intent(in), optional :: color(3)
        real(wp), intent(in), optional :: alpha

        real(wp) :: theta, x_end, y_end
        real(wp) :: c(3), step
        integer :: i

        if (n_spokes <= 0) return

        c = [0.7_wp, 0.7_wp, 0.7_wp]  ! Gray default
        if (present(color)) c = color

        call backend%color(c(1), c(2), c(3))
        call backend%set_line_style(':')
        call backend%set_line_width(0.5_wp)

        step = TWO_PI/real(n_spokes, wp)
        do i = 1, n_spokes
            theta = real(i - 1, wp)*step
            call polar_to_cartesian(theta, radius, x_end, y_end, &
                                    theta_offset, clockwise)
            x_end = center_x + x_end
            y_end = center_y + y_end
            call backend%line(center_x, center_y, x_end, y_end)
        end do

        call backend%set_line_style('-')
    end subroutine render_polar_radial_gridlines

    subroutine render_polar_angular_gridlines(backend, center_x, center_y, &
                                              r_max, n_circles, &
                                              color, alpha)
        !! Render angular gridlines (concentric circles)
        class(plot_context), intent(inout) :: backend
        real(wp), intent(in) :: center_x, center_y, r_max
        integer, intent(in) :: n_circles
        real(wp), intent(in), optional :: color(3)
        real(wp), intent(in), optional :: alpha

        real(wp) :: r_step, r_current
        real(wp) :: c(3)
        integer :: i

        if (n_circles <= 0) return

        c = [0.7_wp, 0.7_wp, 0.7_wp]  ! Gray default
        if (present(color)) c = color

        call backend%color(c(1), c(2), c(3))
        call backend%set_line_style(':')
        call backend%set_line_width(0.5_wp)

        r_step = r_max/real(n_circles + 1, wp)
        do i = 1, n_circles
            r_current = r_step*real(i, wp)
            call render_circle(backend, center_x, center_y, r_current)
        end do

        call backend%set_line_style('-')
    end subroutine render_polar_angular_gridlines

    subroutine render_circle(backend, cx, cy, radius)
        !! Helper: render a circle as line segments
        class(plot_context), intent(inout) :: backend
        real(wp), intent(in) :: cx, cy, radius

        real(wp) :: angle, x1, y1, x2, y2
        integer :: i

        do i = 1, CIRCLE_SEGMENTS
            angle = TWO_PI*real(i - 1, wp)/real(CIRCLE_SEGMENTS, wp)
            x1 = cx + radius*cos(angle)
            y1 = cy + radius*sin(angle)

            angle = TWO_PI*real(i, wp)/real(CIRCLE_SEGMENTS, wp)
            x2 = cx + radius*cos(angle)
            y2 = cy + radius*sin(angle)

            call backend%line(x1, y1, x2, y2)
        end do
    end subroutine render_circle

    subroutine render_polar_angular_ticks(backend, center_x, center_y, radius, &
                                          n_ticks, theta_offset, clockwise, &
                                          label_offset)
        !! Render angular tick labels around the polar plot boundary
        class(plot_context), intent(inout) :: backend
        real(wp), intent(in) :: center_x, center_y, radius
        integer, intent(in) :: n_ticks
        real(wp), intent(in) :: theta_offset
        logical, intent(in) :: clockwise
        real(wp), intent(in), optional :: label_offset

        real(wp) :: theta, x_label, y_label, label_r
        real(wp) :: tick_angles(36)
        character(len=8) :: tick_labels(36)
        integer :: i, n

        if (n_ticks <= 0) return

        n = min(n_ticks, 36)
        call compute_angular_ticks(n, tick_angles(1:n), tick_labels(1:n))

        label_r = radius*1.12_wp
        if (present(label_offset)) label_r = radius + label_offset

        call backend%color(0.0_wp, 0.0_wp, 0.0_wp)

        do i = 1, n
            call polar_to_cartesian(tick_angles(i), label_r, x_label, y_label, &
                                    theta_offset, clockwise)
            x_label = center_x + x_label
            y_label = center_y + y_label
            call backend%text(x_label, y_label, trim(tick_labels(i)))
        end do
    end subroutine render_polar_angular_ticks

    subroutine render_polar_data(backend, theta, r, n, center_x, center_y, &
                                 r_scale, theta_offset, clockwise, color)
        !! Render polar data as connected line segments
        class(plot_context), intent(inout) :: backend
        real(wp), intent(in) :: theta(:), r(:)
        integer, intent(in) :: n
        real(wp), intent(in) :: center_x, center_y, r_scale
        real(wp), intent(in) :: theta_offset
        logical, intent(in) :: clockwise
        real(wp), intent(in), optional :: color(3)

        real(wp) :: x1, y1, x2, y2, r_scaled
        real(wp) :: c(3)
        integer :: i

        if (n < 2) return

        c = [0.0_wp, 0.447_wp, 0.698_wp]  ! Default blue
        if (present(color)) c = color

        call backend%color(c(1), c(2), c(3))
        call backend%set_line_width(1.5_wp)
        call backend%set_line_style('-')

        r_scaled = r(1)*r_scale
        call polar_to_cartesian(theta(1), r_scaled, x1, y1, theta_offset, &
                                clockwise)
        x1 = center_x + x1
        y1 = center_y + y1

        do i = 2, n
            r_scaled = r(i)*r_scale
            call polar_to_cartesian(theta(i), r_scaled, x2, y2, theta_offset, &
                                    clockwise)
            x2 = center_x + x2
            y2 = center_y + y2

            call backend%line(x1, y1, x2, y2)

            x1 = x2
            y1 = y2
        end do
    end subroutine render_polar_data

end module fortplot_polar_rendering