1- import numpy as np
2- from numpy import cos , sin , sqrt , linspace , pi , exp
1+ # grid.py - code to add gridlines to root locus and pole-zero diagrams
2+ #
3+ # This code generates grids for pole-zero diagrams (including root locus
4+ # diagrams). Rather than just draw a grid in place, it uses the AxisArtist
5+ # package to generate a custom grid that will scale with the figure.
6+ #
7+
38import matplotlib .pyplot as plt
4- from mpl_toolkits .axisartist import SubplotHost
5- from mpl_toolkits .axisartist .grid_helper_curvelinear \
6- import GridHelperCurveLinear
79import mpl_toolkits .axisartist .angle_helper as angle_helper
10+ import numpy as np
811from matplotlib .projections import PolarAxes
912from matplotlib .transforms import Affine2D
13+ from mpl_toolkits .axisartist import SubplotHost
14+ from mpl_toolkits .axisartist .grid_helper_curvelinear import \
15+ GridHelperCurveLinear
16+ from numpy import cos , exp , linspace , pi , sin , sqrt
17+
18+ from .iosys import isdtime
1019
1120
1221class FormatterDMS (object ):
@@ -65,14 +74,15 @@ def __call__(self, transform_xy, x1, y1, x2, y2):
6574 return lon_min , lon_max , lat_min , lat_max
6675
6776
68- def sgrid ():
77+ def sgrid (scaling = None ):
6978 # From matplotlib demos:
7079 # https://matplotlib.org/gallery/axisartist/demo_curvelinear_grid.html
7180 # https://matplotlib.org/gallery/axisartist/demo_floating_axis.html
7281
7382 # PolarAxes.PolarTransform takes radian. However, we want our coordinate
74- # system in degree
83+ # system in degrees
7584 tr = Affine2D ().scale (np .pi / 180. , 1. ) + PolarAxes .PolarTransform ()
85+
7686 # polar projection, which involves cycle, and also has limits in
7787 # its coordinates, needs a special method to find the extremes
7888 # (min, max of the coordinate within the view).
@@ -89,6 +99,7 @@ def sgrid():
8999 tr , extreme_finder = extreme_finder , grid_locator1 = grid_locator1 ,
90100 tick_formatter1 = tick_formatter1 )
91101
102+ # Set up an axes with a specialized grid helper
92103 fig = plt .gcf ()
93104 ax = SubplotHost (fig , 1 , 1 , 1 , grid_helper = grid_helper )
94105
@@ -97,15 +108,20 @@ def sgrid():
97108 ax .axis [:].major_ticklabels .set_visible (visible )
98109 ax .axis [:].major_ticks .set_visible (False )
99110 ax .axis [:].invert_ticklabel_direction ()
111+ ax .axis [:].major_ticklabels .set_color ('gray' )
100112
113+ # Set up internal tickmarks and labels along the real/imag axes
101114 ax .axis ["wnxneg" ] = axis = ax .new_floating_axis (0 , 180 )
102115 axis .set_ticklabel_direction ("-" )
103116 axis .label .set_visible (False )
117+
104118 ax .axis ["wnxpos" ] = axis = ax .new_floating_axis (0 , 0 )
105119 axis .label .set_visible (False )
120+
106121 ax .axis ["wnypos" ] = axis = ax .new_floating_axis (0 , 90 )
107122 axis .label .set_visible (False )
108- axis .set_axis_direction ("left" )
123+ axis .set_axis_direction ("right" )
124+
109125 ax .axis ["wnyneg" ] = axis = ax .new_floating_axis (0 , 270 )
110126 axis .label .set_visible (False )
111127 axis .set_axis_direction ("left" )
@@ -119,43 +135,41 @@ def sgrid():
119135 ax .axis ["bottom" ].get_helper ().nth_coord_ticks = 0
120136
121137 fig .add_subplot (ax )
122-
123- # RECTANGULAR X Y AXES WITH SCALE
124- # par2 = ax.twiny()
125- # par2.axis["top"].toggle(all=False)
126- # par2.axis["right"].toggle(all=False)
127- # new_fixed_axis = par2.get_grid_helper().new_fixed_axis
128- # par2.axis["left"] = new_fixed_axis(loc="left",
129- # axes=par2,
130- # offset=(0, 0))
131- # par2.axis["bottom"] = new_fixed_axis(loc="bottom",
132- # axes=par2,
133- # offset=(0, 0))
134- # FINISH RECTANGULAR
135-
136138 ax .grid (True , zorder = 0 , linestyle = 'dotted' )
137139
138- _final_setup (ax )
140+ _final_setup (ax , scaling = scaling )
139141 return ax , fig
140142
141143
142- def _final_setup (ax ):
144+ # Utility function used by all grid code
145+ def _final_setup (ax , scaling = None ):
143146 ax .set_xlabel ('Real' )
144147 ax .set_ylabel ('Imaginary' )
145- ax .axhline (y = 0 , color = 'black' , lw = 1 )
146- ax .axvline (x = 0 , color = 'black' , lw = 1 )
147- plt .axis ('equal' )
148+ ax .axhline (y = 0 , color = 'black' , lw = 0.25 )
149+ ax .axvline (x = 0 , color = 'black' , lw = 0.25 )
148150
151+ # Set up the scaling for the axes
152+ scaling = 'equal' if scaling is None else scaling
153+ plt .axis (scaling )
149154
150- def nogrid ():
151- f = plt .gcf ()
152- ax = plt .axes ()
153155
154- _final_setup (ax )
155- return ax , f
156+ # If not grid is given, at least separate stable/unstable regions
157+ def nogrid (dt = None , ax = None , scaling = None ):
158+ fig = plt .gcf ()
159+ if ax is None :
160+ ax = fig .gca ()
161+
162+ # Draw the unit circle for discrete time systems
163+ if isdtime (dt = dt , strict = True ):
164+ s = np .linspace (0 , 2 * pi , 100 )
165+ ax .plot (np .cos (s ), np .sin (s ), 'k--' , lw = 0.5 , dashes = (5 , 5 ))
156166
167+ _final_setup (ax , scaling = scaling )
168+ return ax , fig
157169
158- def zgrid (zetas = None , wns = None , ax = None ):
170+ # Grid for discrete time system (drawn, not rendered by AxisArtist)
171+ # TODO (at some point): think about using customized grid generator?
172+ def zgrid (zetas = None , wns = None , ax = None , scaling = None ):
159173 """Draws discrete damping and frequency grid"""
160174
161175 fig = plt .gcf ()
@@ -206,5 +220,9 @@ def zgrid(zetas=None, wns=None, ax=None):
206220 ax .annotate (r"$\frac{" + num + r"\pi}{T}$" , xy = (an_x , an_y ),
207221 xytext = (an_x , an_y ), size = 9 )
208222
209- _final_setup (ax )
223+ # Set default axes to allow some room around the unit circle
224+ ax .set_xlim ([- 1.1 , 1.1 ])
225+ ax .set_ylim ([- 1.1 , 1.1 ])
226+
227+ _final_setup (ax , scaling = scaling )
210228 return ax , fig
0 commit comments