Skip to content

Commit

Permalink
fix heatmapshader for ranges (#4434)
Browse files Browse the repository at this point in the history
* fix heatmapshader for ranges

* fix more edge cases

* add tests for range arguments
  • Loading branch information
SimonDanisch authored Oct 2, 2024
1 parent 94b7273 commit 59456d3
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 10 deletions.
4 changes: 4 additions & 0 deletions ReferenceTests/src/tests/examples2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1543,6 +1543,10 @@ end
ax3, pl3 = heatmap(f[2, 1], Resampler(data2))
ax4, pl4 = heatmap(f[2, 2], Resampler(data2))
limits!(ax4, 3000, 3090, 3460, 3500)
heatmap(f[3, 1], (1000, 2000), (500, 1000), Resampler(data2))
ax = Axis(f[3, 2])
limits!(ax, (0, 1), (0, 1))
heatmap!(ax, (1, 2), (1, 2), Resampler(data2))
Colorbar(f[:, 3], pl1)
sleep(1) # give the async operations some time
f
Expand Down
40 changes: 30 additions & 10 deletions src/basic_recipes/datashader.jl
Original file line number Diff line number Diff line change
Expand Up @@ -564,10 +564,21 @@ function conversion_trait(::Type{<:Heatmap}, ::Resampler)
return HeatmapShaderConversion()
end

function Makie.MakieCore.types_for_plot_arguments(::Type{<:Heatmap}, ::HeatmapShaderConversion)
function MakieCore.types_for_plot_arguments(::Type{<:Heatmap}, ::HeatmapShaderConversion)
return Tuple{EndPoints{Float32},EndPoints{Float32},<:Resampler}
end

function data_limits(p::HeatmapShader)
x, y = p[1][], p[2][]
mini = Vec3f(x[1], y[1], 0)
widths = Vec3f(x[2] - x[1], y[2] - y[1], 0)
return Rect3f(mini, widths)
end

function boundingbox(p::HeatmapShader, space::Symbol=:data)
return apply_transform_and_model(p, data_limits(p))
end

function calculated_attributes!(::Type{Heatmap}, plot::HeatmapShader)
return
end
Expand Down Expand Up @@ -597,8 +608,9 @@ function resample_image(x, y, image, max_resolution, limits)
nrange = ranges[i]
si = imgsize[i]
indices = ((nrange .- data_min[i]) ./ data_width[i]) .* (si .- 1) .+ 1
resolution = round(Int, indices[2] - indices[1])
return LinRange(max(1, indices[1]), min(indices[2], si), min(resolution, max_resolution[i]))
resolution = max(2, round(Int, indices[2] - indices[1]))
len = min(resolution, max_resolution[i])
return LinRange(max(1, indices[1]), min(indices[2], si), len)
end
if isempty(x_index_range) || isempty(y_index_range)
return nothing
Expand All @@ -615,7 +627,7 @@ end

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

function empty_channel!(channel::Channel)
Expand Down Expand Up @@ -650,7 +662,9 @@ function Makie.plot!(p::HeatmapShader)

x, y = p.x, p.y
max_resolution = lift(p, p.values, scene.viewport) do resampler, viewport
return resampler.max_resolution isa Automatic ? widths(viewport) : ntuple(x-> resampler.max_resolution, 2)
res = resampler.max_resolution isa Automatic ? widths(viewport) :
ntuple(x -> resampler.max_resolution, 2)
return max.(res, 512) # Not sure why, but viewport can become (1, 1)
end
image = lift(x-> x.data, p, p.values)
image_area = lift(xy_to_rect, x, y; ignore_equal_values=true)
Expand All @@ -676,16 +690,22 @@ function Makie.plot!(p::HeatmapShader)
translate!(lp, 0, 0, -1)

first_downsample = resample_image(x[], y[], image[], max_resolution[], limits[])

# Make sure we don't trigger an event if the image/xy stays the same
args = map(arg -> lift(identity, p, arg; ignore_equal_values=true), (x, y, image, max_resolution))

CT = Tuple{typeof.(to_value.(args))..., typeof(limits_slow[])}
# We hide the image when outside of the limits, but we also want to correctly forward, p.visible
visible = Observable(p.visible[]; ignore_equal_values=true)
on(p, p.visible) do v
visible[] = v
return
end
# if nothing, the image is currently not visible in the limits chosen
if isnothing(first_downsample)
visible[] = false
first_downsample = EndPoints{Float32}(0, 1), EndPoints{Float32}(0, 1), zeros(Float32, 2, 2)
end
# Make sure we don't trigger an event if the image/xy stays the same
args = map(arg -> lift(identity, p, arg; ignore_equal_values=true), (x, y, image, max_resolution))

CT = Tuple{typeof.(to_value.(args))..., typeof(limits_slow[])}

# To actually run the resampling on another thread, we need another channel:
# To make this threadsafe, the observable needs to be triggered from the current thread
# So we need another channel for the result (unbuffered, so `put!` blocks while the image is being updated)
Expand Down
7 changes: 7 additions & 0 deletions test/conversions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ using Makie.MakieCore: plotfunc, plotfunc!, func2type

end

@testset "Heatmapshader with ranges" begin
hm = Heatmap(((0, 1), (0, 1), Resampler(zeros(4, 4))), Dict{Symbol,Any}())
hm.converted[1][] isa Makie.EndPoints{Float32}
hm.converted[2][] isa Makie.EndPoints{Float32}
hm.converted[3][].data == Resampler(zeros(4, 4)).data
end

@testset "changing input types" begin
input = Observable{Any}(decompose(Point2f, Circle(Point2f(0), 2f0)))
f, ax, pl = mesh(input)
Expand Down

0 comments on commit 59456d3

Please sign in to comment.