GLMakie: selecting points in log-log plot / sync dissimilar axes

Hello everyone,

I have a gui which involves a contour plot with log-log axes.
The goal is to be able to select some points from within the plot by clicking on in.

Everything works fine in the case of linear axes:

MRE for linear axes
using GLMakie

f = Figure()
ax = Axis(f[:,:],)

# make some data to plot 
x = range(0.001, 1, 100)
y = x
N = 100
σ = 15.0
μ = N / 2
z = [exp(-((x - μ)^2 + (y - μ)^2) / (2 * σ^2)) for y in 1:N, x in 1:N]

contour!(ax,x,y,z)

point = select_point(f.content[1].scene, marker=:circle)
# Update selection Observable by pushing the selected point to it
on(point) do _
    display(point)
end

but as soon as the axis get changed to log10:

MRE for log10 axes
using GLMakie

f = Figure()
ax = Axis(f[:,:], xscale = log10, yscale = log10)

# make some data to plot 
x = logrange(0.001, 1, 100)
y = x
N = 100
σ = 15.0
μ = N / 2
z = [exp(-((x - μ)^2 + (y - μ)^2) / (2 * σ^2)) for y in 1:N, x in 1:N]

contour!(ax,x,y,z)

point = select_point(f.content[1].scene, marker=:circle)
# Update selection Observable by pushing the selected point to it
on(point) do _
    display(point)
end

there are errors:

 Error in callback:
DomainError with -0.594361:
log10 was called with a negative real argument but will only return a complex result if called with a complex argument. Try log10(Complex(x)).

It seems that the select_point function returns log10.(point) rather than point, or something like that.


When I was working on this, about a year ago, I ended up using two sets of axes, one for displaying the actual log-scale values, and a linear one from 0 to 1 to selecting points from it.
This used to work, but it becomes a bit problematic now that I need to be able to zoom in and out, while keeping these axes synced.

Would there be any straightforward ways to deal with either of these issues?
That is, either select the points directly from the log-log axes without pesky errors;
or keep two axes with different values in sync.

1 Like

Sounds like a bug in Makie, should be easy enough to fix if the projection is just applied incorrectly somewhere?

Why not you supply absolute values inside log10() function?

z = abs.([exp(-((x - μ)^2 + (y - μ)^2) / (2 * σ^2)) for y in 1:N, x in 1:N])
  • Earlier i was also having same issue of evaluating log of negative values. I overcame this issue by writing safe_log() function for normalized input values.

    safe_log(x::Float64) = x > 0 ? log(x) : 1.0
    
  • Or Makie.jl should define a function that evaluates log10(-5) = -log10(5)

    safe_log(x::Float64) = x > 0 ? log(x) : x<0 ? -log(abs(x)) : NaN
    

But in this case z is already full of positive values, I don’t see how that changes things.

Thought so as well, but looking at the code for; the select_point function, it doesn’t seem obvious to me.
Perhaps it has to do with the line point = Observable([Point2f(0,0)])?

Would there be any alternatives in case that’s not straightforward to fix?

Sorry, Please find where and which variable is supplying negative value inlog10() ? Please paste full error message that you are getting. In error message we can see where log10() is getting error.

Yes, I’m trying to find that but it does not seem very obvious :slight_smile:

Here's the error
DomainError with -2.646445:
log10 was called with a negative real argument but will only return a complex result if called with a complex argument. Try log10(Complex(x)).
Stacktrace:
  [1] throw_complex_domainerror(f::Symbol, x::Float32)
    @ Base.Math ./math.jl:33
  [2] _log
    @ ./special/log.jl:330 [inlined]
  [3] log10(x::Float32)
    @ Base.Math ./special/log.jl:259
  [4] apply_transform
    @ ~/.julia/packages/Makie/ux0Te/src/layouting/transformation.jl:373 [inlined]
  [5] #995
    @ ~/.julia/packages/Makie/ux0Te/src/layouting/transformation.jl:369 [inlined]
  [6] iterate
    @ ./generator.jl:48 [inlined]
  [7] _collect(c::Vector{Point{2, Float32}}, itr::Base.Generator{Vector{Point{2, Float32}}, Makie.var"#995#996"{Tuple{typeof(log10), typeof(log10)}}}, ::Base.EltypeUnknown, isz::Base.HasShape{1})
    @ Base ./array.jl:811
  [8] collect_similar
    @ ./array.jl:720 [inlined]
  [9] map
    @ ./abstractarray.jl:3371 [inlined]
 [10] apply_transform
    @ ~/.julia/packages/Makie/ux0Te/src/layouting/transformation.jl:369 [inlined]
 [11] apply_transform
    @ ~/.julia/packages/Makie/ux0Te/src/layouting/transformation.jl:312 [inlined]
 [12] apply_transform_and_f32_conversion(float32convert::Makie.LinearScaling, transform_func::Tuple{typeof(log10), typeof(log10)}, model::StaticArraysCore.SMatrix{4, 4, Float64, 16}, data::Vector{Point{2, Float32}}, space::Symbol)
    @ Makie ~/.julia/packages/Makie/ux0Te/src/float32-scaling.jl:331
 [13] invokelatest(::Any, ::Any, ::Vararg{Any}; kwargs::@Kwargs{})
    @ Base ./essentials.jl:1055
 [14] invokelatest(::Any, ::Any, ::Vararg{Any})
    @ Base ./essentials.jl:1052
 [15] (::Observables.MapCallback)(value::Any)
    @ Observables ~/.julia/packages/Observables/YdEbO/src/Observables.jl:436
 [16] #invokelatest#2
    @ ./essentials.jl:1055 [inlined]
 [17] invokelatest
    @ ./essentials.jl:1052 [inlined]
 [18] notify
    @ ~/.julia/packages/Observables/YdEbO/src/Observables.jl:206 [inlined]
 [19] #70
    @ ./tuple.jl:692 [inlined]
 [20] BottomRF
    @ ./reduce.jl:86 [inlined]
 [21] afoldl
    @ ./operators.jl:553 [inlined]
 [22] _foldl_impl
    @ ./reduce.jl:68 [inlined]
 [23] foldl_impl
    @ ./reduce.jl:48 [inlined]
 [24] mapfoldl_impl
    @ ./reduce.jl:44 [inlined]
 [25] mapfoldl
    @ ./reduce.jl:175 [inlined]
 [26] foldl
    @ ./reduce.jl:198 [inlined]
 [27] foreach
    @ ./tuple.jl:692 [inlined]
 [28] (::Makie.var"#306#307"{UnionAll, Tuple{Observable{Vector{Point{2, Float32}}}}})(kw::Vector{Pair{Symbol, Any}}, args::Vector{Point{2, Float32}})
    @ Makie ~/.julia/packages/Makie/ux0Te/src/interfaces.jl:185
 [29] invokelatest(::Any, ::Any, ::Vararg{Any}; kwargs::@Kwargs{})
    @ Base ./essentials.jl:1055
 [30] invokelatest(::Any, ::Any, ::Vararg{Any})
    @ Base ./essentials.jl:1052
 [31] (::Observables.OnAny)(value::Any)
    @ Observables ~/.julia/packages/Observables/YdEbO/src/Observables.jl:420
 [32] #invokelatest#2
    @ ./essentials.jl:1055 [inlined]
 [33] invokelatest
    @ ./essentials.jl:1052 [inlined]
 [34] notify
    @ ~/.julia/packages/Observables/YdEbO/src/Observables.jl:206 [inlined]
 [35] setindex!
    @ ~/.julia/packages/Observables/YdEbO/src/Observables.jl:123 [inlined]
 [36] (::Makie.var"#1306#1308"{Bool, Scene, Scatter{Tuple{Vector{Point{2, Float32}}}}, Observable{Point{2, Float32}}, Observable{Vector{Point{2, Float32}}}, Observable{Bool}, Makie.Mouse.Button})(event::Makie.MouseButtonEvent)
    @ Makie ~/.julia/packages/Makie/ux0Te/src/interaction/interactive_api.jl:399
 [37] #invokelatest#2
    @ ./essentials.jl:1055 [inlined]
 [38] invokelatest
    @ ./essentials.jl:1052 [inlined]
 [39] notify
    @ ~/.julia/packages/Observables/YdEbO/src/Observables.jl:206 [inlined]
 [40] setindex!
    @ ~/.julia/packages/Observables/YdEbO/src/Observables.jl:123 [inlined]
 [41] (::GLMakie.var"#mousebuttons#170"{Observable{Makie.MouseButtonEvent}})(window::GLFW.Window, button::GLFW.MouseButton, action::GLFW.Action, mods::Int32)
    @ GLMakie ~/.julia/packages/GLMakie/87u59/src/events.jl:104
 [42] _MouseButtonCallbackWrapper(window::GLFW.Window, button::GLFW.MouseButton, action::GLFW.Action, mods::Int32)
    @ GLFW ~/.julia/packages/GLFW/wmoTL/src/callback.jl:43
 [43] PollEvents
    @ ~/.julia/packages/GLFW/wmoTL/src/glfw3.jl:702 [inlined]
 [44] pollevents(screen::GLMakie.Screen{GLFW.Window}, frame_state::Makie.TickState)
    @ GLMakie ~/.julia/packages/GLMakie/87u59/src/screen.jl:546
 [45] on_demand_renderloop(screen::GLMakie.Screen{GLFW.Window})
    @ GLMakie ~/.julia/packages/GLMakie/87u59/src/screen.jl:1033
 [46] renderloop(screen::GLMakie.Screen{GLFW.Window})
    @ GLMakie ~/.julia/packages/GLMakie/87u59/src/screen.jl:1061
 [47] (::GLMakie.var"#79#80"{GLMakie.Screen{GLFW.Window}})()
    @ GLMakie ~/.julia/packages/GLMakie/87u59/src/screen.jl:922
Observable(Float32[-2.646445, -1.0124221])
    0 => (::var"#3#4")(::Any) @ Main REPL[18]:2