3333
3434
3535# Root locus map
36- def root_locus_map (sysdata , gains = None ):
36+ def root_locus_map (sysdata , gains = None , xlim = None , ylim = None ):
3737 """Compute the root locus map for an LTI system.
3838
3939 Calculate the root locus by finding the roots of 1 + k * G(s) where G
@@ -46,6 +46,10 @@ def root_locus_map(sysdata, gains=None):
4646 gains : array_like, optional
4747 Gains to use in computing plot of closed-loop poles. If not given,
4848 gains are chosen to include the main features of the root locus map.
49+ xlim : tuple or list, optional
50+ Set limits of x axis (see `matplotlib.axes.Axes.set_xlim`).
51+ ylim : tuple or list, optional
52+ Set limits of y axis (see `matplotlib.axes.Axes.set_ylim`).
4953
5054 Returns
5155 -------
@@ -75,7 +79,7 @@ def root_locus_map(sysdata, gains=None):
7579 nump , denp = _systopoly1d (sys [0 , 0 ])
7680
7781 if gains is None :
78- kvect , root_array , _ , _ = _default_gains (nump , denp , None , None )
82+ kvect , root_array , _ , _ = _default_gains (nump , denp , xlim , ylim )
7983 else :
8084 kvect = np .atleast_1d (gains )
8185 root_array = _RLFindRoots (nump , denp , kvect )
@@ -205,13 +209,52 @@ def root_locus_plot(
205209 # Plot the root loci
206210 cplt = responses .plot (grid = grid , ** kwargs )
207211
212+ # Add a reaction to axis scale changes, if given LTI systems, and
213+ # there is no set of pre-defined gains
214+ if gains is None :
215+ add_loci_recalculate (sysdata , cplt , cplt .axes [0 ,0 ])
216+
208217 # Legacy processing: return locations of poles and zeros as a tuple
209218 if plot is True :
210219 return responses .loci , responses .gains
211220
212221 return ControlPlot (cplt .lines , cplt .axes , cplt .figure )
213222
214223
224+ def add_loci_recalculate (sysdata , cplt , axis ):
225+ """Add a callback to re-calculate the loci data fitting a zoom action.
226+
227+ Parameters
228+ ----------
229+ sysdata: LTI object or list
230+ Linear input/output systems (SISO only, for now).
231+ cplt: ControlPlot
232+ Collection of plot handles.
233+ axis: matplotlib.axes.Axis
234+ Axis on which callbacks are installed.
235+ """
236+
237+ # if LTI, treat everything as a list of lti
238+ if isinstance (sysdata , LTI ):
239+ sysdata = [sysdata ]
240+
241+ # check that we can actually recalculate the loci
242+ if isinstance (sysdata , list ) and all (
243+ [isinstance (sys , LTI ) for sys in sysdata ]):
244+
245+ # callback function for axis change (zoom, pan) events
246+ # captures the sysdata object and cplt
247+ def _zoom_adapter (_ax ):
248+ newresp = root_locus_map (sysdata , None ,
249+ _ax .get_xlim (),
250+ _ax .get_ylim ())
251+ newresp .replot (cplt )
252+
253+ # connect the callback to axis changes
254+ axis .callbacks .connect ('xlim_changed' , _zoom_adapter )
255+ axis .callbacks .connect ('ylim_changed' , _zoom_adapter )
256+
257+
215258def _default_gains (num , den , xlim , ylim ):
216259 """Unsupervised gains calculation for root locus plot.
217260
@@ -288,7 +331,7 @@ def _default_gains(num, den, xlim, ylim):
288331 # Root locus is on imaginary axis (rare), use just y distance
289332 tolerance = y_tolerance
290333 elif y_tolerance == 0 :
291- # Root locus is on imaginary axis (common), use just x distance
334+ # Root locus is on real axis (common), use just x distance
292335 tolerance = x_tolerance
293336 else :
294337 tolerance = np .min ([x_tolerance , y_tolerance ])
0 commit comments