Problems with titles, axes, grids and other plot properties using Makie

I’m currently converting some code from MATLAB to Julia, and have reached the stage where I have to do some graphics.

The picture shows the MATLAB plot on the right and my attempt in Julia using Makie.jl on the left. I’m almost there but I have some problems.

  1. I need to turn off the axes, grid and labels on the bottom two plots.
  2. The title refuses to appear on the top plot
  3. I need to add colorbars to the bottom two plots.
  4. Using ‘axis image’ in MATLAB the plot on the right has the coins that stay in proportion when the plot is resized. The Makie plot distorts the coins when I resize the window.

Any help gratefully received. My code is below. I’ve cut and pasted it as is, so there’s lots of crap in there, but I hope my feeble attempts at using Makie are clear. Be kind. This is my first attempt.


function solplot(sol::Solution)
    x = vec(collect(range(0.0, sol.target.LX, length = sol.target.NX)))
    y = vec(collect(range(0.0, sol.target.LY, length = sol.target.NY)))
    Zrange = sol.Zmax - sol.Zmin
    if Zrange == 0.0
        title1 = @sprintf(
            "RMS error = %2.2f%s.",
            1000 * sol.err * sol.beamparams.beamdiameter,
            "μ"
        )
    else
        title1 = @sprintf(
            "RMS error = %2.2f%sm (%2.1f%s).",
            1000 * sol.err * sol.beamparams.beamdiameter,
            "μ",
            100 * sol.err * sol.beamparams.beamdiameter / Zrange,
            "%"
        )
    end
    scene1 = Makie.heatmap(
        x,
        y,
        -1000.0 * sol.beamparams.beamdiameter .* sol.Z,
        colormap = :bone,
        axis = (names = (title = title1,),),
    )

    t = vec(collect(range(0.0, 2π, length = 30)))
    Makie.lines!(
        scene1,
        (sol.target.LX .- 0.5 .* (1.0 .+ cos.(t))),
        sol.target.LY .- 0.5 .* (1.0 .+ sin.(t)),
        color = :red,
    )

    if Zrange == 0.0
        title2 = @sprintf(
            "Target mean depth = %2.1f%sm",
            -1000 * sol.beamparams.beamdiameter * minimum(sol.Ztarget[:]),
            "μ"
        )
    elseif sol.Zmax != 0
        title2 = @sprintf(
            "Target depth range = %2.1f%sm",
            1000 * sol.beamparams.beamdiameter * Zrange,
            "μ"
        )
    else
        title2 = @sprintf(
            "Target depth = %2.1f%sm",
            1000 * sol.beamparams.beamdiameter * Zrange,
            "μ"
        )
    end
    scene2 = Makie.heatmap(
        x,
        y,
        -1000.0 * sol.beamparams.beamdiameter .* sol.Ztarget,
        colormap = :bone,
        axis = (names = (title = title2,),),
    )
    Makie.lines!(
        scene2,
        (sol.target.LX .- 0.5 .* (1.0 .+ cos.(t))),
        sol.target.LY .- 0.5 .* (1.0 .+ sin.(t)),
        color = :red,
    )

    v = sol.beamparams.v0 ./ sol.D
    s = sol.beamparams.beamdiameter .* sol.path.s


    title3 = @sprintf(
        "Target is %2.2f by %2.2f beam diameters (%2.2fmm by %2.2fmm)",
        sol.target.LX,
        sol.target.LY,
        sol.target.LX * sol.beamparams.beamdiameter,
        sol.target.LY * sol.beamparams.beamdiameter
    )
    scene3 = Makie.Scene()
    for i = 1:size(sol.path.tracks)[1]
        Makie.lines!(
            scene3,
            s[path.tracks[i, 1]:path.tracks[i, 2]+1],
            v[path.tracks[i, 1]:path.tracks[i, 2]+1],
            color = :black,
        )
    end
    Makie.lines!(
        scene3,
        [s[1], s[end]],
        [
            sol.beamparams.v0 / sol.params.Dmin,
            sol.beamparams.v0 / sol.params.Dmin,
        ],
        color = :black,
        linestyle = :dash,
    )
    Makie.lines!(
        scene3,
        [s[1], s[end]],
        [
            sol.beamparams.v0 / sol.params.Dmax,
            sol.beamparams.v0 / sol.params.Dmax,
        ],
        color = :black,
        linestyle = :dash,
    )
    Makie.xlims!(scene3, 0.0, s[end])
    Makie.ylims!(scene3, 0.0, 1.1 * sol.beamparams.v0 / sol.params.Dmin)
    Makie.xlabel!(scene3, "Path distance (mm)")
    Makie.ylabel!(scene3, "Feedrate (mm s⁻¹)")
    Makie.title(scene3,title3)
    scene = hbox(vbox(scene1, scene2), scene3)
    display(scene)
end

I think MakieLayout.jl might be just the thing you are looking for.

Using it, you have many options to create beautiful layouts.
You can:

  • Hide axes (and also link axes)
  • Set the aspect ratio
  • Title your sub plots
  • Create nice color bars

I have never used it, but I guess this would be your solution :slight_smile:

Added it and tried the example at the start of the package description and it crashed immediately. Not much use really. :frowning:

using CairoMakie; CairoMakie.activate!()
using AbstractPlotting

outer_padding = 30
scene, layout = layoutscene(outer_padding, resolution = (1200, 700),
    backgroundcolor = RGBf0(0.98, 0.98, 0.98))


ERROR: MethodError: getproperty(::GridLayout, ::Symbol) is ambiguous. Candidates:
  getproperty(layoutable::T, s::Symbol) where T<:Union{GridLayout, AbstractPlotting.MakieLayout.LAxis, AbstractPlotting.MakieLayout.LObject} in AbstractPlotting.MakieLayout at C:\Users\pmzjb1\.juliapro\JuliaPro_v1.4.2-1\packages\AbstractPlotting\FxIYW\src\makielayout\lobjects\lobject.jl:14
  getproperty(layoutable::T, s::Symbol) where T<:Union{GridLayout, LAxis, MakieLayout.LObject} in MakieLayout at C:\Users\pmzjb1\.juliapro\JuliaPro_v1.4.2-1\packages\MakieLayout\3a66O\src\lobjects\lobject.jl:14
Possible fix, define
  getproperty(::GridLayout, ::Symbol)
Stacktrace:
 [1] validategridlayout(::GridLayout) at C:\Users\pmzjb1\.juliapro\JuliaPro_v1.4.2-1\packages\GridLayoutBase\Z0vpC\src\gridlayout.jl:94
 [2] GridLayout(::Array{Any,1}, ::Int64, ::Int64, ::Array{GridLayoutBase.ContentSize,1}, ::Array{GridLayoutBase.ContentSize,1}, ::Array{GridLayoutBase.GapSize,1}, ::Array{GridLayoutBase.GapSize,1}, ::Outside, ::Tuple{Bool,Bool}, ::Observable{Bool}, ::LayoutObservables{GridLayout,GridLayout}, ::Observable{Any}, ::Observable{Any}, ::Observable{Any}, ::Observable{Any}, ::Observable{Any}, ::Observable{Any}, ::Fixed, ::Fixed) at C:\Users\pmzjb1\.juliapro\JuliaPro_v1.4.2-1\packages\GridLayoutBase\Z0vpC\src\types.jl:179
 [3] GridLayout(::Int64, ::Int64; rowsizes::Nothing, colsizes::Nothing, addedrowgaps::Nothing, addedcolgaps::Nothing, alignmode::Outside, equalprotrusiongaps::Tuple{Bool,Bool}, bbox::Observable{GeometryBasics.HyperRectangle{2,Float32}}, width::Auto, height::Auto, tellwidth::Bool, tellheight::Bool, halign::Symbol, valign::Symbol, default_rowgap::Float64, default_colgap::Float64, kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at C:\Users\pmzjb1\.juliapro\JuliaPro_v1.4.2-1\packages\GridLayoutBase\Z0vpC\src\gridlayout.jl:48
 [4] GridLayout(; kwargs::Base.Iterators.Pairs{Symbol,Any,Tuple{Symbol,Symbol},NamedTuple{(:bbox, :alignmode),Tuple{Observable{GeometryBasics.HyperRectangle{2,Float32}},Outside}}}) at C:\Users\pmzjb1\.juliapro\JuliaPro_v1.4.2-1\packages\GridLayoutBase\Z0vpC\src\gridlayout.jl:1
 [5] GridLayout(::Scene; kwargs::Base.Iterators.Pairs{Symbol,Outside,Tuple{Symbol},NamedTuple{(:alignmode,),Tuple{Outside}}}) at C:\Users\pmzjb1\.juliapro\JuliaPro_v1.4.2-1\packages\MakieLayout\3a66O\src\helpers.jl:190
 [6] layoutscene(::Int64; kwargs::Base.Iterators.Pairs{Symbol,Any,Tuple{Symbol,Symbol},NamedTuple{(:resolution, :backgroundcolor),Tuple{Tuple{Int64,Int64},ColorTypes.RGB{Float32}}}}) at C:\Users\pmzjb1\.juliapro\JuliaPro_v1.4.2-1\packages\MakieLayout\3a66O\src\helpers.jl:174
 [7] top-level scope at none:0

Could it be that you’re on #master of AbstractPlotting? We are currently in the process of merging MakieLayout with AbstractPlotting, and the error you see comes from having two MakieLayouts… Can you try it again and instead of doing using MakieLayout do using AbstractPlotting.MakieLayout? Or revert to the last tagged version, that shouldn’t have MakieLayout inside if I’m not mistaken.

Here’s one way you could do it (I’ve used AbstractPlotting#master as well this time):

using AbstractPlotting.MakieLayout
using AbstractPlotting
using CairoMakie; CairoMakie.activate!()
using FileIO
using Colors

img = rotr90(Float64.(Gray.(load("coin.jpg")))) # for some reason makie needs rotated images (column / row-major storage etc.)


scene, layout = layoutscene(resolution = (800, 800))

topax = layout[1, 1:2] = LAxis(scene,
    title = "Target is 25.00 by 25.00 beam diameters (2.98mm by 2.98mm)",
    ylabel = "Feedrate (mm s-1)",
    xlabel = "Path distance (mm)")
lines!(topax, randn(250))

leftax = LAxis(scene,
    title = "RMS error = 1234",
    autolimitaspect = 1)
hm1 = heatmap!(leftax, img)
leftcb = LColorbar(scene, hm1, width = 20)

rightax = LAxis(scene,
    title = "Target depth range = 1234",
    autolimitaspect = 1)
hm2 = heatmap!(rightax, img)
rightcb = LColorbar(scene, hm2, width = 20)

hidedecorations!.([leftax, rightax])
hidespines!.([leftax, rightax])
tightlimits!.([leftax, rightax])

layout[2, 1] = hbox!(leftax, leftcb)
layout[2, 2] = hbox!(rightax, rightcb)

save("test.png", scene, px_per_unit = 2) # twice the resolution for exporting

3 Likes

I hacked something together which worked for me. Note, that I use GLMakie instead of Cairo

using MakieLayout, AbstractPlotting, GLMakie
outer_padding = 30
scene, layout = layoutscene(outer_padding, 
                        resolution=(1200, 700), 
                        backgroundcolor=RGBf0(0.98,0.98,0.98))
ax1 = layout[1, 1] = LAxis(scene, title="hello1")
ax2 = layout[1, 2] = LAxis(scene, title="hello2")

linkaxes!(ax1, ax2)
hideydecorations!(ax2, grid=false)
display(scene)

Hope this helps

Dang, this looks great :+1: :+1:

Oooooooo! That looks nice! :heart_eyes:

I’ll get everything installed properly one way or another and have mine looking the same. Thanks v much for your help. Once I’ve got it working, I’ll come back and mark that as the solution. :slight_smile:

When I save the plot I get


which is pretty much what I wanted.

However, I really want to display it and save it later if required. When I do that, it pops up inside the Plots window of Atom, and the scaling depends on the size of that window, for example,

which is not so good. Clearly some issue with font sizings. Also, I liked the way that it popped up outside Atom and could be resized when I did it before. Is there any way of doing that? Help much appreciated.

JB

The separate window happens when you use GLMakie, that’s also what you need for interactivity and 3D plots. You can theme the font size if it’s too big but that’s something that has to be adjusted per backend. In GLMakie a unit is a pixel of your window, in CairoMakie it can either be a pt in vector graphics or a px in bitmaps. The px_per_unit and pt_per_unit keywords when saving in cairomakie allow you to scale your plot’s size up and down while keeping the relative size of all elements intact.

So you can use GLMakie, then GLMakie.activate!() and scenes should open in a window. Then when you like the plot you can CairoMakie.activate!() and save to a vector file. If the size is too big or small with the settings that looked good in GLMakie, use pt_per_unit or px_per_unit to adjust. Or maybe CairoMakie.save could work directly without activate!()

Fantastic. Just changing all the Makies to GLMakies did the trick. It pops out, it’s resizable, it saves nicely. Not sure I have much idea what I’m doing, but I have something to work from now. Thanks v much.