Skip to content

Commit

Permalink
Merge branch 'master' into sd/try-relocatability
Browse files Browse the repository at this point in the history
  • Loading branch information
SimonDanisch authored Feb 4, 2025
2 parents 486f611 + 48ba5f2 commit ca1a3e2
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 45 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
- Added a tutorial on creating an inset plot [#4697](https://github.com/MakieOrg/Makie.jl/pull/4697)
- Enhanced Pattern support: Added general CairoMakie implementation, improved quality, added anchoring, added support in band, density, added tests & fixed various bugs and inconsistencies. [#4715](https://github.com/MakieOrg/Makie.jl/pull/4715)
- Fixed issue with `voronoiplot` for Voronoi tessellations with empty polygons [#4740](https://github.com/MakieOrg/Makie.jl/pull/4740)
- Added option `update_while_dragging=true` to Slider [#4745](https://github.com/MakieOrg/Makie.jl/pull/4745).
- Added option `lowres_background=true` to Resampler, and renamed `resolution` to `max_resolution` [#4745](https://github.com/MakieOrg/Makie.jl/pull/4745).
- Added option `throttle=0.0` to `async_latest`, to allow throttling while skipping latest updates [#4745](https://github.com/MakieOrg/Makie.jl/pull/4745).
- Fixed issue with `WGLMakie.voxels` not rendering on linux with firefox [#4756](https://github.com/MakieOrg/Makie.jl/pull/4756)

## [0.22.1] - 2025-01-17

Expand Down
6 changes: 4 additions & 2 deletions WGLMakie/assets/voxel.vert
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ const mat2x3 orientations[3] = mat2x3[](
);

void main() {
get_dummy(); // otherwise this doesn't render :)
// Without fetching the instanced data the shader wont render. On some
// systems (linux + firefox for example) this even needs to be used.
float zero = get_dummy();

/* How this works:
To simplify lets consider a 2d grid of pixel where the voxel surface would
Expand Down Expand Up @@ -66,7 +68,7 @@ void main() {
// Map instance id to dimension and index along dimension (0..N+1 or 0..2N)
ivec3 size = textureSize(voxel_id, 0);
int dim, id = gl_InstanceID, front = 1;
float gap = get_gap();
float gap = get_gap() + zero;
if (gap > 0.01) {
front = 1 - 2 * int(gl_InstanceID & 1);
if (id < 2 * size.z) {
Expand Down
4 changes: 2 additions & 2 deletions docs/src/reference/blocks/slider.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fig = Figure()
ax = Axis(fig[1, 1])
sl_x = Slider(fig[2, 1], range = 0:0.01:10, startvalue = 3)
sl_x = Slider(fig[2, 1], range = 0:0.01:10, startvalue = 3, update_while_dragging=false)
sl_y = Slider(fig[1, 2], range = 0:0.01:10, horizontal = false, startvalue = 6)
point = lift(sl_x.value, sl_y.value) do x, y
Expand All @@ -42,4 +42,4 @@ The functions [`labelslider!`](@ref) and [`labelslidergrid!`](@ref) are deprecat

```@attrdocs
Slider
```
```
85 changes: 67 additions & 18 deletions src/basic_recipes/datashader.jl
Original file line number Diff line number Diff line change
Expand Up @@ -522,39 +522,66 @@ function xy_to_rect(x, y)
end

"""
Resampler(matrix; resolution=automatic, method=Interpolations.Linear(), update_while_button_pressed=false)
Resampler(matrix; max_resolution=automatic, method=Interpolations.Linear(), update_while_button_pressed=false)
Creates a resampling type which can be used with `heatmap`, to display large images/heatmaps.
Passed can be any array that supports `array(linrange, linrange)`, as the interpolation interface from Interpolations.jl.
If the array doesn't support this, it will be converted to an interpolation object via: `Interpolations.interpolate(data, Interpolations.BSpline(method))`.
* `resolution` can be set to `automatic` to use the full resolution of the screen, or a tuple of the desired resolution.
* `max_resolution` can be set to `automatic` to use the full resolution of the screen, or a tuple/integer of the desired resolution.
* `method` is the interpolation method used, defaulting to `Interpolations.Linear()`.
* `update_while_button_pressed` will update the heatmap while a mouse button is pressed, useful for zooming/panning. Set it to false for e.g. WGLMakie to avoid updating while dragging.
* `lowres_background` will always show a low resolution background while the high resolution image is being calculated.
"""
struct Resampler{T<:AbstractMatrix{<:Union{Real,Colorant}}}
data::T
max_resolution::Union{Automatic, Bool}
max_resolution::Union{Automatic, Tuple{Int, Int}}
update_while_button_pressed::Bool
lowres_background::Bool
end

using Interpolations: Interpolations
using ImageBase: ImageBase

function Resampler(data; resolution=automatic, method=Interpolations.Linear(), update_while_button_pressed=false)
_to_resolution(::Automatic) = automatic
_to_resolution(x::Tuple{Int, Int}) = x
_to_resolution(x::Int) = (x, x)
_to_resolution(x) = error("Resolution must be automatic, a tuple or integer, got $x")

function Resampler(resampler::Resampler, new_data)
return Resampler(
new_data, resampler.max_resolution,
resampler.update_while_button_pressed,
resampler.lowres_background
)
end

function Resampler(
data;
max_resolution=automatic,
method=Interpolations.Linear(),
update_while_button_pressed=false,
lowres_background=true,
resolution=nothing
)
if resolution !== nothing
@warn "Resampler(data; resolution=...) got renamed to max_resolution, please update your code"
max_resolution = resolution
end
# Our interpolation interface is to do matrix(linrange, linrange)
# There doesn't seem to be an official trait for this,
# so we fall back to just check if this method applies:
# The type of LinRange has changed since Julia 1.6, so we need to construct it and use that
lr = LinRange(0, 1, 10)
res = _to_resolution(max_resolution)
if applicable(data, lr, lr)
return Resampler(data, resolution, update_while_button_pressed)
return Resampler(data, res, update_while_button_pressed, lowres_background)
else
dataf32 = el32convert(data)
ET = eltype(dataf32)
# Interpolations happily converts to Float64 here, but that's not desirable for e.g. RGB{N0f8}, or Float32 data
# Since we expect these arrays to be huge, this is no laughing matter ;)
interp = Interpolations.interpolate(eltype(ET), ET, data, Interpolations.BSpline(method))
return Resampler(interp, resolution, update_while_button_pressed)
return Resampler(interp, res, update_while_button_pressed, lowres_background)
end
end

Expand Down Expand Up @@ -627,13 +654,13 @@ end


function convert_arguments(::Type{Heatmap}, image::Resampler)
x, y, img = convert_arguments(Heatmap, image.data)
return (x, y, Resampler(img))
x, y, _ = convert_arguments(Heatmap, image.data)
return (x, y, image)
end

function convert_arguments(::Type{Heatmap}, x, y, image::Resampler)
x, y, img = convert_arguments(Heatmap, x, y, image.data)
return (EndPoints{Float32}(x...), EndPoints{Float32}(y...), Resampler(img))
x, y, _ = convert_arguments(Heatmap, x, y, image.data)
return (EndPoints{Float32}(x...), EndPoints{Float32}(y...), image)
end

function empty_channel!(channel::Channel)
Expand Down Expand Up @@ -666,10 +693,9 @@ function Makie.plot!(p::HeatmapShader)
return
end

x, y = p.x, p.y
x, y = map(identity, p, p.x; ignore_equal_values=true), map(identity, p, p.y; ignore_equal_values=true)
max_resolution = lift(p, p.values, scene.viewport) do resampler, viewport
res = resampler.max_resolution isa Automatic ? widths(viewport) :
ntuple(x -> resampler.max_resolution, 2)
res = resampler.max_resolution isa Automatic ? widths(viewport) : resampler.max_resolution
return max.(res, 512) # Not sure why, but viewport can become (1, 1)
end
image = lift(x-> x.data, p, p.values)
Expand All @@ -690,9 +716,19 @@ function Makie.plot!(p::HeatmapShader)
end
gpa = MakieCore.generic_plot_attributes(p)
cpa = MakieCore.colormap_attributes(p)
overview = overview_image
if !p.values[].lowres_background
# If we don't use the lowres background,
# We still display a background image, but with only the average of the image
# Leading to a background that blends relatively well with the high res image
overview = map(p, colorrange) do cr
Float32[mean(cr) for _ in 1:1, _ in 1:1]
end
end

# Create an overview image that gets shown behind, so we always see the "big picture"
# In case updating the detailed view takes longer
lp = image!(p, x, y, overview_image; gpa..., cpa..., interpolate=p.interpolate, colorrange=colorrange)
lp = image!(p, x, y, overview; gpa..., cpa..., interpolate=p.interpolate, colorrange=colorrange)
translate!(lp, 0, 0, -1)

first_downsample = resample_image(x[], y[], image[], max_resolution[], limits[])
Expand Down Expand Up @@ -752,11 +788,24 @@ function Makie.plot!(p::HeatmapShader)
# So we can skip this update
if isempty(do_resample) && isempty(image_to_obs)
x, y, image = x_y_image
visible[] = false
imgp[1] = x
imgp[2] = y
if !p.values[].lowres_background
# hiding without a background looks really bad
if !visible[]
visible[] = true
end
else
visible[] = false
end
if imgp[1][] != x
imgp[1] = x
end
if imgp[2][] != y
imgp[2] = y
end
imgp[3] = image
visible[] = true
if !visible[]
visible[] = true
end
end
end
bind(image_to_obs, task)
Expand Down
2 changes: 1 addition & 1 deletion src/ffmpeg-util.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ applies to `mp4`. Defaults to `yuv444p` for `profile = "high444"`.
means infinite looping. A value of `-1` turns off looping, and a value of `n > 0`
means `n` repetitions (i.e. the video is played `n+1` times) when supported by backend.
!!! warning
!!! warning
`profile` and `pixel_format` are only used when `format` is `"mp4"`; a warning will be issued if `format`
is not `"mp4"` and those two arguments are not `nothing`. Similarly, `compression` is only
valid when `format` is `"mp4"` or `"webm"`.
Expand Down
46 changes: 28 additions & 18 deletions src/interaction/observables.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,25 @@ function safe_off(o::Observables.AbstractObservable, f)
end
end

function on_latest(f, observable::Observable; update=false, spawn=false)
return on_latest(f, nothing, observable; update=update, spawn=spawn)
function on_latest(f, observable::Observable; update=false, spawn=false, throttle=0.0)
return on_latest(f, nothing, observable; update=update, spawn=spawn, throttle=throttle)
end

function on_latest(f, to_track, observable::Observable; update=false, spawn=false)

function on_latest(f, to_track, observable::Observable; update=false, spawn=false, throttle=0.0)
task_lock = Threads.ReentrantLock()
last_task = nothing
has_changed = Threads.Atomic{Bool}(false)
function run_f(new_value)
t1 = time()
try
f(new_value)
tdiff = time() - t1
if throttle > 0.0 && tdiff < throttle
sleep(throttle - tdiff)
end
catch e
@warn "Error in f" exception=(e, Base.catch_backtrace())
@warn "Error in f" exception = (e, Base.catch_backtrace())
end
# Since we skip updates completely while executing the above `f`
# We need to check after finishing, if the value has changed!
Expand All @@ -43,15 +50,17 @@ function on_latest(f, to_track, observable::Observable; update=false, spawn=fals
end

function on_callback(new_value)
if isnothing(last_task) || istaskdone(last_task)
if spawn
last_task = Threads.@spawn run_f(new_value)
lock(task_lock) do
if isnothing(last_task) || istaskdone(last_task)
if spawn
last_task = Threads.@spawn run_f(new_value)
else
last_task = Threads.@async run_f(new_value)
end
else
last_task = Threads.@async run_f(new_value)
has_changed[] = true
return nothing # Do nothing if working
end
else
has_changed[] = true
return # Do nothing if working
end
end

Expand All @@ -64,19 +73,20 @@ function on_latest(f, to_track, observable::Observable; update=false, spawn=fals
end
end

function onany_latest(f, observables...; update=false, spawn=false)
function onany_latest(f, observables...; update=false, spawn=false, throttle=0.0)
result = Observable{Any}(map(to_value, observables))
onany((args...)-> (result[] = args), observables...)
on_latest((args)-> f(args...), result; update=update, spawn=spawn)
return on_latest((args) -> f(args...), result; update=update, spawn=spawn, throttle=throttle)
end

function map_latest!(f, result::Observable, observables...; update=false, spawn=false)
function map_latest!(f, result::Observable, observables...; update=false, spawn=false, throttle=0.0)
callback = Observables.MapCallback(f, result, observables)
return onany_latest(callback, observables...; update=update, spawn=spawn)
return onany_latest(callback, observables...; update=update, spawn=spawn, throttle=throttle)
end

function map_latest(f, observables...; spawn=false, ignore_equal_values=false)
result = Observable(f(map(to_value, observables)...); ignore_equal_values=ignore_equal_values)
map_latest!(f, result, observables...; spawn=spawn)
function map_latest(f, observables...; spawn=false, ignore_equal_values=false, throttle=0.0)
first_value = f(map(to_value, observables)...)
result = Observable(first_value; ignore_equal_values=ignore_equal_values)
map_latest!(f, result, observables..., spawn=spawn, throttle=throttle)
return result
end
12 changes: 9 additions & 3 deletions src/makielayout/blocks/slider.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,16 @@ function initialize_block!(sl::Slider)
selected_index[] = closest_index(rng, sl.value[])
end

on(topscene, selected_index) do i
sl.value[] = sliderrange[][i]
onany(topscene, selected_index, dragging) do i, dragging
new_val = get(sliderrange[], i, nothing)
has_value = !isnothing(new_val)
has_changed = sl.value[] != new_val
drag_updates = sl.update_while_dragging[] || !dragging[]
if has_value && has_changed && drag_updates
sl.value[] = new_val
end
end

sl.value[] = sliderrange[][selected_index[]]
# initialize slider value with closest from range
selected_index[] = closest_index(sliderrange[], sl.startvalue[])

Expand Down
4 changes: 3 additions & 1 deletion src/makielayout/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,8 @@ end
horizontal::Bool = true
"The align mode of the slider in its parent GridLayout."
alignmode = Inside()
"If false, slider only updates value once dragging stops"
update_while_dragging = true
"Controls if the button snaps to valid positions or moves freely"
snap::Bool = true
end
Expand Down Expand Up @@ -1810,7 +1812,7 @@ end
"The color of y spine 3 opposite of the ticks"
yspinecolor_3 = :black
"The color of z spine 3 opposite of the ticks"
zspinecolor_3 = :black
zspinecolor_3 = :black
"Controls if the 4. Spines are created to close the outline box"
front_spines = false
"The color of x spine 4"
Expand Down

0 comments on commit ca1a3e2

Please sign in to comment.