Makie: Multiline plots, subplots and attributes examples

I wanted some convenience in plotting several lines into one plot or several subplots.
Since Plots.jl does not yet serve the Makie backend, I wrote the functions below.

My first experiments with Makie, did I waste a lot of time for no reason, or can you recommend changes?

makie_plots: multi-line plot
using GLMakie
GLMakie.activate!()

function makie_plots(x, y; legends = [], title = [], xlabel = [], ylabel = [])
    m = size(y, 2)
    if isempty(legends)
        legends = ["y$i" for i=1:m]
    end
    # standard colors from matplotlib
    colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"];
    fig = Figure()
    ax = Axis(fig[1, 1])
    for i = 1:m
        lines!(ax, x, y[:,i], label = legends[i], color = colors[(i-1)%10+1])
    end
    axislegend(ax)
    if !isempty(title)
        ax.title = title
        # Bug? squeezes plots to width of title
        #Label(fig[0, 1], title, textsize = 30) 
    end
    if !isempty(xlabel)
        ax.xlabel = xlabel
    end
    if !isempty(ylabel)
        ax.ylabel = ylabel
    end
    fig
end

n = 100; m = 3
x = 1:n
y = randn(n,m)
#makie_plots(x, y)
#makie_plots(x, y, legends = ["toto", "titi", "tata"])
#makie_plots(x, y2, legends = ["toto", "titi", "tata"], title = "A Title")
makie_plots(x, y, legends = ["toto", "titi", "tata"], title = "A Title", xlabel = "Time [s]", ylabel = "Volts [V]")

makie_subplots: tightly aligned subplots with linked x-axis
using GLMakie
GLMakie.activate!()

function makie_subplots(x, y; legends = [], title = [], xlabel = [], ylabels = [])
    m = size(y, 2)
    if isempty(legends)
        legends = ["y$i" for i=1:m]
    end
    # standard colors from matplotlib
    colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"];
    fig = Figure()
    ax = []
    for i = 1:m
        push!(ax, Axis(fig[i, 1]))
        linkxaxes!(ax[1], ax[i])
        lines!(ax[i], x, y[:,i], label = legends[i], color = colors[(i-1)%10+1])
        axislegend(ax[i])
    end
    if !isempty(title)
        ax[1].title = title
        # Bug? squeezes plots to width of title
        #Label(fig[0, 1], title, textsize = 30)
    end
    if !isempty(xlabel)
        ax[end].xlabel = xlabel
    end
    hidexdecorations!.(ax[1:m-1], grid=false)
    rowgap!(fig.layout,0)
    fig
end

n = 100; m = 3
x = 1:n
y = randn(n,m)
#makie_subplots(x, y)
#makie_subplots(x, y, legends = ["toto", "titi", "tata"])
#makie_subplots(x, y, legends = ["toto", "titi", "tata"], title = "A Title")
makie_subplots(x, y, legends = ["toto", "titi", "tata"], title = "A Title", xlabel = "Time [s]")
makie_addsubplot: incrementally build tightly aligned subplots with linked x-axis
using GLMakie
GLMakie.activate!()

function makie_addsubplot(x, y; varargs...)  
    makie_addsubplot([], x, y; varargs...)
end

function makie_addsubplot(fap, x, y; legends = [], title = [], xlabel = [], ylabels = [])
    if isempty(fap)
        fig = Figure()
        ax = []
        pl = []
    else
        fig, ax, pl = fap
    end
    m = size(y, 2)
    if isempty(legends)
        legends = ["y$i" for i=1:m]
    end
    n = length(ax) + 1
    push!(ax, Axis(fig[n, 1]))
    linkxaxes!(ax...)
    # standard colors from matplotlib
    colors = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"];
    for i = 1:m
        lines!(ax[n], x, y[:,i], label = legends[i], color = colors[(i-1)%10+1])
    end
    axislegend(ax[n])
    if !isempty(title)
        ax[1].title = title
    end
    if !isempty(xlabel)
        ax[end].xlabel = xlabel
    end
    hidexdecorations!.(ax[1:n-1], grid=false)
    rowgap!(fig.layout,0)
    display(fig)
    fap = (fig, ax, pl)
end

n = 100; m = 3
x = 1:n
y = randn(n,m)
fap = makie_addsubplot(x, cumsum(y[:,1]), legends = ["cumsum(toto)"])
makie_addsubplot(fap, x, y, legends = ["toto", "titi", "tata"])
makie_addsubplot(fap, x, y.^2, legends = ["toto2", "titi2", "tata2"], title = "A Title", xlabel = "Time [s]")

2 Likes

The plot method return types are explained here Plot Method Signatures

You can set attributes on the return object of lines!

1 Like

Thx!

@jules Do you mind adding an example (also in the doc)?

scatter(1.0…10, sin)

How to change now the line color to red?

scatter!(args…; kwargs…) → ::Scatter

Not enough space here to show the permutations using color = :red I tried.
No experience with kwargs so far.

This worked, but completely differently:

a,b,c = lines(1.0…10, sin, color=:red)
c[:color] = :blue

Check out this makie-appriciation post I made in zulip:
https://julialang.zulipchat.com/#narrow/stream/225583-appreciation/topic/Makie.20custumizability/near/238608914

Screenshot:

I have been thinking of putting this way of custumizing stuff into a page in the makie docs, but I have not found the time yet.

4 Likes

Thx, we definitively need more short examples.

My quick cheat sheet:

using GLMakie
GLMakie.activate!()

# Makie hierarchy is figure, axis, plot
# 1) direct plot call via lines() or scatter() 
lines(0:0.1:10, sin)               # plot lines figure
fap = lines(0:0.1:10, sin)         # return Makie.FigureAxisPlot object
# ?fap                             # show help for Makie.FigureAxisPlot
fieldnames(typeof(fap))            # (:figure, :axis, :plot)
fap.plot.attributes                # show all properties of plot
fap.plot[:color] = :red            # change the color property of plot
f,a,p = fap                        # destruction in Julia
# 2) direct plot, return components
f,a,p = lines(0:0.1:10, sin)       # figure, axis, plots
# ?f, ?a, ?p                       # show help for figure, axis, plots objects
p[:color] = :red                   # change the color property of plot
p2 = lines!(0:0.1:10, cos)         # add plot, returns plot object
p2.color = :red                    # change the color property of plot
# 3) Hierarchical composition
f = Figure()                       # create figure pane
a = Axis(f[1, 1])                  # create axis at position [1,1] 
p = []                             # if you want to index plots or axes, start empty
push!(p, lines!(a, 0:0.1:10, sin)) # plot lines figure in current axis, mind the !
push!(p, lines!(a, 0:0.1:10, cos)) # add another plot
p[2][:color] = :red                # change the color property of second plot
p[2].color = :red                  # change the color property of second plot
6 Likes

The system is a bit different from Plots where a plot call gives a Plot object, and the plot! call returns the mutated Plot object. That’s because this object is finally translated into a real plot with all elements, while in Makie the real plot is assembled immediately. To me, it was important that if a plotting call creates a figure, an axis in that figure, and a plot in that axis, that neither returning figure or axis or plot is enough or correct. So that’s why you have FigureAxisPlot which you can destructure. For the mutating plot! calls which only make a plot object, you also only get that back as a return value.

1 Like

Hi Julius,
yes, this is clear. And really a big Thank You for your work!

Would you mind adding a cheat sheet (like above, one-liners) to the doc and put it in front?
So, it can serve as an appetizer and quick reference.

You could give a concise mental map of Makie, like: (just my actual understanding)
Makie is hierarchically organized as figure, axes, plots. Axes are mxn matrices, plots can be vectors. Each have attributes which can be accessed by… and changed by …
That’s it.

If Julia and every package would have such helpful snippets, the gap for beginners could be narrowed considerably. The frequent API style is (IMHO, for beginners) rarely helpful without an example.

1 Like

Maybe it will be worth to take a look at Attributes - Julia Data Science

And to(IMO, most things you will need to do layout) Layouts - Julia Data Science

2 Likes

Thanks @lazarusA, the juliadatascience.io site makes a nice, applied Julia manual!

How to call this - “cross-spoiling”? :wink:

Yes, Python does this like Matlabs subplot, you must specify the number of subplots beforehand, right?
The code here can add subplots ad hoc. Handy at times.

This is the latest spam bot - posting tangentially related stuff which includes a spam link at the end. Best to flag and move on.