"""
Common variables and functions used by modules in gcpy.plot.
"""
from os import path
import warnings
from matplotlib import colors
import numpy as np
# Save warnings format to undo overwriting built into pypdf
_warning_format = warnings.showwarning
# Current directory
_plot_dir = path.dirname(__file__)
# Colormap definitions
_rgb_WhGrYlRd = np.genfromtxt(
path.join(_plot_dir, 'colormaps', 'WhGrYlRd.txt'),
delimiter=' '
)
WhGrYlRd = colors.ListedColormap(_rgb_WhGrYlRd / 255.0)
# Use a style sheet to control plot attributes
gcpy_style = path.join(_plot_dir, "gcpy_plot_style")
[docs]
def six_panel_subplot_names(diff_of_diffs):
"""
Returns the names of the subplots for the 6-panel plots.
Parameters
----------
diff_of_diffs : bool
Indicates if this is a diff-of-diffs benchmark (True)
or not (False). Ratio plots are only included if
diff_of_diffs is False.
Returns
-------
subplots : list of str
List of names of each of the subplots in the 6-panel plot.
"""
if diff_of_diffs:
return ["ref", "dev",
"dyn_absdiff", "res_absdiff",
"dyn_absdiff", "res_absdiff"]
return ["ref", "dev",
"dyn_absdiff", "res_absdiff",
"dyn_ratio", "res_ratio",
]
[docs]
def normalize_colors(
vmin,
vmax,
is_difference=False,
log_color_scale=False,
ratio_log=False
):
"""
Normalizes a data range to the colormap range used by matplotlib
functions. For log-color scales, special handling is done to prevent
taking the log of data that is all zeroes.
Parameters
----------
vmin : float
Minimum value of the data range.
vmax : float
Maximum value of the data range.
is_difference : bool, optional
Set this switch to denote that we are using a difference
color scale (i.e. with zero in the middle of the range).
Default value: False
log_color_scale : bool, optional
Logical flag to denote that we are using a logarithmic
color scale instead of a linear color scale.
Default value: False
ratio_log : bool, optional
Indicates whether we are using log scaling for ratio plots
(True) or not (False).
Default value: False
Returns
-------
norm : matplotlib.colors.Normalize
The normalized matplotlib color range, stored in
a matplotlib Normalize object.
Notes
-----
For log color scales, we will use a range of 3 orders of
magnitude (i.e. from vmax/1e3 to vmax).
"""
# Define class for logarithmic non-symmetric color scheme
class MidpointLogNorm(colors.LogNorm):
"""
Class for logarithmic non-symmetric color scheme
"""
def __init__(
self,
vmin=None,
vmax=None,
midpoint=None,
clip=False
):
super().__init__(vmin, vmax, clip)
self.midpoint = midpoint
def __call__(self, value, clip=None):
result, _ = self.process_value(value)
x_val = [
np.log(self.vmin),
np.log(self.midpoint),
np.log(self.vmax)
]
y_val = [0, 0.5, 1]
return np.ma.array(
np.interp(np.log(value), x_val, y_val),
mask=result.mask,
copy=False
)
# Absolute value of v
abs_vmin = abs(vmin)
abs_vmax = abs(vmax)
if (abs_vmin == 0 and abs_vmax == 0) or \
(np.isnan(vmin) and np.isnan(vmax)):
# If the data is zero everywhere (vmin=vmax=0) or undefined
# everywhere (vmin=vmax=NaN), then normalize the data range
# so that the color corresponding to zero (white) will be
# placed in the middle of the colorbar, where we will
# add a single tick.
if is_difference:
return colors.Normalize(vmin=-1.0, vmax=1.0)
return colors.Normalize(vmin=0.0, vmax=1.0)
# For log color scales, assume a range 3 orders of magnitude
# below the maximum value. Otherwise use a linear scale.
if log_color_scale and not ratio_log:
return colors.LogNorm(vmin=vmax / 1e3, vmax=vmax)
if log_color_scale:
return MidpointLogNorm(vmin=vmin, vmax=vmax, midpoint=1)
# For linear color scales: Normalize between min & max
return colors.Normalize(vmin=vmin, vmax=vmax)
[docs]
def text_to_data_units(ax, text):
"""
Computes the width of a label in data units.
Parameters
----------
ax : matplotlib.axes.Axes
Matplotlib Axes object.
text : matplotlib.text.Text
Text object that is being plotted.
Returns
-------
width_in_data_units : float
Length of the text label in data units.
"""
# Get the extent of the text as a Bbox object
bbox = text.get_window_extent()
# Convert Bbox width from pixels to data units
inv = ax.transData.inverted()
pixel_to_data = inv.transform([[bbox.x0, bbox.y0], [bbox.x1, bbox.y0]])
width_in_data_units = abs(pixel_to_data[1][0] - pixel_to_data[0][0])
return width_in_data_units