diff --git a/CHANGELOG.md b/CHANGELOG.md index c6d083abcfd..a2ffffedc2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,10 @@ ## [Unreleased] - Allow `width` to be set per box in `boxplot` [#4447](https://github.com/MakieOrg/Makie.jl/pull/4447). +- Changed image, heatmap and surface picking indices to correctly index the relevant matrix arguments. [#4459](https://github.com/MakieOrg/Makie.jl/pull/4459) - Improved performance of `record` by avoiding unnecessary copying in common cases [#4475](https://github.com/MakieOrg/Makie.jl/pull/4475). +- Fix usage of `AggMean()` and other aggregations operating on 3d data for `datashader` [#4346](https://github.com/MakieOrg/Makie.jl/pull/4346). +- Fixed `pick(scene, rect2)` in WGLMakie [#4488](https://github.com/MakieOrg/Makie.jl/pull/4488) ## [0.21.14] - 2024-10-11 @@ -49,7 +52,7 @@ - `plotfunc()` and `func2type()` support functions ending with `!` [#4275](https://github.com/MakieOrg/Makie.jl/pull/4275). - Fixed Boundserror in clipped multicolor lines in CairoMakie [#4313](https://github.com/MakieOrg/Makie.jl/pull/4313) - Fix float precision based assertions error in GLMakie.volume [#4311](https://github.com/MakieOrg/Makie.jl/pull/4311) - - Support images with reversed axes [#4338](https://github.com/MakieOrg/Makie.jl/pull/4338) +- Support images with reversed axes [#4338](https://github.com/MakieOrg/Makie.jl/pull/4338) ## [0.21.9] - 2024-08-27 diff --git a/CairoMakie/test/runtests.jl b/CairoMakie/test/runtests.jl index b131a1dff32..01b2f4283a9 100644 --- a/CairoMakie/test/runtests.jl +++ b/CairoMakie/test/runtests.jl @@ -184,7 +184,8 @@ excludes = Set([ "Textured meshscatter", # not yet implemented "Voxel - texture mapping", # not yet implemented "Miter Joints for line rendering", # CairoMakie does not show overlap here - "Scatter with FastPixel" # almost works, but scatter + markerspace=:data seems broken for 3D + "Scatter with FastPixel", # almost works, but scatter + markerspace=:data seems broken for 3D + "picking", # Not implemented ]) functions = [:volume, :volume!, :uv_mesh] diff --git a/GLMakie/assets/shader/heatmap.frag b/GLMakie/assets/shader/heatmap.frag index 29171539322..67b43caa1b2 100644 --- a/GLMakie/assets/shader/heatmap.frag +++ b/GLMakie/assets/shader/heatmap.frag @@ -52,5 +52,5 @@ vec4 get_color(sampler2D intensity, vec2 uv, vec2 color_norm, sampler1D color_ma void main(){ vec4 color = get_color(intensity, o_uv, color_norm, color_map); - write2framebuffer(color, uvec2(o_objectid.x, 0)); + write2framebuffer(color, uvec2(o_objectid.x, o_objectid.y)); } diff --git a/GLMakie/assets/shader/heatmap.vert b/GLMakie/assets/shader/heatmap.vert index 2108f0c6111..42390a18133 100644 --- a/GLMakie/assets/shader/heatmap.vert +++ b/GLMakie/assets/shader/heatmap.vert @@ -49,7 +49,7 @@ void main(){ vec2 index01 = vec2(index2D) / (vec2(dims)-1.0); o_uv = vec2(index01.x, 1.0 - index01.y); - o_objectid = uvec2(objectid, index1D+1); + o_objectid = uvec2(objectid, 1 + index); float x = texelFetch(position_x, index2D.x, 0).x; float y = texelFetch(position_y, index2D.y, 0).x; diff --git a/GLMakie/assets/shader/mesh.frag b/GLMakie/assets/shader/mesh.frag index 077a390339e..2e9bbfda2ef 100644 --- a/GLMakie/assets/shader/mesh.frag +++ b/GLMakie/assets/shader/mesh.frag @@ -7,6 +7,9 @@ struct Nothing{ //Nothing type, to encode if some variable doesn't contain any d // Sets which shading procedures to use {{shading}} +// Selects what is used to calculate the picked index +{{picking_mode}} + in vec3 o_world_normal; in vec3 o_view_normal; in vec4 o_color; @@ -125,5 +128,13 @@ void main(){ #ifndef NO_SHADING color.rgb = illuminate(normalize(o_world_normal), color.rgb); #endif + +#ifdef PICKING_INDEX_FROM_UV + ivec2 size = textureSize(image, 0); + ivec2 jl_idx = clamp(ivec2(o_uv * size), ivec2(0), size-1); + uint idx = uint(jl_idx.x + jl_idx.y * size.x); + write2framebuffer(color, uvec2(o_id.x, uint(1) + idx)); +#else write2framebuffer(color, o_id); +#endif } diff --git a/GLMakie/assets/shader/surface.vert b/GLMakie/assets/shader/surface.vert index 0a5021a0d70..31fd8478f33 100644 --- a/GLMakie/assets/shader/surface.vert +++ b/GLMakie/assets/shader/surface.vert @@ -163,7 +163,7 @@ void main() vec3 pos; {{position_calc}} - o_id = uvec2(objectid, index1D+1); + o_id = uvec2(objectid, 0); // calculated from uv in mesh.frag o_InstanceID = 0; // match up with mesh o_uv = apply_uv_transform(uv_transform, vec2(index01.x, 1 - index01.y)); diff --git a/GLMakie/assets/shader/voxel.frag b/GLMakie/assets/shader/voxel.frag index c9c6873210e..35861af89d9 100644 --- a/GLMakie/assets/shader/voxel.frag +++ b/GLMakie/assets/shader/voxel.frag @@ -146,7 +146,7 @@ void main() // TODO: index into 3d array ivec3 size = ivec3(textureSize(voxel_id, 0).xyz); - ivec3 idx = ivec3(o_uvw * size); + ivec3 idx = clamp(ivec3(o_uvw * size), ivec3(0), size-1); int lin = 1 + idx.x + size.x * (idx.y + size.y * idx.z); // draw diff --git a/GLMakie/src/drawing_primitives.jl b/GLMakie/src/drawing_primitives.jl index 9147fce4b6d..f364b2e3468 100644 --- a/GLMakie/src/drawing_primitives.jl +++ b/GLMakie/src/drawing_primitives.jl @@ -708,6 +708,7 @@ function draw_image(screen::Screen, scene::Scene, plot::Union{Heatmap, Image}) else gl_attributes[:image] = Texture(pop!(gl_attributes, :color); minfilter=interp) end + gl_attributes[:picking_mode] = "#define PICKING_INDEX_FROM_UV" return draw_mesh(screen, gl_attributes) end end diff --git a/GLMakie/src/glshaders/mesh.jl b/GLMakie/src/glshaders/mesh.jl index 3a2620e0095..49808f299e2 100644 --- a/GLMakie/src/glshaders/mesh.jl +++ b/GLMakie/src/glshaders/mesh.jl @@ -60,6 +60,7 @@ function draw_mesh(screen, data::Dict) "lighting.frag", view = Dict( "shading" => light_calc(shading), + "picking_mode" => to_value(get(data, :picking_mode, "")), "MAX_LIGHTS" => "#define MAX_LIGHTS $(screen.config.max_lights)", "MAX_LIGHT_PARAMETERS" => "#define MAX_LIGHT_PARAMETERS $(screen.config.max_light_parameters)", "buffers" => output_buffers(screen, to_value(transparency)), diff --git a/GLMakie/src/glshaders/surface.jl b/GLMakie/src/glshaders/surface.jl index 8fb0b04287a..a868b48bd39 100644 --- a/GLMakie/src/glshaders/surface.jl +++ b/GLMakie/src/glshaders/surface.jl @@ -153,6 +153,7 @@ function draw_surface(screen, main, data::Dict) "position_calc" => position_calc(position, position_x, position_y, position_z, Texture), "normal_calc" => normal_calc(normal, to_value(invert_normals)), "shading" => light_calc(shading), + "picking_mode" => "#define PICKING_INDEX_FROM_UV", "MAX_LIGHTS" => "#define MAX_LIGHTS $(screen.config.max_lights)", "MAX_LIGHT_PARAMETERS" => "#define MAX_LIGHT_PARAMETERS $(screen.config.max_light_parameters)", "buffers" => output_buffers(screen, to_value(transparency)), diff --git a/GLMakie/src/picking.jl b/GLMakie/src/picking.jl index 573a52dd26b..47270f9df8e 100644 --- a/GLMakie/src/picking.jl +++ b/GLMakie/src/picking.jl @@ -14,7 +14,7 @@ function pick_native(screen::Screen, rect::Rect2i) rw, rh = widths(rect) w, h = size(screen.root_scene) ppu = screen.px_per_unit[] - if rx > 0 && ry > 0 && rx + rw <= w && ry + rh <= h + if rx >= 0 && ry >= 0 && rx + rw <= w && ry + rh <= h rx, ry, rw, rh = round.(Int, ppu .* (rx, ry, rw, rh)) sid = zeros(SelectionID{UInt32}, rw, rh) glReadPixels(rx, ry, rw, rh, buff.format, buff.pixeltype, sid) diff --git a/Project.toml b/Project.toml index 40c25969914..d4810fd5d46 100644 --- a/Project.toml +++ b/Project.toml @@ -31,6 +31,7 @@ ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19" InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" +InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" Isoband = "f1662d9f-8043-43de-a69a-05efc1cc6ff4" KernelDensity = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b" LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" diff --git a/ReferenceTests/src/tests/examples2d.jl b/ReferenceTests/src/tests/examples2d.jl index e69c726558b..4fa7d400b79 100644 --- a/ReferenceTests/src/tests/examples2d.jl +++ b/ReferenceTests/src/tests/examples2d.jl @@ -1487,12 +1487,39 @@ end fig = Figure() xs = vcat([fill(i, i * 1000) for i in 1:4]...) ys = vcat(RNG.randn(6000), RNG.randn(4000) * 2) - for (i, scale) in enumerate([:area, :count, :width]) - ax = Axis(fig[i, 1]) - violin!(ax, xs, ys; scale, show_median=true) - Makie.xlims!(0.2, 4.8) - ax.title = "scale=:$(scale)" - end + ax, p = violin(fig[1, 1], xs, ys; scale = :area, show_median=true) + Makie.xlims!(0.2, 4.8); ax.title = "scale=:area" + ax, p = violin(fig[2, 1], xs, ys; scale = :count, mediancolor = :red, medianlinewidth = 5) + Makie.xlims!(0.2, 4.8); ax.title = "scale=:count" + ax, p = violin(fig[3, 1], xs, ys; scale = :width, show_median=true, mediancolor = :orange, medianlinewidth = 5) + Makie.xlims!(0.2, 4.8); ax.title = "scale=:width" + fig +end + +@reference_test "Violin" begin + fig = Figure() + + categories = vcat(fill(1, 300), fill(2, 300), fill(3, 300)) + values = vcat(RNG.randn(300), (1.5 .* RNG.rand(300)).^2, -(1.5 .* RNG.rand(300)).^2) + violin(fig[1, 1], categories, values) + + dodge = RNG.rand(1:2, 900) + violin(fig[1, 2], categories, values, dodge = dodge, + color = map(d->d==1 ? :yellow : :orange, dodge), + strokewidth = 2, strokecolor = :black, gap = 0.1, dodge_gap = 0.5 + ) + + violin(fig[2, 1], categories, values, orientation = :horizontal, + color = :gray, side = :left + ) + + violin!(categories, values, orientation = :horizontal, + color = :yellow, side = :right, strokewidth = 2, strokecolor = :black, + weights = abs.(values) + ) + + # TODO: test bandwidth, boundary + fig end @@ -1531,6 +1558,32 @@ end f end +@reference_test "Datashader AggCount" begin + data = [RNG.randn(Point2f, 10_000); (Ref(Point2f(1, 1)) .+ 0.3f0 .* RNG.randn(Point2f, 10_000))] + f = Figure() + ax = Axis(f[1, 1]) + datashader!(ax, data; async = false) + ax2 = Axis(f[1, 2]) + datashader!(ax2, data; async = false, binsize = 3) + ax3 = Axis(f[2, 1]) + datashader!(ax3, data; async = false, operation = xs -> log10.(xs .+ 1)) + ax4 = Axis(f[2, 2]) + datashader!(ax4, data; async = false, point_transform = -) + f +end + +@reference_test "Datashader AggMean" begin + with_z(p2) = Point3f(p2..., cos(p2[1]) * sin(p2[2])) + data2d = RNG.randn(Point2f, 100_000) + data3d = map(with_z, data2d) + f = Figure() + ax = Axis(f[1, 1]) + datashader!(ax, data3d; agg = Makie.AggMean(), operation = identity, async = false) + ax2 = Axis(f[1, 2]) + datashader!(ax2, data3d; agg = Makie.AggMean(), operation = identity, async = false, binsize = 3) + f +end + @reference_test "Heatmap Shader" begin data = Makie.peaks(10_000) data2 = map(data) do x @@ -1551,3 +1604,220 @@ end sleep(1) # give the async operations some time f end + +@reference_test "boxplot" begin + fig = Figure() + + categories = vcat(fill(1, 300), fill(2, 300), fill(3, 300)) + values = RNG.randn(900) .+ range(-1, 1, length=900) + boxplot(fig[1, 1], categories, values) + + dodge = RNG.rand(1:2, 900) + boxplot(fig[1, 2], categories, values, dodge = dodge, show_notch = true, + color = map(d->d==1 ? :blue : :red, dodge), + outliercolor = RNG.rand([:red, :green, :blue, :black, :orange], 900) + ) + + ax_vert = Axis(fig[2,1]; + xlabel = "categories", + ylabel = "values", + xticks = (1:3, ["one", "two", "three"]) + ) + ax_horiz = Axis(fig[2,2]; + xlabel="values", + ylabel="categories", + yticks=(1:3, ["one", "two", "three"]) + ) + + weights = 1.0 ./ (1.0 .+ abs.(values)) + boxplot!(ax_vert, categories, values, orientation=:vertical, weights = weights, + gap = 0.5, + show_notch = true, notchwidth = 0.75, + markersize = 5, strokewidth = 2.0, strokecolor = :black, + medianlinewidth = 5, mediancolor = :orange, + whiskerwidth = 1.0, whiskerlinewidth = 3, whiskercolor = :green, + outlierstrokewidth = 1.0, outlierstrokecolor = :red, + width = 1.5, + + ) + boxplot!(ax_horiz, categories, values; orientation=:horizontal) + + fig +end + +@reference_test "crossbar" begin + fig = Figure() + + xs = [1, 1, 2, 2, 3, 3] + ys = RNG.rand(6) + ymins = ys .- 1 + ymaxs = ys .+ 1 + dodge = [1, 2, 1, 2, 1, 2] + + crossbar(fig[1, 1], xs, ys, ymins, ymaxs, dodge = dodge, show_notch = true) + + crossbar(fig[1, 2], xs, ys, ymins, ymaxs, + dodge = dodge, dodge_gap = 0.25, + gap = 0.05, + midlinecolor = :blue, midlinewidth = 5, + show_notch = true, notchwidth = 0.3, + notchmin = ys .- (0.05:0.05:0.3), notchmax = ys .+ (0.3:-0.05:0.05), + strokewidth = 2, strokecolor = :black, + orientation = :horizontal, color = :lightblue + ) + fig +end + +@reference_test "ecdfplot" begin + f = Figure(size = (500, 250)) + + x = RNG.randn(200) + ecdfplot(f[1, 1], x, color = (:blue, 0.3)) + ecdfplot!(x, color = :red, npoints=10, step = :pre, linewidth = 3) + ecdfplot!(x, color = :orange, npoints=10, step = :center, linewidth = 3) + ecdfplot!(x, color = :green, npoints=10, step = :post, linewidth = 3) + + w = @. x^2 * (1 - x)^2 + ecdfplot(f[1, 2], x) + ecdfplot!(x; weights = w, color=:orange) + + f +end + +@reference_test "qqnorm" begin + fig = Figure() + xs = 2 .* RNG.randn(10) .+ 3 + qqnorm(fig[1, 1], xs, qqline = :fitrobust, strokecolor = :cyan, strokewidth = 2) + qqnorm(fig[1, 2], xs, qqline = :none, markersize = 15, marker = Rect, markercolor = :red) + qqnorm(fig[2, 1], xs, qqline = :fit, linestyle = :dash, linewidth = 6) + qqnorm(fig[2, 2], xs, qqline = :identity, color = :orange) + fig +end + +@reference_test "qqplot" begin + fig = Figure() + xs = 2 .* RNG.randn(10) .+ 3; ys = RNG.randn(10) + qqplot(fig[1, 1], xs, ys, qqline = :fitrobust, strokecolor = :cyan, strokewidth = 2) + qqplot(fig[1, 2], xs, ys, qqline = :none, markersize = 15, marker = Rect, markercolor = :red) + qqplot(fig[2, 1], xs, ys, qqline = :fit, linestyle = :dash, linewidth = 6) + qqplot(fig[2, 2], xs, ys, qqline = :identity, color = :orange) + fig +end + +@reference_test "rainclouds" begin + Makie.RAINCLOUD_RNG[] = RNG.STABLE_RNG + data = RNG.randn(1000) + data[1:200] .+= 3 + data[201:500] .-= 3 + data[501:end] .= 3 .* abs.(data[501:end]) .- 3 + labels = vcat(fill("red", 500), fill("green", 500)) + + fig = Figure() + rainclouds(fig[1, 1], labels, data, plot_boxplots = false, cloud_width = 2.0, + markersize = 5.0) + rainclouds(fig[1, 2], labels, data, color = labels, orientation = :horizontal, cloud_width = 2.0) + rainclouds(fig[2, 1], labels, data, clouds = hist, hist_bins = 30, boxplot_nudge = 0.1, + center_boxplot = false, boxplot_width = 0.2, whiskerwidth = 1.0, strokewidth = 3.0) + rainclouds(fig[2, 2], labels, data, color = labels, side = :right, violin_limits = extrema) + fig +end + +@reference_test "series" begin + fig = Figure() + data = cumsum(RNG.randn(4, 21), dims = 2) + + ax, sp = series(fig[1, 1], data, labels=["label $i" for i in 1:4], + linewidth = 4, linestyle = :dot, markersize = 15, solid_color = :black) + axislegend(ax, position = :lt) + + ax, sp = series(fig[2, 1], data, labels=["label $i" for i in 1:4], markersize = 10.0, + marker = Circle, markercolor = :transparent, strokewidth = 2.0, strokecolor = :black) + axislegend(ax, position = :lt) + + fig +end + +@reference_test "stairs" begin + f = Figure() + + xs = LinRange(0, 4pi, 21) + ys = sin.(xs) + + stairs(f[1, 1], xs, ys) + stairs(f[2, 1], xs, ys; step=:post, color=:blue, linestyle=:dash) + stairs(f[3, 1], xs, ys; step=:center, color=:red, linestyle=:dot) + + f +end + +@reference_test "stem" begin + f = Figure() + + xs = LinRange(0, 4pi, 30) + stem(f[1, 1], xs, sin.(xs)) + + stem(f[1, 2], xs, sin, + offset = 0.5, trunkcolor = :blue, marker = :rect, + stemcolor = :red, color = :orange, + markersize = 15, strokecolor = :red, strokewidth = 3, + trunklinestyle = :dash, stemlinestyle = :dashdot) + + stem(f[2, 1], xs, sin.(xs), + offset = LinRange(-0.5, 0.5, 30), + color = LinRange(0, 1, 30), colorrange = (0, 0.5), + trunkcolor = LinRange(0, 1, 30), trunkwidth = 5) + + ax, p = stem(f[2, 2], 0.5xs, 2 .* sin.(xs), 2 .* cos.(xs), + offset = Point3f.(0.5xs, sin.(xs), cos.(xs)), + stemcolor = LinRange(0, 1, 30), stemcolormap = :Spectral, stemcolorrange = (0, 0.5)) + + center!(ax.scene) + zoom!(ax.scene, 0.8) + ax.scene.camera_controls.settings[:center] = false + + f +end + +@reference_test "waterfall" begin + y = [6, 4, 2, -8, 3, 5, 1, -2, -3, 7] + + fig = Figure() + waterfall(fig[1, 1], y) + waterfall(fig[1, 2], y, show_direction = true, marker_pos = :cross, + marker_neg = :hline, direction_color = :yellow) + + colors = Makie.wong_colors() + x = repeat(1:2, inner=5) + group = repeat(1:5, outer=2) + + waterfall(fig[2, 1], x, y, dodge = group, color = colors[group], + show_direction = true, show_final = true, final_color=(colors[6], 1//3), + dodge_gap = 0.1, gap = 0.05) + + x = repeat(1:5, outer=2) + group = repeat(1:2, inner=5) + + waterfall(fig[2, 2], x, y, dodge = group, color = colors[group], + show_direction = true, stack = :x, show_final = true) + + fig +end + +@reference_test "ablines + hvlines + hvspan" begin + f = Figure() + + ax = Axis(f[1, 1]) + hspan!(ax, -1, -0.9, color = :lightblue, alpha = 0.5, strokewidth = 2, strokecolor = :black) + hspan!(ax, 0.9, 1, xmin = 0.2, xmax = 0.8) + vspan!(ax, -1, -0.9) + vspan!(ax, 0.9, 1, ymin = 0.2, ymax = 0.8, strokecolor = RGBf(0,1,0.1), strokewidth = 3) + + ablines!([0.3, 0.7], [-0.2, 0.2], color = :orange, linewidth = 4, linestyle = :dash) + + hlines!(ax, -0.8) + hlines!(ax, 0.8, xmin = 0.2, xmax = 0.8) + vlines!(ax, -0.8, color = :green, linewidth = 3) + vlines!(ax, 0.8, ymin = 0.2, ymax = 0.8, color = :red, linewidth = 3, linestyle = :dot) + + f +end diff --git a/ReferenceTests/src/tests/examples3d.jl b/ReferenceTests/src/tests/examples3d.jl index 63c096f2898..49b5025051e 100644 --- a/ReferenceTests/src/tests/examples3d.jl +++ b/ReferenceTests/src/tests/examples3d.jl @@ -706,3 +706,17 @@ end ls2, pl = surface(f[1, 2], Makie.peaks(20); interpolate=false, axis=(; show_axis=false)) f end + +@reference_test "volumeslices" begin + r = range(-1, 1, length = 10) + data = RNG.rand(10,10,10) + + fig = Figure() + volumeslices(fig[1, 1], r, r, r, data) + a, p = volumeslices(fig[1, 2], r, r, r, data, bbox_visible = false, colormap = :RdBu, + colorrange = (0.2, 0.8), lowclip = :black, highclip = :green) + p.update_xz[](3) + p.update_yz[](4) + p.update_xy[](10) + fig +end diff --git a/ReferenceTests/src/tests/figures_and_makielayout.jl b/ReferenceTests/src/tests/figures_and_makielayout.jl index b61da7b1348..2e3a28d2ca2 100644 --- a/ReferenceTests/src/tests/figures_and_makielayout.jl +++ b/ReferenceTests/src/tests/figures_and_makielayout.jl @@ -409,5 +409,39 @@ end Makie.Checkbox(f[2, 3], checked = true, checkmarkcolor_checked = :black) Makie.Checkbox(f[2, 4], checked = false, checkboxcolor_unchecked = :yellow) Makie.Checkbox(f[2, 5], checked = true, checkboxcolor_checked = :orange) + f +end + +@reference_test "Button - Slider - Toggle - Textbox" begin + f = Figure(size = (500, 250)) + Makie.Button(f[1, 1:2]) + Makie.Button(f[2, 1:2], buttoncolor = :orange, cornerradius = 20, + strokecolor = :red, strokewidth = 2, # TODO: allocate space for this + fontsize = 16, labelcolor = :blue) + + IntervalSlider(f[1, 3]) + sl = IntervalSlider(f[2, 3], range = 0:100, linewidth = 20, + color_inactive = :orange, color_active_dimmed = :lightgreen) + Makie.set_close_to!(sl, 30, 70) + + Toggle(f[3, 1]) + Toggle(f[4, 1], framecolor_inactive = :lightblue, rimfraction = 0.6) + Toggle(f[3, 2], active = true) + Toggle(f[4, 2], active = true, framecolor_inactive = :lightblue, + framecolor_active = :yellow, rimfraction = 0.6) + + Makie.Slider(f[3, 3]) + sl = Makie.Slider(f[4, 3], range = 0:100, linewidth = 20, color_inactive = :cyan, + color_active_dimmed = :lightgreen) + Makie.set_close_to!(sl, 30) + + gl = GridLayout(f[5, 1:3]) + Textbox(gl[1, 1]) + Textbox(gl[1, 2], bordercolor = :red, cornerradius = 0, + placeholder = "test string", fontsize = 16, textcolor_placeholder = :blue) + tb = Textbox(gl[1, 3], bordercolor = :black, cornerradius = 20, + fontsize =10, textcolor = :red, boxcolor = :lightblue) + Makie.set!(tb, "some string") + f end \ No newline at end of file diff --git a/ReferenceTests/src/tests/generic_components.jl b/ReferenceTests/src/tests/generic_components.jl new file mode 100644 index 00000000000..1e55b31af39 --- /dev/null +++ b/ReferenceTests/src/tests/generic_components.jl @@ -0,0 +1,291 @@ +# For things that aren't as plot related + +@reference_test "picking" begin + scene = Scene(size = (230, 370)) + campixel!(scene) + + sc1 = scatter!(scene, [20, NaN, 20], [20, NaN, 50], marker = Rect, markersize = 20) + sc2 = scatter!(scene, [50, 50, 20, 50], [20, 50, 80, 80], marker = Circle, markersize = 20, color = [:red, :red, :transparent, :red]) + ms = meshscatter!(scene, [20, NaN, 50], [110, NaN, 110], markersize = 10) + l1 = lines!(scene, [20, 50, 50, 20, 20], [140, 140, 170, 170, 140], linewidth = 10) + l2 = lines!(scene, [20, 50, NaN, 20, 50], [200, 200, NaN, 230, 230], linewidth = 20, linecap = :round) + ls = linesegments!(scene, [20, 50, NaN, NaN, 20, 50], [260, 260, NaN, NaN, 290, 290], linewidth = 20, linecap = :square) + tp = text!(scene, Point2f[(15, 320), (NaN, NaN), (15, 350)], text = ["█ ●", "hi", "●"], fontsize = 20, align = (:left, :center)) + t = tp.plots[1] + + i = image!(scene, 80..140, 20..50, rand(RGBf, 3, 2), interpolate = false) + s = surface!(scene, 80..140, 80..110, rand(3, 2), interpolate = false) + hm = heatmap!(scene, [80, 110, 140], [140, 170], [1 4; 2 5; 3 6]) + # mesh coloring should match triangle placements + m = mesh!(scene, Point2f.([80, 80, 110, 110], [200, 230, 200, 230]), [1 2 3; 2 3 4], color = [1,1,1,2]) + vx = voxels!(scene, [65, 155], [245, 305], [-1, 1], reshape([1,2,3,4,5,6], (3,2,1)), shading = NoShading) + vol = volume!(scene, 80..110, 320..350, -1..1, rand(2,2,2)) + + # reversed axis + i2 = image!(scene, 210..180, 20..50, rand(RGBf, 2, 2)) + s2 = surface!(scene, 210..180, 80..110, rand(2, 2)) + hm2 = heatmap!(scene, [210, 180], [140, 170], [1 2; 3 4]) + + scene # for easy reviewing of the plot + + # render one frame to generate picking texture + colorbuffer(scene, px_per_unit = 2); + + # verify that heatmap path is used for heatmaps + if Symbol(Makie.current_backend()) == :WGLMakie + @test length(faces(WGLMakie.create_shader(scene, hm).vertexarray)) > 2 + @test length(faces(WGLMakie.create_shader(scene, hm2).vertexarray)) > 2 + elseif Symbol(Makie.current_backend()) == :GLMakie + screen = scene.current_screens[1] + for plt in (hm, hm2) + robj = screen.cache[objectid(plt)] + shaders = robj.vertexarray.program.shader + names = [string(shader.name) for shader in shaders] + @test any(name -> endswith(name, "heatmap.vert"), names) && any(name -> endswith(name, "heatmap.frag"), names) + end + else + error("picking tests are only meant to run on GLMakie & WGLMakie") + end + + # raw picking tests + + @testset "scatter" begin + @test pick(scene, Point2f(20, 20)) == (sc1, 1) + @test pick(scene, Point2f(29, 59)) == (sc1, 3) + @test pick(scene, Point2f(57, 58)) == (nothing, 0) # maybe fragile + @test pick(scene, Point2f(57, 13)) == (sc2, 1) # maybe fragile + @test pick(scene, Point2f(20, 80)) == (nothing, 0) + @test pick(scene, Point2f(50, 80)) == (sc2, 4) + end + + @testset "meshscatter" begin + @test pick(scene, (20, 110)) == (ms, 1) + @test pick(scene, (44, 117)) == (ms, 3) + @test pick(scene, (57, 117)) == (nothing, 0) + end + + @testset "lines" begin + # Bit less precise since joints aren't strictly one segment or the other + @test pick(scene, 22, 140) == (l1, 2) + @test pick(scene, 48, 140) == (l1, 2) + @test pick(scene, 50, 142) == (l1, 3) + @test pick(scene, 50, 168) == (l1, 3) + @test pick(scene, 48, 170) == (l1, 4) + @test pick(scene, 22, 170) == (l1, 4) + @test pick(scene, 20, 168) == (l1, 5) + @test pick(scene, 20, 142) == (l1, 5) + + # more precise checks around borders (these maybe off by a pixel due to AA) + @test pick(scene, 20, 200) == (l2, 2) + @test pick(scene, 30, 209) == (l2, 2) + @test pick(scene, 30, 211) == (nothing, 0) + @test pick(scene, 59, 200) == (l2, 2) + @test pick(scene, 61, 200) == (nothing, 0) + @test pick(scene, 57, 206) == (l2, 2) + @test pick(scene, 57, 208) == (nothing, 0) + @test pick(scene, 40, 230) == (l2, 5) # nan handling + end + + @testset "linesegments" begin + @test pick(scene, 8, 260) == (nothing, 0) # off by a pixel due to AA + @test pick(scene, 10, 260) == (ls, 2) + @test pick(scene, 30, 269) == (ls, 2) + @test pick(scene, 30, 271) == (nothing, 0) + @test pick(scene, 59, 260) == (ls, 2) + @test pick(scene, 61, 260) == (nothing, 0) + + @test pick(scene, 8, 290) == (nothing, 0) # off by a pixel due to AA + @test pick(scene, 10, 290) == (ls, 6) + @test pick(scene, 30, 280) == (ls, 6) + @test pick(scene, 30, 278) == (nothing, 0) # off by a pixel due to AA + @test pick(scene, 59, 290) == (ls, 6) + @test pick(scene, 61, 290) == (nothing, 0) + end + + @testset "text" begin + @test pick(scene, 15, 320) == (t, 1) + @test pick(scene, 13, 320) == (nothing, 0) + # edge checks, further outside due to AA + @test pick(scene, 20, 306) == (nothing, 0) + @test pick(scene, 20, 320) == (t, 1) + @test pick(scene, 20, 333) == (nothing, 0) + # space is counted + @test pick(scene, 43, 320) == (t, 3) + @test pick(scene, 48, 324) == (t, 3) + @test pick(scene, 49, 326) == (nothing, 0) + # characters at nan position are counted + @test pick(scene, 20, 350) == (t, 6) + end + + @testset "image" begin + # outside border + for p in vcat( + [(x, y) for x in (79, 141) for y in (21, 49)], + [(x, y) for x in (81, 139) for y in (19, 51)] + ) + @test pick(scene, p) == (nothing, 0) + end + + # cell centered checks + @test pick(scene, 90, 30) == (i, 1) + @test pick(scene, 110, 30) == (i, 2) + @test pick(scene, 130, 30) == (i, 3) + @test pick(scene, 90, 40) == (i, 4) + @test pick(scene, 110, 40) == (i, 5) + @test pick(scene, 130, 40) == (i, 6) + + # precise check (around cell intersection) + @test pick(scene, 100-1, 35-1) == (i, 1) + @test pick(scene, 100+1, 35-1) == (i, 2) + @test pick(scene, 100-1, 35+1) == (i, 4) + @test pick(scene, 100+1, 35+1) == (i, 5) + + @test pick(scene, 120-1, 35-1) == (i, 2) + @test pick(scene, 120+1, 35-1) == (i, 3) + @test pick(scene, 120-1, 35+1) == (i, 5) + @test pick(scene, 120+1, 35+1) == (i, 6) + + # reversed axis check + @test pick(scene, 200, 30) == (i2, 1) + @test pick(scene, 190, 30) == (i2, 2) + @test pick(scene, 200, 40) == (i2, 3) + @test pick(scene, 190, 40) == (i2, 4) + end + + @testset "surface" begin + # outside border + for p in vcat( + [(x, y) for x in (79, 141) for y in (81, 109)], + [(x, y) for x in (81, 139) for y in (79, 111)] + ) + @test pick(scene, p) == (nothing, 0) + end + + # cell centered checks + @test pick(scene, 90, 90) == (s, 1) + @test pick(scene, 110, 90) == (s, 2) + @test pick(scene, 130, 90) == (s, 3) + @test pick(scene, 90, 100) == (s, 4) + @test pick(scene, 110, 100) == (s, 5) + @test pick(scene, 130, 100) == (s, 6) + + # precise check (around cell intersection) + @test pick(scene, 95-1, 95-1) == (s, 1) + @test pick(scene, 95+1, 95-1) == (s, 2) + @test pick(scene, 95-1, 95+1) == (s, 4) + @test pick(scene, 95+1, 95+1) == (s, 5) + + @test pick(scene, 125-1, 95-1) == (s, 2) + @test pick(scene, 125+1, 95-1) == (s, 3) + @test pick(scene, 125-1, 95+1) == (s, 5) + @test pick(scene, 125+1, 95+1) == (s, 6) + + # reversed axis check + @test pick(scene, 200, 90) == (s2, 1) + @test pick(scene, 190, 90) == (s2, 2) + @test pick(scene, 200, 100) == (s2, 3) + @test pick(scene, 190, 100) == (s2, 4) + end + + @testset "heatmap" begin + # outside border + for p in vcat( + [(x, y) for x in (64, 156) for y in (126, 184)], + [(x, y) for x in (66, 154) for y in (124, 186)] + ) + @test pick(scene, p) == (nothing, 0) + end + + # cell centered checks + @test pick(scene, 80, 140) == (hm, 1) + @test pick(scene, 110, 140) == (hm, 2) + @test pick(scene, 140, 140) == (hm, 3) + @test pick(scene, 80, 170) == (hm, 4) + @test pick(scene, 110, 170) == (hm, 5) + @test pick(scene, 140, 170) == (hm, 6) + + # precise check (around cell intersection) + @test pick(scene, 94, 154) == (hm, 1) + @test pick(scene, 96, 154) == (hm, 2) + @test pick(scene, 94, 156) == (hm, 4) + @test pick(scene, 96, 156) == (hm, 5) + + @test pick(scene, 124, 154) == (hm, 2) + @test pick(scene, 126, 154) == (hm, 3) + @test pick(scene, 124, 156) == (hm, 5) + @test pick(scene, 126, 156) == (hm, 6) + + # reversed axis check + @test pick(scene, 210, 140) == (hm2, 1) + @test pick(scene, 180, 140) == (hm2, 2) + @test pick(scene, 210, 170) == (hm2, 3) + @test pick(scene, 180, 170) == (hm2, 4) + end + + @testset "mesh" begin + @test pick(scene, 80, 200)[1] == m + @test pick(scene, 79, 200) == (nothing, 0) + @test pick(scene, 80, 199) == (nothing, 0) + @test pick(scene, 81, 201) == (m, 3) + @test pick(scene, 81, 225) == (m, 3) + @test pick(scene, 105, 201) == (m, 3) + @test pick(scene, 85, 229) == (m, 4) + @test pick(scene, 109, 205) == (m, 4) + @test pick(scene, 109, 229) == (m, 4) + @test pick(scene, 109, 229)[1] == m + @test pick(scene, 111, 230) == (nothing, 0) + @test pick(scene, 110, 231) == (nothing, 0) + end + + @testset "voxel" begin + # outside border + for p in vcat( + [(x, y) for x in (64, 246) for y in (126, 184)], + [(x, y) for x in (66, 244) for y in (124, 186)] + ) + @test pick(scene, p) == (nothing, 0) + end + + # cell centered checks + @test pick(scene, 80, 260) == (vx, 1) + @test pick(scene, 110, 260) == (vx, 2) + @test pick(scene, 140, 260) == (vx, 3) + @test pick(scene, 80, 290) == (vx, 4) + @test pick(scene, 110, 290) == (vx, 5) + @test pick(scene, 140, 290) == (vx, 6) + + # precise check (around cell intersection) + @test pick(scene, 94, 274) == (vx, 1) + @test pick(scene, 96, 274) == (vx, 2) + @test pick(scene, 94, 276) == (vx, 4) + @test pick(scene, 96, 276) == (vx, 5) + + @test pick(scene, 124, 274) == (vx, 2) + @test pick(scene, 126, 274) == (vx, 3) + @test pick(scene, 124, 276) == (vx, 5) + @test pick(scene, 126, 276) == (vx, 6) + end + + @testset "volume" begin + # volume doesn't produce indices because we can't resolve the depth of + # the pick + @test pick(scene, 80, 320)[1] == vol + @test pick(scene, 79, 320) == (nothing, 0) + @test pick(scene, 80, 319) == (nothing, 0) + @test pick(scene, 81, 321) == (vol, 0) + @test pick(scene, 81, 349) == (vol, 0) + @test pick(scene, 109, 321) == (vol, 0) + @test pick(scene, 109, 349) == (vol, 0) + @test pick(scene, 109, 349)[1] == vol + @test pick(scene, 111, 350) == (nothing, 0) + @test pick(scene, 110, 351) == (nothing, 0) + end + + # grab all indices and generate a plot for them (w/ fixed px_per_unit) + full_screen = last.(pick(scene, scene.viewport[])) + + scene2 = Scene(size = 2.0 .* widths(scene.viewport[])) + campixel!(scene2) + image!(scene2, full_screen, colormap = :viridis) + scene2 +end \ No newline at end of file diff --git a/ReferenceTests/src/tests/refimages.jl b/ReferenceTests/src/tests/refimages.jl index a73defc502a..1f425a04f76 100644 --- a/ReferenceTests/src/tests/refimages.jl +++ b/ReferenceTests/src/tests/refimages.jl @@ -50,3 +50,6 @@ end @testset "updating_plots" begin include("updating.jl") end +@testset "generic_components" begin + include("generic_components.jl") +end diff --git a/ReferenceTests/src/tests/short_tests.jl b/ReferenceTests/src/tests/short_tests.jl index f05977e1324..6d111230245 100644 --- a/ReferenceTests/src/tests/short_tests.jl +++ b/ReferenceTests/src/tests/short_tests.jl @@ -324,4 +324,4 @@ end # f.scene.events.scroll[] = (0, -10) # # reference test the zoomed out plot # f -# end \ No newline at end of file +# end diff --git a/ReferenceUpdater/src/reference_images.html b/ReferenceUpdater/src/reference_images.html index fc58c9739a8..29f4f69e309 100644 --- a/ReferenceUpdater/src/reference_images.html +++ b/ReferenceUpdater/src/reference_images.html @@ -257,6 +257,7 @@