diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7edb8f79..143e4b3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: if: "!contains(github.event.head_commit.message, '[skip ci]')" env: GKS_ENCODING: "utf8" - GKSwstype: "100" + GKSwstype: "nul" name: Julia ${{ matrix.version }} - ${{ matrix.os }} runs-on: ${{ matrix.os }} @@ -55,19 +55,16 @@ jobs: ${{ runner.os }}-test- ${{ runner.os }}- - ## maybe required if we ever want to run graphical tests for plotly - # OS Dependencies - # - name: Ubuntu OS dependencies - # if: startsWith(matrix.os,'ubuntu') - # run: | - # ./test/install_wkhtmltoimage.sh - # TESTCMD - name: Default TESTCMD run: echo "TESTCMD=julia" >> $GITHUB_ENV - name: Ubuntu TESTCMD if: startsWith(matrix.os,'ubuntu') - run: echo "TESTCMD=xvfb-run --auto-servernum julia" >> $GITHUB_ENV + run: | + echo "TESTCMD=xvfb-run --auto-servernum julia" >> $GITHUB_ENV + sudo apt-get -y update + sudo apt-get -y install gnuplot poppler-utils texlive-{latex-base,latex-extra,luatex} + sudo fc-cache -vr # Julia Dependencies - name: Install Julia dependencies @@ -82,7 +79,9 @@ jobs: # Codecov - uses: julia-actions/julia-processcoverage@v1 + if: startsWith(matrix.os,'ubuntu') - uses: codecov/codecov-action@v2 + if: startsWith(matrix.os,'ubuntu') with: file: lcov.info diff --git a/Project.toml b/Project.toml index cee95ab4..b8a5f15e 100644 --- a/Project.toml +++ b/Project.toml @@ -68,10 +68,12 @@ julia = "1.6" Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" +Gaston = "4b11ee91-296f-5714-9832-002c20994614" Gtk = "4c0ca9eb-093a-5379-98c5-f87ac0bbbf44" HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0" +InspectDR = "d0351b0e-4b05-5898-87b3-e2a8edfddd1d" LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433" OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" PGFPlotsX = "8314cec4-20b6-5062-9cdb-752b83310925" @@ -88,4 +90,4 @@ UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228" VisualRegressionTests = "34922c18-7c2a-561c-bac1-01e79b2c4c92" [targets] -test = ["Colors", "Distributions", "FileIO", "Gtk", "ImageMagick", "Images", "LibGit2", "OffsetArrays", "PGFPlotsX", "PlotlyJS", "PlotlyBase", "PyPlot", "HDF5", "RDatasets", "StableRNGs", "StaticArrays", "StatsPlots", "Test", "TestImages", "UnicodePlots", "VisualRegressionTests"] +test = ["Colors", "Distributions", "FileIO", "Gaston", "Gtk", "ImageMagick", "Images", "InspectDR", "LibGit2", "OffsetArrays", "PGFPlotsX", "PlotlyJS", "PlotlyBase", "PyPlot", "HDF5", "RDatasets", "StableRNGs", "StaticArrays", "StatsPlots", "Test", "TestImages", "UnicodePlots", "VisualRegressionTests"] diff --git a/src/backends/deprecated/pgfplots.jl b/src/backends/deprecated/pgfplots.jl index 96c85e03..f060ea9d 100644 --- a/src/backends/deprecated/pgfplots.jl +++ b/src/backends/deprecated/pgfplots.jl @@ -3,7 +3,7 @@ # significant contributions by: @pkofod # -------------------------------------------------------------------------------------- - +# COV_EXCL_START const _pgfplots_linestyles = KW( :solid => "solid", :dash => "dashed", @@ -740,3 +740,5 @@ function _display(plt::Plot{PGFPlotsBackend}) # cleanup PGFPlots.cleanup(plt.o) end + +# COV_EXCL_STOP diff --git a/src/backends/unicodeplots.jl b/src/backends/unicodeplots.jl index e819665b..3f6d99d8 100644 --- a/src/backends/unicodeplots.jl +++ b/src/backends/unicodeplots.jl @@ -269,27 +269,25 @@ function png(plt::Plot{UnicodePlotsBackend}, fn::AbstractString) else img = UnicodePlots.png_image(plt.o[sps += 1]) canvas_type = eltype(img) - sz = size(img) - s1[r, c] = sz[1] - s2[r, c] = sz[2] + h, w = size(img) + s1[r, c] = h + s2[r, c] = w push!(imgs, img) end end end if canvas_type !== nothing - rows = maximum(sum(s1; dims = 1)) - cols = maximum(sum(s2; dims = 2)) - img = zeros(canvas_type, rows, cols) m1 = maximum(s1; dims = 2) m2 = maximum(s2; dims = 1) + img = zeros(canvas_type, sum(m1), sum(m2)) sps = 0 n1 = 1 for r in 1:nr n2 = 1 for c in 1:nc sp = imgs[sps += 1] - sz = size(sp) - img[n1:(n1 + (sz[1] - 1)), n2:(n2 + (sz[2] - 1))] = sp + h, w = size(sp) + img[n1:(n1 + (h - 1)), n2:(n2 + (w - 1))] = sp n2 += m2[c] end n1 += m1[r] diff --git a/src/components.jl b/src/components.jl index f82500f5..c3095d51 100644 --- a/src/components.jl +++ b/src/components.jl @@ -174,20 +174,19 @@ end translate(shape::Shape, x::Real, y::Real = x) = translate!(deepcopy(shape), x, y) -rotate_x(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real) = - ((x - centerx) * cos(Θ) - (y - centery) * sin(Θ) + centerx) +rotate_x(x::Real, y::Real, θ::Real, centerx::Real, centery::Real) = + ((x - centerx) * cos(θ) - (y - centery) * sin(θ) + centerx) -rotate_y(x::Real, y::Real, Θ::Real, centerx::Real, centery::Real) = - ((y - centery) * cos(Θ) + (x - centerx) * sin(Θ) + centery) +rotate_y(x::Real, y::Real, θ::Real, centerx::Real, centery::Real) = + ((y - centery) * cos(θ) + (x - centerx) * sin(θ) + centery) -rotate(x::Real, y::Real, θ::Real, c = center(shape)) = - (rotate_x(x, y, Θ, c...), rotate_y(x, y, Θ, c...)) +rotate(x::Real, y::Real, θ::Real, c) = (rotate_x(x, y, θ, c...), rotate_y(x, y, θ, c...)) -function rotate!(shape::Shape, Θ::Real, c = center(shape)) +function rotate!(shape::Shape, θ::Real, c = center(shape)) x, y = coords(shape) for i in eachindex(x) - xi = rotate_x(x[i], y[i], Θ, c...) - yi = rotate_y(x[i], y[i], Θ, c...) + xi = rotate_x(x[i], y[i], θ, c...) + yi = rotate_y(x[i], y[i], θ, c...) x[i], y[i] = xi, yi end shape diff --git a/src/examples.jl b/src/examples.jl index 76842d58..04e3cdb1 100644 --- a/src/examples.jl +++ b/src/examples.jl @@ -169,7 +169,7 @@ const _examples = PlotExample[ )], ), PlotExample( # 9 - "", + "Build plot in pieces (continued)", "and add to it later.", [:( begin @@ -314,7 +314,7 @@ const _examples = PlotExample[ ], ), PlotExample( # 18 - "", + "Adding to subplots (continued)", "", [:( begin diff --git a/src/legend.jl b/src/legend.jl index 0d3c3def..4bda6e88 100644 --- a/src/legend.jl +++ b/src/legend.jl @@ -4,8 +4,7 @@ legend_pos_from_angle(theta, xmin, xcenter, xmax, ymin, ycenter, ymax, inout) ``` Return `(x,y)` at an angle `theta` degrees from -`(xcenter,ycenter)` on a rectangle defined by (`xmin`, -`xmax`, `ymin`, `ymax`). +`(xcenter,ycenter)` on a rectangle defined by (`xmin`, `xmax`, `ymin`, `ymax`). """ function legend_pos_from_angle(theta, xmin, xcenter, xmax, ymin, ycenter, ymax) (s, c) = sincosd(theta) diff --git a/src/shorthands.jl b/src/shorthands.jl index d8963443..fe83eeee 100644 --- a/src/shorthands.jl +++ b/src/shorthands.jl @@ -472,10 +472,10 @@ yflip!(flip::Bool = true; kw...) = plot!(; yflip = flip, kw...) "Specify x axis attributes for an existing plot" xaxis!(args...; kw...) = plot!(; xaxis = args, kw...) +xgrid!(args...; kw...) = plot!(; xgrid = args, kw...) "Specify y axis attributes for an existing plot" yaxis!(args...; kw...) = plot!(; yaxis = args, kw...) -xgrid!(args...; kw...) = plot!(; xgrid = args, kw...) ygrid!(args...; kw...) = plot!(; ygrid = args, kw...) @specialize diff --git a/src/utils.jl b/src/utils.jl index 325b6053..8078f393 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -377,8 +377,7 @@ nanappend!(a::AbstractVector, b) = (push!(a, NaN); append!(a, b)) function nansplit(v::AVec) vs = Vector{eltype(v)}[] while true - idx = findfirst(isnan, v) - if idx <= 0 + if (idx = findfirst(isnan, v)) === nothing # no nans push!(vs, v) break diff --git a/test/runtests.jl b/test/runtests.jl index 95833810..71de7769 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -37,6 +37,276 @@ end Plots.use_local_dependencies[] = temp end +@testset "Utils" begin + zipped = ( + [(1, 2)], + [("a", "b")], + [(1, "a"), (2, "b")], + [(1, 2), (3, 4)], + [(1, 2, 3), (3, 4, 5)], + [(1, 2, 3, 4), (3, 4, 5, 6)], + [(1, 2.0), (missing, missing)], + [(1, missing), (missing, "a")], + [(missing, missing)], + [(missing, missing, missing), ("a", "b", "c")], + ) + for z in zipped + @test isequal(collect(zip(Plots.RecipesPipeline.unzip(z)...)), z) + @test isequal( + collect(zip(Plots.RecipesPipeline.unzip(GeometryBasics.Point.(z))...)), + z, + ) + end + op1 = Plots.process_clims((1.0, 2.0)) + op2 = Plots.process_clims((1, 2.0)) + data = randn(100, 100) + @test op1(data) == op2(data) + @test Plots.process_clims(nothing) == + Plots.process_clims(missing) == + Plots.process_clims(:auto) + + @test (==)( + Plots.texmath2unicode( + raw"Equation $y = \alpha \cdot x + \beta$ and eqn $y = \sin(x)^2$", + ), + raw"Equation y = α ⋅ x + β and eqn y = sin(x)²", + ) + + @test Plots.isvector([1, 2]) + @test !Plots.isvector(nothing) + @test Plots.ismatrix([1 2; 3 4]) + @test !Plots.ismatrix(nothing) + @test Plots.isscalar(1.0) + @test !Plots.isscalar(nothing) + @test Plots.tovec([]) isa AbstractVector + @test Plots.tovec(nothing) isa AbstractVector + @test Plots.anynan(1, 3, (1, NaN, 3)) + @test Plots.allnan(1, 2, (NaN, NaN, 1)) + @test Plots.makevec([]) isa AbstractVector + @test Plots.makevec(1) isa AbstractVector + @test Plots.maketuple(1) == (1, 1) + @test Plots.maketuple((1, 1)) == (1, 1) + @test Plots.ok(1, 2) + @test !Plots.ok(1, 2, NaN) + @test Plots.ok((1, 2, 3)) + @test !Plots.ok((1, 2, NaN)) + @test Plots.nansplit([1, 2, NaN, 3, 4]) == [[1.0, 2.0], [3.0, 4.0]] + @test Plots.nanvcat([1, NaN]) |> length == 4 + + @test Plots.nop() === nothing + @test_throws ErrorException Plots.notimpl() + + @test Plots.inch2px(1) isa AbstractFloat + @test Plots.px2inch(1) isa AbstractFloat + @test Plots.inch2mm(1) isa AbstractFloat + @test Plots.mm2inch(1) isa AbstractFloat + @test Plots.px2mm(1) isa AbstractFloat + @test Plots.mm2px(1) isa AbstractFloat + + p = plot() + @test xlims() isa Tuple + @test ylims() isa Tuple + @test zlims() isa Tuple + + Plots.makekw(foo = 1, bar = 2) isa Dict + + @test_throws ErrorException Plots.inline() + @test_throws ErrorException Plots._do_plot_show(plot(), :inline) + @test_throws ErrorException Plots.dumpcallstack() + + Plots.debugplots(true) + Plots.debugplots(false) + Plots.debugshow(devnull, nothing) + Plots.debugshow(devnull, [1]) + + p = plot(1) + push!(p, 1.5) + push!(p, 1, 1.5) + # append!(p, [1., 2.]) + append!(p, 1, 2.5, 2.5) + push!(p, (1.5, 2.5)) + push!(p, 1, (1.5, 2.5)) + append!(p, (1.5, 2.5)) + append!(p, 1, (1.5, 2.5)) + + p = plot([1, 2, 3], [4, 5, 6]) + @test Plots.xmin(p) == 1 + @test Plots.xmax(p) == 3 + @test Plots.ignorenan_extrema(p) == (1, 3) + + @test Plots.get_attr_symbol(:x, "lims") == :xlims + @test Plots.get_attr_symbol(:x, :lims) == :xlims + + @testset "NaN-separated Segments" begin + segments(args...) = collect(iter_segments(args...)) + + nan10 = fill(NaN, 10) + @test segments(11:20) == [1:10] + @test segments([NaN]) == [] + @test segments(nan10) == [] + @test segments([nan10; 1:5]) == [11:15] + @test segments([1:5; nan10]) == [1:5] + @test segments([nan10; 1:5; nan10; 1:5; nan10]) == [11:15, 26:30] + @test segments([NaN; 1], 1:10) == [2:2, 4:4, 6:6, 8:8, 10:10] + @test segments([nan10; 1:15], [1:15; nan10]) == [11:15] + end +end + +@testset "Axes" begin + p = plot() + axis = p.subplots[1][:xaxis] + @test typeof(axis) == Plots.Axis + @test Plots.discrete_value!(axis, "HI") == (0.5, 1) + @test Plots.discrete_value!(axis, :yo) == (1.5, 2) + @test Plots.ignorenan_extrema(axis) == (0.5, 1.5) + @test axis[:discrete_map] == Dict{Any,Any}(:yo => 2, "HI" => 1) + + Plots.discrete_value!(axis, ["x$i" for i in 1:5]) + Plots.discrete_value!(axis, ["x$i" for i in 0:2]) + @test Plots.ignorenan_extrema(axis) == (0.5, 7.5) +end + +@testset "NoFail" begin + # ensure backend with tested display + @test unicodeplots() == Plots.UnicodePlotsBackend() + @test backend() == Plots.UnicodePlotsBackend() + + dsp = TextDisplay(IOContext(IOBuffer(), :color => true)) + + @testset "plot" begin + for plt in [ + histogram([1, 0, 0, 0, 0, 0]), + plot([missing]), + plot([missing, missing]), + plot(fill(missing, 10)), + plot([missing; 1:4]), + plot([fill(missing, 10); 1:4]), + plot([1 1; 1 missing]), + plot(["a" "b"; missing "d"], [1 2; 3 4]), + ] + display(dsp, plt) + end + @test_nowarn plot(x -> x^2, 0, 2) + end + + @testset "bar" begin + p = bar([3, 2, 1], [1, 2, 3]) + @test p isa Plots.Plot + @test display(dsp, p) isa Nothing + end + + @testset "gui" begin + open(tempname(), "w") do io + redirect_stdout(io) do + gui(plot()) + end + end + end +end + +@testset "Coverage" begin + @testset "themes" begin + p = showtheme(:dark) + @test p isa Plots.Plot + end + + @testset "plotattr" begin + tmp = tempname() + open(tmp, "w") do io + redirect_stdout(io) do + plotattr("seriestype") + plotattr(:Plot) + plotattr() + end + end + str = join(readlines(tmp), "") + @test occursin("seriestype", str) + @test occursin("Plot attributes", str) + end + + @testset "legend" begin + @test isa( + Plots.legend_pos_from_angle(20, 0.0, 0.5, 1.0, 0.0, 0.5, 1.0), + NTuple{2,<:AbstractFloat}, + ) + @test Plots.legend_anchor_index(-1) == 1 + @test Plots.legend_anchor_index(+0) == 2 + @test Plots.legend_anchor_index(+1) == 3 + + @test Plots.legend_angle(:foo_bar) == (45, :inner) + @test Plots.legend_angle(20.0) == + Plots.legend_angle((20.0, :inner)) == + (20.0, :inner) + @test Plots.legend_angle((20.0, 10.0)) == (20.0, 10.0) + end +end + +@testset "Output" begin + @test Plots.defaultOutputFormat(plot()) == "png" + @test Plots.addExtension("foo", "bar") == "foo.bar" + + fn = tempname() + gr() + let p = plot() + Plots.png(p, fn) + Plots.png(fn) + savefig(p, "$fn.png") + savefig("$fn.png") + + Plots.pdf(p, fn) + Plots.pdf(fn) + savefig(p, "$fn.pdf") + savefig("$fn.pdf") + + Plots.ps(p, fn) + Plots.ps(fn) + savefig(p, "$fn.ps") + savefig("$fn.ps") + + Plots.svg(p, fn) + Plots.svg(fn) + savefig(p, "$fn.svg") + savefig("$fn.svg") + end + + if Sys.islinux() + pgfplotsx() + let p = plot() + Plots.tex(p, fn) + Plots.tex(fn) + savefig(p, "$fn.tex") + savefig("$fn.tex") + end + end + + unicodeplots() + let p = plot() + Plots.txt(p, fn) + Plots.txt(fn) + savefig(p, "$fn.txt") + savefig("$fn.txt") + end + + plotlyjs() + let p = plot() + Plots.html(p, fn) + Plots.html(fn) + savefig(p, "$fn.html") + savefig("$fn.html") + + if Sys.islinux() + Plots.eps(p, fn) + Plots.eps(fn) + savefig(p, "$fn.eps") + savefig("$fn.eps") + end + end + + @test_throws ErrorException savefig("$fn.foo") +end + +gr() # reset to default backend + for fn in ( "test_args.jl", "test_defaults.jl", @@ -52,6 +322,7 @@ for fn in ( "test_hdf5plots.jl", "test_pgfplotsx.jl", "test_plotly.jl", + "test_animations.jl", ) @testset "$fn" begin include(fn) @@ -117,106 +388,29 @@ const PLOTS_IMG_TOL = parse(Float64, get(ENV, "PLOTS_IMG_TOL", is_ci() ? "1e-4" ## -@testset "Axes" begin - p = plot() - axis = p.subplots[1][:xaxis] - @test typeof(axis) == Plots.Axis - @test Plots.discrete_value!(axis, "HI") == (0.5, 1) - @test Plots.discrete_value!(axis, :yo) == (1.5, 2) - @test Plots.ignorenan_extrema(axis) == (0.5, 1.5) - @test axis[:discrete_map] == Dict{Any,Any}(:yo => 2, "HI" => 1) - - Plots.discrete_value!(axis, ["x$i" for i in 1:5]) - Plots.discrete_value!(axis, ["x$i" for i in 0:2]) - @test Plots.ignorenan_extrema(axis) == (0.5, 7.5) -end - -@testset "NoFail" begin - # ensure backend with tested display - @test unicodeplots() == Plots.UnicodePlotsBackend() - @test backend() == Plots.UnicodePlotsBackend() - - dsp = TextDisplay(IOContext(IOBuffer(), :color => true)) - - @testset "Plot" begin - plots = [ - histogram([1, 0, 0, 0, 0, 0]), - plot([missing]), - plot([missing, missing]), - plot(fill(missing, 10)), - plot([missing; 1:4]), - plot([fill(missing, 10); 1:4]), - plot([1 1; 1 missing]), - plot(["a" "b"; missing "d"], [1 2; 3 4]), - ] - for plt in plots - display(dsp, plt) - end - @test_nowarn plot(x -> x^2, 0, 2) - end - - @testset "Bar" begin - p = bar([3, 2, 1], [1, 2, 3]) - @test p isa Plots.Plot - @test display(dsp, p) isa Nothing - end -end - -@testset "EmptyAnim" begin - anim = @animate for i in [] - end - - @test_throws ArgumentError gif(anim) -end - -@testset "NaN-separated Segments" begin - segments(args...) = collect(iter_segments(args...)) - - nan10 = fill(NaN, 10) - @test segments(11:20) == [1:10] - @test segments([NaN]) == [] - @test segments(nan10) == [] - @test segments([nan10; 1:5]) == [11:15] - @test segments([1:5; nan10]) == [1:5] - @test segments([nan10; 1:5; nan10; 1:5; nan10]) == [11:15, 26:30] - @test segments([NaN; 1], 1:10) == [2:2, 4:4, 6:6, 8:8, 10:10] - @test segments([nan10; 1:15], [1:15; nan10]) == [11:15] -end - -@testset "Utils" begin - zipped = ( - [(1, 2)], - [("a", "b")], - [(1, "a"), (2, "b")], - [(1, 2), (3, 4)], - [(1, 2, 3), (3, 4, 5)], - [(1, 2, 3, 4), (3, 4, 5, 6)], - [(1, 2.0), (missing, missing)], - [(1, missing), (missing, "a")], - [(missing, missing)], - [(missing, missing, missing), ("a", "b", "c")], - ) - for z in zipped - @test isequal(collect(zip(Plots.RecipesPipeline.unzip(z)...)), z) - @test isequal( - collect(zip(Plots.RecipesPipeline.unzip(GeometryBasics.Point.(z))...)), - z, +@testset "Examples" begin + if Sys.islinux() + backends = ( + :unicodeplots, + :pgfplotsx, + :inspectdr, + :plotlyjs, + :gaston, + # :pyplot, # FIXME: fails with system matplotlib ) + only = setdiff( + 1:length(Plots._examples), + (Plots._backend_skips[be] for be in backends)..., + ) + for be in backends + @info be + for (i, p) in Plots.test_examples(be, only = only, disp = false) + fn = tempname() * ".png" + png(p, fn) + @test filesize(fn) > 1_000 + end + end end - op1 = Plots.process_clims((1.0, 2.0)) - op2 = Plots.process_clims((1, 2.0)) - data = randn(100, 100) - @test op1(data) == op2(data) - @test Plots.process_clims(nothing) == - Plots.process_clims(missing) == - Plots.process_clims(:auto) - - @test (==)( - Plots.texmath2unicode( - raw"Equation $y = \alpha \cdot x + \beta$ and eqn $y = \sin(x)^2$", - ), - raw"Equation y = α ⋅ x + β and eqn y = sin(x)²", - ) end @testset "Backends" begin @@ -230,24 +424,32 @@ end p = plot(rand(10)) @test p isa Plots.Plot @test show(io, p) isa Nothing + p = bar(randn(10)) @test p isa Plots.Plot @test show(io, p) isa Nothing + p = plot([1, 2], [3, 4]) annotate!(p, [(1.5, 3.2, Plots.text("Test", :red, :center))]) hline!(p, [3.1]) @test p isa Plots.Plot @test show(io, p) isa Nothing + p = plot([Dates.Date(2019, 1, 1), Dates.Date(2019, 2, 1)], [3, 4]) hline!(p, [3.1]) annotate!(p, [(Dates.Date(2019, 1, 15), 3.2, Plots.text("Test", :red, :center))]) @test p isa Plots.Plot @test show(io, p) isa Nothing + p = plot([Dates.Date(2019, 1, 1), Dates.Date(2019, 2, 1)], [3, 4]) annotate!(p, [(Dates.Date(2019, 1, 15), 3.2, :auto)]) hline!(p, [3.1]) @test p isa Plots.Plot @test show(io, p) isa Nothing + + p = plot((plot(i) for i in 1:4)..., layout = (2, 2)) + @test p isa Plots.Plot + @test show(io, p) isa Nothing end @testset "GR" begin diff --git a/test/test_animations.jl b/test/test_animations.jl new file mode 100644 index 00000000..3666866c --- /dev/null +++ b/test/test_animations.jl @@ -0,0 +1,61 @@ + +@testset "Empty anim" begin + anim = @animate for i in [] + end + @test_throws ArgumentError gif(anim) +end + +@userplot CirclePlot +@recipe function f(cp::CirclePlot) + x, y, i = cp.args + n = length(x) + inds = circshift(1:n, 1 - i) + linewidth --> range(0, 10, length = n) + seriesalpha --> range(0, 1, length = n) + aspect_ratio --> 1 + label --> false + x[inds], y[inds] +end + +@testset "Circle plot" begin + n = 10 + t = range(0, 2π, length = n) + x = sin.(t) + y = cos.(t) + + anim = @animate for i in 1:n + circleplot(x, y, i) + end + @test filesize(gif(anim).filename) > 10_000 + @test filesize(mov(anim).filename) > 10_000 + @test filesize(mp4(anim).filename) > 10_000 + @test filesize(webm(anim).filename) > 10_000 + + @gif for i in 1:n + circleplot(x, y, i, line_z = 1:n, cbar = false, framestyle = :zerolines) + end every 5 +end + +@testset "html" begin + p = plot([sin, cos], zeros(0), leg = false, xlims = (0, 2π), ylims = (-1, 1)) + anim = Animation() + for x in range(0, stop = 2π, length = 10) + push!(p, x, Float64[sin(x), cos(x)]) + frame(anim) + end + + agif = gif(anim) + html = tempname() * ".html" + open(html, "w") do io + show(io, MIME("text/html"), agif) + end + @test filesize(html) > 10_000 + @test showable(MIME("image/gif"), agif) + + agif = mp4(anim) + html = tempname() * ".html" + open(html, "w") do io + show(io, MIME("text/html"), agif) + end + @test filesize(html) > 10_000 +end diff --git a/test/test_components.jl b/test/test_components.jl index 31b643e3..51db1e24 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -3,6 +3,9 @@ using Plots, Test @testset "Shapes" begin @testset "Type" begin square = Shape([(0, 0.0), (1, 0.0), (1, 1.0), (0, 1.0)]) + @test Plots.get_xs(square) == [0, 1, 1, 0] + @test Plots.get_ys(square) == [0, 0, 1, 1] + @test Plots.vertices(square) == [(0, 0), (1, 0), (1, 1), (0, 1)] @test isa(square, Shape{Int64,Float64}) @test coords(square) isa Tuple{Vector{S},Vector{T}} where {T,S} end @@ -58,6 +61,25 @@ using Plots, Test @test_nowarn p = plot(myshapes) @test p[1][1][:seriestype] == :shape end + + @testset "Misc" begin + @test Plots.weave([1, 3], [2, 4]) == collect(1:4) + @test Plots.makeshape(3) isa Plots.Shape + @test Plots.makestar(3) isa Plots.Shape + @test Plots.makecross() isa Plots.Shape + @test Plots.makearrowhead(10.0) isa Plots.Shape + + @test Plots.rotate(1.0, 2.0, 5.0, (0, 0)) isa Tuple + + star = Plots.makestar(3) + star_scaled = Plots.scale(star, 0.5) + + Plots.scale!(star, 0.5) + @test Plots.get_xs(star) == Plots.get_xs(star_scaled) + @test Plots.get_ys(star) == Plots.get_ys(star_scaled) + + @test Plots.extrema_plus_buffer([1, 2], 0.1) == (0.9, 2.1) + end end @testset "Brush" begin @@ -82,6 +104,39 @@ end end end +@testset "Text" begin + t = Plots.PlotText("foo") + f = Plots.font() + + @test Plots.PlotText(nothing).str == "nothing" + @test length(t) == 3 + @test text(t).str == "foo" + @test text(t, f).str == "foo" + @test text("bar", f).str == "bar" + @test text(true).str == "true" +end + +@testset "Annotations" begin + ann = Plots.series_annotations(missing) + + @test Plots.series_annotations(["1" "2"; "3" "4"]) isa AbstractMatrix + @test Plots.series_annotations(10).strs[1].str == "10" + @test Plots.series_annotations(nothing) === nothing + @test Plots.series_annotations(ann) == ann + + @test Plots.annotations(["1" "2"; "3" "4"]) isa AbstractMatrix + @test Plots.annotations(ann) == ann + @test Plots.annotations([ann]) == [ann] + @test Plots.annotations(nothing) == [] + + t = Plots.text("foo") + sp = plot(1)[1] + @test Plots.locate_annotation(sp, 1, 2, t) == (1, 2, t) + @test Plots.locate_annotation(sp, 1, 2, 3, t) == (1, 2, 3, t) + @test Plots.locate_annotation(sp, (0.1, 0.2), t) isa Tuple + @test Plots.locate_annotation(sp, (0.1, 0.2, 0.3), t) isa Tuple +end + @testset "Fonts" begin @testset "Scaling" begin sizesToCheck = [ @@ -158,3 +213,9 @@ end @test pos == (0.1, 0.5) @test txt.str == "(a)" end + +@testset "Bezier" begin + curve = Plots.BezierCurve([Plots.P2(0.0, 0.0), Plots.P2(0.5, 1.0), Plots.P2(1.0, 0.0)]) + @test curve(0.75) == Plots.P2(0.75, 0.375) + @test length(coords(curve, 10)) == 10 +end diff --git a/test/test_layouts.jl b/test/test_layouts.jl index 90cf2ba8..d5d5ccee 100644 --- a/test/test_layouts.jl +++ b/test/test_layouts.jl @@ -32,3 +32,67 @@ end @test p[2][:framestyle] === :grid @test p[3][:framestyle] === :none end + +@testset "Coverage" begin + p = plot((plot(i) for i in 1:4)..., layout = (2, 2)) + + sp = p[end] + @test sp isa Plots.Subplot + @test size(sp) == (1, 1) + @test length(sp) == 1 + @test sp[1, 1] == sp + @test Plots.get_subplot(p, UInt32(4)) == sp + @test Plots.series_list(sp) |> first |> Plots.get_subplot isa Plots.Subplot + @test Plots.get_subplot(p, keys(p.spmap) |> first) isa Plots.Subplot + + gl = p[2, 2] + @test gl isa Plots.GridLayout + @test length(gl) == 1 + @test size(gl) == (1, 1) + @test Plots.layout_args(gl) == (gl, 1) + + @test size(p, 1) == 2 + @test size(p, 2) == 2 + @test size(p) === (2, 2) + @test ndims(p) == 2 + + @test p[1][end] isa Plots.Series + show(devnull, p[1]) + + @test Plots.getplot(p) == p + @test Plots.getattr(p) == p.attr + @test Plots.backend_object(p) == p.o + @test occursin("Plot", string(p)) + print(devnull, p) + + @test Plots.to_pixels(1Plots.mm) isa AbstractFloat + @test Plots.ispositive(1Plots.mm) + @test size(Plots.defaultbox) == (0Plots.mm, 0Plots.mm) + show(devnull, Plots.defaultbox) + show(devnull, p.layout) + + @test Plots.make_measure_hor(1Plots.mm) == 1Plots.mm + @test Plots.make_measure_vert(1Plots.mm) == 1Plots.mm + + @test Plots.parent(p.layout) isa Plots.RootLayout + show(devnull, Plots.parent_bbox(p.layout)) + + rl = Plots.RootLayout() + show(devnull, rl) + @test parent(rl) === nothing + @test Plots.parent_bbox(rl) == Plots.defaultbox + @test Plots.bbox(rl) == Plots.defaultbox + + el = Plots.EmptyLayout() + @test Plots.update_position!(el) === nothing + @test size(el) == (0, 0) + @test length(el) == 0 + @test el[1, 1] === nothing + + @test Plots.left(el) == 0Plots.mm + @test Plots.top(el) == 0Plots.mm + @test Plots.right(el) == 0Plots.mm + @test Plots.bottom(el) == 0Plots.mm + + @test_throws ErrorException Plots.layout_args(nothing) +end diff --git a/test/test_shorthands.jl b/test/test_shorthands.jl index 116548ca..55c2cb6b 100644 --- a/test/test_shorthands.jl +++ b/test/test_shorthands.jl @@ -7,42 +7,132 @@ using Plots, Test xlims!((1, 20)) @test xlims(p) == (1, 20) + xlims!(p, (1, 21)) + @test xlims(p) == (1, 21) + ylims!((-1, 1)) @test ylims(p) == (-1, 1) + ylims!(p, (-2, 2)) + @test ylims(p) == (-2, 2) + zlims!((-1, 1)) @test zlims(p) == (-1, 1) + zlims!(p, (-2, 2)) + @test zlims(p) == (-2, 2) + xlims!(-1, 11) @test xlims(p) == (-1, 11) + xlims!(p, -2, 12) + @test xlims(p) == (-2, 12) + ylims!((-10, 10)) @test ylims(p) == (-10, 10) + ylims!(p, (-11, 9)) + @test ylims(p) == (-11, 9) + zlims!((-10, 10)) @test zlims(p) == (-10, 10) + + zlims!(p, (-9, 8)) + @test zlims(p) == (-9, 8) + end + + @testset "Set Title / Labels" begin + p = plot() + title!(p, "Foo") + sp = p[1] + @test sp[:title] == "Foo" + xlabel!(p, "xlabel") + @test sp[:xaxis][:guide] == "xlabel" + ylabel!(p, "ylabel") + @test sp[:yaxis][:guide] == "ylabel" + end + + @testset "Misc" begin + p = plot() + sp = p[1] + + xflip!(p) + @test sp[:xaxis][:flip] + + yflip!(p) + @test sp[:yaxis][:flip] + + xgrid!(p, true) + @test sp[:xaxis][:grid] + + xgrid!(p, false) + @test !sp[:xaxis][:grid] + + ygrid!(p, true) + @test sp[:yaxis][:grid] + + ygrid!(p, false) + @test !sp[:yaxis][:grid] + + ann = [(7, 3, "(7,3)"), (3, 7, text("hey", 14, :left, :top, :green))] + annotate!(p, ann) + annotate!(p, ann...) + + xaxis!(p, true) + @test sp[:xaxis][:showaxis] + + xaxis!(p, false) + @test !sp[:xaxis][:showaxis] + + yaxis!(p, true) + @test sp[:yaxis][:showaxis] + + yaxis!(p, false) + @test !sp[:yaxis][:showaxis] + + p = plot3d([1, 2], [1, 2], [1, 2]) + plot3d!(p, [3, 4], [3, 4], [3, 4]) + @test Plots.series_list(p[1])[1][:seriestype] == :path3d end @testset "Set Ticks" begin p = plot([0, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + sp = p[1] xticks = 2:6 xticks!(xticks) - @test Plots.get_subplot(current(), 1).attr[:xaxis][:ticks] == xticks + @test sp.attr[:xaxis][:ticks] == xticks + + xticks = 1:5 + xticks!(p, xticks) + @test sp.attr[:xaxis][:ticks] == xticks yticks = 0.2:0.1:0.7 yticks!(yticks) - @test Plots.get_subplot(current(), 1).attr[:yaxis][:ticks] == yticks + @test sp.attr[:yaxis][:ticks] == yticks + + yticks = 0.1:0.5 + yticks!(p, yticks) + @test sp.attr[:yaxis][:ticks] == yticks xticks = [5, 6, 7.5] xlabels = ["a", "b", "c"] - xticks!(xticks, xlabels) - @test Plots.get_subplot(current(), 1).attr[:xaxis][:ticks] == (xticks, xlabels) + @test sp.attr[:xaxis][:ticks] == (xticks, xlabels) + + xticks = [5, 2] + xlabels = ["b", "a"] + xticks!(p, xticks, xlabels) + @test sp.attr[:xaxis][:ticks] == (xticks, xlabels) yticks = [0.5, 0.6, 0.75] ylabels = ["z", "y", "x"] yticks!(yticks, ylabels) - @test Plots.get_subplot(current(), 1).attr[:yaxis][:ticks] == (yticks, ylabels) + @test sp.attr[:yaxis][:ticks] == (yticks, ylabels) + + yticks = [0.5, 0.1] + ylabels = ["z", "y"] + yticks!(p, yticks, ylabels) + @test sp.attr[:yaxis][:ticks] == (yticks, ylabels) end end