[ANN]: Gnuplot v1.1.0 - A Julia interface to gnuplot

I’d like to mention also the not so Frequently Asked Questions, it’s a great resource about gnuplot.

4 Likes

No, it can’t. And the reason is that there would be many details in the plot (such as the individual color lines, point symbols, etc… ) which would hardly be customizable, while Gnuplot.jl aims to provide fine control over all the details of a plot.

Still, you may implement a simple quicklook function, in the form of a plot recipe.
NOTE: these are not yet available in the released version!
But if you download the master branch (]dev Gnuplot) you may try the following code:

using RDatasets, DataFrames, Gnuplot
import Gnuplot: PlotRecipe, DataSetText

function plotdf(df::DataFrame, colx::Symbol, coly::Symbol; group=nothing)
    if isnothing(group)
        return PlotRecipe(data=DataSetText(df[:, colx], df[:, coly]),
                          plot="w p notit",
                          xlab=string(colx), ylab=string(coly))
    end

    data = Vector{Gnuplot.DataSet}()
    plot = Vector{String}()
    for g in sort(unique(df[:, group]))
        i = findall(df[:, group] .== g)
        if length(i) > 0
            push!(data, DataSetText(df[i, colx], df[i, coly]))
            push!(plot, "w p t '$g'")
        end
    end
    return PlotRecipe(data=data, plot=plot,
                      xlab=string(colx), ylab=string(coly))
end

The recipe can be used as follows:

iris = dataset("datasets", "iris")
@gp plotdf(iris, :SepalLength, :SepalWidth)
@gp plotdf(iris, :SepalLength, :SepalWidth, group=:Species)

And the output is:
plotdf

4 Likes

Wow - That’s very cool! Thanks - will try it!

Although a solution with a recipe is elegant and clean, it’s not hard to do it without one. I added 4 examples in the gallery page.

using HTTP, CSV, DataFrames, Gnuplot, Random
using ColorSchemes, Colors, DataFramesMeta
url = "https://raw.githubusercontent.com/alstat/Julia-Data-Query/master/data-raw/flights.csv"
data = HTTP.download(url)
flights = CSV.read(data)
# @df flights plot(:Day, :NumFlights, group=:City)
#this is all the help you need
daily = groupby(flights, [:day, :dest]) # :origin, only 3 (boring)
per_day = @based_on(daily, flights = length(:day))
byCat = per_day.dest
categ = unique(byCat)
# aesthetics
@gp xlab ="day" ylab = "number of flights" "set auto fix"
@gp :- title = "Flights" :-
@gp :- "set offsets graph .05, graph .05, graph .05, graph .05" :-
@gp :- "set key outside font ',8' title 'destination'" :-
@gp :- "set logscale y 2" yrange = (0.8,700)
cmap = get(colorschemes[:rainbow1], LinRange(0,1,length(categ)))
# the actual plot
Random.seed!(213)
for (i,c) in enumerate(categ)
    indc = findall(x->x == c, byCat)
    x, y = per_day.day[indc], per_day.flights[indc]
    @gp :- x y "w linespoints tit '$(c)' lc '#B3$(hex(cmap[i]))' pt $(rand(5:2:13)) ps 0.5"
    lx = length(x)
    rlx = rand(1:lx)
    @gp :- "set label '$(c)' at $(x[rlx]-0.4),$(y[rlx]) font ',5' front"
end
save(term ="pdf size 4,5", output ="flights_ptslines.pdf")

All the best, and looking forward for the recipe proposal from @gcalderone.

4 Likes

Thanks! Those are fantastic examples! – Adam

Thanks again for the all the examples! I was trying to get plots to appear in a Jupyter Notebook. I wrote a very yucky way to do it. The issue is that @gp, @gps don’t return anything. An object needs to be returned and Base.show overloaded to display it. Here’s what I did,

# We don't want the qt terminal, as it steals the window focus after displaying a plot
@gp "set term unknown"

# We need an object that will represent a plot
struct AGnuPlot
    fileName::String
end

# IJulia will call this function to display AGnuPlot
import Base.show
function Base.show(io::IO, ::MIME"image/svg+xml", x::AGnuPlot)
    # Read in the file
    write(io, read(x.fileName, String))
end

# Function to trigger IJulia into showing the plot
#   We'll write the current plot to a svg file. Once AGnuPlot is returned, IJulia will run Base.show on it
#       and the plot should appear
const gnuplotout_file = "/tmp/gnuplotout"
function p()
    save(term="svg", output=gnuplotout_file) # Writing to a file is yucky, but it works
    return AGnuPlot(gnuplotout_file)
end

@gp "p sin(x)" ; p()

Maybe that’s helpful. Perhaps integration with IJulia, Juno, VSCode is on your roadmap.

Thanks again for this great package!

1 Like

Thanks for working this out!
I’ll think about it and try to found a nice way to include it in the next version.

I made a PR for it: https://github.com/gcalderone/Gnuplot.jl/pull/17

2 Likes

Thanks!!! This allows me to send plot with data to my colleague, who does not need Julia to view and/or post-process the plot, which is a nice feature.

One minor question: is there a way to easily suppress the printout of messages from Gnuplot process unless there is a critical error, while not affecting other printing (i.e. not a workaround of redirecting the stdout)?

If I make a package that uses Gnuplot for plotting, I prefer not to show a lot of messages unless it is necessary (e.g. when something did go wrong and the plot wouldn’t work). Is this achievable by changing a setting somewhere, either in Gnuplot.jl or Gnuplot software itself?

Thanks!

No message should be displayed on stdout unless:

  • gnuplot raised an error;
  • you set Gnuplot.options.verbose = true.

If you still see messages please open an issue on the repo describing how to replicate, and the messages you get.

1 Like

Thanks! It works.

Dear all,
this is a small update to announce that a new version of Gnuplot.jl (v1.2.0) is now available, introducing the following functionalities:

  • A gnuplot REPL is now available (#14);
  • Plots are now automatically displayed in Jupyter and Juno (#17);
  • The plot recipe mechanism has been implemented;

Full list of changes is available in the ChangeLog.

Enjoy!

9 Likes

Could someone post a simple example showing fundamental differences between packages Gnuplot and Plots ?

I’m afraid simple examples don’t tell much, i.e.:

using Gnuplot, Plots
x = 1:10; y = rand(10);
@gp x y
plot(x, y)

On the other hand, it is very difficult to envisage complex examples of sufficiently general interest, able to discriminate against one or the other.

IMHO, the best approach is to try to solve a specific problem (e.g. here).
Or have a look at the example galleries

Specific resources for Gnuplot.jl are here and here, but consider that any other gnuplot example (e.g. the official demos) can be reproduced from Julia.

1 Like

The most fundamental difference is: gnuplot is old, hence mature. The oldest reference I found with a quick search is from 1992 (gnuplot for Windows : Free Download, Borrow, and Streaming : Internet Archive)
:smiley:

Julia is young, Plots.jl is even younger and even most (or none? didn’t check.) of the backends of Plots.jl didn’t exist when gnuplot was already old :wink: And I remember using it as a student on a silicon graphics machine (hmmm, must find what it was later) in between some time not playing mtrek (online) by Chuck L. Peterson .

I know, that wasn’t the question, but I like to mention it anyways.

3 Likes

Correct, but is also actively maintained (current stable version released on Dec. 2019).

4 Likes

Right. This is an important note I forgot to mention!

1 Like

In case someone else runs into this annoying problem… When I save(term="pngcairo", output="out.png") or with term="pdfcairo", I get a mangled output with all text kinda printed on top of each other. Looks very ugly. This is apparently a “feature” in pango (the library that cairo uses for text). The latest version v1.44 no longer supports Freetype fonts (see Pango future directions | Goings on). You can repair this by downgrading your version of pango to v1.43. For the Mac, see macos - Gnuplot PDF Terminal Exhibits Font Issues on Mac - Stack Overflow for instructions.

Does anyone know a good way to deal with plotting things against dates? They only way I’ve figured out how to do it is with a named dataset. For example,

dates = [ Date(2020, 4, floor.(Int, x .* 30 .+ 1)) for x in rand(10) ]
df = DataFrame(Date=dates, val=rand(10))

name = "\$thedata"
@gp name => ( string.(df.Date), df.val)
@gp :- "set xdata time" :-
@gp :- "set format x '%Y-%m-%d'" "set xtics rotate by -45" :-
@gp :- "set rmargin 7" :-
@gp :- "p $name u (strptime('%Y-%m-%d', strcol(1))):2 w p notitl"

date

Is there a way to do this without having to make a named dataset? Thanks! – Adam

Yes, simply use the data in the @gp call, e.g.:

using Gnuplot, Dates

dates = [ Date(2020, 4, floor.(Int, x .* 30 .+ 1)) for x in rand(10) ]
val=rand(10)

@gp "set xdata time" "set timefmt '\"%Y-%m-%d\"'" :-
@gp :- "set format x '%Y-%m-%d'" "set xtics rotate by -45" :-
@gp :- "set rmargin 7" :-
@gp :- string.(dates) val "u 1:2 w p notitl"

Note that you can specify a global time format for reading with set timefmt. Also, Gnuplot.jl writes string within double quotes, hence you need to specify those as well in timefmt Finally, note that when plotting time/data the using clause is mandatory, although in this case it simply amounts to using 1:2.