Skip to content

Commit fa43785

Browse files
committed
plot vertical nyquist freq line at same time as data for legend convenience eg legend(('sys1','sys2'))
1 parent e8d233f commit fa43785

1 file changed

Lines changed: 63 additions & 51 deletions

File tree

control/freqplot.py

Lines changed: 63 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,9 @@ def bode_plot(syslist, omega=None,
194194
if not hasattr(syslist, '__iter__'):
195195
syslist = (syslist,)
196196

197+
197198
if omega is None:
199+
omega_was_given = False # used do decide whether to include nyq. freq
198200
if omega_limits is None:
199201
# Select a default range if none is provided
200202
omega = default_frequency_range(syslist, Hz=Hz,
@@ -212,6 +214,46 @@ def bode_plot(syslist, omega=None,
212214
omega = np.logspace(np.log10(omega_limits[0]),
213215
np.log10(omega_limits[1]), num=num,
214216
endpoint=True)
217+
else:
218+
omega_was_given = True
219+
220+
if plot:
221+
# Set up the axes with labels so that multiple calls to
222+
# bode_plot will superimpose the data. This was implicit
223+
# before matplotlib 2.1, but changed after that (See
224+
# https://github.com/matplotlib/matplotlib/issues/9024).
225+
# The code below should work on all cases.
226+
227+
# Get the current figure
228+
229+
if 'sisotool' in kwargs:
230+
fig = kwargs['fig']
231+
ax_mag = fig.axes[0]
232+
ax_phase = fig.axes[2]
233+
sisotool = kwargs['sisotool']
234+
del kwargs['fig']
235+
del kwargs['sisotool']
236+
else:
237+
fig = plt.gcf()
238+
ax_mag = None
239+
ax_phase = None
240+
sisotool = False
241+
242+
# Get the current axes if they already exist
243+
for ax in fig.axes:
244+
if ax.get_label() == 'control-bode-magnitude':
245+
ax_mag = ax
246+
elif ax.get_label() == 'control-bode-phase':
247+
ax_phase = ax
248+
249+
# If no axes present, create them from scratch
250+
if ax_mag is None or ax_phase is None:
251+
plt.clf()
252+
ax_mag = plt.subplot(211,
253+
label='control-bode-magnitude')
254+
ax_phase = plt.subplot(212,
255+
label='control-bode-phase',
256+
sharex=ax_mag)
215257

216258
mags, phases, omegas, nyquistfrqs = [], [], [], []
217259
for sys in syslist:
@@ -223,8 +265,11 @@ def bode_plot(syslist, omega=None,
223265
omega_sys = np.asarray(omega)
224266
if sys.isdtime(strict=True):
225267
nyquistfrq = 2. * math.pi * 1. / sys.dt / 2.
226-
omega_sys = omega_sys[omega_sys < nyquistfrq]
227-
# TODO: What distance to the Nyquist frequency is appropriate?
268+
if not omega_was_given:
269+
# include nyquist frequency
270+
omega_sys = np.hstack((
271+
omega_sys[omega_sys < nyquistfrq],
272+
nyquistfrq))
228273
else:
229274
nyquistfrq = None
230275

@@ -285,56 +330,28 @@ def bode_plot(syslist, omega=None,
285330
omega_plot = omega_sys
286331
if nyquistfrq:
287332
nyquistfrq_plot = nyquistfrq
288-
289-
# Set up the axes with labels so that multiple calls to
290-
# bode_plot will superimpose the data. This was implicit
291-
# before matplotlib 2.1, but changed after that (See
292-
# https://github.com/matplotlib/matplotlib/issues/9024).
293-
# The code below should work on all cases.
294-
295-
# Get the current figure
296-
297-
if 'sisotool' in kwargs:
298-
fig = kwargs['fig']
299-
ax_mag = fig.axes[0]
300-
ax_phase = fig.axes[2]
301-
sisotool = kwargs['sisotool']
302-
del kwargs['fig']
303-
del kwargs['sisotool']
304-
else:
305-
fig = plt.gcf()
306-
ax_mag = None
307-
ax_phase = None
308-
sisotool = False
309-
310-
# Get the current axes if they already exist
311-
for ax in fig.axes:
312-
if ax.get_label() == 'control-bode-magnitude':
313-
ax_mag = ax
314-
elif ax.get_label() == 'control-bode-phase':
315-
ax_phase = ax
316-
317-
# If no axes present, create them from scratch
318-
if ax_mag is None or ax_phase is None:
319-
plt.clf()
320-
ax_mag = plt.subplot(211,
321-
label='control-bode-magnitude')
322-
ax_phase = plt.subplot(212,
323-
label='control-bode-phase',
324-
sharex=ax_mag)
325-
333+
phase_plot = phase * 180. / math.pi if deg else phase
334+
mag_plot = mag
326335
#
327336
# Magnitude plot
328337
#
338+
339+
if nyquistfrq_plot:
340+
# add data for vertical nyquist freq indicator line
341+
# so it is a single plot action. This preserves line
342+
# order when creating legend eg. legend('sys1', 'sys2)
343+
omega_plot = np.hstack((omega_plot, nyquistfrq,nyquistfrq))
344+
mag_plot = np.hstack((mag_plot,
345+
0.7*min(mag_plot),1.3*max(mag_plot)))
346+
phase_range = max(phase_plot) - min(phase_plot)
347+
phase_plot = np.hstack((phase_plot,
348+
min(phase_plot) - 0.2 * phase_range,
349+
max(phase_plot) + 0.2 * phase_range))
329350
if dB:
330-
pltline = ax_mag.semilogx(omega_plot, 20 * np.log10(mag),
351+
ax_mag.semilogx(omega_plot, 20 * np.log10(mag_plot),
331352
*args, **kwargs)
332353
else:
333-
pltline = ax_mag.loglog(omega_plot, mag, *args, **kwargs)
334-
335-
if nyquistfrq_plot:
336-
ax_mag.axvline(nyquistfrq_plot,
337-
color=pltline[0].get_color())
354+
ax_mag.loglog(omega_plot, mag_plot, *args, **kwargs)
338355

339356
# Add a grid to the plot + labeling
340357
ax_mag.grid(grid and not margins, which='both')
@@ -343,7 +360,6 @@ def bode_plot(syslist, omega=None,
343360
#
344361
# Phase plot
345362
#
346-
phase_plot = phase * 180. / math.pi if deg else phase
347363

348364
# Plot the data
349365
ax_phase.semilogx(omega_plot, phase_plot, *args, **kwargs)
@@ -463,10 +479,6 @@ def bode_plot(syslist, omega=None,
463479
'deg' if deg else 'rad',
464480
Wcp, 'Hz' if Hz else 'rad/s'))
465481

466-
if nyquistfrq_plot:
467-
ax_phase.axvline(
468-
nyquistfrq_plot, color=pltline[0].get_color())
469-
470482
# Add a grid to the plot + labeling
471483
ax_phase.set_ylabel("Phase (deg)" if deg else "Phase (rad)")
472484

0 commit comments

Comments
 (0)