From 2fcd90716571d28da0a0bb6b9ffb224295548a37 Mon Sep 17 00:00:00 2001 From: t-bltg Date: Sat, 31 Jul 2021 23:09:23 +0200 Subject: [PATCH] Gaston: add support for img - rework fonts - fix undef --- src/backends.jl | 5 +- src/backends/gaston.jl | 249 ++++++++++++++++++++++------------------- src/examples.jl | 7 +- 3 files changed, 141 insertions(+), 120 deletions(-) diff --git a/src/backends.jl b/src/backends.jl index 06a71d1f..fab0df86 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -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 = [ diff --git a/src/backends/gaston.jl b/src/backends/gaston.jl index 43c108e8..58238f7a 100644 --- a/src/backends/gaston.jl +++ b/src/backends/gaston.jl @@ -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 diff --git a/src/examples.jl b/src/examples.jl index b273a3f3..1397655f 100644 --- a/src/examples.jl +++ b/src/examples.jl @@ -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 ], )