DimensionalData.jl icon indicating copy to clipboard operation
DimensionalData.jl copied to clipboard

Plot of `Observable{DimArray{<:Quantity}}` does not update when using `@lift`

Open DylanMMarques opened this issue 8 months ago • 8 comments

Hello,

I found that plots of observable of DD do not update when using unitful and @lift. For example:

using GLMakie, Unitful, Unitful.DefaultSymbols, DimensionalData
dd = Observable(DimArray(collect(1V:1V:10V), X(1:10)))
dd_2 = @lift identity($dd)
fig, ax, plt = lines(dd_2)

Image

dd[] = DimArray(collect(10V:-1V:1V), X(1:10))
dd_2
Observable(Quantity{Int64, 𝐋^2 𝐌 𝐈^-1 𝐓^-3, Unitful.FreeUnits{(V,), 𝐋^2 𝐌 𝐈^-1 𝐓^-3, nothing}}        [10 V, 9 V, 8 V, 
7 V, 6 V, 5 V, 4 V, 3 V, 2 V, 1 V])

dd_2 is updated correctly but the plot does not update:

Image

I looked a little bit into the issue and the plot is updated correctly if not using Quantity:

using GLMakie, Unitful, Unitful.DefaultSymbols, DimensionalData
dd = Observable(DimArray(collect(1:1:10), X(1:10)))
dd_2 = @lift identity($dd)
fig, ax, plt = lines(dd_2)
dd[] = DimArray(collect(10:-1:1), X(1:10))

Also, it works fine if not using @lift. I.e.:

using GLMakie, Unitful, Unitful.DefaultSymbols, DimensionalData
dd = Observable(DimArray(collect(1:1:10), X(1:10)))
fig, ax, plt = lines(dd)
dd[] = DimArray(collect(10:-1:1), X(1:10))

Image

Thanks!

DylanMMarques avatar Mar 03 '25 18:03 DylanMMarques

This must be something to do with how Makie handles quantities... we don't do anything differently between regular numeric lookups and unitful

Do plots update if you manually uses observerable axes of units as the first two arguments? Thats all we are doing underneath.

@SimonDanisch does this make any sense to you?

rafaqz avatar Mar 08 '25 20:03 rafaqz

It seems to work if the axes are set manually:

using GLMakie, Unitful, Unitful.DefaultSymbols, DimensionalData
dd = Observable(DimArray(collect(1V:1V:10V), X(1:10)))
dd_2 = @lift identity($dd)
fig, ax, plt = lines((@lift lookup($dd_2, X)), (@lift parent($dd_2)))
lines(fig[1,2], dd_2)
dd[] .*= 2
notify(dd)
fig

Image

The package versions:

(jl_EVI1bY) pkg> status
Status `/tmp/jl_EVI1bY/Project.toml`
  [0703355e] DimensionalData v0.29.13
  [e9467ef8] GLMakie v0.11.3
  [1986cc42] Unitful v1.22.0

This issue seems to be related to a change in GLMakie from v0.10 to v0.11 as the code works with v0.10

(jl_xqozAK) pkg> status
Status `/tmp/jl_xqozAK/Project.toml`
  [0703355e] DimensionalData v0.29.13
⌃ [e9467ef8] GLMakie v0.10.18
  [1986cc42] Unitful v1.22.0
Info Packages marked with ⌃ have new versions available and may be upgradable.

using GLMakie, Unitful, Unitful.DefaultSymbols, DimensionalData
dd = Observable(DimArray(collect(1V:1V:10V), X(1:10)))
dd_2 = @lift identity($dd)
fig, ax, plt = lines((@lift lookup($dd_2, X)), (@lift parent($dd_2)))
lines(fig[1,2], dd_2)
dd[] .*= 2
notify(dd)
fig

Image

DylanMMarques avatar Mar 12 '25 19:03 DylanMMarques

Thank you that's an amazing MWE. Yes something must have changed in Makie 0.11 that we haven't followed.

If you have time for a PR you are probably better placed to fix the code here than I am at this point.

It may be something to do with the change in Makie recipe methods and expand_dimensions, maybe @asinghvi17 has some pointers too.

rafaqz avatar Mar 12 '25 19:03 rafaqz

Thanks, I will try to put a PR together for this

DylanMMarques avatar Mar 15 '25 08:03 DylanMMarques

Hi,

I looked a more into this and found a solution for this issue and #906.

using GLMakie, Unitful, Unitful.DefaultSymbols, DimensionalData
using GLMakie.Makie

function Makie.convert_arguments(P::Makie.PointBased, dd::DimensionalData.AbstractDimVector)
    return Makie.convert_arguments(P, parent(lookup(dd, 1)), parent(dd))
end
Makie.expand_dimensions(::Makie.PointBased, y::DimensionalData.AbstractDimVector) = return

function Makie.convert_arguments(P::Type{Makie.Series}, dd::DimensionalData.AbstractDimMatrix)
    # TO DO: this needs to be edited to select right dimension as in DimensionalDataMakieExt
    xs = parent(lookup(dd, 2))
    return Makie.convert_arguments(P, xs, parent(dd))
end
Makie.expand_dimensions(::Makie.Series, y::DimensionalData.AbstractDimMatrix) = return

fig = Figure()
dd = Observable(DimArray(collect(1V:1V:10V), X(10:10:100)))
dd_2 = @lift identity($dd)
lines(fig[1,1], (@lift lookup($dd_2, X)), (@lift parent($dd_2)))
lines(fig[1,2], dd_2)
dd[] .*= 2
notify(dd)

dimarray = rand(Y(3), X(7))
series(fig[2,1], dimarray)
series(fig[2,2], Observable(dimarray))
fig

Image

For testing, I removed DimensionalDataMakieExt, so the labels and etc should appear if the code is merged into DimensionalDataMakieExt.

I have limited experience with recipes in Makie so I want to check that this approach seems sensible before merging it with DimensionalDataMakieExt. Is there any reason that I am not aware of to not set Makie.expand_dimensions to return nothing?

DylanMMarques avatar Mar 16 '25 18:03 DylanMMarques

I don't really know what expand_dimension API rules are, is it documented in Makie? What do Makies internal objects return?

rafaqz avatar Mar 16 '25 19:03 rafaqz

I have not found much documentation about expand_dimension, other than:

help> Makie.expand_dimension
  Expands the dims for e.g. scatter(1:4) becoming scatter(1:4, 1:4) for 2D plots. We're separating this state from convertarguments, to better apply `dimconverts` before convert_arguments.

My current interpretation is that by defining the Makie extension as:

function Makie.convert_arguments(P::Makie.PointBased, dd::DimensionalData.AbstractDimVector)
    return Makie.convert_arguments(P, parent(lookup(dd, 1)), parent(dd))
end
Makie.expand_dimensions(P::Makie.PointBased, y::DimensionalData.AbstractDimVector) = return Makie.convert_arguments(P, y)

The conversion between DimArray to normal Array is performed when the conversion pipeline calls expand_dimension. I.e. the internal call of Makie to convert_arguments is already performed with the Array instead of DimArray.

By defining as:

function Makie.convert_arguments(P::Makie.PointBased, dd::DimensionalData.AbstractDimVector)
    return Makie.convert_arguments(P, parent(lookup(dd, 1)), parent(dd))
end
Makie.expand_dimensions(P::Makie.PointBased, y::DimensionalData.AbstractDimVector) = nothing

The conversion between DimArray to normal Array is performed when Makie call convert_arguments, which is as recommended by the plot recipes. See:

https://github.com/MakieOrg/Makie.jl/blob/d77c537884e139eb476cb552a8129ef270586e6b/src/interfaces.jl#L271 https://github.com/MakieOrg/Makie.jl/blob/d77c537884e139eb476cb552a8129ef270586e6b/src/interfaces.jl#L148

The behaviour of this issue makes sense with the comment https://github.com/MakieOrg/Makie.jl/blob/d77c537884e139eb476cb552a8129ef270586e6b/src/interfaces.jl#L159, as probably the Observable is only looking for changes in one of the outputs from expand_dimensions instead of both.

Looking into the tips for plots recipe in Makie, it seems that the default of Makie is for expand_dimensions to return nothing. The only exception is for AbstractArrays

DylanMMarques avatar Mar 16 '25 21:03 DylanMMarques

Thanks that all makes sense to me.

I guess all you can do now is go through the test plots and check they are working after the change.

Otherwise I'm happy to merge that

rafaqz avatar Mar 16 '25 21:03 rafaqz