3D plots (meshes)

PlantGeom uses Makie.jl to make 3d mesh plots. It also uses MeshViz.jl that is also based on Makie.

This way the plots you make using PlantGeom support all the nice possibilities offered by Makie, such as making sub-plots, interactive plots...

Interactive plot

Here comes the fun part! We can make 3D representations of the plants based on the geometry of each of its nodes.

If you read your MTG from an OPF file, the 3D geometry should already be computed, so you just have to viz() the MTG.

Because we're plotting the interactive plot in the webpage, we must use JSServe first (no need when using Julia from the REPL or VS Code):

using JSServe
Page(exportable=true, offline=true)

Then we can plot our interactive 3D plant:

using PlantGeom, WGLMakie
mtg = read_opf(joinpath(dirname(dirname(pathof(PlantGeom))),"test","files","simple_plant.opf"))
viz(mtg)
Warning

The plot may take some time to appear on your screen.

Note that the plot is interactive. This is because we use WGLMakie as a plotting backend. You can also use GLMakie for better performance, or CairoMakie if you want a fast, non-interactive plot.

Colors

Note about the backend

In this section, we will use the coffee plant provided as an example OPF file from the package. This one is more realistic than the tiny plant shown above. But because it is bigger, we will only provide static images instead of interactive plots. If you want to plot the interactive plots, you can execute the example code from below using GLMakie or WGLMakie instead of CairoMakie simply by replacing:

using CairoMakie

By:

using GLMakie

Set-up

The first step is to compute the node meshes using the reference meshes and the transformation matrices. This is done very easily by mapping refmesh_to_mesh! to each node of the MTG like so:

using MultiScaleTreeGraph
transform!(mtg, refmesh_to_mesh!)
Note

This step is optional, and not needed if only few plots are performed because it is done automatically when plotting an MTG, but the results are discarded afterward to avoid too much memory usage. If you plant to make many plots, we advise to do this step to avoid to wait a long time each time.

Default colors

The default behavior of viz(mtg) -without providing colors- is to use the color of each reference mesh as the color of the corresponding node mesh. In other words, a leaf in a tree will be colored with the same color as the reference mesh used to represent it. This reference mesh is available as an attribute in the root node of the MTG. We can extract those reference meshes like so:

using PlantGeom, CairoMakie
CairoMakie.activate!()

file = joinpath(dirname(dirname(pathof(PlantGeom))),"test","files","coffee.opf")
mtg = read_opf(file)

ref_meshes = get_ref_meshes(mtg)
RefMeshes(RefMesh[RefMesh{String, SimpleMesh{3, CoordRefSystems.Cartesian{CoordRefSystems.NoDatum, 3, Unitful.Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}, Vector{Meshes.Point{3, CoordRefSystems.Cartesian{CoordRefSystems.NoDatum, 3, Unitful.Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}}}, SimpleTopology{Connectivity{Triangle, 3}}}, Phong{Float64, ColorTypes.RGBA{Float64}}, Vector{Meshes.Vec{3, Unitful.Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}}, Vector{Meshes.Point{2, CoordRefSystems.Cartesian{CoordRefSystems.NoDatum, 2, Unitful.Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}}}}("Mesh0", 50 SimpleMesh, Meshes.Vec{3, Unitful.Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}[Vec(-0.0 m, -0.009980268 m, 0.0006278998399999999 m), Vec(-0.0 m, -0.009822871 m, -0.0018738200999999998 m), Vec(-0.0 m, -0.009510565399999999 m, 0.0030901685 m), Vec(-0.0 m, -0.0090482676 m, -0.004257799 m), Vec(-0.0 m, -0.0084432787 m, 0.005358269 m), Vec(-0.0 m, -0.007705131 m, -0.006374241 m), Vec(-0.0 m, -0.0068454736000000006 m, 0.0072896844 m), Vec(-0.0 m, -0.0058778507 m, -0.008090171 m), Vec(-0.0 m, -0.004817542 m, 0.0087630635 m), Vec(-0.0 m, -0.0036812392 m, -0.009297768 m)  …  Vec(-0.0 m, 0.003681251 m, -0.009297763 m), Vec(-0.0 m, 0.0048175377 m, 0.008763066 m), Vec(-0.0 m, 0.0058778507 m, -0.008090171 m), Vec(-0.0 m, 0.0068454695 m, 0.007289689 m), Vec(-0.0 m, 0.007705131 m, -0.006374241 m), Vec(-0.0 m, 0.0084432787 m, 0.005358269 m), Vec(-0.0 m, 0.009048272000000001 m, -0.004257791 m), Vec(-0.0 m, 0.009510565 m, 0.0030901715000000002 m), Vec(-0.0 m, 0.009822873 m, -0.0018738105999999998 m), Vec(-0.0 m, 0.009980268 m, 0.0006279098 m)], Meshes.Point{2, CoordRefSystems.Cartesian{CoordRefSystems.NoDatum, 2, Unitful.Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}}[Point(x: 0.01 m, y: 0.03078761 m), Point(x: 0.01 m, y: 0.0333009 m), Point(x: 0.01 m, y: 0.02827433 m), Point(x: 0.01 m, y: 0.03581416 m), Point(x: 0.01 m, y: 0.02576106 m), Point(x: 0.01 m, y: 0.038327429999999996 m), Point(x: 0.01 m, y: 0.0232478 m), Point(x: 0.01 m, y: 0.040840709999999995 m), Point(x: 0.01 m, y: 0.02073452 m), Point(x: 0.01 m, y: 0.043353989999999995 m)  …  Point(x: 0.01 m, y: 0.050893810000000005 m), Point(x: 0.01 m, y: 0.01068141 m), Point(x: 0.01 m, y: 0.05340707 m), Point(x: 0.01 m, y: 0.00816814 m), Point(x: 0.01 m, y: 0.05592034 m), Point(x: 0.01 m, y: 0.005654869999999999 m), Point(x: 0.01 m, y: 0.05843362 m), Point(x: 0.01 m, y: 0.0031416100000000004 m), Point(x: 0.01 m, y: 0.060946879999999995 m), Point(x: 0.01 m, y: 0.0006282699999999999 m)], Phong{Float64, ColorTypes.RGBA{Float64}}(RGBA{Float64}(0.0,0.0,0.0,0.0), RGBA{Float64}(0.3,0.45,0.0,0.0), RGBA{Float64}(0.35,0.3,0.0,1.0), RGBA{Float64}(0.2,0.25,0.0,0.0), 10.0), true), RefMesh{String, SimpleMesh{3, CoordRefSystems.Cartesian{CoordRefSystems.NoDatum, 3, Unitful.Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}, Vector{Meshes.Point{3, CoordRefSystems.Cartesian{CoordRefSystems.NoDatum, 3, Unitful.Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}}}, SimpleTopology{Connectivity{Triangle, 3}}}, Phong{Float64, ColorTypes.RGBA{Float64}}, Vector{Meshes.Vec{3, Unitful.Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}}, Nothing}("Mesh1", 12 SimpleMesh, Meshes.Vec{3, Unitful.Quantity{Float64, 𝐋, Unitful.FreeUnits{(m,), 𝐋, nothing}}}[Vec(0.0 m, 0.0 m, 0.01 m), Vec(0.0 m, 0.0 m, 0.01 m), Vec(0.0 m, 0.0 m, 0.01 m), Vec(0.0 m, 0.0 m, 0.01 m), Vec(0.0 m, 0.0 m, 0.01 m), Vec(0.0 m, 0.0 m, 0.01 m), Vec(0.0 m, 0.0 m, 0.01 m), Vec(0.0 m, 0.0 m, 0.01 m), Vec(0.0 m, 0.0 m, 0.01 m), Vec(0.0 m, 0.0 m, 0.01 m), Vec(0.0 m, 0.0 m, 0.01 m)], nothing, Phong{Float64, ColorTypes.RGBA{Float64}}(RGBA{Float64}(0.0,0.0,0.0,0.0), RGBA{Float64}(0.1,0.8,0.1,0.5), RGBA{Float64}(0.1,0.7,0.1,1.0), RGBA{Float64}(0.1,0.9,0.2,0.5), 10.0), true)])

Then we can plot them in sequence:

viz(ref_meshes)
Example block output

Here we are looking at the reference meshes used to build the plant. Those meshes are then transformed by transformation matrices from each node to make the mesh of that node. So by default the color used for the nodes will be taken from these reference meshes.

If we plot the coffee plant without providing any color, we would get:

viz(mtg)
Example block output

Single color

Now we can change the color of all meshes by providing a single color:

viz(mtg, color = :gray87)
Example block output

Map color to reference meshes

We can also associate a new color to each reference mesh.

We can get the default color of each reference mesh by using:

get_ref_meshes_color(ref_meshes)

Now we know the first reference mesh is the cylinder (it is brown) and the second one is the leaf (it is green).

To update their colors we can simply pass the new colors as a dictionary mapping colors to reference meshes like so:

viz(mtg, color = Dict(1 => :gray87, 2 => "#42A25ABD"))
Example block output

If we want to update the second reference mesh only (the leaves), we would do:

viz(mtg, color = Dict(2 => "#42A25ABD"))
Example block output

Map color to attributes

Maybe the most interesting coloring option is to color by attribute.

Indeed, each node in the MTG can have specific attributes, e.g. an area, a temperature...

You can see which attributes are available in an MTG using:

print(names(mtg))
[:geometry, :XInsertionAngle, :Variety, :XEuler, :Stifness, :ref_meshes, :Plagiotropy, :_cache_d9b4f7f3c3467a55ad26f362065777c471aee4c7, :Treatment, :Name, :FileName, :Area, :Length, :Width, :Plot, :YInsertionAngle, :StiffnessAngle, :File, :Phyllotaxy]

We can see that we have an attribute called :Area. Let's color each organ by its area:

viz(mtg, color = :Area)
Example block output

Of course all Makie commands are available. For example we can zoom-in the plot using scale!, and add a colorbar:

f, ax, p = viz(mtg, color = :Area)
CairoMakie.scale!(p, 1.5, 1.5, 1.5) # we zoom-in a little bit
CairoMakie.Colorbar(f[1,2], label = "Area")
f
Example block output

We can see that the colorbar is only in relative values (0-1). If you need absolute values, you can use PlantGeom's colorbar instead:

f, ax, p = viz(mtg, color = :Area)
colorbar(f[1, 2], p)
f
Example block output

Map color to vertices

# Compute the z position of each vertices in each mesh:
transform!(mtg, :geometry => (x -> [Meshes.coords(i).z for i in Meshes.vertices(x.mesh)]) => :z, ignore_nothing = true)
viz(mtg, color = :z, showfacets = true, color_vertex = true)
Note

This one is not shown because CairoMakie and WGLMakie are not compatible with coloring each vertices differently. But you can still see the results on your computer using GLMakie.

Map time step to color

The MTG attributes can have several values, for example a value for each time step of a simulation. For example, let's make a dummy variable with 12 time-steps, each value being the area time the time step:

transform!(mtg, :Area => (x -> [x*i for i in 1:12]) => :dummy_var, ignore_nothing = true)

Now we can plot the plant with the color of each organ being the value of the dummy variable at time step 1 using the index keyword argument:

f, ax, p = viz(mtg, color = :dummy_var, index = 1)
colorbar(f[1, 2], p)
f
Example block output

We can even make a video out of it:

f, ax, p = viz(mtg, color = :dummy_var, index = 1)
colorbar(f[1, 2], p)

record(f, "coffee_steps.mp4", 1:12, framerate=2) do timestep
    p.index[] = timestep
end
"coffee_steps.mp4"