module fortplot_spec_frontend_adapters !! Convert figure_t into a proper spec_t with marks, encodings, !! data columns, and config -- no scene_t wrapper. use, intrinsic :: iso_fortran_env, only: wp => real64 use fortplot_figure_core, only: figure_t use fortplot_spec_types, only: spec_t, layer_t, mark_t, & encoding_t, channel_t, data_t, data_column_t, & field_plot_t use fortplot_spec_config_defaults, only: mpl_default_config use fortplot_plot_data, only: plot_data_t, & PLOT_TYPE_LINE, PLOT_TYPE_SCATTER, PLOT_TYPE_BAR, & PLOT_TYPE_FILL, PLOT_TYPE_CONTOUR, & PLOT_TYPE_PCOLORMESH, PLOT_TYPE_ERRORBAR, & PLOT_TYPE_HISTOGRAM implicit none private public :: figure_to_spec contains subroutine figure_to_spec(fig, spec) type(figure_t), intent(in) :: fig type(spec_t), intent(out) :: spec integer :: i spec%width = fig%state%width spec%height = fig%state%height if (allocated(fig%state%title)) spec%title = fig%state%title spec%config = mpl_default_config(fig%state%dpi) call apply_state_metadata(fig, spec) if (fig%plot_count == 0) then spec%mark%type = 'line' spec%encoding%x = make_channel('x', 'quantitative') spec%encoding%y = make_channel('y', 'quantitative') return end if if (fig%plot_count == 1) then call convert_plot(fig%plots(1), spec) return end if spec%is_layered = .true. spec%layer_count = fig%plot_count allocate (spec%layers(fig%plot_count)) spec%encoding%x = make_channel('x', 'quantitative') spec%encoding%y = make_channel('y', 'quantitative') do i = 1, fig%plot_count call convert_plot_to_layer(fig%plots(i), & spec%layers(i)) end do end subroutine figure_to_spec subroutine apply_state_metadata(fig, spec) type(figure_t), intent(in) :: fig type(spec_t), intent(inout) :: spec if (allocated(fig%state%xlabel)) then spec%encoding%x%axis%title = fig%state%xlabel spec%encoding%x%axis%title_set = .true. end if if (allocated(fig%state%ylabel)) then spec%encoding%y%axis%title = fig%state%ylabel spec%encoding%y%axis%title_set = .true. end if if (fig%state%xlim_set) then spec%encoding%x%scale%domain_min = fig%state%x_min spec%encoding%x%scale%domain_max = fig%state%x_max spec%encoding%x%scale%domain_set = .true. end if if (fig%state%ylim_set) then spec%encoding%y%scale%domain_min = fig%state%y_min spec%encoding%y%scale%domain_max = fig%state%y_max spec%encoding%y%scale%domain_set = .true. end if if (fig%state%xscale /= 'linear') then spec%encoding%x%scale%type = trim(fig%state%xscale) end if if (fig%state%yscale /= 'linear') then spec%encoding%y%scale%type = trim(fig%state%yscale) end if if (fig%state%grid_enabled) then spec%encoding%x%axis%grid = .true. spec%encoding%y%axis%grid = .true. end if end subroutine apply_state_metadata subroutine convert_plot(pd, spec) type(plot_data_t), intent(in) :: pd type(spec_t), intent(inout) :: spec call build_mark_from_plot(pd, spec%mark) spec%encoding%x = make_channel('x', 'quantitative') spec%encoding%y = make_channel('y', 'quantitative') if (pd%plot_type == PLOT_TYPE_CONTOUR .or. & pd%plot_type == PLOT_TYPE_PCOLORMESH) then call build_field_from_plot(pd, spec%field) else call build_data_from_plot(pd, spec%data) end if if (allocated(pd%label)) then spec%encoding%color%value = pd%label spec%encoding%color%defined = .true. end if end subroutine convert_plot subroutine convert_plot_to_layer(pd, layer) type(plot_data_t), intent(in) :: pd type(layer_t), intent(out) :: layer call build_mark_from_plot(pd, layer%mark) if (pd%plot_type == PLOT_TYPE_CONTOUR .or. & pd%plot_type == PLOT_TYPE_PCOLORMESH) then call build_field_from_plot(pd, layer%field) else call build_data_from_plot(pd, layer%data) layer%has_data = .true. end if if (allocated(pd%label)) then layer%encoding%color%value = pd%label layer%encoding%color%defined = .true. end if end subroutine convert_plot_to_layer subroutine build_mark_from_plot(pd, m) type(plot_data_t), intent(in) :: pd type(mark_t), intent(out) :: m character(len=7) :: hex select case (pd%plot_type) case (PLOT_TYPE_LINE, PLOT_TYPE_ERRORBAR) m%type = 'line' case (PLOT_TYPE_SCATTER) m%type = 'point' m%size = pd%scatter_size_default m%filled = .true. case (PLOT_TYPE_BAR, PLOT_TYPE_HISTOGRAM) m%type = 'bar' case (PLOT_TYPE_FILL) m%type = 'area' m%opacity = pd%fill_alpha case (PLOT_TYPE_CONTOUR) if (pd%fill_contours) then m%type = 'contour_filled' else m%type = 'contour' end if case (PLOT_TYPE_PCOLORMESH) m%type = 'pcolormesh' case default m%type = 'line' end select call rgb_to_hex(pd%color, hex) m%stroke = hex if (pd%line_width >= 0.0_wp) m%stroke_width = pd%line_width if (allocated(pd%linestyle)) then call linestyle_to_dash(pd%linestyle, m) end if end subroutine build_mark_from_plot subroutine build_data_from_plot(pd, d) type(plot_data_t), intent(in) :: pd type(data_t), intent(out) :: d integer :: n select case (pd%plot_type) case (PLOT_TYPE_BAR, PLOT_TYPE_HISTOGRAM) if (allocated(pd%bar_x) .and. & allocated(pd%bar_heights)) then n = min(size(pd%bar_x), size(pd%bar_heights)) allocate (d%columns(2)) d%columns(1)%field = 'x' d%columns(1)%values = pd%bar_x(1:n) d%columns(2)%field = 'y' d%columns(2)%values = pd%bar_heights(1:n) d%nrows = n end if case default if (allocated(pd%x) .and. allocated(pd%y)) then n = min(size(pd%x), size(pd%y)) allocate (d%columns(2)) d%columns(1)%field = 'x' d%columns(1)%values = pd%x(1:n) d%columns(2)%field = 'y' d%columns(2)%values = pd%y(1:n) d%nrows = n end if end select end subroutine build_data_from_plot subroutine build_field_from_plot(pd, fp) type(plot_data_t), intent(in) :: pd type(field_plot_t), intent(out) :: fp fp%defined = .true. if (allocated(pd%x_grid)) fp%x = pd%x_grid if (allocated(pd%y_grid)) fp%y = pd%y_grid if (allocated(pd%z_grid)) then fp%nrows = size(pd%z_grid, 1) fp%ncols = size(pd%z_grid, 2) allocate (fp%z(fp%nrows * fp%ncols)) fp%z = reshape(pd%z_grid, [fp%nrows * fp%ncols]) end if if (allocated(pd%contour_levels)) fp%levels = pd%contour_levels fp%colormap = trim(pd%colormap) fp%show_colorbar = pd%show_colorbar fp%show_colorbar_set = .true. end subroutine build_field_from_plot pure function make_channel(field, ctype) result(ch) character(len=*), intent(in) :: field, ctype type(channel_t) :: ch ch%field = field ch%type = ctype ch%defined = .true. end function make_channel subroutine rgb_to_hex(rgb, hex) real(wp), intent(in) :: rgb(3) character(len=7), intent(out) :: hex integer :: r, g, b r = max(0, min(255, nint(rgb(1) * 255.0_wp))) g = max(0, min(255, nint(rgb(2) * 255.0_wp))) b = max(0, min(255, nint(rgb(3) * 255.0_wp))) write (hex, '(a,3z2.2)') '#', r, g, b end subroutine rgb_to_hex subroutine linestyle_to_dash(ls, m) character(len=*), intent(in) :: ls type(mark_t), intent(inout) :: m select case (trim(ls)) case ('--', 'dashed') allocate (m%stroke_dash(2)) m%stroke_dash = [6.0_wp, 3.0_wp] case (':', 'dotted') allocate (m%stroke_dash(2)) m%stroke_dash = [2.0_wp, 3.0_wp] case ('-.', 'dashdot') allocate (m%stroke_dash(4)) m%stroke_dash = [6.0_wp, 3.0_wp, 2.0_wp, 3.0_wp] end select end subroutine linestyle_to_dash end module fortplot_spec_frontend_adapters