This tutorial explains the high-level interface to plotting provided by the Bundle. You are of course always welcome to access arrays and plot manually.
The default plotting backend in PHOEBE is matplotlib, and this tutorial will focus solely on matplotlib plots and will assume some familiarity with matplotlib and its terminology (ie axes, artists, subplots, etc).
Let's first make sure we have the latest version of PHOEBE 2.0 installed. (You can comment out this line if you don't use pip for your installation or don't want to update to the latest release).
In [ ]:
!pip install -I "phoebe>=2.0,<2.1"
This first line is only necessary for ipython noteboooks - it allows the plots to be shown on this page instead of in interactive mode
In [1]:
%matplotlib inline
As always, let's do imports and initialize a logger and a new Bundle. See Building a System for more details.
In [2]:
import phoebe
from phoebe import u # units
import numpy as np
import matplotlib.pyplot as plt
logger = phoebe.logger()
b = phoebe.default_binary()
b['q'] = 0.8
b['ecc'] = 0.1
b['irrad_method'] = 'none'
And we'll attach some dummy datasets. See Datasets for more details.
In [3]:
b.add_dataset('orb', times=np.linspace(0,4,1000), dataset='orb01', component=['primary', 'secondary'])
times, fluxes, sigmas = np.loadtxt('test.lc.in', unpack=True)
b.add_dataset('lc', times=times, fluxes=fluxes, sigmas=sigmas, dataset='lc01')
Out[3]:
And run the forward models. See Computing Observables for more details.
In [4]:
b.set_value('incl@orbit', 90)
b.run_compute(model='run_with_incl_90')
b.set_value('incl@orbit', 85)
b.run_compute(model='run_with_incl_85')
b.set_value('incl@orbit', 80)
b.run_compute(model='run_with_incl_80')
Out[4]:
NOTE: in IPython notebooks calling plot will display directly below the call to plot. When not in IPython you have several options for viewing the figure:
In [5]:
axs, artists = b.plot()
Any call to plot returns 2 lists - a list of the axes and a list of the artists that were drawn on those axes. Generally we won't need to do anything with these, but having them returned could come in handy if you want to manually edit those axes or artists before saving the image.
In this example with so many different models and datasets, it is quite simple to build a single plot by filtering the bundle and calling the plot method on the resulting ParameterSet.
In [6]:
axs, artists = b['orb@run_with_incl_80'].plot()
The built-in plot method also provides convenience options to either highlight the interpolated point for a given time, or only show the dataset up to a given time.
The higlight option is enabled by default so long as a time (or times) is passed to plot. It simply adds an extra marker at the sent time - interpolating in the synthetic model if necessary.
In [7]:
axs, artists = b['orb@run_with_incl_80'].plot(time=1.0)
In [8]:
axs, artists = b['orb@run_with_incl_80'].plot(time=1.0, highlight_marker='s', highlight_color='g', highlight_ms=20)
To disable highlighting, simply send highlight=False
In [9]:
axs, artists = b['orb@run_with_incl_80'].plot(time=1.0, highlight=False)
In [10]:
axs, artists = b['orb@run_with_incl_80'].plot(time=1.0, uncover=True)
In [11]:
axs, artists = b['primary@orb@run_with_incl_80'].plot()
In [12]:
axs, artists = b.plot(component='primary', kind='orb', model='run_with_incl_80')
In [13]:
axs, artists = b.plot('primary@orb@run_with_incl_80')
An advantage to this last approach (providing a twig as a positional argument to the plot method) is that it can accept multiple positional arguments to plot from multiple datasets in a single call.
When these have the same dataset method, they will automatically be drawn to the same axes (by default).
In [14]:
axs, artists = b.plot('primary@orb@run_with_incl_80', 'secondary@orb@run_with_incl_80')
If the datasets have multiple dataset kinds, subplots will automatically be created.
In [15]:
axs, artists = b['run_with_incl_80'].plot('primary@orb', 'lc01')
Later we'll see how to customize the layout of these subplots in the figure and how to pass other plotting options.
In [16]:
axs, artists = b['orb01@primary@run_with_incl_80'].plot(x='times', y='vxs')
To see the list of available qualifiers that could be passed for x or y, call the qualifiers (or twigs) property on the ParameterSet.
In [17]:
b['orb01@primary@run_with_incl_80'].qualifiers
Out[17]:
For more information on each of the available arrays, see the relevant tutorial on that dataset method:
In [18]:
axs, artists = b['lc01@dataset'].plot(x='phases', yerrors=None)
In [19]:
axs, artists = b['orb01@primary@run_with_incl_80'].plot(xunit='AU', yunit='AU')
WARNING: when plotting two arrays with the same dimensions, PHOEBE attempts to set the aspect ratio to equal, but overriding to use two different units will result in undesired results. This may be fixed in the future, but for now can be avoided by using consistent units for the x and y axes when they have the same dimensions.
In [20]:
axs, artists = b['orb01@primary@run_with_incl_80'].plot(xlabel='X POS', ylabel='Z POS')
In [21]:
axs, artists = b['orb01@primary@run_with_incl_80'].plot(xlim=(-2,2))
In [22]:
axs, artists = b['lc01@dataset'].plot(yerrors='sigmas')
To disable the errorbars, simply set yerrors=None.
In [23]:
axs, artists = b['lc01@dataset'].plot(yerrors=None)
Colors of points and lines, by default, cycle according to matplotlib's color policy. To manually set the color, simply pass a matplotlib recognized color to the color keyword.
In [24]:
axs, artists = b['orb01@primary@run_with_incl_80'].plot(color='r')
In addition, you can point to an array in the dataset to use as color.
In [25]:
axs, artists = b['orb01@primary@run_with_incl_80'].plot(time=1.0, x='times', color='vzs')
Choosing colors works slightly differently for meshes (ie you can set facecolor and edgecolor and facecmap and edgecmap). For more details, see the tutorial on the mesh dataset.
The colormaps is determined automatically based on the parameter used for coloring (ie RVs will be a red-blue colormap). To override this, pass a matplotlib recognized colormap to the cmap keyword.
In [26]:
axs, artists = b['orb01@primary@run_with_incl_80'].plot(time=1.0, x='times', color='vzs', cmap='spring')
To add a legend, simply call plt.legend (for the current axes) or ax.legend on one of the returned axes.
For details on placement and formatting of the legend see matplotlib's documentation.
In [27]:
axs, artists = b['orb@run_with_incl_80'].plot()
legend = plt.legend()
The legend labels are generated automatically, but can be overriden by passing a string to the label keyword.
In [28]:
axs, artists = b['primary@orb@run_with_incl_80'].plot(label='primary')
axs, artists = b['secondary@orb@run_with_incl_80'].plot(label='secondary')
legend = axs[0].legend()
In [29]:
axs, artists = b['orb01@primary@run_with_incl_80'].plot(linestyle=':', linewidth=4)
The plot method can both optionally take and return a matplotlib axes object. This makes it quite easy to quickly build a figure with multiple subplots.
Below we'll mix a bunch of different ways to call plotting , and mix in highlighting
and uncovering. The only real difference here from before is that we pass a
single matplotlib axes to the plot call - that is the axes on which all lines
will be drawn during that call, even if it loops and creates multiple lines.
The actual axes instance is returned, and we want to create the legend on that
axes.
In [30]:
fig = plt.figure(figsize=(14,10))
ax = [fig.add_subplot(2,2,i+1) for i in range(4)]
axs, artists = b.plot('orb01@primary', y='ys', ax=ax[0])
ax[0].legend()
axs, artists = b['orb01@run_with_incl_80'].plot(y='ys', linestyle='--', time=5, uncover=True, ax=ax[1])
ax[1].legend()
axs, artists = b.plot(dataset='orb01', y='ys', ax=ax[2])
ax[2].legend()
axs, artists = b.plot(dataset='orb01', model='run_with_incl_80', x='times', y='vys', time=5, uncover=True, ax=ax[3])
ax[3].legend()
Out[30]:
Alternatively, this can be done in a single call to plot by passing dictionaries as positional arguments. Each dictionary, in essence, is passed on to its own plot call.
In [31]:
fig = plt.figure(figsize=(14,10))
ax = [fig.add_subplot(2,2,i+1) for i in range(4)]
plot1 = {'twig': 'orb01@primary', 'y': 'ys', 'ax':ax[0]}
plot2 = {'twig': 'orb01@run_with_incl_80', 'y': 'ys', 'linestyle': '--', 'time': 5, 'uncover': True, 'ax':ax[1]}
plot3 = {'twig': 'orb01', 'y': 'ys', 'ax': ax[2]}
plot4 = {'dataset': 'orb01', 'model': 'run_with_incl_80', 'x': 'times', 'y': 'vys', 'time': 5, 'uncover': True, 'ax': ax[3]}
axs, artists = b.plot(plot1, plot2, plot3, plot4)
for axi in ax:
axi.legend()
Note that now when passing additional arguments, those will apply as defaults to EACH of the dictionaries, but will not override any values explicitly provided in the dictionaries.
In [32]:
fig = plt.figure(figsize=(14,10))
ax = [fig.add_subplot(2,2,i+1) for i in range(4)]
plot1 = {'twig': 'orb01@primary', 'y': 'ys', 'ax':ax[0]}
plot2 = {'twig': 'orb01@run_with_incl_80', 'y': 'ys', 'linestyle': '--', 'time': 5, 'uncover': True, 'ax':ax[1]}
plot3 = {'twig': 'orb01', 'y': 'ys', 'ax': ax[2]}
plot4 = {'dataset': 'orb01', 'model': 'run_with_incl_80', 'x': 'times', 'y': 'vys', 'time': 5, 'uncover': True, 'ax': ax[3]}
axs, artists = b.plot(plot1, plot2, plot3, plot4, x='xs', y='zs', color='r')
for axi in ax:
axi.legend()
In [33]:
figure = plt.figure()
ax = figure.add_subplot(111, projection='3d')
axes, artists = b['orb@run_with_incl_80'].plot(time=0, facecolor='teffs', edgecolor=None, ax=ax)
Next (and last) up: let's plot meshes.