From 6a4e95f377e3da76026731a99f2c6a918cd87fe8 Mon Sep 17 00:00:00 2001 From: Giorgio Calderone Date: Tue, 27 Feb 2018 00:32:14 +0100 Subject: [PATCH] Code re-factoring --- src/Gnuplot.jl | 1624 ++++++++++++++++++++++------------------------ test/runtests.jl | 173 ++--- 2 files changed, 847 insertions(+), 950 deletions(-) diff --git a/src/Gnuplot.jl b/src/Gnuplot.jl index 786aa6d..83d3923 100644 --- a/src/Gnuplot.jl +++ b/src/Gnuplot.jl @@ -1,445 +1,252 @@ -__precompile__(false) +__precompile__(true) module Gnuplot using AbbrvKW +###################################################################### +# Exported symbols +###################################################################### + +export CheckGnuplotVersion, GnuplotSession, GnuplotProc, + GnuplotQuit, GnuplotQuitAll, GnuplotGet, setDefault, + @gp, @gpi, @gp_str, @gp_cmd + + ###################################################################### # Structure definitions ###################################################################### - #--------------------------------------------------------------------- -""" -Structure containing a single plot command and the associated -multiplot index. -""" -mutable struct Command - cmd::String # command - id::Int # multiplot index +mutable struct inputData + str::String + sent::Bool + inputData(str::String) = new(str, false) end +mutable struct inputPlot + cmds::Vector{String} + plot::Vector{String} + splot::Bool + inputPlot() = new(Vector{String}(), Vector{String}(), false) +end + + #--------------------------------------------------------------------- -""" -Structure containing the state of a single gnuplot session. -""" -mutable struct Session +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 pin::Base.Pipe pout::Base.Pipe perr::Base.Pipe proc::Base.Process channel::Channel{String} - blockCnt::Int # data blocks counter - lastBlock::String # name of the last data block - cmds::Vector{Command} # gnuplot commands - data::Vector{String} # data blocks - splot::Bool # plot / splot session - plot::Vector{Command} # plot specifications associated to each data block - cid::Int # current multiplot index (0 if no multiplot) - - function Session(cmd) - this = new() - this.pin = Base.Pipe() - this.pout = Base.Pipe() - this.perr = Base.Pipe() - this.channel = Channel{String}(32) - - # Start gnuplot process - this.proc = spawn(`$cmd`, (this.pin, this.pout, this.perr)) - - # Close unused sides of the pipes - Base.close_pipe_sync(this.pout.in) - Base.close_pipe_sync(this.perr.in) - Base.close_pipe_sync(this.pin.out) - Base.start_reading(this.pout.out) - Base.start_reading(this.perr.out) - - this.blockCnt = 0 - this.lastBlock = "" - this.cmds = Vector{Command}() - this.data = Vector{String}() - this.splot = false - this.plot = Vector{Command}() - this.cid = 0 - return this - end -end - - -#--------------------------------------------------------------------- -""" -Structure containing the global package state. -""" -mutable struct State - sessions::Dict{Int, Session} - current::Int - - State() = new(Dict{Int, Session}(), 0) -end - -const state = State() - - -#--------------------------------------------------------------------- -""" -Structure containing the global options. -""" -mutable struct Options - colorOut::Symbol # gnuplot STDOUT is printed with this color - colorIn::Symbol # gnuplot STDIN is printed with this color verbosity::Int # verbosity level (0 - 4), default: 3 - gnuplotCmd::String # command used to start the gnuplot process - startup::String # commands automatically sent to each new gnuplot process - - Options() = new(:cyan, :yellow, 3, "gnuplot", "") + session::GnuplotSession end -const gpOptions = Options() - - ###################################################################### -# Utils +# 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 +end + +function getCurObj() + global g_state + if !(g_state.id in keys(g_state.obj)) + info("Creating default Gnuplot process...") + out = GnuplotProc() + setDefault(out) + end + return g_state.obj[g_state.id] +end + + +###################################################################### +# Private functions ###################################################################### #--------------------------------------------------------------------- """ -Logging facility (each line is prefixed with the session ID.) +Logging facility Printing occur only if the logging level is >= current verbosity level. """ -function log(level::Int, s::String, id=0) - (gpOptions.verbosity < level) && return - if id == 0 - id = state.current - color = gpOptions.colorOut - else - color = gpOptions.colorIn - end - prefix = string("GP(", id, ")") - for v in split(s, "\n") - print_with_color(color, "$prefix $v\n") - end +function logIn(gp::GnuplotProc, s::AbstractString) + (gp.verbosity < 1) && return nothing + print_with_color(:yellow , "GNUPLOT ($(gp.id)) -> $s\n") + return nothing +end + +function logData(gp::GnuplotProc, s::AbstractString) + (gp.verbosity < 4) && return nothing + print_with_color(:light_black, "GNUPLOT ($(gp.id)) -> $s\n") + return nothing +end + +function logOut(gp::GnuplotProc, s::AbstractString) + (gp.verbosity < 2) && return nothing + print_with_color(:cyan , "GNUPLOT ($(gp.id)) $s\n") + return nothing +end + +function logErr(gp::GnuplotProc, s::AbstractString) + (gp.verbosity < 3) && return nothing + print_with_color(:cyan , "GNUPLOT ($(gp.id)) $s\n") + return nothing +end + +function logCaptured(gp::GnuplotProc, s::AbstractString) + (gp.verbosity < 3) && return nothing + print_with_color(:green , "GNUPLOT ($(gp.id)) $s\n") return nothing end #--------------------------------------------------------------------- """ -sessionCollector -""" -function sessionCollector() - for (id, cur) in state.sessions - if !Base.process_running(cur.proc) - log(3, "Deleting session $id") - delete!(state.sessions, id) - - if (id == state.current) - state.current = 0 - end - end - end -end - - -#--------------------------------------------------------------------- -""" -Read gnuplot outputs, and optionally redirect to a `Channel`. +Read gnuplot outputs and optionally redirect to a `Channel`. This fuction is supposed to be run in a `Task`. """ -function readTask(sIN, channel, id) +function readTask(gp::GnuplotProc, useStdErr::Bool) saveOutput = false + + sIN = gp.pout + if useStdErr + sIN = gp.perr + end + while isopen(sIN) line = convert(String, readline(sIN)) - if line == "GNUPLOT_JL_SAVE_OUTPUT" + if line == "GNUPLOT_CAPTURE_BEGIN" saveOutput = true - log(4, "|begin of captured data =========================", id) else if saveOutput - put!(channel, line) + put!(gp.channel, line) end - if line == "GNUPLOT_JL_SAVE_OUTPUT_END" + if line == "GNUPLOT_CAPTURE_END" saveOutput = false - log(4, "|end of captured data ===========================", id) elseif line != "" if saveOutput - log(3, "| " * line, id) + logCaptured(gp, line) else - log(2, " " * line, id) + if useStdErr + logErr(gp, line) + else + logOut(gp, line) + end end end end end - log(1, "pipe closed") + logOut(gp, "pipe closed") + + global g_state + delete!(g_state.obj, gp.id) + return nothing end #--------------------------------------------------------------------- -""" -Return the current session, or start a new one if none is running. -""" -function getCurrentOrStartIt() - sessionCollector() +@AbbrvKW function parseKeywords(; + xrange::Union{Void,NTuple{2, Number}}=nothing, + yrange::Union{Void,NTuple{2, Number}}=nothing, + zrange::Union{Void,NTuple{2, Number}}=nothing, + title::Union{Void,String}=nothing, + xlabel::Union{Void,String}=nothing, + ylabel::Union{Void,String}=nothing, + zlabel::Union{Void,String}=nothing, + xlog::Union{Void,Bool}=nothing, + ylog::Union{Void,Bool}=nothing, + zlog::Union{Void,Bool}=nothing) - if haskey(state.sessions, state.current) - return state.sessions[state.current] - end - - return state.sessions[gpNewSession()] -end - - -###################################################################### -# Exported symbols -###################################################################### - -export gpCheckVersion, gpNewSession, gpExit, gpExitAll, gpSetCurrentID, - gpCurrentID, gpGetIDs, gpSend, gpReset, gpCmd, gpData, gpLastBlock, - gpGetVal, gpPlot, gpMulti, gpNext, gpDump, gpOptions, - gpTerminals, gpTerminal, - @gpi, @gp, @gp_str, @gp_cmd - -#--------------------------------------------------------------------- -""" -Check gnuplot is runnable with the command given in `state.gnuplotCmd`. -Also check that gnuplot version is >= 4.7 (required to use data -blocks). -""" -function gpCheckVersion() - cmd = `$(gpOptions.gnuplotCmd) --version` - out, procs = open(`$cmd`, "r") - s = String(read(out)) - if !success(procs) - error("An error occurred while running: " * string(cmd)) - end - - s = split(s, " ") - ver = "" - for token in s - try - ver = VersionNumber("$token") - break - catch - end - 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 - log(1, "Found gnuplot version: " * string(ver)) - return ver -end - - - -#--------------------------------------------------------------------- -""" -# gpNewSession - -Create a new session (by starting a new gnuplot process), make it the -current one, and return the new session ID. - -E.g., to compare the look and feel of two terminals: -``` -id1 = gpNewSession() -gpSend("set term qt") -gpSend("plot sin(x)") - -id2 = gpNewSession() -gpSend("set term wxt") -gpSend("plot sin(x)") - -gpSetCurrentID(id1) -gpSend("set title 'My title'") -gpSend("replot") - -gpSetCurrentID(id2) -gpSend("set title 'My title'") -gpSend("replot") - -gpExitAll() -``` -""" -function gpNewSession() - gpCheckVersion() - - cur = Session(gpOptions.gnuplotCmd) - - id = 1 - if length(state.sessions) > 0 - id = maximum(keys(state.sessions)) + 1 - end - state.sessions[id] = cur - state.current = id - - # Start reading tasks for STDOUT and STDERR - @async readTask(cur.pout, cur.channel, id) - @async readTask(cur.perr, cur.channel, id) - gpCmd(gpOptions.startup) - - log(1, "New session started with ID $id") - return id + out = Vector{String}() + xrange == nothing || (push!(out, "set xrange [" * join(xrange, ":") * "]")) + yrange == nothing || (push!(out, "set yrange [" * join(yrange, ":") * "]")) + zrange == nothing || (push!(out, "set zrange [" * join(zrange, ":") * "]")) + title == nothing || (push!(out, "set title '" * title * "'")) + xlabel == nothing || (push!(out, "set xlabel '" * xlabel * "'")) + ylabel == nothing || (push!(out, "set ylabel '" * ylabel * "'")) + zlabel == nothing || (push!(out, "set zlabel '" * zlabel * "'")) + xlog == nothing || (push!(out, (xlog ? "" : "un") * "set logscale x")) + ylog == nothing || (push!(out, (ylog ? "" : "un") * "set logscale y")) + zlog == nothing || (push!(out, (zlog ? "" : "un") * "set logscale z")) + return out end #--------------------------------------------------------------------- -""" -# gpExit - -Close current session and quit the corresponding gnuplot process. -""" -function gpExit() - sessionCollector() - - if length(state.sessions) == 0 - log(1, "No session to close.") - return 0 - end - - cur = state.sessions[state.current] - close(cur.pin) - close(cur.pout) - close(cur.perr) - wait( cur.proc) - exitCode = cur.proc.exitcode - log(1, string("Process exited with status ", exitCode)) - - sessionCollector() - - # Select next session - if length(state.sessions) > 0 - state.current = maximum(keys(state.sessions)) - end - - return exitCode -end - - -#--------------------------------------------------------------------- -""" -# gpExitAll - -Repeatedly call `gpExit` until all sessions are closed. -""" -function gpExitAll() - while length(state.sessions) > 0 - gpExit() - end - return nothing -end - - -#--------------------------------------------------------------------- -""" -# gpSetCurrentID - -Change the current session. - -## Arguments: -- `handle::Int`: the handle of the session to select as current. - -## See also: -- `gpCurrentID`: return the current session handle; -- `gpGetIDs`: return the list of available handles. -""" -function gpSetCurrentID(id::Int) - sessionCollector() - @assert haskey(state.sessions, id) "No session with ID $id" - state.current = id - nothing -end - - -#--------------------------------------------------------------------- -""" -# gpCurrentID - -Return the handle of the current session. -""" -function gpCurrentID() - sessionCollector() - return state.current -end - - -#--------------------------------------------------------------------- -""" -# gpGetIDs - -Return a `Vector{Int}` of available session IDs. -""" -function gpGetIDs() - sessionCollector() - return keys(state.sessions) -end - - -###################################################################### -# Send data and commands to Gnuplot -###################################################################### - """ # gpSend -Send a string to the current session's gnuplot STDIN. +Send a string to gnuplot's STDIN. The commands sent through `gpSend` are not stored in the current -session (use `cmd` to save commands in the current session). +session (use `addCmd` to save commands in the current session). ## Example: ``` -println("Current terminal: ", gpSend("print GPVAL_TERM", capture=true)) +gp = GnuplotProc() +gpSend(gp, "plot sin(x)") ``` ## Arguments: -- `cmd::String`: command to be sent. - -## Keywords: -- `capture::Bool`: if `true` waits until gnuplot provide a complete - reply, and return it as a `Vector{String}`. Otherwise return - `nothing` immediately. +- `gp`: a GnuplotProc or GnuplotSession object; +- `str::String`: command to be sent. """ -@AbbrvKW function gpSend(cmd::String; capture::Bool=false, verbosity=2) - p = getCurrentOrStartIt() +function gpSend(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) + w <= 0 && error("Writing on gnuplot STDIN pipe returned $w") + (capture) && (write(gp.pin, "print 'GNUPLOT_CAPTURE_END'\n")) + flush(gp.pin) + out = Vector{String}() if capture - write(p.pin, "print 'GNUPLOT_JL_SAVE_OUTPUT'\n") - end - - for s in split(cmd, "\n") - w = write(p.pin, strip(s) * "\n") - log(verbosity, "-> $s") - w <= 0 && error("Writing on gnuplot STDIN pipe returned $w") - end - - if capture - write(p.pin, "print 'GNUPLOT_JL_SAVE_OUTPUT_END'\n") - end - flush(p.pin) - - if capture - out = Vector{String}() while true - l = take!(p.channel) - l == "GNUPLOT_JL_SAVE_OUTPUT_END" && break + l = take!(gp.channel) + l == "GNUPLOT_CAPTURE_END" && break push!(out, l) end - - length(out) == 1 && (out = out[1]) - return out end - - return nothing + return out end @@ -447,144 +254,36 @@ end """ # gpReset +Delete all commands, data, and plots in the gnuplot session. +""" +function gpReset(gp::GnuplotSession) + gp.blockCnt = 0 + gp.data = Vector{inputData}() + gp.plot = [inputPlot()] + gp.multiID = 1 + addCmd(gp, gp.defCmd) + return nothing +end + +""" +# gpReset + Send a 'reset session' command to gnuplot and delete all commands, -data, and plots in the current session. +data, and plots in the associated session. """ -function gpReset() - cur = getCurrentOrStartIt() - - cur.blockCnt = 0 - cur.lastBlock = "" - cur.cmds = Vector{Command}() - cur.data = Vector{String}() - cur.splot = false - cur.plot = Vector{Command}() - cur.cid = 0 - - gpSend("reset session", capture=true) - gpCmd(gpOptions.startup) +function gpReset(gp::GnuplotProc) + gpReset(gp.session) + gpSend(gp, "reset session") + gpSend(gp, gp.session.defCmd) return nothing end #--------------------------------------------------------------------- -""" -# gpCmd - -Send a command to gnuplot process and store it in the current session. -A few, commonly used, commands may be specified through keywords (see -below). - -## Examples: -``` -gpCmd("set grid") -gpCmd("set key left", xrange=(1,3)) -gpCmd(title="My title", xlab="X label", xla="Y label") -``` - -## Arguments: -- `cmd::String`: command to be sent. - -## Keywords: -- `cid::Int`: ID of the plot the commands belongs to (only useful - for multiplots); -- `splot::Bool`: set to `true` for a "splot" gnuplot session, `false` - for a "plot" one; -- `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; -""" -@AbbrvKW function gpCmd(s::String=""; - splot::Union{Void,Bool}=nothing, - cid::Union{Void,Int}=nothing, - xrange::Union{Void,NTuple{2, Number}}=nothing, - yrange::Union{Void,NTuple{2, Number}}=nothing, - zrange::Union{Void,NTuple{2, Number}}=nothing, - title::Union{Void,String}=nothing, - xlabel::Union{Void,String}=nothing, - ylabel::Union{Void,String}=nothing, - zlabel::Union{Void,String}=nothing, - xlog::Union{Void,Bool}=nothing, - ylog::Union{Void,Bool}=nothing, - zlog::Union{Void,Bool}=nothing) - - cur = getCurrentOrStartIt() - mID = (cid == nothing ? cur.cid : cid) - - if splot != nothing - cur.splot = splot - end - - if s != "" - push!(cur.cmds, Command(s, mID)) - if mID == 0 - gpSend(s) - end - end - - xrange == nothing || gpCmd(cid=mID, "set xrange [" * join(xrange, ":") * "]") - yrange == nothing || gpCmd(cid=mID, "set yrange [" * join(yrange, ":") * "]") - zrange == nothing || gpCmd(cid=mID, "set zrange [" * join(zrange, ":") * "]") - - title == nothing || gpCmd(cid=mID, "set title '" * title * "'") - xlabel == nothing || gpCmd(cid=mID, "set xlabel '" * xlabel * "'") - ylabel == nothing || gpCmd(cid=mID, "set ylabel '" * ylabel * "'") - zlabel == nothing || gpCmd(cid=mID, "set zlabel '" * zlabel * "'") - - xlog == nothing || gpCmd(cid=mID, (xlog ? "" : "un") * "set logscale x") - ylog == nothing || gpCmd(cid=mID, (ylog ? "" : "un") * "set logscale y") - zlog == nothing || gpCmd(cid=mID, (zlog ? "" : "un") * "set logscale z") - - return nothing -end - - -#--------------------------------------------------------------------- -""" -# gpData - -Send data to the gnuplot process, store it in the current session, and -return the name of the data block (to be later used with `plot`). - -## Example: -``` -x = collect(1.:10) - -# Automatically generated data block name -name1 = gpData(x, x.^2) - -# Specify the data block name. NOTE: avoid using the same name -# multiple times! -name2 = gpData(x, x.^1.8, name="MyChosenName") - -gpPlot(name1) -gpPlot(name2) -gpDump() -``` - -## Arguments: -- `data::Vararg{AbstractArray{T,1},N} where {T<:Number,N}`: the data - to be sent to gnuplot; - -## Keywords: -- `name::String`: data block name. If not given an automatically - generated one will be used; -- `prefix::String`: prefix for data block name (an automatic counter - will be appended); -""" -function gpData(data::Vararg{AbstractArray{T},M}; name="") where {T<:Number,M} - cur = getCurrentOrStartIt() - +function addData(gp::GnuplotSession, data::Vararg{AbstractArray{T},M}; name="") where {T<:Number,M} if name == "" - name = string("data", cur.blockCnt) - cur.blockCnt += 1 + name = string("data", gp.blockCnt) + gp.blockCnt += 1 end name = "\$$name" @@ -635,10 +334,8 @@ function gpData(data::Vararg{AbstractArray{T},M}; name="") where {T<:Number,M} end end - v = "$name << EOD" - push!(cur.data, v) - gpSend(v, verbosity=3) + push!(gp.data, inputData(v)) if !is2D for i in 1:dimX @@ -646,8 +343,7 @@ function gpData(data::Vararg{AbstractArray{T},M}; name="") where {T<:Number,M} for j in 1:length(data) v *= " " * string(data[j][i]) end - push!(cur.data, v) - gpSend(v, verbosity=4) + push!(gp.data, inputData(v)) end else for i in 1:dimX @@ -657,21 +353,37 @@ function gpData(data::Vararg{AbstractArray{T},M}; name="") where {T<:Number,M} ndims(d) == 1 && (continue) v *= " " * string(d[i,j]) end - push!(cur.data, v) - gpSend(v, verbosity=4) + push!(gp.data, inputData(v)) end - push!(cur.data, "") - gpSend("", verbosity=4) + push!(gp.data, inputData("")) end end v = "EOD" - push!(cur.data, v) - gpSend(v, verbosity=3) + push!(gp.data, inputData(v)) - cur.lastBlock = name - if is2D - cur.splot = true + return (name, is2D) +end + + +function addData(gp::GnuplotProc, data::Vararg{AbstractArray{T},M}; name="") where {T<:Number,M} + name = addData(gp.session, data..., name=name) + + first = true + count = 0 + for v in gp.session.data + (v.sent) && (continue) + if gp.verbosity >= 4 + (v.str == "EOD") && (count = -1) + if count < 4 + logData(gp, v.str) + elseif count == 4 + logData(gp, "...") + end + count += 1 + end + w = write(gp.pin, v.str*"\n") + v.sent = true end return name @@ -679,30 +391,439 @@ end #--------------------------------------------------------------------- -""" -# gpLastBlock +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) +end +setMultiID(gp::GnuplotProc, id::Int) = setMultiID(gp.session, id) -Return the name of the last data block. + +#--------------------------------------------------------------------- +function setSplot(gp::GnuplotSession, splot::Bool) + gp.plot[gp.multiID].splot = splot +end +setSplot(gp::GnuplotProc, splot::Bool) = setSplot(gp.session, splot) + + + +#--------------------------------------------------------------------- """ -function gpLastBlock() - cur = getCurrentOrStartIt() - return cur.lastBlock +# 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)) + return nothing +end + +function addCmd(gp::GnuplotSession; id::Int=0, args...) + for v in parseKeywords(;args...) + addCmd(gp, v, id=id) + end + return nothing +end + +function addCmd(gp::GnuplotProc, s::String; id::Int=0) + addCmd(gp.session, s, id=id) + (length(gp.session.plot) == 1) && (gpSend(gp, s)) +end + +function addCmd(gp::GnuplotProc; id::Int=0, args...) + for v in parseKeywords(;args...) + addCmd(gp, v, id=id) + end +end + + +#--------------------------------------------------------------------- +function addPlot(gp::GnuplotSession, name, opt=""; id=0) + setMultiID(gp, id) + push!(gp.plot[gp.multiID].plot, "$name $opt") +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}`. +""" +@AbbrvKW function gpDump(gp::Union{GnuplotSession,GnuplotProc}; + term=("", ""), file="", stream=nothing, asArray=false) + + session = (typeof(gp) == GnuplotProc ? gp.session : gp) + ret = Vector{String}() + + dump2Gp = false + dumpCmds = false + dumpData = false + + if file == "" && + stream == nothing && + asArray == false + # No outut is selected + if typeof(gp) == GnuplotProc + dump2Gp = true + else + stream = STDOUT + end + end + + if file != "" || + stream != nothing || + asArray + dumpCmds = true + dumpData = true + end + + if !dumpCmds + dumpCmds = (length(session.plot) > 1) + end + + # Open output file + if file != "" + sfile = open(file, "w") + dumpData = true + end + + function gpDumpInt(s::String) + (file != "") && (println(sfile , s)) + (stream != nothing) && (println(stream, s)) + (asArray) && (push!(ret, s)) + (dump2Gp) && (gpSend(gp, s)) + return nothing + end + + if dumpData + gpDumpInt("reset session") + for v in session.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) + if dumpCmds + for s in session.plot[id].cmds + gpDumpInt(s) + end + end + + plot = Vector{String}() + for s in session.plot[id].plot; push!(plot, s); end + if length(plot) > 0 + s = (session.plot[id].splot ? "splot " : "plot ") * " \\\n " * + join(plot, ", \\\n ") + gpDumpInt(s) + end + end + + (length(session.plot) > 1) && (gpDumpInt("unset multiplot")) + + (term[2] != "") && (gpDumpInt("set output")) + (file != "") && (close(sfile)) + + return ret +end + + +#--------------------------------------------------------------------- +function gpDriver(args...) + gp = nothing + for arg in args + if typeof(arg) == GnuplotProc || + typeof(arg) == GnuplotSession + gp = arg + end + end + if gp == nothing + gp = getCurObj() + end + + if length(args) == 0 + #gpDump(gp) + return nothing + end + + eData = Vector{Any}() + dataName = "" + addDump = false + term = ("", "") + file="" + stream=nothing + + function endOfData(associatedPlot=nothing) + if length(eData) > 0 + (last, splot) = addData(gp, eData...; name=dataName) + if associatedPlot != nothing + setSplot(gp, splot) + addPlot(gp, last, associatedPlot) + end + end + eData = Vector{Any}() + dataName = "" + end + function endOfStream(forceDump::Bool) + endOfData("") + (forceDump || addDump) && (gpDump(gp; term=term, file=file, stream=stream)) + addDump = false + term = ("", "") + file="" + stream=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]))) + (length(s) >= 5) && (s[1:5] == "splo " ) && (return (true, true , strip(s[5:end]))) + (length(s) >= 6) && (s[1:6] == "splot ") && (return (true, true , strip(s[6:end]))) + return (false, false, "") + end + + for iarg in 1:length(args) + arg = args[iarg] + + if typeof(arg) == GnuplotProc || + typeof(arg) == GnuplotSession + continue + elseif typeof(arg) == Symbol + if arg == :> + addDump = true + else + dataName = string(arg) + endOfData() + end + elseif isa(arg, Int) + if arg == 0 + gpReset(gp) + else + endOfData("") + setMultiID(gp, arg) + end + 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) + else + addCmd(gp, 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]) + else + error("The term tuple must contain at most two strings") + 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) + end + else + # A data set + push!(eData, arg) + end + end + endOfStream(false) + + 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). +""" +function CheckGnuplotVersion(cmd::String) + icmd = `$(cmd) --version` + out, procs = open(`$icmd`, "r") + s = String(read(out)) + if !success(procs) + error("An error occurred while running: " * string(icmd)) + end + + s = split(s, " ") + ver = "" + for token in s + try + ver = VersionNumber("$token") + break + catch + end + 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 end #--------------------------------------------------------------------- """ -# gpGetVal +# 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. +""" +function GnuplotSession(;default="") + global g_state + id = newID() + out = GnuplotSession(id, 0, Vector{inputData}(), + [inputPlot()], 1, default) + g_state.obj[id] = out + gpReset(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 = spawn(`$cmd`, (pin, pout, perr)) + + 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_pipe_sync(out.pout.in) + Base.close_pipe_sync(out.perr.in) + Base.close_pipe_sync(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) + gpReset(out) + return out +end + + +#--------------------------------------------------------------------- +""" +# quit + +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 + + +#--------------------------------------------------------------------- +""" +# GnuplotQuitAll + +Close all the started sessions and the associated gnuplot processes. +""" +function GnuplotQuitAll() + global g_state + for (id, obj) in g_state.obj + GnuplotQuit(obj) + end + return 0 +end + + +#--------------------------------------------------------------------- +""" +# GnuplotGet Return the value of one (or more) gnuplot variables. ## Example -- argtuple of strings with gnuplot variable +``` +println("Current gnuplot terminal is: ", GnuplotGet("GPVAL_TERM")) +``` """ -function gpGetVal(args...) +function GnuplotGet(gp::GnuplotProc, args...) out = Vector{String}() for arg in args - push!(out, string(gpSend("print $arg", capture=true)...)) + v = string(arg) + answer = gpSend(gp, "print $v", true) + for check in answer + if length(search(check, "undefined variable:")) > 0 + error(check) + end + end + push!(out, answer...) end if length(out) == 1 @@ -715,267 +836,11 @@ end #--------------------------------------------------------------------- """ -# gpPlot - -Add a new plot/splot comand to the current session - -## Example: -``` -x = collect(1.:10) - -gpData(x, x.^2) -gpPlot(last=true, "w l tit 'Pow 2'") - -src = gpData(x, x.^2.2) -gpPlot("\$src w l tit 'Pow 2.2'") - -# Re use the same data block -gpPlot("\$src u 1:(\\\$2+10) w l tit 'Pow 2.2, offset=10'") - -gpDump() # Do the plot -``` - -## Arguments: -- `spec::String`: plot command (see Gnuplot manual) without the - leading "plot" string; - -## Keywords: - -- `file::String`: if given the plot command will be prefixed with - `'\$file'`; -- `lastBlock::Bool`: if true the plot command will be prefixed with the - last inserted data block name; -- `cid::Int`: ID of the plot the command belongs to (only useful - for multiplots); +# setDefault """ -@AbbrvKW function gpPlot(spec::String; - lastBlock::Bool=false, - file::Union{Void,String}=nothing, - cid::Union{Void,Int}=nothing) - - cur = getCurrentOrStartIt() - mID = (cid == nothing ? cur.cid : cid) - - src = "" - if lastBlock - src = cur.lastBlock - elseif file != nothing - src = "'" * file * "'" - end - push!(cur.plot, Command("$src $spec", mID)) - return nothing -end - - -#--------------------------------------------------------------------- -""" -# gpMulti - -Initialize a multiplot (through the "set multiplot" Gnuplot command). - -## Arguments: - -- `multiCmd::String`: multiplot command (see Gnuplot manual) without - the leading "set multiplot" string; - -## See also: `gpNext`. -""" -function gpMulti(multiCmd::String="") - cur = getCurrentOrStartIt() - if cur.cid != 0 - error("Current multiplot ID is $(cur.cid), while it should be 0") - end - - cur.cid += 1 - gpCmd("set multiplot $multiCmd") - - # Ensure all plot commands have ID >= 1 - for p in cur.plot - p.id < 1 && (p.id = 1) - end - - return nothing -end - - -#--------------------------------------------------------------------- -""" -# gpNext - -Select next slot for multiplot sessions. -""" -function gpNext() - cur = getCurrentOrStartIt() - cur.cid += 1 - return nothing -end - - -#--------------------------------------------------------------------- -""" -# gpDump - -Send all necessary commands to gnuplot to actually do the plot. -Optionally, the commands may be sent to a file. In any case the -commands are returned as `Vector{String}`. - -## Keywords: - -- `all::Bool`: if true all commands and data will be sent again to - gnuplot, if they were already sent (equivalent to `data=true, - cmd=true`); - -- `cmd::Bool`: if true all commands will be sent again to gnuplot, if - they were already sent; - -- `data::Bool`: if true all data will be sent again to gnuplot, if - they were already sent; - -- `dry::Bool`: if true no command/data will be sent to gnuplot; - -- `file::String`: filename to redirect all outputs. Implies - `all=true, dry=true`. -""" -@AbbrvKW function gpDump(; all::Bool=false, - dry::Bool=false, - cmd::Bool=false, - data::Bool=false, - file::Union{Void,String}=nothing) - if file != nothing - all = true - dry = true - end - - cur = getCurrentOrStartIt() - out = Vector{String}() - - all && (push!(out, "reset session")) - - if data || all - for s in cur.data - push!(out, s) - end - end - - for id in 0:cur.cid - for m in cur.cmds - if (m.id == id) && ((id > 0) || all) - push!(out, m.cmd) - end - end - - tmp = Vector{String}() - for m in cur.plot - if m.id == id - push!(tmp, m.cmd) - end - end - - if length(tmp) > 0 - s = cur.splot ? "splot " : "plot " - s *= "\\\n " - s *= join(tmp, ", \\\n ") - push!(out, s) - end - end - - if cur.cid > 0 - push!(out, "unset multiplot") - end - - if file != nothing - sOut = open(file, "w") - for s in out; println(sOut, s); end - close(sOut) - end - - if !dry - for s in out; gpSend(s); end - gpSend("", capture=true) - end - - return join(out, "\n") -end - - -###################################################################### -# Misc. functions -###################################################################### -gpTerminals() = gpSend("print GPVAL_TERMINALS", capture=true) -gpTerminal() = gpSend("print GPVAL_TERM", capture=true) - - -#--------------------------------------------------------------------- -""" -# @gpi - -Similar to `@gp`, but the call to `gpReset()` occur only when -the `:reset` symbol is given, and the `gpDump()` call occur only -if no arguments are given. - -See `@gp` documentation for further information. -""" -macro gpi(args...) - if length(args) == 0 - return :(gpDump()) - end - - exprBlock = Expr(:block) - - exprData = Expr(:call) - push!(exprData.args, :(gpData)) - - pendingPlot = false - pendingMulti = false - for arg in args - #println(typeof(arg), " ", arg) - - if isa(arg, Expr) && (arg.head == :quote) && (arg.args[1] == :reset) - push!(exprBlock.args, :(gpReset())) - elseif isa(arg, Expr) && (arg.head == :quote) && (arg.args[1] == :dump) - push!(exprBlock.args, :(gpDump())) - elseif isa(arg, Expr) && (arg.head == :quote) && (arg.args[1] == :plot) - pendingPlot = true - elseif isa(arg, Expr) && (arg.head == :quote) && (arg.args[1] == :multi) - pendingMulti = true - elseif isa(arg, Expr) && (arg.head == :quote) && (arg.args[1] == :next) - push!(exprBlock.args, :(gpNext())) - elseif (isa(arg, Expr) && (arg.head == :string)) || isa(arg, String) - # Either a plot or cmd string - if pendingPlot - if length(exprData.args) > 1 - push!(exprBlock.args, exprData) - exprData = Expr(:call) - push!(exprData.args, :(gpData)) - end - - push!(exprBlock.args, :(gpPlot(last=true, $arg))) - pendingPlot = false - elseif pendingMulti - push!(exprBlock.args, :(gpMulti($arg))) - pendingMulti = false - else - push!(exprBlock.args, :(gpCmd($arg))) - end - elseif (isa(arg, Expr) && (arg.head == :(=))) - # A cmd keyword - sym = arg.args[1] - val = arg.args[2] - push!(exprBlock.args, :(gpCmd($sym=$val))) - else - # A data set - push!(exprData.args, arg) - pendingPlot = true - end - end - #gpDump(exprBlock) - - if pendingPlot && length(exprData.args) >= 2 - push!(exprBlock.args, exprData) - push!(exprBlock.args, :(gpPlot(last=true, ""))) - end - - return esc(exprBlock) +function setDefault(gp::GnuplotProc) + @assert (gp.id in keys(g_state.obj)) "Invalid Gnuplot ID: $id" + g_state.id = gp.id end @@ -983,52 +848,92 @@ end """ # @gp -The `@gp` (and its companion `gpi`) allows to exploit almost all -**jl** package functionalities using an extremely efficient -and concise syntax. In the vast majority of cases you can use a -single call to `@gp` instead of many calls to `gpCmd`, `gpData`, -`gpPlot`, etc... to produce (even very complex) plots. +The `@gp` (and its companion `@gpi`) allows to exploit all of the +**Gnuplot** package functionalities using an extremely efficient and +concise syntax. -The syntax is as follows: +The `@gp` 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 sink; +- a symbol: specifies the data set name; +- an `Int`: if >0 set the current plot destination (if multiplot is + enabled). If 0 reset the whole session. +- a keyword: set the keysowrd 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"). + +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; + +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( ["a command"], # passed to gpCmd() as a command string - [Symbol=(Value | Expr)] # passed to gpCmd() as a keyword - [(one or more Expression | Array) "plot spec"], # passed to gpData() and - # gpPlot(last=true) respectively - [:plot "plot spec"], # passed to gpPlot() - [:multi "multi spec"], # passed to gpMulti() - [:next] # calls gpNext() - etc... -) +@gp "set key left" tit="My title" xr=(1,12) 1:10 "with lines tit 'Data'" ``` -All entries are optional, and there is no mandatory order. The only -mandatory sequences are: -- the plot specification strings which must follow a data block or the `:plot` symbol; -- the multiplot specification string which must follow `:multi` symbol; +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. -A simple example will clarify the usage: -``` -@gp "set key left" title="My title" xr=(1,12) 1:10 "with lines tit 'Data'" -``` +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. -This call epands as follows: -``` -gpReset() -begin - gpCmd("set key left") - gpCmd(title="My title") - gpCmd(xr=(1, 12)) - gpData(1:10) - gpPlot(last=true, "with lines tit 'Data'") -end -gpDump() -``` -A closely related macro is `@gpi` which do not adds the `gpReset()` -and `gpDump()` calls. ## 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 +@gpi 0 "plot sin(x)" +@gpi "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 @@ -1039,27 +944,64 @@ x = collect(1.:10) 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 + +# Multiplot example: @gp(xr=(-2pi,2pi), "unset key", - :multi, "layout 2,2 title 'Multiplot title'", - :plot, "sin(x)" , :next, - :plot, "sin(2*x)", :next, - :plot, "sin(3*x)", :next, - :plot, "sin(4*x)") + "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 -@gpi(:reset, xr=(-2pi,2pi), "unset key", - :multi, "layout 2,2 title 'Multiplot title'") +@gpi 0 xr=(-2pi,2pi) "unset key" "set multi layout 2,2 title 'Multiplot title'" for i in 1:4 - @gpi :plot "sin(\$i*x)" :next + @gpi i "p sin(\$i*x)" end -@gpi() +@gpi :> + + +# 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" +@gp randn(Float64, 30, 50) + +@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'"); + + +@gpi 0 "f(x) = a * sin(b + c*x); a = 1; b = 1; c = 1;" +@gpi x y+noise e :aa +@gpi "fit f(x) \\\$aa u 1:2:3 via a, b, c;" +@gpi "set multiplot layout 2,1" +@gpi "plot \\\$aa w points" ylab="Data and model" +@gpi "plot \\\$aa u 1:(f(\\\$1)) w lines" +@gpi 2 xlab="X label" ylab="Residuals" +@gpi "plot \\\$aa u 1:((f(\\\$1)-\\\$2) / \\\$3):(1) w errorbars notit" :> + ``` """ macro gp(args...) @@ -1067,27 +1009,46 @@ macro gp(args...) for arg in args push!(esc_args, esc(arg)) end - e = :(@gpi($(esc_args...))) + e = :(@gpi(0, $(esc_args...), :>)) - f = Expr(:block) - push!(f.args, esc(:( gpReset()))) - push!(f.args, e) - push!(f.args, esc(:( gpDump()))) - - return f + return e end + +""" +# @gpi + +See documentation for `@gp`. +""" +macro gpi(args...) + out = Expr(:call) + push!(out.args, :(Gnuplot.gpDriver)) + 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 + return esc(out) +end + + +#--------------------------------------------------------------------- """ # @gp_str -Call `gpSend` with a non-standard string literal. +Send a non-standard string literal to Gnuplot NOTE: this is supposed to be used interactively on the REPL, not in functions. ## Examples: ``` -println("Current terminal: ", gp"print GPVAL_TERM") +gp"print GPVAL_TERM" gp"plot sin(x)" gp" @@ -1109,7 +1070,10 @@ splot '\$grid' matrix with lines notitle ``` """ macro gp_str(s::String) - return gpSend(s, capture=true) + for v in split(s, "\n") + gpSend(getCurObj(), string(v)) + end + return nothing end @@ -1126,14 +1090,12 @@ functions. Example: ``` @gp (1:10).^3 "w l notit lw 4" -gpDump(file="test.gp") -gpExitAll() +gpDump(gp, file="test.gp") gp`test.gp` ``` """ macro gp_cmd(file::String) - return gpSend("load '$file'", capture=true) + return gpSend(getCurObj(), "load '$file'") end - end #module diff --git a/test/runtests.jl b/test/runtests.jl index c83ad15..21909da 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,123 +1,59 @@ using Base.Test using Gnuplot -function pressEnter() - println("Press enter...") - readline(STDIN) -end - -function gp_test(terminal="unknown") - gpOptions.startup = "set term $terminal" - - gpReset() +function gp_test() x = collect(1.:100) #----------------------------------------------------------------- - gpSend("plot sin(x)") - terminal == "unknown" || pressEnter() - - #----------------------------------------------------------------- - id1 = gpCurrentID() - id2 = gpNewSession() - id3 = gpNewSession() + gp1 = GnuplotProc() + gp2 = GnuplotProc() + gp3 = GnuplotProc() for i in 1:10 - gpSetCurrentID(id1) - gpSend("plot sin($i*x)") - - gpSetCurrentID(id2) - gpSend("plot sin($i*x)") - - gpSetCurrentID(id3) - gpSend("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 - terminal == "unknown" || pressEnter() - gpExitAll() + GnuplotQuitAll() #----------------------------------------------------------------- - gpReset() - name = gpData([1,2,3,5,8,13]) - gpPlot("$name w points ps 3") - gpDump() - terminal == "unknown" || pressEnter() + @gp "plot sin(x)" + @gp "plot sin(x)" "pl cos(x)" + @gp "plo sin(x)" "s cos(x)" - gpPlot(last=true, "w l lw 3") - gpDump() - terminal == "unknown" || pressEnter() + @gpi 0 "plot sin(x)" + @gpi "plot cos(x)" :> - #----------------------------------------------------------------- - gpReset() - gpCmd("set format y \"%.1f\"") - gpCmd("set key box opaque") - gpCmd("set xrange [-2*pi:2*pi]") - gpMulti("layout 2,2 columnsfirst title \"Multiplot title\"") - - gpCmd(ylab="Y label") - gpPlot("sin(x) lt 1") - - gpNext() - gpCmd(xlab="X label") - gpPlot("cos(x) lt 2") - - gpNext() - gpCmd("unset ylabel") - gpCmd("unset ytics") - gpCmd("unset xlabel") - gpPlot("sin(2*x) lt 3") - - gpNext() - gpCmd(xlab="X label") - gpPlot("cos(2*x) lt 4") - - gpDump() - terminal == "unknown" || pressEnter() - - #----------------------------------------------------------------- - @gp("set format y \"%.1f\"", - "set key box opaque", - xr=(-2pi,2pi), - :multi, "layout 2,2 columnsfirst title \"Multiplot title\"", - ylab="Y label", - :plot, "sin(x) lt 1", - :next, - xlab="X label", - :plot, "cos(x) lt 2", - :next, - "unset ylabel", - "unset ytics", - "unset xlabel", - :plot, "sin(2*x) lt 3", - :next, - xlab="X label", - :plot, "cos(2*x) lt 4" - ) - terminal == "unknown" || pressEnter() - - #----------------------------------------------------------------- - @gpi(:reset, "set key off", - xr=(1,10), yr=(1,100), xlog=true, ylog=true, - :multi, "layout 2,2 columnsfirst title \"Multiplot title\"") + @gp "plot sin(x)" 2 xr=(-2pi,2pi) "pause 3" "plot cos(4*x)" - for i in 1:4 - @gpi(x, x.^i, "w l lw 3 lt $i", :next) - end - @gpi() - terminal == "unknown" || pressEnter() + x = linspace(-2pi, 2pi, 100); + y = 1.5 * sin.(0.3 + 0.7x) ; + noise = randn(length(x))./2; + e = 0.5 * ones(x); - #----------------------------------------------------------------- - lw = 5 - @gp "set title 'My title'" x x.^2. "w l tit '{/Symbol L}_{/Symbol a}' lw $lw dt 2 lc rgb 'red'" - terminal == "unknown" || pressEnter() + @gp x y + @gp x y "w l" + @gp x y :aa "plot \$aa w l" "pl \$aa u 1:(2*\$2) w l" + + @gp randn(Float64, 30, 50) + + @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'"); + + + @gpi 0 "f(x) = a * sin(b + c*x); a = 1; b = 1; c = 1;" + @gpi x y+noise e :aa + @gpi "fit f(x) \$aa u 1:2:3 via a, b, c;" + @gpi "set multiplot layout 2,1" + @gpi "plot \$aa w points" ylab="Data and model" + @gpi "plot \$aa u 1:(f(\$1)) w lines" + @gpi 2 xlab="X label" ylab="Residuals" + @gpi "plot \$aa u 1:((f(\$1)-\$2) / \$3):(1) w errorbars notit" :> - #----------------------------------------------------------------- - @gp("set title 'My title'", - x, x.^2 , "w l tit '{/Symbol L}_{/Symbol a}' lw $lw dt 2 lc rgb 'red'", - x, x.^2.2, "w l tit 'bbb'" - ) - terminal == "unknown" || pressEnter() #----------------------------------------------------------------- @gp(""" @@ -139,12 +75,12 @@ function gp_test(terminal="unknown") set format y "%.1f" set samples 500 set style fill solid 0.4 noborder""", - :plot, "'+' using 1:(sin(\$1)):(approx_1(\$1)) with filledcurve title label1 lt 3", - :plot, "'+' using 1:(sin(\$1)):(approx_2(\$1)) with filledcurve title label2 lt 2", - :plot, "'+' using 1:(sin(\$1)):(approx_3(\$1)) with filledcurve title label3 lt 1", - :plot, "sin(x) with lines lw 1 lc rgb 'black'") + "plot '+' using 1:(sin(\$1)):(approx_1(\$1)) with filledcurve title label1 lt 3", + "plot '+' using 1:(sin(\$1)):(approx_2(\$1)) with filledcurve title label2 lt 2", + "plot '+' using 1:(sin(\$1)):(approx_3(\$1)) with filledcurve title label3 lt 1", + "plot sin(x) with lines lw 1 lc rgb 'black'") - #----------------------------------------------------------------- + #----------------------------------------------------------------- @gp(""" set zrange [-1:1] unset label @@ -171,19 +107,18 @@ function gp_test(terminal="unknown") x7=xx; xx=xx+dx x8=xx; xx=xx+dx x9=xx; xx=xx+dx""", - splot=true, - :plot, "[u=0:1][v=-4.99:4.99]x0, v, (u<0.5) ? -1 : sinc(x0,v) notitle", - :plot, "x1, v, (u<0.5) ? -1 : sinc(x1,v) notitle", - :plot, "x2, v, (u<0.5) ? -1 : sinc(x2,v) notitle", - :plot, "x3, v, (u<0.5) ? -1 : sinc(x3,v) notitle", - :plot, "x4, v, (u<0.5) ? -1 : sinc(x4,v) notitle", - :plot, "x5, v, (u<0.5) ? -1 : sinc(x5,v) notitle", - :plot, "x6, v, (u<0.5) ? -1 : sinc(x6,v) notitle", - :plot, "x7, v, (u<0.5) ? -1 : sinc(x7,v) notitle", - :plot, "x8, v, (u<0.5) ? -1 : sinc(x8,v) notitle", - :plot, "x9, v, (u<0.5) ? -1 : sinc(x9,v) notitle") + "splot [u=0:1][v=-4.99:4.99]x0, v, (u<0.5) ? -1 : sinc(x0,v) notitle", + "splot x1, v, (u<0.5) ? -1 : sinc(x1,v) notitle", + "splot x2, v, (u<0.5) ? -1 : sinc(x2,v) notitle", + "splot x3, v, (u<0.5) ? -1 : sinc(x3,v) notitle", + "splot x4, v, (u<0.5) ? -1 : sinc(x4,v) notitle", + "splot x5, v, (u<0.5) ? -1 : sinc(x5,v) notitle", + "splot x6, v, (u<0.5) ? -1 : sinc(x6,v) notitle", + "splot x7, v, (u<0.5) ? -1 : sinc(x7,v) notitle", + "splot x8, v, (u<0.5) ? -1 : sinc(x8,v) notitle", + "splot x9, v, (u<0.5) ? -1 : sinc(x9,v) notitle") - gpExitAll() + GnuplotQuitAll() return true end