UnicodePlots: adjust layout width per column (#3825)

This commit is contained in:
t-bltg 2021-09-17 21:42:50 +02:00 committed by GitHub
parent 85739932f0
commit e2539a3d19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 134 additions and 136 deletions

View File

@ -911,16 +911,21 @@ const _gaston_scale = [:identity, :ln, :log2, :log10]
const _unicodeplots_attr = merge_with_base_supported([ const _unicodeplots_attr = merge_with_base_supported([
:annotations, :annotations,
:bins,
:guide,
# :grid,
:label, :label,
:layout,
:legend, :legend,
:seriescolor, :lims,
:seriesalpha, :linealpha,
:linecolor,
:linestyle, :linestyle,
:markershape, :markershape,
:bins, :seriesalpha,
:seriescolor,
:scale,
:title, :title,
:guide,
:lims,
]) ])
const _unicodeplots_seriestype = [ const _unicodeplots_seriestype = [
:path, :path,

View File

@ -1,12 +1,10 @@
# https://github.com/JuliaPlots/UnicodePlots.jl # https://github.com/JuliaPlots/UnicodePlots.jl
# don't warn on unsupported... there's just too many warnings!! # don't warn on unsupported... there's just too many warnings!!
warn_on_unsupported_args(::UnicodePlotsBackend, plotattributes::KW) = nothing warn_on_unsupported_args(::UnicodePlotsBackend, plotattributes) = nothing
# -------------------------------------------------------------------------------------- # ------------------------------------------------------------------------------------------
const _canvas_map = (
_canvas_map() = (
ascii = UnicodePlots.AsciiCanvas, ascii = UnicodePlots.AsciiCanvas,
block = UnicodePlots.BlockCanvas, block = UnicodePlots.BlockCanvas,
braille = UnicodePlots.BrailleCanvas, braille = UnicodePlots.BrailleCanvas,
@ -16,22 +14,26 @@ _canvas_map() = (
lookup = UnicodePlots.LookupCanvas, lookup = UnicodePlots.LookupCanvas,
) )
# do all the magic here... build it all at once, since we need to know about all the series at the very beginning # do all the magic here... build it all at once,
# since we need to know about all the series at the very beginning
function unicodeplots_rebuild(plt::Plot{UnicodePlotsBackend}) function unicodeplots_rebuild(plt::Plot{UnicodePlotsBackend})
plt.o = UnicodePlots.Plot[] plt.o = UnicodePlots.Plot[]
canvas_map = _canvas_map()
for sp in plt.subplots for sp in plt.subplots
xaxis = sp[:xaxis] xaxis = sp[:xaxis]
yaxis = sp[:yaxis] yaxis = sp[:yaxis]
xlim = collect(axis_limits(sp, :x)) xlim = collect(axis_limits(sp, :x))
ylim = collect(axis_limits(sp, :y)) ylim = collect(axis_limits(sp, :y))
# we set x/y to have a single point, since we need to create the plot with some data. # we set x/y to have a single point,
# since this point is at the bottom left corner of the plot, it shouldn't actually be shown # since we need to create the plot with some data.
# since this point is at the bottom left corner of the plot,
# it shouldn't actually be shown
x = Float64[xlim[1]] x = Float64[xlim[1]]
y = Float64[ylim[1]] y = Float64[ylim[1]]
# create a plot window with xlim/ylim set, but the X/Y vectors are outside the bounds # create a plot window with xlim/ylim set,
# but the X/Y vectors are outside the bounds
canvas_type = if (ct = _canvas_type[]) == :auto canvas_type = if (ct = _canvas_type[]) == :auto
isijulia() ? :ascii : :braille isijulia() ? :ascii : :braille
else else
@ -40,16 +42,16 @@ function unicodeplots_rebuild(plt::Plot{UnicodePlotsBackend})
kw = ( kw = (
title = sp[:title], title = sp[:title],
xlim = xlim,
ylim = ylim,
border = isijulia() ? :ascii : :solid,
xlabel = xaxis[:guide], xlabel = xaxis[:guide],
ylabel = yaxis[:guide], ylabel = yaxis[:guide],
xscale = xaxis[:scale], xscale = xaxis[:scale],
yscale = yaxis[:scale], yscale = yaxis[:scale],
border = isijulia() ? :ascii : :solid,
xlim = xlim,
ylim = ylim,
) )
o = UnicodePlots.Plot(x, y, canvas_map[canvas_type]; kw...) o = UnicodePlots.Plot(x, y, _canvas_map[canvas_type]; kw...)
for series in series_list(sp) for series in series_list(sp)
o = addUnicodeSeries!(sp, o, kw, series, sp[:legend] != :none) o = addUnicodeSeries!(sp, o, kw, series, sp[:legend] != :none)
end end
@ -58,7 +60,8 @@ function unicodeplots_rebuild(plt::Plot{UnicodePlotsBackend})
x, y, val = locate_annotation(sp, ann...) x, y, val = locate_annotation(sp, ann...)
o = UnicodePlots.annotate!( o = UnicodePlots.annotate!(
o, x, y, val.str; o, x, y, val.str;
color = up_color(val.font.color), halign = val.font.halign, valign = val.font.valign color = up_color(val.font.color),
halign = val.font.halign, valign = val.font.valign
) )
end end
@ -66,17 +69,10 @@ function unicodeplots_rebuild(plt::Plot{UnicodePlotsBackend})
end end
end end
function up_color(col) up_color(col::UnicodePlots.UserColorType) = col
if typeof(col) <: UnicodePlots.UserColorType up_color(col::RGBA) =
color = col (c = convert(ARGB32, col); map(Int, (red(c).i, green(c).i, blue(c).i)))
elseif typeof(col) <: RGBA up_color(col) = :auto
col = convert(ARGB32, col)
color = map(Int, (red(col).i, green(col).i, blue(col).i))
else
color = :auto
end
color
end
# add a single series # add a single series
function addUnicodeSeries!( function addUnicodeSeries!(
@ -104,9 +100,7 @@ function addUnicodeSeries!(
cmap = [(red(c), green(c), blue(c)) for c in get(get_colorgradient(series), rng)] cmap = [(red(c), green(c), blue(c)) for c in get(get_colorgradient(series), rng)]
return UnicodePlots.heatmap( return UnicodePlots.heatmap(
series[:z].surf; series[:z].surf;
zlabel = sp[:colorbar_title], zlabel = sp[:colorbar_title], colormap = cmap, kw...
colormap = cmap,
kw...
) )
elseif st == :spy elseif st == :spy
return UnicodePlots.spy(series[:z].surf; kw...) return UnicodePlots.spy(series[:z].surf; kw...)
@ -139,9 +133,10 @@ function addUnicodeSeries!(
return up return up
end end
# ------------------------------- # ------------------------------------------------------------------------------------------
# since this is such a hack, it's only callable using `png`... should error during normal `show` # since this is such a hack, it's only callable using `png`...
# should error during normal `show`
function png(plt::Plot{UnicodePlotsBackend}, fn::AbstractString) function png(plt::Plot{UnicodePlotsBackend}, fn::AbstractString)
fn = addExtension(fn, "png") fn = addExtension(fn, "png")
@ -149,17 +144,10 @@ function png(plt::Plot{UnicodePlotsBackend}, fn::AbstractString)
# make some whitespace and show the plot # make some whitespace and show the plot
println("\n\n\n\n\n\n") println("\n\n\n\n\n\n")
gui(plt) gui(plt)
# BEGIN HACK
# wait while the plot gets drawn
sleep(0.5) sleep(0.5)
# use osx screen capture when my terminal is maximized
# use osx screen capture when my terminal is maximized and cursor starts at the bottom (I know, right?) # and cursor starts at the bottom (I know, right?)
# TODO: compute size of plot to adjust these numbers (or maybe implement something good??)
run(`screencapture -R50,600,700,420 $fn`) run(`screencapture -R50,600,700,420 $fn`)
# END HACK (phew)
return return
elseif Sys.islinux() elseif Sys.islinux()
run(`clear`) run(`clear`)
@ -169,12 +157,14 @@ function png(plt::Plot{UnicodePlotsBackend}, fn::AbstractString)
end end
error( error(
"Can only savepng on MacOS or Linux with UnicodePlots (though even then I wouldn't do it)", "Can only savepng on MacOS or Linux with UnicodePlots " *
"(though even then I wouldn't do it)"
) )
end end
# ------------------------------- # ------------------------------------------------------------------------------------------
Base.show(plt::Plot{UnicodePlotsBackend}) = _show(stdout, MIME("text/plain"), plt) Base.show(plt::Plot{UnicodePlotsBackend}) = show(stdout, plt)
Base.show(io::IO, plt::Plot{UnicodePlotsBackend}) = _show(io, MIME("text/plain"), plt)
function _show(io::IO, ::MIME"text/plain", plt::Plot{UnicodePlotsBackend}) function _show(io::IO, ::MIME"text/plain", plt::Plot{UnicodePlotsBackend})
unicodeplots_rebuild(plt) unicodeplots_rebuild(plt)
@ -182,9 +172,11 @@ function _show(io::IO, ::MIME"text/plain", plt::Plot{UnicodePlotsBackend})
lines_colored = Array{Union{Nothing,Vector{String}}}(undef, nr, nc) lines_colored = Array{Union{Nothing,Vector{String}}}(undef, nr, nc)
lines_uncolored = copy(lines_colored) lines_uncolored = copy(lines_colored)
l_max = zeros(Int, nr) l_max = zeros(Int, nr)
w_max = zeros(Int, nc)
buf = IOBuffer() buf = IOBuffer()
cbuf = IOContext(buf, :color => true) cbuf = IOContext(buf, :color => true)
sps = wmax = 0 re_col = r"\x1B\[[0-9;]*[a-zA-Z]"
sps = 0
for r in 1:nr for r in 1:nr
lmax = 0 lmax = 0
for c in 1:nc for c in 1:nc
@ -198,27 +190,27 @@ function _show(io::IO, ::MIME"text/plain", plt::Plot{UnicodePlotsBackend})
sp = plt.o[sps += 1] sp = plt.o[sps += 1]
show(cbuf, sp) show(cbuf, sp)
colored = String(take!(buf)) colored = String(take!(buf))
uncolored = replace(colored, r"\x1B\[[0-9;]*[a-zA-Z]" => "") uncolored = replace(colored, re_col => "")
lines_colored[r, c] = lc = split(colored, "\n") lines_colored[r, c] = lc = split(colored, "\n")
lines_uncolored[r, c] = lu = split(uncolored, "\n") lines_uncolored[r, c] = lu = split(uncolored, "\n")
lmax = max(length(lc), lmax) lmax = max(length(lc), lmax)
wmax = max(maximum(length.(lu)), wmax) w_max[c] = max(maximum(length.(lu)), w_max[c])
end end
end end
end end
l_max[r] = lmax l_max[r] = lmax
end end
empty = ' '^wmax empty = String[' '^w for w w_max]
for r in 1:nr for r in 1:nr
for n in 1:l_max[r] for n in 1:l_max[r]
for c in 1:nc for c in 1:nc
pre = c == 1 ? '\0' : ' ' pre = c == 1 ? '\0' : ' '
lc = lines_colored[r, c] lc = lines_colored[r, c]
if lc === nothing || length(lc) < n if lc === nothing || length(lc) < n
print(io, pre, empty) print(io, pre, empty[c])
else else
lu = lines_uncolored[r, c] lu = lines_uncolored[r, c]
print(io, pre, lc[n], ' '^(wmax - length(lu[n]))) print(io, pre, lc[n], ' '^(w_max[c] - length(lu[n])))
end end
end end
println(io) println(io)

View File

@ -1,18 +1,20 @@
using Plots: guidefont, series_annotations, PLOTS_SEED using Plots: guidefont, series_annotations, PLOTS_SEED
import ImageMagick
using VisualRegressionTests using VisualRegressionTests
using Plots
using Random
using StableRNGs
using Test
using TestImages
using FileIO
using Gtk
using LibGit2
import GeometryBasics
using Dates
using RecipesBase using RecipesBase
using StableRNGs
using TestImages
using LibGit2
using Random
using FileIO
using Plots
using Dates
using JSON using JSON
using Test
using Gtk
import GeometryBasics
import ImageMagick
@testset "Infrastructure" begin @testset "Infrastructure" begin
@test_nowarn JSON.Parser.parse( @test_nowarn JSON.Parser.parse(
@ -33,7 +35,7 @@ end
end end
Plots.plotly_local_file_path[] = nothing Plots.plotly_local_file_path[] = nothing
Plots.use_local_dependencies[] = temp Plots.use_local_dependencies[] = temp
end # testset end
include("test_defaults.jl") include("test_defaults.jl")
include("test_pipeline.jl") include("test_pipeline.jl")
@ -55,7 +57,6 @@ function reference_file(backend, i, version)
refdir = reference_dir("Plots", string(backend)) refdir = reference_dir("Plots", string(backend))
fn = "ref$i.png" fn = "ref$i.png"
versions = sort(VersionNumber.(readdir(refdir)), rev = true) versions = sort(VersionNumber.(readdir(refdir)), rev = true)
reffn = joinpath(refdir, string(version), fn) reffn = joinpath(refdir, string(version), fn)
for v in versions for v in versions
tmpfn = joinpath(refdir, string(v), fn) tmpfn = joinpath(refdir, string(v), fn)
@ -64,7 +65,6 @@ function reference_file(backend, i, version)
break break
end end
end end
return reffn return reffn
end end
@ -79,9 +79,10 @@ if !isdir(reference_dir())
end end
include("imgcomp.jl") include("imgcomp.jl")
# don't actually show the plots
Random.seed!(PLOTS_SEED) Random.seed!(PLOTS_SEED)
default(show = false, reuse = true)
default(show = false, reuse = true) # don't actually show the plots
is_ci() = get(ENV, "CI", "false") == "true" is_ci() = get(ENV, "CI", "false") == "true"
const PLOTS_IMG_TOL = parse(Float64, get(ENV, "PLOTS_IMG_TOL", is_ci() ? "1e-4" : "1e-5")) const PLOTS_IMG_TOL = parse(Float64, get(ENV, "PLOTS_IMG_TOL", is_ci() ? "1e-4" : "1e-5"))
@ -106,68 +107,8 @@ const PLOTS_IMG_TOL = parse(Float64, get(ENV, "PLOTS_IMG_TOL", is_ci() ? "1e-4"
# image_comparison_facts(:pgfplotsx, tol=PLOTS_IMG_TOL, skip = Plots._backend_skips[:pgfplotsx]) # image_comparison_facts(:pgfplotsx, tol=PLOTS_IMG_TOL, skip = Plots._backend_skips[:pgfplotsx])
# end # end
# 10 Histogram2D
## ##
@testset "Backends" begin
@testset "GR" begin
ENV["PLOTS_TEST"] = "true"
ENV["GKSwstype"] = "100"
@test gr() == Plots.GRBackend()
@test backend() == Plots.GRBackend()
@static if haskey(ENV, "APPVEYOR")
@info "Skipping GR image comparison tests on AppVeyor"
else
image_comparison_facts(
:gr,
tol = PLOTS_IMG_TOL,
skip = Plots._backend_skips[:gr],
)
end
end
@testset "UnicodePlots" begin
@test unicodeplots() == Plots.UnicodePlotsBackend()
@test backend() == Plots.UnicodePlotsBackend()
io = IOBuffer()
# lets just make sure it runs without error
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
end
@testset "PlotlyJS" begin
@test plotlyjs() == Plots.PlotlyJSBackend()
@test backend() == Plots.PlotlyJSBackend()
p = plot(rand(10))
@test p isa Plots.Plot
@test_broken display(p) isa Nothing
end
end
@testset "Axes" begin @testset "Axes" begin
p = plot() p = plot()
axis = p.subplots[1][:xaxis] axis = p.subplots[1][:xaxis]
@ -183,10 +124,12 @@ end
end end
@testset "NoFail" begin @testset "NoFail" begin
#ensure backend with tested display # ensure backend with tested display
@test unicodeplots() == Plots.UnicodePlotsBackend() @test unicodeplots() == Plots.UnicodePlotsBackend()
@test backend() == Plots.UnicodePlotsBackend() @test backend() == Plots.UnicodePlotsBackend()
dsp = TextDisplay(IOContext(IOBuffer(), :color => true))
@testset "Plot" begin @testset "Plot" begin
plots = [ plots = [
histogram([1, 0, 0, 0, 0, 0]), histogram([1, 0, 0, 0, 0, 0]),
@ -199,21 +142,20 @@ end
plot(["a" "b"; missing "d"], [1 2; 3 4]), plot(["a" "b"; missing "d"], [1 2; 3 4]),
] ]
for plt in plots for plt in plots
display(plt) display(dsp, plt)
end end
@test_nowarn plot(x -> x^2, 0, 2) @test_nowarn plot(x -> x^2, 0, 2)
end end
@testset "Bar" begin @testset "Bar" begin
p = bar([3, 2, 1], [1, 2, 3]) p = bar([3, 2, 1], [1, 2, 3])
@test isa(p, Plots.Plot) @test p isa Plots.Plot
@test isa(display(p), Nothing) == true @test display(dsp, p) isa Nothing
end end
end end
@testset "EmptyAnim" begin @testset "EmptyAnim" begin
anim = @animate for i in [] anim = @animate for i in [] end
end
@test_throws ArgumentError gif(anim) @test_throws ArgumentError gif(anim)
end end
@ -257,3 +199,62 @@ end
Plots.process_clims(missing) == Plots.process_clims(missing) ==
Plots.process_clims(:auto) Plots.process_clims(:auto)
end end
@testset "Backends" begin
@testset "UnicodePlots" begin
@test unicodeplots() == Plots.UnicodePlotsBackend()
@test backend() == Plots.UnicodePlotsBackend()
io = IOContext(IOBuffer(), :color => true)
# lets just make sure it runs without error
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
end
@testset "PlotlyJS" begin
@test plotlyjs() == Plots.PlotlyJSBackend()
@test backend() == Plots.PlotlyJSBackend()
p = plot(rand(10))
@test p isa Plots.Plot
@test_broken display(p) isa Nothing
end
@testset "GR" begin
ENV["PLOTS_TEST"] = "true"
ENV["GKSwstype"] = "100"
@test gr() == Plots.GRBackend()
@test backend() == Plots.GRBackend()
@static if haskey(ENV, "APPVEYOR")
@info "Skipping GR image comparison tests on AppVeyor"
else
image_comparison_facts(
:gr,
tol = PLOTS_IMG_TOL,
skip = Plots._backend_skips[:gr],
)
end
end
end