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 @@

Images with references

images_to_update = Array.from(document.querySelectorAll(".update-checkbox")) .filter(inp => inp.checked) + .concat(Array.from(document.querySelectorAll(".add-checkbox")).filter(inp => inp.checked)) .map(inp => inp.dataset.image) images_to_delete = Array.from(document.querySelectorAll(".delete-checkbox")) diff --git a/WGLMakie/assets/mesh.frag b/WGLMakie/assets/mesh.frag index 7e5779366fd..68280dbaf6b 100644 --- a/WGLMakie/assets/mesh.frag +++ b/WGLMakie/assets/mesh.frag @@ -109,6 +109,19 @@ vec4 pack_int(uint id, uint index) { return unpack; } +// for picking indices in image, heatmap, surface +uint picking_index_from_uv(sampler2D img, vec2 uv) { + ivec2 size = textureSize(img, 0); + ivec2 jl_idx = clamp(ivec2(uv * vec2(size)), ivec2(0), size-1); + uint idx = uint(jl_idx.y + jl_idx.x * size.y); + return idx; +} + +// These should not get hit +uint picking_index_from_uv(bool img, vec2 uv) { return frag_instance_id; } +uint picking_index_from_uv(vec3 img, vec2 uv) { return frag_instance_id; } +uint picking_index_from_uv(vec4 img, vec2 uv) { return frag_instance_id; } + void main() { for (int i = 0; i < num_clip_planes; i++) @@ -125,10 +138,12 @@ void main() shaded_color = get_ambient() * real_color.rgb + light; } - if (picking) { - if (real_color.a > 0.1) { + if (picking && (real_color.a > 0.1)) { + if (get_PICKING_INDEX_FROM_UV()) { + fragment_color = pack_int(object_id, picking_index_from_uv(uniform_color, frag_uv)); + } else fragment_color = pack_int(object_id, frag_instance_id); - } + return; } diff --git a/WGLMakie/assets/voxel.frag b/WGLMakie/assets/voxel.frag index 4e095306341..dfec45013b8 100644 --- a/WGLMakie/assets/voxel.frag +++ b/WGLMakie/assets/voxel.frag @@ -144,7 +144,7 @@ void main() if (picking) { uvec3 size = uvec3(textureSize(voxel_id, 0).xyz); - uvec3 idx = uvec3(o_uvw * vec3(size)); + uvec3 idx = clamp(uvec3(o_uvw * vec3(size)), uvec3(0), size - uvec3(1)); uint lin = idx.x + size.x * (idx.y + size.y * idx.z); fragment_color = pack_int(object_id, lin); return; diff --git a/WGLMakie/src/imagelike.jl b/WGLMakie/src/imagelike.jl index b286e055e79..0406f3ddb8f 100644 --- a/WGLMakie/src/imagelike.jl +++ b/WGLMakie/src/imagelike.jl @@ -34,7 +34,7 @@ function create_shader(mscene::Scene, plot::Surface) end) per_vertex = Dict(:positions => positions, :faces => faces, :uv => uv, :normals => normals) - uniforms = Dict(:uniform_color => color, :color => false, :model => model) + uniforms = Dict(:uniform_color => color, :color => false, :model => model, :PICKING_INDEX_FROM_UV => true) # TODO: allow passing Mat{2, 3, Float32} (and nothing) uniforms[:uv_transform] = map(plot, plot[:uv_transform]) do x @@ -61,6 +61,7 @@ function create_shader(mscene::Scene, plot::Union{Heatmap, Image}) :shininess => 0.0f0, :backlight => 0.0f0, :model => model, + :PICKING_INDEX_FROM_UV => true ) # TODO: allow passing Mat{2, 3, Float32} (and nothing) diff --git a/WGLMakie/src/meshes.jl b/WGLMakie/src/meshes.jl index 2f6fd3aca9a..240465e6efa 100644 --- a/WGLMakie/src/meshes.jl +++ b/WGLMakie/src/meshes.jl @@ -91,6 +91,7 @@ function draw_mesh(mscene::Scene, per_vertex, plot, uniforms; permute_tex=true) # id + picking gets filled in JS, needs to be here to emit the correct shader uniforms uniforms[:picking] = false uniforms[:object_id] = UInt32(0) + get!(uniforms, :PICKING_INDEX_FROM_UV, false) pos = pop!(per_vertex, :positions) faces = pop!(per_vertex, :faces) mesh = GeometryBasics.Mesh(meta(pos; per_vertex...), faces) diff --git a/WGLMakie/src/particles.jl b/WGLMakie/src/particles.jl index 34d97fd7964..2703bd118fc 100644 --- a/WGLMakie/src/particles.jl +++ b/WGLMakie/src/particles.jl @@ -94,6 +94,7 @@ function create_shader(scene::Scene, plot::MeshScatter) get!(uniform_dict, :shininess, 8f0) get!(uniform_dict, :light_direction, Vec3f(1)) get!(uniform_dict, :light_color, Vec3f(1)) + get!(uniform_dict, :PICKING_INDEX_FROM_UV, false) # id + picking gets filled in JS, needs to be here to emit the correct shader uniforms uniform_dict[:picking] = false diff --git a/WGLMakie/src/picking.jl b/WGLMakie/src/picking.jl index 16217cf5891..74c06e227f6 100644 --- a/WGLMakie/src/picking.jl +++ b/WGLMakie/src/picking.jl @@ -20,7 +20,8 @@ function pick_native(screen::Screen, rect::Rect2i) lookup = plot_lookup(scene) return map(matrix) do (uuid, index) !haskey(lookup, uuid) && return (nothing, 0) - return (lookup[uuid], Int(index) + 1) + plt = lookup[uuid] + return (plt, Int(index) + !(plt isa Volume)) end end end @@ -43,7 +44,8 @@ function Makie.pick_closest(scene::Scene, screen::Screen, xy, range::Integer) """) lookup = plot_lookup(scene) !haskey(lookup, selection[1]) && return (nothing, 0) - return (lookup[selection[1]], selection[2] + 1) + plt = lookup[selection[1]] + return (plt, selection[2] + !(plt isa Volume)) end # Skips some allocations @@ -59,7 +61,10 @@ function Makie.pick_sorted(scene::Scene, screen::Screen, xy, range) """) isnothing(selection) && return Tuple{Plot,Int}[] lookup = plot_lookup(scene) - return [(lookup[plot_id], index + 1) for (plot_id, index) in selection if haskey(lookup, plot_id)] + return map(filter((id, idx) -> haskey(lookup, id), selection)) do (id, idx) + plt = lookup[id] + return (plt, index + !(plt isa Volume)) + end end function Makie.pick(::Scene, screen::Screen, xy) @@ -67,6 +72,10 @@ function Makie.pick(::Scene, screen::Screen, xy) return plot_matrix[1, 1] end +function Makie.pick(::Scene, screen::Screen, r::Rect2) + return pick_native(screen, Rect2i(round.(minimum(r)), round.(widths(r)))) +end + """ ToolTip(figurelike, js_callback; plots=plots_you_want_to_hover) diff --git a/WGLMakie/src/serialization.jl b/WGLMakie/src/serialization.jl index 4a58d98942b..a2942c681b3 100644 --- a/WGLMakie/src/serialization.jl +++ b/WGLMakie/src/serialization.jl @@ -387,7 +387,7 @@ function serialize_three(scene::Scene, @nospecialize(plot::AbstractPlot)) Makie.scalematrix(Vec3f(width ./ size(chunk))) * Makie.translationmatrix(Vec3f(mini)) modelinv = inv(_model) - @assert modelinv[4, 4] == 1 + @assert isapprox(modelinv[4, 4], 1, atol = 1e-6) output = Vector{Vec4f}(undef, 8) for i in 1:min(length(planes), 8) @@ -422,7 +422,7 @@ function serialize_three(scene::Scene, @nospecialize(plot::AbstractPlot)) # with just applying it to the plane origin and transpose(inv(modelinv)) # to plane.normal modelinv = inv(model) - @assert modelinv[4, 4] == 1 + @assert isapprox(modelinv[4, 4], 1, atol = 1e-6) output = Vector{Vec4f}(undef, 8) for i in 1:min(length(planes), 8) diff --git a/WGLMakie/src/voxel.jl b/WGLMakie/src/voxel.jl index 743bee5e5a1..a9d395565db 100644 --- a/WGLMakie/src/voxel.jl +++ b/WGLMakie/src/voxel.jl @@ -49,10 +49,9 @@ function create_shader(scene::Scene, plot::Makie.Voxels) ) do xs, ys, zs, chunk, model mini = minimum.((xs, ys, zs)) width = maximum.((xs, ys, zs)) .- mini - # with f32convert applying to 3D plots patch_model should apply to all of this - return Mat4f(model) * - Makie.scalematrix(Vec3f(width ./ size(chunk))) * - Makie.translationmatrix(Vec3f(mini)) + return Mat4f(model * + Makie.transformationmatrix(Vec3f(mini), Vec3f(width ./ size(chunk))) + ) end maybe_color_mapping = plot.calculated_colors[] diff --git a/docs/fake_interaction.jl b/docs/fake_interaction.jl index 7fa78be5aa5..6e8cf03be43 100644 --- a/docs/fake_interaction.jl +++ b/docs/fake_interaction.jl @@ -129,8 +129,8 @@ function recordframe_with_cursor_overlay!(io, cursor_pos, viewport, cursor_img, copy!(view(io.buffer, 1:xdim, 1:ydim), glnative) render_cursor!(io.buffer, (xdim, ydim), cursor_pos, viewport, cursor_img, cursor_tip_frac) - write(io.io, io.buffer) + Makie.next_tick!(io.tick_controller) return end diff --git a/docs/src/assets/beeswarm_example.png b/docs/src/assets/beeswarm_example.png new file mode 100644 index 00000000000..98d10bbec0c Binary files /dev/null and b/docs/src/assets/beeswarm_example.png differ diff --git a/docs/src/assets/geomakie_example.png b/docs/src/assets/geomakie_example.png index 1cb015d6dc8..670c2175845 100644 Binary files a/docs/src/assets/geomakie_example.png and b/docs/src/assets/geomakie_example.png differ diff --git a/docs/src/ecosystem.md b/docs/src/ecosystem.md index c4fc9b902d9..a064ca7e7aa 100644 --- a/docs/src/ecosystem.md +++ b/docs/src/ecosystem.md @@ -2,7 +2,7 @@ These packages and sites are maintained by third parties. If you install packages, keep an eye on version conflicts or downgrades as the Makie ecosystem is developing quickly so things break occasionally. -## AlgebraOfGraphics.jl +## [AlgebraOfGraphics.jl](https://github.com/MakieOrg/AlgebraOfGraphics.jl) Grammar-of-graphics style plotting, inspired by ggplot2. @@ -10,7 +10,7 @@ Grammar-of-graphics style plotting, inspired by ggplot2. ``` -## Beautiful Makie +## [Beautiful Makie](https://beautiful.makie.org/dev/) This third-party gallery contains many advanced examples. @@ -18,7 +18,7 @@ This third-party gallery contains many advanced examples. ``` -## GraphMakie.jl +## [GraphMakie.jl](https://github.com/MakieOrg/GraphMakie.jl) Graphs with two- and three-dimensional layout algorithms. @@ -26,10 +26,18 @@ Graphs with two- and three-dimensional layout algorithms. ``` -## GeoMakie.jl +## [GeoMakie.jl](https://github.com/MakieOrg/GeoMakie.jl) Geographic plotting utilities including projections. ```@raw html -``` \ No newline at end of file +``` + +## [SwarmMakie.jl](https://github.com/MakieOrg/SwarmMakie.jl) + +Beeswarm plots for Makie.jl! + +```@raw html + +``` diff --git a/docs/src/explanations/events.md b/docs/src/explanations/events.md index 3969a19f62e..d30cf93aff9 100644 --- a/docs/src/explanations/events.md +++ b/docs/src/explanations/events.md @@ -225,7 +225,9 @@ scene ## Point Picking -Makie provides a function `pick(x[, position = events(x).mouseposition[]])` to get the plot displayed at a certain position with `x` being a `Figure`, `Axis`, `FigureAxisPlot` or `Scene`. The function returns a primitive plot and an index. The primitive plots are the base plots drawable in backends: +Makie provides a function `pick(x[, position = events(x).mouseposition[]])` to get the plot displayed at a certain position with `x` being a `Figure`, `Axis`, `FigureAxisPlot` or `Scene`. +The function returns a primitive plot and an index. +The primitive plots are the base plots drawable in backends: - scatter - text @@ -235,12 +237,20 @@ Makie provides a function `pick(x[, position = events(x).mouseposition[]])` to g - meshscatter - surface - volume +- voxels - image - heatmap Every other plot is build from these somewhere down the line. For example `fig, ax, p = scatterlines(rand(10))` has `Lines` and `Scatter` as it's primitive plots in `p.plots`. -The index returned by `pick` relates to the main input of the respective primitive plot. For `scatter`, `test` and `meshscatter` it is the index into the position (character) array that matches the clicked marker (symbol). For `lines` and `linesegments` it's end position of the clicked line segment. For other plots it tends less useful. `mesh`, `image` and `surface` return index of the largest vertex in the clicked (triangle) face. `heatmap` and `volume` always return 0. +The index returned by `pick()` relates to the main input of the respective primitive plot. +- For `scatter` and `meshscatter` it is an index into the positions given to the plot. +- For `text` it is an index into the merged character array. +- For `lines` and `linesegments` it is the end position of the selected line segment. +- For `image`, `heatmap` and `surface` it is the linear index into the matrix argument of the plot (i.e. the given image, value or z-value matrix) that is closest to the selected position. +- For `voxels` it is the linear index into the given 3D Array. +- For `mesh` it is the largest vertex index of the picked triangle face. +- For `volume` it is always 0. Let's implement adding, moving and deleting of scatter markers as an example. We could implement adding and deleting with left and right clicks, however that would overwrite existing axis interactions. To avoid this we implement adding as `a + left click` and removing as `d + left click`. Since these settings are more restrictive we want Makie to check if either of them applies first and default back to normal axis interactions otherwise. This means our interactions should have a higher priority than the defaults and block conditionally. diff --git a/docs/src/reference/blocks/checkbox.md b/docs/src/reference/blocks/checkbox.md index 20cf2f36d4e..33aef2c4597 100644 --- a/docs/src/reference/blocks/checkbox.md +++ b/docs/src/reference/blocks/checkbox.md @@ -1,6 +1,11 @@ # Checkbox -```@figure backend=GLMakie +```@example checkbox +using GLMakie +using Random # hide +GLMakie.activate!() # hide + + f = Figure() gl = GridLayout(f[2, 1], tellwidth = false) @@ -10,26 +15,65 @@ cb1 = Checkbox(subgl[1, 1], checked = false) cb2 = Checkbox(subgl[2, 1], checked = true) cb3 = Checkbox(subgl[3, 1], checked = true) -Label(subgl[1, 2], "Show grid", halign = :left) -Label(subgl[2, 2], "Show ticklabels", halign = :left) -Label(subgl[3, 2], "Show title", halign = :left) +Label(subgl[1, 2], "Dataset A", halign = :left) +Label(subgl[2, 2], "Dataset B", halign = :left) +Label(subgl[3, 2], "Dataset C", halign = :left) rowgap!(subgl, 8) colgap!(subgl, 8) -ax = Axis( - f[1, 1], - title = "Checkboxes", - xgridvisible = cb1.checked, - ygridvisible = cb1.checked, - xticklabelsvisible = cb2.checked, - yticklabelsvisible = cb2.checked, - xticksvisible = cb2.checked, - yticksvisible = cb2.checked, - titlevisible = cb3.checked, - alignmode = Outside(), -) +ax = Axis(f[1, 1]) + +Random.seed!(123) # hide +for cb in [cb1, cb2, cb3] + lines!(ax, cumsum(randn(1000)), alpha = @lift($(cb.checked) ? 1.0 : 0.1)) +end f +nothing # hide +``` + +```@setup checkbox +using ..FakeInteraction + +evts = [ + Wait(0.5), + Lazy() do fig + MouseTo(relative_pos(cb1, (0.7, 0.3))) + end, + Wait(0.2), + LeftClick(), + Wait(0.5), + Lazy() do fig + MouseTo(relative_pos(cb2, (0.5, 0.6))) + end, + Wait(0.2), + LeftClick(), + Wait(0.5), + Lazy() do fig + MouseTo(relative_pos(cb3, (0.4, 0.4))) + end, + Wait(0.2), + LeftClick(), + Wait(0.5), + Lazy() do fig + MouseTo(relative_pos(cb2, (0.6, 0.5))) + end, + Wait(0.2), + LeftClick(), + Wait(0.5), + Lazy() do fig + MouseTo(relative_pos(cb1, (0.3, 0.3))) + end, + Wait(0.2), + LeftClick(), + Wait(1.0), +] + +interaction_record(f, "checkbox_example.mp4", evts) +``` + +```@raw html +