Skip to content

Commit a779b80

Browse files
mgeierfs446
authored andcommitted
Add plot2d.level_contour()
1 parent a684134 commit a779b80

File tree

1 file changed

+81
-39
lines changed

1 file changed

+81
-39
lines changed

sfs/plot2d.py

Lines changed: 81 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,12 @@ def secondary_sources(x0, n0, *, size=0.05, grid=None):
105105

106106

107107
def loudspeakers(x0, n0, a0=0.5, *, size=0.08, show_numbers=False, grid=None,
108-
ax=None):
108+
ax=None, zorder=2):
109109
"""Draw loudspeaker symbols at given locations and angles.
110110
111+
The default ``zorder`` is changed to 2, which is the same as line plots
112+
(e.g. `level_contour()`).
113+
111114
Parameters
112115
----------
113116
x0 : (N, 3) array_like
@@ -161,7 +164,7 @@ def loudspeakers(x0, n0, a0=0.5, *, size=0.08, show_numbers=False, grid=None,
161164

162165
# add collection of patches to current axis
163166
p = _mpl.collections.PatchCollection(
164-
patches, edgecolor='0', facecolor=_np.tile(1 - a0, 3))
167+
patches, edgecolor='0', facecolor=_np.tile(1 - a0, 3), zorder=zorder)
165168
if ax is None:
166169
ax = _plt.gca()
167170
ax.add_collection(p)
@@ -183,6 +186,48 @@ def _visible_secondarysources(x0, n0, grid):
183186
return x0[idx, :], n0[idx, :]
184187

185188

189+
def _plotting_plane(p, grid):
190+
if p.ndim == 3:
191+
if p.shape[2] == 1:
192+
p = p[:, :, 0] # first axis: y; second axis: x
193+
plotting_plane = 'xy'
194+
elif p.shape[1] == 1:
195+
p = p[:, 0, :].T # first axis: z; second axis: y
196+
plotting_plane = 'yz'
197+
elif p.shape[0] == 1:
198+
p = p[0, :, :].T # first axis: z; second axis: x
199+
plotting_plane = 'xz'
200+
else:
201+
raise ValueError("If p is 3D, one dimension must have length 1")
202+
elif len(grid) == 3:
203+
if grid[2].ndim == 0:
204+
plotting_plane = 'xy'
205+
elif grid[1].ndim == 0:
206+
plotting_plane = 'xz'
207+
elif grid[0].ndim == 0:
208+
plotting_plane = 'yz'
209+
else:
210+
raise ValueError(
211+
"If p is 2D and grid is 3D, one grid component must be scalar")
212+
else:
213+
# 2-dimensional case
214+
plotting_plane = 'xy'
215+
216+
if plotting_plane == 'xy':
217+
x, y = grid[[0, 1]]
218+
elif plotting_plane == 'xz':
219+
x, y = grid[[0, 2]]
220+
elif plotting_plane == 'yz':
221+
x, y = grid[[1, 2]]
222+
223+
dx = 0.5 * _np.ptp(x) / p.shape[0]
224+
dy = 0.5 * _np.ptp(y) / p.shape[1]
225+
226+
extent = x.min() - dx, x.max() + dx, y.min() - dy, y.max() + dy
227+
228+
return p, extent, plotting_plane
229+
230+
186231
def amplitude(p, grid, *, xnorm=None, cmap='coolwarm_clip',
187232
vmin=-2.0, vmax=2.0, xlabel=None, ylabel=None,
188233
colorbar=True, colorbar_kwargs={}, ax=None, **kwargs):
@@ -256,41 +301,7 @@ def amplitude(p, grid, *, xnorm=None, cmap='coolwarm_clip',
256301
if xnorm is not None:
257302
p = _util.normalize(p, grid, xnorm)
258303

259-
if p.ndim == 3:
260-
if p.shape[2] == 1:
261-
p = p[:, :, 0] # first axis: y; second axis: x
262-
plotting_plane = 'xy'
263-
elif p.shape[1] == 1:
264-
p = p[:, 0, :].T # first axis: z; second axis: y
265-
plotting_plane = 'yz'
266-
elif p.shape[0] == 1:
267-
p = p[0, :, :].T # first axis: z; second axis: x
268-
plotting_plane = 'xz'
269-
else:
270-
raise ValueError("If p is 3D, one dimension must have length 1")
271-
elif len(grid) == 3:
272-
if grid[2].ndim == 0:
273-
plotting_plane = 'xy'
274-
elif grid[1].ndim == 0:
275-
plotting_plane = 'xz'
276-
elif grid[0].ndim == 0:
277-
plotting_plane = 'yz'
278-
else:
279-
raise ValueError(
280-
"If p is 2D and grid is 3D, one grid component must be scalar")
281-
else:
282-
# 2-dimensional case
283-
plotting_plane = 'xy'
284-
285-
if plotting_plane == 'xy':
286-
x, y = grid[[0, 1]]
287-
elif plotting_plane == 'xz':
288-
x, y = grid[[0, 2]]
289-
elif plotting_plane == 'yz':
290-
x, y = grid[[1, 2]]
291-
292-
dx = 0.5 * _np.ptp(x) / p.shape[0]
293-
dy = 0.5 * _np.ptp(y) / p.shape[1]
304+
p, extent, plotting_plane = _plotting_plane(p, grid)
294305

295306
if ax is None:
296307
ax = _plt.gca()
@@ -300,8 +311,7 @@ def amplitude(p, grid, *, xnorm=None, cmap='coolwarm_clip',
300311
p = _np.clip(p, -1e15, 1e15) # clip to float64 range
301312

302313
im = ax.imshow(_np.real(p), cmap=cmap, origin='lower',
303-
extent=[x.min()-dx, x.max()+dx, y.min()-dy, y.max()+dy],
304-
vmax=vmax, vmin=vmin, **kwargs)
314+
extent=extent, vmax=vmax, vmin=vmin, **kwargs)
305315
if xlabel is None:
306316
xlabel = plotting_plane[0] + ' / m'
307317
if ylabel is None:
@@ -333,6 +343,38 @@ def level(p, grid, *, xnorm=None, power=False, cmap=None, vmax=3, vmin=-50,
333343
vmax=vmax, vmin=vmin, **kwargs)
334344

335345

346+
def level_contour(p, grid, *, xnorm=None, power=False,
347+
xlabel=None, ylabel=None, ax=None, **kwargs):
348+
"""Two-dimensional contour plot of level (dB) of sound field.
349+
350+
Parameters
351+
----------
352+
p, grid, xnorm, power, xlabel, ylabel, ax
353+
Same as in `level()`.
354+
**kwargs
355+
All further parameters are forwarded to
356+
:func:`matplotlib.pyplot.contour`.
357+
358+
"""
359+
p = _np.asarray(p)
360+
grid = _util.as_xyz_components(grid)
361+
# normalize before converting to dB!
362+
if xnorm is not None:
363+
p = _util.normalize(p, grid, xnorm)
364+
p, extent, plotting_plane = _plotting_plane(p, grid)
365+
L = _util.db(p, power=power)
366+
if ax is None:
367+
ax = _plt.gca()
368+
contour = ax.contour(L, extent=extent, **kwargs)
369+
if xlabel is None:
370+
xlabel = plotting_plane[0] + ' / m'
371+
if ylabel is None:
372+
ylabel = plotting_plane[1] + ' / m'
373+
ax.set_xlabel(xlabel)
374+
ax.set_ylabel(ylabel)
375+
return contour
376+
377+
336378
def particles(x, *, trim=None, ax=None, xlabel='x (m)', ylabel='y (m)',
337379
edgecolors=None, marker='.', s=15, **kwargs):
338380
"""Plot particle positions as scatter plot.

0 commit comments

Comments
 (0)