.. only:: html
.. image:: psyplot_framework.gif
.. only:: latex
.. image:: psyplot_framework.png
The main module we used so far, was the :mod:`psyplot.project` module. It is the end of a whole framework that is setup by the psyplot package.
This framework is designed in analogy to matplotlibs figure - axes - artist setup, where one figure controls multiple axes, an axes is the manager of multiple artists (e.g. a simple line) and each artist is responsible for visualizing one or more objects on the plot. The psyplot framework instead is defined through the :class:`~psyplot.project.Project` - (:class:`~psyplot.data.InteractiveBase` - :class:`~psyplot.plotter.Plotter`) - :class:`~psyplot.plotter.Formatoption` relationship.
The last to parts in this framework, the :class:`~psyplot.plotter.Plotter` and :class:`~psyplot.plotter.Formatoption`, are only defined through abstract base classes in this package. They are filled with contents in plugins such as the psy-simple or the psy-maps plugin (see :ref:`plugins`).
The :func:`~psyplot.project.project` function
.. currentmodule:: psyplot.project
The :class:`psyplot.project.Project` class (in analogy to matplotlibs :class:`~matplotlib.figure.Figure` class) is basically a list that controls multiple plot objects. It comprises the full functionality of the package and packs it into one class, the :class:`~psyplot.project.Project` class.
In analogy to pyplots :func:`~matplotlib.pyplot.figure` function, a new project can simply be created via
.. ipython::
In [1]: import psyplot.project as psy
In [2]: p = psy.project()
This automatically sets p to be the current project which can be accessed
through the :func:`gcp` method. You can also set the current
project by using the :func:`scp` function.
Note
We highly recommend to use the :func:`project` function to create new projects instead of creating projects from the :class:`Project`. This ensures the right numbering of the projects of old projects.
The project uses the plotters from the :mod:`psyplot.plotter` module to
visualize your data. Hence you can add new plots and new data to the project by
using the :attr:`Project.plot` attribute or the :attr:`psyplot.project.plot`
attribute which targets the current project. The return types of the plotting
methods are again instances of the :class:`Project` class, however we consider
them as subprojects in contrast main projects that are created through the
:func:`project` function. There is basically no difference but the result of the
:attr:`Project.is_main` attribute which is False for subprojects. Hence,
each new plot creates a subproject but also stores the data array in the
corresponding main project of the :class:`Project` instance from which the plot
method has been called. The newly created subproject can be accessed via
.. ipython::
In [3]: sp = psy.gcp()
whereas the current main project can be accessed via
.. ipython::
In [4]: p = psy.gcp(main=True)
Plots created by a specific method of the :attr:`Project.plot` attribute may however be accessed via the corresponding attribute of the :class:`Project` class. The following example creates three subprojects, two with the :attr:`~ProjectPlotter.mapplot` and :attr:`~ProjectPlotter.mapvector` methods from the psy-maps plugin and one with the simple :attr:`~ProjectPlotter.lineplot` method from the psy-simple plugin to visualize simple lines.
.. ipython::
In [5]: import matplotlib.pyplot as plt
In [6]: import cartopy.crs as ccrs
# the subplots for the maps (need cartopy projections)
In [7]: ax = list(psy.multiple_subplots(2, 2, n=3, for_maps=True))
# the subplot for the line plot
In [8]: ax.append(plt.gcf().add_subplot(2, 2, 4))
# scalar field of the zonal wind velocity in the file demo.nc
In [5]: psy.plot.mapplot('demo.nc', name='u', ax=ax[0], clabel='{desc}')
# a second scalar field of temperature
In [6]: psy.plot.mapplot('demo.nc', name='t2m', ax=ax[1], clabel='{desc}')
# a vector plot projected on the earth
In [7]: psy.plot.mapvector('demo.nc', name=[['u', 'v']], ax=ax[2],
...: attrs={'long_name': 'Wind speed'})
@savefig docs_framework_project_demo1.png width=4in
In [8]: psy.plot.lineplot('demo.nc', name='t2m', x=0, y=0, z=range(4),
...: ax=ax[3], xticklabels='%b %d', ylabel='{desc}',
...: legendlabels='%(zname)s = %(z)s %(zunits)s')
The latter is now the current subproject we could access via :func:`psy.gcp() <gcp>`. However we can access all of them through the main project
.. ipython::
In [9]: mp = psy.gcp(True)
In [10]: mp # all arrays
In [11]: mp.mapplot # all scalar fields
In [12]: mp.mapvector # all vector plots
In [13]: mp.maps # all data arrays that are plotted on a map
In [14]: mp.lineplot # the simple plot we created
The advantage is, since every plotter has different formatoptions, we can
now update them very easily. For example lets update the arrowsize to
1 (which only works for the :attr:`~Project.mapvector` plots), the projection
to an orthogonal (which only works for :attr:`~Project.maps`), the simple
plots to use the 'viridis' colormap for color coding the lines and for all
we choose their title corresponding to the variable names
.. ipython::
@suppress
In [15]: with p.maps.no_auto_update:
....: p.maps.update(grid_labels=False)
In [15]: p.maps.update(projection='ortho')
In [16]: p.mapvector.update(color='r', plot='stream', lonlatbox='Europe')
In [17]: p.lineplot.update(color='viridis')
@savefig docs_framework_project_demo2.png width=4in
In [18]: p.update(title='%(long_name)s')
@suppress
In [19]: psy.close('all')
The :class:`~psyplot.data.InteractiveBase` and the :class:`~psyplot.plotter.Plotter` classes
.. currentmodule:: psyplot.plotter
The next level are instances of the :class:`~psyplot.data.InteractiveBase` class. This abstract base class provides an interface between the data and the visualization. Hence a plotter (that's how we call instances of the :class:`Plotter` class) will deal with the subclasses of the :class:`~psyplot.data.InteractiveBase`:
.. autosummary::
~psyplot.data.InteractiveArray
~psyplot.data.InteractiveList
Those classes (in particular the :class:`~psyplot.data.InteractiveArray`) keep the reference to the base dataset to allow the update of the dataslice you are plotting. The :class:`~psyplot.data.InteractiveList` class can be used in a plotter for the visualization of multiple :class:`~psyplot.data.InteractiveArray` instances (see for example the :class:`psyplot.plotter.simple.LinePlotter` and :class:`psyplot.plotter.maps.CombinedPlotter` classes). Furthermore those data instances have a :attr:`~psyplot.data.InteractiveBase.plotter` attribute that is usually occupied by an instance of a :class:`Plotter` subclass.
Note
The :class:`~psyplot.data.InteractiveArray` serves as a
:class:`~xarray.DataArray` accessor. After you imported psyplot, you can
access it via the psy attribute of a :class:`~xarray.DataArray`, i.e.
via
.. ipython::
In [1]: import xarray as xr
In [2]: xr.DataArray([]).psy
Each plotter class is the coordinator of several visualization options. Thereby the :class:`~psyplot.plotter.Plotter` class itself contains only the structural functionality for managing the formatoptions that do the real work. The plotters for the real usage are defined in plugins like the psy-simple or the psy-maps package.
Hence each :class:`~psyplot.data.InteractiveBase` instance is visualized by exactly one :class:`Plotter` class. If you don't want to use the :ref:`project framework <project_framework>`, the initialization of such an instance nevertheless straight forward. Just open a dataset, extract the right data array and plot it
.. ipython::
In [1]: from psyplot import open_dataset
In [2]: from psy_maps.plotters import FieldPlotter
In [3]: ds = open_dataset('demo.nc')
In [4]: arr = ds.t2m[0, 0]
@savefig docs_framework_plotter_demo.png width=4in
In [5]: plotter = FieldPlotter(arr)
Now we created a plotter with all it's formatoptions:
.. ipython::
In [6]: type(plotter), plotter
You can use the :meth:`~Plotter.show_keys`, :meth:`~Plotter.show_summaries` and :meth:`~Plotter.show_docs` methods to have a look into the documentation into the formatoptions or you simply use the builtin :func:`help` function for it:
>>> help(plotter.clabel)
The update methods are the same as for the :class:`~psyplot.project.Project`
class. You can use the :meth:`psyplot.data.InteractiveArray.update` via
arr.psy.update() which updates the data and forwards the formatoptions to
the :meth:`Plotter.update` method.
Note
Plotters are subclasses of dictionaries where each item represents the key-value pair of one formatoption. Anyway, although you could now simply set a formatoption like you set an item for a dictionary via
.. ipython::
In [7]: plotter['clabel'] = 'my label'
or equivalently
.. ipython::
In [7]: plotter.clabel = 'my label'
this would not change the plot! Instead you have to use the :meth:`psyplot.plotter.Plotter.update` method, i.e.
.. ipython::
In [7]: plotter.update(clabel='my label')
@suppress
In [8]: plt.close('all')
Formatoptions are the core of the visualization in the psyplot framework. They conceptually correspond to the basic :class:`matplotlib.artist.Artist` and inherit from the abstract :class:`Formatoption` class. Each plotter is set up through it's formatoptions where each formatoption has a unique formatoption key inside the plotter. This formatoption key (e.g. 'title' or 'clabel') is what is used for updating the plot etc. You can find more information in :ref:`plugins_guide` .