Skip to content

Commit 71f4bc1

Browse files
committed
update shared axes processing in plot_time_response
1 parent ecf6a38 commit 71f4bc1

File tree

5 files changed

+85
-12
lines changed

5 files changed

+85
-12
lines changed

control/ctrlplot.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def reset_rcParams():
334334

335335
def _process_ax_keyword(
336336
axs, shape=(1, 1), rcParams=None, squeeze=False, clear_text=False,
337-
create_axes=True):
337+
create_axes=True, sharex=False, sharey=False):
338338
"""Process ax keyword to plotting commands.
339339
340340
This function processes the `ax` keyword to plotting commands. If no
@@ -364,10 +364,12 @@ def _process_ax_keyword(
364364
with plt.rc_context(rcParams):
365365
if len(axs) != 0 and create_axes:
366366
# Create a new figure
367-
fig, axs = plt.subplots(*shape, squeeze=False)
367+
fig, axs = plt.subplots(
368+
*shape, sharex=sharex, sharey=sharey, squeeze=False)
368369
elif create_axes:
369370
# Create new axes on (empty) figure
370-
axs = fig.subplots(*shape, squeeze=False)
371+
axs = fig.subplots(
372+
*shape, sharex=sharex, sharey=sharey, squeeze=False)
371373
else:
372374
# Create an empty array and let user create axes
373375
axs = np.full(shape, None)

control/freqplot.py

+13-8
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,12 @@ def bode_plot(
198198
Determine whether and how axis limits are shared between the
199199
indicated variables. Can be set set to 'row' to share across all
200200
subplots in a row, 'col' to set across all subplots in a column, or
201-
`False` to allow independent limits.
201+
`False` to allow independent limits. Note: if `sharex` is given,
202+
it sets the value of `share_frequency`; if `sharey` is given, it
203+
sets the value of both `share_magnitude` and `share_phase`.
204+
Default values are 'row' for `share_magnitude` and `share_phase',
205+
'col', for `share_frequency`, and can be set using
206+
config.defaults['freqplot.share_<axis>'].
202207
show_legend : bool, optional
203208
Force legend to be shown if ``True`` or hidden if ``False``. If
204209
``None``, then show legend when there is more than one line on an
@@ -228,12 +233,12 @@ def bode_plot(
228233
229234
Notes
230235
-----
231-
1. Starting with python-control version 0.10, `bode_plot`returns an
232-
array of lines instead of magnitude, phase, and frequency. To
233-
recover the old behavior, call `bode_plot` with `plot=True`, which
234-
will force the legacy values (mag, phase, omega) to be returned
235-
(with a warning). To obtain just the frequency response of a system
236-
(or list of systems) without plotting, use the
236+
1. Starting with python-control version 0.10, `bode_plot`returns a
237+
:class:`ControlPlot` object instead of magnitude, phase, and
238+
frequency. To recover the old behavior, call `bode_plot` with
239+
`plot=True`, which will force the legacy values (mag, phase, omega)
240+
to be returned (with a warning). To obtain just the frequency
241+
response of a system (or list of systems) without plotting, use the
237242
:func:`~control.frequency_response` command.
238243
239244
2. If a discrete time model is given, the frequency response is plotted
@@ -583,7 +588,7 @@ def bode_plot(
583588
# axes are available and no updates should be made.
584589
#
585590

586-
# Utility function to turn off sharing
591+
# Utility function to turn on sharing
587592
def _share_axes(ref, share_map, axis):
588593
ref_ax = ax_array[ref]
589594
for index in np.nditer(share_map, flags=["refs_ok"]):

control/tests/ctrlplot_test.py

+40
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# RMM, 27 Jun 2024
33

44
import inspect
5+
import itertools
56
import warnings
67

78
import matplotlib.pyplot as plt
@@ -577,6 +578,45 @@ def test_plot_title_processing(resp_fcn, plot_fcn):
577578
assert "title : str, optional" in plot_fcn.__doc__
578579

579580

581+
@pytest.mark.parametrize("plot_fcn", multiaxes_plot_fcns)
582+
@pytest.mark.usefixtures('mplcleanup')
583+
def test_tickmark_label_processing(plot_fcn):
584+
# Generate the response that we will use for plotting
585+
top_row, bot_row = 0, -1
586+
match plot_fcn:
587+
case ct.bode_plot:
588+
resp = ct.frequency_response(ct.rss(4, 2, 2))
589+
top_row = 1
590+
case ct.time_response_plot:
591+
resp = ct.step_response(ct.rss(4, 2, 2))
592+
case ct.gangof4_plot:
593+
resp = ct.gangof4_response(ct.rss(4, 1, 1), ct.rss(3, 1, 1))
594+
case _:
595+
pytest.fail("unknown plot_fcn")
596+
597+
# Turn off axis sharing => all axes have ticklabels
598+
cplt = resp.plot(sharex=False, sharey=False)
599+
for i, j in itertools.product(
600+
range(cplt.axes.shape[0]), range(cplt.axes.shape[1])):
601+
assert len(cplt.axes[i, j].get_xticklabels()) > 0
602+
assert len(cplt.axes[i, j].get_yticklabels()) > 0
603+
plt.clf()
604+
605+
# Turn on axis sharing => only outer axes have ticklabels
606+
cplt = resp.plot(sharex=True, sharey=True)
607+
for i, j in itertools.product(
608+
range(cplt.axes.shape[0]), range(cplt.axes.shape[1])):
609+
if i < cplt.axes.shape[0] - 1:
610+
assert len(cplt.axes[i, j].get_xticklabels()) == 0
611+
else:
612+
assert len(cplt.axes[i, j].get_xticklabels()) > 0
613+
614+
if j > 0:
615+
assert len(cplt.axes[i, j].get_yticklabels()) == 0
616+
else:
617+
assert len(cplt.axes[i, j].get_yticklabels()) > 0
618+
619+
580620
@pytest.mark.parametrize("resp_fcn, plot_fcn", resp_plot_fcns)
581621
@pytest.mark.usefixtures('mplcleanup', 'editsdefaults')
582622
def test_rcParams(resp_fcn, plot_fcn):

control/timeplot.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
{'color': c} for c in [
3333
'tab:red', 'tab:purple', 'tab:brown', 'tab:olive', 'tab:cyan']],
3434
'timeplot.time_label': "Time [s]",
35+
'timeplot.sharex': 'col',
36+
'timeplot.sharey': False,
3537
}
3638

3739

@@ -66,6 +68,14 @@ def time_response_plot(
6668
overlay_signals : bool, optional
6769
If set to True, combine all input and output signals onto a single
6870
plot (for each).
71+
sharex, sharey : str or bool, optional
72+
Determine whether and how x- and y-axis limits are shared between
73+
subplots. Can be set set to 'row' to share across all subplots in
74+
a row, 'col' to set across all subplots in a column, 'all' to share
75+
across all subplots, or `False` to allow independent limits.
76+
Default values are `False` for `sharex' and 'col' for `sharey`, and
77+
can be set using config.defaults['timeplot.sharex'] and
78+
config.defaults['timeplot.sharey'].
6979
transpose : bool, optional
7080
If transpose is False (default), signals are plotted from top to
7181
bottom, starting with outputs (if plotted) and then inputs.
@@ -176,6 +186,8 @@ def time_response_plot(
176186
#
177187
# Set up defaults
178188
ax_user = ax
189+
sharex = config._get_param('timeplot', 'sharex', kwargs, pop=True)
190+
sharey = config._get_param('timeplot', 'sharey', kwargs, pop=True)
179191
time_label = config._get_param(
180192
'timeplot', 'time_label', kwargs, _timeplot_defaults, pop=True)
181193
rcParams = config._get_param('ctrlplot', 'rcParams', kwargs, pop=True)
@@ -289,7 +301,8 @@ def time_response_plot(
289301
nrows, ncols = ncols, nrows
290302

291303
# See if we can use the current figure axes
292-
fig, ax_array = _process_ax_keyword(ax, (nrows, ncols), rcParams=rcParams)
304+
fig, ax_array = _process_ax_keyword(
305+
ax, (nrows, ncols), rcParams=rcParams, sharex=sharex, sharey=sharey)
293306
legend_loc, legend_map, show_legend = _process_legend_keywords(
294307
kwargs, (nrows, ncols), 'center right')
295308

doc/plotting.rst

+13
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,19 @@ various ways. The following general rules apply:
536536
The default values for style parameters for control plots can be restored
537537
using :func:`~control.reset_rcParams`.
538538

539+
* For multi-input, multi-output time and frequency domain plots, the
540+
`sharex` and `sharey` keyword arguments can be used to determine whether
541+
and how axis limits are shared between the individual subplots. Setting
542+
the keyword to 'row' will share the axes limits across all subplots in a
543+
row, 'col' will share across all subplots in a column, 'all' will share
544+
across all subplots in the figure, and `False` will allow independent
545+
limits for each subplot.
546+
547+
For Bode plots, the `share_magnitude` and `share_phase` keyword arguments
548+
can be used to independently control axis limit sharing for the magnitude
549+
and phase portions of the plot, and `share_frequency` can be used instead
550+
of `sharex`.
551+
539552
* The ``title`` keyword can be used to override the automatic creation of
540553
the plot title. The default title is a string of the form "<Type> plot
541554
for <syslist>" where <syslist> is a list of the sys names contained in

0 commit comments

Comments
 (0)