How to tweak tick label position in Makie?

The new layout system is awesome,
and the basic and layout tutorials gave very good explanations.
After reading all the docs, in particular the Modifying ticks section,
I see how to set the ticks strings (otherwise the last one was missing).

But ticklabels of adjacent subplots overlap.
Here is a mwe adapted from the documentation:

using CairoMakie
fig = Figure(resolution = (800, 800))

for i in 1:2, j in 1:2
    Axis(fig[i, j],
        limits = (0.5, 0.7, -2, 2),
        # otherwise the 0.7 tick label is missing
        xticks = ([0.5, 0.55, 0.6, 0.65, 0.7], 
                  ["0.5", "0.55", "0.6", "0.65", "0.7"]),
        xaxisposition = (i == 1 ? :top : :bottom),
        yaxisposition = (j == 1 ? :left : :right)
    )
end
colgap!(fig.layout, 25)
rowgap!(fig.layout, 25)

Is it possible to tweak the first and last ticklabel a little bit inward ?

Yes you can, although that will only work well if you don’t change the ticks anymore after. You can set xticklabelalign for example to an array where the first tick is left aligned, the central ones center aligned, and the right tick right aligned. This just has to fit the number of ticks, which is why they shouldn’t change after.

2 Likes

Thanks for the answer, I was dreaming about such a fine control !

A little trap: with
xticklabelalign = [:left, :center, :center, :center, :right]
it failed:

Error message
MethodError: no method matching getindex(::Symbol, ::Int64)

    layout_text(::String, ::Float32, ::FreeTypeAbstraction.FTFont, ::Symbol, ::AbstractPlotting.Quaternionf0, ::Base.RefValue{StaticArrays.SMatrix{4, 4, Float32, 16}}, ::AbstractPlotting.Automatic, ::Float64)@layouting.jl:70
    (::AbstractPlotting.var"#164#166")(::String, ::Float32, ::FreeTypeAbstraction.FTFont, ::Symbol, ::AbstractPlotting.Quaternionf0, ::Base.RefValue{StaticArrays.SMatrix{4, 4, Float32, 16}}, ::AbstractPlotting.Automatic, ::Float64)@interfaces.jl:295
    broadcast_foreach(::Function, ::Vector{String}, ::Vararg{Any, N} where N)@utilities.jl:162
    (::AbstractPlotting.var"#163#165"{AbstractPlotting.Text{Tuple{Vector{String}}}})(::Vector{String}, ::Vector{Any}, ::Float32, ::String, ::Vector{Symbol}, ::Float32, ::StaticArrays.SMatrix{4, 4, Float32, 16}, ::AbstractPlotting.Automatic, ::Float64)@interfaces.jl:294
    (::Observables.OnUpdate{AbstractPlotting.var"#163#165"{AbstractPlotting.Text{Tuple{Vector{String}}}}, Tuple{Observables.Observable{Vector{String}}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}}})(::Vector{Any})@Observables.jl:334
    #invokelatest#2@essentials.jl:708[inlined]
    invokelatest@essentials.jl:706[inlined]
    notify@Observables.jl:88[inlined]
    setindex!@Observables.jl:248[inlined]
    (::Observables.var"#3#4"{Any, Observables.Observable{Any}})(::Vector{Any})@Observables.jl:58
    #invokelatest#2@essentials.jl:708[inlined]
    invokelatest@essentials.jl:706[inlined]
    notify@Observables.jl:88[inlined]
    setindex!(::Observables.Observable{Vector{Any}}, ::Vector{GeometryBasics.Point{3, Float32}})@Observables.jl:248
    (::AbstractPlotting.var"#167#168"{AbstractPlotting.Text{Tuple{Vector{String}}}, Observables.Observable{Vector{Any}}})(::Vector{Tuple{String, GeometryBasics.Point{2, Float32}}})@interfaces.jl:327
    #invokelatest#2@essentials.jl:708[inlined]
    invokelatest@essentials.jl:706[inlined]
    notify@Observables.jl:88[inlined]
    setindex!@Observables.jl:248[inlined]
    MapUpdater@Observables.jl:372[inlined]
    (::Observables.OnUpdate{Observables.MapUpdater{AbstractPlotting.var"#188#190"{Int64}, Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}, Tuple{Observables.Observable{Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}}}})(::Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}})@Observables.jl:334
    #invokelatest#2@essentials.jl:708[inlined]
    invokelatest@essentials.jl:706[inlined]
    notify@Observables.jl:88[inlined]
    setindex!(::Observables.Observable{Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}}, ::Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}})@Observables.jl:248
    (::Observables.MapUpdater{AbstractPlotting.var"#191#192"{UnionAll}, Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}})(::Vector{Tuple{String, GeometryBasics.Point{2, Float32}}})@Observables.jl:372
    (::Observables.OnUpdate{Observables.MapUpdater{AbstractPlotting.var"#191#192"{UnionAll}, Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}}, Tuple{Observables.Observable{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}}})(::Vector{Tuple{String, GeometryBasics.Point{2, Float32}}})@Observables.jl:334
    #invokelatest#2@essentials.jl:708[inlined]
    invokelatest@essentials.jl:706[inlined]
    notify@Observables.jl:88[inlined]
    setindex!@Observables.jl:248[inlined]
    MapUpdater@Observables.jl:372[inlined]
    (::Observables.OnUpdate{Observables.MapUpdater{AbstractPlotting.var"#188#190"{Int64}, Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}, Tuple{Observables.Observable{Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}}}})(::Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}})@Observables.jl:334
    #invokelatest#2@essentials.jl:708[inlined]
    invokelatest@essentials.jl:706[inlined]
    notify@Observables.jl:88[inlined]
    setindex!(::Observables.Observable{Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}}, ::Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}})@Observables.jl:248
    (::AbstractPlotting.var"#203#205"{AbstractPlotting.Attributes, Observables.Observable{Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}}, DataType})(::Tuple{}, ::Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}})@interfaces.jl:681
    (::Observables.OnUpdate{AbstractPlotting.var"#203#205"{AbstractPlotting.Attributes, Observables.Observable{Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}}, DataType}, Tuple{Observables.Observable{Tuple{}}, Observables.Observable{Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}}}})(::Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}})@Observables.jl:334
    #invokelatest#2@essentials.jl:708[inlined]
    invokelatest@essentials.jl:706[inlined]
    notify@Observables.jl:88[inlined]
    setindex!@Observables.jl:248[inlined]
    MapUpdater@Observables.jl:372[inlined]
    (::Observables.OnUpdate{Observables.MapUpdater{typeof(tuple), Tuple{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}}, Tuple{Observables.Observable{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}}})(::Vector{Tuple{String, GeometryBasics.Point{2, Float32}}})@Observables.jl:334
    #invokelatest#2@essentials.jl:708[inlined]
    invokelatest@essentials.jl:706[inlined]
    notify@Observables.jl:88[inlined]
    setindex!@Observables.jl:248[inlined]
    (::AbstractPlotting.MakieLayout.var"#153#184"{Observables.Observable{Vector{GeometryBasics.Point{2, Float32}}}, Observables.Observable{Vector{Float32}}, Observables.Observable{Float32}, Observables.Observable{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}, Observables.Observable{Tuple{Float32, Tuple{Float32, Float32}, Bool}}, Observables.Observable{Any}, Observables.Observable{Any}})(::Vector{String}, ::Float32, ::Bool)@lineaxis.jl:311
    (::Observables.OnUpdate{AbstractPlotting.MakieLayout.var"#153#184"{Observables.Observable{Vector{GeometryBasics.Point{2, Float32}}}, Observables.Observable{Vector{Float32}}, Observables.Observable{Float32}, Observables.Observable{Vector{Tuple{String, GeometryBasics.Point{2, Float32}}}}, Observables.Observable{Tuple{Float32, Tuple{Float32, Float32}, Bool}}, Observables.Observable{Any}, Observables.Observable{Any}}, Tuple{Observables.Observable{Vector{String}}, Observables.Observable{Float32}, Observables.Observable{Any}}})(::Vector{String})@Observables.jl:334
    #invokelatest#2@essentials.jl:708[inlined]
    invokelatest@essentials.jl:706[inlined]
    notify@Observables.jl:88[inlined]
    setindex!@Observables.jl:248[inlined]
    (::AbstractPlotting.MakieLayout.var"#140#171"{Observables.Observable{Vector{String}}, Observables.Observable{Vector{GeometryBasics.Point{2, Float32}}}, Observables.Observable{Vector{Float32}}, Observables.Observable{Tuple{Float32, Tuple{Float32, Float32}, Bool}}, Observables.Observable{Any}, AbstractPlotting.Attributes})(::Tuple{Vector{Float64}, Vector{String}}, ::Bool)@lineaxis.jl:242
    (::Observables.OnUpdate{AbstractPlotting.MakieLayout.var"#140#171"{Observables.Observable{Vector{String}}, Observables.Observable{Vector{GeometryBasics.Point{2, Float32}}}, Observables.Observable{Vector{Float32}}, Observables.Observable{Tuple{Float32, Tuple{Float32, Float32}, Bool}}, Observables.Observable{Any}, AbstractPlotting.Attributes}, Tuple{Observables.Observable{Tuple{Vector{Float64}, Vector{String}}}, Observables.Observable{Any}}})(::Tuple{Vector{Float64}, Vector{String}})@Observables.jl:334
    #invokelatest#2@essentials.jl:708[inlined]
    invokelatest@essentials.jl:706[inlined]
    notify@Observables.jl:88[inlined]
    setindex!(::Observables.Observable{Tuple{Vector{Float64}, Vector{String}}}, ::Tuple{Vector{Float64}, Vector{String}})@Observables.jl:248
    (::Observables.MapUpdater{AbstractPlotting.MakieLayout.var"#139#170", Tuple{Vector{Float64}, Vector{String}}})(::Tuple{Float32, Tuple{Float32, Float32}, Bool}, ::Vararg{Any, N} where N)@Observables.jl:372
    (::Observables.OnUpdate{Observables.MapUpdater{AbstractPlotting.MakieLayout.var"#139#170", Tuple{Vector{Float64}, Vector{String}}}, Tuple{Observables.Observable{Tuple{Float32, Tuple{Float32, Float32}, Bool}}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}}})(::Tuple{Float32, Float32})@Observables.jl:334
    #invokelatest#2@essentials.jl:708[inlined]
    invokelatest@essentials.jl:706[inlined]
    notify@Observables.jl:88[inlined]
    setindex!(::Observables.Observable{Any}, ::Tuple{Float32, Float32})@Observables.jl:248
    var"#LineAxis#129"(::Base.Iterators.Pairs{Symbol, Observables.Observable, NTuple{36, Symbol}, NamedTuple{(:endpoints, :limits, :flipped, :ticklabelrotation, :ticklabelalign, :labelsize, :labelpadding, :ticklabelpad, :labelvisible, :label, :labelfont, :ticklabelfont, :ticklabelcolor, :labelcolor, :tickalign, :ticklabelspace, :ticks, :tickformat, :ticklabelsvisible, :ticksvisible, :spinevisible, :spinecolor, :spinewidth, :ticklabelsize, :trimspine, :ticksize, :reversed, :tickwidth, :tickcolor, :minorticksvisible, :minortickalign, :minorticksize, :minortickwidth, :minortickcolor, :minorticks, :scale), Tuple{Observables.Observable{Tuple{GeometryBasics.Point{2, Float32}, GeometryBasics.Point{2, Float32}}}, Observables.Observable{Tuple{Float32, Float32}}, Observables.Observable{Bool}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Bool}, Observables.Observable{Symbol}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}, Observables.Observable{Any}}}}, ::Type{AbstractPlotting.MakieLayout.LineAxis}, ::AbstractPlotting.Scene)@lineaxis.jl:382
    var"#layoutable#199"(::Nothing, ::Base.Iterators.Pairs{Symbol, Any, NTuple{5, Symbol}, NamedTuple{(:limits, :xticks, :xticklabelalign, :xaxisposition, :yaxisposition), Tuple{Tuple{Float64, Float64, Int64, Int64}, Tuple{Vector{Float64}, Vector{String}}, Vector{Symbol}, Symbol, Symbol}}}, ::typeof(AbstractPlotting.MakieLayout.layoutable), ::Type{AbstractPlotting.MakieLayout.Axis}, ::AbstractPlotting.Figure)@axis.jl:196
    #_layoutable#11@layoutables.jl:63[inlined]
    #_layoutable#10@layoutables.jl:58[inlined]
    #_#9@layoutables.jl:49[inlined]
    top-level scope@Local: 7[inlined]

This is understandable since the documentation says

xticklabelalign
The horizontal and vertical alignment of the xticklabels.

But it might make sense to accept a vector of horizontal alignments for the xticklabels.

For now (CairoMakie 0.4.4, AbstractPlotting 0.17.4), one has to specify both the horizontal and vertical alignments
(and since the label is always upright, it makes sense that the vertical alignment has to be changed for the top row)

Here is the updated example:

using CairoMakie
fig = Figure(resolution = (800, 800))

for i in 1:2, j in 1:2
    # otherwise the 0.7 tick label is missing
    xticks_values = [0.5, 0.55, 0.6, 0.65, 0.7]
    # no %g equivalent yet
    xticks_strings = ["0.5", "0.55", "0.6", "0.65", "0.7"]
    # between 0 (no shift) and 0.5 (justified)
    xticklabel_relative_shift = 0.3
    N_xticklabel = length(xticks_values)
    xticklabel_halign = fill(0.5, N_xticklabel)
    xticklabel_halign[begin] -= xticklabel_relative_shift
    xticklabel_halign[end] += xticklabel_relative_shift
    xticklabel_valign = fill(i == 1 ? :bottom : :top, N_xticklabel)
    xticklabelalign = collect(zip(xticklabel_halign, xticklabel_valign))
    Axis(fig[i, j];
        limits = (0.5, 0.7, -2, 2),
        xticks = (xticks_values, xticks_strings),
        xticklabelalign,
        xaxisposition = (i == 1 ? :top : :bottom),
        yaxisposition = (j == 1 ? :left : :right)
    )
end
colgap!(fig.layout, 25)
rowgap!(fig.layout, 25)

1 Like

This is a neat solution, but I saved myself a lot of work by simply making the axes limits slightly wider with xlims! and ylims!. In this case, making them, say, 0.48 and 0.72 just to add a tiny bit of padding. The tick labels should still remain “nice”