Implementing your own plot functions
Implementing your own plot functions#
my_plot: creator: some_creator # ... plot configuration parameters here ... my_other_plot: creator: another_creator # ... plot configuration parameters for this plot ...
This leads to the
PlotManager instantiating a plot creator
some_creator, which is instructed to create a plot called
The additional parameters are passed to the plot creator, which then uses these for its own purposes.
The same happens for the
my_other_plot plot, which uses
For more information on the
PlotManager, refer to the dantro documentation.
In Utopia, the
PyPlotCreator has a central role, as it forms the basis of several, more specialized plot creators.
The “external” refers to is abiliy to invoke some plot function from an external module or file. Such a plot function can essentially be arbitrary. However, the
PyPlotCreator has some specialized functionality for working with
matplotlib which aims to make plotting more convenient: the
style option and the
Furthermore, it has access to dantro’s data transformation framework.
In practice, the
PyPlotCreator itself is hardly used in Utopia, but it is the base class of the
UniversePlotCreator and the
Thus, the following information is valid for both these specializations and is important to understand before looking at the other creators.
More detail on the specializations themselves is given later.
Let’s assume we have a plotting function defined somewhere and want to communicate to the
PyPlotCreator that this function should be used for some plot. For the moment, the exact definition of the function is irrelevant. You can read more about it in below.
Importing a plotting function from a module#
To import a plot function, the
plot_func entries are required.
The following example shows a plot that uses a plot function from
utopya.plot_funcs and another plot that uses some (importable) package from which the module and the plot function are imported:
--- my_plot: # Import some module from utopya.plot_funcs (note the leading dot) module: .distribution # Use the function with the following name from that module plot_func: my_plot_func # ... all other arguments my_other_plot: # Import a module from any installed package module: my_installed_plotting_package.some_module plot_func: my_plot_func # ... all other arguments
Importing a plotting function from a file#
There are plenty of plot function implementations provided both by utopya and the various Utopia models. However, you might also want to implement a plot function of your own design. This can be achieved by specifying the
module_file key instead of the
module key in the plot configuration. The python module is then loaded from file and the
plot_func key is used to retrieve the plotting function:
--- my_plot: # Load the following file as a python module module_file: ~/path/to/my/python/script.py # Use the function with the following name from that module plot_func: my_plot_func # ... all other arguments (as usual)
The aim of the
PlotHelper framework is to let the plot functions focus on what cannot easily be automated: being the bridge between some selected data and its visualization. The plot function should not have to concern itself with plot aesthetics, as these can be easily automated. The
PlotHelper framework can make your life significantly easier, as it already takes care of most of the plot aesthetics by making large parts of the matplotlib interface accessible via the plot configuration. That way, you don’t need to touch Python code for trivial tasks like changing the plot limits. It also takes care of setting up a figure and storing it in the appropriate location. Most importantly, it will make your plots future-proof and let them profit from upcoming features. For available plot helpers, have a look at the
PlotHelper API reference.
As an example, the following plot configuration sets the title of the plot as well as the labels and limits of the axes:
my_plot: # ... # Configure the plot helpers helpers: set_title: title: This is My Fancy Plot set_labels: x: $A$ y: Counts $N_A$ set_limits: x: [0, max] y: [1.0, ~]
Furthermore, notice how you can combine the capabilities of the plot helper framework with the ability to set the plot style.
Below, you will learn how to implement a plot function that can be used with the plot creator.
When defining a plot function, we recommend using this decorator.
It takes care of providing essential information to the
PyPlotCreator and makes it easy to configure those parameters relevant for the plot function.
For example, to specify which creator should be used for the plot function, the
creator argument can specify the name of a creator.
To control usage of the data transformation framework, the
use_dag flag can be used and the
required_dag_tags argument can specify which data tags the plot function expects.
When using the data transformation framework, the data selection is taken care of by that framework, moving the data selection procedure to the plot configuration.
In the plot function, one can specify which tags are required by the plot function; the framework will then make sure that these results are computed.
In the following example, two tags called
y are required, which are then fed directly to the plot function.
Importantly, such a plot function can be averse to any creator, because it is compatible not only with the
PyPlotCreator but also with all its specializations.
This makes it very flexible in its usage, serving solely as the bridge between data and visualization.
from utopya.eval import is_plot_func, PlotHelper @is_plot_func(use_dag=True, required_dag_tags=("x", "y")) def my_plot(*, data: dict, hlpr: PlotHelper, **plot_kwargs): """A creator-averse plot function using the data transformation framework and the plot helper framework. Args: data: The selected and transformed data, containing specified tags. hlpr: The associated plot helper. **plot_kwargs: Passed on to matplotlib.pyplot.plot """ # Create a lineplot on the currently selected axis hlpr.ax.plot(data["x"], data["y"], **plot_kwargs) # Done! The plot helper saves the plot.
Simple, right? The corresponding plot configuration could look like this:
my_plot: creator: external # Select the plot function # ... # Select data select: x: data/MyModel/some/path/foo y: path: data/MyModel/some/path/bar transform: - mean: [!dag_prev ] - increment: [!dag_prev ] # Perform some transformation on the data transform:  # ... further arguments
For more detail on the syntax, see above.
While the plot function signature can remain as it is regardless of the chosen specialization of the
PyPlotCreator, the plot configuration will differ for the various specializations.
See uni_and_mv_plots for more information.
This is the recommended way to define a plot function, because it outsources a lot of the typical tasks (data selection and plot aesthetics) to dantro, allowing you to focus on implementing the bridge from data to visualization of the data.
Using these features not only reduces the amount of code required in a plot function, but also makes the plot function future-proof. We highly recommend using this interface.
The examples below are for the
PyPlotCreator and might need to be adapted for the specialized plot creators.
Examples for those creators are given in the dantro documentation and here.
Without DAG framework#
If you wish not to use the data transformation framework, simply omit the
use_dag flag or set it to
False in the decorator.
When not using the transformation framework, the
creator_type should be specified, thus binding the plot function to one type of creator.
from utopya import DataManager from utopya.eval import is_plot_func, PlotHelper @is_plot_func() def my_plot(dm: DataManager, *, hlpr: PlotHelper, **additional_kwargs): """A plot function using the plot helper framework. Args: dm: The DataManager object that contains all loaded data. hlpr: The associated plot helper **additional_kwargs: Anything else from the plot config. """ # Select some data ... data = dm['foo/bar'] # Create a lineplot on the currently selected axis hlpr.ax.plot(data) # When exiting here, the plot helper saves the plot.
dm argument is only provided when not using the DAG framework.
If you really want to do everything by yourself, you can also disable the plot helper framework by passing
use_helper=False to the decorator. The
hlpr argument is then not passed to the plot function.
There is an even more basic version of doing this, leaving out the
from utopya import DataManager def my_bare_basics_plot(dm: DataManager, *, out_path: str, **additional_kwargs): """Bare-basics signature required by the ExternalPlotCreator. Args: dm: The DataManager object that contains all loaded data. out_path: The generated path at which this plot should be saved **additional_kwargs: Anything else from the plot config. """ # Your code here ... # Save to the specified output path plt.savefig(out_path)
When using the bare basics version, you need to set the
creator argument in the plot configuration in order for the plot manager to find the desired creator.