Skip to content

Commit e6c4d46

Browse files
authored
Improve plot recipes (trixi-framework#862)
1 parent 59e3c98 commit e6c4d46

1 file changed

Lines changed: 32 additions & 42 deletions

File tree

src/visualization/recipes_plots.jl

Lines changed: 32 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,31 @@ const TrixiParticlesODESolution = ODESolution{<:Any, <:Any, <:Any, <:Any, <:Any,
44
<:ODEProblem{<:Any, <:Any, <:Any,
55
<:Semidiscretization}}
66

7+
# This is the main recipe
78
RecipesBase.@recipe function f(sol::TrixiParticlesODESolution)
89
# Redirect everything to the next recipe
910
return sol.u[end].x..., sol.prob.p
1011
end
1112

1213
# GPU version
13-
RecipesBase.@recipe function f(sol::TrixiParticlesODESolution, semi::Semidiscretization)
14+
RecipesBase.@recipe function f(v_ode::AbstractGPUArray, u_ode, semi::Semidiscretization)
1415
# Move GPU data to the CPU
15-
v_ode = Array(sol.u[end].x[1])
16-
u_ode = Array(sol.u[end].x[2])
17-
semi_ = Adapt.adapt(Array, sol.prob.p)
16+
v_ode_ = Array(v_ode)
17+
u_ode_ = Array(u_ode)
18+
semi_ = Adapt.adapt(Array, semi)
1819

1920
# Redirect everything to the next recipe
20-
return v_ode, u_ode, semi_, particle_spacings(semi)
21-
end
22-
23-
RecipesBase.@recipe function f(v_ode::AbstractGPUArray, u_ode, semi::Semidiscretization;
24-
particle_spacings=nothing,
25-
size=(600, 400), # Default size
26-
xlims=(-Inf, Inf), ylims=(-Inf, Inf))
27-
throw(ArgumentError("to plot GPU data, use `plot(sol, semi)`"))
21+
return v_ode_, u_ode_, semi_
2822
end
2923

3024
RecipesBase.@recipe function f(v_ode, u_ode, semi::Semidiscretization;
3125
particle_spacings=TrixiParticles.particle_spacings(semi),
3226
size=(600, 400), # Default size
3327
xlims=(-Inf, Inf), ylims=(-Inf, Inf))
34-
return v_ode, u_ode, semi, particle_spacings
35-
end
36-
37-
RecipesBase.@recipe function f(v_ode, u_ode, semi::Semidiscretization, particle_spacings;
38-
size=(600, 400), # Default size
39-
xlims=(-Inf, Inf), ylims=(-Inf, Inf))
28+
# We need to split this in two recipes in order to find the minimum and maximum
29+
# coordinates across all systems.
30+
# In this first recipe, we collect the data for each system,
31+
# and then pass it to the next recipe.
4032
systems_data = map(enumerate(semi.systems)) do (i, system)
4133
u = wrap_u(u_ode, system, semi)
4234
periodic_box = get_neighborhood_search(system, semi).periodic_box
@@ -54,27 +46,20 @@ RecipesBase.@recipe function f(v_ode, u_ode, semi::Semidiscretization, particle_
5446
x_min, y_min = minimum(coordinates, dims=2) .- 0.5particle_spacing
5547
x_max, y_max = maximum(coordinates, dims=2) .+ 0.5particle_spacing
5648

57-
# `x_min`, `x_max`, etc. are used to automatically set the marker size.
58-
# When `xlims` or `ylims` are passed explicitly, we have to update these to get the correct marker size.
59-
isfinite(first(xlims)) && (x_min = xlims[1])
60-
isfinite(last(xlims)) && (x_max = xlims[2])
61-
62-
isfinite(first(ylims)) && (y_min = ylims[1])
63-
isfinite(last(ylims)) && (y_max = ylims[2])
64-
6549
return (; x, y, x_min, x_max, y_min, y_max, particle_spacing,
6650
label=timer_name(system))
6751
end
6852

53+
# Pass the semidiscretization and the collected data to the next recipe
6954
return (semi, systems_data...)
7055
end
7156

7257
function particle_spacings(semi::Semidiscretization)
7358
return [system.initial_condition.particle_spacing for system in semi.systems]
7459
end
7560

76-
RecipesBase.@recipe function f((initial_conditions::InitialCondition)...;
77-
xlims=(Inf, Inf), ylims=(Inf, Inf))
61+
RecipesBase.@recipe function f(initial_conditions::InitialCondition...)
62+
# This recipe is similar to the one above for the semidiscretization
7863
idx = 0
7964
ics = map(initial_conditions) do ic
8065
x = collect(ic.coordinates[1, :])
@@ -88,34 +73,32 @@ RecipesBase.@recipe function f((initial_conditions::InitialCondition)...;
8873
x_min, y_min = minimum(ic.coordinates, dims=2) .- 0.5particle_spacing
8974
x_max, y_max = maximum(ic.coordinates, dims=2) .+ 0.5particle_spacing
9075

91-
# `x_min`, `x_max`, etc. are used to automatically set the marker size.
92-
# When `xlims` or `ylims` are passed explicitly, we have to update these to get the correct marker size.
93-
isfinite(first(xlims)) && (x_min = xlims[1])
94-
isfinite(last(xlims)) && (x_max = xlims[2])
95-
96-
isfinite(first(ylims)) && (y_min = ylims[1])
97-
isfinite(last(ylims)) && (y_max = ylims[2])
98-
9976
idx += 1
10077

10178
return (; x, y, x_min, x_max, y_min, y_max, particle_spacing,
10279
label="initial condition " * "$idx")
10380
end
10481

82+
# Pass the first initial condition and the collected data to the next recipe
10583
return (first(initial_conditions), ics...)
10684
end
10785

10886
RecipesBase.@recipe function f(::Union{InitialCondition, Semidiscretization},
109-
data...; zcolor=nothing, size=(600, 400), colorbar_title="",
110-
xlims=(Inf, Inf), ylims=(Inf, Inf))
87+
data...; size=(600, 400), xlims=(Inf, Inf), ylims=(Inf, Inf))
88+
# `data` is a tuple of named tuples, passed from the recipe above.
89+
# Each named tuple contains coordinates and metadata for a system or initial condition.
90+
#
91+
# First find the minimum and maximum coordinates across all systems.
11192
x_min = minimum(obj.x_min for obj in data)
11293
x_max = maximum(obj.x_max for obj in data)
11394

11495
y_min = minimum(obj.y_min for obj in data)
11596
y_max = maximum(obj.y_max for obj in data)
11697

11798
# `x_min`, `x_max`, etc. are used to automatically set the marker size.
118-
# When `xlims` or `ylims` are passed explicitly, we have to update these to get the correct marker size.
99+
# So they need to be the minimum and maximum coordinates of the plot area.
100+
# When `xlims` or `ylims` are passed explicitly, we have to update these
101+
# to get the correct marker size.
119102
isfinite(first(xlims)) && (x_min = xlims[1])
120103
isfinite(last(xlims)) && (x_max = xlims[2])
121104

@@ -130,14 +113,21 @@ RecipesBase.@recipe function f(::Union{InitialCondition, Semidiscretization},
130113

131114
xlims --> (x_min, x_max)
132115
ylims --> (y_min, y_max)
133-
aspect_ratio --> :equal
134116

117+
# Just having the kwargs `xlims` and `ylims` (without setting them)
118+
# is enough to make `widen = :auto` fall back to no widening.
119+
# When no explicit limits are passed, we overwrite this.
120+
if all(!isfinite, xlims) && all(!isfinite, ylims)
121+
widen --> true
122+
end
123+
124+
aspect_ratio --> :equal
135125
seriestype --> :scatter
126+
# No border around the markers
136127
markerstrokewidth --> 0
137128
grid --> false
138-
colorbar_title --> colorbar_title
139-
zcolor --> zcolor
140129

130+
# Now plot all systems or initial conditions
141131
for obj in data
142132
@series begin
143133
if obj.particle_spacing < eps()

0 commit comments

Comments
 (0)