11.1. Two-dimensional plotting example
As an example case, let us take the evaporating droplet case from Section 7.6, i.e. we refer to the script evaporating_water_droplet.py. To add plotting, first a specialization of the MatplotlibPlotter class must be implemented. The entire plot is defined in the method define_plot(), in which the one first have to define the field of view, i.e. the area which should be covered by the plot. Since this area often depends on the problem settings, one can access the problem with the get_problem() method. Here, the considered area depends on the radius of the droplet:
from evaporating_water_droplet import * # Import the problem
from pyoomph.output.plotting import *
# Specialize the MatplotlibPlotter for the droplet problem
class DropPlotter(MatplotlibPlotter):
# This method defines the entire plot
def define_plot(self):
p=self.get_problem() # we can get access to the problem by get_problem()
r=p.droplet_radius # and hence can access the droplet base radius
xrange=1.5*r # in x-direction, the image will be from [-1.5*r : 1.5*r ]
self.background_color="darkgrey" # background color
# First step: Set the field of view (xmin,ymin,xmax,ymax)
self.set_view(-xrange,-0.165*xrange,xrange,0.9*xrange)
Also the background_color can be set, where either hex-codes for the color or predefined colors from the python package matplotlib can be used.
Once the desired plot area is selected by the set_view() method (arguments are minimum \(x\), minimum \(y\), maximum \(x\) and maximum \(y\), in (potentially dimensional) spatial coordinates), we can start to add parts to the plot. Usually, one starts with color bars with add_colorbar(), that can are used later on to plot the fields. This can read e.g. as follows:
# Second step: add colorbars with different colormaps at different positions
# with 'factor', you can control the multiplicative factor (i.e. mm/s requires a factor of 1000 to convert the m/s to mm/s)
cb_v=self.add_colorbar("velocity [mm/s]",cmap="seismic",position="bottom right",factor=1000)
cb_vap=self.add_colorbar("water vapor [g/m^3]",cmap="Blues",position="top right",factor=1000)
Each color bar gets first a title and can also contain LaTeXcode, usually by a r-string, e.g. r"$\phi$" to obtain \(\phi\). cmap selects the color map, see the documentation of matplotlib for a reference. With position, we can control the location of the color bar. This can either be a tuple of graph coordinates or a string indicating the position as shown in the example above. By default, all fields are plotted in the normal SI units without any prefixes. If a color bar should indicate the range e.g. in \(\:\mathrm{mm}/\mathrm{s}\), one must set the factor to \(1000\) to compensate for the milli prefix.
Color bars have additional properties, which can be set, e.g. the length, thickness, xshift and yshift, xmargin and ymargin (all in graph coordinates). For a complete list of settings, read e.g. the output of print(dir(cb_v)) or have a look at the MatplotLibColorbar class in the module pyoomph.output.plotting.
Once the color bars are set up, one can plot fields with those. Basically all plots of field data can be done by the add_plot() method, e.g.
# Now, we can add all kinds of plots
# plot the velocity (magnitude, since it is a vector) of the droplet domain (on both sides)
self.add_plot("droplet/velocity",colorbar=cb_v,transform=["mirror_x",None])
# add velocity arrows on both sides
self.add_plot("droplet/velocity", mode="arrows",linecolor="green",transform=["mirror_x",None])
Each add_plot() call requires to pass the data to plot as string, e.g. "droplet/velocity". When a color bar is supplied by the colorbar argument, it will be plotted as color map. Vectorial fields, as e.g. the velocity, will be plotted as magnitude. The color bars will automatically increase in range to comprise the visible data range of all plots with the same color bar.
The argument transform (default None) will apply a transform on the plot, which can e.g. by "mirror_x" to mirror the data (and the vector fields) along the \(x\)-axis. You can also supply a list of transforms to plot all transformed data simultaneously. In that case, the return value of add_plot() is also a list of the individual plots. Further strings indicating transforms are "rotate_cw", "rotate_ccw" and "rotate_ccw_mirror" for clock-wise, counter-clockwise and counter-clockwise rotation including mirroring, respectively. If a custom transform is required, you can overload the base class PlotTransform of pyoomph.output.plotting accordingly and pass an instance of your custom transform class as transform.
If no colorbar is set, you have to specify the plotting mode. To plot e.g. arrows indicating the direction of a vector field, you can use mode="arrows". Alternatively, you can also use mode="streamlines". Each mode has a different class with different settings creating the desired part of the plot. In the pyoomph.output.plotting module, you find all available classes for plot modes. These are decorated by @MatplotLibPart.register() and their class string mode indicates the plotting mode. You can furthermore see the attributes that you can set from the class definitions.
Again, you can access the problem to select a reasonable field to plot, e.g. the vapor on both sides:
# Plot the vapor in the gas phase
self.add_plot("gas/c_vap",colorbar=cb_vap,transform=["mirror_x",None])
To plot interface lines, just use add_plot() where the first argument indicates an interface mesh. This will automatically plot the interface lines:
# at the interface lines
self.add_plot("droplet/droplet_gas",linecolor="yellow",transform=["mirror_x",None])
self.add_plot("droplet/droplet_substrate",transform=["mirror_x",None])
self.add_plot("gas/gas_substrate",transform=["mirror_x",None])
You cannot plot an interface if there is no single equation defined on this interface. In that case, just add a dummy equation to this interface when defining the problem. A dummy equation instances can be just e.g. the base class Equations (or InterfaceEquations), which neither define any fields nor residuals nor doing anything else.
Finally, you can also plot interface fields. These can be either plotted as color maps (mode="interfacecmap", which is selected automatically if you pass a colorbar to add_plot()) or as "interfacearrows". The latter mode will be selected automatically, if you pass a arrowkey argument to add_plot(). However, therefore, you first have to add the arrow key, similar as done with the color bars above by using add_arrow_key():
# For the evaporation rate, we require an arrow key, again with a factor 1000, since we convert from kg to g per m^2s
ak_evap=self.add_arrow_key(position="top center",title="evap. rate water [g/(m^2s)]",factor=1000)
# We can hide instances by setting invisible=True:
# ak_evap.invisible=True
# or we can move it a bit relative to the "position" by the xmargin and ymargin
# or with xshift and yshift. All are in graph coordinates, i.e. 1 means the width/height of the entire image
ak_evap.ymargin+=0.2
ak_evap.xmargin *= 2
# add the evaporation arrows at the interface, both sides
arrs=self.add_plot("droplet/droplet_gas/evap_rate",arrowkey=ak_evap,transform=["mirror_x",None])
Finally, there are few additional global parts (i.e. parts without any field data) you can add. These are e.g. add_text(), add_time_label() or add_scale_bar(). To add e.g. the current time and a scale bar to the plot, you can call
# and a time label and a scale bar
self.add_time_label(position="top left")
self.add_scale_bar(position="bottom center").textsize*=0.7
That is all you have to do in the plotter class. To use it, you just have to create an instance of it to the plotter property of the Problem class:
if __name__=="__main__":
with EvaporatingDroplet() as problem:
problem.plotter=DropPlotter(problem) # set the plotter and pass the problem itself
# The rest is the same as before
# .....
#
Alternatively, you can also set plotter to a list of multiple plotters.
On each output, the plotter(s) will be invoked to create each a plot in the _plots folder of the output directory. For an example of the resulting plot with this plotter class, refer to Fig. 7.6.