Scalar Plots Tutorial#
Scalar quantities calculated by up4
can be visualised by the up4.Plotter2D
class. For ease of use, this class takes 3D grids as an input, and will
appropriately depth-average or slice the data for you. The axis
argument to
methods defined in this class refers to which axis the depth-averaging/ slicing
will happen along (cylindrical equivalents in brackets):
0: slice along x(r)-axis, the plane for the data is y-z (\(\theta\)-z).
1: slice along y(\(\theta\))-axis, the plane for the data is x-z (r-z).
2: slice along z(z)-axis, the plane for the data is x-y (r-\(\theta\)).
Available scalar plotting options are:
It is possible to use either a 3D scalar field (see tutorial) or a
3D vector field, e.g. the velocity field. In the latter case, up4
will
calculate the magnitude of the vector quantity after depth-averaging or slicing.
In the proceeding sections, we will focus on the visualisation of scalar fields
from a rotating drum, that rotates about the x-axis. The data for this can be found in
the tests/fixtures
directory of up4
. To avoid unnecessary code repitition,
each example assumes that above it is the following code block:
import up4
drum_hdf5_file = '/path/to/hdf5/file'
data = up4.Data(drum_hdf5_file)
grid_car = up4.Grid(data=data, num_cells=[20, 20, 20])
Plotting with a cylindrical grid happens in an identical way, but using a Cartesian grid for this example maintains the shape for the drum’s free surface so that each plot is easier to understand.
Heat Map#
We can visualise the depth-averaged velocity field, a vector quantity, as a scalar field
using up4.Plotter2D.scalar_map
:
vel_field = data.vectorfield(grid_car) # velocity vectorfield
plotter = up4.Plotter2D(vel_field) # create a Plotter2D instance
axis = 0 # interested in the y-z plane
plot_layout = dict(
width=800, height=800,
xaxis_title="y position (m)",
yaxis_title="z position (m)",
) # set layout parameters for plot
plot_style = dict(
colorbar_title="Velocity (m s<sup>-1</sup>)"
) # style the trace(s) in the plot
fig = plotter.scalar_map(
axis = axis,
selection = "depth_average",
layout = plot_layout,
style = plot_style
) # depth-average the vector grid
Alternatively, we could view just a slice of the y-z plane:
vel_field = data.vectorfield(grid_car) # velocity vectorfield
plotter = up4.Plotter2D(vel_field) # create a Plotter2D instance
axis = 0 # interested in the y-z plane
# select the y-z plane located at index 2, note that index is a required
# argument for "plane" selection.
fig = plotter.scalar_map(
axis = axis,
selection = "plane",
index = 2,
layout = plot_layout,
style = plot_style
) # reusing formatting defined in the previous example
Note that these two examples could be combined into one, as a up4.Plotter2D
instance
can generate multiple figures based on the input grid provided to it since its plotting
methods return a plotly.graph_objects.Figure
instance.
vel_field = data.vectorfield(grid_car) # velocity vectorfield
plotter = up4.Plotter2D(vel_field) # create a Plotter2D instance
axis = 0 # interested in the y-z plane
plot_layout = dict(
width=800, height=800,
xaxis_title="y position (m)",
yaxis_title="z position (m)",
) # set layout parameters for plot
plot_style = dict(
colorbar_title="Velocity (m s<sup>-1</sup>)"
) # style the trace(s) in the plot
depth_fig = plotter.scalar_map(
axis = axis,
selection = "depth_average",
layout = plot_layout,
style = plot_style
)
# The same Plotter2D instance as above is used here to generate a different figure
# and the formatting can be reused!
plane_fig = plotter.scalar_map(
axis = axis,
selection = "plane",
index = 2,
layout = plot_layout,
style = plot_style
)
Contour#
Instead of the velocity field, what if we wanted to understand the quality of mixing in a granular process. We can do this by drawing contours of the granular temperature, as regions with higher values are better mixed.
gran_temp_field = data.granular_temperature(grid_car) # granular temperature field
plotter = up4.Plotter2D(gran_temp_field) # create a Plotter2D instance
axis = 0 # interested in the y-z plane
plot_layout = dict(
width=800, height=800,
xaxis_title="y position (m)",
yaxis_title="z position (m)",
) # set layout parameters for plot
plot_style = dict(
colorbar_title="Granular Temperature (m<sup>2</sup> s<sup>-2</sup>)"
) # style the trace(s) in the plot
fig = plotter.scalar_contour(
axis = axis,
selection = "depth_average",
layout = plot_layout,
style = plot_style
) # depth-average the vector grid
As before, perhaps we may be interested in a specific plane:
gran_temp_field = data.granular_temperature(grid_car) # granular temperature field
plotter = up4.Plotter2D(gran_temp_field) # create a Plotter2D instance
axis = 0 # interested in the y-z plane
# select the y-z plane located at index 2, note that index is a required
# argument for "plane" selection.
fig = plotter.scalar_contour(
axis = axis,
selection = "plane",
index = 2,
layout = plot_layout,
style = plot_style
) # reusing formatting defined in the previous example
Note that the plots discussed in the tutorials for vector plotting
can also be used on the dataset used in these
tutorials, with the same up4.Plotter2D
instance!
Formatting Scalar Plots#
The methods of up4.Plotter2D
return plotly.graph_objects.Figure
instances, so you can
customise your plots to the same level of detail as natively using plotly. Examples of
this are shown in each code block, where the x- and y-axes have been given
labels, and the colourbar has been given a title. At the Python level, you can either pass
dictionaries of plotly.graph_objects.Layout
and trace
style specifications, or
interact with the returned plotly.graph_objects.Figure
instance directly.
The choice to do this is deliberate as the plotly API in Python and Rust is substantially
different. In Python, it is a fully object-oriented approach with a myriad of optional
arguments, something that Rust cannot handle ergonomically. In Rust, the plotly API is
instead following a functional paradigm. Thus, the choice was made that up4
will instead
expose the figure in a manner compatible with the language’s API.
Finally, saving static plotly images to a required dpi is supported in up4
:
# create a plot
import up4
drum_hdf5_file = '/path/to/hdf5/file'
data = up4.Data(drum_hdf5_file)
grid_car = up4.Grid(data=data, num_cells=[20, 20, 20])
vel_field = data.vectorfield(grid_car) # velocity vectorfield
plotter = up4.Plotter2D(vel_field) # create a Plotter2D instance
axis = 0 # interested in the y-z plane
plot_layout = dict(
width=800, height=800,
xaxis_title="y position (m)",
yaxis_title="z position (m)",
) # set layout parameters for plot
plot_style = dict(
colorbar_title="Velocity (m s<sup>-1</sup>)"
) # style the trace(s) in the plot
fig = plotter.scalar_map(
axis = axis,
selection = "depth_average",
layout = plot_layout,
style = plot_style
) # depth-average the vector grid
# now, save it with a required dpi
dpi = 600 # typical requirement for many journals
up4.save_fig(
fig = fig, # figure to save
filename = "velocity_field.png" # location to save file to
dpi = dpi, # image dpi
border_width = 20, # width of paper border (in mm)
paper_width = 210 # a4 paper width (in mm)
)