From c0e35f2358936d6545dd8cfc09baed7ca6134851 Mon Sep 17 00:00:00 2001 From: t-bltg Date: Sun, 25 Jul 2021 15:21:23 +0200 Subject: [PATCH 1/2] add new gnuplot capabilities (surface, scatter3d, wireframe, ...) --- src/backends.jl | 81 ++++++++++++--------- src/backends/gaston.jl | 157 ++++++++++++++++++++++++++++++----------- src/examples.jl | 9 ++- 3 files changed, 171 insertions(+), 76 deletions(-) diff --git a/src/backends.jl b/src/backends.jl index 6205f254..14ff3d6a 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -623,44 +623,55 @@ const _gaston_attr = merge_with_base_supported([ # :camera, # :contour_labels, ]) -const _gaston_seriestype = [:path, - # :path3d, - :scatter, - :steppre, - # :stepmid, - :steppost, - # :histogram2d, - # :ysticks, :xsticks, - # :contour, - :shape, - # :straightline, - ] -const _gaston_style = [:auto, - :solid, - :dash, - :dot, - :dashdot, - :dashdotdot - ] +const _gaston_seriestype = [ + :path, + :path3d, + :scatter, + :steppre, + :stepmid, + :steppost, + :histogram2d, + :ysticks, :xsticks, + :contour, + :shape, + :straightline, + :scatter3d, + :contour3d, + :wireframe, + :heatmap, + :surface, + :mesh3d, +] -const _gaston_marker = [:none, - # :auto, - :circle, - :rect, - :diamond, - :utriangle, - :dtriangle, - :pentagon, - :x, - :+ - ] #vcat(_allMarkers, Shape) +const _gaston_style = [ + :auto, + :solid, + :dash, + :dot, + :dashdot, + :dashdotdot +] -const _gaston_scale = [:identity, - # :ln, - # :log2, - :log10, - ] +const _gaston_marker = [ + :none, + # :auto, + :circle, + :rect, + :diamond, + :utriangle, + :dtriangle, + :pentagon, + :x, + :+ +] #vcat(_allMarkers, Shape) + +const _gaston_scale = [ + :identity, + # :ln, + # :log2, + :log10, +] # ------------------------------------------------------------------------------ # unicodeplots diff --git a/src/backends/gaston.jl b/src/backends/gaston.jl index 51a393ec..c6a19480 100644 --- a/src/backends/gaston.jl +++ b/src/backends/gaston.jl @@ -22,18 +22,11 @@ end function _before_layout_calcs(plt::Plot{GastonBackend}) # Initialize all the subplots first plt.o.subplots = G.SubPlot[] - nr, nc = plt.o.layout = size(plt.layout) - n = 0 - sps = Array{Any}(undef, nr, nc) - for r ∈ 1:nr, c ∈ 1:nc # NOTE: row major - l = plt.layout.grid[r, c] - sps[r, c] = get(l.attr, :blank, false) ? nothing : plt.subplots[n += 1] - end + n, sps = gaston_get_subplots(plt, 0, plt.layout) + @assert n == length(plt.subplots) - for c ∈ 1:nc, r ∈ 1:nr # NOTE: col major - gaston_init_subplot(plt, sps[r, c]) - end + plt.o.layout = gaston_init_subplots(plt, sps) # Then add the series (curves in gaston) for series in plt.series_list @@ -59,8 +52,7 @@ for (mime, term) in ( "text/plain" => "dumb", # NEED fixing TODO ) @eval function _show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{GastonBackend}) - xsize = plt.attr[:size][1] - ysize = plt.attr[:size][2] + xsize, ysize = plt.attr[:size] termopts="""size $xsize,$ysize""" tmpfile = G.tempname() * "." * $term @@ -79,11 +71,9 @@ for (mime, term) in ( end end -function _show(io::IO, mime::MIME{Symbol("image/png")}, - plt::Plot{GastonBackend},) +function _show(io::IO, mime::MIME{Symbol("image/png")}, plt::Plot{GastonBackend}) scaling = plt.attr[:dpi] / GNUPLOT_DPI - xsize = plt.attr[:size][1] * scaling - ysize = plt.attr[:size][2] * scaling + xsize, ysize = plt.attr[:size] .* scaling # Scale all plot elements to match Plots.jl DPI standard termopts="""size $xsize,$ysize fontscale $scaling lw $scaling dl $scaling ps $scaling""" @@ -111,14 +101,44 @@ end # These functions are gaston specific # -------------------------------------------- +gaston_get_subplots(plt, n, layout, level) = begin + nr, nc = size(layout) + sps = Array{Any}(undef, nr, nc) + for r ∈ 1:nr, c ∈ 1:nc # NOTE: col major + l = layout[r, c] + if l isa GridLayout + n, sub = gaston_get_subplots(plt, n, l) + sps[r, c] = size(sub) == (1, 1) ? only(sub) : sub + else + sps[r, c] = get(l.attr, :blank, false) ? nothing : plt.subplots[n += 1] + end + end + n, sps +end + +gaston_init_subplots(plt, sps, level) = begin + sz = nr, nc = size(sps) + for c ∈ 1:nc, r ∈ 1:nr # NOTE: row major + sp = sps[r, c] + if sp isa Subplot || sp === nothing + gaston_init_subplot(plt, sp) + else + gaston_init_subplots(plt, sp, level + 1) + sz = max.(sz, size(sp)) + end + end + sz +end + function gaston_init_subplot(plt::Plot{GastonBackend}, sp::Subplot{GastonBackend}) if sp === nothing push!(plt.o.subplots, sp) else - dims = RecipesPipeline.is3d(sp) ? 3 : 2 - - axesconf = gaston_parse_axes_args(plt, sp) # Gnuplot string - sp.o = GastonSubplot(dims=dims, axesconf=axesconf, curves=[]) + sp.o = GastonSubplot( + dims=RecipesPipeline.is3d(sp) ? 3 : 2, + axesconf=gaston_parse_axes_args(plt, sp), # Gnuplot string + curves=[] + ) push!(plt.o.subplots, sp.o) end @@ -129,45 +149,91 @@ function gaston_add_series(plt::Plot{GastonBackend}, series::Series) sp = series[:subplot] g_sp = sp.o # Gaston subplot object - seriesconf = gaston_parse_series_args(series) # Gnuplot string - c = G.Curve(series[:x], series[:y], nothing, nothing, seriesconf ) + if series[:seriestype] ∈ (:heatmap, :contour) && g_sp.dims == 2 + g_sp.dims = 3 # FIXME: this is ugly, we need heatmap to use splot, not plot + end + + x = series[:x] + y = series[:y] + z = g_sp.dims == 2 ? nothing : series[:z] + if z isa Surface + z = z.surf + end + + seriesconf = gaston_seriesconf!(sp, series) # Gnuplot string + c = G.Curve(x, y, z, nothing, seriesconf) isfirst = length(g_sp.curves) == 0 ? true : false push!(g_sp.curves, c) G.write_data(c, g_sp.dims, g_sp.datafile, append = isfirst ? false : true) end -function gaston_parse_series_args(series::Series) +gaston_lc_ls_lw(series::Series) = ( + gaston_color(series[:linecolor], series[:linealpha]), + gaston_linestyle(series[:linestyle]), + series[:linewidth], +) + +gaston_mk_ms_mc(series::Series) = ( + gaston_marker(series[:markershape]), + series[:markersize] * GASTON_MARKER_SCALING, + gaston_color(series[:markercolor], series[:markeralpha]), +) + +gaston_palette(gradient) = begin + palette = String[] + n = -1 + for rgba ∈ gradient # FIXME: naive conversion, inefficient ? + push!(palette, "$(n += 1) $(rgba.r) $(rgba.g) $(rgba.b)") + end + '(' * join(palette, ", ") * ')' +end + +function gaston_seriesconf!(sp, series::Series) + gsp = sp.o curveconf = String[] st = series[:seriestype] - if st == :scatter - pt = gaston_marker(series[:markershape]) - ps = series[:markersize] * GASTON_MARKER_SCALING - lc = gaston_color(series[:markercolor]) - # alpha = series[:markeralpha] # TODO merge alpha with rgb color + clims = get_clims(sp, series) + if st ∈ (:scatter, :scatter3d) + pt, ps, lc = gaston_mk_ms_mc(series) push!(curveconf, """with points pt $pt ps $ps lc $lc""") - elseif st == :path - lc = gaston_color(series[:linecolor]) - dt = gaston_linestyle(series[:linestyle]) - lw = series[:linewidth] - # alpha = series[:linealpha] # TODO merge alpha with rgb color + 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""") else - pt = gaston_marker(series[:markershape]) - ps = series[:markersize] * GASTON_MARKER_SCALING + pt, ps = gaston_mk_ms_mc(series) push!(curveconf, """with lp lc $lc dt $dt lw $lw pt $pt ps $ps""") end elseif st == :shape - fc = gaston_color(series[:fillcolor]) + fc = gaston_color(series[:fillcolor], series[:fillalpha]) fs = "solid" - lc = gaston_color(series[:linecolor]) + lc, _ = gaston_lc_ls_lw(series) push!(curveconf, """with filledcurves fc $fc fs $fs border lc $lc""") elseif st == :steppre push!(curveconf, """with steps""") elseif st == :steppost push!(curveconf, """with fsteps""") # Not sure if not the other way + elseif st ∈ (:contour, :contour3d) + push!(curveconf, """with lines""") + if st == :contour + gsp.axesconf *= """\nset view map\nunset surface""" + end + levels = join(map(string, collect(contour_levels(series, clims))), ", ") + gsp.axesconf *= """\nset contour base\nset cntrparam levels discrete $levels """ + elseif st ∈ (:surface, :heatmap) + palette = gaston_palette(series[:seriescolor]) + gsp.axesconf *= """\nset palette model RGB defined $palette""" + if st == :heatmap + gsp.axesconf *= """\nset view map""" + end + push!(curveconf, """with pm3d""") + elseif st == :wireframe + lc, dt, lw = gaston_lc_ls_lw(series) + push!(curveconf, """with lines lc $lc dt $dt lw $lw""") + else + @warn "Gaston: $st is not implemented yet" end # label @@ -197,7 +263,7 @@ function gaston_parse_axes_args(plt::Plot{GastonBackend}, sp::Subplot{GastonBack push!(axesconf, """set $(letter)tics $(axis_attr[:tick_direction])""") mirror = axis_attr[:mirror] ? "mirror" : "nomirror" - push!(axesconf, """set $(letter)tics $(mirror) """) + push!(axesconf, """set $(letter)tics $(mirror) """) logscale = if axis_attr[:scale] == :identity "nologscale" @@ -215,7 +281,14 @@ function gaston_parse_axes_args(plt::Plot{GastonBackend}, sp::Subplot{GastonBack ticks = get_ticks(sp, axis_attr) gaston_set_ticks!(axesconf, ticks, letter) end - # set title {""} {offset } {font "{,}"}{{textcolor | tc} { | default}} {{no}enhanced}1 + + ratio = get_aspect_ratio(sp) + if ratio != :none + if ratio == :equal + ratio = -1 + end + push!(axesconf, """set size ratio $ratio""") + end end gaston_set_legend!(axesconf, sp) # Set legend params @@ -317,7 +390,11 @@ function gaston_marker(marker) return 1 end -gaston_color(color) = """rgb "#$(hex(color, :rrggbb))" """ +gaston_color(color, alpha=0.) = begin + col = single_color(color) # in case of gradients + col = alphacolor(col, alpha == nothing ? 0. : alpha) # add a default alpha if non existent + """rgb "#$(hex(col, :aarrggbb))" """ +end function gaston_linestyle(style) style == :solid && return "1" diff --git a/src/examples.jl b/src/examples.jl index 03d4ae4f..f8c9324f 100644 --- a/src/examples.jl +++ b/src/examples.jl @@ -1242,7 +1242,14 @@ _backend_skips = Dict( ], :inspectdr => [4, 6, 10, 22, 24, 28, 30, 38, 43, 45, 47, 48, 49, 50, 51, 55], :unicodeplots => [6, 10, 22, 24, 28, 38, 43, 45, 47, 49, 50, 51, 55], - :gaston => [2, 4, 5, 6, 10, 22, 24, 28, 31, 32, 35, 38, 43, 45, 47, 48, 49, 50, 51, 55], + :gaston => [ + 2, 4, 6, + 16, # TODO: support nested layouts + 27, # TODO: support polar + 30, 31, 47, 48, + 49, # TODO: support polar + 50, 51, 55 + ], ) From b801b2b2728d37d2a86d007b662a9874588abb26 Mon Sep 17 00:00:00 2001 From: t-bltg Date: Mon, 26 Jul 2021 11:20:05 +0200 Subject: [PATCH 2/2] explicit function return, decl - add comments - cleanup --- src/backends/gaston.jl | 112 ++++++++++++++++++++--------------------- src/examples.jl | 13 +++-- 2 files changed, 65 insertions(+), 60 deletions(-) diff --git a/src/backends/gaston.jl b/src/backends/gaston.jl index c6a19480..0ac8a7b7 100644 --- a/src/backends/gaston.jl +++ b/src/backends/gaston.jl @@ -4,11 +4,11 @@ const GastonSubplot = G.Plot const GASTON_MARKER_SCALING = 1.3 / 5.0 const GNUPLOT_DPI = 72 # Compensate for DPI with increased resolution -# + # -------------------------------------------- # These functions are called by Plots # -------------------------------------------- -# + # Create the window/figure for this backend. function _create_backend_figure(plt::Plot{GastonBackend}) xsize, ysize = plt.attr[:size] @@ -26,20 +26,23 @@ function _before_layout_calcs(plt::Plot{GastonBackend}) n, sps = gaston_get_subplots(plt, 0, plt.layout) @assert n == length(plt.subplots) + # FIXME: find a way to support nested layouts + # e.g. figures spanning multiple rows/cols plt.o.layout = gaston_init_subplots(plt, sps) # Then add the series (curves in gaston) for series in plt.series_list gaston_add_series(plt, series) end + nothing end function _update_min_padding!(sp::Subplot{GastonBackend}) + # FIXME: make this more flexible sp.minpad = (20mm, 5mm, 2mm, 10mm) end -function _update_plot_object(plt::Plot{GastonBackend}) -end +_update_plot_object(plt::Plot{GastonBackend}) = nothing for (mime, term) in ( "application/eps" => "epscairo", # NEED fixing TODO @@ -53,21 +56,19 @@ for (mime, term) in ( ) @eval function _show(io::IO, ::MIME{Symbol($mime)}, plt::Plot{GastonBackend}) xsize, ysize = plt.attr[:size] - termopts="""size $xsize,$ysize""" + termopts = """size $xsize,$ysize""" tmpfile = G.tempname() * "." * $term - G.save( - term = $term, - output = tmpfile, - handle = plt.o.handle, - saveopts = termopts + term=$term, + output=tmpfile, + handle=plt.o.handle, + saveopts=termopts ) while !isfile(tmpfile) end # avoid race condition with read in next line write(io, read(tmpfile)) rm(tmpfile, force=true) nothing - end end @@ -76,15 +77,14 @@ function _show(io::IO, mime::MIME{Symbol("image/png")}, plt::Plot{GastonBackend} xsize, ysize = plt.attr[:size] .* scaling # Scale all plot elements to match Plots.jl DPI standard - termopts="""size $xsize,$ysize fontscale $scaling lw $scaling dl $scaling ps $scaling""" + termopts = """size $xsize,$ysize fontscale $scaling lw $scaling dl $scaling ps $scaling""" tmpfile = G.tempname() - G.save( - term = "pngcairo", - output = tmpfile, - handle = plt.o.handle, - saveopts = termopts + term="pngcairo", + output=tmpfile, + handle=plt.o.handle, + saveopts=termopts ) while !isfile(tmpfile) end # avoid race condition with read in next line write(io, read(tmpfile)) @@ -92,16 +92,13 @@ function _show(io::IO, mime::MIME{Symbol("image/png")}, plt::Plot{GastonBackend} nothing end - -function _display(plt::Plot{GastonBackend}) - display(plt.o) -end +_display(plt::Plot{GastonBackend}) = display(plt.o) # -------------------------------------------- # These functions are gaston specific # -------------------------------------------- -gaston_get_subplots(plt, n, layout, level) = begin +function gaston_get_subplots(plt, n, layout, level) nr, nc = size(layout) sps = Array{Any}(undef, nr, nc) for r ∈ 1:nr, c ∈ 1:nc # NOTE: col major @@ -113,10 +110,10 @@ gaston_get_subplots(plt, n, layout, level) = begin sps[r, c] = get(l.attr, :blank, false) ? nothing : plt.subplots[n += 1] end end - n, sps + return n, sps end -gaston_init_subplots(plt, sps, level) = begin +function gaston_init_subplots(plt, sps, level) sz = nr, nc = size(sps) for c ∈ 1:nc, r ∈ 1:nr # NOTE: row major sp = sps[r, c] @@ -127,7 +124,7 @@ gaston_init_subplots(plt, sps, level) = begin sz = max.(sz, size(sp)) end end - sz + return sz end function gaston_init_subplot(plt::Plot{GastonBackend}, sp::Subplot{GastonBackend}) @@ -139,9 +136,9 @@ function gaston_init_subplot(plt::Plot{GastonBackend}, sp::Subplot{GastonBackend axesconf=gaston_parse_axes_args(plt, sp), # Gnuplot string curves=[] ) - push!(plt.o.subplots, sp.o) end + nothing end function gaston_add_series(plt::Plot{GastonBackend}, series::Series) @@ -150,7 +147,7 @@ function gaston_add_series(plt::Plot{GastonBackend}, series::Series) g_sp = sp.o # Gaston subplot object if series[:seriestype] ∈ (:heatmap, :contour) && g_sp.dims == 2 - g_sp.dims = 3 # FIXME: this is ugly, we need heatmap to use splot, not plot + g_sp.dims = 3 # FIXME: this is ugly, we need heatmap/contour to use splot, not plot end x = series[:x] @@ -166,27 +163,7 @@ function gaston_add_series(plt::Plot{GastonBackend}, series::Series) isfirst = length(g_sp.curves) == 0 ? true : false push!(g_sp.curves, c) G.write_data(c, g_sp.dims, g_sp.datafile, append = isfirst ? false : true) -end - -gaston_lc_ls_lw(series::Series) = ( - gaston_color(series[:linecolor], series[:linealpha]), - gaston_linestyle(series[:linestyle]), - series[:linewidth], -) - -gaston_mk_ms_mc(series::Series) = ( - gaston_marker(series[:markershape]), - series[:markersize] * GASTON_MARKER_SCALING, - gaston_color(series[:markercolor], series[:markeralpha]), -) - -gaston_palette(gradient) = begin - palette = String[] - n = -1 - for rgba ∈ gradient # FIXME: naive conversion, inefficient ? - push!(palette, "$(n += 1) $(rgba.r) $(rgba.g) $(rgba.b)") - end - '(' * join(palette, ", ") * ')' + nothing end function gaston_seriesconf!(sp, series::Series) @@ -238,7 +215,6 @@ function gaston_seriesconf!(sp, series::Series) # label push!(curveconf, """title "$(series[:label])" """) - return join(curveconf, " ") end @@ -246,8 +222,7 @@ function gaston_parse_axes_args(plt::Plot{GastonBackend}, sp::Subplot{GastonBack axesconf = String[] # Standard 2d axis if !ispolar(sp) && !RecipesPipeline.is3d(sp) - # TODO - # configure grid, axis spines, thickness + # TODO: configure grid, axis spines, thickness end for letter in (:x, :y, :z) @@ -301,9 +276,8 @@ function gaston_parse_axes_args(plt::Plot{GastonBackend}, sp::Subplot{GastonBack end function gaston_set_ticks!(axesconf, ticks, letter) - ticks == :auto && return - if ticks == :none || ticks === nothing || ticks == false + if ticks ∈ (:none, nothing, false) push!(axesconf, """unset $(letter)tics """) return end @@ -320,7 +294,6 @@ function gaston_set_ticks!(axesconf, ticks, letter) axesconf, "set $(letter)tics (" * join(gaston_tick_string, ", ") * ")" ) - elseif ttype == :ticks_and_labels tick_locations = @view ticks[1][:] tick_labels = @view ticks[2][:] @@ -334,10 +307,10 @@ function gaston_set_ticks!(axesconf, ticks, letter) axesconf, "set $(letter)tics (" * join(gaston_tick_string, ", ") * ")" ) - else error("Invalid input for $(letter)ticks: $ticks") end + nothing end function gaston_set_legend!(axesconf, sp) @@ -370,7 +343,32 @@ function gaston_set_legend!(axesconf, sp) push!(axesconf, "set key off") end + nothing +end +# -------------------------------------------- +# Helpers +# -------------------------------------------- + +gaston_lc_ls_lw(series::Series) = ( + gaston_color(series[:linecolor], series[:linealpha]), + gaston_linestyle(series[:linestyle]), + series[:linewidth], +) + +gaston_mk_ms_mc(series::Series) = ( + gaston_marker(series[:markershape]), + series[:markersize] * GASTON_MARKER_SCALING, + gaston_color(series[:markercolor], series[:markeralpha]), +) + +function gaston_palette(gradient) + palette = String[] + n = -1 + for rgba ∈ gradient # FIXME: naive conversion, inefficient ? + push!(palette, "$(n += 1) $(rgba.r) $(rgba.g) $(rgba.b)") + end + return '(' * join(palette, ", ") * ')' end function gaston_marker(marker) @@ -390,10 +388,10 @@ function gaston_marker(marker) return 1 end -gaston_color(color, alpha=0.) = begin +function gaston_color(color, alpha=0.) col = single_color(color) # in case of gradients col = alphacolor(col, alpha == nothing ? 0. : alpha) # add a default alpha if non existent - """rgb "#$(hex(col, :aarrggbb))" """ + return """rgb "#$(hex(col, :aarrggbb))" """ end function gaston_linestyle(style) diff --git a/src/examples.jl b/src/examples.jl index f8c9324f..15d87009 100644 --- a/src/examples.jl +++ b/src/examples.jl @@ -1243,12 +1243,19 @@ _backend_skips = Dict( :inspectdr => [4, 6, 10, 22, 24, 28, 30, 38, 43, 45, 47, 48, 49, 50, 51, 55], :unicodeplots => [6, 10, 22, 24, 28, 38, 43, 45, 47, 49, 50, 51, 55], :gaston => [ - 2, 4, 6, + 2, # animations + 4, # colors/palette issues + 6, # TODO: support embedded images 16, # TODO: support nested layouts 27, # TODO: support polar - 30, 31, 47, 48, + 30, # uses StatsPlots, deprecated ? + 31, # animations + 47, # TODO: support mesh3d + 48, # TODO: vector of shapes, ... 49, # TODO: support polar - 50, 51, 55 + 50, # TODO: 1D data not supported for pm3d + 51, # TODO: support embedded images + 55, # TODO: scaling is ugly ], )