"""
Plotting functions used in various figure scripts.
"""
import numpy as np
import matplotlib.path as path
import matplotlib.pyplot as plt
import matplotlib.patches as ptch
from windrose import WindroseAxes
from scipy.stats import binned_statistic_2d
[docs]def plot_wind_rose(theta, U, bins, ax, fig, label=None, boxprops=None,
boxloc=(0.5, 0.05), **kwargs):
"""Plot a wind rose from one dimensional time series.
Parameters
----------
theta : array_like
Wind orientation in the trigonometric convention.
U : array_like
Wind velocity, same shape as `theta`.
bins : list
Velocity bin edges.
ax : matplotlib.axes
ax of the figure on which the wind rose is plotted.
fig : matplotlib.figure
figure on which the wind rose is plotted
label : str or None
if not None, label plotted below the wind rose (default is None).
boxprops : dict
Text box properties (the default is None).
boxloc : list, tuple
Text location (x,y), in ax coordinates (the default is (0.5, 0.05)).
**kwargs :
Optional parameters passed to :func:`windrose.WindroseAxes.bar <windrose.WindroseAxes.bar>`.
Returns
-------
WindroseAxes
return the axe on which the wind rose is plotted. Can be used for further modifications.
"""
# changing ax to have to have the windrose projection
subplotspec = ax.get_subplotspec()
ax.remove()
ax_rose = fig.add_subplot(subplotspec, projection='windrose')
Angle = (90 - theta) % 360
ax_rose.bar(Angle, U, bins=bins, normed=True, zorder=20, opening=1, edgecolor=None,
linewidth=0.5, nsector=60, **kwargs)
ax_rose.grid(True, linewidth=0.4, color='k', linestyle='--')
ax_rose.patch.set_alpha(0.6)
ax_rose.set_axisbelow(True)
ax_rose.set_yticks([])
ax_rose.set_xticklabels([])
ax_rose.set_yticklabels([])
if label is not None:
fig.text(boxloc[0], boxloc[1], label, ha='center', va='center',
transform=ax.transAxes, bbox=boxprops)
return ax_rose
[docs]def plot_flux_rose(angles, distribution, ax, fig, nbins=20, withaxe=0, label=None,
boxprops=None, boxloc=(0.5, 0.05), **kwargs):
"""Short summary.
Parameters
----------
angles : array_like
bin center in orientation of the flux distribution.
distributions : array_like
angular flux distribution.
ax : matplotlib.axes
ax of the figure on which the wind rose is plotted.
fig : matplotlib.figure
figure on which the wind rose is plotted.
nbins : int
number of angular bins for the plot (the default is 20).
withaxe : 0 or 1
Define if the polar axes are plotted or not (the default is 0).
label : str
If not None, sets a label at the bottom of the flux rose (the default is None).
boxprops : dict
Text box properties (the default is None).
boxloc : list, tuple
Text location (x,y), in ax coordinates (the default is (0.5, 0.05)).
**kwargs :
Optional parameters passed to :func:`windrose.WindroseAxes.bar <windrose.WindroseAxes.bar>`.
Returns
-------
WindroseAxes
return the axe on which the wind rose is plotted. Can be used for further modifications.
"""
#
PdfQ = distribution/np.nansum(distribution) # normalization
# creating the new pdf with the number of bins
Lbin = 360/nbins
Bins = np.arange(0, 360, Lbin)
Qdat = []
Qangle = []
precision_flux = 0.001
for n in range(len(Bins)):
ind = np.argwhere((angles >= Bins[n] - Lbin/2) & (angles < Bins[n] + Lbin/2))
integral = int(np.nansum(PdfQ[ind])/precision_flux)
for i in range(integral):
Qangle.append(Bins[n])
Qdat.append(1)
Qangle = np.array(Qangle)
# #### making the plot
ax_rose = WindroseAxes.from_ax(fig=fig)
ax_rose.set_position(ax.get_position(), which='both')
# bars = ax.bar(Angle, Intensity, normed=True, opening=1, edgecolor='k', nsector = Nsector, bins = Nbin, cmap = cmap)
Qangle = (90 - Qangle) % 360
if Qangle.size != 0:
_ = ax_rose.bar(Qangle, Qdat, nsector=nbins, **kwargs)
ax_rose.set_rmin(0)
ax_rose.plot(0, 0, '.', color='w', zorder=100, markersize=3)
# ax_rose.set_yticklabels(['{:.1f}'.format(float(i.get_text())*precision_flux) for i in ax.get_yticklabels()])
if withaxe != 1:
ax_rose.set_yticks([])
if label is not None:
fig.text(boxloc[0], boxloc[1], label, ha='center', va='center',
transform=ax.transAxes, bbox=boxprops)
ax.remove()
return ax_rose
[docs]def plot_scatter_surrounded(x, y, color, alpha):
"""Plot a scatter plot with a black thin line surrounding point clusters.
Parameters
----------
x : array_like
`x` vector.
y : array_like
`x` vector, same shape as `y`.
color : str or array_like
color passed to `c` argument of :func:`matplotlib.pyplot.scatter <matplotlib.pyplot.scatter>`.
alpha : float
alpha passed to :func:`matplotlib.pyplot.scatter <matplotlib.pyplot.scatter>`.
Returns
-------
None
Nothing, it just updates the plot.
"""
plt.scatter(x % 360, y % 360, s=5, c='0.0', lw=0.5, rasterized=True)
plt.scatter(x % 360, y % 360, s=5, c='1.0', lw=0, rasterized=True)
plt.scatter(x % 360, y % 360, s=3, c=color, lw=0, alpha=alpha, rasterized=True)
[docs]def rgba_to_rgb(color):
"""Convert a RGBA color to RGB taking transparency into account. From https://stackoverflow.com/a/52101597/9530017.
Parameters
----------
color : np.array, shape (N, 4)
RGBA color array.
Returns
-------
np.array, shape (N, 3)
RGB color array.
"""
white = np.array([1, 1, 1])
alpha = color[..., -1]
color = color[..., :-1]
return alpha[:, None]*color + (1 - alpha[:, None])*white[None, :]
[docs]def plot_regime_diagram(ax, quantity, vars, lims, xlabel, ylabel, type='scatter', bins=None, **kwargs):
x_var, y_var = vars[0], vars[1]
ax.set_xscale('log')
ax.set_yscale('log')
#
if type == 'binned':
x_bin, y_bin = bins[0], bins[1]
# #### binning data
average, x_edge, y_edge, _ = binned_statistic_2d(x_var, y_var, quantity, statistic='mean', bins=[x_bin, y_bin])
#
# #### making plot
a = ax.pcolormesh(x_edge, y_edge, average.T, snap=True, **kwargs)
elif type == 'scatter':
a = ax.scatter(x_var, y_var, s=5, c=quantity, lw=0, rasterized=True, **kwargs)
#
ax.set_xlim(lims[0])
ax.set_ylim(lims[1])
if xlabel is not None:
ax.set_xlabel(xlabel)
else:
ax.set_xticklabels([])
#
if ylabel is not None:
ax.set_ylabel(ylabel)
else:
ax.set_yticklabels([])
return a
[docs]def make_nice_histogram(data, nbins, ax, vmin=None, vmax=None, scale_bins='lin',
density=True, orientation='vertical', **kwargs):
"""Function making a fancy histogram from input data.
Parameters
----------
data : numpy array, dimensions (N,)
One dimensional input data array
nbins : int
Number of bins
ax : matplotlib.axes
Figure ax on which to plot the data.
vmin : float
Minimum value of the histogram (the default is None).
vmax : float
Maximum value of the histogram (the default is None).
scale_bins : str
If 'lin', the `nbins` are taken linearly, while if 'log', the bins are logarithmically spaced (the default is 'lin').
density : bool
If True, the histogram is normalized such that its integral is unity (the default is True).
orientation : str
If vertical, the histogram bars are vertical and the variable is on thr horizontal axis. If 'horizontal', its the other way around (the default is 'vertical').
**kwargs :
Optional parameters passed to :func:`matplotlib.pyplot.hist <matplotlib.pyplot.hist>`.
Returns
-------
type
Description of returned object.
"""
min = np.nanmin(data) if vmin is None else vmin
max = np.nanmax(data) if vmax is None else vmax
if scale_bins == 'log':
bins = np.logspace(np.log10(min), np.log10(max), nbins)
if orientation == 'vertical':
ax.set_xscale('log')
else:
ax.set_yscale('log')
else:
bins = np.linspace(min, max, nbins)
a = ax.hist(data, bins=bins, histtype='stepfilled',
density=density, orientation=orientation, **kwargs)
ax.hist(data, bins=bins, histtype='step',
color=a[-1][0].get_fc(), density=density, orientation=orientation)
[docs]def plot_arrow(ax, start, end, arrowprops):
"""Plot an arrow using matplotlib :class:`FancyArrowPatch <matplotlib.patches.FancyArrowPatch>`. Note that it can plot dashed arrows without having an ugly head depending on `type` argument, following https://stackoverflow.com/questions/47180328/pyplot-dotted-line-with-fancyarrowpatch.
Parameters
----------
ax : matplotlib axe
Axe on which to plot the arrow
start : tuple, list, numpy array
starting coordinates of the arrow
end : tuple, list, numpy array
starting coordinates of the arrow
arrowprops : dict
`arrowprops` dictionnary passed to matplotlib :class:`FancyArrowPatch <matplotlib.patches.FancyArrowPatch>`.
Returns
-------
Return nothing, only plot the arrow
"""
arrow = ptch.FancyArrowPatch(end, start, **arrowprops)
ax.add_patch(arrow)
if arrow.get_linestyle() != '-':
# Tail
v1 = arrow.get_path().vertices[0:3, :]
c1 = arrow.get_path().codes[0:3]
p1 = path.Path(v1, c1)
pp1 = ptch.PathPatch(p1, color=arrow.get_facecolor(), lw=arrow.get_linewidth(), linestyle=arrow.get_linestyle(), fill=False)
ax.add_patch(pp1)
# Heads ====> partie qui ne marche pas
v2 = arrow.get_path().vertices[3:, :]
c2 = arrow.get_path().codes[3:]
c2[0] = 1
p2 = path.Path(v2, c2)
pp2 = ptch.PathPatch(p2, color=arrow.get_facecolor(), lw=arrow.get_linewidth(), linestyle='-')
ax.add_patch(pp2)
arrow.remove()
[docs]def north_arrow(ax, center, length, length_small=None, width=None, radius=None,
theta=0, textcolor='k', transform=None, **kwargs):
"""Plot a arrow indicating the North on a figure.
Parameters
----------
ax : matplotlib axe
Axe on which to plot the arrow
center : list, tuple, np.array
Position of the arrow
length : float
arrow max length
length_small : float
length of the center par tof the arrow (the default is 0.8*length).
width : float
arrow width (the default is (3/7)*length).
radius : float
distance between the text and the arrow (the default is (45/70)*length).
theta : float
rotation of the arrow indicating the north (the default is 0 for an arrow pointing upward).
textcolor : str
color of the text (the default is 'k').
transform : matplotlib transform
transform for the coordinate systen of the input length and positions (the default is ax.transData).
**kwargs :
Optional parameters passed to :class:`Polygon <matplotlib.patches.Polygon>`, used to customize the arrow.
Returns
-------
None
return nothing
"""
if transform is None:
transform = ax.transData
if length_small is None:
length_small = 0.8*length
if width is None:
width = (3/7)*length
if radius is None:
radius = (45/70)*length
y_start = radius + length - length_small
arrow = np.array([[0, y_start], [width/2, radius],
[0, radius + length], [-width/2, radius], [0, y_start]])
# barycentre = np.sum(arrow, axis=0)/arrow.shape[0]
# arrow = np.dot(Rotation_matrix(theta), (arrow-barycentre).T).T + barycentre
r = np.array(((np.cos(theta), -np.sin(theta)),
(np.sin(theta), np.cos(theta))))
arrow = np.dot(r, arrow.T).T
arrow = arrow + np.array(center)
#
ax.add_patch(ptch.Polygon(arrow, transform=transform, **kwargs))
ax.text(center[0], center[1], r'\textbf{N}', transform=transform,
ha='center', va='center')