Skip to content

Commit 8abb618

Browse files
committed
turn off title update if ax is given
1 parent 3a8fa7a commit 8abb618

6 files changed

Lines changed: 88 additions & 32 deletions

File tree

control/ctrlplot.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@
6060
# legend_array[i, j] = ax.legend(
6161
# lines, labels, loc=legend_map[i, j])
6262
#
63-
# # Update the plot title
63+
# # Update the plot title (only if ax was not given)
6464
# sysnames = [response.sysname for response in data]
65-
# if title is None:
65+
# if ax == None and title is None:
6666
# title = "Name plot for " + ", ".join(sysnames)
6767
# _update_plot_title(title, fig, rcParams=rcParams)
68-
# else:
68+
# elif ax == None:
6969
# _update_plot_title(title, fig, rcParams=rcParams, use_existing=False)
7070
#
7171
# # Legacy processing of plot keyword
@@ -362,8 +362,9 @@ def _process_ax_keyword(
362362
text.set_visible(False) # turn off the text
363363
del text # get rid of it completely
364364
else:
365+
axs = np.atleast_1d(axs)
365366
try:
366-
axs = np.asarray(axs).reshape(shape)
367+
axs = axs.reshape(shape)
367368
except ValueError:
368369
raise ValueError(
369370
"specified axes are not the right shape; "

control/freqplot.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -957,14 +957,14 @@ def gen_zero_centered_series(val_min, val_max, period):
957957
sysnames = [response.sysname for response in data \
958958
if not (response.sysname in seen or seen.add(response.sysname))]
959959

960-
if title is None:
960+
if ax is None and title is None:
961961
if data[0].title is None:
962962
title = "Bode plot for " + ", ".join(sysnames)
963963
else:
964964
# Allow data to set the title (used by gangof4)
965965
title = data[0].title
966966
_update_plot_title(title, fig, rcParams=rcParams, frame=suptitle_frame)
967-
else:
967+
elif ax is None:
968968
_update_plot_title(
969969
title, fig=fig, rcParams=rcParams, frame=suptitle_frame,
970970
use_existing=False)
@@ -1679,6 +1679,7 @@ def nyquist_plot(
16791679
arrow_size = config._get_param(
16801680
'nyquist', 'arrow_size', kwargs, _nyquist_defaults, pop=True)
16811681
arrow_style = config._get_param('nyquist', 'arrow_style', kwargs, None)
1682+
ax_user = ax
16821683
max_curve_magnitude = config._get_param(
16831684
'nyquist', 'max_curve_magnitude', kwargs, _nyquist_defaults, pop=True)
16841685
max_curve_offset = config._get_param(
@@ -1773,7 +1774,7 @@ def _parse_linestyle(style_name, allow_false=False):
17731774
return (counts, contours) if return_contour else counts
17741775

17751776
fig, ax = _process_ax_keyword(
1776-
ax, shape=(1, 1), squeeze=True, rcParams=rcParams)
1777+
ax_user, shape=(1, 1), squeeze=True, rcParams=rcParams)
17771778

17781779
# Create a list of lines for the output
17791780
out = np.empty(len(nyquist_responses), dtype=object)
@@ -1956,11 +1957,12 @@ def _parse_linestyle(style_name, allow_false=False):
19561957
legend=None
19571958

19581959
# Add the title
1959-
if title is None:
1960-
title = "Nyquist plot for " + ", ".join(labels)
1961-
_update_plot_title(
1962-
title, fig=fig, rcParams=rcParams, frame=suptitle_frame,
1963-
use_existing=False)
1960+
if ax_user is None:
1961+
if title is None:
1962+
title = "Nyquist plot for " + ", ".join(labels)
1963+
_update_plot_title(
1964+
title, fig=fig, rcParams=rcParams, frame=suptitle_frame,
1965+
use_existing=False)
19641966

19651967
# Legacy return pocessing
19661968
if plot is True or return_contour is not None:
@@ -2418,11 +2420,12 @@ def singular_values_plot(
24182420
legend = None
24192421

24202422
# Add the title
2421-
if title is None:
2422-
title = "Singular values for " + ", ".join(labels)
2423-
_update_plot_title(
2424-
title, fig=fig, rcParams=rcParams, frame=suptitle_frame,
2425-
use_existing=False)
2423+
if ax is None:
2424+
if title is None:
2425+
title = "Singular values for " + ", ".join(labels)
2426+
_update_plot_title(
2427+
title, fig=fig, rcParams=rcParams, frame=suptitle_frame,
2428+
use_existing=False)
24262429

24272430
# Legacy return processing
24282431
if plot is not None:

control/nichols.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,11 @@ def nichols_plot(
150150
legend = None
151151

152152
# Add the title
153-
if title is None:
154-
title = "Nichols plot for " + ", ".join(labels)
155-
_update_plot_title(title, fig=fig, rcParams=rcParams, use_existing=False)
153+
if ax is None:
154+
if title is None:
155+
title = "Nichols plot for " + ", ".join(labels)
156+
_update_plot_title(
157+
title, fig=fig, rcParams=rcParams, use_existing=False)
156158

157159
return ControlPlot(out, ax_nichols, fig, legend=legend)
158160

control/phaseplot.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -219,14 +219,13 @@ def _create_kwargs(global_kwargs, local_kwargs, **other_kwargs):
219219

220220
# TODO: update to common code pattern
221221
if user_ax is None:
222-
with plt.rc_context(rcParams):
223-
if title is None:
224-
title = f"Phase portrait for {sys.name}"
225-
_update_plot_title(title, use_existing=False)
226-
ax.set_xlabel(sys.state_labels[0])
227-
ax.set_ylabel(sys.state_labels[1])
222+
if title is None:
223+
title = f"Phase portrait for {sys.name}"
224+
_update_plot_title(title, use_existing=False)
225+
ax.set_xlabel(sys.state_labels[0])
226+
ax.set_ylabel(sys.state_labels[1])
227+
plt.tight_layout()
228228

229-
plt.tight_layout()
230229
return ControlPlot(out, ax, fig)
231230

232231

control/tests/ctrlplot_test.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def test_plot_ax_processing(resp_fcn, plot_fcn):
126126
if resp_fcn is not None:
127127
cplt3 = resp.plot(**kwargs, **meth_kwargs, ax=cplt1.axes)
128128
else:
129-
cplt3 = plot_fcn(*args, **kwargs, **meth_kwargs)
129+
cplt3 = plot_fcn(*args, **kwargs, **meth_kwargs, ax=cplt1.axes)
130130
assert cplt3.figure == cplt1.figure
131131

132132
# Plot should have landed on top of previous plot, in different colors
@@ -136,6 +136,51 @@ def test_plot_ax_processing(resp_fcn, plot_fcn):
136136
assert get_line_color(cplt3) != get_line_color(cplt1)
137137
assert get_line_color(cplt3) != get_line_color(cplt2)
138138

139+
#
140+
# Plot on a user-contructed figure
141+
#
142+
143+
# Store modified properties from previous figure
144+
cplt_titlesize = cplt3.figure._suptitle.get_fontsize()
145+
cplt_labelsize = \
146+
cplt3.axes.reshape(-1)[0].get_yticklabels()[0].get_fontsize()
147+
148+
# Set up some axes with a known title
149+
fig, axs = plt.subplots(2, 3)
150+
title = "User-constructed figure"
151+
plt.suptitle(title)
152+
titlesize = fig._suptitle.get_fontsize()
153+
assert titlesize != cplt_titlesize
154+
labelsize = axs[0, 0].get_yticklabels()[0].get_fontsize()
155+
assert labelsize != cplt_labelsize
156+
157+
# Figure out what to pass as the ax keyword
158+
match resp_fcn, plot_fcn:
159+
case _, ct.bode_plot:
160+
ax = [axs[0, 1], axs[1, 1]]
161+
162+
case ct.gangof4_response, _:
163+
ax = [axs[0, 1], axs[0, 2], axs[1, 1], axs[1, 2]]
164+
165+
case (ct.forced_response | ct.input_output_response, _):
166+
ax = [axs[0, 1], axs[1, 1]]
167+
168+
case _, _:
169+
ax = [axs[0, 1]]
170+
171+
# Call the plotting function, passing the axes
172+
if resp_fcn is not None:
173+
resp = resp_fcn(*args, **kwargs)
174+
cplt4 = resp.plot(**kwargs, **meth_kwargs, ax=ax)
175+
else:
176+
# No response function available; just plot the data
177+
cplt4 = plot_fcn(*args, **kwargs, **meth_kwargs, ax=ax)
178+
179+
# Check to make sure original settings did not change
180+
assert fig._suptitle.get_text() == title
181+
assert fig._suptitle.get_fontsize() == titlesize
182+
assert ax[0].get_yticklabels()[0].get_fontsize() == labelsize
183+
139184

140185
@pytest.mark.parametrize("resp_fcn, plot_fcn", resp_plot_fcns)
141186
@pytest.mark.usefixtures('mplcleanup')
@@ -338,13 +383,13 @@ def test_plot_title_processing(resp_fcn, plot_fcn):
338383
case _:
339384
raise RuntimeError(f"didn't recognize {resp_fnc}, {plot_fnc}")
340385

341-
# Generate the first plot, with default labels
386+
# Generate the first plot, with default title
342387
cplt1 = plot_fcn(*args1, **kwargs, **plot_fcn_kwargs)
343388
assert cplt1.figure._suptitle._text.startswith(title_prefix)
344389

345390
# Skip functions not intended for sequential calling
346391
if plot_fcn not in nolabel_plot_fcns:
347-
# Generate second plot with default labels
392+
# Generate second plot with default title
348393
cplt2 = plot_fcn(*args2, **kwargs, **plot_fcn_kwargs)
349394
assert cplt1.figure._suptitle._text == title_prefix + default_title
350395
plt.close()
@@ -372,6 +417,11 @@ def test_plot_title_processing(resp_fcn, plot_fcn):
372417
assert cplt2.figure._suptitle._text == "Another title"
373418
plt.close()
374419

420+
# Generate the plots with no title
421+
cplt = plot_fcn(
422+
*args1, **kwargs, **plot_fcn_kwargs, title=False)
423+
assert cplt.figure._suptitle == None
424+
375425

376426
@pytest.mark.usefixtures('mplcleanup')
377427
def test_rcParams():

control/timeplot.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ def time_response_plot(
175175
# Process keywords and set defaults
176176
#
177177
# Set up defaults
178+
ax_user = ax
178179
time_label = config._get_param(
179180
'timeplot', 'time_label', kwargs, _timeplot_defaults, pop=True)
180181
rcParams = config._get_param(
@@ -657,10 +658,10 @@ def _make_line_label(signal_index, signal_labels, trace_index):
657658
# list of systems (e.g., "Step response for sys[1], sys[2]").
658659
#
659660

660-
if title is None:
661+
if ax_user is None and title is None:
661662
title = data.title if title == None else title
662663
_update_plot_title(title, fig, rcParams=rcParams)
663-
else:
664+
elif ax_user is None:
664665
_update_plot_title(title, fig, rcParams=rcParams, use_existing=False)
665666

666667
return ControlPlot(out, ax_array, fig, legend=legend_map)

0 commit comments

Comments
 (0)