diff --git a/README.md b/README.md index c05db24..2f3b726 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,7 @@ **Gnuplot.jl** allows easy and fast use of [Gnuplot](http://gnuplot.info/) as data visualization tool in -Julia. It is mainly focused on - -GnuplotFeatures: +Julia. Its main features are: - transparent interface between Julia and gnuplot to exploit all functionalities of the latter, both present and future ones; @@ -15,15 +13,22 @@ GnuplotFeatures: - fast data transmission to gnuplot through system pipes (no temporary files involved); -- support for running multiple gnuplot istances simulatneously; +- support for running multiple gnuplot process simulatneously; - support for multiplots; -- extremely concise syntax (see Examples below) makes it ideal for - interactive use and data exploration; +- extremely concise syntax (see examples below), makes it ideal for + interactive data exploration; - very easy to use: if you know gnuplot you're ready to go. + +The purpose is similar to +the [Gaston](https://github.com/mbaz/Gaston.jl) package, but +**Gnuplot.jl** main focus is on on the syntax conciseness and ease of +use. + + ## Installation In the Julia REPL type: @@ -35,26 +40,68 @@ You'll also need gnuplot (ver. >= 4.7) installed on your system. ## Quick start: -Here we will show basic usage: +Here we will show a very basic usage: ``` Julia using Gnuplot -# Create some noisy data +# Create some noisy data... x = collect(linspace(-2pi, 2pi, 100)) y = 1.5 * sin.(0.3 + 0.7x) noise = randn(length(x))./2 e = 0.5 * ones(x) -@gp("set key horizontal", - x, y, "w l dt 1 lw 2 t 'Real model'", + +# ...and show them using gnuplot. +@gp("set key horizontal", "set grid", + xrange=(-7,7), ylab="Y label", + x, y, "w l t 'Real model' dt 2 lw 2 lc rgb 'red'", x, y+noise, e, "w errorbars t 'Data'") ``` -That's it for the first plot. The syntax should be familitar to most -gnuplot users. +That's it for the first plot, the syntax should be familiar to most +gnuplot users. With this code we: +- set a few gnuplot properties (`key` and `grid`); +- set the X axis range and Y axis label; +- passed the data to gnuplot; +- plot two data sets specifying a few details (style, line + width, color, legend, etc...). +Note that this simple example already covers the vast majority of use +cases, since the remaining details of the plot can be easily tweaked +by adding the appropriate gnuplot command. Also note that you would +barely recognize the Julia language by just looking at the `@gp` macro +since **Gnuplot.jl** aims to be the mostly transparent: the user is +supposed to focus only on the data and on the gnuplot commands, rather +than the **Gnuplot.jl** package details. -Now some more advanced usage (fit the data and overplot the results): +Before proceeding we will brief discuss the four symbols exported +by the package: +- `@gp`: the *swiss army knife* of the package, it allows to send + command and data to gnuplot, and produce very complex plots; +- `@gpi`: very similar to `@gp`, but it allows to build a plot in + several calls, rather than a single `@gp` call; +- `@gp_str`: run simple gnuplot commands + using + [non-standard string literal](https://docs.julialang.org/en/stable/manual/strings/#non-standard-string-literals-1), + e.g. +``` Julia +gp"print GPVAL_TERM" +``` +- `@gp_cmd`: load a gnuplot script file using a non-standard string literal, e.g. +``` Julia +gp`test.gp` +``` +The last two macros are supposed to be used only in the REPL, not in +Julia function. As you can see there is not much more to know before +starting *gnuplotting*! + +Clearly, the **Gnuplot.jl** hides much more under the hood. The +documentation for each of this function can be retrieved with the +`@doc` macro or by typing `?` in the REPL followed by the function +name. + +Now let's discuss some more advanced usage: fit the data (with +gnuplot) and overplot the results. ``` Julia const gp = Gnuplot # use an alias for the package name to quickly # access non exported symbols. @@ -72,8 +119,7 @@ gp.plot("f(x) w l lw 2 t 'Fit'") (a, b, c) = parse.(Float64, gp.getVal("a", "b", "c")) # Add param. values in the title and the Y label -gp.cmd(title="Fit param: " * @sprintf("a=%5.2f, b=%5.2f, c=%5.2f", a, b ,c), - ylab="Y label") +gp.cmd(title="Fit param: " * @sprintf("a=%5.2f, b=%5.2f, c=%5.2f", a, b ,c)) # Refresh the plot gp.dump() @@ -92,7 +138,7 @@ gp.dump() m = a * sin.(b + c * x) # Start a new gnuplot process and plot again using the @gp macro. -@gp("set key horizontal", +@gp("set key horizontal", "set grid", :multi, "layout 2,1", title="Fit param: " * @sprintf("a=%5.2f, b=%5.2f, c=%5.2f", a, b ,c), ylab="Y label", @@ -114,22 +160,3 @@ any other program. - -similar to gaston -Work in progress... - - - -Examples: - -Multiplot: - -Multiple instaces: - -Documentation: -? at the repl or use the `@doc` macro - -AbbrvKW (si puo scaricare la versione master?) - - - diff --git a/REQUIRE b/REQUIRE index 37ae3ae..137767a 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,2 +1 @@ julia 0.6 -AbbrvKW diff --git a/src/Gnuplot.jl b/src/Gnuplot.jl index cac6abc..2d3c0f6 100644 --- a/src/Gnuplot.jl +++ b/src/Gnuplot.jl @@ -1,10 +1,8 @@ module Gnuplot -using AbbrvKW - include("GnuplotInternals.jl") importall .p_ - +import .p_.@AbbrvKW ###################################################################### # Get/set options diff --git a/src/GnuplotInternals.jl b/src/GnuplotInternals.jl index 32d6976..123760d 100644 --- a/src/GnuplotInternals.jl +++ b/src/GnuplotInternals.jl @@ -6,6 +6,156 @@ module p_ importall Gnuplot const P_ = Gnuplot + +import StatsBase.countmap + + +function findAbbrv(symLong::Vector{Symbol}) + if length(symLong) == 0 + return symLong + end + + out = Dict() + for sym in symLong + out[sym] = Vector{Symbol}() + end + + symAbbr = deepcopy(symLong) + symStr = convert.(String, symLong) + kwCount = length(symLong) + + # Max length of string representation of keywords + maxLen = maximum(length.(symStr)) + + # Identify all abbreviations + for len in 1:maxLen + for i in 1:kwCount + s = symStr[i] + if length(s) >= len + s = s[1:len] + push!(symLong, symLong[i]) + push!(symAbbr, convert(Symbol, s)) + push!(symStr , s) + end + end + end + symStr = nothing # no longer needed + + # Identify unique abbreviations + abbrCount = 0 + for (sym, count) in countmap(symAbbr) + if count == 1 + i = find(symAbbr .== sym) + @assert length(i) == 1 + i = i[1] + if symLong[i] != symAbbr[i] + push!(out[symLong[i]], symAbbr[i]) + abbrCount += 1 + end + end + end + + for (key, val) in out + sort!(out[key]) + end + + return (out, abbrCount) +end + + +macro AbbrvKW(func) + @assert func.head == :function "Not a function" + + if length(func.args[1].args) <= 1 + # Empty parameter list" + return esc(func) + end + + if (typeof(func.args[1].args[2]) != Expr) || + (func.args[1].args[2].head != :parameters) + # No keywords given + return esc(func) + end + + sym = Vector{Symbol}() # Symbol, long version + typ = Dict() # Data type + splat = Symbol() + splatFound = false + for k in func.args[1].args[2].args + @assert typeof(k) == Expr "Expr expected" + @assert k.head in (:kw, :(...)) "Expected :kw or :..., got $(k.head)" + + #dump(k) + if k.head == :kw + @assert typeof(k.args[1]) in (Expr, Symbol) "Expected Expr or Symbol" + + if typeof(k.args[1]) == Symbol + push!(sym, k.args[1]) + typ[sym[end]] = :Any + elseif typeof(k.args[1]) == Expr + @assert k.args[1].head == :(::) "Expected :(::), got $(k.args[1].head)" + push!(sym, k.args[1].args[1]) + typ[sym[end]] = k.args[1].args[2] + end + elseif k.head == :(...) + splat = k.args[1] + splatFound = true + end + end + + # Find abbreviations + (abbr, count) = findAbbrv(sym) + if count == 0 + # No abbreviations found + return esc(func) + end + + # Add a splat variable if not present + if !splatFound + splat = :_abbrvkw_ + a = :($splat...) + a = a.args[1] + push!(func.args[1].args[2].args, a) + a = nothing + end + + # Build output Expr + expr = Expr(:block) + push!(expr.args, :(_ii_ = 1)) + push!(expr.args, Expr(:while, :(_ii_ <= length($splat)), Expr(:block))) + + for (sym, tup) in abbr + length(tup) > 0 || continue + tup = tuple(tup...) + push!(expr.args[end].args[end].args, + :( + if $(splat)[_ii_][1] in $tup + typeassert($(splat)[_ii_][2], $(typ[sym])) + $(sym) = $(splat)[_ii_][2] + deleteat!($splat, _ii_) + continue + end + )) + end + push!(expr.args[end].args[end].args, :(_ii_ += 1)) + push!(expr.args, :(_ii_ = nothing)) + + if !splatFound + push!(expr.args, :(if length($splat) !=0 ; + error("Unrecognized keyword abbreviation(s): " * string($splat)) + end)) + push!(expr.args, :($splat = nothing)) + end + + @assert length(func.args) == 2 "Function Expr has " * string(length(func.args)) * " args" + @assert func.args[2].head == :block "Function block is not a block, but " * string(func.args[2].head) + + prepend!(func.args[2].args, [expr]) + #@show func + return esc(func) +end + + ###################################################################### # Structure definitions ######################################################################