Refactored sgrid based on matplotlib projections and moved sgrid and … · python-control/python-control@89dc937
1+import numpy as np
2+from numpy import cos, sin, sqrt, linspace, pi, exp
3+import matplotlib.pyplot as plt
4+from mpl_toolkits.axisartist import SubplotHost
5+from mpl_toolkits.axisartist.grid_helper_curvelinear import GridHelperCurveLinear
6+import mpl_toolkits.axisartist.angle_helper as angle_helper
7+from matplotlib.projections import PolarAxes
8+from matplotlib.transforms import Affine2D
9+10+class FormatterDMS(object):
11+'''Transforms angle ticks to damping ratios'''
12+def __call__(self,direction,factor,values):
13+angles_deg = values/factor
14+damping_ratios = np.cos((180-angles_deg)*np.pi/180)
15+ret = ["%.2f"%val for val in damping_ratios]
16+return ret
17+18+class ModifiedExtremeFinderCycle(angle_helper.ExtremeFinderCycle):
19+'''Changed to allow only left hand-side polar grid'''
20+def __call__(self, transform_xy, x1, y1, x2, y2):
21+x_, y_ = np.linspace(x1, x2, self.nx), np.linspace(y1, y2, self.ny)
22+x, y = np.meshgrid(x_, y_)
23+lon, lat = transform_xy(np.ravel(x), np.ravel(y))
24+25+with np.errstate(invalid='ignore'):
26+if self.lon_cycle is not None:
27+lon0 = np.nanmin(lon)
28+lon -= 360. * ((lon - lon0) > 360.) # Changed from 180 to 360 to be able to span only 90-270 (left hand side)
29+if self.lat_cycle is not None:
30+lat0 = np.nanmin(lat)
31+lat -= 360. * ((lat - lat0) > 360.) # Changed from 180 to 360 to be able to span only 90-270 (left hand side)
32+33+lon_min, lon_max = np.nanmin(lon), np.nanmax(lon)
34+lat_min, lat_max = np.nanmin(lat), np.nanmax(lat)
35+36+lon_min, lon_max, lat_min, lat_max = \
37+self._adjust_extremes(lon_min, lon_max, lat_min, lat_max)
38+39+return lon_min, lon_max, lat_min, lat_max
40+41+def sgrid():
42+# From matplotlib demos:
43+# https://matplotlib.org/gallery/axisartist/demo_curvelinear_grid.html
44+# https://matplotlib.org/gallery/axisartist/demo_floating_axis.html
45+46+# PolarAxes.PolarTransform takes radian. However, we want our coordinate
47+# system in degree
48+tr = Affine2D().scale(np.pi/180., 1.) + PolarAxes.PolarTransform()
49+# polar projection, which involves cycle, and also has limits in
50+# its coordinates, needs a special method to find the extremes
51+# (min, max of the coordinate within the view).
52+53+# 20, 20 : number of sampling points along x, y direction
54+sampling_points = 20
55+extreme_finder = ModifiedExtremeFinderCycle(sampling_points, sampling_points,
56+lon_cycle=360,
57+lat_cycle=None,
58+lon_minmax=(90,270),
59+lat_minmax=(0, np.inf),)
60+61+grid_locator1 = angle_helper.LocatorDMS(15)
62+tick_formatter1 = FormatterDMS()
63+grid_helper = GridHelperCurveLinear(tr,
64+extreme_finder=extreme_finder,
65+grid_locator1=grid_locator1,
66+tick_formatter1=tick_formatter1
67+ )
68+69+fig = plt.figure()
70+ax = SubplotHost(fig, 1, 1, 1, grid_helper=grid_helper)
71+72+# make ticklabels of right invisible, and top axis visible.
73+visible = True
74+ax.axis[:].major_ticklabels.set_visible(visible)
75+ax.axis[:].major_ticks.set_visible(False)
76+ax.axis[:].invert_ticklabel_direction()
77+78+ax.axis["wnxneg"] = axis = ax.new_floating_axis(0, 180)
79+axis.set_ticklabel_direction("-")
80+axis.label.set_visible(False)
81+ax.axis["wnxpos"] = axis = ax.new_floating_axis(0, 0)
82+axis.label.set_visible(False)
83+ax.axis["wnypos"] = axis = ax.new_floating_axis(0, 90)
84+axis.label.set_visible(False)
85+axis.set_axis_direction("left")
86+ax.axis["wnyneg"] = axis = ax.new_floating_axis(0, 270)
87+axis.label.set_visible(False)
88+axis.set_axis_direction("left")
89+axis.invert_ticklabel_direction()
90+axis.set_ticklabel_direction("-")
91+92+# let left axis shows ticklabels for 1st coordinate (angle)
93+ax.axis["left"].get_helper().nth_coord_ticks = 0
94+ax.axis["right"].get_helper().nth_coord_ticks = 0
95+ax.axis["left"].get_helper().nth_coord_ticks = 0
96+ax.axis["bottom"].get_helper().nth_coord_ticks = 0
97+98+fig.add_subplot(ax)
99+100+### RECTANGULAR X Y AXES WITH SCALE
101+#par2 = ax.twiny()
102+#par2.axis["top"].toggle(all=False)
103+#par2.axis["right"].toggle(all=False)
104+#new_fixed_axis = par2.get_grid_helper().new_fixed_axis
105+#par2.axis["left"] = new_fixed_axis(loc="left",
106+# axes=par2,
107+# offset=(0, 0))
108+#par2.axis["bottom"] = new_fixed_axis(loc="bottom",
109+# axes=par2,
110+# offset=(0, 0))
111+### FINISH RECTANGULAR
112+113+ax.grid(True, zorder=0,linestyle='dotted')
114+115+_final_setup(ax)
116+return ax, fig
117+118+def _final_setup(ax):
119+ax.set_xlabel('Real')
120+ax.set_ylabel('Imaginary')
121+ax.axhline(y=0, color='black', lw=1)
122+ax.axvline(x=0, color='black', lw=1)
123+plt.axis('equal')
124+125+def nogrid():
126+f = plt.figure()
127+ax = plt.axes()
128+129+_final_setup(ax)
130+return ax, f
131+132+def zgrid(zetas=None, wns=None):
133+'''Draws discrete damping and frequency grid'''
134+135+fig = plt.figure()
136+ax = fig.gca()
137+138+# Constant damping lines
139+if zetas is None:
140+zetas = linspace(0, 0.9, 10)
141+for zeta in zetas:
142+# Calculate in polar coordinates
143+factor = zeta/sqrt(1-zeta**2)
144+x = linspace(0, sqrt(1-zeta**2),200)
145+ang = pi*x
146+mag = exp(-pi*factor*x)
147+# Draw upper part in retangular coordinates
148+xret = mag*cos(ang)
149+yret = mag*sin(ang)
150+ax.plot(xret,yret, 'k:', lw=1)
151+# Draw lower part in retangular coordinates
152+xret = mag*cos(-ang)
153+yret = mag*sin(-ang)
154+ax.plot(xret,yret,'k:', lw=1)
155+# Annotation
156+an_i = int(len(xret)/2.5)
157+an_x = xret[an_i]
158+an_y = yret[an_i]
159+ax.annotate(str(round(zeta,2)), xy=(an_x, an_y), xytext=(an_x, an_y), size=7)
160+161+# Constant natural frequency lines
162+if wns is None:
163+wns = linspace(0, 1, 10)
164+for a in wns:
165+# Calculate in polar coordinates
166+x = linspace(-pi/2,pi/2,200)
167+ang = pi*a*sin(x)
168+mag = exp(-pi*a*cos(x))
169+# Draw in retangular coordinates
170+xret = mag*cos(ang)
171+yret = mag*sin(ang)
172+ax.plot(xret,yret,'k:', lw=1)
173+# Annotation
174+an_i = -1
175+an_x = xret[an_i]
176+an_y = yret[an_i]
177+num = '{:1.1f}'.format(a)
178+ax.annotate("$\\frac{"+num+"\pi}{T}$", xy=(an_x, an_y), xytext=(an_x, an_y), size=9)
179+180+_final_setup(ax)
181+return ax, fig
182+