Makie - xscale - no method matching defaultlimits

julia> scatter(1:1.0:10, rand(10); axis=(; xscale=x->2.0*x))
ERROR: MethodError: no method matching defaultlimits(::var"#118#119")

Closest candidates are:
  defaultlimits(::Tuple{Real, Real}, ::Any)
   @ Makie ~/.julia/packages/Makie/gAmAB/src/makielayout/blocks/axis.jl:1333
  defaultlimits(::NTuple{4, Real}, ::Any, ::Any)
   @ Makie ~/.julia/packages/Makie/gAmAB/src/makielayout/blocks/axis.jl:1320
  defaultlimits(::Tuple{Real, Nothing}, ::Any)
   @ Makie ~/.julia/packages/Makie/gAmAB/src/makielayout/blocks/axis.jl:1334

You need to overload a couple methods for custom scales, that’s one of them

I hope it’s ok if I hijack this thread, since I am trying (and failing) to do basically the same thing. I want to implement a general linear transform for axes. What I did was this:

struct LinearScale
    a::Float64
    b::Float64
end

(s::LinearScale)(x) = s.a * x + s.b

Makie.inverse_transform(s::LinearScale) = x->(x-s.b)/s.a
Makie.defined_interval(::LinearScale) = OpenInterval(-Inf, Inf)
Makie.defaultlimits(::LinearScale) = (0.0, 10.0)

Unfortunately it doesn’t work. Both of these:

scatter(rand(10); axis=(;xscale=LinearScale(5, 5)))
scatter(collect(1:10), rand(10); axis=(;xscale=LinearScale(5, 5)))

produce a standard axis with x values 1 to 10. The same works perfectly fine with a log scale, though.

It does work, it just works differently than you expect :wink: I actually did exactly the same thing a couple years ago while playing around with the scales.

If you think about what the result is of setting xscale = log10, if you plot data from 1 to 100 you still get the ticks from 1 to 100, not from 0 to 2 (which would be log10(1) and log10(100)). What happens is that the data is transformed, but the ticks are adjusted in accordance with that so that you know what the original values were.

So when you do this with your linear transformation, the data are linearly transformed, and the ticks adjusted such that you know what the original data was. However, that means the effect of the transform is canceled out.

1 Like

Ok, thanks that does make sense.

Is there a different way to “translate” x values to a different scale? As an example, assume I have simulation steps starting at 1, but they represent months of years since 1957. Is there a way to let the axis show (x/12)+1957 instead of x? I’m aware that I could simply provide a vector of correct x values, but for a live graph displayed by a running simulation that would mean that I have to update x and y values with every time step. It’s not a huge issue, admittedly, but I thought maybe there’s a simpler way to do it.

Yes you can, you can make a new ticks type for that. Here is an example implementation, where I overload get_ticks which returns both tick values and tick labels. We just transform the limits with our linear transformation, then pass them on to the actual tick finder (WilkinsonTicks, Makie’s default).
We have to transform the found ticks back to our real limits at the end, otherwise they would not fit.

I haven’t touched the tick finding algorithm or the formatting here, so of course this could be improved given that the numbers are supposed to be years or months.

using CairoMakie


struct LinearTransformationTicks{T}
    ticks::T
    offset::Float64
    slope::Float64
end

function Makie.get_ticks(lt::LinearTransformationTicks, scale, formatter, vmin, vmax)
    lims_transformed = (vmin, vmax) .* lt.slope .+ lt.offset
    tickvals_transformed, ticklabels = Makie.get_ticks(lt.ticks, scale, formatter, lims_transformed...)
    tickvals_untransformed = (tickvals_transformed .- lt.offset) ./ lt.slope
    return tickvals_untransformed, ticklabels
end

lines(
    1:24,
    cumsum(randn(24)),
    axis=(; xticks=LinearTransformationTicks(WilkinsonTicks(5), 1958.0, 1 / 12))
)

1 Like

Cool, thanks! I’ll give that a try.