"""
Scientific Python plotter
@author: Frank Rhein
"""
import matplotlib.pyplot as plt
print('Imported plotter module (18.09.2024)')
# ----------------------------------
# Define Plot defaults
# ----------------------------------
# scl_a4 define scaling of given figuresize
# 2: half page figure. Generate additional margin for legend.
# 1: full page figure. Generate additional margin for legend.
# page_lnewdth_cm: Linewidth of document in cm
# scl: Additional font scaling
# fnt: Font type used in plot
# figsze: Figure size in inches (defines aspect ratio)
# frac_lnewdth: Additional scaling option to width = frac_lnewith*document_linewidth
# mrksze: Markersize
# lnewdth: Linewidth (of lines used in plot)
# use_locale: If True use local number format
[docs]def plot_init(scl_a4=1,
page_lnewdth_cm=16.5,
scl=1,
fnt='sans-serif', # This is a custom font ('serif')
figsze=[6.4,4.8],
frac_lnewdth=0.6,
mrksze=6,
lnewdth=1.5,
use_locale=False,
fontsize = 10,
labelfontsize=9,
tickfontsize=8):
# --- Initialize defaults ---
plt.rcdefaults()
# --- Scale figure ---
# 2: Half page figure
if scl_a4==2:
fac=scl*page_lnewdth_cm/(2.54*figsze[0]*2) #2.54: cm --> inch
figsze=[figsze[0]*fac,figsze[1]*fac]
# 2: Full page figure
elif scl_a4==1:
fac=scl*page_lnewdth_cm/(2.54*figsze[0]) #2.54: cm --> inch
figsze=[figsze[0]*fac,figsze[1]*fac]
# 3: Scaling for presentations (OLD)
elif scl_a4==3:
fac=scl*page_lnewdth_cm/(2.54*figsze[0]) #2.54: cm --> inch
figsze=[figsze[0]*fac,figsze[1]*fac]
scl=1.6
# 4: Scaling for variable fraction of linewidth frac_lnewdth
elif scl_a4==4:
fac=scl*frac_lnewdth*page_lnewdth_cm/(2.54*figsze[0])
figsze=[figsze[0]*fac,figsze[1]*fac]
# --- Adjust legend ---
plt.rc('legend',fontsize=fontsize*scl,fancybox=True, shadow=False,edgecolor='k',
handletextpad=0.2,handlelength=1,borderpad=0.2,
labelspacing=0.2,columnspacing=0.2)
# --- General plot setup ---
plt.rc('mathtext', fontset='cm')
plt.rc('font', family=fnt)
plt.rc('xtick', labelsize=tickfontsize*scl)
plt.rc('ytick', labelsize=tickfontsize*scl)
plt.rc('axes', labelsize=labelfontsize*scl, linewidth=0.5*scl, titlesize=labelfontsize*scl)
plt.rc('legend', fontsize=fontsize*scl)
plt.rc('axes', axisbelow=True) # Grid lines in Background
plt.rcParams['lines.markersize']=mrksze
plt.rcParams['hatch.linewidth']=lnewdth/2
plt.rcParams['lines.linewidth']=lnewdth
plt.rcParams['figure.figsize']=figsze
if use_locale: plt.rc('axes.formatter',use_locale=True)
# ----------------------------------
# Standard plot function
# ----------------------------------
# x: x-Data
# y: y-Data
# err: (optional) Error data
# fig: (optional) Plot in given fig. Create new if None
# ax: (optional) Plot in given ax. Create new in None
# plt_type: Type of Plot
# Default: Points with lines
# 'scatter': Scatter without lines
# 'bar': Bar plot
# 'line': Line plot
# lbl: (optional) Label of given Dataset (legend entry)
# xlbl: (optional) Label of x-axis
# ylbl: (optional) Label of y-axis
# mrk: (optional) Marker type, defaul 'o'
# lnstyle: (optional) Linestyle, default '-'
# clr: (optional) Color of plot, default 'k'
# tit: (optional) Title of plot
# grd: (optional) Set grid, default 'major. For no grid use None
# grd_ax: (optional) Define axis for grid, defaul 'both'
# leg: (optional) Bool. Define if legend is plotted, default True
# leg_points_only: (optional) Bool. If True only marker are plottet in legend
# barwidth: (optional) Width of bar plot
# hatch: (optional) Hatch of bar plot
# alpha: (optional) Alpha of plot
# err_clr: (optional) Color of error bars, If None (default) use plot color
# zorder: (optional) Z-Order of plot. Higher values plotted above lower values
# mrkedgecolor: (optional) Edgecolor of marker
# mrkedgewidth: (optional) Width of marker edge
[docs]def plot_data(x,y,err=None,fig=None,ax=None,plt_type=None,lbl=None,xlbl=None,ylbl=None,
mrk='o',lnstyle='-',clr='k',tit=None,grd='major',grd_ax='both',leg=True,
leg_points_only=False,barwidth=0.5,hatch=None,alpha=1,err_clr=None,zorder=None,
mrkedgecolor=None,mrkedgewidth=0.5, err_ax='y'):
# --- If fig is not given by user: create new figure ---
if fig == None:
fig=plt.figure()
# --- If ax is not given create new axis on figure (only reasonable if fig==None also) ---
if fig == None or ax == None:
ax=fig.add_subplot(1,1,1)
# --- Plot data according to plt_type ---
# --- Scatter ---
if plt_type == 'scatter':
if mrkedgecolor == None:
ax.scatter(x,y,label=lbl,marker=mrk,color=clr,zorder=zorder,alpha=alpha)
else:
ax.scatter(x,y,label=lbl,marker=mrk,color=clr,zorder=zorder,edgecolor=mrkedgecolor,linewidths=mrkedgewidth,alpha=alpha)
# --- Bar ---
elif plt_type == 'bar':
ax.bar(x,y,width=barwidth,label=lbl,color=clr,edgecolor='k',alpha=alpha,hatch=hatch,zorder=zorder,linewidth=plt.rcParams['hatch.linewidth'])
# --- Line ---
elif plt_type == 'line':
# NOTE: If only line is plotted increase default linewidth by 50%
ax.plot(x,y,label=lbl,linestyle=lnstyle,color=clr,linewidth=1.5*plt.rcParams['lines.linewidth'],alpha=alpha,zorder=zorder)
# --- Default: Marker and Lines ---
else:
# --- Plot scatter first to show up in legend ---
if leg_points_only:
if mrkedgecolor == None:
ax.scatter(x,y,label=lbl,marker=mrk,color=clr,zorder=zorder,alpha=alpha)
ax.plot(x,y,marker=mrk,linestyle=lnstyle,color=clr,zorder=zorder,alpha=alpha)
else:
ax.scatter(x,y,label=lbl,marker=mrk,color=clr,zorder=zorder,edgecolor=mrkedgecolor,linewidths=mrkedgewidth,alpha=alpha)
ax.plot(x,y,marker=mrk,linestyle=lnstyle,color=clr,zorder=zorder,mec=mrkedgecolor,mew=mrkedgewidth,alpha=alpha)
else:
if mrkedgecolor == None:
ax.plot(x,y,label=lbl,marker=mrk,linestyle=lnstyle,color=clr,zorder=zorder,alpha=alpha)
else:
ax.plot(x,y,label=lbl,marker=mrk,linestyle=lnstyle,color=clr,zorder=zorder,mec=mrkedgecolor,mew=mrkedgewidth,alpha=alpha)
# --- Plot errorbars if error is given, if no err_color is given use plot color ---
if err_clr == None: err_clr=clr
if err is not None:
if err_ax =='y':
if plt_type == 'bar':
ax.errorbar(x,y,yerr=err,fmt='none',color=err_clr,capsize=plt.rcParams['lines.markersize']-2,alpha=0.5,zorder=99)
else:
ax.errorbar(x,y,yerr=err,fmt='none',color=err_clr,capsize=plt.rcParams['lines.markersize']-2,alpha=0.5,zorder=0)
else:
if plt_type == 'bar':
ax.errorbar(x,y,xerr=err,fmt='none',color=err_clr,capsize=plt.rcParams['lines.markersize']-2,alpha=0.5,zorder=99)
else:
ax.errorbar(x,y,xerr=err,fmt='none',color=err_clr,capsize=plt.rcParams['lines.markersize']-2,alpha=0.5,zorder=0)
# --- Set labels, title and grid if given ---
if xlbl != None: ax.set_xlabel(xlbl)
if ylbl != None: ax.set_ylabel(ylbl)
if tit != None: ax.set_title(tit)
if grd!=None: ax.grid(True,which=grd,axis=grd_ax,alpha=0.5)
if leg: ax.legend()
# --- return ax and fig ---
return ax, fig
# ----------------------------------
# Calculate and plot 1D histogram
# ----------------------------------
# x: x-Data
# w: (optional) weights
# bins: (optional) int with number of bins
# scale: (optional) str with scaling ['lin'/'log']
# fig: (optional) Plot in given fig. Create new if None
# ax: (optional) Plot in given ax. Create new in None
# xlbl: (optional) Label of x-axis
# ylbl: (optional) Label of y-axis
# clr: (optional) Color of plot
# tit: (optional) Title of plot
# grd: (optional) Set grid
# norm: (optional) If True normalize histogram (on np.sum(H))
# only_calc: (optional) Only calculate bins and data without plotting
[docs]def plot_1d_hist(x,w=None,bins=10,scale='lin',fig=None,ax=None,xlbl=None,
ylbl=None,clr='k',tit=None,grd=True,norm=True,
only_calc=False, alpha=0.7):
import numpy as np
# --- Calculate histogram data
H, xe = np.histogram(x, bins=bins, weights=w)
if scale == 'log':
logbins_x = np.logspace(np.log10(xe[0]),np.log10(xe[-1]),len(xe))
H, xe = np.histogram(x, bins=logbins_x, weights=w)
if norm: H = H/np.sum(H)
# --- Plot Histogram ---
if only_calc:
fig, ax = None, None
else:
x_mean = np.array([(xe[i]+xe[i-1])/2 for i in range(1,len(xe))])
dx = np.array([xe[i]-xe[i-1] for i in range(1,len(xe))])
ax, fig = plot_data(x_mean, H, plt_type='bar', barwidth=dx,leg=False,
alpha=alpha, clr=clr, ax=ax, fig=fig)
# --- Set labels, title and grid if given ---
if scale == 'log': ax.set_xscale('log')
if xlbl != None: ax.set_xlabel(xlbl)
if ylbl != None: ax.set_ylabel(ylbl)
if tit != None: ax.set_title(tit)
if grd != None: ax.grid(True)
# --- return ax and fig ---
return ax, fig, H, xe
# ----------------------------------
# Calculate and plot 2D histogram
# ----------------------------------
# x: x-Data
# y: y-Data
# w: (optional) weights
# bins: (optional) Tuple with bins in (x,y) direction
# scale: (optional) Tubple with scale of (x,y) data ['lin'/'log']
# fig: (optional) Plot in given fig. Create new if None
# ax: (optional) Plot in given ax. Create new in None
# xlbl: (optional) Label of x-axis
# ylbl: (optional) Label of y-axis
# clr: (optional) Colormap of plot
# tit: (optional) Title of plot
# grd: (optional) Set grid
# norm: (optional) If True normalize histogram (on np.sum(H))
# colorbar: (optional) If True plot colorbar
# cblbl: (optional) Label for Colorbar
# only_calc: (optional) Only calculate bins and data without plotting
[docs]def plot_2d_hist(x,y,w=None,bins=(10,10),scale=('lin','lin'),fig=None,ax=None,xlbl=None,
ylbl=None, clr='viridis',tit=None,grd=True, norm=True, colorbar=True, cblbl='',
only_calc=False, scale_hist='lin', hist_thr=1e-6):
import numpy as np
from mpl_toolkits.axes_grid1 import make_axes_locatable
import matplotlib.colors as colors
# --- Calculate histogram data
H, xe, ye = np.histogram2d(x, y, bins=bins, weights=w)
if scale[0] == 'log':
logbins_x = np.logspace(np.log10(xe[0]),np.log10(xe[-1]),len(xe))
if scale[1] == 'log':
logbins_y = np.logspace(np.log10(ye[0]),np.log10(ye[-1]),len(ye))
H, xe, ye = np.histogram2d(x, y, bins=(logbins_x, logbins_y), weights=w)
else:
H, xe, ye = np.histogram2d(x, y, bins=(logbins_x, ye), weights=w)
# np.histogram requires transposition (see docs)
H = H.T
if norm: H = H/np.sum(H)
# --- Plot Histogram ---
if only_calc:
fig, ax, cb = None, None, None
else:
X, Y = np.meshgrid(xe, ye)
if grd:
ecl = 'k'
else:
ecl = None
# --- If fig is not given by user: create new figure ---
if fig == None:
fig=plt.figure()
# --- If ax is not given create new axis on figure (only reasonable if fig==None also) ---
if fig == None or ax == None:
ax=fig.add_subplot(1,1,1)
if scale_hist == 'log':
# H == 0 is not allowed. set to hist_thr*H.max()
H[H==0] = hist_thr*H.max()
cp = ax.pcolormesh(X, Y, H, cmap=clr, edgecolor=ecl, antialiased=True, linewidth=0.1,
norm=colors.LogNorm(vmin=H.min(), vmax=H.max()))
else:
cp = ax.pcolormesh(X, Y, H, cmap=clr, edgecolor=ecl, antialiased=True, linewidth=0.1)
if colorbar:
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.1)
cb = plt.colorbar(cp,cax)
cb.set_label(cblbl)
else:
cb = None
# --- Set labels, title and grid if given ---
if scale[0] == 'log': ax.set_xscale('log')
if scale[1] == 'log': ax.set_yscale('log')
if xlbl != None: ax.set_xlabel(xlbl)
if ylbl != None: ax.set_ylabel(ylbl)
if tit != None: ax.set_title(tit)
# --- return ax and fig ---
return ax, fig, cb, H, xe, ye
# ----------------------------------
# Export current plot
# ----------------------------------
# filename: Path for export. File extension determines format! (.pdf / .png)
# squeeze: Bool. Define if tight layout is used or not, default True
# dpi: DPI value for export. Only relevant for picture formats like .png / .jpg
[docs]def plot_export(filename,squeeze=True,dpi=1000,pad_inch=False):
if squeeze:
bb='tight'
else:
bb=None
if pad_inch:
plt.savefig(filename,dpi=dpi,bbox_inches=bb,pad_inches = 0)
else:
plt.savefig(filename,dpi=dpi,bbox_inches=bb)
# ----------------------------------
# Close all plots (no separate import of matplotlib required)
# ----------------------------------
[docs]def close():
plt.close('all')