Source code for gbm.plot.gbmplot

# 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)