10000 update plot title handling and make uniform · toaster-code/python-control@3a8fa7a · GitHub
[go: up one dir, main page]

Skip to content

Commit 3a8fa7a

Browse files
committed
update plot title handling and make uniform
1 parent 89cda3d commit 3a8fa7a

File tree

11 files changed

+152
-118
lines changed

11 files changed

+152
-118
lines changed

control/ctrlplot.py

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@
6464
# sysnames = [response.sysname for response in data]
6565
# if title is None:
6666
# title = "Name plot for " + ", ".join(sysnames)
67-
# _update_suptitle(title, fig, rcParams=rcParams)
68-
# else
69-
# suptitle(title, fig, rcParams=rcParams)
67+
# _update_plot_title(title, fig, rcParams=rcParams)
68+
# else:
69+
# _update_plot_title(title, fig, rcParams=rcParams, use_existing=False)
7070
#
7171
# # Legacy processing of plot keyword
7272
# if plot is True:
@@ -162,7 +162,28 @@ def reshape(self, *args):
162162
return self.lines.reshape(*args)
163163

164164
def set_plot_title(self, title, frame='axes'):
165-
suptitle(title, fig=self.figure, frame=frame)
165+
"""Set the title for a control plot.
166+
167+
This is a wrapper for the matplotlib `suptitle` function, but by
168+
setting ``frame`` to 'axes' (default) then the title is centered on
169+
the midpoint of the axes in the figure, rather than the center of
170+
the figure. This usually looks better (particularly with
171+
multi-panel plots), though it takes longer to render.
172+
173+
Parameters
174+
----------
175+
title : str
176+
Title text.
177+
fig : Figure, optional
178+
Matplotlib figure. Defaults to current figure.
179+
frame : str, optional
180+
Coordinate frame to use for centering: 'axes' (default) or 'figure'.
181+
**kwargs : :func:`matplotlib.pyplot.suptitle` keywords, optional
182+
Additional keywords (passed to matplotlib).
183+
184+
"""
185+
_update_plot_title(
186+
title, fig=self.figure, frame=frame, use_existing=False)
166187

167188

168189
#
@@ -177,42 +198,13 @@ def suptitle(
177198
title, fig=None, frame='axes', **kwargs):
178199
"""Add a centered title to a figure.
179200
180-
This is a wrapper for the matplotlib `suptitle` function, but by
181-
setting ``frame`` to 'axes' (default) then the title is centered on the
182-
midpoint of the axes in the figure, rather than the center of the
183-
figure. This usually looks better (particularly with multi-panel
184-
plots), though it takes longer to render.< B41A /span>
185-
186-
Parameters
187-
----------
188-
title : str
189-
Title text.
190-
fig : Figure, optional
191-
Matplotlib figure. Defaults to current figure.
192-
frame : str, optional
193-
Coordinate frame to use for centering: 'axes' (default) or 'figure'.
194-
**kwargs : :func:`matplotlib.pyplot.suptitle` keywords, optional
195-
Additional keywords (passed to matplotlib).
201+
This function is deprecated. Use :func:`ControlPlot.set_plot_title`.
196202
197203
"""
198-
rcParams = config._get_param('ctrlplot', 'rcParams', kwargs, pop=True)
199-
200-
if fig is None:
201-
fig = plt.gcf()
202-
203-
if frame == 'figure':
204-
with plt.rc_context(rcParams):
205-
fig.suptitle(title, **kwargs)
206-
207-
elif frame == 'axes':
208-
with plt.rc_context(rcParams):
209-
fig.suptitle(title, **kwargs) # Place title in center
210-
plt.tight_layout() # Put everything into place
211-
xc, _ = _find_axes_center(fig, fig.get_axes())
212-
fig.suptitle(title, x=xc, **kwargs) # Redraw title, centered
213-
214-
else:
215-
raise ValueError(f"unknown frame '{frame}'")
204+
warnings.warn(
205+
"suptitle is deprecated; use cplt.set_plot_title", FutureWarning)
206+
_update_plot_title(
207+
title, fig=fig, frame=frame, use_existing=False, **kwargs)
216208

217209

218210
# Create vectorized function to find axes from lines
@@ -472,8 +464,18 @@ def _make_legend_labels(labels, ignore_common=False):
472464
return labels
473465

474466

475-
def _update_suptitle(title, fig=None, frame='axes', rcParams=None):
476-
if fig is not None and isinstance(title, str):
467+
def _update_plot_title(
468+
title, fig=None, frame='axes', use_existing=True, **kwargs):
469+
if title is False:
470+
return
471+
if fig is None:
472+
fig = plt.gcf()
473+
rcParams = config._get_param('ctrlplot', 'rcParams', kwargs, pop=True)
474+
if rcParams is None:
475+
rcParams = _ctrlplot_rcParams
476+
print(f"{rcParams['figure.titlesize']=}")
477+
478+
if use_existing:
477479
# Get the current title, if it exists
478480
old_title = None if fig._suptitle is None else fig._suptitle._text
479481

@@ -492,8 +494,19 @@ def _update_suptitle(title, fig=None, frame='axes', rcParams=None):
492494
separator = ',' if len(common_prefix) > 0 else ';'
493495
title = old_title + separator + title[common_len:]
494496

495-
# Add the title
496-
suptitle(title, fig=fig, rcParams=rcParams, frame=frame)
497+
if frame == 'figure':
498+
with plt.rc_context(rcParams):
499+
fig.suptitle(title, **kwargs)
500+
501+
elif frame == 'axes':
502+
with plt.rc_context(rcParams):
503+
fig.suptitle(title, **kwargs) # Place title in center
504+
plt.tight_layout() # Put everything into place
505+
xc, _ = _find_axes_center(fig, fig.get_axes())
506+
fig.suptitle(title, x=xc, **kwargs) # Redraw title, centered
507+
508+
else:
509+
raise ValueError(f"unknown frame '{frame}'")
497510

498511

499512
def _find_axes_center(fig, axs):

control/freqplot.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from .bdalg import feedback
2222
from .ctrlplot import ControlPlot, _add_arrows_to_line2D, _ctrlplot_rcParams, \
2323
_find_axes_center, _get_line_labels, _make_legend_labels, \
24-
_process_ax_keyword, _process_line_labels, _update_suptitle, suptitle
24+
_process_ax_keyword, _process_line_labels, _update_plot_title
2525
from .ctrlutil import unwrap
2626
from .exception import ControlMIMONotImplemented
2727
from .frdata import FrequencyResponseData
@@ -963,9 +963,11 @@ def gen_zero_centered_series(val_min, val_max, period):
963963
else:
964964
# Allow data to set the title (used by gangof4)
965965
title = data[0].title
966-
_update_suptitle(title, fig, rcParams=rcParams, frame=suptitle_frame)
966+
_update_plot_title(title, fig, rcParams=rcParams, frame=suptitle_frame)
967967
else:
968-
suptitle(title, fig=fig, rcParams=rcParams, frame=suptitle_frame)
968+
_update_plot_title(
969+
title, fig=fig, rcParams=rcParams, frame=suptitle_frame,
970+
use_existing=False)
969971

970972
#
971973
# Create legends
@@ -1956,7 +1958,9 @@ def _parse_linestyle(style_name, allow_false=False):
19561958
# Add the title
19571 1C6A 959
if title is None:
19581960
title = "Nyquist plot for " + ", ".join(labels)
1959-
suptitle(title, fig=fig, rcParams=rcParams, frame=suptitle_frame)
1961+
_update_plot_title(
1962+
title, fig=fig, rcParams=rcParams, frame=suptitle_frame,
1963+
use_existing=False)
19601964

19611965
# Legacy return pocessing
19621966
if plot is True or return_contour is not None:
@@ -2416,7 +2420,9 @@ def singular_values_plot(
24162420
# Add the title
24172421
if title is None:
24182422
title = "Singular values for " + ", ".join(labels)
2419-
suptitle(title, fig=fig, rcParams=rcParams, frame=suptitle_frame)
2423+
_update_plot_title(
2424+
title, fig=fig, rcParams=rcParams, frame=suptitle_frame,
2425+
use_existing=False)
24202426

24212427
# Legacy return processing
24222428
if plot is not None:

control/nichols.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from . import config
2121
from .ctrlplot import ControlPlot, _get_line_labels, _process_ax_keyword, \
22-
_process_line_labels, suptitle
22+
_process_line_labels, _update_plot_title
2323
from .ctrlutil import unwrap
2424
from .freqplot import _default_frequency_range, _freqplot_defaults
2525
from .lti import frequency_response
@@ -152,7 +152,7 @@ def nichols_plot(
152152
# Add the title
153153
if title is None:
154154
title = "Nichols plot for " + ", ".join(labels)
155-
suptitle(title, fig=fig, rcParams=rcParams)
155+
_update_plot_title(title, fig=fig, rcParams=rcParams, use_existing=False)
156156

157157
return ControlPlot(out, ax_nichols, fig, legend=legend)
158158

control/phaseplot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
from . import config
3939
from .ctrlplot import ControlPlot, _add_arrows_to_line2D, \
40-
_ctrlplot_rcParams, _process_ax_keyword, suptitle
40+
_ctrlplot_rcParams, _process_ax_keyword, _update_plot_title
4141
from .exception import ControlNotImplemented
4242
from .nlsys import NonlinearIOSystem, find_eqpt, input_output_response
4343

@@ -222,7 +222,7 @@ def _create_kwargs(global_kwargs, local_kwargs, **other_kwargs):
222222
with plt.rc_context(rcParams):
223223
if title is None:
224224
title = f"Phase portrait for {sys.name}"
225-
suptitle(title)
225+
_update_plot_title(title, use_existing=False)
226226
ax.set_xlabel(sys.state_labels[0])
227227
ax.set_ylabel(sys.state_labels[1])
228228

control/pzmap.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
from numpy import cos, exp, imag, linspace, real, sin, sqrt
1919

2020
from . import config
21-
from .ctrlplot import ControlPlot, suptitle, _get_line_labels, \
22-
_process_ax_keyword, _process_line_labels
21+
from .ctrlplot import ControlPlot, _get_line_labels, _process_ax_keyword, \
22+
_process_line_labels, _update_plot_title
2323
from .freqplot import _freqplot_defaults
2424
from .grid import nogrid, sgrid, zgrid
2525
from .iosys import isctime, isdtime
@@ -461,8 +461,9 @@ def pole_zero_plot(
461461
title = ("Root locus plot for " if rlocus_plot
462462
else "Pole/zero plot for ") + ", ".join(labels)
463463
if user_ax is None:
464-
with plt.rc_context(rcParams):
465-
fig.suptitle(title)
464+
_update_plot_title(
465+
title, fig, rcParams=rcParams, frame='figure',
466+
use_existing=False)
466467

467468
# Add dispather to handle choosing a point on the diagram
468469
if interactive:

control/tests/freqplot_test.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,12 @@ def test_response_plots(
126126
assert len(ax.get_lines()) > 1
127127

128128
# Update the title so we can see what is going on
129-
ct.suptitle(
129+
cplt.set_plot_title(
130130
cplt.figure._suptitle._text +
131131
f" [{sys.noutputs}x{sys.ninputs}, pm={pltmag}, pp={pltphs},"
132132
f" sm={shrmag}, sp={shrphs}, sf={shrfrq}]", # TODO: ", "
133133
# f"oo={ovlout}, oi={ovlinp}, ss={secsys}]", # TODO: add back
134-
frame='figure', fontsize='small')
134+
frame='figure')
135135

136136
# Get rid of the figure to free up memory
137137
if clear:
@@ -552,19 +552,21 @@ def test_mixed_systypes():
552552
resp_tf = ct.frequency_response(sys_tf)
553553
resp_ss = ct.frequency_response(sys_ss)
554554
plt.figure()
555-
ct.bode_plot([resp_tf, resp_ss, sys_frd1, sys_frd2], plot_phase=False)
556-
ct.suptitle("bode_plot([resp_tf, resp_ss, sys_frd1, sys_frd2])")
555+
cplt = ct.bode_plot(
556+
[resp_tf, resp_ss, sys_frd1, sys_frd2], plot_phase=False)
557+
cplt.set_plot_title("bode_plot([resp_tf, resp_ss, sys_frd1, sys_frd2])")
557558

558559
# Same thing, but using frequency response
559560
plt.figure()
560561
resp = ct.frequency_response([sys_tf, sys_ss, sys_frd1, sys_frd2])
561-
resp.plot(plot_phase=False)
562-
ct.suptitle("frequency_response([sys_tf, sys_ss, sys_frd1, sys_frd2])")
562+
cplt = resp.plot(plot_phase=False)
563+
cplt.set_plot_title(
564+
"frequency_response([sys_tf, sys_ss, sys_frd1, sys_frd2])")
563565

564566
# Same thing, but using bode_plot
565567
plt< D7AE /span>.figure()
566-
resp = ct.bode_plot([sys_tf, sys_ss, sys_frd1, sys_frd2], plot_phase=False)
567-
ct.suptitle("bode_plot([sys_tf, sys_ss, sys_frd1, sys_frd2])")
568+
cplt = ct.bode_plot([sys_tf, sys_ss, sys_frd1, sys_frd2], plot_phase=False)
569+
cplt.set_plot_title("bode_plot([sys_tf, sys_ss, sys_frd1, sys_frd2])")
568570

569571

570572
def test_suptitle():
@@ -575,24 +577,25 @@ def test_suptitle():
575577
assert plt.gcf()._suptitle._x != 0.5
576578

577579
# Try changing the the title
578-
ct.suptitle("New title")
580+
cplt.set_plot_title("New title")
579581
assert plt.gcf()._suptitle._text == "New title"
580582

581583
# Change the location of the title
582-
ct.suptitle("New title", frame='figure')
584+
cplt.set_plot_title("New title", frame='figure')
583585
assert plt.gcf()._suptitle._x == 0.5
584586

585587
# Change the location of the title back
586-
ct.suptitle("New title", frame='axes')
588+
cplt.set_plot_title("New title", frame='axes')
587589
assert plt.gcf()._suptitle._x != 0.5
588590

589591
# Bad frame
590592
with pytest.raises(ValueError, match="unknown"):
591-
ct.suptitle("New title", frame='nowhere')
593+
cplt.set_plot_title("New title", frame='nowhere')
592594

593595
# Bad keyword
594-
with pytest.raises(AttributeError, match="unexpected keyword|no property"):
595-
ct.suptitle("New title", unknown=None)
596+
with pytest.raises(
597+
TypeError, match="unexpected keyword|no property"):
598+
cplt.set_plot_title("New title", unknown=None)
596599

597600

598601
@pytest.mark.parametrize("plt_fcn", [ct.bode_plot, ct.singular_values_plot])

control/tests/kwargs_test.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ def test_kwarg_search(module, prefix):
5555
# Get the signature for the function
5656
sig = inspect.signature(obj)
5757

58-
# Skip anything that is inherited
59-
if inspect.isclass(module) and obj.__name__ not in module.__dict__:
58+
# Skip anything that is inherited or hidden
59+
if inspect.isclass(module) and obj.__name__ not in module.__dict__ \
60+
or obj.__name__.startswith('_'):
6061
continue
6162

6263
# See if there is a variable keyword argument
@@ -298,6 +299,7 @@ def test_response_plot_kwargs(data_fcn, plot_fcn, mimo):
298299
'optimal.create_mpc_iosystem': optimal_test.test_mpc_iosystem_rename,
299300
'optimal.solve_ocp': optimal_test.test_ocp_argument_errors,
300301
'optimal.solve_oep': optimal_test.test_oep_argument_errors,
302+
'ControlPlot.set_plot_title': freqplot_test.test_suptitle,
301303
'FrequencyResponseData.__init__':
302304
frd_test.TestFRD.test_unrecognized_keyword,
303305
'FrequencyResponseData.plot': test_response_plot_kwargs,

0 commit comments

Comments
 (0)
0