#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Feb 25 05:45:52 2020
@author: deborahkhider
Contains all relevant plotting functions
"""
__all__ = [
'set_style',
'showfig',
'savefig',
'closefig',
]
# from tkinter import Variable
import matplotlib.pyplot as plt
import pathlib
import matplotlib as mpl
[docs]def plot_scatter_xy(x1, y1,x2,y2, figsize=None, xlabel=None,
ylabel=None, title=None, xlim=None, ylim=None,
savefig_settings=None, ax=None, legend=True,
plot_kwargs=None, lgd_kwargs=None, mute=False):
''' Plot a scatter on top of a line plot.
Parameters
----------
x1 : array
x axis of timeseries1 - plotted as a line
y1 : array
values of timeseries1 - plotted as a line
x2 : array
x axis of scatter points
y2 : array
y of scatter points
figsize : list
a list of two integers indicating the figure size
xlabel : str
label for x-axis
ylabel : str
label for y-axis
title : str
the title for the figure
xlim : list
set the limits of the x axis
ylim : list
set the limits of the y axis
ax : pyplot.axis
the pyplot.axis object
legend : bool
plot legend or not
lgd_kwargs : dict
the keyword arguments for ax.legend()
plot_kwargs : dict
the keyword arguments for ax.plot()
mute : bool
if True, the plot will not show;
recommend to turn on when more modifications are going to be made on ax
(going to be deprecated)
savefig_settings : dict
the dictionary of arguments for plt.savefig(); some notes below:
- "path" must be specified; it can be any existed or non-existed path,
with or without a suffix; if the suffix is not given in "path", it will follow "format"
- "format" can be one of {"pdf", "eps", "png", "ps"}
Returns
-------
ax : the pyplot.axis object
See Also
--------
pyleoclim.utils.plotting.set_style : set different styles for the figures. Should be set before invoking the plotting functions
pyleoclim.utils.plotting.savefig : save figures
pyleoclim.utils.plotting.showfig : equivalent to plt.show(). Platform-dependent
'''
# handle dict defaults
savefig_settings = {} if savefig_settings is None else savefig_settings.copy()
plot_kwargs = {} if plot_kwargs is None else plot_kwargs.copy()
lgd_kwargs = {} if lgd_kwargs is None else lgd_kwargs.copy()
if ax is None:
fig, ax = plt.subplots(figsize=figsize)
ax.plot(x1, y1, **plot_kwargs, color='green')
ax.scatter(x2, y2, color='red')
if xlabel is not None:
ax.set_xlabel(xlabel)
if ylabel is not None:
ax.set_ylabel(ylabel)
if title is not None:
ax.set_title(title)
if xlim is not None:
ax.set_xlim(xlim)
if ylim is not None:
ax.set_ylim(ylim)
if legend:
ax.legend(**lgd_kwargs)
else:
ax.legend().remove()
if 'fig' in locals():
if 'path' in savefig_settings:
savefig(fig, settings=savefig_settings)
# else:
# if not mute:
# showfig(fig)
return fig, ax
else:
return ax
[docs]def plot_xy(x, y, figsize=None, xlabel=None, ylabel=None, title=None,
xlim=None, ylim=None,savefig_settings=None, ax=None,
legend=True, plot_kwargs=None, lgd_kwargs=None, mute=False,
invert_xaxis=False):
''' Plot a timeseries
Parameters
----------
x : array
The time axis for the timeseries
y : array
The values of the timeseries
figsize : list
a list of two integers indicating the figure size
xlabel : str
label for x-axis
ylabel : str
label for y-axis
title : str
the title for the figure
xlim : list
set the limits of the x axis
ylim : list
set the limits of the y axis
ax : pyplot.axis
the pyplot.axis object
legend : bool
plot legend or not
lgd_kwargs : dict
the keyword arguments for ax.legend()
plot_kwargs : dict
the keyword arguments for ax.plot()
mute : bool
if True, the plot will not show;
recommend to turn on when more modifications are going to be made on ax
(going to be deprecated)
savefig_settings : dict
the dictionary of arguments for plt.savefig(); some notes below:
- "path" must be specified; it can be any existed or non-existed path,
with or without a suffix; if the suffix is not given in "path", it will follow "format"
- "format" can be one of {"pdf", "eps", "png", "ps"}
invert_xaxis : bool, optional
if True, the x-axis of the plot will be inverted
Returns
-------
ax : the pyplot.axis object
See Also
--------
pyleoclim.utils.plotting.set_style : set different styles for the figures. Should be set before invoking the plotting functions
pyleoclim.utils.plotting.savefig : save figures
pyleoclim.utils.plotting.showfig : equivalent to plt.show(). Platform-dependent
'''
# handle dict defaults
savefig_settings = {} if savefig_settings is None else savefig_settings.copy()
plot_kwargs = {} if plot_kwargs is None else plot_kwargs.copy()
lgd_kwargs = {} if lgd_kwargs is None else lgd_kwargs.copy()
if ax is None:
fig, ax = plt.subplots(figsize=figsize)
ax.plot(x, y, **plot_kwargs)
if xlabel is not None:
ax.set_xlabel(xlabel)
if ylabel is not None:
ax.set_ylabel(ylabel)
if title is not None:
ax.set_title(title)
if xlim is not None:
ax.set_xlim(xlim)
if ylim is not None:
ax.set_ylim(ylim)
if legend:
ax.legend(**lgd_kwargs)
else:
ax.legend().remove()
if invert_xaxis:
ax.invert_xaxis()
if 'fig' in locals():
if 'path' in savefig_settings:
savefig(fig, settings=savefig_settings)
# else:
# if not mute:
# showfig(fig)
return fig, ax
else:
return ax
[docs]def stackplot(x, y, figsize=None, xlabel=None, ylabel=None,
xlim=None, ylim=None, title=None,
savefig_settings=None, ax=None, style=None,
plot_kwargs=None, mute=False, color=None):
''' Stack plot of timeseries
Please not that this function uses a different default style than the Pyleoclim package.
To change the style, pass it in the set_style argument
Parameters
----------
x : nested list
x values of individual timeseries
y : nested list
y values of individual timeseries
figsize : list
a list of two integers indicating the figure size
xlabel : str
label for x-axis
ylabel : str
label for y-axis
xlim : list
set the limits of the x axis
ylim : nested list
set the limits of the y axes. Should be the same length as y
title : str
the title for the figure
ax : pyplot.axis
the pyplot.axis object
style : str
style of the plot. See set_style
plot_kwargs : dict
the keyword arguments for ax.plot()
mute : bool
if True, the plot will not show;
recommend to turn on when more modifications are going to be made on ax
(going to be deprecated)
savefig_settings : dict
the dictionary of arguments for plt.savefig(); some notes below:
- "path" must be specified; it can be any existed or non-existed path,
with or without a suffix; if the suffix is not given in "path", it will follow "format"
- "format" can be one of {"pdf", "eps", "png", "ps"}
color : list
list of colors chosen from a particular coloring scheme with the same size as y to distinguish different series
See Also
--------
pyleoclim.utils.plotting.set_style : set different styles for the figures. Should be set before invoking the plotting functions
pyleoclim.utils.plotting.savefig : save figures
pyleoclim.utils.plotting.showfig : equivalent to plt.show(). Platform-dependent
'''
savefig_settings = {} if savefig_settings is None else savefig_settings.copy()
plot_kwargs = {} if plot_kwargs is None else plot_kwargs.copy()
min_x = min([a for i in x for a in i])
max_x = max([a for i in x for a in i])
if style is None:
set_style('journal_spines')
if ax is None:
fig, ax = plt.subplots(len(x),1,figsize=figsize,sharex=True)
fig.subplots_adjust(hspace=0.0001)
for i in range(len(x)):
if color is not None:
ax[i].plot(x[i], y[i], color=color[i])
else:
ax[i].plot(x[i], y[i])
#Set the limits
if xlim is not None:
ax[i].set_xlim(xlim)
else:
ax[i].set_xlim(0, max_x)
if ylim is not None:
ax[i].set_ylim(ylim[i])
# ax[i].set_xticks(np.arange(min_x,max_x))
if xlabel is None:
ax[i].set_xlabel('Time')
if ylabel is None:
ax[i].set_ylabel('Series {}'.format(i + 1))
if i % 2 == 1:
ax[i].yaxis.set_label_position("right")
ax[i].yaxis.tick_right()
ax[i].spines['left'].set_visible(False)
else:
ax[i].spines['right'].set_visible(False)
ax[i].spines['top'].set_visible(False)
if i!=len(x)-1:
ax[i].spines['bottom'].set_visible(False)
if color is not None:
ax[i].tick_params(axis='y', colors=color[i])
ax[i].yaxis.label.set_color(color[i])
ax[i].tick_params(axis='both', which='major', labelsize=12)
if 'fig' in locals():
if 'path' in savefig_settings:
savefig(fig, settings=savefig_settings)
# else:
# if not mute:
# showfig(fig)
return fig, ax
else:
return ax
# ----------
# utilities
# ----------
[docs]def in_notebook():
''' Check if the code is executed in a Jupyter notebook
Returns
-------
bool
'''
try:
from IPython import get_ipython
if 'IPKernelApp' not in get_ipython().config: # pragma: no cover
return False
except ImportError:
return False
return True
[docs]def showfig(fig, close=False):
'''Show the figure
Parameters
----------
fig : matplotlib.pyplot.figure
The matplotlib figure object
close : bool
if True, close the figure automatically
See Also
--------
pyleoclim.utils.plotting.savefig : saves a figure to a specific path
pyleoclim.utils.plotting.in_notebook: Functions to sense a notebook environment
'''
# if in_notebook:
# try:
# from IPython.display import display
# except ImportError as error:
# # Output expected ImportErrors.
# print(f'{error.__class__.__name__}: {error.message}')
# display(fig)
# else:
# plt.show()
plt.show()
if close:
closefig(fig)
[docs]def closefig(fig=None):
'''Show the figure
Parameters
----------
fig : matplotlib.pyplot.figure
The matplotlib figure object
See Also
--------
pyleoclim.utils.plotting.savefig : saves a figure to a specific path
pyleoclim.utils.plotting.in_notebook: Functions to sense a notebook environment
'''
if fig is not None:
plt.close(fig)
else:
plt.close()
[docs]def savefig(fig, path=None, settings={}, verbose=True):
''' Save a figure to a path
Parameters
----------
fig : matplotlib.pyplot.figure
the figure to save
path : str
the path to save the figure, can be ignored and specify in "settings" instead
settings : dict
the dictionary of arguments for plt.savefig(); some notes below:
- "path" must be specified in settings if not assigned with the keyword argument;
it can be any existed or non-existed path, with or without a suffix;
if the suffix is not given in "path", it will follow "format"
- "format" can be one of {"pdf", "eps", "png", "ps"}
See Also
--------
pyleoclim.utils.plotting.showfig : returns a visual of the figure.
'''
if path is None and 'path' not in settings:
raise ValueError('"path" must be specified, either with the keyword argument or be specified in `settings`!')
savefig_args = {'bbox_inches': 'tight', 'path': path}
savefig_args.update(settings)
path = pathlib.Path(savefig_args['path'])
savefig_args.pop('path')
dirpath = path.parent
if not dirpath.exists():
dirpath.mkdir(parents=True, exist_ok=True)
if verbose:
print(f'Directory created at: "{dirpath}"')
path_str = str(path)
if path.suffix not in ['.eps', '.pdf', '.png', '.ps']:
path = pathlib.Path(f'{path_str}.pdf')
fig.savefig(path_str, **savefig_args)
plt.close(fig)
if verbose:
print(f'Figure saved at: "{str(path)}"')
[docs]def set_style(style='journal', font_scale=1.0):
''' Modify the visualization style
This function is inspired by [Seaborn](https://github.com/mwaskom/seaborn).
See a demo in the example_notebooks folder on GitHub to look at the different styles
Parameters
----------
style : {journal,web,matplotlib,_spines, _nospines,_grid,_nogrid}
set the styles for the figure:
- journal (default): fonts appropriate for paper
- web: web-like font (e.g. ggplot)
- matplotlib: the original matplotlib style
In addition, the following options are available:
- _spines/_nospines: allow to show/hide spines
- _grid/_nogrid: allow to show gridlines (default: _grid)
font_scale : float
Default is 1. Corresponding to 12 Font Size.
'''
font_dict = {
'font.size': 12,
'axes.labelsize': 12,
'axes.titlesize': 12,
'xtick.labelsize': 11,
'ytick.labelsize': 11,
'legend.fontsize': 11,
}
style_dict = {}
if 'journal' in style:
style_dict.update({
'axes.axisbelow': True,
'axes.facecolor': 'white',
'axes.edgecolor': 'black',
'axes.grid': True,
'grid.color': 'lightgrey',
'grid.linestyle': '--',
'xtick.direction': 'out',
'ytick.direction': 'out',
'font.sans-serif': ['Arial', 'DejaVu Sans', 'Liberation Sans', 'Bitstream Vera Sans', 'sans-serif'],
'axes.spines.left': True,
'axes.spines.bottom': True,
'axes.spines.right': False,
'axes.spines.top': False,
'legend.frameon': False,
'axes.linewidth': 1,
'grid.linewidth': 1,
'lines.linewidth': 2,
'lines.markersize': 6,
'patch.linewidth': 1,
'xtick.major.width': 1.25,
'ytick.major.width': 1.25,
'xtick.minor.width': 0,
'ytick.minor.width': 0,
})
elif 'web' in style:
style_dict.update({
'figure.facecolor': 'white',
'axes.axisbelow': True,
'axes.facecolor': 'whitesmoke',
'axes.edgecolor': 'lightgrey',
'axes.grid': True,
'grid.color': 'white',
'grid.linestyle': '-',
'xtick.direction': 'out',
'ytick.direction': 'out',
'text.color': 'grey',
'axes.labelcolor': 'grey',
'xtick.color': 'grey',
'ytick.color': 'grey',
'font.sans-serif': ['Arial', 'DejaVu Sans', 'Liberation Sans', 'Bitstream Vera Sans', 'sans-serif'],
'axes.spines.left': False,
'axes.spines.bottom': False,
'axes.spines.right': False,
'axes.spines.top': False,
'legend.frameon': False,
'axes.linewidth': 1,
'grid.linewidth': 1,
'lines.linewidth': 2,
'lines.markersize': 6,
'patch.linewidth': 1,
'xtick.major.width': 1.25,
'ytick.major.width': 1.25,
'xtick.minor.width': 0,
'ytick.minor.width': 0,
})
else:
raise ValueError(f'Style [{style}] not availabel!')
if '_spines' in style:
style_dict.update({
'axes.spines.left': True,
'axes.spines.bottom': True,
'axes.spines.right': True,
'axes.spines.top': True,
})
elif '_nospines' in style:
style_dict.update({
'axes.spines.left': False,
'axes.spines.bottom': False,
'axes.spines.right': False,
'axes.spines.top': False,
})
if '_grid' in style:
style_dict.update({
'axes.grid': True,
})
elif '_nogrid' in style:
style_dict.update({
'axes.grid': False,
})
# modify font size based on font scale
font_dict.update({k: v * font_scale for k, v in font_dict.items()})
for d in [style_dict, font_dict]:
mpl.rcParams.update(d)