Skip to content

Commit

Permalink
Allow plots to move between scenes in SpecApi (#4478)
Browse files Browse the repository at this point in the history
* refactor JS Plot object to be movable

* allow to move plots between scenes

* correctly close channel

* re-use plots, that got moved between axes

* remove lock

* fix specapi

* fix makie tests

* fix CairoMakie

* Update CHANGELOG.md

* try showing more infos

* implement moveto! correctly for GLMakie and test for all backends

* test & fix move_to!

* add another test

* make move_to optional

* revert GLMakie changes

* revert more changes

* fix WGLMakie move_to

* rename test

* improve tests

* clean up test
  • Loading branch information
SimonDanisch authored Nov 4, 2024
1 parent 7375072 commit 0f4b02d
Show file tree
Hide file tree
Showing 18 changed files with 844 additions and 494 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]

- Allow plots to move between scenes in SpecApi [#4132](https://github.com/MakieOrg/Makie.jl/pull/4132).
- Added empty constructor to all backends for `Screen` allowing `display(Makie.current_backend().Screen(), fig)` [#4561](https://github.com/MakieOrg/Makie.jl/pull/4561).
- Added `subsup` and `left_subsup` functions that offer stacked sub- and superscripts for `rich` text which means this style can be used with arbitrary fonts and is not limited to fonts supported by MathTeXEngine.jl [#4489](https://github.com/MakieOrg/Makie.jl/pull/4489).
- Added the `jitter_width` and `side_nudge` attributes to the `raincloud` plot definition, so that they can be used as kwargs [#4517]https://github.com/MakieOrg/Makie.jl/pull/4517)
Expand Down
4 changes: 2 additions & 2 deletions MakieCore/src/recipes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ function default_theme(scene, T::Type{<: Plot})
end
return attr
end

function extract_docstring(str)
if VERSION >= v"1.11" && str isa Base.Docs.DocStr
return only(str.text::Core.SimpleVector)
Expand Down Expand Up @@ -502,7 +502,7 @@ function create_recipe_expr(Tsym, args, attrblock)
end
function ($funcname!)(args...; kw...)
kwdict = Dict{Symbol, Any}(kw)
_create_plot!($funcname, kwdict, args...)
_create_plot!($funcname, kwdict, args...)
end

$(arg_type_func)
Expand Down
42 changes: 21 additions & 21 deletions ReferenceTests/src/tests/examples2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1498,22 +1498,22 @@ 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),
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,
violin!(categories, values, orientation = :horizontal,
color = :yellow, side = :right, strokewidth = 2, strokecolor = :black,
weights = abs.(values)
)
Expand Down Expand Up @@ -1607,7 +1607,7 @@ 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)
Expand Down Expand Up @@ -1646,16 +1646,16 @@ 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,

crossbar(fig[1, 2], xs, ys, ymins, ymaxs,
dodge = dodge, dodge_gap = 0.25,
gap = 0.05,
midlinecolor = :blue, midlinewidth = 5,
Expand All @@ -1679,7 +1679,7 @@ end
w = @. x^2 * (1 - x)^2
ecdfplot(f[1, 2], x)
ecdfplot!(x; weights = w, color=:orange)

f
end

Expand Down Expand Up @@ -1710,26 +1710,26 @@ end
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,
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
@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,
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)

Expand All @@ -1741,11 +1741,11 @@ end

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

Expand All @@ -1760,7 +1760,7 @@ end
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),
Expand All @@ -1782,21 +1782,21 @@ end

fig = Figure()
waterfall(fig[1, 1], y)
waterfall(fig[1, 2], y, show_direction = true, marker_pos = :cross,
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],
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],

waterfall(fig[2, 2], x, y, dodge = group, color = colors[group],
show_direction = true, stack = :x, show_final = true)

fig
Expand Down
35 changes: 31 additions & 4 deletions ReferenceTests/src/tests/specapi.jl
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,39 @@ end
st
end

AxNoTicks(;kw...) = S.Axis(; xticksvisible=false,
yticksvisible=false, yticklabelsvisible=false,
xticklabelsvisible=false, kw...)

@reference_test "Moving Plots in SpecApi" begin
pl1 = S.Heatmap((1, 4), (1, 4), Makie.peaks(50))
pl2 = S.Scatter(1:4; color=1:4, markersize=30, strokewidth=1, strokecolor=:black)
ax1 = AxNoTicks(; plots=[pl1, pl2])
grid = S.GridLayout(AxNoTicks())
f, _, pl = plot(S.GridLayout([ax1 grid]; colgaps=Fixed(4)); figure=(; figure_padding=2, size=(500, 100)))
cb1 = copy(colorbuffer(f))

pl1 = S.Heatmap((1, 4), (1, 4), Makie.peaks(50); colormap=:inferno)
ax1 = AxNoTicks()
grid = S.GridLayout(AxNoTicks(; plots=[pl1, pl2]))
pl[1] = S.GridLayout([ax1 grid]; colgaps=Fixed(4))
cb2 = copy(colorbuffer(f))

pl1 = S.Heatmap((1, 4), (1, 4), Makie.peaks(50))
ax1 = AxNoTicks(; plots=[pl1])
ax2 = S.GridLayout(AxNoTicks(; plots=[pl2]))
pl[1] = S.GridLayout([ax1 ax2]; colgaps=Fixed(4))
cb3 = copy(colorbuffer(f))

imgs = hcat(rotr90.((cb1, cb2, cb3))...)
s = Scene(; size=size(imgs))
image!(s, imgs; space=:pixel)
s
end

function to_plot(plots)
axes = map(permutedims(plots)) do plot
ax = S.Axis(;
plots=[plot], xticksvisible=false,
yticksvisible=false, yticklabelsvisible=false,
xticklabelsvisible=false)
ax = AxNoTicks(; plots=[plot])
return S.GridLayout([ax S.Colorbar(plot)])
end
return S.GridLayout(axes)
Expand Down
70 changes: 66 additions & 4 deletions ReferenceTests/src/tests/updating.jl
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ end
end

@reference_test "event ticks in record" begin
# Checks whether record calculates and triggers event.tick by drawing a
# Checks whether record calculates and triggers event.tick by drawing a
# Point at y = 1 for each frame where it does. The animation is irrelevant
# here, so we can just check the final image.
# The first point maybe at 0 depending on when the backend sets up it's
Expand All @@ -192,6 +192,68 @@ end
f
end

@reference_test "Moving plots with move_to" begin
f, ax, pl1 = scatter(5:-1:1; markersize=20, axis=(; title="Axis 1"))
pl2 = poly!(ax, Rect2f(10, 10, 100, 100); color=:green, space=:pixel)
pl3 = scatter!(ax, 1:5; color=Float64[1:5;], markersize=0.5, colorrange=(1, 5), lowclip=:black,
highclip=:red, markerspace=:data)
pl4 = poly!(ax, Rect2f(0, 0, 1, 1); color=(:green, 0.5), strokewidth=2, strokecolor=:black)
f
img1 = copy(colorbuffer(f; px_per_unit=1))
plots = [pl1, pl2, pl3, pl4]
get_listener_lengths() = map(plots) do x
arg_l = length(x[1].listeners)
attr_l = length(x.color.listeners)
return [arg_l, attr_l]
end
listener_lengths_1 = get_listener_lengths()

ax2 = Axis(f[1, 2]; title="Axis 2")
ls = LScene(f[2, :]; show_axis=false)
scene = Makie.camrelative(ls.scene)

Makie.move_to!(pl2, ax2.scene)
Makie.move_to!(pl3, ax2.scene)
Makie.move_to!(pl4, scene)
# Make sure updating still works for color
pl3.color = [-1, 2, 3, 4, 7]
pl3.colormap = :inferno
pl3.markersize = 1

@test listener_lengths_1 == get_listener_lengths()

img2 = copy(colorbuffer(f; px_per_unit=1))
@test length(ax.scene.plots) == 1
@test ax.scene.plots[1] === pl1
@test length(ax2.scene.plots) == 2
@test pl2 in ax2.scene.plots
@test pl3 in ax2.scene.plots
@test pl4 in scene.plots
@test length(scene) == 1

# Move everything back
pl3.color = Float64[1:5;]
pl3.colormap = :viridis
pl3.markersize = 0.5
Makie.move_to!(pl1, ax.scene)
Makie.move_to!(pl2, ax.scene)
Makie.move_to!(pl3, ax.scene)
Makie.move_to!(pl4, ax.scene)
# Make it easier to see similarity to first plot, by removing new scenes
delete!(ls)
delete!(ax2)
trim!(f.layout)

img3 = copy(colorbuffer(f; px_per_unit=1))

@test listener_lengths_1 == get_listener_lengths()

imgs = hcat(rotr90.((img1, img2, img3))...)
s = Scene(; size=size(imgs))
image!(s, imgs; space=:pixel)
s
end

@reference_test "updating surface size" begin
X = Observable(-5:5)
Y = Observable(-5:5)
Expand All @@ -200,8 +262,8 @@ end
f = Figure(size = (800, 400))
surface(f[1, 1], X, Y, Z)
surface(f[1, 2], map(collect, X), map(collect, Y), Z)
surface(f[1, 3],
map((X, Y) -> [x for x in X, y in Y], X, Y),
surface(f[1, 3],
map((X, Y) -> [x for x in X, y in Y], X, Y),
map((X, Y) -> [y for x in X, y in Y], X, Y), Z)
st = Stepper(f)
Makie.step!(st)
Expand All @@ -215,4 +277,4 @@ end
Z.val = [0.01 * x*x * y*y for x in X.val, y in Y.val]
notify(Z)
Makie.step!(st)
end
end
10 changes: 5 additions & 5 deletions ReferenceUpdater/src/local_server.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function serve_update_page_from_dir(folder)

@info "Downloading latest reference folder for $tag"
tempdir = download_refimages(tag)

@info "Updating files in $tempdir"

for image in images_to_update
Expand Down Expand Up @@ -253,20 +253,20 @@ end

function group_files(path, input_filename, output_filename)
isfile(joinpath(path, output_filename)) && return

# Group files in new_files/missing_files into a table like layout:
# GLMakie CairoMakie WGLMakie

# collect refimg names and which backends they exist for
data = Dict{String, Vector{Bool}}()
open(joinpath(path, input_filename), "r") do file
for filepath in eachline(file)
pieces = split(filepath, '/')
pieces = splitpath(filepath)
backend = pieces[1]
if !(backend in ("GLMakie", "CairoMakie", "WGLMakie"))
error("Failed to parse backend in \"$line\", got \"$backend\"")
error("Failed to parse backend in \"$pieces\", got \"$backend\"")
end

filename = join(pieces[2:end], '/')
exists = get!(data, filename, [false, false, false])

Expand Down
Loading

0 comments on commit 0f4b02d

Please sign in to comment.