# gbmplot.py: Module containing the base plot class and plot element classes
#
# Authors: William Cleveland (USRA),
# Adam Goldstein (USRA) and
# Daniel Kocevski (NASA)
#
# Portions of the code are Copyright 2020 William Cleveland and
# Adam Goldstein, Universities Space Research Association
# All rights reserved.
#
# Written for the Fermi Gamma-ray Burst Monitor (Fermi-GBM)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
import re
from functools import partial
from matplotlib.colors import colorConverter, PowerNorm
import matplotlib.patches as patches
import matplotlib.ticker as ticker
from matplotlib.figure import Figure
from matplotlib.path import Path
from matplotlib.ticker import AutoMinorLocator
from .lib import *
from ..detectors import Detector
[docs]class GbmPlot:
"""Base for plots of GBM data.
Note:
This class is not intended to be instantiated directly by the user,
rather it is inherited by different plot classes.
Parameters:
figsize ((float, float), optional): The figure size (width, height)
dpi (int, optional): The resolution of the plot. Default is 100
canvas (Canvas Backend object, optional):
If interfacing with a backend (e.g. Tk), pass the relavant
Canvas Backend object and the master widget window. If not set,
uses the default the matplotlib backend.
interactive (bool, optional): If True, then enables interactive plotting
(python environment is available while plot
is displayed). Default is True. This is
overriden if canvas is set.
axis (:class:`matplotlib.axes`, optional):
An existing axes object to plot to. If not set, will create a new
axes object.
**kwargs: Additional options for `Figure.add_subplot
<https://matplotlib.org/3.2.0/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.add_subplot>`_.
Attributes:
ax (:class:`matplotlib.axes`): The matplotlib axes object for the plot
canvas (Canvas Backend object): The plotting canvas, if set upon
initialization.
fig (:class:`matplotlib.figure`): The matplotlib figure object
xlim (float, float): The plotting range of the x axis.
This attribute can be set.
xscale (str): The scale of the x axis, either 'linear' or 'log'.
This attribute can be set.
ylim (float, float): The plotting range of the y axis.
This attribute can be set.
yscale (str): The scale of the y axis, either 'linear' or 'log'.
This attribute can be set.
"""
def __init__(self, figsize=None, dpi=100, canvas=None, interactive=True,
axis=None, **kwargs):
self._canvas = None
if canvas is not None:
if figsize is None:
figsize = (5, 4)
self._figure = Figure(figsize=figsize, dpi=100)
# if tkagg, remember to assign to frame
master = canvas[1]
canvas = canvas[0]
self._canvas = canvas(self._figure, master)
else:
# just use pyplot, but disable blocking to enable simultaneous
# access to the python command line and dynamic updating
if figsize is None:
figsize = (7.7, 4.7)
self._figure = plt.figure(figsize=figsize, dpi=100)
self._canvas = self._figure.canvas
if interactive:
plt.ion()
if axis is None:
self._ax = self._figure.add_subplot(111, **kwargs)
else:
self._ax = axis
self._format_coordinates()
def _format_coordinates(self):
# Set the format of the coordinate readout
self._ax.format_coord = lambda x, y: ""
# Set the x minor tick frequency
minorLocator = AutoMinorLocator()
self._ax.xaxis.set_minor_locator(minorLocator)
# Set the y minor tick frequency
minorLocator = AutoMinorLocator()
self._ax.yaxis.set_minor_locator(minorLocator)
@property
def canvas(self):
return self._canvas
@property
def fig(self):
return self._figure
@property
def ax(self):
return self._ax
@property
def xscale(self):
return self._ax.get_xscale()
@xscale.setter
def xscale(self, val):
if (val != 'linear') and (val != 'log'):
raise ValueError("Scale must be either 'linear' or 'log'")
self._ax.set_xscale(val)
@property
def xlim(self):
return self._ax.get_xlim()
@xlim.setter
def xlim(self, val):
self._ax.set_xlim(val)
@property
def yscale(self):
return self._ax.get_yscale()
@yscale.setter
def yscale(self, val):
if (val != 'linear') and (val != 'log'):
raise ValueError("Scale must be either 'linear' or 'log'")
self._ax.set_yscale(val)
@property
def ylim(self):
return self._ax.get_ylim()
@ylim.setter
def ylim(self, val):
self._ax.set_ylim(val)
[docs]class GbmPlotElement():
"""A base class representing a plot element. A plot element can be a
complex collection of more primitive matplotlib plot elements, but are
treated as a single element.
Note:
This class is not intended to be instantiated directly by the user,
rather it is inherited by child plot element objects.
Parameters:
alpha (float): The alpha opacity value, between 0 and 1.
color (str): The color of the plot element
Attributes:
alpha (float): The alpha opacity value, between 0 and 1.
color (str): The color of the plot element.
Note:
Inherited classes may have sub-elements that can have
different colors (and alphas). For those classes, refer to
their specifications as to the definition of :attr:`color`.
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, color=None, alpha=None):
self._artists = []
self._visible = True
self._color = color
self._alpha = alpha
self._kwargs = None
def __del__(self):
self.remove()
@property
def visible(self):
return self._visible
@property
def color(self):
return self._color
@color.setter
def color(self, color):
[artist.set_color(color) for artist in self._artists]
self._color = color
@property
def alpha(self):
return self._alpha
@alpha.setter
def alpha(self, alpha):
[artist.set_alpha(alpha) for artist in self._artists]
self._alpha = alpha
[docs] def hide(self):
"""Hide the plot element"""
self._change_visibility(False)
[docs] def show(self):
"""Show the plot element"""
self._change_visibility(True)
[docs] def toggle(self):
"""Toggle the visibility of the plot element"""
self._change_visibility(not self._visible)
[docs] def remove(self):
"""Remove the plot element"""
for artist in self._artists:
try:
artist.remove()
except:
pass
def _sanitize_artists(self, old_artists):
"""Each artist collection is a collection of artists, and each artist
is a collection of elements. Matplotlib isn't exactly consistent
on how each of these artists are organized for different artist
classes, so we have to have some sanitizing
"""
artists = []
for artist in old_artists:
if artist is None:
continue
elif isinstance(artist, (tuple, list)):
if len(artist) == 0:
continue
artists.extend(self._sanitize_artists(artist))
else:
artists.append(artist)
return artists
def _set_visible(self, artist, visible):
"""In some cases, the artist is a collection of sub-artists and the
set_visible() function has not been exposed to the artists, so
we must iterate.
"""
try:
for subartist in artist.collections:
subartist.set_visible(visible)
except:
pass
def _change_visibility(self, visible):
for artist in self._artists:
try:
artist.set_visible(visible)
except:
self._set_visible(artist, visible)
self._visible = visible
[docs]class Histo(GbmPlotElement):
"""Plot a rate histogram of either lightcurves or count spectra.
Parameters:
bins (:class:`~gbm.data.primitives.TimeBins` or \
:class:`~gbm.data.primitives.EnergyBins`):
The lightcurve or count spectrum histograms
ax (:class:`matplotlib.axes`): The axis on which to plot
color (str, optional): The color of the rate histograms
alpha (float, optional): The alpha of the fill. Default is 1
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value, between 0 and 1.
color (str): The color of the plot element.
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, bins, ax, color=None, alpha=None, label=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
artists = self._create(bins, ax)
self._artists = self._sanitize_artists(artists)
if label is not None:
self._artists[0].set_label(label)
def _create(self, bins, ax):
return histo(bins, ax, color=self._color, alpha=self._alpha,
**self._kwargs)
[docs]class HistoErrorbars(GbmPlotElement):
"""Plot errorbars for lightcurves or count spectra.
Parameters:
bins (:class:`~gbm.data.primitives.TimeBins` or \
:class:`~gbm.data.primitives.EnergyBins`):
The lightcurve or count spectrum histograms
ax (:class:`matplotlib.axes`): The axis on which to plot
color (str, optional): The color of the rate histograms
alpha (float, optional): The alpha of the fill. Default is 1
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value
color (str): The color of the plot element.
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, bins, ax, color=None, alpha=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
artists = self._create(bins, ax)
self._artists = self._sanitize_artists(artists)
def _create(self, bins, ax):
return histo_errorbars(bins, ax, color=self._color, alpha=self._alpha,
**self._kwargs)
[docs]class HistoFilled(GbmPlotElement):
"""Plot a filled histogram.
Parameters:
bins (:class:`~gbm.data.primitives.TimeBins` or \
:class:`~gbm.data.primitives.EnergyBins`):
The lightcurve or count spectrum histograms
ax (:class:`matplotlib.axes`): The axis on which to plot
color (str, optional): The color of the rate histograms
alpha (float, optional): The alpha of the fill. Default is 1
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value
color (str): The color of the plot element.
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, bins, ax, color=None, alpha=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
artists = self._create(bins, ax)
self._artists = self._sanitize_artists(artists)
def _create(self, bins, ax):
return histo_filled(bins, ax, color=self._color,
fill_alpha=self._alpha,
**self._kwargs)
[docs]class LightcurveBackground(GbmPlotElement):
"""Plot a lightcurve background model with an error band.
Parameters:
backrates (:class:`~gbm.background.BackgroundRates`):
The background rates object integrated over energy. If there is
more than one remaining energy channel, the background will be
integrated over the remaining energy channels.
ax (:class:`matplotlib.axes`): The axis on which to plot
cent_alpha (float, optional): The alpha of the background centroid line.
Default is 1
cent_color (str, optional): The color of the background centroid line
err_alpha (float, optional): The alpha of the background uncertainty.
Default is 1
err_color (str, optional): The color of the background uncertainty
color (str, optional): The color of the background. If set, overrides
``cent_color`` and ``err_color``.
alpha (float, optional): The alpha of the background. If set, overrides
``cent_alpha`` and ``err_alpha``
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value
cent_alpha (float): The opacity of the centroid line
cent_color (str): The color of the centroid line
color (str): The color of the plot element.
err_alpha (float): The opacity of the uncertainty band
err_color (str): The color of the uncertainty band
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, backrates, ax, color=None, alpha=None, cent_alpha=None,
err_alpha=None, cent_color=None, err_color=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
# color and alpha act as setting the global color values for the object
if color is not None:
cent_color = color
err_color = color
if alpha is not None:
cent_alpha = alpha
err_alpha = alpha
self._cent_alpha = cent_alpha
self._err_alpha = err_alpha
self._cent_color = cent_color
self._err_color = err_color
artists = self._create(backrates, ax)
self._artists = self._sanitize_artists(artists)
@property
def color(self):
return self._color
@color.setter
def color(self, color):
[artist.set_color(color) for artist in self._artists]
self._color = color
self._cent_color = color
self._err_color = color
@property
def alpha(self):
return self._alpha
@alpha.setter
def alpha(self, alpha):
[artist.set_alpha(alpha) for artist in self._artists]
self._alpha = alpha
self._cent_alpha = alpha
self._err_alpha = alpha
@property
def cent_alpha(self):
return self._cent_alpha
@cent_alpha.setter
def cent_alpha(self, alpha):
[artist.set_alpha(alpha) for artist in self._artists \
if artist.__class__.__name__ == 'Line2D']
self._cent_alpha = alpha
@property
def err_alpha(self):
return self._err_alpha
@err_alpha.setter
def err_alpha(self, alpha):
[artist.set_alpha(alpha) for artist in self._artists \
if artist.__class__.__name__ == 'PolyCollection']
self._err_alpha = alpha
@property
def cent_color(self):
return self._cent_color
@cent_color.setter
def cent_color(self, color):
[artist.set_color(color) for artist in self._artists \
if artist.__class__.__name__ == 'Line2D']
self._cent_color = color
@property
def err_color(self):
return self._err_color
@err_color.setter
def err_color(self, color):
[artist.set_color(color) for artist in self._artists \
if artist.__class__.__name__ == 'PolyCollection']
self._err_color = color
def _create(self, backrates, ax):
return lightcurve_background(backrates, ax,
cent_color=self._cent_color,
err_color=self._err_color,
cent_alpha=self._cent_alpha,
err_alpha=self._err_alpha, **self._kwargs)
[docs]class SpectrumBackground(GbmPlotElement):
"""Plot a count spectrum background model with an error band.
Parameters:
backspec (:class:`~gbm.background.BackgroundSpectrum`):
The background sepctrum object integrated over time
ax (:class:`matplotlib.axes`): The axis on which to plot
cent_alpha (float, optional): The alpha of the background centroid line.
Default is 1
cent_color (str, optional): The color of the background centroid line
err_alpha (float, optional): The alpha of the background uncertainty.
Default is 1
err_color (str, optional): The color of the background uncertainty
color (str, optional): The color of the background. If set, overrides
``cent_color`` and ``err_color``.
alpha (float, optional): The alpha of the background. If set, overrides
``cent_alpha`` and ``err_alpha``
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value
cent_alpha (float): The opacity of the centroid line
cent_color (str): The color of the centroid line
color (str): The color of the plot element.
err_alpha (float): The opacity of the uncertainty band
err_color (str): The color of the uncertainty band
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, backspec, ax, color=None, alpha=None, cent_alpha=None,
err_alpha=None, cent_color=None, err_color=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
# color and alpha act as setting the global color values for the object
if color is not None:
cent_color = color
err_color = color
if alpha is not None:
cent_alpha = alpha
err_alpha = alpha
self._cent_alpha = cent_alpha
self._err_alpha = err_alpha
self._cent_color = cent_color
self._err_color = err_color
artists = self._create(backspec, ax)
self._artists = self._sanitize_artists(artists)
@property
def color(self):
return self._color
@color.setter
def color(self, color):
[artist.set_color(color) for artist in self._artists]
self._color = color
self._cent_color = color
self._err_color = color
@property
def alpha(self):
return self._alpha
@alpha.setter
def alpha(self, alpha):
[artist.set_alpha(alpha) for artist in self._artists]
self._alpha = alpha
self._cent_alpha = alpha
self._err_alpha = alpha
@property
def cent_alpha(self):
return self._cent_alpha
@cent_alpha.setter
def cent_alpha(self, alpha):
[artist.set_alpha(alpha) for artist in self._artists \
if artist.__class__.__name__ == 'Line2D']
self._cent_alpha = alpha
@property
def err_alpha(self):
return self._err_alpha
@err_alpha.setter
def err_alpha(self, alpha):
[artist.set_alpha(alpha) for artist in self._artists \
if artist.__class__.__name__ == 'PolyCollection']
self._err_alpha = alpha
@property
def cent_color(self):
return self._cent_color
@cent_color.setter
def cent_color(self, color):
[artist.set_color(color) for artist in self._artists \
if artist.__class__.__name__ == 'Line2D']
self._cent_color = color
@property
def err_color(self):
return self._err_color
@err_color.setter
def err_color(self, color):
[artist.set_color(color) for artist in self._artists \
if artist.__class__.__name__ == 'PolyCollection']
self._err_color = color
def _create(self, backspec, ax):
return spectrum_background(backspec, ax, cent_color=self._cent_color,
err_color=self._err_color,
cent_alpha=self._cent_alpha,
err_alpha=self._err_alpha, **self._kwargs)
[docs]class HeatMap(GbmPlotElement):
"""Plot a general heatmap (color gradient). By default, the heatmap opacity
will scale from 0 (fully transparent) to 1 (fully opaque) corresponding to
the colormap value, creating an opacity gradient. This behavior can be
adjusted by setting ``alpha_min`` and ``alpha_max``.
Parameters:
x (np.array): The x-coordinate array of the heatmap grid
y (np.array): The y-coordinate array of the heatmap grid
heatmap (np.array): The heatmap array, of shape (``x.size``, ``y.size``)
ax (:class:`matplotlib.axes`): The axis on which to plot
colorbar (bool, optional): If True, add the colorbar scale.
Default is True.
color (str, optional): The colormap of the heatmap. Default is 'Greens'
alpha_min (float, optional): The alpha opacity for the minimum color
value. Default is 0.
alpha_max (float, optional): The alpha opacity for the maximum color
value. Default is 1.
norm (:class:`matplotlib.colors.Normalize` or similar, optional):
The normalization used to scale the colormapping to the heatmap
values. This can be initialized by the defined matplotlib
normalizations or a custom normalization.
num_contours (int, optional): The number of contour lines to draw.
Default is 100.
**kwargs: Other plotting options
Attributes:
alpha_max (float): The alpha opacity for the maximum color value
alpha_min (float): The alpha opacity for the minimum color value
color (str): The colormap
colorbar (:class:`matplotlib.colorbar.Colorbar): The colorbar object
norm (:class:`matplotlib.colors.Normalize` or similar):
The colormap normalization
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, x, y, heatmap, ax, colorbar=True, color='Greens',
alpha=None, alpha_min=0.0, alpha_max=1.0, norm=None,
num_contours=100, **kwargs):
self._alpha_min = alpha_min
self._alpha_max = alpha_max
self._heatmap_min = np.min(heatmap)
self._heatmap_max = np.max(heatmap)
self._colorbar = colorbar
self._num_contours = num_contours
# set the normalization, and ensure that it is scaled to the data range
if norm is None:
norm = PowerNorm(gamma=0.3)
self._norm = norm
self._norm.vmin = self._heatmap_min
self._norm.vmax = self._heatmap_max
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
artists = self._create(x, y, heatmap, ax)
self._artists = self._sanitize_artists(artists)
self._set_cmap()
@property
def colorbar(self):
if self._colorbar:
return self._artists[-1]
@property
def color(self):
return self._color
@color.setter
def color(self, color):
self._color = color
self._set_cmap()
@property
def alpha_min(self):
return self._alpha_min
@alpha_min.setter
def alpha_min(self, alpha):
self._alpha_min = alpha
self._set_cmap()
@property
def alpha_max(self):
return self._alpha_max
@alpha_max.setter
def alpha_max(self, alpha):
self._alpha_max = alpha
self._set_cmap()
@property
def norm(self):
return self._norm
@norm.setter
def norm(self, norm):
norm.vmin = self._heatmap_min
norm.vmax = self._heatmap_max
self._artists[0].set_norm(norm)
self._norm = norm
def _set_cmap(self):
# our specialized colormap
# we set all bad values to alpha=0.0, and we create an alpha gradient
cmap = plt.cm.get_cmap(self.color)
cmap._init()
cmap.set_bad(alpha=0.0)
cmap._lut[:-3, -1] = np.abs(np.linspace(self.alpha_min, self.alpha_max,
cmap.N))
self._artists[0].set_cmap(cmap)
def _create(self, x, y, heatmap, ax):
refs = response_matrix(x, y, heatmap, ax, cmap=self._color,
norm=self._norm, num_contours=self._num_contours,
**self._kwargs)
refs = [refs]
if self._colorbar:
refs.append(self._make_colorbar(ax, refs[0]))
return refs
def _make_colorbar(self, ax, artist):
# scientific format for the colorbar
def sci_fmt(x, pos):
a, b = '{:.0e}'.format(x).split('e')
b = int(b)
return r'${} \times 10^{{{}}}$'.format(a, b)
# vertical colorbar
cb = plt.colorbar(artist, label=r'Eff. Area (cm$^2$)', ax=ax,
cmap=self._color, norm=self._norm,
orientation='vertical',
format=ticker.FuncFormatter(sci_fmt))
cb.draw_all()
return cb
[docs]class EffectiveArea(GbmPlotElement):
"""Plot a histogram of the effective area of a detector response.
Parameters:
bins (:class:`~gbm.data.primitives.Bins`):
The effective area histograms
ax (:class:`matplotlib.axes`): The axis on which to plot
color (str, optional): The color of the rate histograms
alpha (float, optional): The alpha of the histogram. Default is 1
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value, between 0 and 1.
color (str): The color of the plot element.
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, bins, ax, color=None, alpha=None,
orientation='vertical',
**kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
self._orientation = orientation
artists = self._create(bins, ax)
self._artists = self._sanitize_artists(artists)
def _create(self, bins, ax):
return effective_area(bins, ax, color=self._color, alpha=self._alpha,
orientation=self._orientation, **self._kwargs)
[docs]class SAA(GbmPlotElement):
"""Plot the SAA polygon
Parameters:
m (:class:`mpl_toolkits.basemap.Basemap`):
The basemap reference
ax (:class:`matplotlib.axes`): The axis on which to plot
color (str, optional): The color of the SAA polygon
alpha (float, optional): The alpha of the interior of the polygon
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value.
color (str): The color of the polygon.
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, m, ax, color=None, alpha=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
artists = self._create(m, ax)
self._artists = self._sanitize_artists(artists)
self._path = artists[0].get_path()
[docs] def in_saa(self, lon, lat):
"""Check if a point or points is inside the SAA
Args:
lon (np.array): longitudes
lat (np.array): latitudes
Returns:
np.array: Boolean array for each point where True indicates the \
point is in the SAA.
"""
pts = np.array((lon.ravel(), lat.ravel())).T
mask = self._path.contains_points(pts)
return mask
def _create(self, m, ax):
artists = saa_polygon(m, color=self._color, alpha=self._alpha,
**self._kwargs)
ax.add_patch(artists)
return [artists]
[docs]class McIlwainL(GbmPlotElement):
"""Plot the McIlwain L heatmap
Parameters:
m (:class:`mpl_toolkits.basemap.Basemap`):
The basemap reference
ax (:class:`matplotlib.axes`): The axis on which to plot
colorbar (bool, optional): If True, create a colorbar for the heatmap.
Default is True
color (str, optional): The colormap of the heatmap
alpha (float, optional): The alpha opacity of the heatmap
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value.
color (str): The colormap of the heatmap.
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, m, ax, colorbar=True, color=None, alpha=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._colorbar = colorbar
self._kwargs = kwargs
artists = self._create(m, ax)
self._artists = self._sanitize_artists(artists)
@property
def alpha(self):
return self._alpha
@alpha.setter
def alpha(self, alpha):
[artist.set_alpha(alpha) for artist in self._artists[0].collections]
if len(self._artists) == 2:
self._artists[1].set_alpha(alpha)
self._artists[1].draw_all()
self._alpha = alpha
def _make_colorbar(self, ax, artist):
cb = plt.colorbar(artist, label='McIlwain L', ax=ax, shrink=0.6,
pad=0.2,
orientation='horizontal')
cb.draw_all()
return cb
def _create(self, m, ax):
lat_range = (-27.0, 27.0)
lon_range = (-180.0, 180.0)
artists = mcilwain_map(lat_range, lon_range, m, ax, color=self._color,
alpha=self._alpha, **self._kwargs)
artists = [artists]
if self._colorbar:
artists.append(self._make_colorbar(ax, artists[0]))
return artists
[docs]class EarthLine(GbmPlotElement):
"""Plot a line on the Earth
Parameters:
lat (np.array): The latitude values of the line
lon (np.array): The longitude values of the line
m (:class:`mpl_toolkits.basemap.Basemap`):
The basemap reference
ax (:class:`matplotlib.axes`): The axis on which to plot
color (str, optional): The color of the line
alpha (float, optional): The alpha opacity of the line
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value.
color (str): The color of the line.
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, lat, lon, m, ax, color=None, alpha=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
artists = self._create(lat, lon, m, ax)
self._artists = self._sanitize_artists(artists)
def _create(self, lat, lon, m, ax):
artists = earth_line(lat, lon, m, ax=ax, color=self._color,
alpha=self._alpha, **self._kwargs)
return artists
[docs]class EarthPoints(GbmPlotElement):
"""Plot a point or set of points on the Earth.
Parameters:
lat (np.array): The latitude values
lon (np.array): The longitude values
m (:class:`mpl_toolkits.basemap.Basemap`):
The basemap reference
ax (:class:`matplotlib.axes`): The axis on which to plot
color (str, optional): The color of the line
alpha (float, optional): The alpha opacity of the line
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value.
color (str): The color of the points.
coordinates (list of str): The formatted coordinate list of the points
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, lat, lon, m, ax, color=None, alpha=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
artists = self._create(lat, lon, m, ax)
self._artists = self._sanitize_artists(artists)
self._lat = lat
self._lon = lon
@property
def coordinates(self):
coords = self._to_geocoords()
return coords
def _to_geocoords(self):
lat_formatter = lambda x: "%.2fN" % (x) if x > 0 else "%.2fS" % (-x)
lon_formatter = lambda x: "%.2fE" % (x) if x > 0 else "%.2fW" % (-x)
lat = lat_formatter(self._lat)
lon = lon_formatter(self._lon)
return (lat, lon)
def _create(self, lat, lon, m, ax):
artists = earth_points(lat, lon, m, ax=ax, color=self._color,
alpha=self._alpha, **self._kwargs)
return artists
[docs]class FermiIcon(EarthPoints):
"""Plot a Fermi icon on the Earth.
Parameters:
lat (np.array): The latitude value
lon (np.array): The longitude value
m (:class:`mpl_toolkits.basemap.Basemap`):
The basemap reference
ax (:class:`matplotlib.axes`): The axis on which to plot
alpha (float, optional): The alpha opacity of the line
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value.
coordinates (list of str): The formatted coordinate list of the points
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, lat, lon, m, ax, color=None, alpha=None, **kwargs):
self._norm_width = 31.4 * 2.0
self._norm_height = 7.0 * 2.0
super().__init__(lat, lon, m, ax, color=None, alpha=alpha, **kwargs)
@property
def lat(self):
return self._normalize(self._lat())
@property
def gbm_side(self):
return self._normalize(self._gbm_side())
@property
def left_panel(self):
return self._normalize(self._left_panel())
@property
def right_panel(self):
return self._normalize(self._right_panel())
@property
def antenna(self):
return self._normalize(self._antenna())
def _create(self, lat, lon, m, ax):
lon[(lon > 180.0)] -= 360.0
x, y = m(lon, lat)
z = 10
factor = 5000000.
fermilat = self.lat * factor
fermilat[:, 0] += x
fermilat[:, 1] += y
path1 = Path(fermilat, closed=True)
patch1 = patches.PathPatch(path1, facecolor='#DCDCDC', zorder=z)
ax.add_patch(patch1)
gbm = self.gbm_side * factor
gbm[:, 0] += x
gbm[:, 1] += y
path2 = Path(gbm, closed=True)
patch2 = patches.PathPatch(path2, facecolor='#B79241', zorder=z)
ax.add_patch(patch2)
panel = self.left_panel * factor
panel[:, 0] += x
panel[:, 1] += y
path3 = Path(panel, closed=True)
patch3 = patches.PathPatch(path3, facecolor='#45597C', zorder=z)
ax.add_patch(patch3)
panel = self.right_panel * factor
panel[:, 0] += x
panel[:, 1] += y
path4 = Path(panel, closed=True)
patch4 = patches.PathPatch(path4, facecolor='#45597C', zorder=z)
ax.add_patch(patch4)
antenna = self.antenna * factor
antenna[:, 0] += x
antenna[:, 1] += y
path5 = Path(antenna, closed=True)
patch5 = patches.PathPatch(path5, facecolor='#546165', zorder=z)
ax.add_patch(patch5)
return [patch1, patch2, patch3, patch4, patch5]
def _normalize(self, pts):
return (pts / self._norm_width)
def _lat(self):
pts = [[-2.5, 3.5], [-2.5, 1.2], [2.5, 1.2], [2.5, 3.5], [-2.5, 3.5]]
pts = np.array(pts)
return pts
def _gbm_side(self):
pts = [[-2.5, 1.2], [-2.5, -2.1], [2.5, -2.1], [2.5, 1.2], [-2.5, 1.2]]
pts = np.array(pts)
return pts
def _left_panel(self):
pts = [[-2.5, -1.0], [-4.5, -2.5], [-15.7, -2.5], [-15.7, 0.5],
[-4.5, 0.5], [-2.5, -1.0]]
pts = np.array(pts)
return pts
def _right_panel(self):
pts = [[2.5, -1.0], [4.5, -2.5], [15.7, -2.5], [15.7, 0.5],
[4.5, 0.5], [2.5, -1.0]]
pts = np.array(pts)
return pts
def _antenna(self):
pts = [[0.5, -2.1], [0.5, -3.5], [1.5, -3.5], [1.5, -2.1], [0.5, -2.1]]
pts = np.array(pts)
return pts
[docs]class SkyPoints(GbmPlotElement):
"""Plot a set of points on the sky in either equatorial coordinates or
in spacecraft coordinates.
Parameters:
x (float or np.array):
The azimuthal coordinate (RA or Fermi azimuth), in degrees
y (float or np.array):
The polar coordinate (Dec or Fermi zenith), in degrees
ax (:class:`matplotlib.axes`): The axis on which to plot
flipped (bool, optional): If True, the azimuthal axis is flipped,
following equatorial convention
fermi (bool, optional): If True, plot in Fermi spacecraft coordinates,
else plot in equatorial. Default is False.
color (str, optional): The color of the points
alpha (float, optional): The alpha opacity of the points
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value.
color (str): The color of the points.
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, x, y, ax, flipped=True, fermi=False, color=None,
alpha=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
artists = self._create(x, y, ax, flipped, fermi)
self._artists = self._sanitize_artists(artists)
def _create(self, x, y, ax, flipped, fermi):
ref = sky_point(x, y, ax, flipped=flipped, fermi=fermi,
color=self._color, alpha=self._alpha, **self._kwargs)
return [ref]
[docs]class SkyLine(GbmPlotElement):
"""Plot a line on the sky in either equatorial coordinates or in
spacecraft coordinates.
Parameters:
x (np.array):
The azimuthal coordinate (RA or Fermi azimuth), in degrees
y (np.array):
The polar coordinate (Dec or Fermi zenith), in degrees
ax (:class:`matplotlib.axes`): The axis on which to plot
flipped (bool, optional): If True, the azimuthal axis is flipped,
following equatorial convention
fermi (bool, optional): If True, plot in Fermi spacecraft coordinates,
else plot in equatorial. Default is False.
color (str, optional): The color of the line
alpha (float, optional): The alpha opacity of the line
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value.
color (str): The color of the line
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, x, y, ax, flipped=True, fermi=False, color=None,
alpha=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
artists = self._create(x, y, ax, flipped, fermi)
self._artists = self._sanitize_artists(artists)
def _create(self, x, y, ax, flipped, fermi):
refs = sky_line(x, y, ax, flipped=flipped, fermi=fermi,
color=self._color, alpha=self._alpha, **self._kwargs)
return refs
[docs]class SkyCircle(GbmPlotElement):
"""Plot a circle on the sky in either equatorial coordinates or in
spacecraft coordinates.
Note:
This class requires installation of Matplotlib Basemap.
Parameters:
x (float): The azimuthal coordinate of the center (RA or Fermi azimuth),
in degrees
y (float): The polar coordinate of the center (Dec or Fermi zenith),
in degrees
radius (float): The radius of the circle, in degrees
ax (:class:`matplotlib.axes`): The axis on which to plot
flipped (bool, optional): If True, the azimuthal axis is flipped,
following equatorial convention
fermi (bool, optional): If True, plot in Fermi spacecraft coordinates,
else plot in equatorial. Default is False.
face_color (str, optional): The color of the circle fill
face_alpha (float, optional): The alpha opacity of the circle fill
edge_color (str, optional): The color of the circle edge
edge_alpha (float, optional): The alpha opacity of the circle edge
color (str, optional): The color of the circle. If set, overrides
``face_color`` and ``edge_color``.
alpha (float, optional): The alpha of the circle. If set, overrides
``face_alpha`` and ``edge_alpha``.
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value.
color (str): The color of the circle
edge_alpha (float): The alpha opacity of the circle edge
edge_color (str): The color of the circle edge
face_alpha (float): The alpha opacity of the circle fill
face_color (str): The color of the circle fill
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, x, y, radius, ax, flipped=True, fermi=False, color=None,
alpha=None, face_color=None, face_alpha=None, edge_color=None,
edge_alpha=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
# color and alpha act as setting the global color values for the object
if color is not None:
face_color = color
edge_color = color
if alpha is not None:
face_alpha = alpha
edge_alpha = alpha
self._face_alpha = face_alpha
self._edge_alpha = edge_alpha
self._face_color = face_color
self._edge_color = edge_color
artists = self._create(x, y, radius, ax, flipped, fermi)
self._artists = self._sanitize_artists(artists)
@property
def color(self):
return self._color
@color.setter
def color(self, color):
self.face_color = color
self.edge_color = color
self._color = color
@property
def alpha(self):
return self._alpha
@alpha.setter
def alpha(self, alpha):
self.face_alpha = alpha
self.edge_alpha = alpha
self._alpha = alpha
@property
def face_alpha(self):
return self._face_alpha
@face_alpha.setter
def face_alpha(self, alpha):
face = colorConverter.to_rgba(self._face_color, alpha=alpha)
[artist.set_facecolor(face) for artist in self._artists \
if hasattr(artist, 'set_facecolor')]
self._face_alpha = alpha
@property
def edge_alpha(self):
return self._edge_alpha
@edge_alpha.setter
def edge_alpha(self, alpha):
edge = colorConverter.to_rgba(self._edge_color, alpha=alpha)
[artist.set_edgecolor(edge) for artist in self._artists \
if hasattr(artist, 'set_edgecolor')]
self._edge_alpha = alpha
@property
def face_color(self):
return self._face_color
@face_color.setter
def face_color(self, color):
face = colorConverter.to_rgba(color, alpha=self._face_alpha)
[artist.set_facecolor(face) for artist in self._artists \
if hasattr(artist, 'set_facecolor')]
self._face_color = color
@property
def edge_color(self):
return self._edge_color
@edge_color.setter
def edge_color(self, color):
edge = colorConverter.to_rgba(color, alpha=self._edge_alpha)
[artist.set_edgecolor(edge) for artist in self._artists \
if hasattr(artist, 'set_edgecolor')]
self._edge_color = color
def _create(self, x, y, radius, ax, flipped, fermi):
refs = sky_circle(x, y, radius, ax, flipped=flipped, fermi=fermi,
face_color=self._face_color,
face_alpha=self._face_alpha,
edge_color=self._edge_color,
edge_alpha=self._edge_alpha,
**self._kwargs)
return refs
[docs]class SkyPolygon(GbmPlotElement):
"""Plot a polygon on the sky in either equatorial coordinates or in
spacecraft coordinates.
Parameters:
x (float): The azimuthal coordinate (RA or Fermi azimuth), in degrees
y (float): The polar coordinate (Dec or Fermi zenith), in degrees
ax (:class:`matplotlib.axes`): The axis on which to plot
flipped (bool, optional): If True, the azimuthal axis is flipped,
following equatorial convention
fermi (bool, optional): If True, plot in Fermi spacecraft coordinates,
else plot in equatorial. Default is False.
face_color (str, optional): The color of the polygon fill
face_alpha (float, optional): The alpha opacity of the polygon fill
edge_color (str, optional): The color of the polygon edge
edge_alpha (float, optional): The alpha opacity of the polygon edge
color (str, optional): The color of the polygon. If set, overrides
``face_color`` and ``edge_color``.
alpha (float, optional): The alpha of the polygon. If set, overrides
``face_alpha`` and ``edge_alpha``.
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value.
color (str): The color of the polygon
edge_alpha (float): The alpha opacity of the polygon edge
edge_color (str): The color of the polygon edge
face_alpha (float): The alpha opacity of the polygon fill
face_color (str): The color of the polygon fill
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, x, y, ax, flipped=True, fermi=False, color=None,
alpha=None, face_color=None, face_alpha=None, edge_color=None,
edge_alpha=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
# color and alpha act as setting the global color values for the object
if color is not None:
face_color = color
edge_color = color
if alpha is not None:
face_alpha = alpha
edge_alpha = alpha
self._face_alpha = face_alpha
self._edge_alpha = edge_alpha
self._face_color = face_color
self._edge_color = edge_color
artists = self._create(x, y, ax, flipped, fermi)
self._artists = self._sanitize_artists(artists)
@property
def color(self):
return self._color
@color.setter
def color(self, color):
self.face_color = color
self.edge_color = color
self._color = color
@property
def alpha(self):
return self._alpha
@alpha.setter
def alpha(self, alpha):
self.face_alpha = alpha
self.edge_alpha = alpha
self._alpha = alpha
@property
def face_alpha(self):
return self._face_alpha
@face_alpha.setter
def face_alpha(self, alpha):
[artist.set_alpha(alpha) for artist in self._artists \
if artist.__class__.__name__ == 'Polygon']
self._face_alpha = alpha
@property
def edge_alpha(self):
return self._edge_alpha
@edge_alpha.setter
def edge_alpha(self, alpha):
[artist.set_alpha(alpha) for artist in self._artists \
if artist.__class__.__name__ == 'Line2D']
self._edge_alpha = alpha
@property
def face_color(self):
return self._face_color
@face_color.setter
def face_color(self, color):
[artist.set_color(color) for artist in self._artists \
if artist.__class__.__name__ == 'Polygon']
self._face_color = color
@property
def edge_color(self):
return self._edge_color
@edge_color.setter
def edge_color(self, color):
[artist.set_color(color) for artist in self._artists \
if artist.__class__.__name__ == 'Line2D']
self._edge_color = color
def _create(self, x, y, ax, flipped, fermi):
refs = sky_polygon(x, y, ax, flipped=flipped, fermi=fermi,
face_color=self._face_color,
face_alpha=self._face_alpha,
edge_color=self._edge_color,
edge_alpha=self._edge_alpha,
**self._kwargs)
return refs
[docs]class SkyAnnulus(GbmPlotElement):
"""Plot an annulus on the sky in either equatorial coordinates or in
spacecraft coordinates.
Note:
This class requires installation of Matplotlib Basemap.
Parameters:
x (float): The azimuthal center (RA or Fermi azimuth), in degrees
y (float): The polar center (Dec or Fermi zenith), in degrees
radius (float): The radius in degrees, defined as the angular distance
from the center to the middle of the width of the
annulus band.
width (float): The width onf the annulus in degrees
ax (:class:`matplotlib.axes`): The axis on which to plot
flipped (bool, optional): If True, the azimuthal axis is flipped,
following equatorial convention
fermi (bool, optional): If True, plot in Fermi spacecraft coordinates,
else plot in equatorial. Default is False.
color (str, optional): The color of the annulus.
alpha (float, optional): The opacity of the annulus.
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value.
color (str): The color of the annulus
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, x, y, radius, width, ax, flipped=True, fermi=False,
color=None,
alpha=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
# color and alpha act as setting the global color values for the object
artists = self._create(x, y, radius, width, ax, flipped, fermi)
self._artists = self._sanitize_artists(artists)
@property
def color(self):
return self._color
@color.setter
def color(self, color):
edge = colorConverter.to_rgba(color, alpha=1.0)
face = colorConverter.to_rgba(color, alpha=self.alpha)
[artist.set_edgecolor(edge) for artist in self._artists]
[artist.set_facecolor(face) for artist in self._artists]
self._color = color
def _create(self, x, y, radius, width, ax, flipped, fermi):
refs = sky_annulus(x, y, radius, width, ax, flipped=flipped,
fermi=fermi,
color=self._color, alpha=self._alpha,
**self._kwargs)
return refs
[docs]class Sun(GbmPlotElement):
"""Plot the sun on the sky.
Parameters:
x (float): The azimuthal coordinate (RA or Fermi azimuth), in degrees
y (float): The polar coordinate (Dec or Fermi zenith), in degrees
ax (:class:`matplotlib.axes`): The axis on which to plot
flipped (bool, optional): If True, the azimuthal axis is flipped,
following equatorial convention
fermi (bool, optional): If True, plot in Fermi spacecraft coordinates,
else plot in equatorial. Default is False.
alpha (float, optional): The opacity of the annulus.
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value.
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, x, y, ax, flipped=True, fermi=False, alpha=None,
**kwargs):
super().__init__(color=None, alpha=None)
self._kwargs = kwargs
artists = self._create(x, y, ax, flipped, fermi)
self._artists = self._sanitize_artists(artists)
def _create(self, x, y, ax, flipped, fermi):
# :)
marker1 = "$\u263c$"
marker2 = "$\u263b$"
edge = colorConverter.to_rgba('gold', alpha=1.0)
face = colorConverter.to_rgba('yellow', alpha=0.75)
point1 = sky_point(x, y, ax, marker=marker1, s=150.0, facecolor=face,
edgecolor=edge, flipped=flipped, fermi=fermi,
**self._kwargs)
point2 = sky_point(x, y, ax, marker=marker2, s=75.0, facecolor=face,
edgecolor=edge, flipped=flipped, fermi=fermi,
**self._kwargs)
return [point1, point2]
[docs]class DetectorPointing(SkyCircle):
"""Plot a detector pointing on the sky in either equatorial coordinates or
spacecraft coordinates.
Parameters:
x (float): The azimuthal coordinate (RA or Fermi azimuth), in degrees
y (float): The polar coordinate (Dec or Fermi zenith), in degrees
radius (float): The radius of the circle, in degrees
ax (:class:`matplotlib.axes`): The axis on which to plot
flipped (bool, optional): If True, the azimuthal axis is flipped,
following equatorial convention
fermi (bool, optional): If True, plot in Fermi spacecraft coordinates,
else plot in equatorial. Default is False.
face_color (str, optional): The color of the circle fill
face_alpha (float, optional): The alpha opacity of the circle fill
edge_color (str, optional): The color of the circle edge
edge_alpha (float, optional): The alpha opacity of the circle edge
color (str, optional): The color of the circle. If set, overrides
``face_color`` and ``edge_color``.
alpha (float, optional): The alpha of the circle. If set, overrides
``face_alpha`` and ``edge_alpha``.
fontsize (float, optional): The size of the detector label
font_alpha (float, optional): The alpha opacity of the detector label
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value.
color (str): The color of the circle
edge_alpha (float): The alpha opacity of the circle edge
edge_color (str): The color of the circle edge
face_alpha (float): The alpha opacity of the circle fill
face_color (str): The color of the circle fill
fontsize (float): The size of the detector label
font_alpha (float): The alpha opacity of the detector label
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, x, y, radius, det, ax, flipped=None, fermi=None,
color='dimgray', edge_alpha=0.5, face_alpha=0.25, fontsize=10,
font_alpha=0.8, **kwargs):
self._det = det
if fermi:
flipped = False
det_obj = Detector.from_str(det)
x, y = det_obj.azimuth, det_obj.zenith
super().__init__(x, y, radius, ax, color=color, face_alpha=face_alpha,
edge_alpha=edge_alpha, fermi=fermi)
self._fontsize = fontsize
self._fontalpha = font_alpha
self._annotate(x, y, ax, flipped, fermi)
@property
def fontsize(self):
return self._fontsize
@fontsize.setter
def fontsize(self, size):
self._artists[-1].set_fontsize(size)
self._fontsize = size
@property
def font_alpha(self):
return self._fontalpha
@font_alpha.setter
def font_alpha(self, alpha):
self._artists[-1].set_alpha(alpha)
self._fontalpha = alpha
def _annotate(self, x, y, ax, flipped, fermi):
theta = np.deg2rad(y)
phi = np.deg2rad(180.0 - x)
if fermi:
theta = np.pi / 2.0 - theta
phi -= np.pi
if phi < -np.pi:
phi += 2 * np.pi
if not flipped:
phi *= -1.0
txt = ax.text(phi, theta, self._det, fontsize=self._fontsize,
ha='center', va='center', color=self._color,
alpha=self._fontalpha, **self._kwargs)
self._artists.append(txt)
[docs]class GalacticPlane(GbmPlotElement):
"""Plot the Galactic Plane in either equatorial coordinates or in
spacecraft coordinates.
Parameters:
ax (:class:`matplotlib.axes`): The axis on which to plot
flipped (bool, optional): If True, the azimuthal axis is flipped,
following equatorial convention
fermi (bool, optional): If True, plot in Fermi spacecraft coordinates,
else plot in equatorial. Default is False.
inner_color (str, optional): The color of the inner line element
outer_color (str, optional): The color of the outer line element
line_alpha (float, optional): The alpha opacity of the line elements of
the Galactic Plane
center_alpha (float, optional): The alpha opacity of the Galactic center
color (str, optional): The color of the Galactic plane. Overrides
``inner_color`` and ``outer_color``.
alpha (float, optional): The opacity of the Galactic plane. Overrides
``line_alpha`` and ``center_alpha``.
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value.
center_alpha (float): The alpha opacity of the Galactic plane
color (str): The color of the Galactic plane
inner_color (str): The color of the inner line element
line_alpha (float): The alpha opacity of the line elements
outer_color (str): The color of the outer line element
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, ax, flipped=True, fermi_quat=None, color=None,
inner_color='black', outer_color='dimgray', alpha=None,
line_alpha=0.5, center_alpha=0.75, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
# color and alpha act as setting the global color values for the object
if color is not None:
inner_color = color
outer_color = color
if alpha is not None:
line_alpha = alpha
center_alpha = alpha
self._line_alpha = line_alpha
self._center_alpha = center_alpha
self._inner_color = inner_color
self._outer_color = outer_color
artists = self._create(ax, flipped, fermi_quat)
self._artists = self._sanitize_artists(artists)
@property
def color(self):
return self._color
@color.setter
def color(self, color):
self.inner_color = color
self.outer_color = color
self._color = color
@property
def alpha(self):
return self._alpha
@alpha.setter
def alpha(self, alpha):
self.line_alpha = alpha
self.center_alpha = alpha
self._alpha = alpha
@property
def line_alpha(self):
return self._line_alpha
@line_alpha.setter
def line_alpha(self, alpha):
[artist.set_alpha(alpha) for artist in self._artists \
if artist.__class__.__name__ == 'Line2D']
self._line_alpha = alpha
@property
def center_alpha(self):
return self._center_alpha
@center_alpha.setter
def center_alpha(self, alpha):
[artist.set_alpha(alpha) for artist in self._artists \
if artist.__class__.__name__ == 'PathCollection']
self._center_alpha = alpha
@property
def inner_color(self):
return self._inner_color
@inner_color.setter
def inner_color(self, color):
[artist.set_color(color) for artist in self._artists \
if artist.__class__.__name__ == 'Line2D' and \
artist.get_color() == self._inner_color]
self._artists[-1].set_facecolor(color)
self._inner_color = color
@property
def outer_color(self):
return self._outer_color
@outer_color.setter
def outer_color(self, color):
[artist.set_color(color) for artist in self._artists \
if artist.__class__.__name__ == 'Line2D' and \
artist.get_color() == self._outer_color]
self._artists[-2].set_facecolor(color)
self._outer_color = color
def _create(self, ax, flipped, fermi_quat):
refs = galactic_plane(ax, flipped=flipped, fermi_quat=fermi_quat,
outer_color=self._outer_color,
inner_color=self._inner_color,
line_alpha=self._line_alpha,
center_alpha=self._center_alpha, **self._kwargs)
return refs
[docs]class SkyHeatmap(GbmPlotElement):
"""Plot a heatmap on the sky. By default, the heatmap opacity will scale
from 0 (fully transparent) to 1 (fully opaque) corresponding to the colormap
value, creating an alpha gradient. This behavior can be adjust by setting
``alpha_min`` and ``alpha_max``.
Parameters:
x (np.array):The azimuthal coordinate array of the heatmap grid
y (np.array): The polar coordinate array of the heatmap grid
heatmap (np.array): The heatmap array, of shape (``x.size``, ``y.size``)
ax (:class:`matplotlib.axes`): The axis on which to plot
color (str, optional): The colormap of the heatmap. Default is 'RdPu'
alpha_min (float, optional): The alpha opacity for the minimum color
value. Default is 0.
alpha_max (float, optional): The alpha opacity for the maximum color
value. Default is 1.
norm (:class:`matplotlib.colors.Normalize` or similar, optional):
The normalization used to scale the colormapping to the heatmap
values. This can be initialized by the defined matplotlib
normalizations or a custom normalization.
flipped (bool, optional): If True, the azimuthal axis is flipped,
following equatorial convention
fermi (bool, optional): If True, plot in Fermi spacecraft coordinates,
else plot in equatorial. Default is False.
**kwargs: Other plotting options
Attributes:
alpha_max (float): The alpha opacity for the maximum color value
alpha_min (float): The alpha opacity for the minimum color value
color (str): The colormap
norm (:class:`matplotlib.colors.Normalize` or similar):
The colormap normalization
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, x, y, heatmap, ax, flipped=True, fermi=False,
color='RdPu',
alpha=None, alpha_min=0.0, alpha_max=1.0, norm=None,
**kwargs):
self._alpha_min = alpha_min
self._alpha_max = alpha_max
self._heatmap_min = np.min(heatmap)
self._heatmap_max = np.max(heatmap)
# set the normalization, and ensure that it is scaled to the data range
if norm is None:
norm = PowerNorm(gamma=0.3)
self._norm = norm
self._norm.vmin = self._heatmap_min
self._norm.vmax = self._heatmap_max
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
artists = self._create(x, y, heatmap, ax, flipped, fermi)
self._artists = self._sanitize_artists(artists)
self._set_cmap()
@property
def color(self):
return self._color
@color.setter
def color(self, color):
self._color = color
self._set_cmap()
@property
def alpha_min(self):
return self._alpha_min
@alpha_min.setter
def alpha_min(self, alpha):
self._alpha_min = alpha
self._set_cmap()
@property
def alpha_max(self):
return self._alpha_max
@alpha_max.setter
def alpha_max(self, alpha):
self._alpha_max = alpha
self._set_cmap()
@property
def norm(self):
return self._norm
@norm.setter
def norm(self, norm):
norm.vmin = self._heatmap_min
norm.vmax = self._heatmap_max
self._artists[0].set_norm(norm)
self._norm = norm
def _set_cmap(self):
# our specialized colormap
# we set all bad values to alpha=0.0, and we create an alpha gradient
cmap = plt.cm.get_cmap(self.color)
cmap._init()
cmap.set_bad(alpha=0.0)
cmap._lut[:-3, -1] = np.abs(np.linspace(self.alpha_min, self.alpha_max,
cmap.N))
self._artists[0].set_cmap(cmap)
def _create(self, x, y, heatmap, ax, flipped, fermi):
refs = sky_heatmap(x, y, heatmap, ax, cmap=self._color,
norm=self._norm,
flipped=flipped, fermi=fermi, **self._kwargs)
ax.grid(True)
return [refs]
[docs]class ModelData(GbmPlotElement):
"""Plot a set of data with errors derived from the model variances.
Parameters:
x (np.array): The x coordinates of the data plot points
y (np.array): The y coordinates of the data plot points
xerr (np.array): The uncertainty of the x points
yerr (np.array): The uncertainty of the y points
ax (:class:`matplotlib.axes`): The axis on which to plot
ulmask (np.array(dtype=bool), optional):
A boolean array of the same size as x and y indicating which
points are upper limits (True) and which are data points (False).
color (str, optional): The color of the model
alpha (float, optional): The alpha of the fill. Default is 1
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value, between 0 and 1.
color (str): The color of the model.
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, x, y, xerr, yerr, ax, ulmask=None, color=None,
alpha=None, **kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
artists = self._create(x, y, xerr, yerr, ulmask, ax)
self._artists = self._sanitize_artists(artists)
def _create(self, x, y, xerr, yerr, ulmask, ax):
return ax.errorbar(x, y, xerr=xerr, yerr=yerr, uplims=ulmask,
fmt='none',
color=self._color, alpha=self._alpha,
**self._kwargs)
[docs]class ModelSamples(GbmPlotElement):
"""Plot a series of spectral model samples
Parameters:
x (np.array): The x coordinates of the model
y_list (list of np.array): A list of arrays, where each element is a
single spectral sample, having length equal
to x
ax (:class:`matplotlib.axes`): The axis on which to plot
color (str, optional): The color of the samples
alpha (float, optional): The alpha of the fill. Default is 1
label (str, optional): The label for the samples
**kwargs: Other plotting options
Attributes:
alpha (float): The alpha opacity value, between 0 and 1.
color (str): The color of the model.
visible (bool): True if the element is shown on the plot,
False otherwise
"""
def __init__(self, x, y_list, ax, color=None, alpha=None, label=None,
**kwargs):
super().__init__(color=color, alpha=alpha)
self._kwargs = kwargs
artists = self._create(x, y_list, ax)
self._artists = self._sanitize_artists(artists)
if label is not None:
self._artists[0].set_label(label)
def _create(self, x, y_list, ax):
return [
ax.plot(x, y, color=self._color, alpha=self._alpha, **self._kwargs) \
for y in y_list]
[docs]class Collection:
"""A container for a collection of like objects. The Collection class
exposes the individual objects as attributes, and it exposes the
methods of the object class so that methods can be called
on the collection as a whole. For that reason, each object in the
Collection must be of the same type, otherwise an error is raised. The
type of the Collection is set by the first object inserted into the
collection.
Attributes:
item_type (str): The type of the items in the Collection
items (list): The items in the Collection
size (int): The number of items in the Collection
<item_name> (object):
If the item name can be converted to an attribute name, then each
item name in the Collection becomes an attribute, returning the
individual object
<item_attrib> (<attr>):
If the individual objects have public attributes, these will be made
attributes of the Collection
<item_method> (<function>):
If the individual objects have public methods, these will be made
methods of the Collection
"""
def __init__(self):
object.__setattr__(self, '_name_dict', {})
self._dtype = None
def __getattr__(self, name):
return self.get(name)
@property
def size(self):
return len(self._name_dict)
@property
def items(self):
return self._name_dict.keys()
@property
def item_type(self):
return self._dtype
[docs] @classmethod
def from_list(cls, obj_list, names):
"""Create a Collection of objects from a list and list of corresponding
names
Args:
obj_list (list): A list of objects of the same type
names (list of str): A list of string names corresponding to the obj_list
Returns:
:class:`Collection`: The collection of objects
"""
collection = Collection()
[collection.insert(names[i], obj_list[i]) for i in
range(len(obj_list))]
return collection
[docs] def insert(self, name, obj):
"""Insert an object into the Collection
Args:
name (str): The name of the object
obj (object): The object to insert
"""
# if this is the first item inserted, set the type of the Collection
# and expose the attributes and methods of the object
if self.size == 0:
self._dtype = type(obj)
dir = [key for key in obj.__dir__() if not re.match('_.', key)]
[setattr(self, key, partial(self._method_call, key)) for key in
dir]
else:
# otherwise, ensure that each object inserted is of the same type
if type(obj) != self._dtype:
raise TypeError('A Collection must contain like objects')
# insert
self._name_dict[name] = obj
[docs] def get(self, name):
"""Retrieve an object by name
Args:
name (str): The name of the object
Returns:
<object>: The requested object reference
"""
return self._name_dict[name]
def _method_call(self, method_name, *args, **kwargs):
"""This is the wrapper for the attribute and method calls. Applies
method_name over all items in the Collection
Args:
method_name (str): The name of the method or attribute
*args: Additional arguments to be passed to the method
**kwargs: Additional options to be passed to the method
Returns:
list: If not None, will return the results from all objects in the list
"""
# get the attributes/methods for each item
refs = [getattr(obj, method_name) for obj in self._name_dict.values()]
# if method_name is a method, then it will be callable
if callable(refs[0]):
res = [getattr(obj, method_name)(*args, **kwargs) \
for obj in self._name_dict.values()]
# otherwise, method_name will not be callable if it is an attribute
else:
# we are setting an attribute
if len(args) != 0:
res = [setattr(obj, method_name, *args) \
for obj in self._name_dict.values()]
# we are retrieving an attribute
else:
res = refs
if res[0] is not None:
return res
# -----------Testing------------#
class PlotSettingsList(object):
def __init__(self):
object.__setattr__(self, '_name_dict', {})
def __getattr__(self, name):
return self.get(name)
def __setattr__(self, name, val):
self._name_dict[name].set(val)
@property
def size(self):
return len(self._name_dict)
@property
def settings(self):
return self._name_dict.keys()
def insert(self, name, setting, **kwargs):
p = PlotSetting(name, setting, **kwargs)
self._name_dict[p.name] = p
def get(self, name):
return self._name_dict[name].value
class PlotSetting():
def __init__(self, name, value, callback=None):
self._name = name
self._value = value
self._type = value
self._callback = callback
@property
def name(self):
return self._name
@property
def value(self):
return self._value
def set(self, val):
try:
self._value = type(self._type)(val)
except:
raise TypeError(
"Value for '{0}' must be of type {1}".format(self.name,
type(self._type)))
self._callback(self.name)