Source code for sluyspy.plot

# -*- coding: utf-8 -*-
# SPDX-License-Identifier: EUPL-1.2
#  
#  Copyright (c) 2022-2024  Marc van der Sluys - Nikhef/Utrecht University - marc.vandersluys.nl
#  
#  This file is part of the sluyspy Python package:
#  Marc van der Sluys' personal Python modules.
#  See: https://github.com/MarcvdSluys/sluyspy
#  
#  This is free software: you can redistribute it and/or modify it under the terms of the European Union
#  Public Licence 1.2 (EUPL 1.2).  This software 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 EU Public Licence for more details.  You should have received a copy of the European
#  Union Public Licence along with this code.  If not, see <https://www.eupl.eu/1.2/en/>.


"""Plot functions for the sluyspy package."""

import matplotlib.pyplot as _plt         # Get matplotlib.pyplot
import numpy.core as _np                 # Get NumPy
from .cli import error as _error


[docs] def start_plot(ptype='both', hsize=None,vsize=None, dark_bg=False, xkcd=False, title='Python plot', fz=None,lw=None, plot3d=False, man_zorder=True): """Start a matplotlib.pyplot plot with a choice of my favourite options. Parameters: ptype (str): Plot type: 'screen', 'file', 'both' (a compromise) or 'square'. hsize (float): Horizontal size of the figure (hectopixels); can overrule setting by ptype. vsize (float): Vertical size of the figure (hectopixels); can overrule setting by ptype. dark_bg (bool): Use a dark background (optional, defaults to False). xkcd (bool): Use XKCD style (optional, defaults to False). title (str): Window title (optional, defaults to 'Python plot'). fz (int): Font size (optional, defaults to None -> 14 or 16). lw (int): Line width (optional, defaults to None -> 1 or 2). plot3d (bool): Start a 3D plot (optional, defaults to False -> 2D). map_zorder (bool): Use manual zorder (defaults to True - opposed to pyplot default: compute). Returns: (tuple): Tuple containing the plot and axis objects. Defaults: - screen: size 1920x1080, font 14, lw: 1; - file: size 1250x700, font 14, lw: 2; - both: size 1580x850, font 16, lw: 2; - square: size 850x850, font 16, lw: 2. """ import matplotlib if xkcd: _plt.xkcd() # Plot everything that follows in XKCD style if dark_bg: _plt.style.use('dark_background') # Invert colours if xkcd and dark_bg: from matplotlib import patheffects _plt.rcParams['path.effects'] = [patheffects.withStroke(linewidth=0)] # Needed for XKCD style on a dark background if ptype == 'screen': if hsize is None: hsize = 19.2 # 1920 if vsize is None: vsize = 10.8 # 1080 matplotlib.rcParams.update({'font.size': 14}) # Set font size for all text, aimed at full screen display elif ptype == 'file': if hsize is None: hsize = 12.5 # 1250 if vsize is None: vsize = 7.0 # 700 matplotlib.rcParams.update({'font.size': 14}) # Set font size for all text aimed at an electonic file matplotlib.rcParams.update({'lines.linewidth': 2}) # Set default line width to 2 elif ptype == 'both': if hsize is None: hsize = 15.8 # 1580 if vsize is None: vsize = 8.5 # 850 matplotlib.rcParams.update({'font.size': 16}) # Set font size for all text: compromise screen visibility and report readability matplotlib.rcParams.update({'lines.linewidth': 2}) # Set default line width to 2 elif ptype == 'square': if hsize is None: hsize = 8.5 # 850 if vsize is None: vsize = 8.5 # 850 matplotlib.rcParams.update({'font.size': 16}) # Set font size for all text: compromise screen visibility and report readability else: _error('Unknown plot type: '+ptype+', aborting.') if fz is not None: matplotlib.rcParams.update({'font.size': fz}) # Set font size for all text if lw is not None: matplotlib.rcParams.update({'lines.linewidth': lw}) # Set custom default line width fig = _plt.figure(figsize=(hsize,vsize), num=title) # Custom size if plot3d: ax = fig.add_subplot(111, projection='3d', computed_zorder=(not man_zorder)) # Create a 3D axes object for the current figure else: ax = fig.add_subplot(111) # Create a 2D axes object for the current figure if xkcd and dark_bg: ax.spines[:].set_color('white') # Needed for XKCD style on a dark background return fig, ax
[docs] def hist_norm(obj, x, bins=None, range=None, density=False, weights=None, cumulative=False, bottom=None, histtype='bar', align='mid', orientation='vertical', rwidth=None, log=False, color=None, label=None, stacked=False, *, data=None, **kwargs): """Draw a histogram normalised by total number rather than probability density. Parameters: obj (pyplot object): Pyplot object or axes object to plot to. x (array or sequence of arrays): Data. bins (int or sequence or str): Number of bins. Default: rcParams["hist.bins"] (default: 10). range (tuple or None): Bin range, default: None. density (bool): Plot probability density. Default: False. weights (array-like or None): Weights. Default: None. cumulative (bool or -1): Plot a cumulative histogram. Default: False. bottom (array-like, scalar, or None): Location of the bottom of the bins. Default: None. histtype (str): Histogram type: {'bar', 'barstacked', 'step', 'stepfilled'}, default: 'bar'. align (str): Bin alignment: {'left', 'mid', 'right'}, default: 'mid'. orientation (str): Bin orientation: {'vertical', 'horizontal'}, default: 'vertical'. rwidth (float or None): The relative width of the bars as a fraction of the bin width, default: None. log (bool): Set the histogram axis to a log scale, default: False. color (color or array-like of colors or None): Color or sequence of colors for bins, one per dataset, default: None. label (str or None): Legend label, default: None. stacked (bool): Stack multiple data on top of each other, rather than side by side, default: False. data (indexable object, optional): If given, the following parameters also accept a string s, which is interpreted as data[s] (unless this raises an exception): x, weights. Returns: (tuple): Tuple consisting of (n, bin_edges, patches): - (array or list of arrays): The values of the histogram bins. - (array): The edges of the bins. Length nbins + 1. - (BarContainer or list of Polygons): (List of) container(s) of individual artists used to create the histogram(s). """ weights = _np.ones_like(x) / len(x) # Set all weights to 1/n to pass them to pyplot.hist() n, bin_edges, patches = obj.hist(x, bins=bins, range=range, density=density, weights=weights, cumulative=cumulative, bottom=bottom, histtype=histtype, align=align, orientation=orientation, rwidth=rwidth, log=log, color=color, label=label, stacked=stacked, data=data, **kwargs) return n, bin_edges, patches
[docs] def show_ctrlc(): """Call pyplot.show() and catch Ctrl-C to exit gently.""" try: _plt.show() except KeyboardInterrupt: print(' - Received keyboard interrupt whilst showing plot, aborting.') # " - " to create some space after "^C" exit(0) return
[docs] def pause_ctrlc(interval): """Call pyplot.pause() and catch Ctrl-C to exit gently. Parameters: interval (float): pause time in seconds. """ try: _plt.pause(interval) except KeyboardInterrupt: print(' - Received keyboard interrupt whilst pausing plot, aborting.') # " - " to create some space after "^C" exit(0) return
[docs] def arrow_head_between_points(ax, xx,yy, dx=0,dy=0, scale=1, **kwargs): """Plot an arrow head between two 2D points. Parameters: ax (axes): Axes object to plot on. xx (float): Array with two x values, for arrow-head position and direction. yy (float): Array with two y values, for arrow-head position and direction. dx (float): Shift arrow head in x direction. dy (float): Shift arrow head in y direction. scale (float): Scale arrow head. """ # Local scaling parameter: undo arrow-head scaling with length: lscale = 0.1 * scale / _np.sqrt((xx[1]-xx[0])**2 + (yy[1]-yy[0])**2) if len(xx)!=2: _error('arrow_head_between_points(): The input variable xx must have two elements') if len(yy)!=2: _error('arrow_head_between_points(): The input variable yy must have two elements') ax.quiver(xx[1]+dx, yy[1]+dy, xx[1]-xx[0], yy[1]-yy[0], angles='xy', scale_units='width', pivot='mid', scale=3/lscale, width=5e-3*lscale, headwidth=10*lscale, headlength=15*lscale, headaxislength=10*lscale, **kwargs) return