From e4372ef1f994eb700792c2f3c08f53b28656ac94 Mon Sep 17 00:00:00 2001 From: Giorgio Calderone Date: Sat, 20 Oct 2018 18:43:32 +0200 Subject: [PATCH] Code refactoring and API modified --- REQUIRE | 1 + src/Gnuplot.jl | 1445 ++++++++++++++++++++-------------------------- test/runtests.jl | 46 +- 3 files changed, 668 insertions(+), 824 deletions(-) diff --git a/REQUIRE b/REQUIRE index 66e3749..0a8e11e 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,2 +1,3 @@ julia 0.6 +StatsBase 0.25 ColorTypes 0.6.7 diff --git a/src/Gnuplot.jl b/src/Gnuplot.jl index df66067..c155f79 100644 --- a/src/Gnuplot.jl +++ b/src/Gnuplot.jl @@ -2,20 +2,13 @@ __precompile__(true) module Gnuplot -using StructC14N -using ColorTypes -using Printf +using StatsBase, StructC14N, ColorTypes, Printf import Base.reset +import Base.quit - -###################################################################### -# Exported symbols -###################################################################### - -export CheckGnuplotVersion, GnuplotSession, GnuplotProc, - GnuplotQuit, GnuplotQuitAll, GnuplotGet, setCurrent, getCurrent, - @gp, @gsp, @gp_str, @gp_cmd +export gnuplot, quit, quitall, + hist, @gp, @gsp, gpeval ###################################################################### @@ -23,68 +16,55 @@ export CheckGnuplotVersion, GnuplotSession, GnuplotProc, ###################################################################### #--------------------------------------------------------------------- -mutable struct inputData +mutable struct Data str::String sent::Bool - inputData(str::String) = new(str, false) + Data(str::String) = new(str, false) end -mutable struct inputPlot +mutable struct Commands cmds::Vector{String} plot::Vector{String} splot::Bool - inputPlot() = new(Vector{String}(), Vector{String}(), false) + Commands() = new(Vector{String}(), Vector{String}(), false) +end + +#--------------------------------------------------------------------- +mutable struct PackedDataPlot + data::Vector{Any} + name::String + cmds::Vector{String} + plot::Vector{String} end #--------------------------------------------------------------------- -mutable struct GnuplotSession - id::Int - blockCnt::Int # data blocks counter - data::Vector{inputData} # data blocks - plot::Vector{inputPlot} # commands and plot commands - multiID::Int - defCmd::String -end - - -#--------------------------------------------------------------------- -mutable struct GnuplotProc - id::Int +mutable struct Process pin::Base.Pipe pout::Base.Pipe perr::Base.Pipe proc::Base.Process channel::Channel{String} - verbosity::Int # verbosity level (0 - 4), default: 3 - session::GnuplotSession end +mutable struct Session + id::Symbol + data::Vector{Data} # data blocks + plot::Vector{Commands} # commands and plot commands + multi::Int + proc::Union{Nothing,Process} +end -###################################################################### -# Global variables and functions to handle it -###################################################################### #--------------------------------------------------------------------- -mutable struct GlobalState - obj::Dict{Int, Union{GnuplotSession,GnuplotProc}} - id::Int - GlobalState() = new(Dict{Int, Union{GnuplotSession,GnuplotProc}}(), 0) -end -const g_state = GlobalState() - -function newID() - global g_state - countProc = 0 - newID = 0 - for (id, obj) in g_state.obj - (id > newID) && (newID = id) - (typeof(obj) == GnuplotProc) && (countProc += 1) - end - @assert countProc <= 10 "Too many Gnuplot processes are running." - newID += 1 - return newID +mutable struct State + sessions::Dict{Symbol, Session} + default::Symbol + verbosity::Int # verbosity level (0 - 1), default: 1 + datalines::Int # How many data lines are printed + State() = new(Dict{Symbol, Session}(), :default, 1, 4) end +const state = State() ###################################################################### @@ -93,160 +73,87 @@ end #--------------------------------------------------------------------- """ -Logging facility - -Printing occur only if the logging level is >= current verbosity -level. + # CheckGnuplotVersion + + Check whether gnuplot is runnable with the command given in `cmd`. + Also check that gnuplot version is >= 4.7 (required to use data + blocks). """ -function logIn(gp::GnuplotProc, s::AbstractString) - (gp.verbosity < 1) && return nothing - printstyled(color=:yellow , "GNUPLOT ($(gp.id)) -> $s\n") - return nothing -end +function CheckGnuplotVersion(cmd::String) + icmd = `$(cmd) --version` -function logData(gp::GnuplotProc, s::AbstractString) - (gp.verbosity < 4) && return nothing - printstyled(color=:light_black, "GNUPLOT ($(gp.id)) -> $s\n") - return nothing -end - -function logData(gp::GnuplotProc, v::Vector{T}) where T <: AbstractString - for s in v - logData(gp, s) - end - return nothing -end - -function logOut(gp::GnuplotProc, s::AbstractString) - (gp.verbosity < 2) && return nothing - printstyled(color=:cyan , "GNUPLOT ($(gp.id)) $s\n") - return nothing -end - -function logErr(gp::GnuplotProc, s::AbstractString) - (gp.verbosity < 3) && return nothing - printstyled(color=:cyan , "GNUPLOT ($(gp.id)) $s\n") - return nothing -end - -function logCaptured(gp::GnuplotProc, s::AbstractString) - (gp.verbosity < 3) && return nothing - printstyled(color=:green , "GNUPLOT ($(gp.id)) $s\n") - return nothing -end - - -#--------------------------------------------------------------------- -""" -Read gnuplot outputs and optionally redirect to a `Channel`. - -This fuction is supposed to be run in a `Task`. -""" -function readTask(gp::GnuplotProc, useStdErr::Bool) - saveOutput = false - - sIN = gp.pout - if useStdErr - sIN = gp.perr + proc = open(`$icmd`, read=true) + s = String(read(proc)) + if !success(proc) + error("An error occurred while running: " * string(icmd)) end - while isopen(sIN) - line = convert(String, readline(sIN)) - - if line == "GNUPLOT_CAPTURE_BEGIN" - saveOutput = true - else - if saveOutput - put!(gp.channel, line) - end - - if line == "GNUPLOT_CAPTURE_END" - saveOutput = false - elseif line != "" - if saveOutput - logCaptured(gp, line) - else - if useStdErr - logErr(gp, line) - else - logOut(gp, line) - end - end - end + s = split(s, " ") + ver = "" + for token in s + try + ver = VersionNumber("$token") + break + catch end end - logOut(gp, "pipe closed") - - global g_state - delete!(g_state.obj, gp.id) - - return nothing + if ver < v"4.7" + # Do not raise error in order to pass Travis CI test, since it has v4.6 + @warn "gnuplot ver. >= 4.7 is required, but " * string(ver) * " was found." + end + if ver < v"4.6" + error("gnuplot ver. >= 4.7 is required, but " * string(ver) * " was found.") + end + @info " Gnuplot version: " * string(ver) + return ver end #--------------------------------------------------------------------- -function parseKeywords(; kwargs...) - template = (xrange=NTuple{2, Number}, - yrange=NTuple{2, Number}, - zrange=NTuple{2, Number}, - cbrange=NTuple{2, Number}, - title=String, - xlabel=String, - ylabel=String, - zlabel=String, - xlog=Bool, - ylog=Bool, - zlog=Bool) - - kw = canonicalize(template; kwargs...) - out = Vector{String}() - ismissing(kw.xrange ) || (push!(out, "set xrange [" * join(kw.xrange , ":") * "]")) - ismissing(kw.yrange ) || (push!(out, "set yrange [" * join(kw.yrange , ":") * "]")) - ismissing(kw.zrange ) || (push!(out, "set zrange [" * join(kw.zrange , ":") * "]")) - ismissing(kw.cbrange) || (push!(out, "set cbrange [" * join(kw.cbrange, ":") * "]")) - ismissing(kw.title ) || (push!(out, "set title '" * kw.title * "'")) - ismissing(kw.xlabel ) || (push!(out, "set xlabel '" * kw.xlabel * "'")) - ismissing(kw.ylabel ) || (push!(out, "set ylabel '" * kw.ylabel * "'")) - ismissing(kw.zlabel ) || (push!(out, "set zlabel '" * kw.zlabel * "'")) - ismissing(kw.xlog ) || (push!(out, (kw.xlog ? "" : "un") * "set logscale x")) - ismissing(kw.ylog ) || (push!(out, (kw.ylog ? "" : "un") * "set logscale y")) - ismissing(kw.zlog ) || (push!(out, (kw.zlog ? "" : "un") * "set logscale z")) - return out +function getsession(id::Symbol) + global state + if !(id in keys(state.sessions)) + @info "Creating session $id..." + gnuplot(id) + end + return state.sessions[id] +end +function getsession() + global state + return getsession(state.default) end #--------------------------------------------------------------------- """ -# send - -Send a string to gnuplot's STDIN. - -The commands sent through `send` are not stored in the current -session (use `addCmd` to save commands in the current session). - -## Example: -``` -gp = GnuplotProc() -send(gp, "plot sin(x)") -``` - -## Arguments: -- `gp`: a GnuplotProc or GnuplotSession object; -- `str::String`: command to be sent. + # send + + Send a string to gnuplot's STDIN. + + The commands sent through `send` are not stored in the current + session (use `addCmd` to save commands in the current session). + + ## Arguments: + - `gp`: a `Session` object; + - `str::String`: command to be sent; + - `capture=false`: set to `true` to capture and return the output. """ -function send(gp::GnuplotProc, str::AbstractString, capture=false) - (capture) && (write(gp.pin, "print 'GNUPLOT_CAPTURE_BEGIN'\n")) - w = write(gp.pin, strip(str) * "\n") - logIn(gp, str) +function send(gp::Session, str::AbstractString, capture=false) + (gp.proc == nothing) && (return nothing) + + (capture) && (write(gp.proc.pin, "print 'GNUPLOT_CAPTURE_BEGIN'\n")) + if state.verbosity >= 1 + printstyled(color=:yellow , "GNUPLOT ($(gp.id)) -> $str\n") + end + w = write(gp.proc.pin, strip(str) * "\n") w <= 0 && error("Writing on gnuplot STDIN pipe returned $w") - (capture) && (write(gp.pin, "print 'GNUPLOT_CAPTURE_END'\n")) - flush(gp.pin) - + (capture) && (write(gp.proc.pin, "print 'GNUPLOT_CAPTURE_END'\n")) + flush(gp.proc.pin) out = Vector{String}() if capture while true - l = take!(gp.channel) + l = take!(gp.proc.channel) l == "GNUPLOT_CAPTURE_END" && break push!(out, l) end @@ -256,44 +163,44 @@ end #--------------------------------------------------------------------- -""" -# reset - -Delete all commands, data, and plots in the gnuplot session. -""" -function reset(gp::GnuplotSession) - gp.blockCnt = 0 - gp.data = Vector{inputData}() - gp.plot = [inputPlot()] - gp.multiID = 1 - addCmd(gp, gp.defCmd) - return nothing -end - -""" -# reset - -Send a 'reset session' command to gnuplot and delete all commands, -data, and plots in the associated session. -""" -function reset(gp::GnuplotProc) - reset(gp.session) - send(gp, "reset session") - send(gp, gp.session.defCmd) +function setWindowTitle(ss::Session) + (ss.proc == nothing) && (return nothing) + term = send(ss, "print GPVAL_TERM", true)[1] + if term in ("aqua", "x11", "qt", "wxt") + opts = send(ss, "print GPVAL_TERMOPTIONS", true)[1] + if findfirst("title", opts) == nothing + send(ss, "set term $term $opts title 'Gnuplot.jl: $(ss.id)'") + end + end return nothing end #--------------------------------------------------------------------- -function addData(gp::GnuplotSession, args...; name="") - function toString(n::Number) - return @sprintf("%.4g", n) - end +function reset(gp::Session) + gp.data = Vector{Data}() + gp.plot = [Commands()] + gp.multi = 1 + send(gp, "reset session") + setWindowTitle(gp) + return nothing +end - if name == "" - name = string("data", gp.blockCnt) - gp.blockCnt += 1 +#--------------------------------------------------------------------- +function setmulti(gp::Session, id::Int) + @assert id >= 0 "Multiplot ID must be a >= 0" + for i in length(gp.plot)+1:id + push!(gp.plot, Commands()) end + (id > 0) && (gp.multi = id) +end + + +#--------------------------------------------------------------------- +function addData(gp::Session, args...; name="") + toString(n::Number) = @sprintf("%.4g", n) + + (name == "") && (name = string("data", length(gp.data))) name = "\$$name" # Check dimensions @@ -366,7 +273,7 @@ function addData(gp::GnuplotSession, args...; name="") # Prepare data v = "$name << EOD" - push!(gp.data, inputData(v)) + push!(gp.data, Data(v)) if dimZ > 0 # 3D for ix in 1:dimX @@ -381,10 +288,10 @@ function addData(gp::GnuplotSession, args...; name="") d = args[iarg] v *= " " * string(d[ix,iy,iz]) end - push!(gp.data, inputData(v)) + push!(gp.data, Data(v)) end end - push!(gp.data, inputData("")) + push!(gp.data, Data("")) end elseif dimY > 0 # 2D for ix in 1:dimX @@ -403,9 +310,9 @@ function addData(gp::GnuplotSession, args...; name="") v *= " " * toString(d[ix,iy]) end end - push!(gp.data, inputData(v)) + push!(gp.data, Data(v)) end - push!(gp.data, inputData("")) + push!(gp.data, Data("")) end elseif dimX > 0 # 1D for ix in 1:dimX @@ -414,7 +321,7 @@ function addData(gp::GnuplotSession, args...; name="") d = args[iarg] v *= " " * string(d[ix]) end - push!(gp.data, inputData(v)) + push!(gp.data, Data(v)) end else # scalars v = "" @@ -422,110 +329,131 @@ function addData(gp::GnuplotSession, args...; name="") d = args[iarg] v *= " " * string(d) end - push!(gp.data, inputData(v)) + push!(gp.data, Data(v)) end - push!(gp.data, inputData("EOD")) + push!(gp.data, Data("EOD")) - return name -end - - -function addData(gp::GnuplotProc, args...; name="") - name = addData(gp.session, args..., name=name) - - i = findall(.!getfield.(gp.session.data, :sent)) - if length(i) > 0 - v = getfield.(gp.session.data[i], :str) - push!(v, " ") - if gp.verbosity >= 4 - if length(v) > 4 - logData(gp, v[1:4]) - logData(gp, "...") - else - logData(gp, v) + if gp.proc != nothing + i = findall(.!getfield.(gp.data, :sent)) + if length(i) > 0 + v = getfield.(gp.data[i], :str) + push!(v, " ") + + if state.verbosity >= 1 + for ii in 1:length(v) + printstyled(color=:light_black, "GNUPLOT ($(gp.id)) -> $(v[ii])\n") + if ii == state.datalines + printstyled(color=:light_black, "GNUPLOT ($(gp.id)) ...\n") + break + end + end end + vv = join(v, "\n") + w = write(gp.proc.pin, vv) + flush(gp.proc.pin) + setfield!.(gp.data[i], :sent, true) end - vv = join(v, "\n") - w = write(gp.pin, vv) - flush(gp.pin) - setfield!.(gp.session.data[i], :sent, true) end - return name +return name end #--------------------------------------------------------------------- -function setMultiID(gp::GnuplotSession, id::Int) - @assert id >= 0 "Multiplot ID must be a >= 0" - for i in length(gp.plot)+1:id - push!(gp.plot, inputPlot()) - end - (id > 0) && (gp.multiID = id) +function parseKeywords(; kwargs...) + template = (xrange=NTuple{2, Number}, + yrange=NTuple{2, Number}, + zrange=NTuple{2, Number}, + cbrange=NTuple{2, Number}, + title=String, + xlabel=String, + ylabel=String, + zlabel=String, + xlog=Bool, + ylog=Bool, + zlog=Bool) + + kw = canonicalize(template; kwargs...) + out = Vector{String}() + ismissing(kw.xrange ) || (push!(out, "set xrange [" * join(kw.xrange , ":") * "]")) + ismissing(kw.yrange ) || (push!(out, "set yrange [" * join(kw.yrange , ":") * "]")) + ismissing(kw.zrange ) || (push!(out, "set zrange [" * join(kw.zrange , ":") * "]")) + ismissing(kw.cbrange) || (push!(out, "set cbrange [" * join(kw.cbrange, ":") * "]")) + ismissing(kw.title ) || (push!(out, "set title '" * kw.title * "'")) + ismissing(kw.xlabel ) || (push!(out, "set xlabel '" * kw.xlabel * "'")) + ismissing(kw.ylabel ) || (push!(out, "set ylabel '" * kw.ylabel * "'")) + ismissing(kw.zlabel ) || (push!(out, "set zlabel '" * kw.zlabel * "'")) + ismissing(kw.xlog ) || (push!(out, (kw.xlog ? "" : "un") * "set logscale x")) + ismissing(kw.ylog ) || (push!(out, (kw.ylog ? "" : "un") * "set logscale y")) + ismissing(kw.zlog ) || (push!(out, (kw.zlog ? "" : "un") * "set logscale z")) + return out end -setMultiID(gp::GnuplotProc, id::Int) = setMultiID(gp.session, id) - - -#--------------------------------------------------------------------- -function setSplot(gp::GnuplotSession, splot::Bool) - gp.plot[gp.multiID].splot = splot -end -setSplot(gp::GnuplotProc, splot::Bool) = setSplot(gp.session, splot) - - #--------------------------------------------------------------------- """ -# addCmd - -Send a command to gnuplot process and store it in the current session. + # addCmd + + Send a command to gnuplot process and store it in the current session. """ -function addCmd(gp::GnuplotSession, v::String; id::Int=0) - setMultiID(gp, id) - (v != "") && (push!(gp.plot[gp.multiID].cmds, v)) +function addCmd(gp::Session, v::String; mid::Int=0) + setmulti(gp, mid) + (v != "") && (push!(gp.plot[gp.multi].cmds, v)) + (length(gp.plot) == 1) && (send(gp, v)) return nothing end -function addCmd(gp::GnuplotSession; id::Int=0, args...) +function addCmd(gp::Session; mid::Int=0, args...) for v in parseKeywords(;args...) - addCmd(gp, v, id=id) + addCmd(gp, v, mid=mid) end return nothing end -function addCmd(gp::GnuplotProc, s::String; id::Int=0) - addCmd(gp.session, s, id=id) - (length(gp.session.plot) == 1) && (send(gp, s)) + +#--------------------------------------------------------------------- +function addPlot(gp::Session, name, opt=""; mid=0) + setmulti(gp, mid) + push!(gp.plot[gp.multi].plot, "$name $opt") end -function addCmd(gp::GnuplotProc; id::Int=0, args...) - for v in parseKeywords(;args...) - addCmd(gp, v, id=id) + +#--------------------------------------------------------------------- +function quitsession(gp::Session) + global state + exitcode = 0 + if gp.proc != nothing + close(gp.proc.pin) + close(gp.proc.pout) + close(gp.proc.perr) + wait( gp.proc.proc) + exitCode = gp.proc.proc.exitcode end + delete!(state.sessions, gp.id) + return exitcode end #--------------------------------------------------------------------- -function addPlot(gp::GnuplotSession, name, opt=""; id=0) - setMultiID(gp, id) - push!(gp.plot[gp.multiID].plot, "$name $opt") +function hist(v::Vector{T}; addright=false, closed::Symbol=:left, args...) where T <: AbstractFloat + i = findall(isfinite.(v)) + hh = fit(Histogram, v[i]; closed=closed, args...) + if addright == 0 + return PackedDataPlot([hh.edges[1], [hh.weights;0]], "", [], ["w steps"]) + end + return PackedDataPlot([hh.edges[1], [0;hh.weights]], "", [], ["w fsteps"]) end -addPlot(gp::GnuplotProc, name, opt=""; id=0) = addPlot(gp.session, name, opt, id=id) - - #--------------------------------------------------------------------- -""" -# gpDump - -Send all necessary commands to gnuplot to actually do the plot. -Optionally, the commands may be sent to a file or returned as a -`Vector{String}`. -""" -function gpDump(gp::Union{GnuplotSession,GnuplotProc}; +function gpDump(gp::Session; term=("", ""), file="", stream=nothing, asArray=false) + function gpDumpInt(s::String) + (file != "") && (println(sfile , s)) + (stream != nothing) && (println(stream, s)) + (asArray) && (push!(ret, s)) + (dump2Gp) && (send(gp, s)) + return nothing + end - session = (typeof(gp) == GnuplotProc ? gp.session : gp) ret = Vector{String}() dump2Gp = false @@ -536,7 +464,7 @@ function gpDump(gp::Union{GnuplotSession,GnuplotProc}; stream == nothing && asArray == false # No outut is selected - if typeof(gp) == GnuplotProc + if gp.proc != nothing dump2Gp = true else stream = stdout @@ -551,7 +479,7 @@ function gpDump(gp::Union{GnuplotSession,GnuplotProc}; end if !dumpCmds - dumpCmds = (length(session.plot) > 1) + dumpCmds = (length(gp.plot) > 1) end # Open output file @@ -560,39 +488,31 @@ function gpDump(gp::Union{GnuplotSession,GnuplotProc}; dumpData = true end - function gpDumpInt(s::String) - (file != "") && (println(sfile , s)) - (stream != nothing) && (println(stream, s)) - (asArray) && (push!(ret, s)) - (dump2Gp) && (send(gp, s)) - return nothing - end - if dumpData gpDumpInt("reset session") - for v in session.data; gpDumpInt(v.str); end + for v in gp.data; gpDumpInt(v.str); end end (term[1] != "") && (gpDumpInt("set term $(term[1])")) (term[2] != "") && (gpDumpInt("set output '$(term[2])'")) - for id in 1:length(session.plot) + for id in 1:length(gp.plot) if dumpCmds - for s in session.plot[id].cmds + for s in gp.plot[id].cmds gpDumpInt(s) end end plot = Vector{String}() - for s in session.plot[id].plot; push!(plot, s); end + for s in gp.plot[id].plot; push!(plot, s); end if length(plot) > 0 - s = (session.plot[id].splot ? "splot " : "plot ") * " \\\n " * + s = (gp.plot[id].splot ? "splot " : "plot ") * " \\\n " * join(plot, ", \\\n ") gpDumpInt(s) end end - (length(session.plot) > 1) && (gpDumpInt("unset multiplot")) + (length(gp.plot) > 1) && (gpDumpInt("unset multiplot")) (term[2] != "") && (gpDumpInt("set output")) (file != "") && (close(sfile)) @@ -602,36 +522,31 @@ end #--------------------------------------------------------------------- -function gpDriver(splot, args...) +function gpDriver(args...; splot=false) if length(args) == 0 - gpDump(getCurrent()) + gp = getsession() + gpDump(gp) return nothing end - gp = nothing - eData = Vector{Any}() - dataName = "" - addDump = true - term = ("", "") - file="" - stream=nothing - - function endOfData(associatedPlot=nothing) - if length(eData) > 0 - last = addData(gp, eData...; name=dataName) - if associatedPlot != nothing - addPlot(gp, last, associatedPlot) - end + data = Vector{Any}() + dataname = "" + dataplot = nothing + + function dataCompleted() + if length(data) > 0 + last = addData(gp, data...; name=dataname) + (dataplot != nothing) && (addPlot(gp, last, dataplot)) end - eData = Vector{Any}() - dataName = "" + data = Vector{Any}() + dataname = "" + dataplot = nothing end function isPlotCmd(s::String) (length(s) >= 2) && (s[1:2] == "p " ) && (return (true, false, strip(s[2:end]))) (length(s) >= 3) && (s[1:3] == "pl " ) && (return (true, false, strip(s[3:end]))) (length(s) >= 4) && (s[1:4] == "plo " ) && (return (true, false, strip(s[4:end]))) (length(s) >= 5) && (s[1:5] == "plot " ) && (return (true, false, strip(s[5:end]))) - (length(s) >= 2) && (s[1:2] == "s " ) && (return (true, true , strip(s[2:end]))) (length(s) >= 3) && (s[1:3] == "sp " ) && (return (true, true , strip(s[3:end]))) (length(s) >= 4) && (s[1:4] == "spl " ) && (return (true, true , strip(s[4:end]))) @@ -640,475 +555,377 @@ function gpDriver(splot, args...) return (false, false, "") end - for iarg in 1:length(args) - arg = args[iarg] + gp = nothing + term = ("", "") + file = "" + stream = nothing + doDump = true + doReset = true - if typeof(arg) == GnuplotProc || - typeof(arg) == GnuplotSession - gp = arg - end - if gp == nothing - gp = getCurrent() - end - if iarg == 1 - if (typeof(arg) != Symbol) || (arg != :-) - reset(gp) - end - setSplot(gp, splot) - end - if typeof(arg) == GnuplotProc || - typeof(arg) == GnuplotSession - continue + for loop in 1:2 + if loop == 2 + (gp == nothing) && (gp = getsession()) + doReset && reset(gp) + gp.plot[gp.multi].splot = splot end + + for iarg in 1:length(args) + arg = args[iarg] - if typeof(arg) == Symbol - if arg == :- - (iarg == length(args)) && (addDump = false) - else - dataName = string(arg) - endOfData() - end - elseif isa(arg, Int) - @assert arg > 0 - endOfData("") - setMultiID(gp, arg) - elseif isa(arg, String) - # Either a plot or cmd string - if length(eData) > 0 - endOfData(arg) - else - (isPlot, splot, cmd) = isPlotCmd(arg) - if isPlot - setSplot(gp, splot) - addPlot(gp, cmd) + if typeof(arg) == Symbol + if arg == :- + (loop == 1) && (iarg < length(args)) && (doReset = false) + (loop == 1) && (iarg > 1) && (doDump = false) else - addCmd(gp, arg) + (loop == 1) && (gp = getsession(arg)) end - end - elseif isa(arg, Tuple) && length(arg) == 2 && isa(arg[1], Symbol) - if arg[1] == :term - if typeof(arg[2]) == String - term = (deepcopy(arg[2]), "") - elseif length(arg[2]) == 2 - term = deepcopy(arg[2]) + elseif isa(arg, Tuple) && length(arg) == 2 && isa(arg[1], Symbol) + if arg[1] == :term + if loop == 1 + if typeof(arg[2]) == String + term = (deepcopy(arg[2]), "") + elseif length(arg[2]) == 2 + term = deepcopy(arg[2]) + else + error("The term tuple must contain at most two strings") + end + end + elseif arg[1] == :verb + (loop == 1) && (state.verbosity = arg[2]) + elseif arg[1] == :file + (loop == 1) && (file = arg[2]) + elseif arg[1] == :stream + (loop == 1) && (stream = arg[2]) else - error("The term tuple must contain at most two strings") + (loop == 2) && addCmd(gp; [arg]...) # A cmd keyword + end + elseif isa(arg, Int) + (loop == 2) && (@assert arg > 0) + (loop == 2) && (dataplot = ""; dataCompleted()) + (loop == 2) && setmulti(gp, arg) + elseif isa(arg, String) + # Either a dataname, a plot or a command + if loop == 2 + if arg[1] == '$' + dataname = arg[2:end] + dataCompleted() + elseif length(data) > 0 + dataplot = arg + dataCompleted() + else + (isPlot, splot, cmd) = isPlotCmd(arg) + if isPlot + gp.plot[gp.multi].splot = splot + addPlot(gp, cmd) + else + addCmd(gp, arg) + end + end + end + elseif typeof(arg) == PackedDataPlot + if loop == 2 + last = addData(gp, arg.data..., name=arg.name) + for v in arg.cmds; addCmd(gp, v); end + for v in arg.plot; addPlot(gp, last, v); end end - elseif arg[1] == :verb - gp = gp - gp.verbosity = arg[2] - elseif arg[1] == :file - file = arg[2] - elseif arg[1] == :stream - stream = arg[2] else - # A cmd keyword - addCmd(gp; [arg]...) + (loop == 2) && push!(data, arg) # a data set end - else - # A data set - push!(eData, arg) end end - endOfData("") - (addDump) && (gpDump(gp; term=term, file=file, stream=stream)) + dataplot = "" + dataCompleted() + (doDump) && (gpDump(gp; term=term, file=file, stream=stream)) return nothing end + ###################################################################### # Public functions ###################################################################### #--------------------------------------------------------------------- """ -# CheckGnuplotVersion - -Check whether gnuplot is runnable with the command given in `cmd`. -Also check that gnuplot version is >= 4.7 (required to use data -blocks). + # gnuplot + + Initialize a new session and (optionally) the associated Gnuplot process. This is intended to use the package facilities to save the Gnuplot commands and data in a script file, rather than sending them to an actual process. + + The newly created session becomes the default sink for the @gp macro. """ -function CheckGnuplotVersion(cmd::String) - icmd = `$(cmd) --version` - - proc = open(`$icmd`, read=true) - s = String(read(proc)) - if !success(proc) - error("An error occurred while running: " * string(icmd)) +function gnuplot(id::Symbol; dry=false, cmd="gnuplot") + if id in keys(state.sessions) + error("Gnuplot session $id is already active") end - s = split(s, " ") - ver = "" - for token in s - try - ver = VersionNumber("$token") - break - catch + function readTask(id, stream, channel) + global state + saveOutput = false + + while isopen(stream) + line = convert(String, readline(stream)) + if line == "GNUPLOT_CAPTURE_BEGIN" + saveOutput = true + else + (saveOutput) && (put!(channel, line)) + if line == "GNUPLOT_CAPTURE_END" + saveOutput = false + elseif line != "" + (state.verbosity >= 1) && (printstyled(color=:cyan, "GNUPLOT ($id) $line\n")) + end + end end + global state + delete!(state.sessions, id) + return nothing + end + + global state + proc = nothing + if !dry + CheckGnuplotVersion(cmd) + pin = Base.Pipe() + pout = Base.Pipe() + perr = Base.Pipe() + iproc = run(pipeline(`$cmd`, stdin=pin, stdout=pout, stderr=perr), wait=false) + + proc = Process(pin, pout, perr, iproc, Channel{String}(32)) + + # Close unused sides of the pipes + Base.close(proc.pout.in) + Base.close(proc.perr.in) + Base.close(proc.pin.out) + Base.start_reading(proc.pout.out) + Base.start_reading(proc.perr.out) + + # Start reading tasks + @async readTask(id, proc.pout, proc.channel) + @async readTask(id, proc.perr, proc.channel) end - if ver < v"4.7" - # Do not raise error in order to pass Travis CI test, since it has v4.6 - @warn "gnuplot ver. >= 4.7 is required, but " * string(ver) * " was found." - end - if ver < v"4.6" - error("gnuplot ver. >= 4.7 is required, but " * string(ver) * " was found.") - end - @info "Running gnuplot version: " * string(ver) - return ver + out = Session(id, Vector{Data}(), [Commands()], 1, proc) + state.sessions[id] = out + setWindowTitle(out) + return id +end + +function gnuplot(;args...) + global state + return gnuplot(state.default, args...) end #--------------------------------------------------------------------- """ -# GnuplotSession - -Initialize a new session without any underlying Gnuplot process. This -is intended to use the package facilities to save the Gnuplot commands -and data in a script file, rather than sending them to an actual -process. - -The newly created session becomes the default sink for the @gp macro. + # quit + + Quit the current session and the associated gnuplot process (if any). """ -function GnuplotSession(;default="") - global g_state - id = newID() - out = GnuplotSession(id, 0, Vector{inputData}(), - [inputPlot()], 1, default) - g_state.obj[id] = out - return out -end - - -#--------------------------------------------------------------------- -""" -# GnuplotProc - -Initialize a new session (see `GnuplotSession`) and the -associated Gnuplot process. - -The newly created session becomes the default sink for the @gp macro. -""" -function GnuplotProc(cmd="gnuplot"; default="") - global g_state - CheckGnuplotVersion(cmd) - - pin = Base.Pipe() - pout = Base.Pipe() - perr = Base.Pipe() - proc = run(pipeline(`$cmd`, stdin=pin, stdout=pout, stderr=perr), wait=false) - - id = newID() - out = GnuplotProc(id, pin, pout, perr, proc, - Channel{String}(32), 4, - GnuplotSession(id, 0, Vector{inputData}(), - [inputPlot()], 1, default) - ) - g_state.obj[id] = out - - # Close unused sides of the pipes - Base.close(out.pout.in) - Base.close(out.perr.in) - Base.close(out.pin.out) - Base.start_reading(out.pout.out) - Base.start_reading(out.perr.out) - - # Start reading tasks - @async readTask(out, false) - @async readTask(out, true) - return out -end - - -#--------------------------------------------------------------------- -""" -# GnuplotQuit - -Close the current session and the associated gnuplot process (if any). -""" -function GnuplotQuit(gp::GnuplotSession) - global g_state - delete!(g_state.obj, gp.id) - return 0 -end - -function GnuplotQuit(gp::GnuplotProc) - close(gp.pin) - close(gp.pout) - close(gp.perr) - wait( gp.proc) - exitCode = gp.proc.exitcode - logOut(gp, string("Process exited with status ", exitCode)) - GnuplotQuit(gp.session) - return exitCode -end - -function GnuplotQuit(id::Int) - global g_state - if !(id in keys(g_state.obj)) - error("Gnuplot ID $id do not exists") +function quit(id::Symbol) + global state + if !(id in keys(state.sessions)) + error("Gnuplot session $id do not exists") end - return GnuplotQuit(g_state.obj[id]) + return quitsession(state.sessions[id]) end - -#--------------------------------------------------------------------- """ -# GnuplotQuitAll - -Close all the started sessions and the associated gnuplot processes. + # quitall + + Quit all the sessions and the associated gnuplot processes. """ -function GnuplotQuitAll() - global g_state - for (id, obj) in g_state.obj - GnuplotQuit(obj) +function quitall() + global state + for id in keys(state.sessions) + quit(id) end return 0 end -#--------------------------------------------------------------------- -""" -# GnuplotGet - -Return the value of one (or more) gnuplot variables. - -## Example -``` -println("Current gnuplot terminal is: ", GnuplotGet("GPVAL_TERM")) -``` -""" -function GnuplotGet(gp::GnuplotProc, var::String) - out = Vector{String}() - answer = send(gp, "print $var", true) - for line in answer - if length(search(line, "undefined variable:")) > 0 - error(line) - end - push!(out, line) - end - return join(out, "\n") -end -GnuplotGet(var::String) = GnuplotGet(getCurrent(), var) - #--------------------------------------------------------------------- """ -# setCurrent -""" -function setCurrent(gp::GnuplotProc) - @assert (gp.id in keys(g_state.obj)) "Invalid Gnuplot ID: $id" - g_state.id = gp.id -end - -function setCurrent(gp::GnuplotSession) - @assert (gp.id in keys(g_state.obj)) "Invalid Gnuplot ID: $id" - g_state.id = gp.id -end - - -#--------------------------------------------------------------------- -function getCurrent() - global g_state - if !(g_state.id in keys(g_state.obj)) - @info "Creating default Gnuplot process..." - out = GnuplotProc() - setCurrent(out) - end - return g_state.obj[g_state.id] -end - - -#--------------------------------------------------------------------- -""" -# @gp - -The `@gp`, and its companion `@gsp`(to be used for the `splot` -operations) allows to exploit all of the **Gnuplot** package -functionalities using an extremely efficient and concise syntax. Both -macros accept the same syntax, described below: - -The `@gp` macro accepts any number of arguments, with the following -meaning: -- a string: a command (e.g. "set key left") or plot specification; -- a `GnuplotProc` or `GnuplotSession` object: set the current destination; -- a symbol: specifies the data set name; -- an `Int`: if >0 set the current plot destination (if multiplot is - enabled); -- a keyword: set the keyword value (see below); -- any other data type: data to be passed to Gnuplot. Each dataset - must be terminated by either: a symbol (i.e. the data set name) or a - string with the plot specifications (e.g. "with lines"); -- the `:-` symbol, used as first argument, avoids resetting the -Gnuplot session. Used as last argument avoids immediate execution of -the plot/splot command. This symbol can be used to split a single -`@gp` call in multiple ones. - -All entries are optional, and there is no mandatory order. The plot -specification can either be: a complete plot/splot command (e.g., -"plot sin(x)", both "plot" and "splot" can be abbreviated to "p" and -"s" respectively), or a partial specification starting with the "with" -clause (if it follows a data set). - -The list of accepted keyword is as follows: -- `title::String`: plot title; -- `xlabel::String`: X axis label; -- `ylabel::String`: Y axis label; -- `zlabel::String`: Z axis label; -- `xlog::Bool`: logarithmic scale for X axis; -- `ylog::Bool`: logarithmic scale for Y axis; -- `zlog::Bool`: logarithmic scale for Z axis; -- `xrange::NTuple{2, Number}`: X axis range; -- `yrange::NTuple{2, Number}`: Y axis range; -- `zrange::NTuple{2, Number}`: Z axis range; -- `cbrange::NTuple{2, Number}`: Color box axis range; - -The symbol for the above-mentioned keywords may also be used in a -shortened form, as long as there is no ambiguity with other keywords. -E.g. you can use: `xr=(1,10)` in place of `xrange=(1,10)`. - -Beside the above-mentioned keyword the following can also be used -(although with no symbol shortening): - -- verb=Int: a number between 0 and 4, to set the verbosity level; -- file="A string": send all the data and command to a file rather than - to a Gnuplot process; -- stream=A stream: send all the data and command to a stream rather than - to a Gnuplot process; -- term="a string", or term=("a string", "a filename"): to specify the - terminal (and optionally the output file); - - -A rather simple example for the usage of `@gp` is as follows: -``` -@gp "set key left" tit="My title" xr=(1,12) 1:10 "with lines tit 'Data'" -``` - -In general, the `@gp` macro tries to figure out the appropriate -meaning of each arugment to allows an easy and straightforward use of -the underlying Gnuplot process. - -The `@gp` macro always send a "reset session" command to Gnuplot at -the beginning, and always run all the given commands at the end, -i.e. it is supposed to be used in cases where all data/commands are -provided in a single `@gp` call. - -To split the call in several statements, avoiding a session reset at -the beginning and an automatic execution of all commands at then, you -should use the `@gpi` macro instead, with exaclty the same syntax as -`@gp`. The `@gpi` macro also accepts the following arguments: -- the `0` number to reset the whole session; -- the `:.` symbol to send all commands to Gnuplot. - - -## Examples: -``` -# Simple examples with no data -@gp "plot sin(x)" -@gp "plot sin(x)" "pl cos(x)" -@gp "plo sin(x)" "s cos(x)" - -# Split a `@gp` call in two -@gp "plot sin(x)" :- -@gp :- "plot cos(x)" - -# Insert a 3 second pause between one plot and the next -@gp "plot sin(x)" 2 xr=(-2pi,2pi) "pause 3" "plot cos(4*x)" - -# Simple examples with data: -x = collect(1.:10) -@gp x -@gp x x -@gp x -x -@gp x x.^2 -@gp x x.^2 "w l" - -lw = 3 -@gp x x.^2 "w l lw \$lw" - -# A more complex example -@gp("set grid", "set key left", xlog=true, ylog=true, - title="My title", xlab="X label", ylab="Y label", - x, x.^0.5, "w l tit 'Pow 0.5' dt 2 lw 2 lc rgb 'red'", - x, x , "w l tit 'Pow 1' dt 1 lw 3 lc rgb 'blue'", - x, x.^2 , "w l tit 'Pow 2' dt 3 lw 2 lc rgb 'purple'") - - -# Multiplot example: -@gp(xr=(-2pi,2pi), "unset key", - "set multi layout 2,2 title 'Multiplot title'", - 1, "p sin(x)" , - 2, "p sin(2*x)", - 3, "p sin(3*x)", - 4, "p sin(4*x)") - -# or equivalently -@gp xr=(-2pi,2pi) "unset key" "set multi layout 2,2 title 'Multiplot title'" :- -for i in 1:4 - @gp :- i "p sin(\$i*x)" :- -end -@gp - - -# Multiple gnuplot instances -gp1 = GnuplotProc(default="set term wxt") -gp2 = GnuplotProc(default="set term qt") - -@gp gp1 "plot sin(x)" -@gp gp2 "plot sin(x)" - -GnuplotQuitAll() - - -# Further examples -x = linspace(-2pi, 2pi, 100); -y = 1.5 * sin.(0.3 + 0.7x) ; -noise = randn(length(x))./2; -e = 0.5 * ones(x); - -@gp verb=2 x y :aa "plot \\\$aa w l" "pl \\\$aa u 1:(2*\\\$2) w l" - -@gsp randn(Float64, 30, 50) -@gp randn(Float64, 30, 50) "w image" - -@gp("set key horizontal", "set grid", - xrange=(-7,7), ylabel="Y label", - x, y, "w l t 'Real model' dt 2 lw 2 lc rgb 'red'", - x, y+noise, e, "w errorbars t 'Data'"); - - -@gp "f(x) = a * sin(b + c*x); a = 1; b = 1; c = 1;" :- -@gp :- x y+noise e :aa :- -@gp :- "fit f(x) \\\$aa u 1:2:3 via a, b, c;" :- -@gp :- "set multiplot layout 2,1" :- -@gp :- "plot \\\$aa w points tit 'Data'" ylab="Data and model" :- -@gp :- "plot \\\$aa u 1:(f(\\\$1)) w lines tit 'Best fit'" :- -@gp :- 2 xlab="X label" ylab="Residuals" :- -@gp :- "plot \\\$aa u 1:((f(\\\$1)-\\\$2) / \\\$3):(1) w errorbars notit" - -# Display an image -using TestImages -img = testimage("lena"); -@gp img "w image" -@gp "set size square" img "w rgbimage" # Color image with correct proportions -@gp "set size square" img "u 2:(-\\\$1):3:4:5 with rgbimage" # Correct orientation -``` + # @gp + + The `@gp`, and its companion `@gsp`(to be used for the `splot` + operations) allows to exploit all of the **Gnuplot** package + functionalities using an extremely efficient and concise syntax. Both + macros accept the same syntax, described below: + + The `@gp` macro accepts any number of arguments, with the following + meaning: + - a string: a command (e.g. "set key left") or plot specification; + - a `Process` or `Session` object: set the current destination; + - a symbol: specifies the data set name; + - an `Int`: if >0 set the current plot destination (if multiplot is + enabled); + - a keyword: set the keyword value (see below); + - any other data type: data to be passed to Gnuplot. Each dataset + must be terminated by either: a symbol (i.e. the data set name) or a + string with the plot specifications (e.g. "with lines"); + - the `:-` symbol, used as first argument, avoids resetting the + Gnuplot session. Used as last argument avoids immediate execution of + the plot/splot command. This symbol can be used to split a single + `@gp` call in multiple ones. + + All entries are optional, and there is no mandatory order. The plot + specification can either be: a complete plot/splot command (e.g., + "plot sin(x)", both "plot" and "splot" can be abbreviated to "p" and + "s" respectively), or a partial specification starting with the "with" + clause (if it follows a data set). + + The list of accepted keyword is as follows: + - `title::String`: plot title; + - `xlabel::String`: X axis label; + - `ylabel::String`: Y axis label; + - `zlabel::String`: Z axis label; + - `xlog::Bool`: logarithmic scale for X axis; + - `ylog::Bool`: logarithmic scale for Y axis; + - `zlog::Bool`: logarithmic scale for Z axis; + - `xrange::NTuple{2, Number}`: X axis range; + - `yrange::NTuple{2, Number}`: Y axis range; + - `zrange::NTuple{2, Number}`: Z axis range; + - `cbrange::NTuple{2, Number}`: Color box axis range; + + The symbol for the above-mentioned keywords may also be used in a + shortened form, as long as there is no ambiguity with other keywords. + E.g. you can use: `xr=(1,10)` in place of `xrange=(1,10)`. + + Beside the above-mentioned keyword the following can also be used + (although with no symbol shortening): + + - verb=Int: a number between 0 and 4, to set the verbosity level; + - file="A string": send all the data and command to a file rather than + to a Gnuplot process; + - stream=A stream: send all the data and command to a stream rather than + to a Gnuplot process; + - term="a string", or term=("a string", "a filename"): to specify the + terminal (and optionally the output file); + + + A rather simple example for the usage of `@gp` is as follows: + ``` + @gp "set key left" tit="My title" xr=(1,12) 1:10 "with lines tit 'Data'" + ``` + + In general, the `@gp` macro tries to figure out the appropriate + meaning of each arugment to allows an easy and straightforward use of + the underlying Gnuplot process. + + The `@gp` macro always send a "reset session" command to Gnuplot at + the beginning, and always run all the given commands at the end, + i.e. it is supposed to be used in cases where all data/commands are + provided in a single `@gp` call. + + To split the call in several statements, avoiding a session reset at + the beginning and an automatic execution of all commands at then, you + should use the `@gpi` macro instead, with exaclty the same syntax as + `@gp`. The `@gpi` macro also accepts the following arguments: + - the `0` number to reset the whole session; + - the `:.` symbol to send all commands to Gnuplot. + + + ## Examples: + ``` + # Simple examples with no data + @gp "plot sin(x)" + @gp "plot sin(x)" "pl cos(x)" + @gp "plo sin(x)" "s cos(x)" + + # Split a `@gp` call in two + @gp "plot sin(x)" :- + @gp :- "plot cos(x)" + + # Insert a 3 second pause between one plot and the next + @gp "plot sin(x)" 2 xr=(-2pi,2pi) "pause 3" "plot cos(4*x)" + + # Simple examples with data: + x = collect(1.:10) + @gp x + @gp x x + @gp x -x + @gp x x.^2 + @gp x x.^2 "w l" + + lw = 3 + @gp x x.^2 "w l lw \$lw" + + # A more complex example + @gp("set grid", "set key left", xlog=true, ylog=true, + title="My title", xlab="X label", ylab="Y label", + x, x.^0.5, "w l tit 'Pow 0.5' dt 2 lw 2 lc rgb 'red'", + x, x , "w l tit 'Pow 1' dt 1 lw 3 lc rgb 'blue'", + x, x.^2 , "w l tit 'Pow 2' dt 3 lw 2 lc rgb 'purple'") + + + # Multiplot example: + @gp(xr=(-2pi,2pi), "unset key", + "set multi layout 2,2 title 'Multiplot title'", + 1, "p sin(x)" , + 2, "p sin(2*x)", + 3, "p sin(3*x)", + 4, "p sin(4*x)") + + # or equivalently + @gp xr=(-2pi,2pi) "unset key" "set multi layout 2,2 title 'Multiplot title'" :- + for i in 1:4 + @gp :- i "p sin(\$i*x)" :- + end + @gp + + + # Multiple gnuplot instances + gp1 = Process(default="set term wxt") + gp2 = Process(default="set term qt") + + @gp gp1 "plot sin(x)" + @gp gp2 "plot sin(x)" + + quitall() + + + # Further examples + x = linspace(-2pi, 2pi, 100); + y = 1.5 * sin.(0.3 + 0.7x) ; + noise = randn(length(x))./2; + e = 0.5 * ones(x); + + @gp verb=2 x y :aa "plot \\\$aa w l" "pl \\\$aa u 1:(2*\\\$2) w l" + + @gsp randn(Float64, 30, 50) + @gp randn(Float64, 30, 50) "w image" + + @gp("set key horizontal", "set grid", + xrange=(-7,7), ylabel="Y label", + x, y, "w l t 'Real model' dt 2 lw 2 lc rgb 'red'", + x, y+noise, e, "w errorbars t 'Data'"); + + + @gp "f(x) = a * sin(b + c*x); a = 1; b = 1; c = 1;" :- + @gp :- x y+noise e :aa :- + @gp :- "fit f(x) \\\$aa u 1:2:3 via a, b, c;" :- + @gp :- "set multiplot layout 2,1" :- + @gp :- "plot \\\$aa w points tit 'Data'" ylab="Data and model" :- + @gp :- "plot \\\$aa u 1:(f(\\\$1)) w lines tit 'Best fit'" :- + @gp :- 2 xlab="X label" ylab="Residuals" :- + @gp :- "plot \\\$aa u 1:((f(\\\$1)-\\\$2) / \\\$3):(1) w errorbars notit" + + # Display an image + using TestImages + img = testimage("lena"); + @gp img "w image" + @gp "set size square" img "w rgbimage" # Color image with correct proportions + @gp "set size square" img "u 2:(-\\\$1):3:4:5 with rgbimage" # Correct orientation + ``` """ macro gp(args...) - # esc_args = Vector{Any}() - # for arg in args - # push!(esc_args, esc(arg)) - # end - # e = :(@gp(splot=true, $(esc_args...))) - # return e - out = Expr(:call) push!(out.args, :(Gnuplot.gpDriver)) - push!(out.args, false) for iarg in 1:length(args) - arg = args[iarg ] + arg = args[iarg] if (isa(arg, Expr) && (arg.head == :(=))) sym = string(arg.args[1]) val = arg.args[2] @@ -1122,86 +939,96 @@ end """ -# @gsp - -See documentation for `@gp`. + # @gsp + + See documentation for `@gp`. """ macro gsp(args...) - out = Expr(:call) - push!(out.args, :(Gnuplot.gpDriver)) - push!(out.args, true) - for iarg in 1:length(args) - arg = args[iarg ] - if (isa(arg, Expr) && (arg.head == :(=))) - sym = string(arg.args[1]) - val = arg.args[2] - push!(out.args, :((Symbol($sym),$val))) - else - push!(out.args, arg) - end - end + out = Expr(:macrocall, Symbol("@gp"), LineNumberNode(1, "Gnuplot.jl")) + push!(out.args, args...) + push!(out.args, Expr(:kw, :splot, true)) return esc(out) end +# #--------------------------------------------------------------------- +# """ +# # repl +# +# Read/evaluate/print/loop +# """ +# function repl(id::Symbol) +# verb = state.verbosity +# state.verbosity = 0 +# gp = getsession(id) +# while true +# line = readline(stdin) +# (line == "") && break +# answer = send(gp, line, true) +# for line in answer +# println(line) +# end +# end +# state.verbosity = verb +# return nothing +# end +# function repl() +# global state +# return repl(state.default) +# end + + #--------------------------------------------------------------------- """ -# @gp_str + # @gp_str + + Send a non-standard string literal to Gnuplot + + NOTE: this is supposed to be used interactively on the REPL, not in + functions. + + ## Examples: + ``` + gp"print GPVAL_TERM" + gp"plot sin(x)" + + gp" + set title \\"3D surface from a grid (matrix) of Z values\\" + set xrange [-0.5:4.5] + set yrange [-0.5:4.5] + + set grid + set hidden3d + \$grid << EOD + 5 4 3 1 0 + 2 2 0 0 1 + 0 0 0 1 0 + 0 0 0 2 3 + 0 1 2 4 3 + EOD + splot '\$grid' matrix with lines notitle + " + ``` -Send a non-standard string literal to Gnuplot - -NOTE: this is supposed to be used interactively on the REPL, not in -functions. - -## Examples: -``` -gp"print GPVAL_TERM" -gp"plot sin(x)" - -gp" -set title \\"3D surface from a grid (matrix) of Z values\\" -set xrange [-0.5:4.5] -set yrange [-0.5:4.5] - -set grid -set hidden3d -\$grid << EOD -5 4 3 1 0 -2 2 0 0 1 -0 0 0 1 0 -0 0 0 2 3 -0 1 2 4 3 -EOD -splot '\$grid' matrix with lines notitle -" -``` + ## Example + ``` + println("Current gnuplot terminal is: ", repl("print GPVAL_TERM")) + ``` """ -macro gp_str(s::String) - for v in split(s, "\n") - send(getCurrent(), string(v)) +function gpeval(id::Symbol, s::Vector{String}) + global state + gp = getsession(id) + answer = Vector{String}() + for v in s + push!(answer, send(gp, v, true)...) end - return nothing + return join(answer, "\n") end - - -#--------------------------------------------------------------------- -""" -# @gp_cmd - -Call the gnuplot "load" command passing the filename given as -non-standard string literal. - -NOTE: this is supposed to be used interactively on the REPL, not in -functions. - -Example: -``` -@gp (1:10).^3 "w l notit lw 4" file="test.gp" -gp`test.gp` -``` -""" -macro gp_cmd(file::String) - return send(getCurrent(), "load '$file'") +function gpeval(s::String) + global state + gpeval(state.default, [s]) end +gpeval(id::Symbol, s::String) = gpeval(id, [s]) + end #module diff --git a/test/runtests.jl b/test/runtests.jl index 4092185..d358639 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,17 +4,13 @@ function gp_test() x = collect(1.:100); #----------------------------------------------------------------- - gp1 = GnuplotProc() - gp2 = GnuplotProc() - gp3 = GnuplotProc() - for i in 1:10 - @gp gp1 "plot sin($i*x)" - @gp gp2 "plot sin($i*x)" - @gp gp3 "plot sin($i*x)" + @gp :gp1 "plot sin($i*x)" + @gp :gp2 "plot sin($i*x)" + @gp :gp3 "plot sin($i*x)" sleep(0.3) end - GnuplotQuitAll() + quitall() #----------------------------------------------------------------- @gp "plot sin(x)" @@ -32,9 +28,14 @@ function gp_test() noise = randn(length(x))./2; e = 0.5 * fill(1, size(x)); + @gp hist(noise, nbins=10) + + @gp x y @gp x y "w l" - @gp x y :aa "plot \$aa w l" "pl \$aa u 1:(2*\$2) w l" + + d = "\$aa" + @gp x y d "plot $d w l" "pl $d u 1:(2*\$2) w l" @gsp randn(Float64, 30, 50) @gp randn(Float64, 30, 50) "w image" @@ -43,11 +44,10 @@ function gp_test() @gp("set key horizontal", "set grid", xrange=(-7,7), ylabel="Y label", x, y, "w l t 'Real model' dt 2 lw 2 lc rgb 'red'", - x, y+noise, e, "w errorbars t 'Data'"); + x, y+noise, e, "w errorbars t 'Data'") - @gp "f(x) = a * sin(b + c*x); a = 1; b = 1; c = 1;" :- - @gp :- x y+noise e :aa :- + @gp :- x y+noise e "\$aa" :- @gp :- "fit f(x) \$aa u 1:2:3 via a, b, c;" :- @gp :- "set multiplot layout 2,1" :- @gp :- "plot \$aa w points" ylab="Data and model" :- @@ -55,7 +55,24 @@ function gp_test() @gp :- 2 xlab="X label" ylab="Residuals" :- @gp :- "plot \$aa u 1:((f(\$1)-\$2) / \$3):(1) w errorbars notit" - + # Retrieve values fr a, b and c + a = parse(Float64, gpeval("print a")) + b = parse(Float64, gpeval("print b")) + c = parse(Float64, gpeval("print c")) + + gnuplot(:dry, dry=true) + @gp :dry "f(x) = a * sin(b + c*x); a = 1; b = 1; c = 1;" :- + @gp :- :dry "a = $a; b = $b; c = $c" :- + @gp :- :dry "set multiplot layout 2,1" ylab="Data and model" :- + d = "\$aa" + @gp :- :dry x y+noise e d :- + @gp :- :dry "plot $d w points" :- + @gp :- :dry "plot $d u 1:(f(\$1)) w lines" :- + @gp :- :dry 2 xlab="X label" ylab="Residuals" :- + @gp :- :dry "plot $d u 1:((f(\$1)-\$2) / \$3):(1) w errorbars notit" :- + @gp :- :dry file="test" # write on file test + gpeval("load 'test'") # load file test + #----------------------------------------------------------------- @gp(""" approx_1(x) = x - x**3/6 @@ -119,9 +136,8 @@ function gp_test() "splot x8, v, (u<0.5) ? -1 : sinc(x8,v) notitle", "splot x9, v, (u<0.5) ? -1 : sinc(x9,v) notitle") - GnuplotQuitAll() + quitall() return true end - gp_test()