From 9ed66ef2ce9a82f59b5df26533a9f2f3b3d553b3 Mon Sep 17 00:00:00 2001 From: Connor Robertson Date: Mon, 29 Apr 2024 16:46:55 -0700 Subject: [PATCH 1/2] add plot_layout basic functionality: nrow, ncol, width, height --- src/TidierPlots.jl | 1 + src/patchwork.jl | 93 +++++++++++++++++++++++- src/structs.jl | 10 ++- test/test_patchwork.jl | 159 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 260 insertions(+), 3 deletions(-) diff --git a/src/TidierPlots.jl b/src/TidierPlots.jl index 8c9c070..8976a84 100644 --- a/src/TidierPlots.jl +++ b/src/TidierPlots.jl @@ -75,6 +75,7 @@ export draw_ggplot, ggplot, ggsave export TidierPlots_set export @aes, @es, aes export geom_template +export plot_layout # geoms diff --git a/src/patchwork.jl b/src/patchwork.jl index a324694..9bef930 100644 --- a/src/patchwork.jl +++ b/src/patchwork.jl @@ -20,10 +20,101 @@ function combine_plots(catfunc::Function, args::Union{GGPlot,GGPlotGrid}...) end # + -> side by side -Base.:+(args::Union{GGPlot, GGPlotGrid}...) = combine_plots(hcat, args...) +function Base.:+(args::Union{GGPlot, GGPlotGrid, GGPlotGridSpec}...) + if any(isa.(args, GGPlotGridSpec)) + return add_gridspec(args...) + else + return combine_plots(hcat, args...) + end +end # | -> side by side Base.:|(args::Union{GGPlot, GGPlotGrid}...) = combine_plots(hcat, args...) # / -> p1 above p2 Base.:/(args::Union{GGPlot, GGPlotGrid}...) = combine_plots(vcat, args...) + +# Define plot layout +function plot_layout(;ncol=0, nrow=0, byrow=false, widths=Int[], heights=Int[]) + return GGPlotGridSpec(ncol, nrow, byrow, widths, heights) +end + +function add_gridspec(args::Union{GGPlot, GGPlotGrid, GGPlotGridSpec}...) + # Separate plots from gridspec + plots = Union{GGPlot,GGPlotGrid}[] + gridspec = nothing + for arg in args + if arg isa GGPlotGridSpec + # Merge gridspecs + if isnothing(gridspec) + gridspec = arg + else + for k in fieldnames(GGPlotGridSpec) + setfield!(gridspec, getfield(arg, k)) + end + end + else + push!(plots, arg) + end + end + + # Arrange plots + nplots = length(plots) + ncol = gridspec.ncol + nrow = gridspec.nrow + if (nrow == 0) & (ncol == 0) + # Default: more cols than rows + nrow = Int(round(sqrt(nplots))) + ncol = Int(ceil(sqrt(nplots))) + elseif (nrow == 0) + # As many rows as needed + nrow = Int(ceil(nplots / ncol)) + elseif (ncol == 0) + # As many cols as needed + ncol = Int(ceil(nplots / nrow)) + end + layout = Pair{Tuple{Int64,Int64}, Union{Makie.BlockSpec, Makie.GridLayoutSpec}}[] + plot_i = 1 + if gridspec.byrow + for j in 1:ncol, i in 1:nrow + if plot_i <= nplots + tmp_plot = plots[plot_i] + axis = tmp_plot isa GGPlot ? SpecApi.Axis(tmp_plot) : tmp_plot.grid + push!(layout, (i, j) => axis) + plot_i += 1 + end + end + else + for i in 1:nrow, j in 1:ncol + if plot_i <= nplots + tmp_plot = plots[plot_i] + axis = tmp_plot isa GGPlot ? SpecApi.Axis(tmp_plot) : tmp_plot.grid + push!(layout, (i, j) => axis) + plot_i += 1 + end + end + end + + # Adjust sizes + widths = isempty(gridspec.widths) ? ones(ncol) : gridspec.widths + heights = isempty(gridspec.heights) ? ones(nrow) : gridspec.heights + total_width = sum(widths) + total_height = sum(heights) + all_plots = GGPlot[] + for p in plots + if isa(p, GGPlot) + push!(all_plots, p) + else + push!(all_plots, p.plots...) + end + end + return GGPlotGrid( + all_plots, + SpecApi.GridLayout( + layout, + colsizes = [Relative(w/total_width) for w in widths], + rowsizes = [Relative(h/total_height) for h in heights] + ) + ) +end + diff --git a/src/structs.jl b/src/structs.jl index fb78ea8..ff5e70e 100644 --- a/src/structs.jl +++ b/src/structs.jl @@ -35,4 +35,12 @@ end struct GGPlotGrid plots::Vector{GGPlot} grid::Any -end \ No newline at end of file +end + +struct GGPlotGridSpec + ncol::Int + nrow::Int + byrow::Bool + widths::Vector{Int} + heights::Vector{Int} +end diff --git a/test/test_patchwork.jl b/test/test_patchwork.jl index 0739db4..5638d0e 100644 --- a/test/test_patchwork.jl +++ b/test/test_patchwork.jl @@ -65,4 +65,161 @@ end @test plot_images_equal((t | t) / t, m21) end -end \ No newline at end of file + +@testset "plot layout organization" begin + t = ggplot(penguins) + + geom_point(@aes(x = bill_length_mm, y = bill_depth_mm)) + t2 = ggplot(penguins) + + geom_point(@aes(x = bill_depth_mm, y = bill_length_mm)) + test_ax = Makie.SpecApi.Axis( + plots = [Makie.PlotSpec( + :Scatter, penguins.bill_length_mm, penguins.bill_depth_mm + )] + ) + test_ax2 = Makie.SpecApi.Axis( + plots = [Makie.PlotSpec( + :Scatter, penguins.bill_depth_mm, penguins.bill_length_mm + )] + ) + # 2x2 grid + m22 = Makie.plot( + Makie.SpecApi.GridLayout([ + test_ax test_ax2; + test_ax test_ax2 + ], + ) + ) + @test plot_images_equal(t + t2 + t + t2 + plot_layout(), m22) + @test plot_images_equal(t + t2 + t + t2 + plot_layout(;ncol=2,nrow=2), m22) + @test plot_images_equal(t + t + t2 + t2 + plot_layout(;ncol=2,nrow=2,byrow=true), m22) + @test plot_images_equal(t + t2 + t + t2 + plot_layout(;ncol=2,nrow=0), m22) + + # 2x2 grid (missing bottom right) + m21 = Makie.plot( + Makie.SpecApi.GridLayout([ + (1,1) => test_ax, + (1,2) => test_ax2, + (2,1) => test_ax + ] + ) + ) + @test plot_images_equal(t + t2 + t + plot_layout(nrow=2,ncol=2), m21) + @test plot_images_equal(t + t2 + t + plot_layout(nrow=2), m21) + @test plot_images_equal(t + t2 + t + plot_layout(), m21) + + # 1x3 grid + m13 = Makie.plot( + Makie.SpecApi.GridLayout([ + (1,1) => test_ax, + (1,2) => test_ax, + (1,3) => test_ax + ] + ) + ) + @test plot_images_equal(t + t + t + plot_layout(;nrow=1), m13) +end + +@testset "plot layout sizes" begin + t = ggplot(penguins) + + geom_point(@aes(x = bill_length_mm, y = bill_depth_mm)) + t2 = ggplot(penguins) + + geom_point(@aes(x = bill_depth_mm, y = bill_length_mm)) + test_ax = Makie.SpecApi.Axis( + plots = [Makie.PlotSpec( + :Scatter, penguins.bill_length_mm, penguins.bill_depth_mm + )] + ) + test_ax2 = Makie.SpecApi.Axis( + plots = [Makie.PlotSpec( + :Scatter, penguins.bill_depth_mm, penguins.bill_length_mm + )] + ) + # 2x2 grid (variable columns widths) + m22c = Makie.plot( + Makie.SpecApi.GridLayout([ + test_ax test_ax2; + test_ax test_ax2 + ], + colsizes = [Relative(1/3), Relative(2/3)] + ) + ) + @test plot_images_equal(t + t2 + t + t2 + plot_layout(;widths=[1,2]), m22c) + + # 2x2 grid (missing bottom right) + m21 = Makie.plot( + Makie.SpecApi.GridLayout([ + (1,1) => test_ax, + (1,2) => test_ax2, + (2,1) => test_ax + ], + colsizes = [Relative(1/3), Relative(2/3)] + ) + ) + @test plot_images_equal(t + t2 + t + plot_layout(;widths=[1,2]), m21) + + # 2x2 grid (variable row heights) + m22r = Makie.plot( + Makie.SpecApi.GridLayout([ + test_ax test_ax2; + test_ax test_ax2 + ], + rowsizes = [Relative(1/3), Relative(2/3)] + ) + ) + @test plot_images_equal(t + t2 + t + t2 + plot_layout(;heights=[1,2]), m22r) + + # 1x3 grid + m13 = Makie.plot( + Makie.SpecApi.GridLayout([ + (1,1) => test_ax, + (1,2) => test_ax, + (1,3) => test_ax + ], + colsizes = [Relative(1/6), Relative(2/6), Relative(3/6)] + ) + ) + @test plot_images_equal(t + t + t + plot_layout(;nrow=1, widths=[1,2,3]), m13) + + # 2x3 grid variable height and width + m23 = Makie.plot( + Makie.SpecApi.GridLayout([ + (1,1) => test_ax, + (1,2) => test_ax, + (1,3) => test_ax, + (2,1) => test_ax2, + (2,2) => test_ax2, + (2,3) => test_ax2 + ], + colsizes = [Relative(1/6), Relative(2/6), Relative(3/6)], + rowsizes = [Relative(1/5), Relative(4/5)] + ) + ) + @test plot_images_equal(t + t + t + t2 + t2 + t2 + plot_layout(;nrow=2, widths=[1,2,3], heights=[1,4]), m23) +end + +@testset "plot layout grids" begin + t = ggplot(penguins) + + geom_point(@aes(x = bill_length_mm, y = bill_depth_mm)) + t2 = ggplot(penguins) + + geom_point(@aes(x = bill_depth_mm, y = bill_length_mm)) + test_ax = Makie.SpecApi.Axis( + plots = [Makie.PlotSpec( + :Scatter, penguins.bill_length_mm, penguins.bill_depth_mm + )] + ) + test_ax2 = Makie.SpecApi.Axis( + plots = [Makie.PlotSpec( + :Scatter, penguins.bill_depth_mm, penguins.bill_length_mm + )] + ) + m22c = Makie.plot( + Makie.SpecApi.GridLayout([ + Makie.SpecApi.GridLayout([test_ax test_ax]) test_ax2; + test_ax test_ax2 + ], + colsizes = [Relative(1/3), Relative(2/3)] + ) + ) + @test plot_images_equal((t | t) + t2 + t + t2 + plot_layout(;widths=[1,2]), m22c) +end +end From 2bb9d06409e07de021af7c9f88ab7b0338580958 Mon Sep 17 00:00:00 2001 From: Connor Robertson Date: Mon, 13 May 2024 11:11:11 -0700 Subject: [PATCH 2/2] cleanup patchwork tests kwargs --- test/test_patchwork.jl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/test_patchwork.jl b/test/test_patchwork.jl index 5638d0e..caecba8 100644 --- a/test/test_patchwork.jl +++ b/test/test_patchwork.jl @@ -90,9 +90,9 @@ end ) ) @test plot_images_equal(t + t2 + t + t2 + plot_layout(), m22) - @test plot_images_equal(t + t2 + t + t2 + plot_layout(;ncol=2,nrow=2), m22) - @test plot_images_equal(t + t + t2 + t2 + plot_layout(;ncol=2,nrow=2,byrow=true), m22) - @test plot_images_equal(t + t2 + t + t2 + plot_layout(;ncol=2,nrow=0), m22) + @test plot_images_equal(t + t2 + t + t2 + plot_layout(ncol=2,nrow=2), m22) + @test plot_images_equal(t + t + t2 + t2 + plot_layout(ncol=2,nrow=2,byrow=true), m22) + @test plot_images_equal(t + t2 + t + t2 + plot_layout(ncol=2,nrow=0), m22) # 2x2 grid (missing bottom right) m21 = Makie.plot( @@ -116,7 +116,7 @@ end ] ) ) - @test plot_images_equal(t + t + t + plot_layout(;nrow=1), m13) + @test plot_images_equal(t + t + t + plot_layout(nrow=1), m13) end @testset "plot layout sizes" begin @@ -143,7 +143,7 @@ end colsizes = [Relative(1/3), Relative(2/3)] ) ) - @test plot_images_equal(t + t2 + t + t2 + plot_layout(;widths=[1,2]), m22c) + @test plot_images_equal(t + t2 + t + t2 + plot_layout(widths=[1,2]), m22c) # 2x2 grid (missing bottom right) m21 = Makie.plot( @@ -155,7 +155,7 @@ end colsizes = [Relative(1/3), Relative(2/3)] ) ) - @test plot_images_equal(t + t2 + t + plot_layout(;widths=[1,2]), m21) + @test plot_images_equal(t + t2 + t + plot_layout(widths=[1,2]), m21) # 2x2 grid (variable row heights) m22r = Makie.plot( @@ -166,7 +166,7 @@ end rowsizes = [Relative(1/3), Relative(2/3)] ) ) - @test plot_images_equal(t + t2 + t + t2 + plot_layout(;heights=[1,2]), m22r) + @test plot_images_equal(t + t2 + t + t2 + plot_layout(heights=[1,2]), m22r) # 1x3 grid m13 = Makie.plot( @@ -178,7 +178,7 @@ end colsizes = [Relative(1/6), Relative(2/6), Relative(3/6)] ) ) - @test plot_images_equal(t + t + t + plot_layout(;nrow=1, widths=[1,2,3]), m13) + @test plot_images_equal(t + t + t + plot_layout(nrow=1, widths=[1,2,3]), m13) # 2x3 grid variable height and width m23 = Makie.plot( @@ -194,7 +194,7 @@ end rowsizes = [Relative(1/5), Relative(4/5)] ) ) - @test plot_images_equal(t + t + t + t2 + t2 + t2 + plot_layout(;nrow=2, widths=[1,2,3], heights=[1,4]), m23) + @test plot_images_equal(t + t + t + t2 + t2 + t2 + plot_layout(nrow=2, widths=[1,2,3], heights=[1,4]), m23) end @testset "plot layout grids" begin @@ -220,6 +220,6 @@ end colsizes = [Relative(1/3), Relative(2/3)] ) ) - @test plot_images_equal((t | t) + t2 + t + t2 + plot_layout(;widths=[1,2]), m22c) + @test plot_images_equal((t | t) + t2 + t + t2 + plot_layout(widths=[1,2]), m22c) end end