"""
Part of comet/pyhed/plot
"""
# COMET - COupled Magnetic resonance Electrical resistivity Tomography
# Copyright (C) 2019 Nico Skibbe
# 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 pygimli as pg
# from comet.pyhed.misc import vec
from comet.pyhed.misc import plt_ioff
from comet.pyhed import log
import inspect
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from pathlib import Path
[docs]def cmap_phase():
from matplotlib import colors
white = '#ffffff'
black = '#444444'
red = '#ff0000'
blue = '#0000ff'
# anglemap = colors.ListedColormap(
# [black, blue, white, red, black], N=721)
anglemap = colors.LinearSegmentedColormap.from_list(
'anglemap',
[black, blue, white, red, black], N=721)
return anglemap
[docs]def quantile(data, perc=0.99, add_rel=0.1, add_abs=0.0):
""" Returns the the data point that lies over a given percentage (50 %) of
a data set and returns value (+ 10%).
Parameters
----------
data:
Dataset for which the value is to be searched.
perc: float [ 0.5 ]
Percentage [0...1] defining he search for a parameter.
Searches for the value in the dataset that lies above *perc* of the
other data.
add_rel: float [ 0.1 ]
Relative value added to the result of the search.
add_abs: float [ 0.0 ]
Absolute value added to the result of the search.
For very small values in data, add_rel should be replaced by add_abs (and
an appropriate value) or set 0.
For add_rel = 0.0 and add_abs = 0.0, the returned value will always be part
of the given dataset.
Hint for colorbars: usually a median(data, perc=...) with perc = 0.95 for
small data sets and perc = 0.99 for large data sets will result in nice
colorbar settings as it effectively removes spikes.
"""
quant = np.quantile(data, perc)
quant = quant * (1.0 + add_rel) + add_abs
return quant
[docs]def amp(field):
""" Amplitude of a complex field.
Internally used.
"""
log.warning('Deprication Warning. Function "amp" should not be used '
'anymore. I have to remove this.')
XX = field[0].real**2 + field[0].imag**2
YY = field[1].real**2 + field[1].imag**2
ZZ = field[2].real**2 + field[2].imag**2
return np.sqrt(XX + YY + ZZ)
[docs]def printv(string, *args):
"""
print function for maintenance and debugging
"""
callerframerecord = inspect.stack()[1]
# print(callerframerecord)
frame = callerframerecord[0] # frame/namespace des callers
caller_globals = frame.f_globals # globals des Callers
debugging_value = caller_globals['debugging']['debugging']
# print('printv: %s' % (debugging_value))
if debugging_value is True:
# print(inspect.stack()[:][1])
# if inspect.stack()[0][1] != inspect.stack()[1][1]:
# origin = '\.: '
# else:
origin = callerframerecord[1].split('\\')[-1] + ': '
info = inspect.getframeinfo(frame)
# mit info.lineno bekommt man die zeilennummer,
# in der die funktion ausgeführt wird
if not args:
print(origin + str(info.lineno) + ' ', string)
else:
if type(args[0]) != np.float64:
try:
print(origin + str(info.lineno), string + '\t',
args[0].shape)
except AttributeError:
print(origin + str(info.lineno), string + '\t',
str(args[0]) + '\t', str(type(args[0])))
else:
print(origin + str(info.lineno), string + '\t',
str(args[0]) + '\tnp.float')
else:
return
[docs]def pickleFig(savename, fig):
import pickle
with open(savename, 'wb') as fileout:
pickle.dump(fig, fileout)
[docs]def loadPickledFig(savename):
import pickle
with open(savename, 'rb') as filein:
fig = pickle.load(filein)
return fig
[docs]def setAxSize(ax, size):
for item in ([ax.title, ax.xaxis.label,
ax.yaxis.label] + ax.get_xticklabels() +
ax.get_yticklabels()):
item.set_fontsize(size)
[docs]def addPatch(ax, cbar_ax=None, offset_left=28.5, offset_top=1, distance=1,
color='lightgray', lw=0, z_order=0):
from pylab import Rectangle
axis = ax.axis()
if cbar_ax is not None:
x1, y1, width, height = cbar_ax.bbox.bounds
# offset_left = width
# Rectangle input:
# (x, y), width, height, **kwargs
rec = Rectangle((axis[0] - offset_left, axis[2] + distance),
(axis[1] - axis[0]) + offset_left + distance + 0.5,
(axis[3] - axis[2]) - 2 * distance - offset_top,
fill=True,
color=color,
lw=lw)
rec = ax.add_patch(rec)
rec.set_zorder(z_order)
rec.set_clip_on(False)
return rec
[docs]def showLoop(pos, phi, ds, referenzpunkte=None, ax=None, color=None, **kwargs):
""" Plots a loop as set of dipoles on a given axis.
Used by *show* method of loop class.
"""
if ax is None:
fig, ax = plt.subplots(1, 1)
for i in range(len(phi)):
if color is not None:
ax.scatter(pos[i][0], pos[i][1], color=color)
else:
ax.scatter(pos[i][0], pos[i][1])
ax.arrow(pos[i][0],
pos[i][1],
np.cos(phi[i]) * ds[i]/2.5,
np.sin(phi[i]) * ds[i]/2.5,
head_width=ds[i]/25., head_length=ds[i]/25., fc='k', ec='k')
if referenzpunkte is not None:
ax.scatter(referenzpunkte[i][0],
referenzpunkte[i][1], color='red')
bbox = np.max(np.abs(pos))*0.1
xlim = kwargs.pop('xlim', None)
if xlim is None:
minx = np.min(pos[:, 0]) - bbox
maxx = np.max(pos[:, 0]) + bbox
if np.isclose(minx, maxx):
minx -= 1
maxx += 1
xlim = [minx, maxx]
ax.set_xlim(xlim)
ylim = kwargs.pop('ylim', None)
if ylim is None:
miny = np.min(pos[:, 1]) - bbox
maxy = np.max(pos[:, 1]) + bbox
if np.isclose(miny, maxy):
miny -= 1
maxy += 1
ylim = [miny, maxy]
ax.set_ylim(ylim)
ax.grid(True)
ax.set_aspect('equal')
# plt.show()
pg.checkAndFixLocaleDecimal_point()
return ax
[docs]def showLoopLayout(*loops, ax=None, **kwargs):
""" Shows multiple loops at once on a given axis.
"""
fig, ax = returnFigureAndAx(ax, 1, 1)
if isinstance(loops[0], (list, tuple)):
loops = loops[0]
color = iter(plt.cm.rainbow(np.linspace(0, 1, len(loops))))
num_loops = len(loops)
label = kwargs.pop('label', 'loop {}')
if isinstance(label, str):
if '{}' not in label:
label_master = label + ' {}'
else:
label_master = label + ''
label = []
for i in range(num_loops):
label.append(label_master.format(i))
elif isinstance(label, (tuple, list, np.ndarray)):
if len(label) != num_loops:
raise Exception(
'Error in showLoopLayout: either give one label per loop or a'
' single string containing {} or not (it will be appended' +
' otherwise). Got {} for {} loops.'.format(label, num_loops))
min_x = []
max_x = []
min_y = []
max_y = []
patches = []
for idx, loop in enumerate(loops):
c = next(color)
ax = loop.show(ax=ax, color=c, **kwargs)
cur_x_lim = ax.get_xlim()
cur_y_lim = ax.get_ylim()
min_x.append(cur_x_lim[0])
max_x.append(cur_x_lim[1])
min_y.append(cur_y_lim[0])
max_y.append(cur_y_lim[1])
patches.append(
mpatches.Patch(color=c, label=label[idx]))
ax.set_xlim(np.min(min_x), np.max(max_x))
ax.set_ylim(np.min(min_y), np.max(max_y))
plt.legend(handles=patches, bbox_to_anchor=(0., 1.02, 1., .102), loc=3,
ncol=int(len(loops)/2), mode="expand", borderaxespad=0.)
return ax
[docs]def drawMeshLines(ax, mesh, color='white', linewidth=0.5, marker=None,
**kwargs):
""" Draw all mesh boundaries in given ax.
"""
if marker is not None:
marker = np.atleast_1d(marker)
for bound in mesh.boundaries():
if marker is None or bound.marker() in marker:
a = np.row_stack([bound.nodes()[0].pos().array(),
bound.nodes()[1].pos().array()])
ax.plot(a[:, 0], a[:, 1], color=color, linewidth=linewidth,
**kwargs)
[docs]def getCMapAndLim(toplot, phase=False, misfit=False, perc=0.99,
minimum=False, lut=None):
""" Chooses colorbar limits and appropriate colobar based on input.
lut: If lut is not None it must be an integer giving the number of entries
desired in the lookup table, and name must be a standard mpl colormap name.
"""
toplot = np.array(toplot)
if phase:
cmap = cmap_phase()
clim = [-np.pi, np.pi]
elif misfit:
cmap = plt.get_cmap('bwr', lut=lut)
clim = [-4, 4]
else:
absmax = np.quantile(toplot, perc)
absmin = np.quantile(-toplot, perc)
bothabs = np.max([np.abs(absmax), np.abs(absmin)])
if np.any(toplot < 0):
cmap = plt.get_cmap('bwr', lut=lut)
absmin = -bothabs
absmax = bothabs
else:
cmap = plt.get_cmap('bwr', lut=lut)
if not minimum:
absmin = 0
clim = [absmin, absmax]
log.debug('getCMapAndLim(): min/max data: {}/{}, clim: {}/{}'
.format(np.min(toplot), np.max(toplot), *clim))
return cmap, clim
[docs]def drawFid(ax, fid, clim=None, clab=None, draw='data',
to_plot='real', cmap=None, cbar=True, gated=True, title=None):
"""Plot any data (or response, error, misfit) cube nicely.
if response is True:
response vector from fid taken and used for plotting misfit.
"""
if to_plot.lower() not in ('abs', 'real', 'imag', 'phase', 'rot'):
raise Exception('to_plot is limited to "real", "imag", "abs" or "rot" '
'"phase" got {}'.format(to_plot))
to_plot = to_plot.lower()
allowed = ('data', 'response', 'misfit', 'error')
if draw.lower() not in allowed:
raise Exception('draw is limited to {}, '
' got {}'.format(allowed, draw))
draw = draw.lower()
if gated:
data = fid.getComplexData()
error = fid.error_gated
timevec = fid.gates
else:
data = fid.data_raw
error = fid.error_raw
timevec = fid.times
if data is None:
raise Exception(f'No data to plot in fid: {fid!r}, gated={gated}')
if draw in ('response', 'misfit'):
response = fid.response
if response is None:
raise Exception('No response vector found in fid.')
gated = True
if to_plot in ['real', 'rot']:
func = np.real
elif to_plot == 'imag':
func = np.imag
elif to_plot == 'abs':
func = np.abs
elif to_plot == 'phase':
func = np.angle
if draw == 'misfit':
if np.iscomplexobj(error):
error = func(error)
if to_plot != 'rot':
toplot = (func(data) - func(response)) / error
else:
toplot = (func(data) - np.abs(response)) / error
# MMP: not good for rotated Amplitudes
# fixed: to_plot == 'rot'
elif draw == 'data':
if to_plot == 'phase':
toplot = func(data)
else:
toplot = func(data) * 1e9 # V -> nV
elif draw == 'error':
if to_plot == 'phase':
toplot = func(error)
else:
toplot = func(error) * 1e9 # V -> nV
elif draw == 'response':
if to_plot == 'phase':
toplot = func(response)
else:
toplot = func(response) * 1e9 # V -> nV
if cmap is None or clim is None:
cmap_auto, clim_auto = getCMapAndLim(
toplot, phase=to_plot == 'phase',
misfit=draw == 'misfit')
if cmap is None:
cmap = cmap_auto
if clim is None:
clim = clim_auto
if cmap == 'bwr':
# middle = white = 0
cmax = np.max(np.abs(clim))
clim = [-cmax, cmax]
xt = np.linspace(0, len(timevec) - 1, 6)
xtl = []
for ixt in xt:
xtl.append('{:d}'.format(int(np.round(timevec[int(ixt)] * 1000.))))
# qt = range(0, len(fid.pulses), 5) # including last value
qt = np.linspace(0, len(fid.pulses) - 1, 5, dtype=int)
qtl = [str(qi) for qi in np.round(fid.pulses[qt] * 10.) / 10.]
# plot
mat = toplot.reshape((len(fid.pulses),
len(timevec)))
im = ax.imshow(mat, interpolation='nearest', aspect='auto',
cmap=cmap)
im.set_clim(clim)
ax.set_xticks(xt)
ax.set_xticklabels(xtl)
ax.set_yticks(qt)
ax.set_yticklabels(qtl)
ax.set_xlabel('time (ms)')
ax.set_ylabel('pulse moment (As)')
if title is not None:
ax.set_title(title)
if cbar:
cb = plt.colorbar(im, ax=ax, orientation='horizontal')
if clab is not None:
cb.ax.set_title(clab)
return im, cb
# if draw == 'misfit':
# figh, axh = plt.subplots()
# axh.hist(toplot.flatten(), bins=33, density=True)
# axh.set_xlim((-5, 5))
# figh.savefig('histogram.pdf', bbox_inches='tight')
# plt.close(figh)
return im
[docs]def showEtraData(survey, to_plot='real', draw='data', savename='auto',
rdir='.', praefix='', size=12, patch=True, clim=None,
perc=0.995, cmap='auto', pdf=True, png=False):
""" Plot function to create and save data and misfit plots of etra data.
Parameters
----------
datas: array_like
Array containing the measured data in nV. Expect one dimensional array
of concatenated datas. First dimension defines the different recievers.
If array is real, expect first half to contain the real component and
second half to contain the imaginary data.
gates: array_like
Midpoints of the used time gates for plotting in s.
pulses: array_like
Used pulse moments for plotting in As.
errors: array_like [ None ]
Assumed errors of the datas for plotting of misfit. Same shape as data.
draw: string [ 'data' ]
This function can plot 'data', 'response' or 'misfit'. The last two
only if fid are eqipped with proper response vector. See setResponse()
of Survey class or setResponse() of Fid class for information about
setting response vectors.
to_plot: string [ 'real' ]
Decides weather real or imaginary part of the data is plotted.
Alternatively 'abs' can be used to plot absolute values.
savename: string [ 'auto' ]
If on auto, the savename is generated out of the other given
parameters. If other than 'auto', the given savename is used to save
the resulting figures. If on 'auto', see *rdir* and *praefix* for
additional information.
rdir: string ['.']
If *savename*=='auto', rdir defines the directory the results are saved
in. This is ignored if savename is not 'auto'.
praefix: string [ '' ]
If *savename*=='auto', praefix can be used to distinguish different
data sets in the same *rdir*. This is ignored if savename is not
'auto'.
size: integer [ 17 ]
Fontsize for the exported figure ticks and labels.
patch: boolean [ True ]
As the coincident measurement and the other seven get different
colorbars (see *clim*), a grey patch is optionally used as background
for the first data plot. This switch can be used to omit this patch.
clim: list or list of lists [ None ]
The colorbar limits of the plots can be fixed. Except a list of min and
maximum value for misfit and two of those lists for the data plot,
whereas the first min and max is used fot the coincident measurement,
and the second for the other seven measurements.
perc: float [ 0.999 ]
Percentage to autodefine the colorbar values. The defaults sets the
maximum value to the value that is greater than 99.9 % of the data.
"""
if isinstance(rdir, str):
rdir = Path(rdir)
to_plot = to_plot.lower()
if to_plot not in ('abs', 'real', 'imag', 'phase'):
raise Exception('to_plot is limited to "real", "imag", or "abs", '
'"phase" got {}'.format(to_plot))
draw = draw.lower()
if draw not in ('data', 'response', 'misfit'):
raise Exception('draw is limited to "data", "response", or "misfit", '
' got {}'.format(draw))
if draw.lower() == 'misfit' or to_plot.lower() == 'phase':
patch = False
log.info(f'plot etra {draw}, {to_plot}')
with plt_ioff():
fig, ax_all = plt.subplots(2, 4, figsize=(16, 9))
fig.subplots_adjust(
left=0.12, bottom=None, right=None, top=None, wspace=0.1,
hspace=0.125)
# limits first (coincident measurement)
if to_plot == 'real':
func = np.real
elif to_plot == 'imag':
func = np.imag
elif to_plot == 'abs':
func = np.abs
elif to_plot == 'phase':
func = np.angle
# determine style and limits for colorbars
if draw == 'data':
hlp1 = np.ones(0, dtype=complex)
for dat in survey.data:
hlp1 = np.append(hlp1, dat.flatten())
hlp1 = func(hlp1 * 1e9)
hlp2 = np.ones(0, dtype=complex)
for dat in survey.data[1:]:
hlp2 = np.append(hlp2, dat.flatten())
hlp2 = func(hlp2 * 1e9)
elif draw == 'response':
hlp1 = np.ones(0, dtype=complex)
for dat in survey.response:
hlp1 = np.append(hlp1, dat.flatten())
hlp1 = func(hlp1 * 1e9)
hlp2 = np.ones(0, dtype=complex)
for dat in survey.response[1:]:
hlp2 = np.append(hlp2, dat.flatten())
hlp2 = func(hlp2 * 1e9)
elif draw == 'misfit':
hlp1 = None
hlp2 = None
# misfit has fixed colorbar and range, no data needed for eval
if to_plot == 'phase':
unit = ' (rad)'
elif draw == 'misfit':
unit = ''
else:
unit = ' (nV)'
cbar_label = f'{draw}, {to_plot}{unit}'
cmap_auto, clim1 = getCMapAndLim(hlp1,
misfit=draw == 'misfit',
phase=to_plot == 'phase',
perc=perc)
cmap_auto, clim2 = getCMapAndLim(hlp2,
misfit=draw == 'misfit',
phase=to_plot == 'phase',
perc=perc)
if cmap == 'auto':
cmap = cmap_auto
onecbar = False
if clim is not None:
# force colorbar manually -> one colorbar to rule them all
if isinstance(clim[0], (list, tuple, np.ndarray)):
clim1 = clim[0]
clim2 = clim[1]
onecbar = False
else:
clim1 = clim
clim2 = clim
onecbar = True
patch = False
bb0 = ax_all[0, 0].figbox.bounds
bb1 = ax_all[1, 0].figbox.bounds
if draw == 'misfit' or to_plot == 'phase' or onecbar:
cba0 = fig.add_axes([0.05, bb1[1], 0.02, bb0[1] - bb1[1] + bb1[3]])
cba1 = None
else:
cba0 = fig.add_axes([0.05, bb0[1], 0.02, bb0[3]])
cba1 = fig.add_axes([0.05, bb1[1], 0.02, bb0[3]])
for i, ax in enumerate(ax_all.flatten()):
im = drawFid(ax, survey.fids[i], draw=draw,
cmap=cmap,
clim=clim1 if i == 0 else clim2,
to_plot=to_plot,
cbar=False)
if i == 0:
ax.set_title('Tx/Rx (coincident)')
im0 = im
else:
ax.set_title('Rx {}'.format(i))
if draw in ('data', 'response') and i == 1:
im1 = im
if i not in (0, 4):
ax.set_ylabel('')
ax.set_yticks([])
if i < 4:
ax.set_xlabel('')
ax.set_xticks([])
setAxSize(ax, size)
cb0 = plt.colorbar(im0, cax=cba0, orientation='vertical')
cb0.set_label(cbar_label, rotation=90, size=size)
cba0.yaxis.set_ticks_position('left')
cba0.yaxis.set_label_position('left')
cba0.tick_params(labelsize=size)
if cba1 is not None:
cb1 = plt.colorbar(im1, cax=cba1, orientation='vertical')
cb1.set_label(cbar_label, rotation=90, size=size)
cba1.yaxis.set_ticks_position('left')
cba1.yaxis.set_label_position('left')
cba1.tick_params(labelsize=size)
if patch:
addPatch(ax_all[0, 0], cbar_ax=cba0)
if savename == 'auto':
name = rdir.joinpath(f'{praefix}{draw}_{to_plot}')
else:
name = Path(savename)
if png:
log.info('saving png: {}'.format(name.as_posix()))
fig.savefig(
name.with_suffix('.png').as_posix(), bbox_inches='tight',
facecolor='none', edgecolor='none')
if pdf:
log.info('saving pdf: {}'.format(name.with_suffix('.pdf')
.as_posix()))
fig.savefig(
name.with_suffix('.pdf').as_posix(), bbox_inches='tight',
facecolor='none', edgecolor='none')
plt.close(fig)
[docs]def drawCWeight(ax, mesh, cweight, lmin=0, lmax=0.8, cmin=0.2, cmax=1,
min_plot=0.02, color='black', cell_indices=None):
""" Draws the given cweights defined for given mesh on given ax.
Parameters
----------
ax: plt.ax
Ax to plot constraint weights in.
mesh: pg.Mesh
Mesh object where the constraints are defined in.
cweight: np.ndarray
Constraint values to be plotted.
lmin: float [ 0 ]
Minimum linewidth for maximum cweight defined via **cmax**.
Note that by default high constraint values are plotted with thinner
lines.
lmin: float [ 0.8 ]
Maximum linewidth for minimum cweight defined via **cmin**.
cmin: float [ 0 ]
Minimum constraint weight to plot. All values smaller than cmin are
plotted with the same linewidth as cmin.
cmax: float [ 1 ]
Maximum constraint weight to plot. All values greater than cmax are
plotted with the same linewidth as cmax.
min_plot: float [ 0.02 ]
Minimum linewidth to plot to avoid large pdfs.
color: string [ 'black' ]
Color of lines.
cell_indices [ None ]
f(cweight) -> linewidth:
(cmin, cmax) -> (lmax, lmin) if cmin < cweight < cmax
Returns:
--------
None
"""
from matplotlib.collections import LineCollection
mesh.createNeighbourInfos()
lines = []
linewidths = []
bi = 0
if cell_indices is None:
# case 1/2: no node indices means we try plotting in implicit order of
# boundaries in mesh
boundary_ids = np.arange(len(cweight))
bi = 0
for bound in mesh.boundaries():
if bound.rightCell() is not None:
pos0 = np.array(bound.node(0).pos())[:2].tolist()
pos1 = np.array(bound.node(1).pos())[:2].tolist()
linew = (1 - (cweight[bi] - cmin) / (cmax - cmin)) *\
(lmax - lmin) + lmin
linew = np.max([np.min([linew, lmax]), lmin])
if linew >= min_plot:
lines.append([pos0, pos1])
linewidths.append(linew)
bi += 1
else:
# case 2/2: node indices are given, which means we plot each entry of
# cweight between the nodes with the ids given in node_indices.
assert len(cweight) == len(cell_indices[0])
assert len(cweight) == len(cell_indices[1])
cells = mesh.cells()
boundary_ids = []
for ci in range(len(cweight)):
cell0 = cells[cell_indices[0][ci]]
cell1 = cells[cell_indices[1][ci]]
try:
boundary_ids.append(
np.intersect1d([cell0.boundary(0).id(),
cell0.boundary(1).id(),
cell0.boundary(2).id()],
[cell1.boundary(0).id(),
cell1.boundary(1).id(),
cell1.boundary(2).id()]
)[0])
except IndexError as ie:
log.error([[cell0.boundary(0).id(),
cell0.boundary(1).id(),
cell0.boundary(2).id()],
[cell1.boundary(0).id(),
cell1.boundary(1).id(),
cell1.boundary(2).id()]])
raise ie
boundaries = mesh.boundaries()
for ci, bi in enumerate(boundary_ids):
bound = boundaries[bi]
if bound.rightCell() is not None:
pos0 = np.array(bound.node(0).pos())[:2].tolist()
pos1 = np.array(bound.node(1).pos())[:2].tolist()
linew = (1 - (cweight[ci] - cmin) / (cmax - cmin)) *\
(lmax - lmin) + lmin
linew = np.max([np.min([linew, lmax]), lmin])
if linew >= min_plot:
lines.append([pos0, pos1])
linewidths.append(linew)
ax.add_collection(
LineCollection(lines, linewidths=linewidths,
color=color))
ax.set_xlim([mesh.xmin(), mesh.xmax()])
ax.set_ylim([mesh.ymin(), mesh.ymax()])
[docs]def markCbar(cbar, pos, text=None, color='white', linewidth=0.5, size=None,
text_y_pos=1.35, cbar_horizontal=True, **kwargs):
""" Marks a given colorbar of a plot at a specific position and optionally
displaysa describing text. Useful to remind on a synthetic background or
focus the view on a specific range.
Parameters
----------
cbar : matplotlib colorbar
Colorbar to mark.
pos : float
Marker position in values of the colorbar (data values).
text : string, optional
Text to display. The default is None.
color : string, optional
Color used for the marker. The string is redirected to matplotlib.
The default is 'white'.
linewidth : float, optional
Thickness of the marker line. The default is 0.5.
size : integer, optional
Size of the Text. If None the size of the ticklabel of the cbar
axis is used. If no label is found the size is set to 9.
The default is None.
text_y_pos : flat, optional
Vertical Offset for the displayed text. (or horizontal offset for
vertical colorbars, see next argument). The default is 1.35.
cbar_horizontal : boolean, optional
Flag for a horizontal colorbar. The default is True.
**kwargs : dictionary
Redirected to the text function. Filled with default values for
'horizontalalignment' ('center') and 'verticalalignment' ('center').
Returns
-------
None.
"""
# get labelsize from cbar
if size is None:
if cbar_horizontal:
cl = cbar.ax.xaxis.get_ticklabels()
else:
cl = cbar.ax.yaxis.get_ticklabels()
if len(cl) > 0:
size = cl[0].get_size()
else:
size = 9 # default size, no label found
# transform data -> [0, 1]
inv = cbar.ax.transData
# transform [0, 1] -> cbar trans axes (plot object)
trans = cbar.ax.transAxes.inverted()
kwargs.setdefault('horizontalalignment', 'center')
kwargs.setdefault('verticalalignment', 'center')
clim = cbar.mappable.get_clim()
# horizontal line
cbar.ax.plot([pos, pos], [clim[0], clim[1]], '-', color=color,
linewidth=linewidth)
if text is not None:
if cbar_horizontal:
x_trans = trans.transform(inv.transform((pos, pos)))[0]
y_trans = text_y_pos
else:
y_trans = trans.transform(inv.transform((pos, pos)))[0]
x_trans = text_y_pos
cbar.ax.text(x_trans, y_trans, text, transform=cbar.ax.transAxes,
size=size, **kwargs)
[docs]def setOuterLabelOnly(ax, xlabel='X (m)', ylabel='Z (m)'):
"""
Removes all ticks from the given axes and labels exept the outer left
and lower axes which are labeled using the the given labels.
This is a convenience function for multi ax plots, where the subplots have
the same outer dimension.
"""
if len(ax.shape) != 2:
log.waring(
'setOuterLabelOnly: Warning expected 2D ax object. '
'Add dimension, however this function does not know if given '
'object was a column or row. Fix this by giving a 2D Axes.')
ax = np.atleast_2d(ax)
for xi in range(ax.shape[0]):
for yi in range(ax.shape[1]):
if yi == 0:
ax[xi, yi].set_ylabel(ylabel)
else:
ax[xi, yi].yaxis.set_ticks([])
ax[xi, yi].set_ylabel('')
if xi == ax.shape[0] - 1:
ax[xi, yi].set_xlabel(xlabel)
else:
ax[xi, yi].xaxis.set_ticks([])
ax[xi, yi].set_xlabel('')
# The End