XarrayTimelines

class PlasmaCalcs.plotting.xarray_timelines.XarrayTimelines(array, t='t', dims=None, *, werr=False, fill_center=None, cycles=None, short_cycle_ok=False, dmax=UNSET, styles=None, plot=True, label=None, custom_labels=None, add_legend=True, legend_cols_align=True, legend_max_rows=20, skipna=False, robust=UNSET, ymargin=None, ybounds=None, xincrease=None, cstyles=None, cstyles_default=False, **kw_super)

Bases: PlotSettingsMixin

plotting lines vs time for an xarray.

array: xarray.DataArray or xarray.Dataset
the array to plot.
Must have coord or dimension with name self.t (probably ‘t’ or ‘snap’).
if dataset, will be converted to DataArray with new dim named ‘variable’.
t: str
name for the time coordinate (to be plotted along the x axis).
if ‘snap’, use int(snap) for snap in array.coords[‘snap’].
if ‘snap_str’, use int(str(snap)) for snap in array.coords[‘snap’]
if looks like ‘{cname}_index’ or ‘log_{cname}’, and not in array yet,
but cname in array, array.pc.fill_coords() to infer the t coord to use.
if any other value, use array.coords[t].values.
if array.coords[t].dtype==object, convert to strs when plotting.
e.g. [Fluid(‘e’), Fluid(‘H+’)] –> ticks with ‘e’, ‘H+’.
dims: None or list-like of str/None objects.
the dimensions; plot one line for each combination of dimensions.
None –> infer, based on array and t.
list-like –> if any element is None, infer it based on the other dims.
werr: bool, ‘bar’, or ‘fill’
if array is a Dataset with info about mean and std, use this to make plot with error bars.
(else, werr=False is the only valid option.)
True –> equivalent to ‘bar’
‘bar’ –> plt.errorbar. Expect array to have ‘mean’ and ‘std’ data_vars.
‘fill’ –> plt.fill_between. Expect array to have one of the following pairs of data vars:
(‘mean’ and ‘std’, ‘mean+std’ and ‘mean-std’, ‘eval+std’ and ‘eval-std’)
Note: if using werr, can pass any kwargs here, (of plt.errorbar or plt.fill_between) for style.
E.g. capsize=5, elinewidth=3, capthick=5
fill_center: None, bool, or dict
if True or provided, and werr=’fill’ and array has ‘mean’ (or ‘eval’) data_var, also make timeline at center,
using kwargs from __init__ but overwriting some things for the internal call to timelines():
anything provided in fill_center; werr=False, fill_center=False; alpha=1.
(The alpha=1 prevents fill_center from appearing transparent if alpha<1 in __init__.)
cycles: None or list-like of dict/Cycler/None objects.
the styles to use for each dimension. Must have length >= len(dims).
E.g. cycles=[dict(color=[‘r’, ‘b’, ‘g’]), dict(linestyle=[‘-’, ‘–‘])]
–> dims[0] will cycle through colors; dims[1] will cycle through linestyles.
None –> use DEFAULTS.PLOT.TIMELINES_CYCLE0, and (if needed) DEFAULTS.PLOT.TIMELINES_CYCLE1.
(default: [None, {‘ls’: [‘-’, ‘–’, ‘:’, ‘-.’, (0, (3, 1, 1, 1, 1, 1))]}])
if len(dims) > 2, this will fail.
list-like –> if any element is None, use plt.rcParams[‘axes.prop_cycle’] for that dimension;
and cannot have more than 1 None cycle.
short_cycle_ok: bool
whether it is okay for cycles to be shorter than the number of points in that dimension.
dmax: UNSET, None, or int
maximum length of a timelines dimension before crashing (with TooManyPlottablesError).
This prevents accidentally asking to make plot with hundreds of lines or more,
e.g. if array has maindims in it due to forgetting to use ‘mean_’.
Not applied to self.t dimension, which can be any length,
since t goes along an axis instead of having 1 line for each.
UNSET –> self.dmax = DEFAULTS.PLOT.TIMELINES_DMAX (default: 10)
None –> no maximum. Use with caution.
int –> maximum length.
styles: None or list-like of dicts.
the styles to use for each line. i’th line will use styles[i].
if any value in styles dicts conflicts with cycles, use value from syles instead of cycle.
plot: bool
whether to call self.plot() immediately, during init.
label: None, False, or str
if provided, prepend this value to all lines’ labels generated by this XarrayTimelines.
(If it doesn’t end with whitespace, add a single space. E.g. “myinfo” –> “myinfo “.)
Useful if plotting timelines on the same axes as other information.
False –> use label=’’ for all lines.
custom_labels: None or str
if provided, use this label instead of label + dim info;
will be passed dict of nondim coord values for each line.
E.g. custom_labels=’u_{fluid},{component}’ –> ‘u_H+,x’ when fluid=’H+’ and component=’x’.
add_legend: bool
whether to plot a legend, by default.
legend_cols_align: bool
whether to align legend cols such that one of the dims (the longest one), is the same in each row.
E.g. if True, and self.dims = [‘fluid’, ‘component’], with more fluids than components,
then ‘fluid’ will be the same across each row.
Only applied if len(self) > legend_max_rows > (number of rows when cols aligned)
legend_max_rows: None or int
maximum number of rows in the legend. Add legend columns if len(self) > legend_max_rows.
(For more precise control, make your own legend after plotting…)
None –> no maximum.
skipna: bool
whether to drop NaN values before plotting each line of values.
robust: UNSET, bool, or number between 0 and 50 (default: UNSET)
use np.percentile when determining vmin/vmax, if robust.
For imshow/image plots, this refers to colorbar lims; for line plots, this refers to y lims.
UNSET –> use DEFAULTS.PLOT.ROBUST (default: True).
False –> just use min and max of values, don’t use percentile.
True –> use DEFAULTS.PLOT.ROBUST_PERCENTILE (default: 2.0)
number –> use np.percentile with this percentile for vmin (and 100 - this percentile for vmax).
Will consider minimum vmin and maximum vmax across all lines, to avoid fully-hiding lines.
ymargin: None or number (greater than -0.5, probably close to 0.05) (default: None)
margin to use for x/y axis, as a fraction of the data interval for that axis.
None –> use matplotlib defaults
(e.g., plt.rcParams[“axes.xmargin”] or [“axes.ymargin”], or 0 if using imshow)
positive number –> pad around the data region, with this much whitespace.
E.g. 0.05 means adding 5% whitespace on each side.
Use this to zoom out.
negative number –> remove this much of the outer parts of the data region.
E.g. -0.2 means removing 20% space from each side.
Use this to zoom in.
For line plots, if also using robust, ymargin will be applied to the robust y lims.
(margin-related params share the same docstring, but refer to:
‘xmargin’: x-axis, ‘ymargin’: y-axis, ‘margin’: x and/or y-axis.)
ybounds: None, False, or 2-tuple/iterable of [max_ymin, min_ymax] (default: None)
tells the (maximum ymin, minimum ymax) when determining ylims.
None –> use the current ylims if current_axes_has_data(), else (None, None).
False –> equivalent to (None, None), i.e. ignore this setting.
E.g., overlaying multiple plots; plot 1 from -10 to 10, plot 2 from -5 to 15;
by default would use plot 2 ybounds=(-10, 10) –> final ylims of (-10, 15).
If provided plot 2 ybounds=(-7, 12), would instead have ylims of (-7, 15).
xincrease: None or bool
whether the x-axis should increase from left to right.
None –> False if monotonically nonincreasing. I.e., False if all(t[i+1] <= t[i]), else True.
(“if t values are obviously inverted, take it as a hint from user to do xincrease=False”)
cstyles: None or dict of {coordname: dict or list of tuples of (val, dict of kwargs for a line)}
if provided, pass these dicts to individual lines with corresponding scalar val for coord.
use tuples of values to test equality instead of indexing a dict.
E.g., styles={‘fluid’: [(‘e’, dict(ls=’–‘)), (‘H_II’, dict(color=’blue’))]}
would ensure dashed line when arr[‘fluid’]==’e’, blue line when arr[‘fluid’]==’H_II’,
and have no effect whenever arr[‘fluid’] is not scalar, doesn’t exist, or isn’t ‘e’ or ‘H_II’.
cstyles_default: bool
tells how to handle conflict between kwargs from cstyles and other kwargs.
True –> treat cstyles as ‘defaults’; kwargs from other sources override kwargs from cstyles.
False –> kwargs from cstyles take precedence.
Additional kwargs go to super().__init__, and eventually to a plotting routine. Options include:
- self.plot_settings.get_mpl_kwargs(‘plt.legend’)
- self.plot_settings.get_mpl_kwargs(‘plt.plot’)
- self.plot_settings.get_mpl_kwargs(‘plt.errorbar’) # if werr=True or ‘bar’
- self.plot_settings.get_mpl_kwargs(‘plt.fill_between’) # if werr=’fill’
(Note that any plot settings which also appear in cycles will use the cycles values,
e.g. if ‘linestyle’ appears in cycle but also in kwargs, use the cycle value instead.)
— Examples —
import PlasmaCalcs as pc
# basic usage:
array.pc.timelines() # make timelines plot with default settings.
# alternative call signature:
pc.XarrayTimelines(array) # same result as above ^
# specify cycle:
# for dims[0], use plt.rcParams[‘axes.prop_cycle’];
# for dims[1], use a gradient in alpha (instead of the default, which is different linestyles):
array.pc.timelines(cycles=[None, dict(alpha=np.linspace(1, 0.3, 10)])
# use linewidth and linestyle in cycle for dims[1], use colors for dims[0]:
array.pc.timelines(cycles=[None, dict(linewidth=[5, 4, 3, 2], linestyle=[‘-’, ‘–’, ‘:’, ‘-.’])])
# specify handlelength (for legend, ensure lines are long enough to see):
array.pc.timelines(handlelength=5)

Methods

__init_subclass__(*args_super, **kw)

appends note about using self.plot_settings, to cls.__doc__.

__len__()

returns number lines which self would plot if plotted.

add_labels()

add labels to plot, based on self.array.

get_effective_data_interval(*[, robust])

returns (vmin, vmax) taken across all lines in self.

iter_dimpoints()

iterates through points across all dimensions.

legend([ncols])

plot the legend for the current plot, via plt.legend().

plot(*[, legend, add_labels, robust, ...])

plot the lines vs time.

set_ylim(*[, robust, ymargin, ybounds])

sets plt.ylim() to a nice range for viewing all lines from self.

_check_size()

crash (with TooManyPlottablesError), if the number of lines to plot is too large.

_get_cyclers([called, indexable])

return list of Cycler objects.

_get_idx_cstyle(idx)

returns the cstyle for plot line at this dimpoint index (a dict).

_get_ith_style(i)

returns style for ith line on the plot, inferred from self.styles.

_get_label(dimsel, *[, fmtn, fmts])

return labels given this dict of {dim: (i for this dim)} for dims in self.dims.

_get_t_values()

return values for the time axis.

_get_ybounds(*[, ybounds])

get ybounds to use later; call before plotting, then pass to self.set_ylim after plotting

_infer_dims(dims)

infer dims from self.array, as needed.

_plot_lines(**kw_plot)

plot the lines vs time.

classmethod __init_subclass__(*args_super, **kw)

appends note about using self.plot_settings, to cls.__doc__.

if “PlotSettings” or “plot_settings” appears in cls.__doc__, do NOT append this note;
assuming instead that this means the doc already mentions how to use plot_settings.
__len__()

returns number lines which self would plot if plotted.

_check_size()

crash (with TooManyPlottablesError), if the number of lines to plot is too large.

I.e., if any of the dims have length > self.dmax.
_get_cyclers(called=False, *, indexable=True)

return list of Cycler objects. Get from self.cycles, or defaults if self.cycles is None.

result must have length >= len(self.dims).

Each cycle must have length >= number of points in the corresponding dimension,

unless self.short_cycle_ok is True.
called: bool
if True, call each cycle before returning it. (Turning it into an itertools.cycle() result)
Equivalent to [cycle() for cycle in self._get_cyclers(called=False)].
indexable: bool
if True, convert each result to IndexableCycler. Ignored if called=True.
_get_idx_cstyle(idx)

returns the cstyle for plot line at this dimpoint index (a dict).

_get_ith_style(i)

returns style for ith line on the plot, inferred from self.styles.

_get_label(dimsel, *, fmtn='{:.3g}', fmts='{!s:s}')

return labels given this dict of {dim: (i for this dim)} for dims in self.dims.

This corresponds to 1 timeline in the plot.
if custom_labels provided instead format it, by providing the dict of
self.array.sel(dimsel).pc.nondims_coords(scalars_only=True, item=True).
fmtn: str
attempt to format values using this format string.
if fails (with ValueError or TypeError), use fmts instead.
fmts: str
format string to use if fmtn fails.
(default ‘{!s:s}’… ‘!s’ converts to str; ‘:s’ is the format specifier)
_get_t_values()

return values for the time axis.

_get_ybounds(*, ybounds=UNSET)

get ybounds to use later; call before plotting, then pass to self.set_ylim after plotting

to ensure any previous plots on this axes don’t get cut off.
robust: UNSET, bool, or number between 0 and 50 (default: UNSET)
use np.percentile when determining vmin/vmax, if robust.
For imshow/image plots, this refers to colorbar lims; for line plots, this refers to y lims.
UNSET –> use DEFAULTS.PLOT.ROBUST (default: True).
False –> just use min and max of values, don’t use percentile.
True –> use DEFAULTS.PLOT.ROBUST_PERCENTILE (default: 2.0)
number –> use np.percentile with this percentile for vmin (and 100 - this percentile for vmax).
If robust, take min vmin(line) and max vmax(line) across all lines.
If UNSET, use self.plot_settings.get(‘robust’). (if still UNSET, use behavior described above.)
ymargin: None or number (greater than -0.5, probably close to 0.05) (default: None)
margin to use for x/y axis, as a fraction of the data interval for that axis.
None –> use matplotlib defaults
(e.g., plt.rcParams[“axes.xmargin”] or [“axes.ymargin”], or 0 if using imshow)
positive number –> pad around the data region, with this much whitespace.
E.g. 0.05 means adding 5% whitespace on each side.
Use this to zoom out.
negative number –> remove this much of the outer parts of the data region.
E.g. -0.2 means removing 20% space from each side.
Use this to zoom in.
For line plots, if also using robust, ymargin will be applied to the robust y lims.
(margin-related params share the same docstring, but refer to:
‘xmargin’: x-axis, ‘ymargin’: y-axis, ‘margin’: x and/or y-axis.)
If None, use self.plot_settings.get(‘ymargin’) instead.
ybounds: None, False, or 2-tuple/iterable of [max_ymin, min_ymax] (default: None)
tells the (maximum ymin, minimum ymax) when determining ylims.
None –> use the current ylims if current_axes_has_data(), else (None, None).
False –> equivalent to (None, None), i.e. ignore this setting.
E.g., overlaying multiple plots; plot 1 from -10 to 10, plot 2 from -5 to 15;
by default would use plot 2 ybounds=(-10, 10) –> final ylims of (-10, 15).
If provided plot 2 ybounds=(-7, 12), would instead have ylims of (-7, 15).
if UNSET, use self.plot_settings.get(‘ybounds’) instead.
_infer_dims(dims)

infer dims from self.array, as needed. return result.

_plot_lines(**kw_plot)

plot the lines vs time. Probably via plt.plot.

(if self.werr, might instead use plt.errorbar or plt.fill_between.)
Does not do any of the nice preprocessing or postprocessing to help with formatting.
User should use self.plot() instead.
returns dict of info, including ‘any_legend_labels’ telling whether any labels were added
(if no labels were added, later functions should not add legend.)
add_labels()

add labels to plot, based on self.array.

get_effective_data_interval(*, robust=UNSET)

returns (vmin, vmax) taken across all lines in self.

If robust, take min vmin(line) and max vmax(line) across all lines.
robust: UNSET, bool, or number between 0 and 50 (default: UNSET)
use np.percentile when determining vmin/vmax, if robust.
For imshow/image plots, this refers to colorbar lims; for line plots, this refers to y lims.
UNSET –> use DEFAULTS.PLOT.ROBUST (default: True).
False –> just use min and max of values, don’t use percentile.
True –> use DEFAULTS.PLOT.ROBUST_PERCENTILE (default: 2.0)
number –> use np.percentile with this percentile for vmin (and 100 - this percentile for vmax).
If UNSET, use self.plot_settings.get(‘robust’). (if still UNSET, use behavior described above.)
iter_dimpoints()

iterates through points across all dimensions.

Yields (dimsel, style) pairs, where:
dimsel = dict of {dim: (i of dim at this point)} for dim in self.dims
style = dict of {matplotlib kwarg: value} which are relevant at this point.
only includes relevant cstyles if not self.cstyles_default.
legend(ncols=None)

plot the legend for the current plot, via plt.legend().

legend anchored to upper right corner of plot, outside the axes.
if self.legend_max_rows is not None:
put a limit on nrows.
if self.legend_cols_align is True:
align the columns such that (the longest) one of the dims is the same in each row.
Only applied if len(self) > legend_max_rows > (number of rows when cols aligned)
ncols: None or int
number of columns to use in the legend.
None –> picks a decent-looking value, based on self.legend_max_rows.
For more precise legend control, use plt.legend() instead.
plot(*, legend=UNSET, add_labels=True, robust=UNSET, ymargin=None, ybounds=UNSET, **kw_plot)

plot the lines vs time. Probably via plt.plot.

(if self.werr, might instead use plt.errorbar or plt.fill_between.)
legend: UNSET or bool
whether to plt.legend().
UNSET –> use legend = self.add_legend (default: True)
note: if all labels are None or empty string, will never attempt to add legend.
add_labels: bool
whether to self.add_labels() to plot, based on self.array:
xlabel (self.t), and ylabel if known (self.array.name).
Also put units on those labels if self.array.attrs[‘units’] is provided.
robust: {robust}
If robust, take min vmin(line) and max vmax(line) across all lines.
If UNSET, use self.plot_settings.get(‘robust’). (if still UNSET, use behavior described above.)
ymargin: {ymargin}
If None, use self.plot_settings.get(‘ymargin’) instead.
ybounds: {ybounds}
if UNSET, use self.plot_settings.get(‘ybounds’) instead.
set_ylim(*, robust=UNSET, ymargin=None, ybounds=UNSET)

sets plt.ylim() to a nice range for viewing all lines from self.

robust: UNSET, bool, or number between 0 and 50 (default: UNSET)
use np.percentile when determining vmin/vmax, if robust.
For imshow/image plots, this refers to colorbar lims; for line plots, this refers to y lims.
UNSET –> use DEFAULTS.PLOT.ROBUST (default: True).
False –> just use min and max of values, don’t use percentile.
True –> use DEFAULTS.PLOT.ROBUST_PERCENTILE (default: 2.0)
number –> use np.percentile with this percentile for vmin (and 100 - this percentile for vmax).
If robust, take min vmin(line) and max vmax(line) across all lines.
If UNSET, use self.plot_settings.get(‘robust’). (if still UNSET, use behavior described above.)
ymargin: None or number (greater than -0.5, probably close to 0.05) (default: None)
margin to use for x/y axis, as a fraction of the data interval for that axis.
None –> use matplotlib defaults
(e.g., plt.rcParams[“axes.xmargin”] or [“axes.ymargin”], or 0 if using imshow)
positive number –> pad around the data region, with this much whitespace.
E.g. 0.05 means adding 5% whitespace on each side.
Use this to zoom out.
negative number –> remove this much of the outer parts of the data region.
E.g. -0.2 means removing 20% space from each side.
Use this to zoom in.
For line plots, if also using robust, ymargin will be applied to the robust y lims.
(margin-related params share the same docstring, but refer to:
‘xmargin’: x-axis, ‘ymargin’: y-axis, ‘margin’: x and/or y-axis.)
If None, use self.plot_settings.get(‘ymargin’) instead.
ybounds: None, False, or 2-tuple/iterable of [max_ymin, min_ymax] (default: None)
tells the (maximum ymin, minimum ymax) when determining ylims.
None –> use the current ylims if current_axes_has_data(), else (None, None).
False –> equivalent to (None, None), i.e. ignore this setting.
E.g., overlaying multiple plots; plot 1 from -10 to 10, plot 2 from -5 to 15;
by default would use plot 2 ybounds=(-10, 10) –> final ylims of (-10, 15).
If provided plot 2 ybounds=(-7, 12), would instead have ylims of (-7, 15).
if UNSET, use self.plot_settings.get(‘ybounds’) instead.