Skip to content

Basic Makie.jl support #165

New issue

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

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

Already on GitHub? # to your account

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open

Basic Makie.jl support #165

wants to merge 26 commits into from

Conversation

icweaver
Copy link

@icweaver icweaver commented Mar 9, 2025

Closes #164

Demo

using DynamicQuantities, GLMakie

fig = Figure()

y = 60:10:100

scatter(fig[1, 1], y * u"m")
scatter(fig[1, 2], QuantityArray(y, u"m"))
scatter(fig[2, 1], y * u"kg", y * u"m")
scatter(fig[2, 2], y * us"m/s/Hz")

fig

example

See docs for more examples using Makie's dimension conversion machinery

TODO

Future PR(s)?

  • Affine units?
  • Heatmap and Image support. Maybe with a recipe
  • Rich strings (Unitful example)

Copy link
Contributor

github-actions bot commented Mar 9, 2025

Benchmark Results

main ba44320... main / ba44320...
Quantity/creation/Quantity(x) 3.11 ± 0.01 ns 3.1 ± 0.01 ns 1 ± 0.0046
Quantity/creation/Quantity(x, length=y) 3.74 ± 0.01 ns 3.73 ± 0.01 ns 1 ± 0.0038
Quantity/with_numbers/*real 2.79 ± 0.01 ns 3.11 ± 0.01 ns 0.9 ± 0.0043
Quantity/with_numbers/^int 8.37 ± 1.6 ns 8.68 ± 1.6 ns 0.964 ± 0.25
Quantity/with_numbers/^int * real 8.99 ± 1.6 ns 8.98 ± 1.6 ns 1 ± 0.25
Quantity/with_quantity/+y 4.35 ± 0.01 ns 4.35 ± 0.001 ns 1 ± 0.0023
Quantity/with_quantity//y 3.42 ± 0.001 ns 3.42 ± 0.011 ns 1 ± 0.0032
Quantity/with_self/dimension 4.03 ± 0.011 ns 2.79 ± 0 ns 1.44 ± 0.0039
Quantity/with_self/inv 3.12 ± 0.92 ns 3.11 ± 0.01 ns 1 ± 0.3
Quantity/with_self/ustrip 3.11 ± 0.01 ns 2.79 ± 0.01 ns 1.11 ± 0.0053
QuantityArray/broadcasting/multi_array_of_quantities 0.0872 ± 0.00033 ms 0.0875 ± 0.00034 ms 0.997 ± 0.0054
QuantityArray/broadcasting/multi_normal_array 0.0496 ± 0.0019 ms 0.0477 ± 0.0023 ms 1.04 ± 0.064
QuantityArray/broadcasting/multi_quantity_array 0.053 ± 0.00025 ms 0.0529 ± 0.0028 ms 1 ± 0.054
QuantityArray/broadcasting/x^2_array_of_quantities 12.9 ± 1.7 μs 13.5 ± 1.7 μs 0.958 ± 0.18
QuantityArray/broadcasting/x^2_normal_array 1.99 ± 0.37 μs 2 ± 0.33 μs 0.995 ± 0.25
QuantityArray/broadcasting/x^2_quantity_array 3.45 ± 0.12 μs 3.46 ± 0.091 μs 0.997 ± 0.044
QuantityArray/broadcasting/x^4_array_of_quantities 0.0811 ± 0.00029 ms 0.0813 ± 0.00031 ms 0.997 ± 0.0052
QuantityArray/broadcasting/x^4_normal_array 0.0466 ± 0.00012 ms 0.0466 ± 0.00011 ms 1 ± 0.0035
QuantityArray/broadcasting/x^4_quantity_array 0.0467 ± 0.00013 ms 0.0468 ± 0.0031 ms 0.998 ± 0.067
time_to_load 0.195 ± 0.0027 s 0.196 ± 0.00024 s 0.993 ± 0.014

Benchmark Plots

A plot of the benchmark results have been uploaded as an artifact to the workflow run for this PR.
Go to "Actions"->"Benchmark a pull request"->[the most recent run]->"Artifacts" (at the bottom).

@icweaver
Copy link
Author

Just saw this really cool update from Julius on the AoG side of things!

MakieOrg/AlgebraOfGraphics.jl#619

https://aog.makie.org/v0.9.6/examples/scales/units#units

This also looks to address MakieOrg/Makie.jl#3890 in one fell swoop

@MilesCranmer
Copy link
Member

Cool!

Let me know (by tagging me) when you're ready for review

@icweaver
Copy link
Author

icweaver commented Mar 31, 2025

Hi @MilesCranmer, I think this should be just about ready for a first pass of reviews now.

The main additions are:

I'm not quite sure yet how I feel about Makie's choice to automatically change non-compound units for folks out from under them, so I kept that bit of machinery out for now. I do see the convenience of it though, it just seems to add a fair bit of complexity that we maybe don't need right now. Instead, this current design favors explicit over implicit unit handling, e.g.,

Unitful example (taken from ReferenceTests)

# Don't swallow units past the first
f, a, p = scatter((1:10) .* u"J/s")
# Don't simplify (assume the user knows better)
scatter(f[1, 2], (1:10) .* u"K", exp.(1:10) .* u"mm/m^2")
# Only change prefixes of simple units, not compound units
scatter(f[2, 1], 10 .^ (1:6) .* u"W/m^2", (1:6) .* 1000 .* u"nm")
# Only change units/prefixes for simple units when adding more plots
scatter(f[2, 2], (0:10) .* u"W/m^2", (0:10) .* u"g")
scatter!((0:10) .* u"kW/m^2", (0:10) .* u"kg")
f

unitful_example

Here the nanometer plot in the bottom left is auto-converted to microns without the user specifying that unit. In contrast:

This PR

using DynamicQuantities, Makie
const DQConversion = Base.get_extension(DynamicQuantities, :DynamicQuantitiesMakieExt).DQConversion

fig = Figure()

ax1 = Axis(fig[1, 1]; dim2_conversion=DQConversion(us"J/s"))
ax2 = Axis(fig[1, 2]; dim2_conversion=DQConversion(us"mm/m^2"))
ax3 = Axis(fig[2, 1]; dim1_conversion=DQConversion(us"W/m^2"), dim2_conversion=DQConversion(us"μm"))
ax4 = Axis(fig[2, 2]; dim1_conversion=DQConversion(us"W/m^2"))

scatter!(ax1, (1:10) .* u"J/s")
scatter!(ax2, (1:10) .* u"K", exp.(1:10) .* u"mm/m^2")
scatter!(ax3, 10 .^ (1:6) .* u"W/m^2", (1:6) .* 1000 .* u"nm")
scatter!(ax4, (0:10) .* u"W/m^2", (0:10) .* u"g")
scatter!(ax4, (0:10) .* u"kW/m^2", (0:10) .* u"kg")

fig

or with the appropriate use of SymbolicDimensions:

fig = Figure()

scatter(fig[1, 1], (1:10) .* us"J/s")
scatter(fig[1, 2], (1:10) .* u"K", exp.(1:10) .* us"mm/m^2")
scatter(fig[2, 1], 10 .^ (1:6) .* us"W/m^2", (1:6) .* 1000 .* u"nm"; axis=(; dim2_conversion=DQConversion(us"μm")))
scatter(fig[2, 2], (0:10) .* u"W/m^2", (0:10) .* u"g"; axis=(; dim1_conversion=DQConversion(us"W/m^2")))
scatter!(fig[2, 2], (0:10) .* u"kW/m^2", (0:10) .* us"kg")

fig

dq_example

Happy to save that kind of discussion for a separate PR though if it's starting to get too in the weeds for this basic functionality PR. Thanks again for taking a look!

@icweaver icweaver marked this pull request as ready for review April 7, 2025 07:07
@bertini97
Copy link

This would be great for a project I'm working on right now. Can it be pushed?

@MilesCranmer
Copy link
Member

Sure let me review it; sorry i missed the earlier notification. I think I unsubscribed from the thread as it was a draft

Project.toml Outdated
DynamicQuantitiesMeasurementsExt = "Measurements"
DynamicQuantitiesScientificTypesExt = "ScientificTypes"
DynamicQuantitiesUnitfulExt = "Unitful"

[compat]
DispatchDoctor = "0.4"
LinearAlgebra = "1"
Makie = "0.22.2"
Copy link
Member

Choose a reason for hiding this comment

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

Can you comment on why choose this version as a minimum compat?

Copy link
Author

Choose a reason for hiding this comment

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

I think I was looking at Measurements.jl for prior art in adding Makie as a package extension when I was just getting started, and was just being conservative. Looking back, I think this could be relaxed to Makie v0.21.0 since it looks like that is when MakieOrg/Makie.jl#3226 was added

Copy link
Author

Choose a reason for hiding this comment

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

Comment on lines 53 to +57
y0 = 10km
v0 = 250m/s
θ = deg2rad(60)
g = 9.81m/s^2
nothing # hide
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
y0 = 10km
v0 = 250m/s
θ = deg2rad(60)
g = 9.81m/s^2
nothing # hide
y0 = 10km
v0 = 250m/s
θ = deg2rad(60)
g = 9.81m/s^2;

A bit simpler. Can you also use ; rather than nothing elsewhere too

Copy link
Author

Choose a reason for hiding this comment

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

hmm, no dice just yet:

image

I noticed you mention this in your discussion with Abhro in #175, and I've wanted to try it out ever since. Is there something else that I could try? Found some relevant discussion here: JuliaDocs/Documenter.jl#1509

Comment on lines 32 to 49
DQConversion(unit=automatic; units_in_label=false)
Allows to plot arrays of DynamicQuantity objects into an axis.
# Arguments
- `unit=automatic`: sets the unit as conversion target. If left at automatic, the best unit will be chosen for all plots + values plotted to the axis (e.g. years for long periods, or km for long distances, or nanoseconds for short times).
- `units_in_label=true`: controls, whether plots are shown in the label_prefix of the axis labels, or in the tick labels
# Examples
```julia
using DynamicQuantities, CairoMakie
# DQConversion will get chosen automatically:
scatter(1:4, [1u"ns", 2u"ns", 3u"ns", 4u"ns"])
```
Fix unit to always use Meter & display unit in the xlabel:
```julia
# Temporary until this is upstreamed to Makie.jl
const DQConversion = Base.get_extension(DynamicQuantities, :DynamicQuantitiesMakieExt).DQConversion
dqc = DQConversion(us"m"; units_in_label=false)
scatter(1:4, [0.01u"km", 0.02u"km", 0.03u"km", 0.04u"km"]; axis=(dim2_conversion=dqc, xlabel="x (km)"))
```
Copy link
Member

Choose a reason for hiding this comment

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

Can these be spaced out a bit more

Copy link
Author

Choose a reason for hiding this comment

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

icweaver and others added 4 commits May 12, 2025 19:00
@icweaver
Copy link
Author

Hi @MilesCranmer, haha, I think I missed a notification too, sorry for the delay! Thanks for the comments, I think I have responded to all of them now for your review

return Float64(conv)
end

# TODO: Maybe only allow symbolic units to avoid bugs?
Copy link
Member

Choose a reason for hiding this comment

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

Status of this TODO?

Copy link
Author

@icweaver icweaver May 13, 2025

Choose a reason for hiding this comment

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

Just completed. I've also updated the docs accordingly ef2d0d8

using DynamicQuantities: UnionAbstractQuantity, ustrip, dimension
using TestItems: @testitem

import Makie as M
Copy link
Member

Choose a reason for hiding this comment

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

Just out of curiosity, is there a package higher up the plotting tools hierarchy that we should be overloading instead? For example there is RecipesBase.jl.

Copy link
Member

Choose a reason for hiding this comment

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

Make MakieCore.jl?

Copy link
Author

Choose a reason for hiding this comment

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

You know, the distinction between MakieCore and Makie proper when it comes to defining recipes and axis conversions is something that I've been trying to clarify for myself too

AFAICT, MakieCore is for recipes, while axis converts (like in this PR) require the whole megillah so things like expand_dimensions and create_dim_conversion will work. At least, that is the sense I get from the docs, which seem to specifically advise using Makie over MakieCore for defining package extensions

Interestingly, it looks like the long term plan may be to phase out MakieCore, but idk what the latest on that is MakieOrg/Makie.jl#3645 (comment)

@MilesCranmer
Copy link
Member

MilesCranmer commented May 13, 2025

One other question: @jkrumbiegel, do you think this functionality should live directly in Makie.jl, or is it better kept as an extension here?

I’m a bit concerned about maintainability if we keep it here, since it relies on internal, non-public APIs of Makie.jl (though it uses the public API of DQ). Typically, in scenarios where an interface primarily relies on the internal details of package A (Makie.jl), but the public API of package B (DQ.jl), it feels safer to have it reside directly within package A as it lives closer to the people who might edit such internals. However, if there are strong objections, I’m fine with keeping it here.

@@ -504,3 +511,60 @@ function my_func(x::UnionAbstractQuantity{T,D}) where {T,D}
return x / ustrip(x)
end
```

### Plotting
Copy link
Member

Choose a reason for hiding this comment

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

Do we need an explicit example of this? i.e., is it something that users would expect to "just work" anyways?

@jkrumbiegel
Copy link
Contributor

do you think this functionality should live directly in Makie.jl, or is it better kept as an extension here?

Good question, Makie depends directly on Unitful so I'm not sure if we shouldn't add DynamicQuantities as an extension. But I didn't implement unit support so I'll ping @SimonDanisch here

@SimonDanisch
Copy link

We're pretty tight on maintenance, so I'd prefer this to stay here.
I'm not sure what exactly you mean by non public interface, since on a quick look it seems to overload the interface as documented.
To be fair, it may change a bit in the next breaking release, but that may always happen, and we'll try to document & make it as non breaking as possible.

@MilesCranmer
Copy link
Member

MilesCranmer commented May 14, 2025

@SimonDanisch If @icweaver is happy to maintain the extension, could we move it into Makie? My main concern is that changes are far more likely on the Makie side than in DQ, so leaving it here risks silent breakage, whereas Makie’s CI would flag issues before release. It would remain an extension – no hard dependency – and we could add @icweaver to CODEOWNERS, meaning no extra maintenance for core Makie. @icweaver – would you be comfortable taking this on?

@SimonDanisch
Copy link

I'm not sure what you mean by no extra maintenance if our CIs start failing and we can only move on once someone else fixed the failure ;)

@SimonDanisch
Copy link

To be fair, if DQ is 100% stable, our CI should only fail if we actually change the interface.
Still, I'd rather not have more extensions in Makie.jl.
There's also the problem, that extensions trigger compilation if any indirect dependency brings in DQ, making Makie compilation take much longer for everyone.
So, I think DQ is the right place.
We do try to make sure we really only break APIs on a breaking release, so silent breakage shouldn't be as much of an issue.

@icweaver
Copy link
Author

Hi folks, I just wanted to start by thanking Miles for making, imo, a nice alternative to Unitful, and to the Makie team for putting together such a slick system for pluggable dim conversions

I'm happy to go wherever the code is and to help maintain things to the best of my ability. Thanks all again for your time =]

@MilesCranmer
Copy link
Member

@SimonDanisch Thanks for clarifying; those are fair concerns. Quickly addressing each:

  1. Compile time: Definitely important, but here there's no impact. The extension precompiles whenever Makie and DQ coexist, regardless of where the source lives, so moving it wouldn't affect users either way.
  2. CI complexity: Good point. To avoid overhead, we could easily run these DQ tests separately (e.g., non-blocking CI job or allow_failure), set up to alert @icweaver directly rather than core Makie.

The reason I'm suggesting Makie is mostly semantic: the extension depends primarily on Makie symbols rather than DQ's API. It feels more natural to keep it alongside those internals (similar to Unitful). That way, repo-wide edits (like renaming M.needs_tick_update_observable) would automatically update this extension, rather than us both discovering such issues after users report them.

If that approach seems reasonable, we can quickly set up a minimal-overhead solution. But I'm happy to defer if you'd still prefer keeping it in DQ; I just wanted to clearly lay out the rationale first.

@jkrumbiegel
Copy link
Contributor

If neither side currently wants to take on this glue code, one could also just make a package that implements the integration. It would not be as slick because you wouldn't get the benefits automatically with installing Makie and DynamicQuantities, but it's also much lower stakes. It could still be integrated into either DQ or Makie later.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Potential Makie.jl support?
5 participants