Fixed recipe interface; DatasetBin methods for images are now implemented as recipes

This commit is contained in:
Giorgio Calderone 2020-04-14 12:21:34 +02:00
parent 7a647f08e1
commit f8d239d9c2
3 changed files with 145 additions and 130 deletions

View File

@ -15,39 +15,39 @@ export session_names, dataset_names, palette_names, linetypes, palette,
# │ TYPE DEFINITIONS │ # │ TYPE DEFINITIONS │
# ╰───────────────────────────────────────────────────────────────────╯ # ╰───────────────────────────────────────────────────────────────────╯
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
abstract type DataSet end abstract type Dataset end
mutable struct DataSetText <: DataSet mutable struct DatasetText <: Dataset
preview::Vector{String} preview::Vector{String}
data::String data::String
DataSetText(::Val{:inner}, preview, data) = new(preview, data) DatasetText(::Val{:inner}, preview, data) = new(preview, data)
end end
mutable struct DataSetBin <: DataSet mutable struct DatasetBin <: Dataset
file::String file::String
source::String source::String
DataSetBin(::Val{:inner}, file, source) = new(file, source) DatasetBin(::Val{:inner}, file, source) = new(file, source)
end end
struct PlotRecipe struct PlotElements
mid::Int mid::Int
cmds::Vector{String} cmds::Vector{String}
data::Vector{DataSet} data::Vector{Dataset}
plot::Vector{String} plot::Vector{String}
function PlotRecipe(;mid::Int=0, function PlotElements(;mid::Int=0,
cmds::Union{String, Vector{String}}=Vector{String}(), cmds::Union{String, Vector{String}}=Vector{String}(),
data::Union{DataSet, Vector{DataSet}}=Vector{DataSet}(), data::Union{Dataset, Vector{Dataset}}=Vector{Dataset}(),
plot::Union{String, Vector{String}}="", plot::Union{String, Vector{String}}="",
kwargs...) kwargs...)
c = isa(cmds, String) ? [cmds] : cmds c = isa(cmds, String) ? [cmds] : cmds
append!(c, parseKeywords(; kwargs...)) append!(c, parseKeywords(; kwargs...))
new(mid, c, new(mid, c,
isa(data, DataSet) ? [data] : data, isa(data, Dataset) ? [data] : data,
isa(plot, String) ? [plot] : plot) isa(plot, String) ? [plot] : plot)
end end
end end
plotrecipe() = nothing recipe() = nothing
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
@ -64,7 +64,7 @@ abstract type Session end
mutable struct DrySession <: Session mutable struct DrySession <: Session
sid::Symbol # session ID sid::Symbol # session ID
datas::OrderedDict{String, DataSet} # data sets datas::OrderedDict{String, Dataset} # data sets
plots::Vector{SinglePlot} # commands and plot commands (one entry for each plot of the multiplot) plots::Vector{SinglePlot} # commands and plot commands (one entry for each plot of the multiplot)
curmid::Int # current multiplot ID curmid::Int # current multiplot ID
end end
@ -73,7 +73,7 @@ end
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
mutable struct GPSession <: Session mutable struct GPSession <: Session
sid::Symbol # session ID sid::Symbol # session ID
datas::OrderedDict{String, DataSet} # data sets datas::OrderedDict{String, Dataset} # data sets
plots::Vector{SinglePlot} # commands and plot commands (one entry for each plot of the multiplot) plots::Vector{SinglePlot} # commands and plot commands (one entry for each plot of the multiplot)
curmid::Int # current multiplot ID curmid::Int # current multiplot ID
pin::Base.Pipe; pin::Base.Pipe;
@ -307,7 +307,7 @@ end
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
function DrySession(sid::Symbol) function DrySession(sid::Symbol)
(sid in keys(sessions)) && error("Gnuplot session $sid is already active") (sid in keys(sessions)) && error("Gnuplot session $sid is already active")
out = DrySession(sid, OrderedDict{String, DataSet}(), [SinglePlot()], 1) out = DrySession(sid, OrderedDict{String, Dataset}(), [SinglePlot()], 1)
sessions[sid] = out sessions[sid] = out
return out return out
end end
@ -470,9 +470,9 @@ function write(gp::GPSession, str::AbstractString)
end end
write(gp::DrySession, name::String, d::DataSet) = nothing write(gp::DrySession, name::String, d::Dataset) = nothing
write(gp::GPSession, name::String, d::DataSetBin) = nothing write(gp::GPSession, name::String, d::DatasetBin) = nothing
function write(gp::GPSession, name::String, d::DataSetText) function write(gp::GPSession, name::String, d::DatasetText)
if options.verbose if options.verbose
printstyled(color=:light_black, "GNUPLOT ($(gp.sid)) ", name, " << EOD\n") printstyled(color=:light_black, "GNUPLOT ($(gp.sid)) ", name, " << EOD\n")
printstyled(color=:light_black, join("GNUPLOT ($(gp.sid)) " .* d.preview, "\n") * "\n") printstyled(color=:light_black, join("GNUPLOT ($(gp.sid)) " .* d.preview, "\n") * "\n")
@ -530,7 +530,7 @@ end
# ╭───────────────────────────────────────────────────────────────────╮ # ╭───────────────────────────────────────────────────────────────────╮
# │ DataSet CONSTRUCTORS │ # │ Dataset CONSTRUCTORS │
# ╰───────────────────────────────────────────────────────────────────╯ # ╰───────────────────────────────────────────────────────────────────╯
#= #=
@ -555,80 +555,27 @@ end
=# =#
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
function DataSetBin(M::Matrix{T}) where T <: Number function DatasetBin(VM::Vararg{AbstractMatrix{T}, N}) where {T <: Number, N}
for i in 2:N
@assert size(VM[i]) == size(VM[1])
end
s = size(VM[1])
(path, io) = mktemp() (path, io) = mktemp()
for j in 1:size(M)[2] for j in 1:s[2]
for i in 1:size(M)[1] for i in 1:s[1]
write(io, Float32(M[i,j])) for k in 1:N
write(io, Float32(VM[k][i,j]))
end
end end
end end
close(io) close(io)
source = " '$path' binary array=(" * join(string.(size(M)), ", ") * ")" source = " '$path' binary array=(" * join(string.(s), ", ") * ")"
return DataSetBin(Val(:inner), path, source) return DatasetBin(Val(:inner), path, source)
end end
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
function DataSetBin(M::Matrix{ColorTypes.RGB{T}}) where T function DatasetBin(cols::Vararg{AbstractVector, N}) where N
(path, io) = mktemp()
for j in 1:size(M)[2]
for i in 1:size(M)[1]
write(io, Float32(256 * M[i,j].r))
write(io, Float32(256 * M[i,j].g))
write(io, Float32(256 * M[i,j].b))
end
end
close(io)
source = " '$path' binary array=(" * join(string.(size(M)), ", ") * ")"
return DataSetBin(Val(:inner), path, source)
end
# ---------------------------------------------------------------------
function DataSetBin(M::Matrix{ColorTypes.RGBA{T}}) where T
(path, io) = mktemp()
for j in 1:size(M)[2]
for i in 1:size(M)[1]
write(io, Float32(256 * M[i,j].r))
write(io, Float32(256 * M[i,j].g))
write(io, Float32(256 * M[i,j].b))
end
end
close(io)
source = " '$path' binary array=(" * join(string.(size(M)), ", ") * ")"
return DataSetBin(Val(:inner), path, source)
end
# ---------------------------------------------------------------------
function DataSetBin(M::Matrix{ColorTypes.Gray{T}}) where T
(path, io) = mktemp()
for j in 1:size(M)[2]
for i in 1:size(M)[1]
write(io, Float32(256 * M[i,j].val))
end
end
close(io)
source = " '$path' binary array=(" * join(string.(size(M)), ", ") * ")"
return DataSetBin(Val(:inner), path, source, "")
end
# ---------------------------------------------------------------------
function DataSetBin(M::Matrix{ColorTypes.GrayA{T}}) where T
(path, io) = mktemp()
for j in 1:size(M)[2]
for i in 1:size(M)[1]
write(io, Float32(256 * M[i,j].val))
end
end
close(io)
source = " '$path' binary array=(" * join(string.(size(M)), ", ") * ")"
return DataSetBin(Val(:inner), path, source)
end
# ---------------------------------------------------------------------
function DataSetBin(cols::Vararg{AbstractVector, N}) where N
source = "binary record=$(length(cols[1])) format='" source = "binary record=$(length(cols[1])) format='"
types = Vector{DataType}() types = Vector{DataType}()
(length(cols) == 1) && (source *= "%int") (length(cols) == 1) && (source *= "%int")
@ -654,15 +601,15 @@ function DataSetBin(cols::Vararg{AbstractVector, N}) where N
end end
end end
close(io) close(io)
return DataSetBin(Val(:inner), path, source) return DatasetBin(Val(:inner), path, source)
end end
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
DataSetText(args...) = DataSetText(arrays2datablock(args...)) DatasetText(args...) = DatasetText(arrays2datablock(args...))
function DataSetText(data::Vector{String}) function DatasetText(data::Vector{String})
preview = (length(data) <= 4 ? deepcopy(data) : [data[1:4]..., "..."]) preview = (length(data) <= 4 ? deepcopy(data) : [data[1:4]..., "..."])
d = DataSetText(Val(:inner), preview, join(data, "\n")) d = DatasetText(Val(:inner), preview, join(data, "\n"))
return d return d
end end
@ -673,7 +620,7 @@ end
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
function reset(gp::Session) function reset(gp::Session)
delete_binaries(gp) delete_binaries(gp)
gp.datas = OrderedDict{String, DataSet}() gp.datas = OrderedDict{String, Dataset}()
gp.plots = [SinglePlot()] gp.plots = [SinglePlot()]
gp.curmid = 1 gp.curmid = 1
gpexec(gp, "set output") gpexec(gp, "set output")
@ -695,7 +642,7 @@ end
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
newDataSetName(gp::Session) = string("\$data", length(gp.datas)+1) newDatasetName(gp::Session) = string("\$data", length(gp.datas)+1)
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
@ -737,7 +684,7 @@ end
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
function delete_binaries(gp::Session) function delete_binaries(gp::Session)
for (name, d) in gp.datas for (name, d) in gp.datas
if isa(d, DataSetBin) && (d.file != "") if isa(d, DatasetBin) && (d.file != "")
rm(d.file, force=true) rm(d.file, force=true)
end end
end end
@ -850,7 +797,7 @@ function savescript(gp::Session, filename; term::AbstractString="", output::Abst
path_to = Vector{String}() path_to = Vector{String}()
datapath = data_dirname(filename) datapath = data_dirname(filename)
for (name, d) in gp.datas for (name, d) in gp.datas
if isa(d, DataSetBin) && (d.file != "") if isa(d, DatasetBin) && (d.file != "")
if (length(path_from) == 0) if (length(path_from) == 0)
isdir(datapath) && rm(datapath, recursive=true) isdir(datapath) && rm(datapath, recursive=true)
mkdir(datapath) mkdir(datapath)
@ -886,7 +833,7 @@ function savescript(gp::Session, filename; term::AbstractString="", output::Abst
paths = copy_binary_files(gp, filename) paths = copy_binary_files(gp, filename)
for (name, d) in gp.datas for (name, d) in gp.datas
if isa(d, DataSetText) if isa(d, DatasetText)
println(stream, name * " << EOD") println(stream, name * " << EOD")
println(stream, d.data) println(stream, d.data)
println(stream, "EOD") println(stream, "EOD")
@ -929,7 +876,7 @@ function driver(args...; flag3d=false)
if cmd != "" if cmd != ""
for (name, d) in gp.datas for (name, d) in gp.datas
if isa(d, DataSetBin) && (d.file != "") if isa(d, DatasetBin) && (d.file != "")
cmd = replace(cmd, name => d.source) cmd = replace(cmd, name => d.source)
end end
end end
@ -973,35 +920,35 @@ function driver(args...; flag3d=false)
plotspec = nothing plotspec = nothing
function dataset_ready() function dataset_ready()
if length(dataAccum) > 0 if length(dataAccum) > 0
# Ensure DataSet objects are processed one at a time # Ensure Dataset objects are processed one at a time
for i in 1:length(dataAccum) for i in 1:length(dataAccum)
@assert !isa(dataAccum[i], DataSet) || (length(dataAccum) == 1) @assert !isa(dataAccum[i], Dataset) || (length(dataAccum) == 1)
end end
# Check if dataset is empty # Check if dataset is empty
emptyset = false emptyset = false
if !isa(dataAccum[1], DataSet) if !isa(dataAccum[1], Dataset)
mm = extrema(length.(dataAccum)) mm = extrema(length.(dataAccum))
(mm[1] == 0) && (@assert mm[1] == mm[2] "At least one input array is empty, while other(s) are not") (mm[1] == 0) && (@assert mm[1] == mm[2] "At least one input array is empty, while other(s) are not")
emptyset = (mm[2] == 0) emptyset = (mm[2] == 0)
end end
if !emptyset if !emptyset
if isa(dataAccum[1], DataSet) if isa(dataAccum[1], Dataset)
d = dataAccum[1] d = dataAccum[1]
else else
if sendAsBinary(dataAccum...) if sendAsBinary(dataAccum...)
d = DataSetBin(dataAccum...) d = DatasetBin(dataAccum...)
else else
d = DataSetText(dataAccum...) d = DatasetText(dataAccum...)
end end
end end
isnothing(dsetname) && (dsetname = newDataSetName(gp)) isnothing(dsetname) && (dsetname = newDatasetName(gp))
gp.datas[dsetname] = d gp.datas[dsetname] = d
write(gp, dsetname, d) # send now to gnuplot process write(gp, dsetname, d) # send now to gnuplot process
if !isnothing(plotspec) if !isnothing(plotspec)
if isa(d, DataSetBin) if isa(d, DatasetBin)
add_plot(gp, d.source * " " * plotspec) add_plot(gp, d.source * " " * plotspec)
else else
add_plot(gp, dsetname * " " * plotspec) add_plot(gp, dsetname * " " * plotspec)
@ -1054,7 +1001,7 @@ function driver(args...; flag3d=false)
push!(dataAccum, arg) push!(dataAccum, arg)
elseif isa(arg, Real) # ==> a dataset column with only one row elseif isa(arg, Real) # ==> a dataset column with only one row
push!(dataAccum, arg) push!(dataAccum, arg)
elseif isa(arg, DataSet) elseif isa(arg, Dataset)
push!(dataAccum, arg) push!(dataAccum, arg)
else else
error("Unexpected argument at position $iarg with type " * string(typeof(arg))) error("Unexpected argument at position $iarg with type " * string(typeof(arg)))
@ -1070,7 +1017,7 @@ end
function expandrecipes(args...; flag3d=false) function expandrecipes(args...; flag3d=false)
function push_recipe!(out::Vector{Any}, pr::PlotRecipe) function push_elements!(out::Vector{Any}, pr::PlotElements)
@assert length(pr.data) <= length(pr.plot) @assert length(pr.data) <= length(pr.plot)
(pr.mid > 0) && push!(out, pr.mid) (pr.mid > 0) && push!(out, pr.mid)
append!(out, pr.cmds) append!(out, pr.cmds)
@ -1079,19 +1026,22 @@ function expandrecipes(args...; flag3d=false)
push!(out, pr.plot[i]) push!(out, pr.plot[i])
end end
end end
function push_elements!(out::Vector{Any}, v::Vector{PlotElements})
for pr in v
push_elements!(out, pr)
end
end
out = Vector{Any}() out = Vector{Any}()
for arg in args for arg in args
if hasmethod(plotrecipe, tuple(typeof(arg))) if hasmethod(recipe, tuple(typeof(arg))) # implicit recipe
push_recipe!(out, plotrecipe(arg)) push_elements!(out, recipe(arg))
elseif isa(arg, PlotRecipe) elseif isa(arg, PlotElements) # explicit recipe (scalar)
push_recipe!(out, arg) push_elements!(out, arg)
elseif isa(arg, Vector{PlotRecipe}) elseif isa(arg, Vector{PlotElements}) # explicit recipe (vector)
for pr in arg push_elements!(out, pr)
push_recipe!(out, pr)
end
else else
push!(out, arg) push!(out, arg) # simple argument
end end
end end
driver(out...; flag3d=flag3d) driver(out...; flag3d=flag3d)
@ -1756,29 +1706,18 @@ function contourlines(args...; cntrparam="level auto 10")
end end
# ╭───────────────────────────────────────────────────────────────────╮
# │ RECIPES │
# ╰───────────────────────────────────────────────────────────────────╯
# --------------------------------------------------------------------
plotrecipe(h::Histogram1D) = PlotRecipe(cmds="set grid", data=DataSetText(h.bins, h.counts), plot="w histep notit lw 2 lc rgb 'black'")
plotrecipe(h::Histogram2D) = PlotRecipe(cmds="set autoscale fix", data=DataSetText(h.bins1, h.bins2, h.counts), plot="w image notit 'black'")
# ╭───────────────────────────────────────────────────────────────────╮ # ╭───────────────────────────────────────────────────────────────────╮
# │ GNUPLOT REPL │ # │ GNUPLOT REPL │
# ╰───────────────────────────────────────────────────────────────────╯ # ╰───────────────────────────────────────────────────────────────────╯
# -------------------------------------------------------------------- # --------------------------------------------------------------------
""" """
Gnuplot.init_repl(start_key='>') Gnuplot.init_repl(; start_key='>')
Install a hook to replace the common Julia REPL with a gnuplot one. The key to start the REPL is the one provided in `start_key` (default: `>`). Install a hook to replace the common Julia REPL with a gnuplot one. The key to start the REPL is the one provided in `start_key` (default: `>`).
Note: the gnuplot REPL operates only on the default session. Note: the gnuplot REPL operates only on the default session.
""" """
function repl_init(start_key='>') function repl_init(; start_key='>')
function repl_exec(s) function repl_exec(s)
for s in writeread(getsession(), s) for s in writeread(getsession(), s)
println(s) println(s)
@ -1800,4 +1739,9 @@ function repl_init(start_key='>')
valid_input_checker=repl_isvalid) valid_input_checker=repl_isvalid)
end end
include("recipes.jl")
end #module end #module

71
src/recipes.jl Normal file
View File

@ -0,0 +1,71 @@
# ╭───────────────────────────────────────────────────────────────────╮
# │ IMPLICIT RECIPES │
# ╰───────────────────────────────────────────────────────────────────╯
# --------------------------------------------------------------------
# Histograms
recipe(h::Histogram1D) =
PlotElements(cmds="set grid",
data=DatasetText(h.bins, h.counts),
plot="w histep notit lw 2 lc rgb 'black'")
recipe(h::Histogram2D) =
PlotElements(cmds=["set autoscale fix", "set size ratio -1"],
data=DatasetText(h.bins1, h.bins2, h.counts),
plot="w image notit")
# Images
recipe(M::Matrix{ColorTypes.RGB{T}}) where T =
PlotElements(cmds=["set autoscale fix", "set size square"],
data=DatasetBin(256 .* getfield.(M, :r),
256 .* getfield.(M, :g),
256 .* getfield.(M, :b)),
plot="rotate=-90deg with rgbimage notit")
recipe(M::Matrix{ColorTypes.RGBA{T}}) where T =
PlotElements(cmds=["set autoscale fix", "set size square"],
data=DatasetBin(256 .* getfield.(M, :r),
256 .* getfield.(M, :g),
256 .* getfield.(M, :b)),
plot="rotate=-90deg with rgbimage notit")
recipe(M::Matrix{ColorTypes.Gray{T}}) where T =
PlotElements(cmds=["set autoscale fix", "set size square"],
data=DatasetBin(256 .* getfield.(M, :val)),
plot="rotate=-90deg with image notit")
recipe(M::Matrix{ColorTypes.GrayA{T}}) where T =
PlotElements(cmds=["set autoscale fix", "set size square"],
data=DatasetBin(256 .* getfield.(M, :val)),
plot="rotate=-90deg with image notit")
# ╭───────────────────────────────────────────────────────────────────╮
# │ EXPLICIT RECIPES │
# ╰───────────────────────────────────────────────────────────────────╯
macro recipes_DataFrames()
return esc(:(
function plotdf(df::DataFrame, colx::Symbol, coly::Symbol; group=nothing);
if isnothing(group);
return Gnuplot.PlotElements(data=Gnuplot.DatasetText(df[:, colx], df[:, coly]),
plot="w p notit",
xlab=string(colx), ylab=string(coly));
end;
data = Vector{Gnuplot.Dataset}();
plot = Vector{String}();
for g in sort(unique(df[:, group]));
i = findall(df[:, group] .== g);
if length(i) > 0;
push!(data, Gnuplot.DatasetText(df[i, colx], df[i, coly]));
push!(plot, "w p t '$g'");
end;
end;
return Gnuplot.PlotElements(data=data, plot=plot,
xlab=string(colx), ylab=string(coly));
end
))
end

View File

@ -97,7 +97,7 @@ s = Gnuplot.arrays2datablock(1:3, 1:3, ["One", "Two", "Three"])
pal = palette(:deepsea) pal = palette(:deepsea)
@test pal == "set palette defined (0.0 '#2B004D', 0.25 '#4E0F99', 0.5 '#3C54D4', 0.75 '#48A9F8', 1.0 '#C5ECFF')\nset palette maxcol 5\n" @test pal == "set palette defined (0.0 '#2B004D', 0.25 '#4E0F99', 0.5 '#3C54D4', 0.75 '#48A9F8', 1.0 '#C5ECFF')\nset palette maxcol 5\n"
ls = linetypes(:deepsea) ls = linetypes(:deepsea)
@test ls == "set linetype 1 lc rgb '#2B004D\nset linetype 2 lc rgb '#4E0F99\nset linetype 3 lc rgb '#3C54D4\nset linetype 4 lc rgb '#48A9F8\nset linetype 5 lc rgb '#C5ECFF\nset linetype cycle 5\n" @test ls == "unset for [i=1:256] linetype i\nset linetype 1 lc rgb '#2B004D' lw 1 dt solid pt 1 ps default\nset linetype 2 lc rgb '#4E0F99' lw 1 dt solid pt 2 ps default\nset linetype 3 lc rgb '#3C54D4' lw 1 dt solid pt 3 ps default\nset linetype 4 lc rgb '#48A9F8' lw 1 dt solid pt 4 ps default\nset linetype 5 lc rgb '#C5ECFF' lw 1 dt solid pt 5 ps default\nset linetype cycle 5\n"
#----------------------------------------------------------------- #-----------------------------------------------------------------
# Test wth empty dataset # Test wth empty dataset