Makie.jl
Makie.jl copied to clipboard
Fix automatic unit decision for Unitful axes
Description
Fixes the automatically determined units for Unitful axes that are symmetric.
Currently if an axis has the same absolute value for its positive and negative extrema, the determination of the best unit in Makie.best_unit gives the smallest possible prefix. E.g. Makie.best_unit(-1.0u"mm", 1.0u"mm") gives ym=yoctometer=10^-24 m because it only uses the value in the middle between min and max which is zero in this case.
This results in useless units for these types of axes:
lines(range(start=-1.0u"mm", stop=1.0u"mm", length=50), randn(50))
For a non-symmetric axis everything is fine
lines(range(start=0.0u"mm", stop=1.0u"mm", length=50), randn(50))
Proposed fix
In this PR I changed the decision of the unit to not use the middle value but instead the total length of the axis, which should be a characteristic value based on which the correct unit can be determined:
There are cases where the automatically decided axis unit is different than before, but I do not think this would count as a breaking change. Before:
Makie.best_unit(0u"mm",10u"mm") # mm
This PR:
Makie.best_unit(0u"mm",10u"mm") # cm
Type of change
Delete options that do not apply:
- [x] Bug fix (non-breaking change which fixes an issue)
I see that the reference images actually hit one case where the automatic unit is different now. How should I proceed here?
I'm not sure if the length would be a good metric either. Something like 1km .. 1km + 10cm would show up as 1000 00cm .. 1000 10cm then, right? Maybe 0.5 * (abs(low) + abs(high)) would be better?
Hm, that´s a good point I have not considered yet. Axes where the length greatly differs from the order of magnitude of the values are in general difficult to plot. One could argue for both the larger or the smaller unit, depending on which property is more important in that axis, the difference between two samples, or the overall size.
Using your example these are basically the possible options:
x = range(1.0u"km", 1.0u"km"+10u"cm", length=50); y=randn(50);
fig = Figure()
ax1 = Axis(fig[1,1], dim1_conversion=Makie.UnitfulConversion(u"km"))
ax2 = Axis(fig[1,2], dim1_conversion=Makie.UnitfulConversion(u"m"))
ax3 = Axis(fig[2,1], dim1_conversion=Makie.UnitfulConversion(u"cm"))
ax4 = Axis(fig[2,2], dim1_conversion=Makie.UnitfulConversion(u"mm"))
for ax in [ax1,ax2,ax3,ax4]
lines!(ax,x,y)
end
In this case I would manually choose the m scale as its a tradeoff between both, however to automate that we would have to further extend the logic from using a single value.
For axes with even more orders of magnitude between offset and length it gets more difficult:
x = range(1.0u"km", 1.0u"km"+10u"µm", length=50); y=randn(50);
fig = Figure()
ax1 = Axis(fig[1,1], dim1_conversion=Makie.UnitfulConversion(u"km"))
ax2 = Axis(fig[1,2], dim1_conversion=Makie.UnitfulConversion(u"m"))
ax3 = Axis(fig[2,1], dim1_conversion=Makie.UnitfulConversion(u"mm"))
ax4 = Axis(fig[2,2], dim1_conversion=Makie.UnitfulConversion(u"µm"))
for ax in [ax1,ax2,ax3,ax4]
lines!(ax,x,y)
end
I would say its good enough to have a default thats not completely off and for more complex axes the user has to manually change the units, depending on the use case.
My preference for a default would be a unit based on the length of the axis, as then you can see the scale of the steps at the first glance.
Yea, I've also had mixed feelings about best_unit and friends (https://github.com/SymbolicML/DynamicQuantities.jl/pull/165#issuecomment-2764825640) lately. I was wondering how much the added complexity buys us over just keeping things explicit
For reference, here's how that might look with DynamicQuantities over in that PR:
import DynamicQuantities as DQ
using CairoMakie
lines(range(start=-1.0*DQ.us"mm", stop=1.0*DQ.us"mm", length=50), randn(50))
# Or with axis conversion API
#const DQConversion = Base.get_extension(DQ, :DynamicQuantitiesMakieExt).DQConversion
#
#lines(range(start=-1.0*DQ.u"mm", stop=1.0*DQ.u"mm", length=50), randn(50);
# axis = (; dim1_conversion=DQConversion(DQ.us"mm")),
#)
That´s a good thought, I agree that we can probably reach the same level of convenience by using the unit already attached to the values of the axis as given by the user. So if the user gives values in km it will be km etc., for Unitful it would also be intuitive to just use uconvert (or |> u"mm" ) before plotting to achieve the desired unit. Otherwise the Makie.UnitfulConversion option is still there.
@ffreyer If you agree I would try to come up with something for that
Howdy, the PR experimenting with this approach with DynamicQuantities lives here now: #5280
Update: The DQ approach is in Makie v0.24.7 now, and looks like the unit handling with Unitful will be simplified too in #5323 along with a whole bunch of other really nice improvements =D