Skip to content

Conversation

@ffreyer
Copy link
Collaborator

@ffreyer ffreyer commented Oct 7, 2025

Description

This pr adds argument_dims() as an overloadable function similar to convert_arguments() to allow recipes to define how their arguments map to plot dimensions. With that you can specify which dim_convert applies to first, second, ... argument by returning the index of the appropriate dimension/dim convert.

This pr fixes:

Other affected:

This pr also aims to make some usability improvements:

  • simplify units in axis labels by allowing dim_converts to generate label suffixes, fixing Units in axis labels #3890
  • add more dim convert related attributes to blocks to avoid needing to create dim converts manually

Changes:

  • removed dynamic unit prefixes (1e-6u"m" does not convert to 1u"µm" anymore)
  • removed units_in_label from UnitfulConversion as it's now controlled by Axis
  • added force_dimconverts as a plot kwarg. This is automatically set to true when plotting to a scene and false when plotting to another plot. When the kwarg is true the plot must conform to existing dim_converts, otherwise unitless are also allowed. (Needed for various axis decoration plots in Axis3)
  • conversions now check space and avoid dim converts if space !== :data, fixing text! with relative space does not work with DateTime x-axis #4324

TODO:

  • type errors when plotting into an existing Axis (untyped label rotation?)
  • finalize Axis attributes for dim_converts
  • update Axis3, PolarAxis, LScene to include new dim_convert attributes and axis label suffixes
  • update docs
  • add tests
  • add refimg test for new attributes
  • check space when setting up conversions (only data should be allowed to use dim converts)

Type of change

The pr has some breaking changes:

  • UnitfulConversion and DQConversion no longer have units_in_label
  • UnitfulConversion no longer rescales units
  • plots with a single (converted) argument are no longer excluded from dim converts by default
  • (get_ticks() signature for dim converts now also includes show_in_label as an argument, though it defaults to the method without it)
  • (more typing for recipe conversion targets may cause some errors from unexpected conversions)

Checklist

  • Added an entry in CHANGELOG.md (for new features and breaking changes)
  • Added or changed relevant sections in the documentation
  • Added unit tests for new algorithms, conversion methods, etc.
  • Added reference image tests for new plotting functions, recipes, visual options, etc.

@github-project-automation github-project-automation bot moved this to Work in progress in PR review Oct 7, 2025
@MakieBot
Copy link
Collaborator

MakieBot commented Oct 7, 2025

Benchmark Results

SHA: 7feb429b0d3a374a6310058021379b9441e9d458

Warning

These results are subject to substantial noise because GitHub's CI runs on shared machines that are not ideally suited for benchmarking.

GLMakie
CairoMakie
WGLMakie

Copy link
Member

@SimonDanisch SimonDanisch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! :)
A few things I'd like to change:

argument_dims(::Type{<:Annotation}, x, y) = (1, 2)
argument_dims(::Type{<:Annotation}, xy::VecTypes{2}, xy2::VecTypes{2}) = ((1, 2), (1, 2))

I think the fallback for these should be smart enough so that these don't need to get defined?

The suffix format "[{}]" seems a bit unusal.
I think we should offer:

  • FormatExpr("({1})") via Format.jl
  • Functional unit-> "[$(unit)]"

Maybe it would also make sense to have one big ticklabel function one can overload?
(unit, suffix, prefix, label) -> ... Not sure if that could entirely replace suffix?

I was also thinking if we should take the chance to move unitful integration into an Extension. I could take a look at that, I just moved a couple of things to extensions recently!

@jkrumbiegel what do you thing? Any comment about the new axis attributes?

@ffreyer ffreyer marked this pull request as ready for review October 20, 2025 21:52
@ffreyer
Copy link
Collaborator Author

ffreyer commented Oct 21, 2025

I now added conversion target types for almost all recipes because I'm worried there will be issues otherwise. If you define a recipe that uses a plot that does not have them set, you will end up with status = nothing from convert_arguments. That leads into trying dim converts. If argument_dims() are set for the child plot, which is now usually the case, dim converts will be applied. If the recipe already applied dim converts, then the child plot will error as numeric arguments are not compatible (when dim converts are used/needed).

Plots a surface, where `(x, y)` define a grid whose heights are the entries in `z`.
`x` and `y` may be `Vectors` which define a regular grid, **or** `Matrices` which define an irregular grid.
"""
@recipe Surface (x::VecOrMat{<:FloatType}, y::VecOrMat{<:FloatType}, z::VecOrMat{<:FloatType}) begin
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does z actually allow vectors? I didn't allow it in the conversion trait...

Comment on lines +565 to +571
if !is_data_space(space)
# dim converts do not apply in relative, pixel or clip space
map!(attr, :args, :dim_converted) do args
return Ref{Any}(args)
end
elseif force_dimconverts && needs_dimconvert(dim_converts)
add_dim_converts!(P, attr, dim_converts, args, args_converted, user_kw)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes here are:

  1. relative, pixel and clip space skip dim converts
  2. need_dimconverts() now only triggers when force_dimconverts == true for the plot, which happens by default for plots to a scene but not for plots to another plot. This was necessary to avoid forcing dim converts in recipes, where the parent had already done the conversion. Previously this was decided based on whether the argument was a Vector{<:Point}, more or less. Axis3 also needed this behavior to be overwritable for axis decorations, so you can pass force_dimconverts = false to plots like an argument.
  3. the force_dimconvert and status == nothing branches now apply recursive/early convert_arguments like the false branch did. (This moved to add_dim_converts)

# Something we can convert to an EndPoints type
const EndPointsLike = Union{ClosedInterval, Tuple{Real, Real}}

const RangeLike = Union{AbstractVector{T}, ClosedInterval{T}, Tuple{T, T}} where {T}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type changed from
Union{AbstractVector, ClosedInterval, Tuple{Real, Real}}
i.e. the eltype is now settable instead of being partially fixed to Real

Comment on lines +90 to +94
get_label_suffix(dc, format, use_short) = apply_format(get_label_suffix(dc, use_short), format)
get_label_suffix(dc, format) = apply_format(get_label_suffix(dc), format)
get_label_suffix(dc, ::Bool) = get_label_suffix(dc)
get_label_suffix(dc) = error("No axis label suffix defined for conversion $dc.")
get_label_suffix(dc::Union{Nothing, NoDimConversion}) = ""
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I set this up to error when setting ax.x_unit_in_label = true for a dim convert that doesn't implement get_label_suffix. We could also have it return an empty suffix, but then we would probably want to not automatically apply the format. Because that results in "[]".
And if this does error we should probably remove the Nothing/NoDimConversion case.

Comment on lines +300 to +325
"Controls whether the x dim_convert is shown in ticklabels."
x_unit_in_ticklabel::Union{Bool, Automatic} = automatic
"Controls whether the y dim_convert is shown in ticklabels."
y_unit_in_ticklabel::Union{Bool, Automatic} = automatic
"Controls whether the x dim_convert is shown in the xlabel."
x_unit_in_label::Union{Bool, Automatic} = automatic
"Controls whether the y dim_convert is shown in the ylabel."
y_unit_in_label::Union{Bool, Automatic} = automatic
"""
Formatter for the xlabel suffix generated from dim_converts. Can be a
Format.jl format string or a callback function acting acting on the
string or rich text generated from the dim convert.
Can also be a plain String replacing an active dim_convert label.
"""
xlabel_suffix = "[{}]"
"""
Formatter for the ylabel suffix generated from dim_converts. Can be a
Format.jl format string or a callback function acting acting on the
string or rich text generated from the dim convert.
Can also be a plain String replacing an active dim_convert label.
"""
ylabel_suffix = "[{}]"
"Switches between short and long x units, e.g. \"s\" vs \"Second\""
use_short_x_units::Bool = true
"Switches between short and long y units, e.g. \"s\" vs \"Second\""
use_short_y_units::Bool = true
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These probably need some renaming to fix with Axis attributes better? I guess just remove all the _?

Note that the `::Type{<:Plot}` methods take precedence over `::ConversionTrait`.
"""
function argument_dims(PT, args...; kwargs...)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to export argument_dims and argument_dim_kwargs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope... Also, I think it would be nice to get rid of arguments_dim_kwargs!

@SimonDanisch SimonDanisch mentioned this pull request Oct 22, 2025
5 tasks
@ffreyer ffreyer added the breaking a PR with breaking changes label Nov 5, 2025
@ffreyer ffreyer moved this from Work in progress to Ready to review in PR review Nov 11, 2025
@jkrumbiegel
Copy link
Member

As this is similar to what AoG needs to do, maybe we can compare the two. Does this implementation have something that can respect the direction keywords and similar attributes? Also in AoG I have a DeltaY scale for errorbar for example where the unit needs to match. For example a DateTime axis can't have a DateTime as an error magnitude.

@ffreyer
Copy link
Collaborator Author

ffreyer commented Nov 18, 2025

As this is similar to what AoG needs to do, maybe we can compare the two. Does this implementation have something that can respect the direction keywords and similar attributes?

argument_dims can include attributes as kwargs (without removing them and with reactiveness), so direction is handled there.

Also in AoG I have a DeltaY scale for errorbar for example where the unit needs to match. For example a DateTime axis can't have a DateTime as an error magnitude.

Didn't do anything like that. This pr just tags args as "should convert with the n-th dim_convert` or no dim convert.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking a PR with breaking changes

Projects

Status: Ready to review

6 participants