| | 1 | """ |
| | 2 | Arcs of circle and ellipse |
| | 3 | """ |
| | 4 | #***************************************************************************** |
| | 5 | # Copyright (C) 2010 Vincent Delecroix <20100.delecroix@gmail.com>, |
| | 6 | # |
| | 7 | # Distributed under the terms of the GNU General Public License (GPL) |
| | 8 | # |
| | 9 | # This code is distributed in the hope that it will be useful, |
| | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| | 12 | # General Public License for more details. |
| | 13 | # |
| | 14 | # The full text of the GPL is available at: |
| | 15 | # |
| | 16 | # http://www.gnu.org/licenses/ |
| | 17 | #***************************************************************************** |
| | 18 | from sage.plot.primitive import GraphicPrimitive |
| | 19 | from sage.plot.colors import to_mpl_color |
| | 20 | |
| | 21 | from sage.plot.misc import options, rename_keyword |
| | 22 | |
| | 23 | from math import fmod, floor, sin, cos, sqrt, tan, pi, atan |
| | 24 | |
| | 25 | def is_cyclic_ordered(x1,x2,x3): |
| | 26 | return ( |
| | 27 | (x1 < x2 and x2 < x3) or |
| | 28 | (x2 < x3 and x3 < x1) or |
| | 29 | (x3 < x1 and x1 < x2)) |
| | 30 | |
| | 31 | class Arc(GraphicPrimitive): |
| | 32 | """ |
| | 33 | Primitive class for the Arc graphics type. See arc? for information |
| | 34 | about actually plotting an arc of a circle or an ellipse. |
| | 35 | |
| | 36 | INPUT: |
| | 37 | |
| | 38 | - ``x,y`` - coordinates of the center of the arc |
| | 39 | |
| | 40 | - ``r1``, ``r2`` - lengths of the two radii |
| | 41 | |
| | 42 | - ``angle`` - angle of the horizontal with width |
| | 43 | |
| | 44 | - ``sector`` - sector of angle |
| | 45 | |
| | 46 | - ``options`` - dict of valid plot options to pass to constructor |
| | 47 | |
| | 48 | EXAMPLES: |
| | 49 | |
| | 50 | Note that the construction should be done using arc:: |
| | 51 | |
| | 52 | sage: from sage.plot.arc import Arc |
| | 53 | sage: print Arc(0,0,1,1,pi/4,pi/4,pi/2,{}) |
| | 54 | Arc with center (0.0,0.0) radii (1.0,1.0) angle 0.785398163397 inside the sector (0.785398163397,1.57079632679) |
| | 55 | """ |
| | 56 | def __init__(self, x, y, r1, r2, angle, s1, s2, options): |
| | 57 | """ |
| | 58 | Initializes base class Arc. |
| | 59 | |
| | 60 | EXAMPLES: |
| | 61 | |
| | 62 | sage: A = arc((2,3),1,1,pi/4,(0,pi)) |
| | 63 | sage: A[0].x == 2 |
| | 64 | True |
| | 65 | sage: A[0].y == 3 |
| | 66 | True |
| | 67 | sage: A[0].r1 == 1 |
| | 68 | True |
| | 69 | sage: A[0].r2 == 1 |
| | 70 | True |
| | 71 | sage: bool(A[0].angle == pi/4) |
| | 72 | True |
| | 73 | sage: bool(A[0].s1 == 0) |
| | 74 | True |
| | 75 | sage: bool(A[0].s2 == pi) |
| | 76 | True |
| | 77 | |
| | 78 | TESTS:: |
| | 79 | |
| | 80 | sage: from sage.plot.arc import Arc |
| | 81 | sage: a = Arc(0,0,1,1,0,0,1,{}) |
| | 82 | sage: print loads(dumps(a)) |
| | 83 | Arc with center (0.0,0.0) radii (1.0,1.0) angle 0.0 inside the sector (0.0,1.0) |
| | 84 | """ |
| | 85 | self.x = float(x) |
| | 86 | self.y = float(y) |
| | 87 | self.r1 = float(r1) |
| | 88 | self.r2 = float(r2) |
| | 89 | if self.r1 <= 0 or self.r2 <= 0: |
| | 90 | raise ValueError, "the radii must be positive real numbers." |
| | 91 | |
| | 92 | self.angle = float(angle) |
| | 93 | self.s1 = float(s1) |
| | 94 | self.s2 = float(s2) |
| | 95 | if self.s2 < self.s1: |
| | 96 | self.s1,self.s2=self.s2,self.s1 |
| | 97 | GraphicPrimitive.__init__(self, options) |
| | 98 | |
| | 99 | def get_minmax_data(self): |
| | 100 | """ |
| | 101 | Returns a dictionary with the bounding box data. |
| | 102 | |
| | 103 | The bounding box is computed as minimal as possible. |
| | 104 | |
| | 105 | EXAMPLES: |
| | 106 | |
| | 107 | An example without angle:: |
| | 108 | |
| | 109 | sage: p = arc((-2, 3), 1, 2) |
| | 110 | sage: d = p.get_minmax_data() |
| | 111 | sage: d['xmin'] |
| | 112 | -3.0 |
| | 113 | sage: d['xmax'] |
| | 114 | -1.0 |
| | 115 | sage: d['ymin'] |
| | 116 | 1.0 |
| | 117 | sage: d['ymax'] |
| | 118 | 5.0 |
| | 119 | |
| | 120 | The same example with a rotation of angle pi/2:: |
| | 121 | |
| | 122 | sage: p = arc((-2, 3), 1, 2, pi/2) |
| | 123 | sage: d = p.get_minmax_data() |
| | 124 | sage: d['xmin'] |
| | 125 | -4.0 |
| | 126 | sage: d['xmax'] |
| | 127 | 0.0 |
| | 128 | sage: d['ymin'] |
| | 129 | 2.0 |
| | 130 | sage: d['ymax'] |
| | 131 | 4.0 |
| | 132 | """ |
| | 133 | from sage.plot.plot import minmax_data |
| | 134 | |
| | 135 | twopi = 2*pi |
| | 136 | |
| | 137 | s1 = self.s1 |
| | 138 | s2 = self.s2 |
| | 139 | s = s2-s1 |
| | 140 | s1 = fmod(s1,twopi) |
| | 141 | if s1 < 0: s1 += twopi |
| | 142 | s2 = fmod(s1 + s,twopi) |
| | 143 | if s2 < 0: s2 += twopi |
| | 144 | |
| | 145 | r1 = self.r1 |
| | 146 | r2 = self.r2 |
| | 147 | |
| | 148 | angle = fmod(self.angle,twopi) |
| | 149 | if angle < 0: angle += twopi |
| | 150 | |
| | 151 | epsilon = float(0.0000001) |
| | 152 | |
| | 153 | cos_angle = cos(angle) |
| | 154 | sin_angle = sin(angle) |
| | 155 | |
| | 156 | if cos_angle > 1-epsilon: |
| | 157 | xmin=-r1; ymin=-r2 |
| | 158 | xmax=r1; ymax=r2 |
| | 159 | axmin = pi; axmax = 0 |
| | 160 | aymin = 3*pi/2; aymax = pi/2 |
| | 161 | |
| | 162 | elif cos_angle < -1+epsilon: |
| | 163 | xmin=-r1; ymin=-r2 |
| | 164 | xmax=r1; ymax=r2 |
| | 165 | axmin=0; axmax=pi |
| | 166 | aymin=pi/2; aymax=3*pi/2 |
| | 167 | |
| | 168 | elif sin_angle > 1-epsilon: |
| | 169 | xmin=-r2; ymin=-r1 |
| | 170 | xmax=r2; ymax=r1 |
| | 171 | axmin = pi/2; axmax = 3*pi/2 |
| | 172 | aymin = pi; aymax = 0 |
| | 173 | |
| | 174 | elif sin_angle < -1+epsilon: |
| | 175 | xmin=-r2; ymin=-r1 |
| | 176 | xmax=r2; ymax=r1 |
| | 177 | axmin = 3*pi/2; axmax = pi/2 |
| | 178 | aymin = 0; aymax = pi |
| | 179 | |
| | 180 | else: |
| | 181 | tan_angle = sin_angle / cos_angle |
| | 182 | axmax = atan(-r2/r1*tan_angle) |
| | 183 | if axmax < 0: axmax += twopi |
| | 184 | xmax = ( |
| | 185 | r1 * cos_angle * cos(axmax) - |
| | 186 | r2 * sin_angle * sin(axmax)) |
| | 187 | if xmax < 0: |
| | 188 | xmax = -xmax |
| | 189 | axmax = fmod(axmax+pi,twopi) |
| | 190 | xmin = -xmax |
| | 191 | axmin = fmod(axmax + pi,twopi) |
| | 192 | |
| | 193 | aymax = atan(r2/(r1*tan_angle)) |
| | 194 | if aymax < 0: aymax += twopi |
| | 195 | ymax = ( |
| | 196 | r1 * sin_angle * cos(aymax) + |
| | 197 | r2 * cos_angle * sin(aymax)) |
| | 198 | if ymax < 0: |
| | 199 | ymax = -ymax |
| | 200 | aymax = fmod(aymax+pi,twopi) |
| | 201 | ymin = -ymax |
| | 202 | aymin = fmod(aymax + pi, twopi) |
| | 203 | |
| | 204 | if s < twopi-epsilon: # bb determined by the sector |
| | 205 | x1 = cos_angle*r1*cos(s1) - sin_angle*r2*sin(s1) |
| | 206 | x2 = cos_angle*r1*cos(s2) - sin_angle*r2*sin(s2) |
| | 207 | y1 = sin_angle*r1*cos(s1) + cos_angle*r2*sin(s1) |
| | 208 | y2 = sin_angle*r1*cos(s2) + cos_angle*r2*sin(s2) |
| | 209 | |
| | 210 | if is_cyclic_ordered(s1,s2,axmin): xmin = min(x1,x2) |
| | 211 | if is_cyclic_ordered(s1,s2,aymin): ymin = min(y1,y2) |
| | 212 | if is_cyclic_ordered(s1,s2,axmax): xmax = max(x1,x2) |
| | 213 | if is_cyclic_ordered(s1,s2,aymax): ymax = max(y1,y2) |
| | 214 | |
| | 215 | return minmax_data([self.x + xmin, self.x + xmax], |
| | 216 | [self.y + ymin, self.y + ymax], |
| | 217 | dict=True) |
| | 218 | |
| | 219 | def _allowed_options(self): |
| | 220 | """ |
| | 221 | Return the allowed options for the Arc class. |
| | 222 | |
| | 223 | EXAMPLES:: |
| | 224 | |
| | 225 | sage: p = arc((3, 3), 1, 1) |
| | 226 | sage: p[0]._allowed_options()['alpha'] |
| | 227 | 'How transparent the figure is.' |
| | 228 | """ |
| | 229 | return {'alpha':'How transparent the figure is.', |
| | 230 | 'thickness':'How thick the border of the arc is.', |
| | 231 | 'hue':'The color given as a hue.', |
| | 232 | 'rgbcolor':'The color', |
| | 233 | 'zorder':'2D only: The layer level in which to draw', |
| | 234 | 'linestyle':"2D only: The style of the line, which is one of 'dashed', 'dotted', 'solid', 'dashdot'."} |
| | 235 | |
| | 236 | def _repr_(self): |
| | 237 | """ |
| | 238 | String representation of Arc primitive. |
| | 239 | |
| | 240 | EXAMPLES:: |
| | 241 | |
| | 242 | sage: from sage.plot.arc import Arc |
| | 243 | sage: print Arc(2,3,2.2,2.2,0,2,3,{}) |
| | 244 | Arc with center (2.0,3.0) radii (2.2,2.2) angle 0.0 inside the sector (2.0,3.0) |
| | 245 | """ |
| | 246 | return "Arc with center (%s,%s) radii (%s,%s) angle %s inside the sector (%s,%s)" %(self.x,self.y,self.r1,self.r2,self.angle,self.s1,self.s2) |
| | 247 | |
| | 248 | def _render_on_subplot(self, subplot): |
| | 249 | """ |
| | 250 | TESTS:: |
| | 251 | |
| | 252 | sage: A = arc((1,1),3,4,pi/4,(pi,4*pi/3)); A |
| | 253 | """ |
| | 254 | import matplotlib.patches as patches |
| | 255 | |
| | 256 | options = self.options() |
| | 257 | |
| | 258 | p = patches.Arc( |
| | 259 | (self.x,self.y), |
| | 260 | 2.*self.r1, |
| | 261 | 2.*self.r2, |
| | 262 | fmod(self.angle,2*pi)*(180./pi), |
| | 263 | self.s1*(180./pi), |
| | 264 | self.s2*(180./pi)) |
| | 265 | p.set_linewidth(float(options['thickness'])) |
| | 266 | a = float(options['alpha']) |
| | 267 | p.set_alpha(a) |
| | 268 | z = int(options.pop('zorder',1)) |
| | 269 | p.set_zorder(z) |
| | 270 | c = to_mpl_color(options['rgbcolor']) |
| | 271 | p.set_linestyle(options['linestyle']) |
| | 272 | p.set_edgecolor(c) |
| | 273 | subplot.add_patch(p) |
| | 274 | |
| | 275 | def plot3d(self): |
| | 276 | r""" |
| | 277 | TESTS: |
| | 278 | |
| | 279 | sage: from sage.plot.arc import Arc |
| | 280 | sage: Arc(0,0,1,1,0,0,1,{}).plot3d() |
| | 281 | Traceback (most recent call last): |
| | 282 | ... |
| | 283 | NotImplementedError |
| | 284 | """ |
| | 285 | raise NotImplementedError |
| | 286 | |
| | 287 | @rename_keyword(color='rgbcolor') |
| | 288 | @options(alpha=1, thickness=1, linestyle='solid', zorder=5,rgbcolor='blue') |
| | 289 | def arc(center, r1, r2=None, angle=0.0, sector=(0.0,2*pi), **options): |
| | 290 | r""" |
| | 291 | An arc (that is a portion of a circle or an ellipse) |
| | 292 | |
| | 293 | Type ``arc.options`` to see all options. |
| | 294 | |
| | 295 | INPUT: |
| | 296 | |
| | 297 | - ``center`` - 2-tuple of real numbers - position of the center. |
| | 298 | |
| | 299 | - ``r1``, ``r2`` - positive real numbers - radii of the ellipse. If only r1 is set |
| | 300 | then the two radii are supposed to be equal and this function returns an |
| | 301 | arc of of circle. |
| | 302 | |
| | 303 | - ``angle`` - real number - angle between the horizontal and the axis that |
| | 304 | corresponds to r1. |
| | 305 | |
| | 306 | - ``sector`` - 2-tuple (default: (0,2*pi))- angles sector in which the arc will |
| | 307 | be drawn. |
| | 308 | |
| | 309 | OPTIONS: |
| | 310 | |
| | 311 | - ``alpha`` - float (default: 1) - transparency |
| | 312 | |
| | 313 | - ``thickness`` - float (default: 1) - thickness of the arc |
| | 314 | |
| | 315 | - ``color``, ``rgbcolor`` - string or 2-tuple (default: 'blue') - the color |
| | 316 | of the arc |
| | 317 | |
| | 318 | - ``linestyle`` - string (default: 'solid') - the style of the line |
| | 319 | |
| | 320 | EXAMPLES: |
| | 321 | |
| | 322 | Plot an arc of circle centered at (0,0) with radius 1 in the sector |
| | 323 | (pi/4,3*pi/4):: |
| | 324 | |
| | 325 | sage: arc((0,0), 1, sector=(pi/4,3*pi/4)) |
| | 326 | |
| | 327 | Plot an arc of an ellipse between the angles 0 and pi/2:: |
| | 328 | |
| | 329 | sage: arc((2,3), 2, 1, sector=(0,pi/2)) |
| | 330 | |
| | 331 | Plot an arc of a rotated ellipse between the angles 0 and pi/2:: |
| | 332 | |
| | 333 | sage: arc((2,3), 2, 1, angle=pi/5, sector=(0,pi/2)) |
| | 334 | |
| | 335 | Plot an arc of an ellipse in red with a dashed linestyle:: |
| | 336 | |
| | 337 | sage: arc((0,0), 2, 1, 0, (0,pi/2), linestyle="dashed", color="red") |
| | 338 | |
| | 339 | It is not possible to draw ellipses in 3-D:: |
| | 340 | |
| | 341 | sage: A = arc((0,0,0), 1) |
| | 342 | Traceback (most recent call last): |
| | 343 | ... |
| | 344 | NotImplementedError |
| | 345 | """ |
| | 346 | from sage.plot.plot import Graphics |
| | 347 | if len(center)==2: |
| | 348 | if r2 is None: r2 = r1 |
| | 349 | g = Graphics() |
| | 350 | g._set_extra_kwds(Graphics._extract_kwds_for_show(options)) |
| | 351 | if len(sector) != 2: |
| | 352 | raise ValueError, "the sector must consist of two angles" |
| | 353 | g.add_primitive(Arc( |
| | 354 | center[0],center[1], |
| | 355 | r1,r2, |
| | 356 | angle, |
| | 357 | sector[0],sector[1], |
| | 358 | options)) |
| | 359 | return g |
| | 360 | elif len(center)==3: |
| | 361 | raise NotImplementedError |
| | 362 | |