@@ -128,7 +128,7 @@ def test_plot_ax_processing(resp_fcn, plot_fcn):
128128 else :
129129 cplt3 = plot_fcn (* args , ** kwargs , ** meth_kwargs )
130130 assert cplt3 .figure == cplt1 .figure
131-
131+
132132 # Plot should have landed on top of previous plot, in different colors
133133 assert np .all (cplt3 .axes == cplt1 .axes )
134134 assert len (cplt3 .lines [0 ]) == len (cplt1 .lines [0 ])
@@ -255,6 +255,124 @@ def assert_legend(cplt, expected_texts):
255255 assert_legend (cplt , expected_labels )
256256
257257
258+ @pytest .mark .parametrize ("resp_fcn, plot_fcn" , resp_plot_fcns )
259+ @pytest .mark .usefixtures ('mplcleanup' )
260+ def test_plot_title_processing (resp_fcn , plot_fcn ):
261+ # Create some systems to use
262+ sys1 = ct .rss (2 , 1 , 1 , strictly_proper = True , name = "sys[1]" )
263+ sys1c = ct .rss (4 , 1 , 1 , strictly_proper = True , name = "sys[1]_C" )
264+ sys2 = ct .rss (2 , 1 , 1 , strictly_proper = True , name = "sys[2]" )
265+
266+ # Set up arguments
267+ kwargs = meth_kwargs = plot_fcn_kwargs = {}
268+ default_title = "sys[1], sys[2]"
269+ expected_title = "sys1_, sys2_"
270+ match resp_fcn , plot_fcn :
271+ case ct .describing_function_response , _:
272+ F = ct .descfcn .saturation_nonlinearity (1 )
273+ amp = np .linspace (1 , 4 , 10 )
274+ args1 = (sys1 , F , amp )
275+ args2 = (sys2 , F , amp )
276+
277+ case ct .gangof4_response , _:
278+ args1 = (sys1 , sys1c )
279+ args2 = (sys2 , sys1c )
280+ default_title = "P=sys[1], C=sys[1]_C, P=sys[2], C=sys[1]_C"
281+
282+ case ct .frequency_response , ct .nichols_plot :
283+ args1 = (sys1 , )
284+ args2 = (sys2 , )
285+ meth_kwargs = {'plot_type' : 'nichols' }
286+
287+ case ct .root_locus_map , ct .root_locus_plot :
288+ args1 = (sys1 , )
289+ args2 = (sys2 , )
290+ meth_kwargs = plot_fcn_kwargs = {'interactive' : False }
291+
292+ case (ct .forced_response | ct .input_output_response , _):
293+ timepts = np .linspace (1 , 10 )
294+ U = np .sin (timepts )
295+ args1 = (resp_fcn (sys1 , timepts , U ), )
296+ args2 = (resp_fcn (sys2 , timepts , U ), )
297+ argsc = (resp_fcn ([sys1 , sys2 ], timepts , U ), )
298+
299+ case (ct .impulse_response | ct .initial_response | ct .step_response , _):
300+ args1 = (resp_fcn (sys1 ), )
301+ args2 = (resp_fcn (sys2 ), )
302+ argsc = (resp_fcn ([sys1 , sys2 ]), )
303+
304+ case _, _:
305+ args1 = (sys1 , )
306+ args2 = (sys2 , )
307+
308+ # Store the expected title prefix
309+ match resp_fcn , plot_fcn :
310+ case _, ct .bode_plot :
311+ title_prefix = "Bode plot for "
312+ case _, ct .nichols_plot :
313+ title_prefix = "Nichols plot for "
314+ case _, ct .singular_values_plot :
315+ title_prefix = "Singular values for "
316+ case _, ct .gangof4_plot :
317+ title_prefix = "Gang of Four for "
318+ case _, ct .describing_function_plot :
319+ title_prefix = "Nyquist plot for "
320+ case _, ct .phase_plane_plot :
321+ title_prefix = "Phase portrait for "
322+ case _, ct .pole_zero_plot :
323+ title_prefix = "Pole/zero plot for "
324+ case _, ct .nyquist_plot :
325+ title_prefix = "Nyquist plot for "
326+ case _, ct .root_locus_plot :
327+ title_prefix = "Root locus plot for "
328+ case ct .initial_response , _:
329+ title_prefix = "Initial response for "
330+ case ct .step_response , _:
331+ title_prefix = "Step response for "
332+ case ct .impulse_response , _:
333+ title_prefix = "Impulse response for "
334+ case ct .forced_response , _:
335+ title_prefix = "Forced response for "
336+ case ct .input_output_response , _:
337+ title_prefix = "Input/output response for "
338+ case _:
339+ raise RuntimeError (f"didn't recognize { resp_fnc } , { plot_fnc } " )
340+
341+ # Generate the first plot, with default labels
342+ cplt1 = plot_fcn (* args1 , ** kwargs , ** plot_fcn_kwargs )
343+ assert cplt1 .figure ._suptitle ._text .startswith (title_prefix )
344+
345+ # Skip functions not intended for sequential calling
346+ if plot_fcn not in nolabel_plot_fcns :
347+ # Generate second plot with default labels
348+ cplt2 = plot_fcn (* args2 , ** kwargs , ** plot_fcn_kwargs )
349+ assert cplt1 .figure ._suptitle ._text == title_prefix + default_title
350+ plt .close ()
351+
352+ # Generate both plots at the same time
353+ if len (args1 ) == 1 and plot_fcn != ct .time_response_plot :
354+ cplt = plot_fcn ([* args1 , * args2 ], ** kwargs , ** plot_fcn_kwargs )
355+ assert cplt .figure ._suptitle ._text == title_prefix + default_title
356+ elif len (args1 ) == 1 and plot_fcn == ct .time_response_plot :
357+ # Use TimeResponseList.plot() to generate combined response
358+ cplt = argsc [0 ].plot (** kwargs , ** plot_fcn_kwargs )
359+ assert cplt .figure ._suptitle ._text == title_prefix + default_title
360+ plt .close ()
361+
362+ # Generate plots sequentially, with updated titles
363+ cplt1 = plot_fcn (
364+ * args1 , ** kwargs , ** plot_fcn_kwargs , title = "My first title" )
365+ cplt2 = plot_fcn (
366+ * args2 , ** kwargs , ** plot_fcn_kwargs , title = "My new title" )
367+ assert cplt2 .figure ._suptitle ._text == "My new title"
368+ plt .close ()
369+
370+ # Update using set_plot_title
371+ cplt2 .set_plot_title ("Another title" )
372+ assert cplt2 .figure ._suptitle ._text == "Another title"
373+ plt .close ()
374+
375+
258376@pytest .mark .usefixtures ('mplcleanup' )
259377def test_rcParams ():
260378 sys = ct .rss (2 , 2 , 2 )
0 commit comments