You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

splotch

Package Overview
Dependencies
Maintainers
2
Versions
62
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

splotch - pypi Package Compare versions

Comparing version
0.6.2.1
to
0.6.5.2
+1
-1
PKG-INFO
Metadata-Version: 2.1
Name: splotch
Version: 0.6.2.1
Version: 0.6.5.2
Summary: Simple PLOTs, Contours and Histograms is a small package with wrapper functions designed to simplify plotting calls from matplotlib.

@@ -5,0 +5,0 @@ Home-page: https://github.com/MBravoS/splot

@@ -6,3 +6,3 @@ import atexit

setup(name='splotch',
version='0.6.2.1',
version='0.6.5.2',
description='Simple PLOTs, Contours and Histograms is a small package with wrapper functions designed to simplify plotting calls from matplotlib.',

@@ -9,0 +9,0 @@ url='https://github.com/MBravoS/splot',

Metadata-Version: 2.1
Name: splotch
Version: 0.6.2.1
Version: 0.6.5.2
Summary: Simple PLOTs, Contours and Histograms is a small package with wrapper functions designed to simplify plotting calls from matplotlib.

@@ -5,0 +5,0 @@ Home-page: https://github.com/MBravoS/splot

########################################################################
######### Base functions for axis_funcs, plots_1d and plots_2d #########
########################################################################
from collections.abc import Iterable
from warnings import warn
from numbers import Number

@@ -11,6 +14,8 @@ from math import ceil, floor

from matplotlib import rcParams
from matplotlib.pyplot import fill_between, fill_betweenx, gca, grid, sca, title as plt_title
from matplotlib.pyplot import fill_between, fill_betweenx, gca, gcf, grid, sca, title as plt_title
from matplotlib.pyplot import xlabel as plt_xlabel, xlim as plt_xlim, xscale
from matplotlib.pyplot import ylabel as plt_ylabel, ylim as plt_ylim, yscale
from .defaults import Params
####################################

@@ -47,2 +52,100 @@ # Boolean, unsigned integer, signed integer, float, complex.

#########################################
# Handle various inputs to grid parameter
#########################################
def grid_handler(grid, ax=None):
""" Handles grid behaviour
Base-level function to handle the behaviour of inputs to the grid parameter. If grid is a boolean, then
nothing is changed, however, if grid is None, first check whether this is the first plot call to this
axis, if so, use rcParams, otherwise do nothing.
Parameters
----------
grid : boolean, dict or None
The requested grid settings.
* If boolean, then the grid is set on (True) or off (False).
* If dict, then parameters in grid will be parsed into the plt.grid() function.
* If None and this is the initial plotting call, set to rcParams, else do nothing.
ax : Axes object or None
The axis in which to check the current grid settings.
Returns
-------
gridpar : boolean
Returns the modified grid parameters as a dictionary.
"""
if ax is None:
ax = gca()
if grid is None:
if ax.has_data():
return None # i.e. do nothing
else:
grid = rcParams['axes.grid']
if isinstance(grid, bool):
if grid is True: # only set additional grid properties if grid=True, otherwise warnings given.
return dict(visible=grid, which=rcParams['axes.grid.which'], axis=rcParams['axes.grid.axis'],
color=Params.grid_color, alpha=Params.grid_alpha,
linestyle=Params.grid_ls, linewidth=Params.grid_lw)
else:
return dict(visible=grid, which=rcParams['axes.grid.which'], axis=rcParams['axes.grid.axis'])
elif isinstance(grid, dict):
return grid
else:
warn(f"grid must be given as either boolean, dict or None. Instead got {type(grid)}.")
return None
###############################################
# Handle various inputs to xlim/ylim parameters
###############################################
def lims_handler(lims, ax=None):
""" Handles xlim/ylim behaviour
Base-level function to handle the behaviour of inputs to the xlim/ylim parameters. If xlim/ylim is a valid tuple,
then set these as the limits. However, if xlim/ylim is None, first check whether this is the first plot call to this
axis, if so, set to 'auto' (i.e. autoscale), otherwise do nothing.
Parameters
----------
lims : tuple, str or None
The requested values for xlim/ylim.
* If tuple, then return these values (if valid).
* If None and this is the initial plotting call, set to 'auto', else set to None
* If str, must be of the value 'auto' return.
ax : Axes object or None
The axis in which to check the current grid settings.
Returns
-------
limspar : boolean
Returns the modified grid parameters as a dictionary.
"""
if ax is None:
ax = gca()
if lims is None:
if ax.has_data():
return None # i.e. do nothing
else:
return 'auto'
elif isinstance(lims, Iterable):
if isinstance(lims, str):
if lims != 'auto':
warn(f"Only 'auto' is accepted when giving lims as a str. Instead got {type(lims)}.")
return None
else:
return 'auto'
else:
return lims # should we validate limits here?
else:
warn(f"lims must be given as either list-like, str or None. Instead got {type(lims)}.")
return None
####################################

@@ -256,12 +359,3 @@ # Base function for 2D histograms

####################################
# Density/scaled counts for hexbin
####################################
#def hexarea(value,norm):
# return(value/norm)
#
#def hexscaled(value,scale):
# return(1.0*value/scale)
####################################

@@ -317,3 +411,2 @@ # General check for numeric values

"""
from numbers import Number

@@ -467,3 +560,3 @@ return isinstance(var, Number)

if grid_control is not None:
if isinstance(grid_control, bool):
grid(b=grid_control, which=rcParams['axes.grid.which'], axis=rcParams['axes.grid.axis'])

@@ -475,2 +568,82 @@ else:

####################################
# Set labels, limits and more
####################################
def _plot_finalizer(xlog, ylog, xlim, ylim, title, xlabel, ylabel, xinvert, yinvert, gridpar, ax=None):
"""New axis handler
This function is a base-level function used by most other plotting functions to set the current
Axes instance to ax and returns the old Axes instance to be later reverted.
Parameters
----------
xlog : None or bool
If True, the x-axis scale is set to 'log'.
ylog : None or bool
If True, the y-axis scale is set to 'log'.
xlim : None or array-like
If given, defines the low and high limits for the x-axis. The first two elements must be int
and/or float.
ylim : None or array-like
If given, defines the low and high limits for the y-axis. The first two elements must be int
and/or float.
title : None or str
If given, defines the title of the figure.
xlabel : None or str
If given, defines the label of the x-axis.
ylabel : None or str
If given, defines the label of the y-axis.
xinvert : None or bool
If True, ensures the x-axis is inverted. If False, ensures the x-axis is not inverted.
yinvert : None or bool
If True, ensures the y-axis is inverted. If False, ensures the y-axis is not inverted.
gridpar : None or dict
The parameters to be given to matplotlib's grid function.
ax : Axes object or None
The axis for which to apply the plot parameters.
Returns
-------
None
"""
ax = gca() if ax is None else ax
if xlog:
ax.set_xscale('log')
if ylog:
ax.set_yscale('log')
if xlim == 'auto':
ax.autoscale(axis='x')
else:
ax.set_xlim(xlim)
if ylim == 'auto':
ax.autoscale(axis='y')
else:
ax.set_ylim(ylim)
if title is not None:
ax.set_title(title)
if xlabel is not None:
ax.set_xlabel(xlabel)
if ylabel is not None:
ax.set_ylabel(ylabel)
if xinvert:
if not ax.xaxis_inverted():
ax.invert_xaxis()
if yinvert:
if not ax.yaxis_inverted():
ax.invert_yaxis()
if gridpar is not None:
ax.grid(**gridpar)
####################################
# Modified fill_between for hist

@@ -477,0 +650,0 @@ ####################################

@@ -21,3 +21,3 @@ ########################################################################

hist1D_yaxis_log = False
hist2D_output = False
hist2D_output = True
hist2D_caxis_log = False

@@ -30,5 +30,5 @@

# Grids
grid_color = 'white'
grid_color = 'grey'
grid_alpha = 1.0
grid_ls = '--'
grid_ls = '-'
grid_lw = 1.0

@@ -35,0 +35,0 @@

@@ -12,3 +12,3 @@ ########################################################################

from matplotlib.legend_handler import HandlerLine2D, HandlerPathCollection, HandlerTuple
from matplotlib.pyplot import bar, barh, fill_between, fill_betweenx, gca, legend, plot as plt_plot, sca, stairs # step
from matplotlib.pyplot import bar, barh, fill_between, fill_betweenx, gcf, gca, legend, plot as plt_plot, sca, stairs # step
from matplotlib.transforms import Bbox

@@ -20,5 +20,6 @@ from matplotlib.colors import to_rgba_array

from .base_func import axes_handler, bin_axis, dict_splicer, is_numeric, is_number, plot_finalizer, simpler_dict_splicer
from .base_func import axes_handler, grid_handler, lims_handler, bin_axis, dict_splicer, is_numeric, is_number, plot_finalizer, _plot_finalizer, simpler_dict_splicer
from .defaults import Params
####################################

@@ -65,4 +66,7 @@ # Generalized lines

Sets the legend for the plot.
grid : boolean, optional
If not given defaults to the value defined in splotch.Params.
grid : boolean, dict or None
The grid behaviour, acts according to:
* If boolean, the grid is set on (True) or off (False).
* If dict, allows specific `.Line2D` properties of the grid to be set.
* If None, use default grid parameters if this is the initial plotting call, otherwise do nothing.
ax : pyplot.Axes or list-like, optional

@@ -85,3 +89,3 @@ Specifies the axes on which to plot the lines, defaults to the current axes.

# Set the current axis
# Get the relevant axis
if ax is not None:

@@ -91,9 +95,8 @@ if isinstance(ax, (list, tuple, ndarray)):

ax = array(ax).flatten()
old_ax = axes_handler(ax[0])
else:
ax = [ax] # Axis must be a list to be enumerated over
old_ax = axes_handler(ax[0])
ax = [ax] # Axis must be a list-like object
else:
ax = [gca()]
old_ax = ax[0]
old_ax = axes_handler(ax[0]) # sets the current axis and returns old axis

@@ -150,5 +153,3 @@ # Validate input parameters

# Combine the `explicit` plot_kw dictionary with the `implicit` **kwargs dictionary
# plot_par={**plot_kw, **kwargs} # For Python > 3.5
plot_par = plot_kw.copy()
plot_par.update(kwargs)
plot_par = {**plot_kw, **kwargs}

@@ -158,5 +159,9 @@ # Create 'L' number of plot kwarg dictionaries to parse into each plot call

lines = [[] for kk in range(len(ax))] # Initialise list which contains each Line2D object
lines = [[]] * len(ax) # Initialise list which contains each Line2D object
for jj, axis in enumerate(ax): # Loop over all axes
sca(axis)
gridpar = grid_handler(grid, axis)
ax_xlim = axis.get_xlim() if lims_handler(xlim, axis) is None else xlim
ax_ylim = axis.get_ylim() if lims_handler(ylim, axis) is None else ylim
if (x is not None):

@@ -169,18 +174,9 @@ for ii, xx in enumerate(x):

if (a is not None):
for ii, (aa, bb) in enumerate(zip(a, b)):
xLims = axis.get_xlim() if xlim is None else xlim
yLims = axis.get_ylim() if ylim is None else ylim
for ii, (aa, bb) in enumerate(zip(a, b)): # Loop over all lines
lines[jj].append(axis.axline(xy1=(0, bb), slope=aa, label=label[ii], **plot_par[ii]))
lines[jj].append(axis.plot([xLims[0], xLims[1]], [aa * xLims[0] + bb, aa * xLims[1] + bb], label=label[ii], **plot_par[ii])[0])
axis.set_xlim(xLims)
axis.set_ylim(yLims)
_plot_finalizer(xlog, ylog, ax_xlim, ax_ylim, title, xlabel, ylabel, xinvert, yinvert, gridpar, axis)
plot_finalizer(xlog, ylog, xlim, ylim, title, xlabel, ylabel, xinvert, yinvert, grid)
# Autoscale the axes if needed
if xlim is None: axis.autoscale(axis='x')
if ylim is None: axis.autoscale(axis='y')
if old_ax is not None: # Reset the previously set axis
old_ax = axes_handler(old_ax)
sca(old_ax)

@@ -193,6 +189,5 @@ return squeeze(lines).tolist() # Reduce the dimensionality of the lines, if needed

####################################
def brokenplot(x,y=None,xbreak=None,ybreak=None,xlim=None,ylim=None,sep=0.05,
xinvert=False,yinvert=False,xlog=False,ylog=False,title=None,
xlabel=None,ylabel=None,label=None,lab_loc=0,ax=None,grid=None,plot_kw={},**kwargs):
def brokenplot(x, y=None, xbreak=None, ybreak=None, xlim=None, ylim=None, sep=0.05, overflow=0.05,
xinvert=False, yinvert=False, xlog=False, ylog=False, title=None,
xlabel=None, ylabel=None, label=None, lab_loc=0, ax=None, grid=None, plot_kw={}, **kwargs):
"""Broken Axis Plot Function

@@ -218,4 +213,7 @@

Defines the limits of the y-axis, it must contain two elements (lower and higer limits).
sep : float, optional, default: 0.05
sep : float, optional (default: 0.05)
The separation size of the axis break, given as a fraction of the axis dimensions.
overflow : float, optional (default: 0.05)
The fractional overflow of the axes beyond the break values. Often good to keep this non-zero as
breaking the axis exactly at the break point leaves the final gridlines and ticks out of view.
xinvert : bool or list, optional

@@ -247,3 +245,3 @@ If true inverts the x-axis.

kwargs are used to specify matplotlib specific properties such as linecolor, linewidth,
antialiasing, etc. A list of available `Line2D` properties can be found here:
antialiasing, etc. A list of available `Line2D` properties can be found here:
https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D

@@ -253,122 +251,188 @@

-------
lines
lines : list of Line2D
A list of Line2D objects (paired as tuples) representing the plotted data.
The lines are given as pairs to correspond to the separate lines either side of the x/ybreak.
ax2 : pyplot.Axes, optional
The additional axis object created.
"""
if ax is not None:
old_axes=axes_handler(ax)
else:
ax=gca()
old_axes=ax
# Get the relevant axis
ax1 = gca() if ax is None else ax
old_ax = axes_handler(ax1)
if type(x) is not list or len(shape(x))==1:
x=[x]
L=len(x)
gridpar = grid_handler(grid, ax1)
if not isinstance(x, (list, tuple, ndarray)) or len(shape(x)) == 1:
x = [x]
L = len(x)
if y is None:
y=x
x=[arange(len(x[i])) for i in range(L)]
y = x
x = [arange(len(x[i])) for i in range(L)]
else:
if type(y) is not list or len(shape(y))==1:
y=[y]
if type(label) is not list:
label=[label for i in range(L)]
if not isinstance(y, (list, tuple, ndarray)) or len(shape(y)) == 1:
y = [y]
if not isinstance(label, (list, tuple, ndarray)) or len(shape(label)) == 1:
label = [label] * L
# Combine the `explicit` plot_kw dictionary with the `implicit` **kwargs dictionary
plot_par = {**plot_kw, **kwargs}
# Validate x/ybreak
if (xbreak == None):
# Create 'L' number of plot kwarg dictionaries to parse into each plot call
plot_par = dict_splicer(plot_par, L, [1] * L)
# Get the original axis position
pos0 = ax.get_position(original=True)
width0, height0 = pos0.x1 - pos0.x0, pos0.y1 - pos0.y0
# Perform first plot call to original axis
lines = []
for i in range(L):
lines += ax1.plot(x[i], y[i], label=label[i], **plot_par[i])
# Get the axis limits if not already specified
xlims = ax1.get_xlim() if xlim in [None, 'auto'] else xlim
ylims = ax1.get_ylim() if ylim in [None, 'auto'] else ylim
# Validate xbreak/ybreak
if (xbreak is None and ybreak is None):
raise ValueError("Require either xbreak/ybreak to be specified.")
if (ybreak != None):
raise NotImplementedError("ybreak not yet implemented.")
if (ybreak is not None and xbreak is not None):
raise ValueError("Cannot specify both xbreak and ybreak.")
breaks = xbreak if xbreak else ybreak
if not isinstance(breaks, (list, tuple, ndarray)):
breaks = (breaks,) * 2
else:
if len(breaks) == 1:
breaks = (breaks[0],) * 2
elif len(breaks) == 2:
if breaks[0] > breaks[1]:
raise ValueError("First value in xbreak/ybreak must not be greater than the second value.")
else:
raise ValueError("xbreak/ybreak must be a single value or a list-like object with two elements.")
if type(xbreak) not in [list,tuple,ndarray]:
xbreak=(xbreak, xbreak)
else:
if (len(xbreak) != 2):
raise ValueError("xbreak must be a single value of a tuple-like list of two elements.")
if xbreak and (breaks[0] < xlims[0] or breaks[1] > xlims[1]):
raise ValueError(f"xbreak given ({xbreak}) must be within the xlim ({xlims}).")
if ybreak and (breaks[0] < ylims[0] or breaks[1] > ylims[1]):
raise ValueError(f"ybreak given ({ybreak}) must be within the ylim ({ylims}).")
# Define the positions of the two separated axes
pos1 = Bbox(list(pos0.get_points()))
if xbreak:
pos1.x1 = pos1.x0 + (pos1.x1 - pos1.x0) * (sum(breaks) / 2 - xlims[0]) / (xlims[1] - xlims[0]) - sep * (pos1.x1 - pos1.x0) / 2
if ybreak:
pos1.y1 = pos1.y0 + (pos1.y1 - pos1.y0) * (sum(breaks) / 2 - ylims[0]) / (ylims[1] - ylims[0]) - sep * (pos1.y1 - pos1.y0) / 2
pos2 = Bbox(list(pos0.get_points()))
if xbreak:
pos2.x0 = pos2.x0 + (pos2.x1 - pos2.x0) * (sum(breaks) / 2 - xlims[0]) / (xlims[1] - xlims[0]) + sep * (pos2.x1 - pos2.x0) / 2
if ybreak:
pos2.y0 = pos2.y0 + (pos2.y1 - pos2.y0) * (sum(breaks) / 2 - ylims[0]) / (ylims[1] - ylims[0]) + sep * (pos2.y1 - pos2.y0) / 2
ax1.set_position(pos1) # Resize the first axis
ax2 = ax1.figure.add_axes(pos2) # Add and duplicate the plotting in the second axis
# Combine the `explicit` plot_kw dictionary with the `implicit` **kwargs dictionary
#plot_par={**plot_kw, **kwargs} # For Python > 3.5
plot_par=plot_kw.copy()
plot_par.update(kwargs)
# Perform first plot call to original axis
lines = []
for i in range(L):
lines += ax2.plot(x[i], y[i], label=label[i], **plot_par[i])
# Create 'L' number of plot kwarg dictionaries to parse into each plot call
plot_par=dict_splicer(plot_par,L,[1]*L)
width1, height1 = pos1.x1 - pos1.x0, pos1.y1 - pos1.y0
width2, height2 = pos2.x1 - pos2.x0, pos2.y1 - pos2.y0
# Adjust x/y limits for both axes
xlim1 = (xlims[0], breaks[0] + overflow * (width1 + sep) / width0) if xbreak else xlims
xlim2 = (breaks[1] - overflow * (width2 + sep) / width0, xlims[1]) if xbreak else xlims
# Get the original axis position
pos0=ax.get_position(original=True)
width0, height0=pos0.x1 - pos0.x0, pos0.y1 - pos0.y0
ylim1 = (ylims[0], breaks[0]) if ybreak else ylims
ylim2 = (breaks[1], ylims[1]) if ybreak else ylims
lines=[] # Initialising list which contains each line
for i in range(L):
# First side plot call
l1=ax.plot(x[i],y[i],label=label[i],**plot_par[i])
if xbreak:
dx1, dy1 = 0.01 * width0 / (width0 - width2 - sep / 2), height1 * 0.025
dx2, dy2 = 0.01 * width0 / (width0 - width1 - sep / 2), height2 * 0.025
# Get the axis limits if not already specified
xlims=ax.get_xlim() if xlim == None else xlim
ylims=ax.get_ylim() if ylim == None else ylim
# Define the positions of the two separated axes
if (i == 0):
pos1=Bbox(list(pos0.get_points()))
pos1.x1=pos1.x0 + (pos1.x1-pos1.x0)*(sum(xbreak)/2-xlims[0])/(xlims[1]-xlims[0]) - sep*(pos1.x1-pos1.x0)/2
pos2=Bbox(list(pos0.get_points()))
pos2.x0=pos2.x0 + (pos2.x1-pos2.x0)*(sum(xbreak)/2-xlims[0])/(xlims[1]-xlims[0]) + sep*(pos2.x1-pos2.x0)/2
ax.set_position(pos1) # Resize the first axis
ax2=ax.figure.add_axes(pos2) # Add and duplicate the plotting in the second axis
# Set the new axis limits at the break point
ax.set_xlim(xlims[0],xbreak[0])
ax2.set_xlim(xbreak[1],xlims[1])
dash_kw = dict(transform=ax1.transAxes, color='black', linestyle='-', marker='', clip_on=False)
temp1 = (breaks[0] - xlim1[0]) / (breaks[0] - xlim1[0] + overflow * (width1 + sep) / width0)
ax1.plot((temp1 - dx1, temp1 + dx1), (0 - dy1, 0 + dy1), **dash_kw) # bottom-left
ax1.plot((temp1 - dx1, temp1 + dx1), (1 - dy1, 1 + dy1), **dash_kw) # top-left
# Second side plot call
l2=ax2.plot(x[i],y[i],label=None,**plot_par[i])
dash_kw.update(transform=ax2.transAxes) # switch to the left axes
temp2 = 1 - (xlim2[1] - breaks[1]) / (xlim2[1] - breaks[1] + overflow * (width2 + sep) / width0)
ax2.plot((temp2 - dx2, temp2 + dx2), (0 - dy2, 0 + dy2), **dash_kw) # bottom-right
ax2.plot((temp2 - dx2, temp2 + dx2), (1 - dy2, 1 + dy2), **dash_kw) # top-right
lines.append((*l1,*l2)) # Add line as tuple of both axes.
# Fix axes labels and splines
ax1.spines['right'].set_visible(False)
ax1.tick_params(labelright=False, which='both')
ax1.yaxis.tick_left()
ax2.spines['left'].set_visible(False)
ax2.tick_params(labelleft=False, which='both')
ax2.yaxis.tick_right()
if (ax1.get_xticks()[-1] == ax2.get_xticks()[0]):
if (breaks[0] >= (xlims[0] + xlims[1]) * 0.5):
ax1.set_xticks(ax1.get_xticks()[:-1]) # Remove duplicate tick on left side
else:
ax2.set_xticks(ax2.get_xticks()[1:]) # Remove duplicate tick on right side
ax1.axes.spines[['top', 'bottom']].set_bounds((xlim1[0], breaks[0]))
ax2.axes.spines[['top', 'bottom']].set_bounds((breaks[1], xlim2[-1]))
width1, height1=pos1.x1 - pos1.x0, pos1.y1 - pos1.y0
width2, height2=pos2.x1 - pos2.x0, pos2.y1 - pos2.y0
ax1.xaxis.set_label_coords(0.5 * (width0 / width1), -0.125, transform=ax1.axes.transAxes)
if ybreak:
dx1, dy1 = width1 * 0.0125, 0.02 * height0 / (height0 - height2 - sep / 2)
dx2, dy2 = width2 * 0.0125, 0.02 * height0 / (height0 - height1 - sep / 2)
dx1, dy1=0.01 * width0/(width0-width1-sep/2), height1*0.025
dash_kw = dict(transform=ax1.transAxes, color='black', linestyle='-', marker='', clip_on=False)
temp1 = (breaks[0] - ylim1[0]) / (breaks[0] - ylim1[0] + overflow * (height1 + sep) / height0)
ax1.plot((0 - dx1, 0 + dx1), (temp1 - dy1, temp1 + dy1), **dash_kw) # bottom-left
ax1.plot((1 - dx1, 1 + dx1), (temp1 - dy1, temp1 + dy1), **dash_kw) # bottom-right
dash_kw=dict(transform=ax2.transAxes, color='black', linestyle='-', marker='', clip_on=False)
ax2.plot((0 - dx1, 0 + dx1), (0 - dy1, 0 + dy1), **dash_kw) # bottom-right diagonal
ax2.plot((0 - dx1, 0 + dx1), (1 - dy1, 1 + dy1), **dash_kw) # top-right diagonal
dx2, dy2=0.01 * width0/(width0-width2-sep/2), height2*0.025
dash_kw.update(transform=ax.transAxes) # switch to the left axes
ax.plot((1 - dx2, 1 + dx2), (0 - dy2, 0 + dy2), **dash_kw) # bottom-left sep/5iagonal
ax.plot((1 - dx2, 1 + dx2), (1 - dy2, 1 + dy2), **dash_kw) # top-left sep/5iagonal
ax.spines['right'].set_visible(False)
ax.tick_params(labelright=False,which='both') # don't put tick labels at the top
ax.yaxis.tick_left()
ax2.spines['left'].set_visible(False)
ax2.tick_params(labelleft=False,which='both') # don't put tick labels at the top
ax2.yaxis.tick_right()
dash_kw.update(transform=ax2.transAxes) # switch to the left axes
temp2 = 1 - (ylim2[1] - breaks[1]) / (ylim2[1] - breaks[1] + overflow * (height2 + sep) / height0)
ax2.plot((0 - dx2, 0 + dx2), (temp2 - dy2, temp2 + dy2), **dash_kw) # top-left
ax2.plot((1 - dx2, 1 + dx2), (temp2 - dy2, temp2 + dy2), **dash_kw) # top-right
# Check that there is no duplicate ticks over both axes
if (xbreak):
if (ax.get_xticks()[-1] == ax2.get_xticks()[0]):
if (xbreak[0] >= (xlims[0] + xlims[1])*0.5):
ax.set_xticks(ax.get_xticks()[:-1]) # Remove duplicate tick on left side
# Fix axes labels and splines
ax1.spines['top'].set_visible(False)
ax1.tick_params(labeltop=False, which='both')
ax1.xaxis.tick_bottom()
ax2.spines['bottom'].set_visible(False)
ax2.tick_params(labelbottom=False, which='both')
ax2.xaxis.tick_top()
if (ax1.get_yticks()[-1] == ax2.get_yticks()[0]):
if (breaks[0] >= (ylims[0] + ylims[1]) * 0.5):
ax1.set_yticks(ax1.get_yticks()[:-1]) # Remove duplicate tick on top
else:
ax2.set_xticks(ax2.get_xticks()[1:]) # Remove duplicate tick on right side
sca(ax)
ax2.set_yticks(ax2.get_yticks()[1:]) # Remove duplicate tick on bottom
ax1.axes.spines[['left', 'right']].set_bounds((ylim1[0], breaks[0]))
ax2.axes.spines[['left', 'right']].set_bounds((breaks[1], ylim2[-1]))
ax1.yaxis.set_label_coords(-0.125, 0.5 * (height0 / height1), transform=ax1.axes.transAxes)
if any(label):
ax.legend(loc=lab_loc)
ax2.legend(loc=lab_loc)
plot_finalizer(xlog,ylog,xlim,ylim,title,xlabel,ylabel,xinvert,yinvert,grid)
_plot_finalizer(xlog, ylog, xlim1, ylim1, title, xlabel, ylabel, xinvert, yinvert, gridpar, ax=ax1)
_plot_finalizer(xlog, ylog, xlim2, ylim2, None, None, None, xinvert, yinvert, gridpar, ax=ax2)
if old_axes is not ax:
old_axes=axes_handler(old_axes)
# Share axes
if xbreak:
ax1.get_shared_y_axes().join(ax, ax2)
if ybreak:
ax1.get_shared_x_axes().join(ax, ax2)
if old_ax is not None:
sca(old_ax)
return (lines[0] if len(lines) == 1 else lines)
return (lines[0] if len(lines) == 1 else lines, ax2)

@@ -427,4 +491,7 @@

If True the scale of the x-axis is logarithmic.
grid : boolean, optional
If not given defaults to the value defined in splotch.Params.
grid : boolean, dict or None
The grid behaviour, acts according to:
* If boolean, the grid is set on (True) or off (False).
* If dict, allows specific `.Line2D` properties of the grid to be set.
* If None, use default grid parameters if this is the initial plotting call, otherwise do nothing.
title : str, optional

@@ -472,14 +539,16 @@ Sets the title of the plot

if ax is not None:
old_axes = axes_handler(ax)
else:
ax = gca()
old_axes = ax
# Get the relevant axis
if ax is None: ax = gca()
old_ax = axes_handler(ax)
gridpar = grid_handler(grid, ax)
xlim = lims_handler(xlim, ax)
ylim = lims_handler(ylim, ax)
# Assign bounds if none given
if (bounds is None):
if orientation == 'horizontal':
bounds = xlim if xlim is not None else ax.get_xlim()
bounds = xlim if xlim not in [None, 'auto'] else ax.get_xlim()
else:
bounds = ylim if ylim is not None else ax.get_ylim()
bounds = ylim if ylim not in [None, 'auto'] else ax.get_ylim()

@@ -589,11 +658,7 @@ # Parse expression

plot_finalizer(xlog, ylog, xlim, ylim, title, xlabel, ylabel, xinvert, yinvert, grid)
_plot_finalizer(xlog, ylog, xlim, ylim, title, xlabel, ylabel, xinvert, yinvert, gridpar, ax)
# Autoscale the axes if needed
if xlim is None: ax.autoscale(axis='x')
if ylim is None: ax.autoscale(axis='y')
if old_ax is not None:
sca(old_ax)
if old_axes is not ax:
old_axes = axes_handler(old_axes)
return(curves[0] if len(curves) == 1 else curves, expr)

@@ -611,3 +676,3 @@

Plot the curve(s) corresponding to mathematical expressions over a given range across the independent variable.
Expressions can be given with multiple variables, whereby one is taken to be the independent variable and all
Expressions can be given with multiple variables, whereby one is taken to be the independent variable and all
others are substitution variables. This function can only accept one value per substitution variable.

@@ -619,3 +684,3 @@

An expression parsed either as a string, sympy expression or callable (function or lambda)
which will be evaluated by the function in the range of `bounds`. A piece-wise function is defined
which will be evaluated by the function in the range of `bounds`. A piece-wise function is defined
by parsing a list of expressions. The piece-wise functionality must also be reflected in `bounds`.

@@ -631,5 +696,5 @@ var : str or sympy symbol, required.

the x-axis ('horizontal') or the y-axis ('vertical') of the plot.
splotch.curve('a*x') is notationally the same as splotch.curve('1/a*y',var='y',orientation='vertical').
splotch.curve('a*x') is notationally the same as splotch.curve('1/a*y',var='y',orientation='vertical').
bounds : list-like, optional
The range over which the function will be plotted. If not given, these default to the current bounds
The range over which the function will be plotted. If not given, these default to the current bounds
of the axes being plotted onto, given by `ax`.

@@ -653,4 +718,7 @@ intervals : list-like, required

If True the scale of the x-axis is logarithmic.
grid : boolean, optional
If not given defaults to the value defined in splotch.Params.
grid : boolean, dict or None
The grid behaviour, acts according to:
* If boolean, the grid is set on (True) or off (False).
* If dict, allows specific `.Line2D` properties of the grid to be set.
* If None, use default grid parameters if this is the initial plotting call, otherwise do nothing.
title : str, optional

@@ -684,6 +752,6 @@ Sets the title of the plot

**kwargs: Line2D properties, optional
kwargs are used to specify matplotlib specific properties such as linecolor, linewidth,
antialiasing, etc. A list of available `Line2D` properties can be found here:
kwargs are used to specify matplotlib specific properties such as linecolor, linewidth,
antialiasing, etc. A list of available `Line2D` properties can be found here:
https://matplotlib.org/3.1.0/api/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D
Returns

@@ -696,13 +764,14 @@ -------

Otherwise, simply returns the `expr` that was given.
"""
# Get the relevant axis
if ax is None: ax = gca()
old_ax = axes_handler(ax)
gridpar = grid_handler(grid, ax)
xlim = lims_handler(xlim, ax)
ylim = lims_handler(ylim, ax)
if ax is not None:
old_axes=axes_handler(ax)
else:
ax=gca()
old_axes=ax
# Assign bounds if none given
if (bounds == None):
# Assign bounds if none given
if (bounds is None):
if orientation == 'horizontal':

@@ -714,5 +783,5 @@ bounds = xlim if xlim is not None else ax.get_xlim()

# Check if iterable
try: # duck-type check
try: # duck-type check
_ = (k for k in expr)
if isinstance(expr,str):
if isinstance(expr, str):
expr = [expr]

@@ -725,6 +794,6 @@ elif isinstance(expr, tuple):

# Parse expressions
isfunc=[False]*len(expr)
isfunc = [False] * len(expr)
for ii in range(len(expr)):
if (isinstance(expr[ii], str)):
expr[ii]=sympify(expr[ii])
expr[ii] = sympify(expr[ii])
elif (callable(expr[ii])):

@@ -737,3 +806,3 @@ isfunc[ii] = True

if not (all(isfunc) or all([not b for b in isfunc])): # Must either be all expressions or all callables
if not (all(isfunc) or all([not b for b in isfunc])): # Must either be all expressions or all callables
raise TypeError("`expr` cannot mix callable functions with expressions.")

@@ -747,3 +816,3 @@

intervals = list(intervals)
if len(intervals) == 0: # if no intervals are given, set the interval to be the ending bound as a placeholder
if len(intervals) == 0: # if no intervals are given, set the interval to be the ending bound as a placeholder
if len(expr) == 1:

@@ -753,5 +822,5 @@ intervals = [bounds[-1]]

raise ValueError(f"No intervals given for {len(expr)} expressions.")
elif len(intervals) != len(expr) - 1:
elif len(intervals) != len(expr) - 1:
raise ValueError(f"There should be N-1 intervals for N expressions, instead received {len(intervals)} intervals for {len(expr)} expressions.")
else: # If intervals are given, ensure they are within the bounds given.
else: # If intervals are given, ensure they are within the bounds given.
if min(intervals) <= bounds[0]:

@@ -763,3 +832,2 @@ raise ValueError(f"The minimum interval value should be within the current bounds ({bounds[0]}, {bounds[1]}).")

intervals = [intervals]

@@ -772,16 +840,16 @@ # Validate the substitution variable names

if subs is None: subs = dict()
if not any(isfunc): # expr contains Sympy expressions
if not any(isfunc): # expr contains Sympy expressions
symbols = expr[0].free_symbols
for ii in range(len(expr)):
symbols=expr[ii].free_symbols # Get expression Symbols
symbolkeys=[str(symb) for symb in symbols] # convert these to strings, instead of sympy.Symbols
symbols = expr[ii].free_symbols # Get expression Symbols
symbolkeys = [str(symb) for symb in symbols] # convert these to strings, instead of sympy.Symbols
if var is None: # Assume independent variable is 'x', otherwise, assume the first symbol.
if var is None: # Assume independent variable is 'x', otherwise, assume the first symbol.
if orientation == 'horizontal':
var = 'x' #if 'x' in symbolkeys or len(symbolkeys)==0 else None
else: # first test for 'y' as an independent variable, then default to x.
var = 'x' # if 'x' in symbolkeys or len(symbolkeys)==0 else None
else: # first test for 'y' as an independent variable, then default to x.
if 'y' in symbolkeys:
var = 'y'
else:
var = 'x' #if 'x' in symbolkeys or len(symbolkeys)==0 else symbolkeys[0]
var = 'x' # if 'x' in symbolkeys or len(symbolkeys)==0 else symbolkeys[0]

@@ -797,3 +865,3 @@ # remove the independent variable from the symbols and append to list

# Check for any substitution variables that are not in any expressions
for key in list(subs):
for key in list(subs):
if not any([key in symb for symb in symbolkeysArr]):

@@ -803,25 +871,25 @@ raise KeyError(f"Substitution variable '{key}' does not exist in any 'expr'")

# The lengths of each substitute value list, len=1 if just a single value
lens = [len(subs[key]) if (isinstance(subs[key], Iterable) and type(subs[key])!=str) else 1 for key in list(subs)]
if (permute == True):
lens = [len(subs[key]) if (isinstance(subs[key], Iterable) and not isinstance(subs[key], str)) else 1 for key in list(subs)]
if (permute is True):
L = prod(lens)
perms=array(meshgrid(*subs.values())).reshape(len(subs),-1)
permsubs={}
perms = array(meshgrid(*subs.values())).reshape(len(subs), -1)
permsubs = {}
for ii, key in enumerate(list(subs)):
permsubs[key]=perms[ii]
subsarr=simpler_dict_splicer(permsubs,L,[1]*L)
permsubs[key] = perms[ii]
subsarr = simpler_dict_splicer(permsubs, L, [1] * L)
else:
L=max(lens) if len(lens) > 0 else 1
subsarr=simpler_dict_splicer(subs,L,[1]*L)
L = max(lens) if len(lens) > 0 else 1
subsarr = simpler_dict_splicer(subs, L, [1] * L)
# Combine the `explicit` plot_kw dictionary with the `implicit` **kwargs dictionary
plot_par={**plot_kw, **kwargs}
plot_par = {**plot_kw, **kwargs}
# Create 'L' number of plot kwarg dictionaries to parse into each plot call
plot_par=dict_splicer(plot_par,L,[1]*L)
plot_par = dict_splicer(plot_par, L, [1] * L)
# Create the legend object
if bool(label) == False: # label was `None` or `False`
labellist = None
if bool(label) is False: # label was `None` or `False`
labellist = [None] * L
elif label == True: # Auto-generate labels
elif label is True: # Auto-generate labels
if subsarr == [{}]:

@@ -832,15 +900,15 @@ labellist = [f"${latex(expr)}$" if uselatex else str(expr)]

exprstr = f"${latex(expr)}$" if uselatex else str(expr)
for ii in range(L): # Make a label for each of sub values
for ii in range(L): # Make a label for each of sub values
if uselatex:
labellist.append(f"{exprstr} (" + "; ".join( [f"${key}$={subsarr[ii][key]}" for jj, key in enumerate(list(subsarr[ii])) ] ) +")" ) # join substitute strings together
labellist.append(f"{exprstr} (" + "; ".join([f"${key}$={subsarr[ii][key]}" for jj, key in enumerate(list(subsarr[ii]))]) + ")")
else:
labellist.append(f"{exprstr} (" + "; ".join( [f"{key}={subsarr[ii][key]}" for jj, key in enumerate(list(subsarr[ii])) ] ) +")" ) # join substitute strings together
labellist.append(f"{exprstr} (" + "; ".join([f"{key}={subsarr[ii][key]}" for jj, key in enumerate(list(subsarr[ii]))]) + ")")
elif isinstance(label,str): # A single string
labellist = [label]*L
elif isinstance(label, str): # A single string
labellist = [label] * L
else:
try: # Test whether the parameter is iterable
try: # Test whether the parameter is iterable
_ = (k for k in label)
except TypeError: # was not an iterable
except TypeError: # was not an iterable
raise TypeError(f"`label` of type {type(label)} is not recognised.")

@@ -853,6 +921,6 @@

vararr = logspace(*log10(bounds),num=num) if xlog else linspace(*bounds,num=num)
vararr = logspace(*log10(bounds), num=num) if xlog else linspace(*bounds, num=num)
intervals.append(vararr[-1])
curves=[None]*L
for ii in range(L):
curves = [None] * L
for ii in range(L):
lines = []

@@ -863,3 +931,3 @@ start = 0

if any(isfunc):
lines.append( list( zip(vararr[start:end+1],expr[jj](vararr[start:end+1],**subsarr[ii])) ) )
lines.append(zip(vararr[start: end + 1], expr[jj](vararr[start: end + 1], **subsarr[ii])))
else:

@@ -873,7 +941,7 @@ lamb = lambdify(var, expr[jj].subs(subsarr[ii]), modules='numpy')

if orientation == 'horizontal':
lines.append( list( zip(vararr[start:end+1],func(vararr[start:end+1])) ) )
lines.append(list(zip(vararr[start: end + 1], func(vararr[start: end + 1]))))
else:
lines.append( list( zip(func(vararr[start:end+1]),vararr[start:end+1]) ) )
lines.append(list(zip(func(vararr[start: end + 1]), vararr[start: end + 1])))
start = end # move to next interval
start = end # move to next interval

@@ -883,11 +951,8 @@ curves[ii] = LineCollection(lines, label=labellist[ii], **plot_par[ii])

plot_finalizer(xlog,ylog,xlim,ylim,title,xlabel,ylabel,xinvert,yinvert,grid)
_plot_finalizer(xlog, ylog, xlim, ylim, title, xlabel, ylabel, xinvert, yinvert, gridpar, ax)
if xlim is None: ax.autoscale(axis='x')
if ylim is None: ax.autoscale(axis='y')
if old_ax is not None:
sca(old_ax)
if old_axes is not ax:
old_axes=axes_handler(old_axes)
return(curves[0] if len(curves)==1 else curves)
return(curves[0] if len(curves) == 1 else curves)

@@ -898,10 +963,2 @@

####################################
########################################################################
############## Definition of all wrappers for 1D plotting ##############
########################################################################
####################################
# 1D histogram and binned statistics
####################################
def hist(data, bin_type=None, bins=None, dens=True, cumul=None, scale=None, weights=None, hist_type=None, orientation='vertical',

@@ -986,4 +1043,7 @@ v=None, vstat=None, nmin=0, color=None, xlim=None, ylim=None, xinvert=False, yinvert=False, xlog=False, ylog=None, title=None,

Use the given axes to make the plot, defaults to the current axes.
grid : boolean, optional
If not given defaults to the value defined in splotch.Params.
grid : boolean, dict or None
The grid behaviour, acts according to:
* If boolean, the grid is set on (True) or off (False).
* If dict, allows specific `.Line2D` properties of the grid to be set.
* If None, use default grid parameters if this is the initial plotting call, otherwise do nothing.
plot_par : dict, optional

@@ -1004,11 +1064,14 @@ Passes the given dictionary as a kwarg to the plotting function.

# Set the current axis
if ax is not None:
old_axes = axes_handler(ax)
else:
ax = gca()
old_axes = ax
# Get the relevant axis
if ax is None: ax = gca()
old_ax = axes_handler(ax)
if not isinstance(data, (list, tuple, ndarray)) or (len(shape(data)) == 1 and array(data).dtype is not dtype('O')):
gridpar = grid_handler(grid, ax)
if not isinstance(data, (list, tuple, ndarray)):
data = [data]
elif isinstance(data, ndarray):
if (len(shape(data)) == 1 and data.dtype is not dtype('O')):
data = [data]
L = len(data)

@@ -1041,3 +1104,2 @@

xlim = [nanmean(data) - xlim * nanstd(data), nanmean(data) + xlim * nanstd(data)]
if ylog is None:

@@ -1053,6 +1115,7 @@ ylog = Params.hist1D_yaxis_log

xlim = lims_handler(xlim, ax)
ylim = lims_handler(ylim, ax)
# Combine the `explicit` plot_kw dictionary with the `implicit` **kwargs dictionary
# plot_par={**plot_kw, **kwargs} # For Python > 3.5
plot_par = plot_kw.copy()
plot_par.update(kwargs)
plot_par = {**plot_kw, **kwargs}

@@ -1158,18 +1221,21 @@ # Check if width is given as a kwarg

if ylim is None and orientation == 'vertical': # Adjust ylims if None given.
if ylim == 'auto' and orientation == 'vertical': # Adjust ylims if None given.
if not ylog and all([val is None for val in v]): # These automatic limits do not apply when ylog=True or statistics are used.
ylim = [0, max(nanmax(y) * (1 + rcParams['axes.ymargin']), gca().get_ylim()[1])]
if xlim is None and orientation == 'horizontal': # Adjust xlims if None given.
if xlim == 'auto' and orientation == 'horizontal': # Adjust xlims if 'auto'.
if not xlog and all([val is None for val in v]): # These automatic limits do not apply when ylog=True or statistics are used.
xlim = [0, max(nanmax(y) * (1 + rcParams['axes.xmargin']), gca().get_xlim()[1])]
plot_finalizer(xlog, ylog, xlim, ylim, title, xlabel, ylabel, xinvert, yinvert, grid)
_plot_finalizer(xlog, ylog, xlim, ylim, title, xlabel, ylabel, xinvert, yinvert, gridpar, ax)
if old_axes is not ax:
old_axes = axes_handler(old_axes)
if old_ax is not ax:
sca(old_ax)
if len(n_return) == 1:
n_return = n_return[0]
if len(bin_edges) == 1:
bin_edges = bin_edges[0]
if output:

@@ -1219,4 +1285,7 @@ return(n_return, bin_edges)

Use the given axes to make the plot, defaults to the current axes.
grid : boolean, optional
If not given defaults to the value defined in splotch.Params.
grid : boolean, dict or None
The grid behaviour, acts according to:
* If boolean, the grid is set on (True) or off (False).
* If dict, allows specific `.Line2D` properties of the grid to be set.
* If None, use default grid parameters if this is the initial plotting call, otherwise do nothing.
plot_kw : dict, optional

@@ -1236,8 +1305,10 @@ Passes the given dictionary as a kwarg to the plotting function. Valid kwargs are

if ax is not None:
old_axes = axes_handler(ax) # Set current axis to ax and return the previous axis to old_axes.
else:
ax = gca()
old_axes = ax
# Get the relevant axis
if ax is None: ax = gca()
old_ax = axes_handler(ax)
gridpar = grid_handler(grid, ax)
xlim = lims_handler(xlim, ax)
ylim = lims_handler(ylim, ax)
if not isinstance(x, list) or len(shape(x)) == 1:

@@ -1256,5 +1327,3 @@ x = [x]

# Combine the `explicit` plot_kw dictionary with the `implicit` **kwargs dictionary
# plot_par={**plot_kw, **kwargs} # For Python > 3.5
plot_par = plot_kw.copy()
plot_par.update(kwargs)
plot_par = {**plot_kw, **kwargs}

@@ -1266,11 +1335,11 @@ # Create 'L' number of plot kwarg dictionaries to parse into each plot call

for i in range(L):
lines += plt_plot(x[i], y[i], label=label[i], **plot_par[i])
lines += ax.plot(x[i], y[i], label=label[i], **plot_par[i])
if any(label):
legend(loc=lab_loc)
plot_finalizer(xlog, ylog, xlim, ylim, title, xlabel, ylabel, xinvert, yinvert, grid)
_plot_finalizer(xlog, ylog, xlim, ylim, title, xlabel, ylabel, xinvert, yinvert, gridpar, ax)
if old_axes is not ax:
old_axes = axes_handler(old_axes)
if old_ax is not ax:
sca(old_ax)
return (lines[0] if len(lines) == 1 else lines)

Sorry, the diff of this file is too big to display