Zooming and date axes in Makie

Hi there!

I recently got acquainted with Makie and I absolutely love it!
Here are 2 small questions related to zooming (in GLMakie):

  • Is it possible to update the ticks dynamically when zooming? For instance I would like ticks at 10, 20 and 30 with the default view, but when zooming by x10 I would like ticks at 1, 2 and 3
  • Is it possible to synchronize zooming on several axes? For instance, I have a stack of 3 axes with the same x axis. When I select a rectangle region in the first one, I would like the x-zoom on the other two to react accordingly.

Thanks a lot!

1 Like

I think linkxaxes! does this.

1 Like

Awesome! This is exactly what I needed :heart_eyes:
Any idea about the ticks?

The ticks normally do update when zooming, what version are you on? Or did you set ticks manually somehow?

The existing ticks do update and the space between them widens, but I would like for new ticks to appear as I zoom in

Yes that should normally happen, so if you zoom in further the existing ticks disappear to the sides?

New ticks appear when I don’t specify the ticks precisely. However, when I need to define ticks manually, they don’t adapt to the zoom level. This is especially problematic for axes with dates, since those are not supported natively.
On the screenshots below, which were generated with

using GLMakie
GLMakie.inline!(false)
too_few_ticks = scatter(
    1:100, rand(100),
    axis=(
        xticks=(0:10:100, string.(0:10:100)),
    )
)

the xticks stay the same while the yticks adapt.


Ok yes if you fix the ticks, they’re fixed :slight_smile: If you need different dynamic ticks than the defaults then you can create your own tick finder. This is probably a bit tricky for date ticks, and it will depend on how the numbers that you plot relate to the dates that they represent. You will sometimes see solutions where only date-specific formatters are applied to a non-date-aware tick finder. This then leads to weird tick choices that happen to match the underlying nice integer ticks, like "Sunday 10:50pm", "Wednesday 9:10am" as a made-up example. A real date tick finder would be aware that on a span of weeks you want to see days, on a span of a day you want to see hours, etc. Makie currently internally converts to Float32 precision, which is why we don’t have Date machinery, dates can not be represented faithfully in 32 bit…

Seems obvious when you state it like that :sweat_smile: You mean I should try to imitate LinearTicks(n), but with dates? Something like LinearDateTicks(min_date, max_date, n)? I can’t seem to find the interface requirements for these tick finders

It’s not terribly well documented because it’s a bit of a niche use. But this paragraph hints to some functions.

And the default is actually WilkinsonTicks and not LinearTicks anymore, the docs seem to have desynchronized there.

For example, if you have numbers that mean dates somehow, you could do this

struct DateTicks end

function MakieLayout.get_ticks(::DateTicks, ::typeof(identity), ::Makie.Automatic, vmin, vmax)
    mindate, maxdate = convert_numbers_to_dates.((vmin, vmax))
    dates = find_some_date_ticks(mindate, maxdate)
    ticklabels = make_labels(dates)
    tickvalues = convert_dates_to_numbers(dates)
    return tickvalues, ticklabels
end

# and then
lines(datenumbers, values, axis = (; xticks = DateTicks()))
1 Like

Thanks, this is precisely what I was looking for. Here’s a working prototype

using Dates
using GLMakie
GLMakie.inline!(false)

date_to_number(d::Date) = float(Dates.value(d) - Dates.value(Date(1970, 1, 1)))
number_to_date(x::Real) = Date(unix2datetime(x * 24 * 3600))

struct LinearDateTicks
    n::Int
end

function MakieLayout.get_ticks(ticks::LinearDateTicks, ::typeof(identity), ::Makie.Automatic, vmin, vmax)
    mindate = number_to_date(vmin)
    maxdate = number_to_date(vmax)
    step = (maxdate - mindate) ÷ ticks.n
    @assert step >= Day(1)
    tickdates = collect(range(mindate, maxdate; step=step))
    tickvalues = date_to_number.(tickdates)
    ticklabels = string.(tickdates)
    return tickvalues, ticklabels
end

x = Date(2000):Day(1):Date(2020)
y = dayofyear.(x)
scatter(date_to_number.(x), y, color=(:blue, 0.5), axis=(xticks=LinearDateTicks(10),xticklabelrotation=0.5))

Since the dependencies are minimal, do you think a beefed up version would be worthy of a PR? I know this would come in handy to lots of people working with time series

2 Likes

Hmm I don’t want to canonicalize a “hack”, when we add real Date support it will be integrated better. This solution here depends on the type of conversion from Dates to numbers which will probably not be the same for everyone. But we could think about adding guidance to the docs that I linked until that’s happening.

1 Like

I understand that this is a hack, and that putting it in the docs may be the best you can do for now.
However, note that according to the Dates documentation, there are only two natural ways to perform conversions between dates/times and numbers, because date/time objects are nothing but number wrappers:

  • A Date stores the equivalent nb of days
  • A DateTime stores the equivalent nb of milliseconds

The forward conversion operation is performed by Dates.value in both cases, and the backward operation can be defined thanks to unix2datetime.

The most annoying thing is the resolution with Float32. Currently doing the conversion directly has way too low of a resolution. Let’s look at one minute today:

julia> d1 = DateTime(Year(2022), Month(05), Day(10), Hour(15), Minute(44))
2022-05-10T15:44:00

julia> d2 = DateTime(Year(2022), Month(05), Day(10), Hour(15), Minute(45))
2022-05-10T15:45:00

julia> Dates.value(d2) - Dates.value(d1)
60000

julia> Float32(Dates.value(d2)) - Float32(Dates.value(d1))
0.0f0

So either Makie has to first scale the dates into a range where Float32 is okay, or we need some other mechanism to deal with the precision problems. GLMakie will probably never be able to deal with Float64 though, as I don’t think GPUs like those very much.

2 Likes