module TrixiMakieExt
using Makie: Makie, GeometryBasics
using Trixi
using Trixi: PlotData2DTriangulated, TrixiODESolution, PlotDataSeries, ScalarData, @muladd,
wrap_array_native, mesh_equations_solver_cache
import Trixi: iplot, iplot!
@muladd begin
function global_plotting_triangulation_makie(pds::PlotDataSeries{<:PlotData2DTriangulated};
set_z_coordinate_zero = false)
@unpack variable_id = pds
pd = pds.plot_data
@unpack x, y, data, t = pd
makie_triangles = Makie.to_triangles(t)
num_plotting_nodes, num_elements = size(x)
trimesh = Vector{GeometryBasics.Mesh{3, Float32}}(undef, num_elements)
coordinates = zeros(Float32, num_plotting_nodes, 3)
for element in Base.OneTo(num_elements)
for i in Base.OneTo(num_plotting_nodes)
coordinates[i, 1] = x[i, element]
coordinates[i, 2] = y[i, element]
if set_z_coordinate_zero == false
coordinates[i, 3] = data[i, element][variable_id]
end
end
trimesh[element] = GeometryBasics.normal_mesh(Makie.to_vertices(coordinates),
makie_triangles)
end
plotting_mesh = merge([trimesh...])
return plotting_mesh
end
function convert_PlotData2D_to_mesh_Points(pds::PlotDataSeries{<:PlotData2DTriangulated};
set_z_coordinate_zero = false)
@unpack variable_id = pds
pd = pds.plot_data
@unpack x_face, y_face, face_data = pd
if set_z_coordinate_zero
sol_f = zeros(eltype(first(x_face)), size(x_face))
else
sol_f = StructArrays.component(face_data, variable_id)
end
xyz_wireframe = GeometryBasics.Point.(map(x -> vec(vcat(x,
fill(NaN, 1, size(x, 2)))),
(x_face, y_face, sol_f))...)
return xyz_wireframe
end
function global_plotting_triangulation_makie(pd::PlotData2DTriangulated{<:ScalarData};
set_z_coordinate_zero = false)
@unpack x, y, data, t = pd
makie_triangles = Makie.to_triangles(t)
num_plotting_nodes, num_elements = size(x)
trimesh = Vector{GeometryBasics.Mesh{3, Float32}}(undef, num_elements)
coordinates = zeros(Float32, num_plotting_nodes, 3)
for element in Base.OneTo(num_elements)
for i in Base.OneTo(num_plotting_nodes)
coordinates[i, 1] = x[i, element]
coordinates[i, 2] = y[i, element]
if set_z_coordinate_zero == false
coordinates[i, 3] = data.data[i, element]
end
end
trimesh[element] = GeometryBasics.normal_mesh(Makie.to_vertices(coordinates),
makie_triangles)
end
plotting_mesh = merge([trimesh...])
return plotting_mesh
end
function convert_PlotData2D_to_mesh_Points(pd::PlotData2DTriangulated{<:ScalarData};
set_z_coordinate_zero = false)
@unpack x_face, y_face, face_data = pd
if set_z_coordinate_zero
sol_f = zeros(eltype(first(x_face)), size(x_face))
else
sol_f = face_data
end
xyz_wireframe = GeometryBasics.Point.(map(x -> vec(vcat(x,
fill(NaN, 1, size(x, 2)))),
(x_face, y_face, sol_f))...)
return xyz_wireframe
end
default_Makie_colormap() = :inferno
struct FigureAndAxes{Axes}
fig::Makie.Figure
axes::Axes
end
Base.show(io::IO, fa::FigureAndAxes) = nothing
function Base.iterate(fa::FigureAndAxes, state = 1)
if state == 1
return (fa.fig, 2)
elseif state == 2
return (fa.axes, 3)
else
return nothing
end
end
"""
iplot(u, mesh::UnstructuredMesh2D, equations, solver, cache;
plot_mesh=true, show_axis=false, colormap=default_Makie_colormap(),
variable_to_plot_in=1)
Creates an interactive surface plot of the solution and mesh for an `UnstructuredMesh2D` type.
Keywords:
- variable_to_plot_in: variable to show by default
!!! warning "Experimental implementation"
This is an experimental feature and may change in future releases.
"""
function iplot end
function iplot(pd::PlotData2DTriangulated;
plot_mesh = true, show_axis = false, colormap = default_Makie_colormap(),
variable_to_plot_in = 1)
@unpack variable_names = pd
fig = Makie.Figure()
menu_options = [zip(variable_names, 1:length(variable_names))...]
menu = Makie.Menu(fig, options = menu_options)
toggle_solution_mesh = Makie.Toggle(fig, active = plot_mesh)
toggle_mesh = Makie.Toggle(fig, active = plot_mesh)
fig[1, 1] = Makie.vgrid!(Makie.Label(fig, "Solution field", width = nothing), menu,
Makie.Label(fig, "Solution mesh visible"),
toggle_solution_mesh,
Makie.Label(fig, "Mesh visible"), toggle_mesh;
tellheight = false, width = 200)
ax = Makie.LScene(fig[1, 2], scenekw = (show_axis = show_axis,))
menu.selection[] = variable_to_plot_in
menu.i_selected[] = variable_to_plot_in
plotting_mesh = Makie.@lift(global_plotting_triangulation_makie(getindex(pd,
variable_names[$(menu.selection)])))
solution_z = Makie.@lift(getindex.($plotting_mesh.position, 3))
Makie.mesh!(ax, plotting_mesh; color = solution_z, colormap)
wire_points = Makie.@lift(convert_PlotData2D_to_mesh_Points(getindex(pd,
variable_names[$(menu.selection)])))
wire_mesh_top = Makie.lines!(ax, wire_points, color = :white,
visible = toggle_solution_mesh.active)
wire_mesh_bottom = Makie.lines!(ax, wire_points, color = :white,
visible = toggle_solution_mesh.active)
Makie.translate!(wire_mesh_top, 0, 0, 1e-3)
Makie.translate!(wire_mesh_bottom, 0, 0, -1e-3)
function compute_z_offset(solution_z)
zmin = minimum(solution_z)
zrange = (x -> x[2] - x[1])(extrema(solution_z))
return zmin - 0.25 * zrange
end
z_offset = Makie.@lift(compute_z_offset($solution_z))
function get_flat_points(wire_points, z_offset)
[Makie.Point(point.data[1:2]..., z_offset) for point in wire_points]
end
flat_wire_points = Makie.@lift get_flat_points($wire_points, $z_offset)
wire_mesh_flat = Makie.lines!(ax, flat_wire_points, color = :black,
visible = toggle_mesh.active)
function scaled_extrema(x)
ex = extrema(x)
if ex[2] ≈ ex[1]
return ex .+ 1e-5 .* maximum(abs.(ex)) .* (-1, 1)
else
return ex
end
end
Makie.Colorbar(fig[1, 3], limits = Makie.@lift(scaled_extrema($solution_z)),
colormap = colormap)
Makie.cameracontrols(ax.scene).controls.up_key = Makie.Keyboard.right_shift
fig
end
function iplot(u, mesh, equations, solver, cache;
solution_variables = nothing, nvisnodes = 2 * nnodes(solver), kwargs...)
@assert ndims(mesh) == 2
pd = PlotData2DTriangulated(u, mesh, equations, solver, cache;
solution_variables = solution_variables,
nvisnodes = nvisnodes)
iplot(pd; kwargs...)
end
iplot(sol::TrixiODESolution; kwargs...) = iplot(sol.u[end], sol.prob.p; kwargs...)
function iplot(u, semi; kwargs...)
iplot(wrap_array_native(u, semi), mesh_equations_solver_cache(semi)...; kwargs...)
end
function iplot(pd::PlotData2DTriangulated{<:ScalarData};
show_axis = false, colormap = default_Makie_colormap(),
plot_mesh = false)
fig = Makie.Figure()
ax = Makie.LScene(fig[1, 1], scenekw = (show_axis = show_axis,))
fig_axis_plt = iplot!(FigureAndAxes(fig, ax), pd; colormap = colormap,
plot_mesh = plot_mesh)
fig
return fig_axis_plt
end
function iplot!(fig_axis::Union{FigureAndAxes, Makie.FigureAxisPlot},
pd::PlotData2DTriangulated{<:ScalarData};
colormap = default_Makie_colormap(), plot_mesh = false)
fig, ax = fig_axis
plotting_mesh = global_plotting_triangulation_makie(pd)
solution_z = getindex.(plotting_mesh.position, 3)
plt = Makie.mesh!(ax, plotting_mesh; color = solution_z, colormap)
if plot_mesh
wire_points = convert_PlotData2D_to_mesh_Points(pd)
wire_mesh_top = Makie.lines!(ax, wire_points, color = :white)
wire_mesh_bottom = Makie.lines!(ax, wire_points, color = :white)
Makie.translate!(wire_mesh_top, 0, 0, 1e-3)
Makie.translate!(wire_mesh_bottom, 0, 0, -1e-3)
end
Makie.Colorbar(fig[1, end + 1], plt)
fig
return Makie.FigureAxisPlot(fig, ax, plt)
end
Makie.@recipe(TrixiHeatmap, plot_data_series) do scene
Makie.Theme(colormap = default_Makie_colormap())
end
function Makie.plot!(myplot::TrixiHeatmap)
pds = myplot[:plot_data_series][]
plotting_mesh = global_plotting_triangulation_makie(pds;
set_z_coordinate_zero = true)
pd = pds.plot_data
solution_z = vec(StructArrays.component(pd.data, pds.variable_id))
Makie.mesh!(myplot, plotting_mesh, color = solution_z, shading = Makie.NoShading,
colormap = myplot[:colormap])
myplot.colorrange = extrema(solution_z)
plot_mesh = if haskey(myplot, :plot_mesh)
myplot.plot_mesh[]
else
true
end
if plot_mesh
xyz_wireframe = convert_PlotData2D_to_mesh_Points(pds;
set_z_coordinate_zero = true)
Makie.lines!(myplot, xyz_wireframe, color = :lightgrey)
end
myplot
end
Makie.plottype(::Trixi.PlotDataSeries{<:Trixi.PlotData2DTriangulated}) = TrixiHeatmap
function Makie.plot(sol::TrixiODESolution;
plot_mesh = false, solution_variables = nothing,
colormap = default_Makie_colormap())
return Makie.plot(PlotData2DTriangulated(sol; solution_variables); plot_mesh,
colormap)
end
function Makie.plot(pd::PlotData2DTriangulated, fig = Makie.Figure();
plot_mesh = false, colormap = default_Makie_colormap())
figAxes = Makie.plot!(fig, pd; plot_mesh, colormap)
display(figAxes.fig)
return figAxes
end
function Makie.plot!(fig, pd::PlotData2DTriangulated;
plot_mesh = false, colormap = default_Makie_colormap())
if length(pd) <= 3
cols = length(pd)
rows = 1
else
cols = ceil(Int, sqrt(length(pd)))
rows = cld(length(pd), cols)
end
axes = [Makie.Axis(fig[i, j], xlabel = "x", ylabel = "y")
for j in 1:rows, i in 1:cols]
row_list, col_list = ([i for j in 1:rows, i in 1:cols],
[j for j in 1:rows, i in 1:cols])
for (variable_to_plot, (variable_name, pds)) in enumerate(pd)
ax = axes[variable_to_plot]
plt = trixiheatmap!(ax, pds; plot_mesh, colormap)
row = row_list[variable_to_plot]
col = col_list[variable_to_plot]
Makie.Colorbar(fig[row, col][1, 2], colormap = colormap)
ax.aspect = Makie.DataAspect()
ax.title = variable_name
Makie.xlims!(ax, extrema(pd.x))
Makie.ylims!(ax, extrema(pd.y))
end
return FigureAndAxes(fig, axes)
end
end
end