From 2b22dae75747c959fb171fcf83cc31239e580dde Mon Sep 17 00:00:00 2001 From: Simon Christ Date: Thu, 17 Jun 2021 09:29:39 +0200 Subject: [PATCH] Handle matrices of annotations and copy for `plot(plots)` (#3572) * add tests for series_annotations in layouts * add methods for matrices of annotations * defensive copy on plot(plots) * seperate in plot(plots) and plot!(plots) --- src/components.jl | 6 +++ src/plot.jl | 3 +- test/runtests.jl | 2 +- test/test_components.jl | 106 ++++++++++++++++++++++++---------------- 4 files changed, 74 insertions(+), 43 deletions(-) diff --git a/src/components.jl b/src/components.jl index 51914ffa..63be7b15 100644 --- a/src/components.jl +++ b/src/components.jl @@ -474,6 +474,11 @@ mutable struct SeriesAnnotations baseshape::Union{Shape, AbstractVector{Shape}, Nothing} scalefactor::Tuple end + +series_annotations(scalar) = series_annotations([scalar]) +function series_annotations(anns::AMat) + map(series_annotations, anns) +end function series_annotations(strs::AbstractVector, args...) fnt = font() shp = nothing @@ -570,6 +575,7 @@ end annotations(::Nothing) = [] annotations(anns::AVec) = anns +annotations(anns::AMat) = map(annotations, anns) annotations(anns) = Any[anns] annotations(sa::SeriesAnnotations) = sa diff --git a/src/plot.jl b/src/plot.jl index 914dbc9d..fb802774 100644 --- a/src/plot.jl +++ b/src/plot.jl @@ -91,7 +91,8 @@ end # build a new plot from existing plots # note: we split into plt1 and plts_tail so we can dispatch correctly -function plot(plt1::Plot, plts_tail::Plot...; kw...) +plot(plt1::Plot, plts_tail::Plot...; kw...) = plot!(deepcopy(plt1), deepcopy.(plts_tail)...; kw...) +function plot!(plt1::Plot, plts_tail::Plot...; kw...) @nospecialize plotattributes = KW(kw) RecipesPipeline.preprocess_attributes!(plotattributes) diff --git a/test/runtests.jl b/test/runtests.jl index 2383a931..997e1258 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -using Plots: guidefont +using Plots: guidefont, series_annotations import ImageMagick using VisualRegressionTests using Plots diff --git a/test/test_components.jl b/test/test_components.jl index 77510181..ce616075 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -2,58 +2,58 @@ using Plots, Test @testset "Shapes" begin @testset "Type" begin - square = Shape([(0,0.0),(1,0.0),(1,1.0),(0,1.0)]) - @test isa(square, Shape{Int64, Float64}) - @test coords(square) isa Tuple{Vector{S}, Vector{T}} where {T,S} + square = Shape([(0, 0.0), (1, 0.0), (1, 1.0), (0, 1.0)]) + @test isa(square, Shape{Int64,Float64}) + @test coords(square) isa Tuple{Vector{S},Vector{T}} where {T,S} end @testset "Copy" begin - square = Shape([(0,0),(1,0),(1,1),(0,1)]) + square = Shape([(0, 0), (1, 0), (1, 1), (0, 1)]) square2 = Shape(square) @test square2.x == square.x @test square2.y == square.y end @testset "Center" begin - square = Shape([(0,0),(1,0),(1,1),(0,1)]) - @test Plots.center(square) == (0.5,0.5) + square = Shape([(0, 0), (1, 0), (1, 1), (0, 1)]) + @test Plots.center(square) == (0.5, 0.5) end @testset "Translate" begin - square = Shape([(0,0),(1,0),(1,1),(0,1)]) - squareUp = Shape([(0,1),(1,1),(1,2),(0,2)]) - squareUpRight = Shape([(1,1),(2,1),(2,2),(1,2)]) + square = Shape([(0, 0), (1, 0), (1, 1), (0, 1)]) + squareUp = Shape([(0, 1), (1, 1), (1, 2), (0, 2)]) + squareUpRight = Shape([(1, 1), (2, 1), (2, 2), (1, 2)]) - @test Plots.translate(square,0,1).x == squareUp.x - @test Plots.translate(square,0,1).y == squareUp.y + @test Plots.translate(square, 0, 1).x == squareUp.x + @test Plots.translate(square, 0, 1).y == squareUp.y - @test Plots.center(translate!(square,1)) == (1.5,1.5) + @test Plots.center(translate!(square, 1)) == (1.5, 1.5) end @testset "Rotate" begin # 2 radians rotation matrix R2 = [cos(2) sin(2); -sin(2) cos(2)] coords = [0 0; 1 0; 1 1; 0 1]' - coordsRotated2 = R2*(coords.-0.5).+0.5 + coordsRotated2 = R2 * (coords .- 0.5) .+ 0.5 - square = Shape([(0,0),(1,0),(1,1),(0,1)]) + square = Shape([(0, 0), (1, 0), (1, 1), (0, 1)]) # make a new, rotated square square2 = Plots.rotate(square, -2) - @test square2.x ≈ coordsRotated2[1,:] - @test square2.y ≈ coordsRotated2[2,:] + @test square2.x ≈ coordsRotated2[1, :] + @test square2.y ≈ coordsRotated2[2, :] # unrotate the new square in place rotate!(square2, 2) - @test square2.x ≈ coords[1,:] - @test square2.y ≈ coords[2,:] + @test square2.x ≈ coords[1, :] + @test square2.y ≈ coords[2, :] end @testset "Plot" begin ang = range(0, 2π, length = 60) - ellipse(x, y, w, h) = Shape(w*sin.(ang).+x, h*cos.(ang).+y) - myshapes = [ellipse(x,rand(),rand(),rand()) for x = 1:4] - @test coords(myshapes) isa Tuple{Vector{Vector{S}}, Vector{Vector{T}}} where {T,S} + ellipse(x, y, w, h) = Shape(w * sin.(ang) .+ x, h * cos.(ang) .+ y) + myshapes = [ellipse(x, rand(), rand(), rand()) for x = 1:4] + @test coords(myshapes) isa Tuple{Vector{Vector{S}},Vector{Vector{T}}} where {T,S} local p @test_nowarn p = plot(myshapes) @test p[1][1][:seriestype] == :shape @@ -62,7 +62,7 @@ end @testset "Brush" begin @testset "Colors" begin - baseline = brush(1, RGB(0,0,0)) + baseline = brush(1, RGB(0, 0, 0)) @test brush(:black) == baseline @test brush("black") == baseline end @@ -76,7 +76,7 @@ end end @testset "Bad Argument" begin # using test_logs because test_warn seems to not work anymore - @test_logs (:warn,"Unused brush arg: nothing (Nothing)") begin + @test_logs (:warn, "Unused brush arg: nothing (Nothing)") begin brush(nothing) end end @@ -84,44 +84,68 @@ end @testset "Fonts" begin @testset "Scaling" begin - sizesToCheck = [:titlefontsize, :legendfontsize, :legendtitlefontsize, - :xtickfontsize, :ytickfontsize, :ztickfontsize, - :xguidefontsize, :yguidefontsize, :zguidefontsize,] + sizesToCheck = [ + :titlefontsize, + :legendfontsize, + :legendtitlefontsize, + :xtickfontsize, + :ytickfontsize, + :ztickfontsize, + :xguidefontsize, + :yguidefontsize, + :zguidefontsize, + ] # get inital font sizes - initialSizes = [Plots.default(s) for s in sizesToCheck ] + initialSizes = [Plots.default(s) for s in sizesToCheck] #scale up font sizes scalefontsizes(2) # get inital font sizes - doubledSizes = [Plots.default(s) for s in sizesToCheck ] + doubledSizes = [Plots.default(s) for s in sizesToCheck] - @test doubledSizes == initialSizes*2 + @test doubledSizes == initialSizes * 2 # reset font sizes resetfontsizes() - finalSizes = [Plots.default(s) for s in sizesToCheck ] + finalSizes = [Plots.default(s) for s in sizesToCheck] @test finalSizes == initialSizes end end @testset "Series Annotations" begin - square = Shape([(0,0),(1,0),(1,1),(0,1)]) - @test_logs (:warn,"Unused SeriesAnnotations arg: triangle (Symbol)") begin - p = plot([1,2,3], - series_annotations=(["a"], - 2, # pass a scale factor - (1,4), # pass two scale factors (overwrites first one) - square, # pass a shape - font(:courier), # pass an annotation font - :triangle # pass an incorrect argument - )) + square = Shape([(0, 0), (1, 0), (1, 1), (0, 1)]) + @test_logs (:warn, "Unused SeriesAnnotations arg: triangle (Symbol)") begin + p = plot( + [1, 2, 3], + series_annotations = ( + ["a"], + 2, # pass a scale factor + (1, 4), # pass two scale factors (overwrites first one) + square, # pass a shape + font(:courier), # pass an annotation font + :triangle, # pass an incorrect argument + ), + ) sa = p.series_list[1].plotattributes[:series_annotations] @test sa.strs == ["a"] @test sa.font.family == "courier" @test sa.baseshape == square - @test sa.scalefactor == (1,4) + @test sa.scalefactor == (1, 4) end + spl = scatter( + 4.53 .* [1/1 1/2 1/3 1/4 1/5], + [0 0 0 0 0], + layout = (5, 1), + ylims = (-1.1, 1.1), + xlims = (0, 5), + series_annotations = permutedims([["1/1"],["1/2"],["1/3"],["1/4"],["1/5"]]), + ) + @test spl.series_list[1].plotattributes[:series_annotations].strs == ["1/1"] + @test spl.series_list[2].plotattributes[:series_annotations].strs == ["1/2"] + @test spl.series_list[3].plotattributes[:series_annotations].strs == ["1/3"] + @test spl.series_list[4].plotattributes[:series_annotations].strs == ["1/4"] + @test spl.series_list[5].plotattributes[:series_annotations].strs == ["1/5"] end