Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance the DateTime, Date and Time axis formatting #4404

Open
mathieu17g opened this issue Sep 24, 2024 · 1 comment
Open

Enhance the DateTime, Date and Time axis formatting #4404

mathieu17g opened this issue Sep 24, 2024 · 1 comment
Labels
enhancement Feature requests and enhancements Makie Backend independent issues (Makie core) planning For discussion and planning development units aka dim converts

Comments

@mathieu17g
Copy link

mathieu17g commented Sep 24, 2024

Feature description

Extend the handling of TimeType type for axis ticks and labels by handling Date and Time and enable the use of a formatter specified as a DateFormat or any Function on ticks' values

Here are three light modifications in src/date-integration.jl of v0.21.11, enabling those:

1. Add easy to define formatter with DateFormat

In src/date-integration.jl add the following get_ticklabels functions:

# Default implementation for `get_ticklabels` with `DateFormat`.
# Used before DateTimeConversion is set to the axis via plots addition.
get_ticklabels(::DateFormat, values) = get_ticklabels(Automatic(), values)

function get_ticklabels(::Type{T}, df::DateFormat, values) where {T<:TimeType}
    return [Dates.format(number_to_date(T, v), df) for v in values]
end

2. Extend get_ticks for DateTimeConversion to use PlotUtilsjl function optimize_datetime_ticks for Date beyond DateTime. And use the DateFormat formatter or any formatter defined by a Function on ticks' values.

Add to function get_ticks(conversion::DateTimeConversion, args...)

A conversion of extrema values to DateTime corresponding values

<<< if T <: DateTime
>>> if T <: Union{Date,DateTime}
        if T == Date
            # Convert T float extrema values to DateTime corresponding float values
            vmin = date_to_number(DateTime, DateTime(number_to_date(T, vmin)))
            vmax = date_to_number(DateTime, DateTime(number_to_date(T, vmax)))
        end

And at the end of the corresponding if clause, before returning, either convert back ticks values computed by optimize_datetime_ticks to the axis unit and use the DateFormat type formatter or use Function type formatter on internal numbers ticks' values:

<<< conversion, dates = PlotUtils.optimize_datetime_ticks(vmin, vmax; k_min=k_min, k_max=k_max)
    return conversion, dates
>>> tickvalues, ticklabels = PlotUtils.optimize_datetime_ticks(vmin, vmax; k_min=k_min, k_max=k_max)
    if T == Date
        tickvalues = date_to_number.((T,), T.(number_to_date.((DateTime,), tickvalues)))
    end
    ticklabels = if formatter isa Automatic
        ticklabels
    elseif formatter isa DateFormat
        get_ticklabels(T, formatter, tickvalues)
    elseif formatter isa Function
        formatter(tickvalues)
    else
        error("$(formatter) not supported for DateTimeConversion")
    end
    return tickvalues, ticklabels

3. Handle the Time type in get_ticks for DateTimeConversion by using the DateFormat formatter (with its restriction of not handling precision below the milisecond) or any formatter defined by a Function on ticks' values

<<< else
        # TODO implement proper ticks for Time Date
        tickvalues = get_tickvalues(formatter, scale, vmin, vmax)
        dates = number_to_date.(T, round.(Int64, tickvalues))
        return tickvalues, string.(dates)
    end
>>> elseif T == Time
        tickvalues = get_tickvalues(ticks, scale, vmin, vmax)   
        ticklabels = if formatter isa Automatic
            times = number_to_date.(T, round.(Int64, tickvalues))
            string.(times)
        elseif formatter isa DateFormat
            get_ticklabels(T, formatter, tickvalues)
        elseif formatter isa Function
            formatter(tickvalues)
        else
            error("$(formatter) not supported for DateTimeConversion")
        end
        return tickvalues, ticklabels
    else
        error("$(T) not supported for DateTimeConversion")
    end

Output plot examples

This should enable to produce this type of Date, DateTime and Time axis formatting with Automatic, DateFormat or Function formatter.

By the way, it would be interesting to document a bit the number_to_date functions, even if not exported, with a docstring. It would ease the use of the Function formatted. I had not seen the docstrings, maybe a mention to it in documentation then

Examples code
using GLMakie
using Dates

# Generate some sample data
dates = [Date(2022, 1, 1) + Dates.Day(i) for i = 0:10]
datetimes = [DateTime(2022, 1, 1, i, 20) for i = 0:12]
times = [Time(0, 0, 0, i) for i = 0:10]
values = [1, 3, 2, 4, 5, 3, 6, 4, 7, 6, 8]
values4datetimes = [1, 3, 2, 4, 5, 3, 6, 4, 7, 6, 8, 7, 10]

# Create a Figure
fig = Figure(size = (1500, 1000))

# Add an Axis with and without Date formatting on X axis
ax11 = Axis(
    fig[1, 1],
    xticks = WilkinsonTicks(4; k_max = 4),
    title = rich("Date format on X axis with ", rich("Automatic", color = :blue, font = "Consolas"), " as formatter"),
)
ax21 = Axis(
    fig[2, 1],
    xticks = WilkinsonTicks(4; k_max = 4),
    xtickformat = dateformat"Y-u-dd (e)",
    title = rich(
        "Date format on X axis with ",
        rich("dateformat\"Y-u-dd (e)\"", color = :blue, font = "Consolas"),
        " as formatter",
    ),
)
ax31 = Axis(
    fig[3, 1],
    xticks = WilkinsonTicks(10),
    xtickformat = (
        V -> [
            dayofweek(v) == 1 ? Dates.format(v, "e") * "\n" * Dates.format(v, "Y-W") * string(week(v); pad = 2) :
            Dates.format(v, "e") for v in Makie.number_to_date.((Date,), V)
        ]
    ),
    title = rich(
        "Date format on X axis with \n",
        rich(
            "V -> [dayofweek(v) == 1 ? Dates.format(v, \"e\") * \"\\n\" * \nDates.format(v, \"Y-W\") * string(week(v); pad = 2) : \nDates.format(v, \"e\") \nfor v in Makie.number_to_date.((Date,), V)]\n",
            color = :blue,
            font = "Consolas",
        ),
        " as formatter",
    ),
)
# Create a line plots with Date type for x value
lines!(ax11, dates, values)
lines!(ax21, dates, values)
lines!(ax31, dates, values)

# Add an Axis with and without DateTime formatting on X axis
ax12 = Axis(
    fig[1, 2],
    xticks = WilkinsonTicks(2; k_max = 2),
    title = rich("DateTime format on X axis with ", rich("Automatic", color = :blue, font = "Consolas"), " as formatter"),
)
ax22 = Axis(
    fig[2, 2],
    xticks = WilkinsonTicks(4; k_max = 4),
    xtickformat = dateformat"u-dd Hh",
    title = rich(
        "DateTime format on X axis with ",
        rich("dateformat\"u-dd Hh\"", color = :blue, font = "Consolas"),
        " as formatter",
    ),
)
ax32 = Axis(
    fig[3, 2],
    xticks = WilkinsonTicks(4; k_max = 4),
    xtickformat = (
        V -> [
            hour(v) == 0 ? string(hour(v)) * "h\n" * string(Date(v)) : string(hour(v)) * "h" for
            v in Makie.number_to_date.((DateTime,), V)
        ]
    ),
    title = rich(
        "DateTime format on X axis with function\n",
        rich(
            "V -> [hour(v) == 0 ? string(hour(v)) * \"h\\n\" * \nstring(Date(v)) : string(hour(v)) * \"h\" \nfor v in Makie.number_to_date.((DateTime,), V)]\n",
            color = :blue,
            align = :left,
            font = "Consolas",
        ),
        " as formatter",
    ),
)
# Create a line plots with DateTime type for x value
lines!(ax12, datetimes, values4datetimes)
lines!(ax22, datetimes, values4datetimes)
lines!(ax32, datetimes, values4datetimes)

# Add an Axis with and without Time formatting on X axis
ax13 = Axis(
    fig[1, 3],
    xticks = WilkinsonTicks(4; k_min = 4),
    title = rich("Time format on X axis with ", rich("Automatic", color = :blue, font = "Consolas"), " as formatter"),
)
ax23 = Axis(
    fig[2, 3],
    xticks = WilkinsonTicks(4; k_min = 4),
    xtickformat = dateformat"S.s\s",
    title = rich(
        "Time format on X axis with ",
        rich("dateformat\"S.s\\s\"", color = :blue, font = "Consolas"),
        " as formatter",
    ),
)
ax33 = Axis(
    fig[3, 3],
    xticks = WilkinsonTicks(4; k_min = 4),
    xtickformat = (V -> [string(round(Int64, v) ÷ 1_000_000) * "ms" for v in V]),
    title = rich(
        "Time format on X axis with function\n",
        rich("V -> [string(round(Int64, v) ÷ 1_000_000) * \"ms\" for v in V]\n", color = :blue, font = "Consolas"),
        "as formatter",
    ),
)
# Create a line plot with Time type for x value
lines!(ax13, times, values)
lines!(ax23, times, values)
lines!(ax33, times, values)

# Show the plot
display(fig)

# Save the plot
save("Feature_Request_example1.png", fig)

Example output

Further enhancement

It would be great to have a default (Automatic ?) well behaved Date, Time, DateTime formatter like the one in Plotly.js

@oscarvdvelde
Copy link

I have bug associated with this:
Formatting an axis in which you plot Time data. In my case (every 5 minutes from 03:30 to 6:30) I only get three ticks. Specifying xticks = LinearTicks(10), for example, before or after, is ignored.
Besides that, a good way to control date and time ticks may be to be able to specify them as follows:
xticks = DateTimeTicks(Minute(15))

@ffreyer ffreyer added planning For discussion and planning development Makie Backend independent issues (Makie core) units aka dim converts labels Oct 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Feature requests and enhancements Makie Backend independent issues (Makie core) planning For discussion and planning development units aka dim converts
Projects
None yet
Development

No branches or pull requests

3 participants