Refactored parsing of input arguments

This commit is contained in:
Giorgio Calderone 2020-04-15 01:37:54 +02:00
parent 0e147f8c13
commit 44a7ea2ecd
3 changed files with 333 additions and 231 deletions

View File

@ -5,18 +5,23 @@ using REPL, ReplMaker
import Base.reset import Base.reset
import Base.write import Base.write
import Base.display
export session_names, dataset_names, palette_names, linetypes, palette, export session_names, dataset_names, palette_names, linetypes, palette,
terminal, terminals, test_terminal, terminal, terminals, test_terminal,
stats, @gp, @gsp, save, gpexec, stats, @gp, @gsp, save, gpexec,
boxxyerror, contourlines, hist boxxyerror, contourlines, hist, recipe
# ╭───────────────────────────────────────────────────────────────────╮ # ╭───────────────────────────────────────────────────────────────────╮
# │ TYPE DEFINITIONS │ # │ TYPE DEFINITIONS │
# │ User data representation │
# ╰───────────────────────────────────────────────────────────────────╯ # ╰───────────────────────────────────────────────────────────────────╯
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
abstract type Dataset end abstract type Dataset end
struct DatasetEmpty <: Dataset
end
mutable struct DatasetText <: Dataset mutable struct DatasetText <: Dataset
preview::Vector{String} preview::Vector{String}
data::String data::String
@ -29,32 +34,60 @@ mutable struct DatasetBin <: Dataset
DatasetBin(::Val{:inner}, file, source) = new(file, source) DatasetBin(::Val{:inner}, file, source) = new(file, source)
end end
struct PlotElements # ---------------------------------------------------------------------
mutable struct PlotElement
mid::Int mid::Int
is3d::Bool
cmds::Vector{String} cmds::Vector{String}
data::Vector{Dataset} name::String
data::Dataset
plot::Vector{String} plot::Vector{String}
function PlotElements(;mid::Int=0, function PlotElement(;mid::Int=0, is3d::Bool=false,
cmds::Union{String, Vector{String}}=Vector{String}(), cmds::Union{String, Vector{String}}=Vector{String}(),
data::Union{Dataset, Vector{Dataset}}=Vector{Dataset}(), name::String="",
plot::Union{String, Vector{String}}="", data::Dataset=DatasetEmpty(),
plot::Union{String, Vector{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, is3d, deepcopy(c), name, data,
isa(data, Dataset) ? [data] : data, isa(plot, String) ? [plot] : deepcopy(plot))
isa(plot, String) ? [plot] : plot)
end end
end end
recipe() = nothing
function display(v::PlotElement)
if isa(v.data, DatasetText)
data = "DatasetText: \n" * join(v.data.preview, "\n")
elseif isa(v.data, DatasetBin)
data = "DatasetBin: \n" * v.data.source
else
data = "DatasetEmpty"
end
plot = length(v.plot) > 0 ? join(v.plot, "\n") : []
@info("PlotElement", mid=v.mid, is3d=v.is3d, cmds=join(v.cmds, "\n"),
name=v.name, data, plot=plot)
end
function display(v::Vector{PlotElement})
for p in v
display(p)
println()
end
end
# ╭───────────────────────────────────────────────────────────────────╮
# │ TYPE DEFINITIONS │
# │ Sessions data structures │
# ╰───────────────────────────────────────────────────────────────────╯
# ---------------------------------------------------------------------
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
mutable struct SinglePlot mutable struct SinglePlot
cmds::Vector{String} cmds::Vector{String}
elems::Vector{String} elems::Vector{String}
flag3d::Bool is3d::Bool
SinglePlot() = new(Vector{String}(), Vector{String}(), false) SinglePlot() = new(Vector{String}(), Vector{String}(), false)
end end
@ -123,7 +156,6 @@ const options = Options()
# ╭───────────────────────────────────────────────────────────────────╮ # ╭───────────────────────────────────────────────────────────────────╮
# │ LOW LEVEL FUNCTIONS │ # │ LOW LEVEL FUNCTIONS │
# ╰───────────────────────────────────────────────────────────────────╯ # ╰───────────────────────────────────────────────────────────────────╯
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
function parseKeywords(; kwargs...) function parseKeywords(; kwargs...)
template = (xrange=NTuple{2, Real}, template = (xrange=NTuple{2, Real},
@ -157,16 +189,11 @@ function parseKeywords(; kwargs...)
ismissing(kw.ylog ) || (push!(out, (kw.ylog ? "" : "un") * "set logscale y")) ismissing(kw.ylog ) || (push!(out, (kw.ylog ? "" : "un") * "set logscale y"))
ismissing(kw.zlog ) || (push!(out, (kw.zlog ? "" : "un") * "set logscale z")) ismissing(kw.zlog ) || (push!(out, (kw.zlog ? "" : "un") * "set logscale z"))
ismissing(kw.cblog ) || (push!(out, (kw.cblog ? "" : "un") * "set logscale cb")) ismissing(kw.cblog ) || (push!(out, (kw.cblog ? "" : "un") * "set logscale cb"))
return out return join(out, ";\n")
end end
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
tostring(v::AbstractString) = "\"" * string(v) * "\""
tostring(v::Number) = string(v)
tostring(::Missing) = "?"
tostring(c::ColorTypes.RGB) = string(Int(c.r*255)) * " " * string(Int(c.g*255)) * " " * string(Int(c.b*255))
""" """
arrays2datablock(arrays...) arrays2datablock(arrays...)
@ -177,6 +204,10 @@ Data are sent from Julia to gnuplot in the form of an array of strings, also cal
If you experience errors when sending data to gnuplot try to filter the arrays through this function. If you experience errors when sending data to gnuplot try to filter the arrays through this function.
""" """
function arrays2datablock(args...) function arrays2datablock(args...)
tostring(v::AbstractString) = "\"" * string(v) * "\""
tostring(v::Real) = string(v)
tostring(::Missing) = "?"
#tostring(c::ColorTypes.RGB) = string(Int(c.r*255)) * " " * string(Int(c.g*255)) * " " * string(Int(c.b*255))
@assert length(args) > 0 @assert length(args) > 0
# Collect lengths and number of dims # Collect lengths and number of dims
@ -537,7 +568,7 @@ end
The following is dismissed since `binary matrix` do not allows to use The following is dismissed since `binary matrix` do not allows to use
keywords such as `rotate`. keywords such as `rotate`.
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
function write_binary(M::Matrix{T}) where T <: Number function write_binary(M::Matrix{T}) where T <: Real
x = collect(1:size(M)[1]) x = collect(1:size(M)[1])
y = collect(1:size(M)[2]) y = collect(1:size(M)[2])
@ -555,7 +586,7 @@ end
=# =#
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
function DatasetBin(VM::Vararg{AbstractMatrix{T}, N}) where {T <: Number, N} function DatasetBin(VM::Vararg{AbstractMatrix{T}, N}) where {T <: Real, N}
for i in 2:N for i in 2:N
@assert size(VM[i]) == size(VM[1]) @assert size(VM[i]) == size(VM[1])
end end
@ -646,7 +677,7 @@ newDatasetName(gp::Session) = string("\$data", length(gp.datas)+1)
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
function sendAsBinary(args...) function useBinaryMethod(args...)
@assert options.preferred_format in [:auto, :bin, :text] "Unexpected value for `options.preferred_format`: $(options.preferred_format)" @assert options.preferred_format in [:auto, :bin, :text] "Unexpected value for `options.preferred_format`: $(options.preferred_format)"
binary = false binary = false
if options.preferred_format == :bin if options.preferred_format == :bin
@ -667,13 +698,6 @@ function add_cmd(gp::Session, v::String)
return nothing return nothing
end end
function add_cmd(gp::Session; args...)
for v in parseKeywords(;args...)
add_cmd(gp, v)
end
return nothing
end
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
function add_plot(gp::Session, plotspec) function add_plot(gp::Session, plotspec)
@ -721,9 +745,8 @@ end
# ╭───────────────────────────────────────────────────────────────────╮ # ╭───────────────────────────────────────────────────────────────────╮
# │ gpexec(), execall(), dump() and driver() # │ gpexec(), execall(), amd savescript()
# ╰───────────────────────────────────────────────────────────────────╯ # ╰───────────────────────────────────────────────────────────────────╯
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
gpexec(gp::DrySession, command::String) = "" gpexec(gp::DrySession, command::String) = ""
function gpexec(gp::GPSession, command::String) function gpexec(gp::GPSession, command::String)
@ -763,7 +786,7 @@ function execall(gp::GPSession; term::AbstractString="", output::AbstractString=
gpexec(gp, d.cmds[j]) gpexec(gp, d.cmds[j])
end end
if length(d.elems) > 0 if length(d.elems) > 0
s = (d.flag3d ? "splot " : "plot ") * " \\\n " * s = (d.is3d ? "splot " : "plot ") * " \\\n " *
join(d.elems, ", \\\n ") join(d.elems, ", \\\n ")
gpexec(gp, s) gpexec(gp, s)
end end
@ -799,8 +822,8 @@ function savescript(gp::Session, filename; term::AbstractString="", output::Abst
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) mkpath(datapath)
end end
to = datapath * basename(d.file) to = datapath * basename(d.file)
cp(d.file, to, force=true) cp(d.file, to, force=true)
@ -846,7 +869,7 @@ function savescript(gp::Session, filename; term::AbstractString="", output::Abst
println(stream, d.cmds[j]) println(stream, d.cmds[j])
end end
if length(d.elems) > 0 if length(d.elems) > 0
s = (d.flag3d ? "splot " : "plot ") * " \\\n " * s = (d.is3d ? "splot " : "plot ") * " \\\n " *
join(redirect_elements(d.elems, paths...), ", \\\n ") join(redirect_elements(d.elems, paths...), ", \\\n ")
println(stream, s) println(stream, s)
end end
@ -858,12 +881,13 @@ function savescript(gp::Session, filename; term::AbstractString="", output::Abst
end end
# ╭───────────────────────────────────────────────────────────────────╮
# │ parseArgument() amd driver() │
# ╰───────────────────────────────────────────────────────────────────╯
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
function driver(args...; flag3d=false) function parseArguments(_args...)
function parseCmd(s::String)
function parseCmd(gp, s::String) (isplot, is3d, cmd) = (false, false, s)
(isplot, is3d, cmd) = (false, false, "")
(length(s) >= 2) && (s[1:2] == "p " ) && ((isplot, is3d, cmd) = (true, false, strip(s[2:end]))) (length(s) >= 2) && (s[1:2] == "p " ) && ((isplot, is3d, cmd) = (true, false, strip(s[2:end])))
(length(s) >= 3) && (s[1:3] == "pl " ) && ((isplot, is3d, cmd) = (true, false, strip(s[3:end]))) (length(s) >= 3) && (s[1:3] == "pl " ) && ((isplot, is3d, cmd) = (true, false, strip(s[3:end])))
(length(s) >= 4) && (s[1:4] == "plo " ) && ((isplot, is3d, cmd) = (true, false, strip(s[4:end]))) (length(s) >= 4) && (s[1:4] == "plo " ) && ((isplot, is3d, cmd) = (true, false, strip(s[4:end])))
@ -873,178 +897,252 @@ function driver(args...; flag3d=false)
(length(s) >= 4) && (s[1:4] == "spl " ) && ((isplot, is3d, cmd) = (true, true , strip(s[4:end]))) (length(s) >= 4) && (s[1:4] == "spl " ) && ((isplot, is3d, cmd) = (true, true , strip(s[4:end])))
(length(s) >= 5) && (s[1:5] == "splo " ) && ((isplot, is3d, cmd) = (true, true , strip(s[5:end]))) (length(s) >= 5) && (s[1:5] == "splo " ) && ((isplot, is3d, cmd) = (true, true , strip(s[5:end])))
(length(s) >= 6) && (s[1:6] == "splot ") && ((isplot, is3d, cmd) = (true, true , strip(s[6:end]))) (length(s) >= 6) && (s[1:6] == "splot ") && ((isplot, is3d, cmd) = (true, true , strip(s[6:end])))
return (isplot, is3d, string(cmd))
if cmd != ""
for (name, d) in gp.datas
if isa(d, DatasetBin) && (d.file != "")
cmd = replace(cmd, name => d.source)
end
end
end
return (isplot, is3d, cmd)
end
if length(args) == 0
gp = getsession()
execall(gp)
return nothing
end end
# First pass: check for `:-` and session names # First pass: check for `:-` and session names
gp = nothing sid = options.default
doDump = true doDump = true
doReset = true doReset = true
for iarg in 1:length(args) if length(_args) == 0
arg = args[iarg] return (sid, doReset, doDump, Vector{PlotElement}())
end
for iarg in 1:length(_args)
arg = _args[iarg]
if typeof(arg) == Symbol if typeof(arg) == Symbol
if arg == :- if arg == :-
if iarg == 1 if iarg == 1
doReset = false doReset = false
elseif iarg == length(args) elseif iarg == length(_args)
doDump = false doDump = false
else else
@warn "Symbol `:-` at position $iarg in argument list has no meaning." @warn "Symbol `:-` at position $iarg in argument list has no meaning."
end end
else else
@assert isnothing(gp) "Only one session at a time can be addressed" @assert (sid == options.default) "Only one session at a time can be addressed"
gp = getsession(arg) sid = arg
end end
end end
end end
(gp == nothing) && (gp = getsession())
doReset && reset(gp)
dataAccum = Vector{Any}()
dsetname = nothing
plotspec = nothing
function dataset_ready()
if length(dataAccum) > 0
# Ensure Dataset objects are processed one at a time
for i in 1:length(dataAccum)
@assert !isa(dataAccum[i], Dataset) || (length(dataAccum) == 1)
end
# Check if dataset is empty
emptyset = false
if !isa(dataAccum[1], Dataset)
mm = extrema(length.(dataAccum))
(mm[1] == 0) && (@assert mm[1] == mm[2] "At least one input array is empty, while other(s) are not")
emptyset = (mm[2] == 0)
end
if !emptyset
if isa(dataAccum[1], Dataset)
d = dataAccum[1]
else
if sendAsBinary(dataAccum...)
d = DatasetBin(dataAccum...)
else
d = DatasetText(dataAccum...)
end
end
isnothing(dsetname) && (dsetname = newDatasetName(gp)) # Second pass: check data types, run implicit recipes and splat
gp.datas[dsetname] = d # Vector{PlotElement}
write(gp, dsetname, d) # send now to gnuplot process args = Vector{Any}([_args...])
if !isnothing(plotspec) pos = 1
if isa(d, DatasetBin) while pos <= length(args)
add_plot(gp, d.source * " " * plotspec) arg = args[pos]
else if isa(arg, Symbol) # session ID (already handled)
add_plot(gp, dsetname * " " * plotspec) deleteat!(args, pos)
end continue
gp.plots[gp.curmid].flag3d = flag3d elseif isa(arg, Int) # ==> multiplot index
end
end
end
dataAccum = Vector{Any}()
dsetname = nothing
plotspec = nothing
end
# Second pass
for iarg in 1:length(args)
arg = args[iarg]
isa(arg, Symbol) && continue # already handled
if isa(arg, Int) # ==> change current multiplot index
@assert arg > 0 "Multiplot index must be a positive integer" @assert arg > 0 "Multiplot index must be a positive integer"
plotspec = "" # use an empty plotspec for pending dataset elseif isa(arg, AbstractString) # ==> a plotspec or a command
dataset_ready() deleteat!(args, pos)
setmulti(gp, arg) insert!(args, pos, string(strip(arg)))
gp.plots[gp.curmid].flag3d = flag3d elseif isa(arg, Tuple) && # ==> a keyword/value pair
elseif isa(arg, String) # ==> either a plotspec or a command length(arg) == 2 &&
arg = string(strip(arg)) isa(arg[1], Symbol)
if length(dataAccum) > 0 # ==> a plotspec deleteat!(args, pos)
plotspec = arg insert!(args, pos, parseKeywords(; [arg]...))
dataset_ready() continue
else
(isPlot, is3d, cmd) = parseCmd(gp, arg)
if isPlot # ==> a (s)plot command
gp.plots[gp.curmid].flag3d = is3d
add_plot(gp, cmd)
else # ==> a command
add_cmd(gp, arg)
end
end
elseif isa(arg, Tuple) && length(arg) == 2 && isa(arg[1], Symbol)
add_cmd(gp; [arg]...) # ==> a keyword/value pair
elseif isa(arg, Pair) # ==> a named dataset elseif isa(arg, Pair) # ==> a named dataset
@assert typeof(arg[1]) == String "Dataset name must be a string" @assert typeof(arg[1]) == String "Dataset name must be a string"
@assert arg[1][1] == '$' "Dataset name must start with a dollar sign" @assert arg[1][1] == '$' "Dataset name must start with a dollar sign"
dsetname = arg[1] deleteat!(args, pos)
for d in arg[2] for i in length(arg[2]):-1:1
push!(dataAccum, d) insert!(args, pos, arg[2][i])
end end
dataset_ready() insert!(args, pos, string(strip(arg[1])) => nothing)
elseif isa(arg, AbstractArray) # ==> a dataset column elseif isa(arg, AbstractArray) && # ==> a dataset column
push!(dataAccum, arg) ((valtype(arg) <: Real) ||
(valtype(arg) <: AbstractString)) ;
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) args[pos] = [arg]
elseif isa(arg, Dataset) elseif isa(arg, Dataset) ; # ==> a Dataset object
push!(dataAccum, arg) elseif hasmethod(recipe, tuple(typeof(arg))) # ==> implicit recipe
@info which(recipe, tuple(typeof(arg))) # debug
deleteat!(args, pos)
insert!(args, pos, recipe(arg))
continue
elseif isa(arg, Vector{PlotElement}) # ==> explicit recipe (vector)
deleteat!(args, pos)
for i in length(arg):-1:1
insert!(args, arg[i])
end
elseif isa(arg, PlotElement) ; # ==> explicit recipe (scalar)
else else
error("Unexpected argument at position $iarg with type " * string(typeof(arg))) error("Unexpected argument with type " * string(typeof(arg)))
end
pos += 1
end
# Third pass: collect PlotElement objects
mid = 0
name = ""
cmds = Vector{String}()
elems = Vector{PlotElement}()
pos = 1
while pos <= length(args)
arg = args[pos]
if isa(arg, Int) # ==> multiplot index
if length(cmds) > 0
push!(elems, PlotElement(mid=mid, cmds=cmds))
empty!(cmds)
end
mid = arg
name = ""
empty!(cmds)
elseif isa(arg, String) # ==> a plotspec or a command
(isPlot, is3d, s) = parseCmd(arg)
if isPlot
push!(elems, PlotElement(mid=mid, is3d=is3d, cmds=cmds, plot=s))
empty!(cmds)
else
push!(cmds, s)
end
name = ""
elseif isa(arg, Pair) # ==> dataset name
name = arg[1]
elseif isa(arg, AbstractArray) && # ==> beginning of a dataset
((valtype(arg) <: Real) ||
(valtype(arg) <: AbstractString))
accum = Vector{Any}()
while isa(arg, AbstractArray) &&
((valtype(arg) <: Real) ||
(valtype(arg) <: AbstractString))
push!(accum, arg)
deleteat!(args, pos)
if pos <= length(args)
arg = args[pos]
else
break
end end
end end
plotspec = "" spec = Vector{String}()
dataset_ready() if name == "" # only unnamed data sets have an associated plot spec
(doDump) && (execall(gp)) spec = ""
if (pos <= length(args)) &&
isa(args[pos], String)
spec = args[pos]
deleteat!(args, pos)
end
end
mm = extrema(length.(accum))
if mm[1] == 0
# empty Dataset
@assert mm[1] == mm[2] "At least one input array is empty, while other(s) are not"
else
if useBinaryMethod(accum...)
d = DatasetBin(accum...)
else
d = DatasetText(accum...)
end
push!(elems, PlotElement(mid=mid, cmds=cmds, name=name, data=d, plot=spec))
end
name = ""
empty!(cmds)
continue
elseif isa(arg, Dataset) # ==> a Dataset object
deleteat!(args, pos)
spec = ""
if (pos <= length(args)) &&
isa(args[pos], String)
spec = args[pos]
deleteat!(args, pos)
end
push!(elems, PlotElement(mid=mid, cmds=cmds, name=name, data=arg, plot=spec))
name = ""
empty!(cmds)
continue
elseif isa(arg, PlotElement)
if length(cmds) > 0
push!(elems, PlotElement(mid=mid, cmds=cmds))
empty!(cmds)
end
name = ""
(mid != 0) && (arg.mid = mid)
push!(elems, arg)
else
error("Unexpected argument with type " * string(typeof(arg)))
end
pos += 1
end
if length(cmds) > 0
push!(elems, PlotElement(mid=mid, cmds=cmds))
empty!(cmds)
end
return (sid, doReset, doDump, elems)
end
function driver(_args...; is3d=false)
if length(_args) == 0
gp = getsession()
execall(gp)
return nothing return nothing
end end
(sid, doReset, doDump, elems) = parseArguments(_args...)
gp = getsession(sid)
doReset && reset(gp)
function expandrecipes(args...; flag3d=false) # Set curent multiplot ID and sort elements
function push_elements!(out::Vector{Any}, pr::PlotElements) for elem in elems
@assert length(pr.data) <= length(pr.plot) if elem.mid == 0
(pr.mid > 0) && push!(out, pr.mid) elem.mid = gp.curmid
append!(out, pr.cmds)
for i in 1:length(pr.plot)
(i <= length(pr.data)) && push!(out, pr.data[i])
push!(out, pr.plot[i])
end end
end end
function push_elements!(out::Vector{Any}, v::Vector{PlotElements}) elems = elems[sortperm(getfield.(elems, :mid))]
for pr in v display(elems) # debug
push_elements!(out, pr)
# Set dataset names and send them to gnuplot process
for elem in elems
(elem.name == "") && (elem.name = newDatasetName(gp))
if !isa(elem.data, DatasetEmpty) &&
!haskey(gp.datas, elem.name)
gp.datas[elem.name] = elem.data
write(gp, elem.name, elem.data)
end end
end end
out = Vector{Any}() for elem in elems
for arg in args (elem.mid > 0) && setmulti(gp, elem.mid)
if hasmethod(recipe, tuple(typeof(arg))) # implicit recipe gp.plots[gp.curmid].is3d = (is3d | elem.is3d)
push_elements!(out, recipe(arg))
elseif isa(arg, PlotElements) # explicit recipe (scalar) for cmd in elem.cmds
push_elements!(out, arg) add_cmd(gp, cmd)
elseif isa(arg, Vector{PlotElements}) # explicit recipe (vector) end
push_elements!(out, pr)
if !isa(elem.data, DatasetEmpty)
for spec in elem.plot
if isa(elem.data, DatasetBin)
add_plot(gp, elem.data.source * " " * spec)
else else
push!(out, arg) # simple argument add_plot(gp, elem.name * " " * spec)
end end
end end
driver(out...; flag3d=flag3d) else
for spec in elem.plot
for (name, data) in gp.datas
if isa(data, DatasetBin)
spec = replace(spec, name => data.source)
end
end
add_plot(gp, spec)
end
end
end
(doDump) && (execall(gp))
return nothing
end end
@ -1146,13 +1244,13 @@ end
The `@gp` macro, and its companion `@gsp` for 3D plots, allows to send data and commands to the gnuplot using an extremely concise syntax. The macros accepts any number of arguments, with the following meaning: The `@gp` macro, and its companion `@gsp` for 3D plots, allows to send data and commands to the gnuplot using an extremely concise syntax. The macros accepts any number of arguments, with the following meaning:
- one, or a group of consecutive, array(s) build up a dataset. The different arrays are accessible as columns 1, 2, etc. from the `gnuplot` process. The number of required input arrays depends on the chosen plot style (see `gnuplot` documentation); - one, or a group of consecutive, array(s) of either `Real` or `String` build up a dataset. The different arrays are accessible as columns 1, 2, etc. from the `gnuplot` process. The number of required input arrays depends on the chosen plot style (see `gnuplot` documentation);
- a string occurring before a dataset is interpreted as a `gnuplot` command (e.g. `set grid`); - a string occurring before a dataset is interpreted as a `gnuplot` command (e.g. `set grid`);
- a string occurring immediately after a dataset is interpreted as a *plot element* for the dataset, by which you can specify `using` clause, `with` clause, line styles, etc.. All keywords may be abbreviated following gnuplot conventions. Moreover, "plot" and "splot" can be abbreviated to "p" and "s" respectively; - a string occurring immediately after a dataset is interpreted as a *plot element* for the dataset, by which you can specify `using` clause, `with` clause, line styles, etc.. All keywords may be abbreviated following gnuplot conventions. Moreover, "plot" and "splot" can be abbreviated to "p" and "s" respectively;
- the special symbol `:-`, whose meaning is to avoid starting a new plot (if given as first argument), or to avoid immediately running all commands to create the final plot (if given as last argument). Its purpose is to allow splitting one long statement into multiple (shorter) ones; - the special symbol `:-` allows to split one long statement into multiple (shorter) ones. If given as first argument it avoids starting a new plot. If it given as last argument it avoids immediately running all commands to create the final plot;
- any other symbol is interpreted as a session ID; - any other symbol is interpreted as a session ID;
@ -1176,10 +1274,14 @@ The `@gp` macro, and its companion `@gsp` for 3D plots, allows to send data and
- `zlog=true` => `set logscale z`. - `zlog=true` => `set logscale z`.
- `cblog=true` => `set logscale cb`. - `cblog=true` => `set logscale cb`.
All Keyword names can be abbreviated as long as the resulting name is unambiguous. E.g. you can use `xr=[1,10]` in place of `xrange=[1,10]`. All Keyword names can be abbreviated as long as the resulting name is unambiguous. E.g. you can use `xr=[1,10]` in place of `xrange=[1,10]`.
- a `PlotElement` object is expanded in and its fields processed as one of the previous arguments;
- any other data type is processed through an implicit recipe. If a suitable recipe do not exists an error is raised.
""" """
macro gp(args...) macro gp(args...)
out = Expr(:call) out = Expr(:call)
push!(out.args, :(Gnuplot.expandrecipes)) push!(out.args, :(Gnuplot.driver))
for iarg in 1:length(args) for iarg in 1:length(args)
arg = args[iarg] arg = args[iarg]
if (isa(arg, Expr) && (arg.head == :(=))) if (isa(arg, Expr) && (arg.head == :(=)))
@ -1202,7 +1304,7 @@ This macro accepts the same syntax as [`@gp`](@ref), but produces a 3D plot inst
macro gsp(args...) macro gsp(args...)
out = Expr(:macrocall, Symbol("@gp"), LineNumberNode(1, nothing)) out = Expr(:macrocall, Symbol("@gp"), LineNumberNode(1, nothing))
push!(out.args, args...) push!(out.args, args...)
push!(out.args, Expr(:kw, :flag3d, true)) push!(out.args, Expr(:kw, :is3d, true))
return esc(out) return esc(out)
end end
@ -1311,8 +1413,8 @@ palette_names() = Symbol.(keys(ColorSchemes.colorschemes))
""" """
linetypes(cmap::ColorScheme; lw=1, ps="default", dashed=false, rev=false) linetypes(cmap::ColorScheme; lw=1, ps=1, dashed=false, rev=false)
linetypes(s::Symbol; lw=1, ps="default", dashed=false, rev=false) linetypes(s::Symbol; lw=1, ps=1, dashed=false, rev=false)
Convert a `ColorScheme` object into a string containing the gnuplot commands to set up *linetype* colors. Convert a `ColorScheme` object into a string containing the gnuplot commands to set up *linetype* colors.
@ -1321,7 +1423,7 @@ If the argument is a `Symbol` it is interpreted as the name of one of the predef
If `rev=true` the line colors are reversed. If a numeric or string value is provided through the `lw` and `ps` keywords thay are used to set the line width and the point size respectively. If `dashed` is true the linetypes with index greater than 1 will be displayed with dashed pattern. If `rev=true` the line colors are reversed. If a numeric or string value is provided through the `lw` and `ps` keywords thay are used to set the line width and the point size respectively. If `dashed` is true the linetypes with index greater than 1 will be displayed with dashed pattern.
""" """
linetypes(s::Symbol; kwargs...) = linetypes(colorschemes[s]; kwargs...) linetypes(s::Symbol; kwargs...) = linetypes(colorschemes[s]; kwargs...)
function linetypes(cmap::ColorScheme; lw=1, ps="default", dashed=false, rev=false) function linetypes(cmap::ColorScheme; lw=1, ps=1, dashed=false, rev=false)
out = Vector{String}() out = Vector{String}()
push!(out, "unset for [i=1:256] linetype i") push!(out, "unset for [i=1:256] linetype i")
for i in 1:length(cmap.colors) for i in 1:length(cmap.colors)
@ -1447,7 +1549,7 @@ end
# -------------------------------------------------------------------- # --------------------------------------------------------------------
""" """
hist(v::Vector{T}; range=extrema(v), bs=NaN, nbins=0, pad=true) where T <: Number hist(v::Vector{T}; range=extrema(v), bs=NaN, nbins=0, pad=true) where T <: Real
Calculates the histogram of the values in `v` and returns a [`Histogram1D`](@ref) structure. Calculates the histogram of the values in `v` and returns a [`Histogram1D`](@ref) structure.
@ -1468,7 +1570,7 @@ h = hist(v, bs=0.5)
@gp h.bins h.counts "w histep notit" @gp h.bins h.counts "w histep notit"
``` ```
""" """
function hist(v::Vector{T}; range=[NaN,NaN], bs=NaN, nbins=0, pad=true) where T <: Number function hist(v::Vector{T}; range=[NaN,NaN], bs=NaN, nbins=0, pad=true) where T <: Real
i = findall(isfinite.(v)) i = findall(isfinite.(v))
isnan(range[1]) && (range[1] = minimum(v[i])) isnan(range[1]) && (range[1] = minimum(v[i]))
isnan(range[2]) && (range[2] = maximum(v[i])) isnan(range[2]) && (range[2] = maximum(v[i]))
@ -1502,7 +1604,7 @@ end
""" """
hist(v1::Vector{T1 <: Number}, v2::Vector{T2 <: Number}; range1=[NaN,NaN], bs1=NaN, nbins1=0, range2=[NaN,NaN], bs2=NaN, nbins2=0) hist(v1::Vector{T1 <: Real}, v2::Vector{T2 <: Real}; range1=[NaN,NaN], bs1=NaN, nbins1=0, range2=[NaN,NaN], bs2=NaN, nbins2=0)
Calculates the 2D histogram of the values in `v1` and `v2` and returns a [`Histogram2D`](@ref) structure. Calculates the 2D histogram of the values in `v1` and `v2` and returns a [`Histogram2D`](@ref) structure.
@ -1529,7 +1631,7 @@ h = hist(v1, v2, bs1=0.5, bs2=0.5)
""" """
function hist(v1::Vector{T1}, v2::Vector{T2}; function hist(v1::Vector{T1}, v2::Vector{T2};
range1=[NaN,NaN], bs1=NaN, nbins1=0, range1=[NaN,NaN], bs1=NaN, nbins1=0,
range2=[NaN,NaN], bs2=NaN, nbins2=0) where {T1 <: Number, T2 <: Number} range2=[NaN,NaN], bs2=NaN, nbins2=0) where {T1 <: Real, T2 <: Real}
@assert length(v1) == length(v2) @assert length(v1) == length(v2)
i = findall(isfinite.(v1) .& isfinite.(v2)) i = findall(isfinite.(v1) .& isfinite.(v2))
isnan(range1[1]) && (range1[1] = minimum(v1[i])) isnan(range1[1]) && (range1[1] = minimum(v1[i]))
@ -1659,7 +1761,7 @@ end
""" """
function contourlines(args...; cntrparam="level auto 10") function contourlines(args...; cntrparam="level auto 10")
lines = gp_write_table("set contour base", "unset surface", lines = gp_write_table("set contour base", "unset surface",
"set cntrparam $cntrparam", args..., flag3d=true) "set cntrparam $cntrparam", args..., is3d=true)
level = NaN level = NaN
path = Path2d() path = Path2d()

View File

@ -5,41 +5,41 @@
# -------------------------------------------------------------------- # --------------------------------------------------------------------
# Histograms # Histograms
recipe(h::Histogram1D) = recipe(h::Histogram1D) =
PlotElements(cmds="set grid", PlotElement(cmds="set grid",
data=DatasetText(h.bins, h.counts), data=DatasetText(h.bins, h.counts),
plot="w histep notit lw 2 lc rgb 'black'") plot="w histep notit lw 2 lc rgb 'black'")
recipe(h::Histogram2D) = recipe(h::Histogram2D) =
PlotElements(cmds=["set autoscale fix", "set size ratio -1"], PlotElement(cmds=["set autoscale fix", "set size ratio -1"],
data=DatasetText(h.bins1, h.bins2, h.counts), data=DatasetText(h.bins1, h.bins2, h.counts),
plot="w image notit") plot="w image notit")
# --------------------------------------------------------------------
# Images # Images
recipe(M::Matrix{ColorTypes.RGB{T}}) where T = recipe(M::Matrix{ColorTypes.RGB{T}}; rot="-90deg", opt="") where T =
PlotElements(cmds=["set autoscale fix", "set size square"], PlotElement(cmds=["set autoscale fix", "set size square"],
data=DatasetBin(256 .* getfield.(M, :r), data=DatasetBin(256 .* getfield.(M, :r),
256 .* getfield.(M, :g), 256 .* getfield.(M, :g),
256 .* getfield.(M, :b)), 256 .* getfield.(M, :b)),
plot="rotate=-90deg with rgbimage notit") plot="rotate=$rot $opt with rgbimage notit")
recipe(M::Matrix{ColorTypes.RGBA{T}}) where T = recipe(M::Matrix{ColorTypes.RGBA{T}}; rot="-90deg", opt="") where T =
PlotElements(cmds=["set autoscale fix", "set size square"], PlotElement(cmds=["set autoscale fix", "set size square"],
data=DatasetBin(256 .* getfield.(M, :r), data=DatasetBin(256 .* getfield.(M, :r),
256 .* getfield.(M, :g), 256 .* getfield.(M, :g),
256 .* getfield.(M, :b)), 256 .* getfield.(M, :b)),
plot="rotate=-90deg with rgbimage notit") plot="rotate=$rot $opt with rgbimage notit")
recipe(M::Matrix{ColorTypes.Gray{T}}) where T = recipe(M::Matrix{ColorTypes.Gray{T}}; rot="-90deg", opt="") where T =
PlotElements(cmds=["set autoscale fix", "set size square"], PlotElement(cmds=["set autoscale fix", "set size square"],
data=DatasetBin(256 .* getfield.(M, :val)), data=DatasetBin(256 .* getfield.(M, :val)),
plot="rotate=-90deg with image notit") plot="rotate=$rot $opt with image notit")
recipe(M::Matrix{ColorTypes.GrayA{T}}) where T = recipe(M::Matrix{ColorTypes.GrayA{T}}; rot="-90deg", opt="") where T =
PlotElements(cmds=["set autoscale fix", "set size square"], PlotElement(cmds=["set autoscale fix", "set size square"],
data=DatasetBin(256 .* getfield.(M, :val)), data=DatasetBin(256 .* getfield.(M, :val)),
plot="rotate=-90deg with image notit") plot="rotate=$rot $opt with image notit")
# ╭───────────────────────────────────────────────────────────────────╮ # ╭───────────────────────────────────────────────────────────────────╮
@ -50,9 +50,9 @@ macro recipes_DataFrames()
return esc(:( return esc(:(
function plotdf(df::DataFrame, colx::Symbol, coly::Symbol; group=nothing); function plotdf(df::DataFrame, colx::Symbol, coly::Symbol; group=nothing);
if isnothing(group); if isnothing(group);
return Gnuplot.PlotElements(data=Gnuplot.DatasetText(df[:, colx], df[:, coly]), return Gnuplot.PlotElement(xlab=string(colx), ylab=string(coly),
plot="w p notit", data=Gnuplot.DatasetText(df[:, colx], df[:, coly]),
xlab=string(colx), ylab=string(coly)); plot="w p notit");
end; end;
data = Vector{Gnuplot.Dataset}(); data = Vector{Gnuplot.Dataset}();
@ -64,8 +64,8 @@ macro recipes_DataFrames()
push!(plot, "w p t '$g'"); push!(plot, "w p t '$g'");
end; end;
end; end;
return Gnuplot.PlotElements(data=data, plot=plot, return Gnuplot.PlotElement(xlab=string(colx), ylab=string(coly),
xlab=string(colx), ylab=string(coly)); data=data, plot=plot);
end end
)) ))
end end

View File

@ -96,8 +96,8 @@ 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(:Set1_5)
@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 ls == "unset for [i=1:256] linetype i\nset linetype 1 lc rgb '#E41A1C' lw 1 dt solid pt 1 ps 1\nset linetype 2 lc rgb '#377EB8' lw 1 dt solid pt 2 ps 1\nset linetype 3 lc rgb '#4DAF4A' lw 1 dt solid pt 3 ps 1\nset linetype 4 lc rgb '#984EA3' lw 1 dt solid pt 4 ps 1\nset linetype 5 lc rgb '#FF7F00' lw 1 dt solid pt 5 ps 1\nset linetype cycle 5\n"
#----------------------------------------------------------------- #-----------------------------------------------------------------
# Test wth empty dataset # Test wth empty dataset
@ -136,7 +136,7 @@ noise = err .* randn(length(x));
h = hist(noise, nbins=10) h = hist(noise, nbins=10)
@gp h.bins h.counts "w histeps" @gp h.bins h.counts "w histeps"
@gp h
@gp x y @gp x y
@gp x y "w l" @gp x y "w l"
@ -183,7 +183,7 @@ name = "\$MyDataSet1"
@gp :- :dry 2 xlab="X label" ylab="Residuals" :- @gp :- :dry 2 xlab="X label" ylab="Residuals" :-
@gp :- :dry "plot $name u 1:((f(\$1)-\$2) / \$3):(1) w errorbars notit" :- @gp :- :dry "plot $name u 1:((f(\$1)-\$2) / \$3):(1) w errorbars notit" :-
@gp :- :dry @gp :- :dry
save("test.gp") # write on file test.gp save(:dry, "test.gp") # write on file test.gp
Gnuplot.quitall() Gnuplot.quitall()
gpexec("load 'test.gp'") # load file test.gp gpexec("load 'test.gp'") # load file test.gp