@@ -153,6 +153,19 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted', ax=None,
153153 Axes to add grid to. If ``None``, use ``plt.gca()``.
154154 label_cl_phases: bool, optional
155155 If True, closed-loop phase lines will be labelled.
156+
157+ Returns
158+ -------
159+ cl_mag_lines: list of `matplotlib.line.Line2D`
160+ The constant closed-loop gain contours
161+ cl_phase_lines: list of `matplotlib.line.Line2D`
162+ The constant closed-loop phase contours
163+ cl_mag_labels: list of `matplotlib.text.Text`
164+ mcontour labels; each entry corresponds to the respective entry
165+ in ``cl_mag_lines``
166+ cl_phase_labels: list of `matplotlib.text.Text`
167+ ncontour labels; each entry corresponds to the respective entry
168+ in ``cl_phase_lines``
156169 """
157170 if ax is None :
158171 ax = plt .gca ()
@@ -163,8 +176,8 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted', ax=None,
163176 ol_mag_min = - 40.0
164177 ol_mag_max = default_ol_mag_max = 50.0
165178
166- # Find bounds of the current dataset, if there is one.
167179 if ax .has_data ():
180+ # Find extent of intersection the current dataset or view
168181 ol_phase_min , ol_mag_min , ol_phase_max , ol_mag_max = _inner_extents (ax )
169182
170183 # M-circle magnitudes.
@@ -184,20 +197,22 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted', ax=None,
184197 ol_mag_min + cl_mag_step , cl_mag_step )
185198 cl_mags = np .concatenate ((extended_cl_mags , key_cl_mags ))
186199
187- phase_offset_min = 360.0 * np .ceil (ol_phase_min / 360.0 )
188- phase_offset_max = 360.0 * np .ceil (ol_phase_max / 360.0 ) + 360.0
200+ # a minimum 360deg extent containing the phases
201+ phase_round_max = 360.0 * np .ceil (ol_phase_max / 360.0 )
202+ phase_round_min = min (phase_round_max - 360 ,
203+ 360.0 * np .floor (ol_phase_min / 360.0 ))
189204
190205 # N-circle phases (should be in the range -360 to 0)
191206 if cl_phases is None :
192207 # aim for 9 lines, but always show (-360+eps, -180, -eps)
193208 # smallest spacing is 45, biggest is 180
194- phase_span = phase_offset_max - phase_offset_min
209+ phase_span = phase_round_max - phase_round_min
195210 spacing = np .clip (round (phase_span / 8 / 45 ) * 45 , 45 , 180 )
196211 key_cl_phases = np .array ([- 0.25 , - 359.75 ])
197212 other_cl_phases = np .arange (- spacing , - 360.0 , - spacing )
198213 cl_phases = np .unique (np .concatenate ((key_cl_phases , other_cl_phases )))
199- else :
200- assert (( - 360.0 < np . min ( cl_phases )) and ( np . max ( cl_phases ) < 0.0 ) )
214+ elif not (( - 360 < np . min ( cl_phases )) and ( np . max ( cl_phases ) < 0.0 )) :
215+ raise ValueError ( ' cl_phases must between -360 and 0, exclusive' )
201216
202217 # Find the M-contours
203218 m = m_circles (cl_mags , phase_min = np .min (cl_phases ),
@@ -216,21 +231,29 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted', ax=None,
216231 # over the range -360 < phase < 0. Given the range
217232 # the base chart is computed over, the phase offset should be 0
218233 # for -360 < ol_phase_min < 0.
219- phase_offsets = np .arange (phase_offset_min , phase_offset_max , 360.0 )
234+ phase_offsets = 360 + np .arange (phase_round_min , phase_round_max , 360.0 )
235+
236+ cl_mag_lines = []
237+ cl_phase_lines = []
238+ cl_mag_labels = []
239+ cl_phase_labels = []
220240
221241 for phase_offset in phase_offsets :
222242 # Draw M and N contours
223- ax .plot (m_phase + phase_offset , m_mag , color = 'lightgray' ,
224- linestyle = line_style , zorder = 0 )
225- ax .plot (n_phase + phase_offset , n_mag , color = 'lightgray' ,
226- linestyle = line_style , zorder = 0 )
243+ cl_mag_lines .extend (
244+ ax .plot (m_phase + phase_offset , m_mag , color = 'lightgray' ,
245+ linestyle = line_style , zorder = 0 ))
246+ cl_phase_lines .extend (
247+ ax .plot (n_phase + phase_offset , n_mag , color = 'lightgray' ,
248+ linestyle = line_style , zorder = 0 ))
227249
228250 # Add magnitude labels
229251 for x , y , m in zip (m_phase [:][- 1 ] + phase_offset , m_mag [:][- 1 ],
230252 cl_mags ):
231253 align = 'right' if m < 0.0 else 'left'
232- ax .text (x , y , str (m ) + ' dB' , size = 'small' , ha = align ,
233- color = 'gray' , clip_on = True )
254+ cl_mag_labels .append (
255+ ax .text (x , y , str (m ) + ' dB' , size = 'small' , ha = align ,
256+ color = 'gray' , clip_on = True ))
234257
235258 # phase labels
236259 if label_cl_phases :
@@ -243,20 +266,23 @@ def nichols_grid(cl_mags=None, cl_phases=None, line_style='dotted', ax=None,
243266 align = 'center'
244267 else :
245268 align = 'left'
246- ax .text (x , y , f'{ round (p )} \N{DEGREE SIGN} ' ,
247- size = 'small' ,
248- ha = align ,
249- va = 'bottom' ,
250- color = 'gray' ,
251- clip_on = True )
269+ cl_phase_labels .append (
270+ ax .text (x , y , f'{ round (p )} \N{DEGREE SIGN} ' ,
271+ size = 'small' ,
272+ ha = align ,
273+ va = 'bottom' ,
274+ color = 'gray' ,
275+ clip_on = True ))
252276
253277
254278 # Fit axes to generated chart
255- ax .axis ([phase_offset_min - 360.0 ,
256- phase_offset_max - 360.0 ,
279+ ax .axis ([phase_round_min ,
280+ phase_round_max ,
257281 np .min (np .concatenate ([cl_mags ,[ol_mag_min ]])),
258282 np .max ([ol_mag_max , default_ol_mag_max ])])
259283
284+ return cl_mag_lines , cl_phase_lines , cl_mag_labels , cl_phase_labels
285+
260286#
261287# Utility functions
262288#
0 commit comments