Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/plot/arrow.py
4034 views
1
"""
2
Arrows
3
"""
4
#*****************************************************************************
5
# Copyright (C) 2006 Alex Clemesha <[email protected]>,
6
# William Stein <[email protected]>,
7
# 2008 Mike Hansen <[email protected]>,
8
# 2009 Emily Kirkman
9
#
10
# Distributed under the terms of the GNU General Public License (GPL)
11
#
12
# This code is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
# General Public License for more details.
16
#
17
# The full text of the GPL is available at:
18
#
19
# http://www.gnu.org/licenses/
20
#*****************************************************************************
21
from sage.plot.primitive import GraphicPrimitive
22
from sage.misc.decorators import options, rename_keyword
23
from sage.plot.colors import to_mpl_color
24
25
class CurveArrow(GraphicPrimitive):
26
def __init__(self, path, options):
27
"""
28
Returns an arrow graphics primitive along the provided path (bezier curve).
29
30
EXAMPLES::
31
32
sage: from sage.plot.arrow import CurveArrow
33
sage: b = CurveArrow(path=[[(0,0),(.5,.5),(1,0)],[(.5,1),(0,0)]],options={})
34
sage: b
35
CurveArrow from (0, 0) to (0, 0)
36
"""
37
import numpy as np
38
self.path = path
39
codes = [1] + (len(self.path[0])-1)*[len(self.path[0])]
40
vertices = self.path[0]
41
for curve in self.path[1:]:
42
vertices += curve
43
codes += (len(curve))*[len(curve)+1]
44
self.codes = codes
45
self.vertices = np.array(vertices, np.float)
46
GraphicPrimitive.__init__(self, options)
47
48
def get_minmax_data(self):
49
"""
50
Returns a dictionary with the bounding box data.
51
52
EXAMPLES::
53
54
sage: from sage.plot.arrow import CurveArrow
55
sage: b = CurveArrow(path=[[(0,0),(.5,.5),(1,0)],[(.5,1),(0,0)]],options={})
56
sage: d = b.get_minmax_data()
57
sage: d['xmin']
58
0.0
59
sage: d['xmax']
60
1.0
61
"""
62
return {'xmin': self.vertices[:,0].min(),
63
'xmax': self.vertices[:,0].max(),
64
'ymin': self.vertices[:,1].min(),
65
'ymax': self.vertices[:,1].max()}
66
67
def _allowed_options(self):
68
"""
69
Return the dictionary of allowed options for the curve arrow graphics primitive.
70
71
EXAMPLES::
72
73
sage: from sage.plot.arrow import CurveArrow
74
sage: list(sorted(CurveArrow(path=[[(0,0),(2,3)]],options={})._allowed_options().iteritems()))
75
[('arrowsize', 'The size of the arrowhead'),
76
('arrowstyle', 'todo'),
77
('head', '2-d only: Which end of the path to draw the head (one of 0 (start), 1 (end) or 2 (both)'),
78
('hue', 'The color given as a hue.'),
79
('legend_label', 'The label for this item in the legend.'),
80
('linestyle', "2d only: The style of the line, which is one of 'dashed', 'dotted', 'solid', 'dashdot'."),
81
('rgbcolor', 'The color as an RGB tuple.'),
82
('width', 'The width of the shaft of the arrow, in points.'),
83
('zorder', '2-d only: The layer level in which to draw')]
84
"""
85
return {'width':'The width of the shaft of the arrow, in points.',
86
'rgbcolor':'The color as an RGB tuple.',
87
'hue':'The color given as a hue.',
88
'legend_label':'The label for this item in the legend.',
89
'arrowstyle': 'todo',
90
'linestyle': 'todo',
91
'arrowsize':'The size of the arrowhead',
92
'zorder':'2-d only: The layer level in which to draw',
93
'head':'2-d only: Which end of the path to draw the head (one of 0 (start), 1 (end) or 2 (both)',
94
'linestyle':"2d only: The style of the line, which is one of 'dashed', 'dotted', 'solid', 'dashdot'."}
95
96
def _repr_(self):
97
"""
98
Text representation of an arrow graphics primitive.
99
100
EXAMPLES::
101
102
sage: from sage.plot.arrow import CurveArrow
103
sage: CurveArrow(path=[[(0,0),(1,4),(2,3)]],options={})._repr_()
104
'CurveArrow from (0, 0) to (2, 3)'
105
"""
106
return "CurveArrow from %s to %s"%(self.path[0][0],self.path[-1][-1])
107
108
def _render_on_subplot(self, subplot):
109
"""
110
Render this arrow in a subplot. This is the key function that
111
defines how this arrow graphics primitive is rendered in
112
matplotlib's library.
113
114
EXAMPLES::
115
116
This function implicitly ends up rendering this arrow on a matplotlib subplot:
117
sage: arrow(path=[[(0,1), (2,-1), (4,5)]])
118
"""
119
options = self.options()
120
width = float(options['width'])
121
head = options.pop('head')
122
if head == 0: style = '<|-'
123
elif head == 1: style = '-|>'
124
elif head == 2: style = '<|-|>'
125
else: raise KeyError('head parameter must be one of 0 (start), 1 (end) or 2 (both).')
126
arrowsize = float(options.get('arrowsize',5))
127
head_width=arrowsize
128
head_length=arrowsize*2.0
129
color = to_mpl_color(options['rgbcolor'])
130
from matplotlib.patches import FancyArrowPatch
131
from matplotlib.path import Path
132
bpath = Path(self.vertices, self.codes)
133
p = FancyArrowPatch(path=bpath,
134
lw=width, arrowstyle='%s,head_width=%s,head_length=%s'%(style,head_width, head_length),
135
fc=color, ec=color, linestyle=options['linestyle'])
136
p.set_zorder(options['zorder'])
137
p.set_label(options['legend_label'])
138
subplot.add_patch(p)
139
return p
140
141
142
class Arrow(GraphicPrimitive):
143
"""
144
Primitive class that initializes the (line) arrow graphics type
145
146
EXAMPLES:
147
148
We create an arrow graphics object, then take the 0th entry
149
in it to get the actual Arrow graphics primitive::
150
151
sage: P = arrow((0,1), (2,3))[0]
152
sage: type(P)
153
<class 'sage.plot.arrow.Arrow'>
154
sage: P
155
Arrow from (0.0,1.0) to (2.0,3.0)
156
"""
157
def __init__(self, xtail, ytail, xhead, yhead, options):
158
"""
159
Create an arrow graphics primitive.
160
161
EXAMPLES::
162
163
sage: from sage.plot.arrow import Arrow
164
sage: Arrow(0,0,2,3,{})
165
Arrow from (0.0,0.0) to (2.0,3.0)
166
"""
167
self.xtail = float(xtail)
168
self.xhead = float(xhead)
169
self.ytail = float(ytail)
170
self.yhead = float(yhead)
171
GraphicPrimitive.__init__(self, options)
172
173
def get_minmax_data(self):
174
"""
175
Returns a bounding box for this arrow.
176
177
EXAMPLES::
178
179
sage: d = arrow((1,1), (5,5)).get_minmax_data()
180
sage: d['xmin']
181
1.0
182
sage: d['xmax']
183
5.0
184
"""
185
return {'xmin': min(self.xtail, self.xhead),
186
'xmax': max(self.xtail, self.xhead),
187
'ymin': min(self.ytail, self.yhead),
188
'ymax': max(self.ytail, self.yhead)}
189
190
191
def _allowed_options(self):
192
"""
193
Return the dictionary of allowed options for the line arrow graphics primitive.
194
195
EXAMPLES::
196
197
sage: from sage.plot.arrow import Arrow
198
sage: list(sorted(Arrow(0,0,2,3,{})._allowed_options().iteritems()))
199
[('arrowshorten', 'The length in points to shorten the arrow.'),
200
('arrowsize', 'The size of the arrowhead'),
201
('head', '2-d only: Which end of the path to draw the head (one of 0 (start), 1 (end) or 2 (both)'),
202
('hue', 'The color given as a hue.'),
203
('legend_label', 'The label for this item in the legend.'),
204
('linestyle', "2d only: The style of the line, which is one of 'dashed', 'dotted', 'solid', 'dashdot'."),
205
('rgbcolor', 'The color as an RGB tuple.'),
206
('width', 'The width of the shaft of the arrow, in points.'),
207
('zorder', '2-d only: The layer level in which to draw')]
208
"""
209
return {'width':'The width of the shaft of the arrow, in points.',
210
'rgbcolor':'The color as an RGB tuple.',
211
'hue':'The color given as a hue.',
212
'arrowshorten':'The length in points to shorten the arrow.',
213
'arrowsize':'The size of the arrowhead',
214
'legend_label':'The label for this item in the legend.',
215
'zorder':'2-d only: The layer level in which to draw',
216
'head':'2-d only: Which end of the path to draw the head (one of 0 (start), 1 (end) or 2 (both)',
217
'linestyle':"2d only: The style of the line, which is one of 'dashed', 'dotted', 'solid', 'dashdot'."}
218
219
def _plot3d_options(self, options=None):
220
"""
221
Translate 2D plot options into 3D plot options.
222
223
EXAMPLES::
224
225
sage: P = arrow((0,1), (2,3), width=5)
226
sage: p=P[0]; p
227
Arrow from (0.0,1.0) to (2.0,3.0)
228
sage: q=p.plot3d()
229
sage: q.thickness
230
5
231
"""
232
if options == None:
233
options = self.options()
234
options = dict(self.options())
235
options_3d = {}
236
if 'width' in options:
237
options_3d['thickness'] = options['width']
238
del options['width']
239
# ignore zorder and head in 3d plotting
240
if 'zorder' in options:
241
del options['zorder']
242
if 'head' in options:
243
del options['head']
244
if 'linestyle' in options:
245
del options['linestyle']
246
options_3d.update(GraphicPrimitive._plot3d_options(self, options))
247
return options_3d
248
249
def plot3d(self, ztail=0, zhead=0, **kwds):
250
"""
251
Takes 2D plot and places it in 3D.
252
253
EXAMPLES::
254
255
sage: A = arrow((0,0),(1,1))[0].plot3d()
256
sage: A.jmol_repr(A.testing_render_params())[0]
257
'draw line_1 diameter 2 arrow {0.0 0.0 0.0} {1.0 1.0 0.0} '
258
259
Note that we had to index the arrow to get the Arrow graphics
260
primitive. We can also change the height via the plot3d method
261
of Graphics, but only as a whole::
262
263
sage: A = arrow((0,0),(1,1)).plot3d(3)
264
sage: A.jmol_repr(A.testing_render_params())[0][0]
265
'draw line_1 diameter 2 arrow {0.0 0.0 3.0} {1.0 1.0 3.0} '
266
267
Optional arguments place both the head and tail outside the
268
`xy`-plane, but at different heights. This must be done on
269
the graphics primitive obtained by indexing::
270
271
sage: A=arrow((0,0),(1,1))[0].plot3d(3,4)
272
sage: A.jmol_repr(A.testing_render_params())[0]
273
'draw line_1 diameter 2 arrow {0.0 0.0 3.0} {1.0 1.0 4.0} '
274
"""
275
from sage.plot.plot3d.shapes2 import line3d
276
options = self._plot3d_options()
277
options.update(kwds)
278
return line3d([(self.xtail, self.ytail, ztail), (self.xhead, self.yhead, zhead)], arrow_head=True, **options)
279
280
def _repr_(self):
281
"""
282
Text representation of an arrow graphics primitive.
283
284
EXAMPLES::
285
286
sage: from sage.plot.arrow import Arrow
287
sage: Arrow(0,0,2,3,{})._repr_()
288
'Arrow from (0.0,0.0) to (2.0,3.0)'
289
"""
290
return "Arrow from (%s,%s) to (%s,%s)"%(self.xtail, self.ytail, self.xhead, self.yhead)
291
292
def _render_on_subplot(self, subplot):
293
"""
294
Render this arrow in a subplot. This is the key function that
295
defines how this arrow graphics primitive is rendered in
296
matplotlib's library.
297
298
EXAMPLES:
299
300
This function implicitly ends up rendering this arrow on
301
a matplotlib subplot::
302
303
sage: arrow((0,1), (2,-1))
304
305
TESTS:
306
307
The length of the ends (shrinkA and shrinkB) should not depend
308
on the width of the arrow, because Matplotlib already takes
309
this into account. See :trac:`12836`::
310
311
sage: fig = Graphics().matplotlib()
312
sage: sp = fig.add_subplot(1,1,1)
313
sage: a = arrow((0,0), (1,1))
314
sage: b = arrow((0,0), (1,1), width=20)
315
sage: p1 = a[0]._render_on_subplot(sp)
316
sage: p2 = b[0]._render_on_subplot(sp)
317
sage: p1.shrinkA == p2.shrinkA
318
True
319
sage: p1.shrinkB == p2.shrinkB
320
True
321
322
"""
323
options = self.options()
324
head = options.pop('head')
325
if head == 0: style = '<|-'
326
elif head == 1: style = '-|>'
327
elif head == 2: style = '<|-|>'
328
else: raise KeyError('head parameter must be one of 0 (start), 1 (end) or 2 (both).')
329
width = float(options['width'])
330
arrowshorten_end = float(options.get('arrowshorten',0))/2.0
331
arrowsize = float(options.get('arrowsize',5))
332
head_width=arrowsize
333
head_length=arrowsize*2.0
334
color = to_mpl_color(options['rgbcolor'])
335
from matplotlib.patches import FancyArrowPatch
336
p = FancyArrowPatch((self.xtail, self.ytail), (self.xhead, self.yhead),
337
lw=width, arrowstyle='%s,head_width=%s,head_length=%s'%(style,head_width, head_length),
338
shrinkA=arrowshorten_end, shrinkB=arrowshorten_end,
339
fc=color, ec=color, linestyle=options['linestyle'])
340
p.set_zorder(options['zorder'])
341
p.set_label(options['legend_label'])
342
subplot.add_patch(p)
343
return p
344
345
def arrow(tailpoint=None, headpoint=None, **kwds):
346
"""
347
Returns either a 2-dimensional or 3-dimensional arrow depending
348
on value of points.
349
350
For information regarding additional arguments, see either arrow2d?
351
or arrow3d?.
352
353
EXAMPLES::
354
355
sage: arrow((0,0), (1,1))
356
sage: arrow((0,0,1), (1,1,1))
357
"""
358
try:
359
return arrow2d(tailpoint, headpoint, **kwds)
360
except ValueError:
361
from sage.plot.plot3d.shapes import arrow3d
362
return arrow3d(tailpoint, headpoint, **kwds)
363
364
@rename_keyword(color='rgbcolor')
365
@options(width=2, rgbcolor=(0,0,1),zorder=2, head = 1, linestyle='solid', legend_label=None)
366
def arrow2d(tailpoint=None, headpoint=None, path=None, **options):
367
"""
368
If tailpoint and headpoint are provided, returns an arrow from (xmin, ymin)
369
to (xmax, ymax). If tailpoint or headpoint is None and path is not None,
370
returns an arrow along the path. (See further info on paths in bezier_path).
371
372
INPUT:
373
374
- ``tailpoint`` - the starting point of the arrow
375
376
- ``headpoint`` - where the arrow is pointing to
377
378
- ``path`` - the list of points and control points (see bezier_path for detail) that
379
the arrow will follow from source to destination
380
381
- ``head`` - 0, 1 or 2, whether to draw the head at the start (0), end (1) or both (2)
382
of the path (using 0 will swap headpoint and tailpoint). This is ignored
383
in 3D plotting.
384
385
- ``width`` - (default: 2) the width of the arrow shaft, in points
386
387
- ``color`` - (default: (0,0,1)) the color of the arrow (as an RGB tuple or a string)
388
389
- ``hue`` - the color of the arrow (as a number)
390
391
- ``arrowsize`` - the size of the arrowhead
392
393
- ``arrowshorten`` - the length in points to shorten the arrow (ignored if using path
394
parameter)
395
396
- ``legend_label`` - the label for this item in the legend
397
398
- ``zorder`` - the layer level to draw the arrow-- note that this is ignored in 3D
399
plotting.
400
401
EXAMPLES:
402
403
A straight, blue arrow::
404
405
sage: arrow2d((1, 1), (3, 3))
406
407
Make a red arrow::
408
409
sage: arrow2d((-1, -1), (2, 3), color=(1,0,0))
410
sage: arrow2d((-1, -1), (2, 3), color='red')
411
412
You can change the width of an arrow::
413
414
sage: arrow2d((1, 1), (3, 3), width=5, arrowsize=15)
415
416
A pretty circle of arrows::
417
418
sage: sum([arrow2d((0,0), (cos(x),sin(x)), hue=x/(2*pi)) for x in [0..2*pi,step=0.1]])
419
420
If we want to draw the arrow between objects, for example, the
421
boundaries of two lines, we can use the arrowshorten option
422
to make the arrow shorter by a certain number of points::
423
424
sage: line([(0,0),(1,0)],thickness=10)+line([(0,1),(1,1)], thickness=10)+arrow2d((0.5,0),(0.5,1), arrowshorten=10,rgbcolor=(1,0,0))
425
426
If BOTH headpoint and tailpoint are None, then an empty plot is returned::
427
428
sage: arrow2d(headpoint=None, tailpoint=None)
429
430
431
We can also draw an arrow with a legend::
432
433
sage: arrow((0,0), (0,2), legend_label='up')
434
435
Extra options will get passed on to show(), as long as they are valid::
436
437
sage: arrow2d((-2, 2), (7,1), frame=True)
438
sage: arrow2d((-2, 2), (7,1)).show(frame=True)
439
"""
440
from sage.plot.all import Graphics
441
g = Graphics()
442
g._set_extra_kwds(Graphics._extract_kwds_for_show(options))
443
if headpoint is not None and tailpoint is not None:
444
xtail, ytail = tailpoint
445
xhead, yhead = headpoint
446
g.add_primitive(Arrow(xtail, ytail, xhead, yhead, options=options))
447
elif path is not None:
448
g.add_primitive(CurveArrow(path, options=options))
449
elif tailpoint is None and headpoint is None:
450
return g
451
else:
452
raise TypeError('Arrow requires either both headpoint and tailpoint or a path parameter.')
453
if options['legend_label']:
454
g.legend(True)
455
return g
456
457