diff --git a/src/backends.jl b/src/backends.jl index 85ebfe98..c81e0645 100644 --- a/src/backends.jl +++ b/src/backends.jl @@ -256,6 +256,7 @@ end @init_backend PGFPlotsX @init_backend InspectDR @init_backend HDF5 +@init_backend Gaston # --------------------------------------------------------- @@ -574,6 +575,92 @@ const _pyplot_style = [:auto, :solid, :dash, :dot, :dashdot] const _pyplot_marker = vcat(_allMarkers, :pixel) const _pyplot_scale = [:identity, :ln, :log2, :log10] +# ------------------------------------------------------------------------------ +# Gaston + +function _initialize_backend(::GastonBackend) + @eval Main begin + import Gaston + export Gaston + end +end + +const _gaston_attr = merge_with_base_supported([ + # :annotations, + # :background_color_legend, + # :background_color_inside, + # :background_color_outside, + # :foreground_color_legend, + # :foreground_color_grid, :foreground_color_axis, + # :foreground_color_text, :foreground_color_border, + :label, + # :seriescolor, :seriesalpha, + :linecolor, :linestyle, :linewidth, :linealpha, + :markershape, :markercolor, :markersize, :markeralpha, + # :markerstrokewidth, :markerstrokecolor, :markerstrokealpha, :markerstrokestyle, + # :fillrange, :fillcolor, :fillalpha, + # :bins, + # :bar_width, :bar_edges, + # :title, + # :window_title, + :guide, + # :guide_position, + :lims, :ticks, :scale, # :flip, :rotation, + :tickfont, :guidefont, + # :legendfont, + # :grid, :legend, + # :colorbar, :colorbar_title, + # :fill_z, :line_z, :marker_z, :levels, + # :ribbon, :quiver, :arrow, + # :orientation, + # :overwrite_figure, + # :polar, + # :normalize, :weights, :contours, + # :aspect_ratio, + :tick_direction, + # :framestyle, + # :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_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 new file mode 100644 index 00000000..4b8cc384 --- /dev/null +++ b/src/backends/gaston.jl @@ -0,0 +1,331 @@ +# https://github.com/mbaz/Gaston. +const G = Gaston +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 = plt.attr[:size][1] + ysize = plt.attr[:size][2] + G.set(termopts="""size $xsize,$ysize""") + + state_handle = G.nexthandle() # for now all the figures will be kept + plt.o = G.newfigure(state_handle) +end + + +function _before_layout_calcs(plt::Plot{GastonBackend}) + # Initialize all the subplots first + plt.o.subplots = G.SubPlot[] + grid = size(plt.layout) + plt.o.layout = grid + + + for sp in plt.subplots + gaston_init_subplot(plt, sp) + end + + # Then add the series (curves in gaston) + for series in plt.series_list + gaston_add_series(plt, series) + end +end + +function _update_min_padding!(sp::Subplot{GastonBackend}) + sp.minpad = (20mm, 5mm, 2mm, 10mm) +end + +function _update_plot_object(plt::Plot{GastonBackend}) +end + +for (mime, term) in ( + "application/eps" => "epscairo", # NEED fixing TODO + "image/eps" => "epslatex", # NEED fixing TODO + "application/pdf" => "pdfcairo", # NEED fixing TODO + "application/postscript" => "postscript", # NEED fixing TODO + "image/svg+xml" => "svg", + "text/latex" => "tikz", # NEED fixing TODO + "application/x-tex" => "epslatex", # NEED fixing TODO + "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] + termopts="""size $xsize,$ysize""" + + tmpfile = G.tempname() * "." * $term + + G.save( + 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 + +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 + + # Scale all plot elements to match Plots.jl DPI standard + 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 + ) + while !isfile(tmpfile) end # avoid race condition with read in next line + write(io, read(tmpfile)) + rm(tmpfile, force=true) + nothing +end + + +function _display(plt::Plot{GastonBackend}) + display(plt.o) +end +# +# -------------------------------------------- +# These functions are gaston specific +# -------------------------------------------- +# +function gaston_init_subplot(plt::Plot{GastonBackend}, sp::Subplot{GastonBackend}) + dims = RecipesPipeline.is3d(sp) ? 3 : 2 + + axesconf = gaston_parse_axes_args(plt, sp) # Gnuplot string + sp.o = GastonSubplot(dims=dims, axesconf = axesconf, curves = []) + push!(plt.o.subplots, sp.o) + +end + +function gaston_add_series(plt::Plot{GastonBackend}, series::Series) + # Gaston.Curve = Plots.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 ) + + 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) + 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 + 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 + 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 + push!(curveconf, """with lp lc $lc dt $dt lw $lw pt $pt ps $ps""") + end + elseif st == :shape + fc = gaston_color(series[:fillcolor]) + fs = "solid" + lc = gaston_color(series[:linecolor]) + 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 + end + + # label + push!(curveconf, """title "$(series[:label])" """) + + return join(curveconf, " ") +end + +function gaston_parse_axes_args(plt::Plot{GastonBackend}, sp::Subplot{GastonBackend}) + axesconf = String[] + # Standard 2d axis + if !ispolar(sp) && !RecipesPipeline.is3d(sp) + # TODO + # configure grid, axis spines, thickness + end + + for letter in (:x, :y, :z) + axis_attr = sp.attr[Symbol(letter, :axis)] + # label names + push!(axesconf, """set $(letter)label "$(axis_attr[:guide])" """) + push!(axesconf, """set $(letter)label font "$(axis_attr[:guidefontfamily]), $(axis_attr[:guidefontsize])" """) + + # Handle ticks + # ticksyle + push!(axesconf, """set $(letter)tics font "$(axis_attr[:tickfontfamily]), $(axis_attr[:tickfontsize])" """) + push!(axesconf, """set $(letter)tics textcolor rgb "#$(hex(axis_attr[:tickfontcolor], :rrggbb))" """) + push!(axesconf, """set $(letter)tics $(axis_attr[:tick_direction])""") + + mirror = axis_attr[:mirror] ? "mirror" : "nomirror" + push!(axesconf, """set $(letter)tics $(mirror) """) + + logscale = if axis_attr[:scale] == :identity + "nologscale" + elseif axis_attr[:scale] == :log10 + "logscale" + end + push!(axesconf, """set $logscale $(letter)""") + + # tick locations + if axis_attr[:ticks] != :native + # axis limits + from, to = axis_limits(sp, letter) + push!(axesconf, """set $(letter)range [$from:$to]""") + + ticks = get_ticks(sp, axis_attr) + gaston_set_ticks!(axesconf, ticks, letter) + end + # set title {""} {offset } {font "{,}"}{{textcolor | tc} { | default}} {{no}enhanced}1 + end + gaston_set_legend!(axesconf, sp) # Set legend params + + if sp[:title] != nothing + push!(axesconf, """set title '$(sp[:title])' """) + push!(axesconf, """set title font '$(sp[:titlefontfamily]), $(sp[:titlefontsize])' """) + end + + return join(axesconf, "\n") +end + +function gaston_set_ticks!(axesconf, ticks, letter) + + ticks == :auto && return + if ticks == :none || ticks === nothing || ticks == false + push!(axesconf, """unset $(letter)tics """) + return + end + + ttype = ticksType(ticks) + if ttype == :ticks + tick_locations = @view ticks[:] + gaston_tick_string = [] + for i in eachindex(tick_locations) + lac = tick_locations[i] + push!(gaston_tick_string, "$loc") + end + push!( + axesconf, + "set $(letter)tics (" * join(gaston_tick_string, ", ") * ")" + ) + + elseif ttype == :ticks_and_labels + tick_locations = @view ticks[1][:] + tick_labels = @view ticks[2][:] + gaston_tick_string = [] + for i in eachindex(tick_locations) + loc = tick_locations[i] + lab = gaston_enclose_tick_string(tick_labels[i]) + push!(gaston_tick_string, "'$lab' $loc") + end + push!( + axesconf, + "set $(letter)tics (" * join(gaston_tick_string, ", ") * ")" + ) + + else + error("Invalid input for $(letter)ticks: $ticks") + end +end + +function gaston_set_legend!(axesconf, sp) + leg = sp[:legend] + if !(sp[:legend] in(:none, :inline)) + if leg == :best + leg = :topright + end + + if occursin("outer", string(leg)) + push!(axesconf, "set key outside") + else + push!(axesconf, "set key inside") + end + positions = ["top", "bottom", "left", "right"] + for position in positions + if occursin(position, string(leg)) + push!(axesconf, "set key $position") + end + end + if sp[:legendtitle] != nothing + push!(axesconf, "set key title '$(sp[:legendtitle])'") + end + push!(axesconf, "set key box linewidth 1") + push!(axesconf, "set key opaque") + + push!(axesconf, "set border back") + push!(axesconf, """set key font "$(sp[:legendfontfamily]), $(sp[:legendfontsize])" """) + else + push!(axesconf, "set key off") + + end + +end + +function gaston_marker(marker) + marker == :none && return -1 + marker == :circle && return 7 + marker == :rect && return 5 + marker == :diamond && return 28 + marker == :utriangle && return 9 + marker == :dtriangle && return 11 + marker == :+ && return 1 + marker == :x && return 2 + marker == :star5 && return 3 + marker == :pentagon && return 15 + marker == :pixel && return 0 + + @warn("Unsupported marker $marker") + return 1 +end + +gaston_color(color) = """rgb "#$(hex(color, :rrggbb))" """ + +function gaston_linestyle(style) + style == :solid && return "1" + style == :dash && return "2" + style == :dot && return "3" + style == :dashdot && return "4" + style == :dashdotdot && return "5" +end + +function gaston_enclose_tick_string(tick_string) + if findfirst("^", tick_string) == nothing + return tick_string + end + + + base, power = split(tick_string, "^") + power = string("{", power, "}") + return string(base, "^", power) +end diff --git a/src/init.jl b/src/init.jl index 22bb4b51..8b17f5b9 100644 --- a/src/init.jl +++ b/src/init.jl @@ -70,6 +70,11 @@ function __init__() include(fn) end + @require Gaston = "4b11ee91-296f-5714-9832-002c20994614" begin + fn = joinpath(@__DIR__, "backends", "gaston.jl") + include(fn) + end + @require IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" begin if IJulia.inited _init_ijulia_plotting()