Merge pull request #3738 from t-bltg/segments

Gaston: add support for img - rework fonts - fix undef
This commit is contained in:
t-bltg 2021-08-01 00:09:28 +02:00 committed by GitHub
commit a3e1a43358
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 141 additions and 120 deletions

View File

@ -590,7 +590,7 @@ function _initialize_backend(::GastonBackend)
end
const _gaston_attr = merge_with_base_supported([
# :annotations,
:annotations,
# :background_color_legend,
# :background_color_inside,
# :background_color_outside,
@ -620,7 +620,7 @@ const _gaston_attr = merge_with_base_supported([
# :overwrite_figure,
# :polar,
# :normalize, :weights, :contours,
# :aspect_ratio,
:aspect_ratio,
:tick_direction,
# :framestyle,
# :camera,
@ -645,6 +645,7 @@ const _gaston_seriestype = [
:heatmap,
:surface,
:mesh3d,
:image,
]
const _gaston_style = [

View File

@ -28,21 +28,18 @@ function _before_layout_calcs(plt::Plot{GastonBackend})
@error "Gaston: $n != $(length(plt.subplots))"
end
mapping, plt.o.layout = gaston_init_subplots(plt, sps)
plt.o.layout = gaston_init_subplots(plt, sps)
# Then add the series (curves in gaston)
for series plt.series_list
gaston_add_series(plt, series)
end
for (sp, gsp) mapping
(sp === nothing || gsp === nothing) && continue
for sp plt.subplots
sp === nothing && continue
for ann in sp[:annotations]
x, y, val = locate_annotation(sp, ann...); ft = val.font
gsp.axesconf *= (
"\nset label \"$(val.str)\" at $x,$y $(ft.halign) rotate by $(ft.rotation) " *
"font \"$(ft.family),$(ft.pointsize)\" front textcolor $(gaston_color(ft.color))"
)
x, y, val = locate_annotation(sp, ann...)
sp.o.axesconf *= "\nset label '$(val.str)' at $x,$y $(gaston_font(val.font))"
end
end
nothing
@ -99,7 +96,7 @@ end
function gaston_get_subplots(n, plt_subplots, layout)
nr, nc = size(layout)
sps = Array{Any}(undef, nr, nc)
sps = Array{Any}(nothing, nr, nc)
for r 1:nr, c 1:nc # NOTE: col major
l = layout[r, c]
if l isa GridLayout
@ -114,35 +111,35 @@ end
function gaston_init_subplots(plt, sps)
sz = nr, nc = size(sps)
mapping = Dict{Union{Nothing,Subplot{GastonBackend}}, Gaston.SubPlot}()
for c 1:nc, r 1:nr # NOTE: row major
sp = sps[r, c]
if sp isa Subplot || sp === nothing
mapping[sp] = gaston_init_subplot(plt, sp)
gaston_init_subplot(plt, sp)
else
gaston_init_subplots(plt, sp)
sz = max.(sz, size(sp))
end
end
return mapping, sz
return sz
end
function gaston_init_subplot(plt::Plot{GastonBackend}, sp::Union{Nothing,Subplot{GastonBackend}})
if sp === nothing
push!(plt.o.subplots, sp)
else
dims = RecipesPipeline.is3d(sp) ? 3 : 2
dims = RecipesPipeline.is3d(sp) || sp.attr[:projection] == "3d" || needs_any_3d_axes(sp) ? 3 : 2
any_label = false
if dims == 2
for series plt.series_list
if series[:seriestype] (:heatmap, :contour)
dims = 3 # we need heatmap/contour to use splot, not plot
break
end
any_label |= should_add_to_legend(series)
end
end
sp.o = Gaston.Plot(
dims=dims,
axesconf=gaston_parse_axes_args(plt, sp, dims), # Gnuplot string
axesconf=gaston_parse_axes_args(plt, sp, dims, any_label), # Gnuplot string
curves=[]
)
push!(plt.o.subplots, sp.o)
@ -152,17 +149,17 @@ end
function gaston_multiplot_pos_size(layout, parent_xy_wh)
nr, nc = size(layout)
xy_wh = Array{Any}(undef, nr, nc)
xy_wh = Array{Any}(nothing, nr, nc)
for r 1:nr, c 1:nc # NOTE: col major
l = layout[r, c]
if !isa(l, EmptyLayout)
# previous position (origin)
prev_r = r > 1 ? xy_wh[r - 1, c] : undef
prev_c = c > 1 ? xy_wh[r, c - 1] : undef
prev_r = r > 1 ? xy_wh[r - 1, c] : nothing
prev_c = c > 1 ? xy_wh[r, c - 1] : nothing
prev_r isa Array{Any} && (prev_r = prev_r[end, end])
prev_c isa Array{Any} && (prev_c = prev_c[end, end])
x = prev_c !== undef ? prev_c[1] + prev_c[3] : parent_xy_wh[1]
y = prev_r !== undef ? prev_r[2] + prev_r[4] : parent_xy_wh[2]
x = prev_c !== nothing ? prev_c[1] + prev_c[3] : parent_xy_wh[1]
y = prev_r !== nothing ? prev_r[2] + prev_r[4] : parent_xy_wh[2]
# width and height (pct) are multiplicative (parent)
w = layout.widths[c].value * parent_xy_wh[3]
h = layout.heights[r].value * parent_xy_wh[4]
@ -180,12 +177,13 @@ function gaston_multiplot_pos_size!(n::Int, plt, origin_size)
nr, nc = size(origin_size)
for c 1:nc, r 1:nr # NOTE: row major
xy_wh = origin_size[r, c]
if xy_wh === undef
if xy_wh === nothing
continue
elseif xy_wh isa Tuple
x, y, w, h = xy_wh
gsp = plt.o.subplots[n += 1]
gsp.axesconf = "set origin $x,$y\nset size $w,$h\n" * gsp.axesconf
if (gsp = plt.o.subplots[n += 1]) !== nothing
gsp.axesconf = "set origin $x,$y\nset size $w,$h\n" * gsp.axesconf
end
else
n = gaston_multiplot_pos_size!(n, plt, xy_wh)
end
@ -194,39 +192,54 @@ function gaston_multiplot_pos_size!(n::Int, plt, origin_size)
end
function gaston_add_series(plt::Plot{GastonBackend}, series::Series)
# Gaston.Curve = Plots.Series
sp = series[:subplot]
gsp = sp.o # Gaston subplot object
sp = series[:subplot]; gsp = sp.o
x, y, z = series[:x], series[:y], series[:z]
st = series[:seriestype]
x = series[:x]
y = series[:y]
z = gsp.dims == 2 ? nothing : series[:z]
if z isa Surface
z = z.surf
curves = []
if gsp.dims == 2 && z === nothing
for seg series_segments(series, st; check=true)
i, rng = seg.attr_index, seg.range
seriesconf = gaston_seriesconf!(sp, series, i)
push!(curves, Gaston.Curve(x[rng], y[rng], nothing, nothing, seriesconf))
end
else
seriesconf = gaston_seriesconf!(sp, series, 1)
if z isa Surface
z = z.surf
if st == :image
z = Float32.(Gray.(z))
nr, nc = size(z)
lx = length(x)
if lx == 2 && lx != nr
x = collect(range(x[1], x[2], length=nr))
end
ly = length(y)
if ly == 2 && ly != nc
y = collect(range(y[1], y[2], length=nc))
end
end
end
push!(curves, Gaston.Curve(x, y, z, nothing, seriesconf))
end
seriesconf = gaston_seriesconf!(sp, series) # Gnuplot string
c = Gaston.Curve(x, y, z, nothing, seriesconf)
isfirst = length(gsp.curves) == 0 ? true : false
push!(gsp.curves, c)
Gaston.write_data(c, gsp.dims, gsp.datafile, append = isfirst ? false : true)
for c curves
append = length(gsp.curves) > 0; push!(gsp.curves, c)
Gaston.write_data(c, gsp.dims, gsp.datafile, append=append)
end
nothing
end
function gaston_hvline!(sp, series, curveconf, pt, dt, lw, lc, command)
if pt (:hline, :vline)
xs, ys = straightline_data(series)
if pt == :hline
lo, hi = axis_limits(sp, :x)
for y ys
sp.o.axesconf *= "\nset arrow from $lo,$y to $hi,$y nohead lc $lc lw $lw dt $dt"
end
elseif pt == :vline
lo, hi = axis_limits(sp, :y)
for x xs
sp.o.axesconf *= "\nset arrow from $x,$lo to $x,$hi nohead lc $lc lw $lw dt $dt"
end
if pt == :hline
lo, hi = axis_limits(sp, :x)
for y series[:y]
sp.o.axesconf *= "\nset arrow from $lo,$y to $hi,$y nohead lc $lc lw $lw dt $dt"
end
elseif pt == :vline
lo, hi = axis_limits(sp, :y)
for x series[:x]
sp.o.axesconf *= "\nset arrow from $x,$lo to $x,$hi nohead lc $lc lw $lw dt $dt"
end
else
push!(curveconf, command)
@ -234,7 +247,7 @@ function gaston_hvline!(sp, series, curveconf, pt, dt, lw, lc, command)
nothing
end
function gaston_seriesconf!(sp::Subplot{GastonBackend}, series::Series)
function gaston_seriesconf!(sp::Subplot{GastonBackend}, series::Series, i::Int)
#=
gnuplot abbreviations (see gnuplot/src/set.c)
---------------------------------------------
@ -251,56 +264,63 @@ function gaston_seriesconf!(sp::Subplot{GastonBackend}, series::Series)
pn: pointnumber
ps: pointscale
pt: pointtype
tc: textcolor
tc: textcolor
w: with
=#
gsp = sp.o; curveconf = String[]
st = series[:seriestype]
clims = get_clims(sp, series)
if st (:scatter, :scatter3d)
lc, dt, lw = gaston_lc_ls_lw(series)
pt, ps, mc = gaston_mk_ms_mc(series)
gaston_hvline!(sp, series, curveconf, pt, dt, lw, mc, "with points pt $pt ps $ps lc $mc")
lc, dt, lw = gaston_lc_ls_lw(series, clims, i)
pt, ps, mc = gaston_mk_ms_mc(series, clims, i)
gaston_hvline!(sp, series, curveconf, pt, dt, lw, mc, "w points pt $pt ps $ps lc $mc")
elseif st (:path, :straightline, :path3d)
lc, dt, lw = gaston_lc_ls_lw(series)
if series[:markershape] == :none # simplepath
push!(curveconf, "with lines lc $lc dt $dt lw $lw")
lc, dt, lw = gaston_lc_ls_lw(series, clims, i)
if series[:markershape] == :none # simplepath
push!(curveconf, "w lines lc $lc dt $dt lw $lw")
else
pt, ps, mc = gaston_mk_ms_mc(series)
gaston_hvline!(sp, series, curveconf, x, y, pt, dt, lw, mc, "with lp lc $mc dt $dt lw $lw pt $pt ps $ps")
pt, ps, mc = gaston_mk_ms_mc(series, clims, i)
gaston_hvline!(sp, series, curveconf, pt, dt, lw, mc, "w lp lc $mc dt $dt lw $lw pt $pt ps $ps")
end
elseif st == :shape
fc = gaston_color(series[:fillcolor], series[:fillalpha])
lc, _ = gaston_lc_ls_lw(series)
push!(curveconf, "with filledcurves fc $fc fs solid border lc $lc")
fc = gaston_color(get_fillcolor(series, i), get_fillalpha(series, i))
lc, _ = gaston_lc_ls_lw(series, clims, i)
push!(curveconf, "w filledcurves fc $fc fs solid border lc $lc")
elseif st == :steppre
push!(curveconf, "with steps")
push!(curveconf, "w steps")
elseif st == :steppost
push!(curveconf, "with fsteps") # Not sure if not the other way
push!(curveconf, "w fsteps") # Not sure if not the other way
elseif st == :image
palette = gaston_palette(series[:seriescolor])
gsp.axesconf *= "\nset palette model RGB defined $palette"
push!(curveconf, "w image pixels")
elseif st (:contour, :contour3d)
push!(curveconf, "with lines")
push!(curveconf, "w lines")
st == :contour && (gsp.axesconf *= "\nset view map\nunset surface") # 2D
gsp.axesconf *= "\nunset key" # FIXME: too many legend (key) entries
levels = join(map(string, collect(contour_levels(series, clims))), ", ")
gsp.axesconf *= "\nset contour base\nset cntrparam levels discrete $levels"
elseif st (:surface, :heatmap)
push!(curveconf, "with pm3d")
push!(curveconf, "w pm3d")
palette = gaston_palette(series[:seriescolor])
gsp.axesconf *= "\nset palette model RGB defined $palette"
st == :heatmap && (gsp.axesconf *= "\nset view map")
elseif st == :wireframe
lc, dt, lw = gaston_lc_ls_lw(series)
push!(curveconf, "with lines lc $lc dt $dt lw $lw")
lc, dt, lw = gaston_lc_ls_lw(series, clims, i)
push!(curveconf, "w lines lc $lc dt $dt lw $lw")
else
@warn "Gaston: $st is not implemented yet"
end
push!(curveconf, should_add_to_legend(series) ? "title \"$(series[:label])\"" : "notitle")
push!(curveconf, should_add_to_legend(series) ? "title '$(series[:label])'" : "notitle")
return join(curveconf, " ")
end
function gaston_parse_axes_args(plt::Plot{GastonBackend}, sp::Subplot{GastonBackend}, dims::Int)
axesconf = String[]
function gaston_parse_axes_args(
plt::Plot{GastonBackend}, sp::Subplot{GastonBackend}, dims::Int, any_label::Bool
)
axesconf = String["reset"]
# Standard 2d axis
if !ispolar(sp) && !RecipesPipeline.is3d(sp)
# TODO: configure grid, axis spines, thickness
@ -310,17 +330,12 @@ function gaston_parse_axes_args(plt::Plot{GastonBackend}, sp::Subplot{GastonBack
(letter == :z && dims == 2) && continue
axis = sp.attr[Symbol(letter, :axis)]
# label names
push!(axesconf, "set $(letter)label \"$(axis[:guide])\"")
push!(axesconf, "set $(letter)label font \"$(axis[:guidefontfamily]), $(axis[:guidefontsize])\"")
# Handle ticks
# ticksyle
push!(axesconf, "set $(letter)tics font \"$(axis[:tickfontfamily]), $(axis[:tickfontsize])\"")
push!(axesconf, "set $(letter)tics textcolor $(gaston_color(axis[:tickfontcolor]))")
push!(axesconf, "set $(letter)tics $(axis[:tick_direction])")
push!(axesconf, "set $(letter)label '$(axis[:guide])' $(gaston_font(guidefont(axis)))")
mirror = axis[:mirror] ? "mirror" : "nomirror"
push!(axesconf, "set $(letter)tics $(mirror)")
# Handle ticks
push!(axesconf, "set $(letter)tics $(mirror) $(axis[:tick_direction]) $(gaston_font(tickfont(axis)))")
logscale = if axis[:scale] == :identity
"nologscale"
@ -331,8 +346,8 @@ function gaston_parse_axes_args(plt::Plot{GastonBackend}, sp::Subplot{GastonBack
# major tick locations
if axis[:ticks] != :native
from, to = axis_limits(sp, letter) # axis limits
push!(axesconf, "set $(letter)range [$from:$to]")
lo, hi = axis_limits(sp, letter)
push!(axesconf, "set $(letter)range [$lo:$hi]")
ticks = get_ticks(sp, axis)
gaston_set_ticks!(axesconf, ticks, letter, "", "")
@ -354,11 +369,14 @@ function gaston_parse_axes_args(plt::Plot{GastonBackend}, sp::Subplot{GastonBack
push!(axesconf, "set size ratio $ratio")
end
end
gaston_set_legend!(axesconf, sp) # Set legend params
gaston_set_legend!(axesconf, sp, any_label) # Set legend params
if sp[:title] != nothing
push!(axesconf, "set title '$(sp[:title])'")
push!(axesconf, "set title font '$(sp[:titlefontfamily]), $(sp[:titlefontsize])'")
if hascolorbar(sp)
push!(axesconf, "set cbtics $(gaston_font(colorbartitlefont(sp)))")
end
if sp[:title] !== nothing
push!(axesconf, "set title '$(sp[:title])' $(gaston_font(titlefont(sp)))")
end
return join(axesconf, "\n")
@ -394,27 +412,27 @@ function gaston_set_ticks!(axesconf, ticks, letter, maj_min, add)
gaston_ticks = nothing
@error "Gaston: invalid input for $(maj_min)$(letter)ticks: $ticks"
end
gaston_ticks !== nothing && push!(axesconf, "set $(letter)tics $add (" * join(gaston_ticks, ", ") * ")")
if gaston_ticks !== nothing
push!(axesconf, "set $(letter)tics $add (" * join(gaston_ticks, ", ") * ")")
end
nothing
end
function gaston_set_legend!(axesconf, sp)
function gaston_set_legend!(axesconf, sp, any_label)
leg = sp[:legend]
if sp[:legend] (:none, :inline)
if sp[:legend] (:none, :inline) && any_label
leg == :best && (leg = :topright)
push!(axesconf, "set key " * (occursin("outer", string(leg)) ? "outside" : "inside"))
for position ("top", "bottom", "left", "right")
occursin(position, string(leg)) && push!(axesconf, "set key $position")
end
if sp[:legendtitle] != nothing
push!(axesconf, "set key title '$(sp[:legendtitle])'")
if sp[:legendtitle] !== nothing
push!(axesconf, "set key title '$(sp[:legendtitle])' $(gaston_font(legendfont(sp)))")
end
push!(axesconf, "set key box lw 1")
push!(axesconf, "set key opaque")
push!(axesconf, "set key box lw 1 opaque")
# push!(axesconf, "set key $(gaston_font(legendtitlefont(sp), rot=false, align=false))")
push!(axesconf, "set border back")
push!(axesconf, "set key font \"$(sp[:legendfontfamily]), $(sp[:legendfontsize])\"")
else
push!(axesconf, "set key off")
@ -426,23 +444,32 @@ end
# Helpers
# --------------------------------------------
function gaston_font(f; rot=true, align=true)
font = "font '$(f.family),$(f.pointsize)' "
align && (font *= "$(gaston_halign(f.halign)) ")
rot && (font *= "rotate by $(f.rotation) ")
font *= "textcolor $(gaston_color(f.color))"
end
gaston_halign(k) = (left=:left, hcenter=:center, right=:right)[k]
gaston_valign(k) = (top=:top, vcenter=:center, bottom=:bottom)[k]
gaston_alpha(alpha) = alpha === nothing ? 0 : alpha
gaston_lc_ls_lw(series::Series) = (
gaston_color(series[:linecolor], series[:linealpha]),
gaston_linestyle(series[:linestyle]),
series[:linewidth],
gaston_lc_ls_lw(series::Series, clims, i::Int) = (
gaston_color(get_linecolor(series, clims, i), get_linealpha(series, i)),
gaston_linestyle(get_linestyle(series, i)),
get_linewidth(series, i),
)
gaston_mk_ms_mc(series::Series) = (
gaston_marker(series[:markershape], series[:markeralpha]),
series[:markersize] * 1.3 / 5.,
gaston_color(series[:markercolor], series[:markeralpha]),
gaston_mk_ms_mc(series::Series, clims, i::Int) = (
gaston_marker(_cycle(series[:markershape], i), get_markeralpha(series, i)),
_cycle(series[:markersize], i) * 1.3 / 5,
gaston_color(get_markercolor(series, clims, i), get_markeralpha(series, i)),
)
function gaston_palette(gradient)
palette = String[]
n = -1
palette = String[]; n = -1
for rgba gradient # FIXME: naive conversion, inefficient ?
push!(palette, "$(n += 1) $(rgba.r) $(rgba.g) $(rgba.b)")
end
@ -470,13 +497,9 @@ function gaston_marker(marker, alpha)
end
function gaston_color(col, alpha=0)
if isvector(col)
return gaston_color.(col)
else
col = single_color(col) # in case of gradients
col = alphacolor(col, gaston_alpha(alpha)) # add a default alpha if non existent
return "rgb \"#$(hex(col, :aarrggbb))\""
end
col = single_color(col) # in case of gradients
col = alphacolor(col, gaston_alpha(alpha)) # add a default alpha if non existent
return "rgb '#$(hex(col, :aarrggbb))'"
end
function gaston_linestyle(style)
@ -488,7 +511,7 @@ function gaston_linestyle(style)
end
function gaston_enclose_tick_string(tick_string)
findfirst("^", tick_string) == nothing && return tick_string
findfirst("^", tick_string) === nothing && return tick_string
base, power = split(tick_string, "^")
return "$base^{$power}"
end

View File

@ -1244,19 +1244,16 @@ _backend_skips = Dict(
:unicodeplots => [6, 10, 22, 24, 28, 38, 43, 45, 47, 49, 50, 51, 55],
:gaston => [
2, # animations
4, # colors/palette issues
6, # TODO: support embedded images
10, # TODO: support histogram2d
10, # TODO: support histogram2d
16, # TODO: support nested layouts
27, # TODO: support polar
30, # uses StatsPlots, deprecated ?
31, # animations
38, # TODO: support histogram2d
38, # TODO: support histogram2d
47, # TODO: support mesh3d
48, # TODO: vector of shapes, ...
49, # TODO: support polar
50, # TODO: 1D data not supported for pm3d
51, # TODO: support embedded images
55, # TODO: scaling is ugly
],
)