Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/plot/bezier_path.py
8815 views
1
r"""
2
Bezier Paths
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 copy import deepcopy
22
from sage.plot.primitive import GraphicPrimitive_xydata
23
from sage.misc.decorators import options, rename_keyword
24
from sage.plot.colors import to_mpl_color
25
26
class BezierPath(GraphicPrimitive_xydata):
27
"""
28
Path of Bezier Curves graphics primitive.
29
30
The input to this constructor is a list of curves, each a list of points,
31
along which to create the curves, along with a dict of any options passed.
32
33
EXAMPLES::
34
35
sage: from sage.plot.bezier_path import BezierPath
36
sage: BezierPath([[(0,0),(.5,.5),(1,0)],[(.5,1),(0,0)]],{'linestyle':'dashed'})
37
Bezier path from (0, 0) to (0, 0)
38
39
We use :func:`bezier_path` to actually plot Bezier curves::
40
41
sage: bezier_path([[(0,0),(.5,.5),(1,0)],[(.5,1),(0,0)]],linestyle="dashed")
42
"""
43
def __init__(self, path, options):
44
"""
45
Returns a graphics primitive of a path of Bezier curves.
46
47
EXAMPLES::
48
49
sage: from sage.plot.bezier_path import BezierPath
50
sage: BezierPath([[(0,0),(.5,.5),(1,0)],[(.5,1),(0,0)]],{'linestyle':'dashed'})
51
Bezier path from (0, 0) to (0, 0)
52
"""
53
import numpy as np
54
self.path = deepcopy(path)
55
codes = [1] + (len(self.path[0])-1)*[len(self.path[0])]
56
vertices = self.path[0]
57
for curve in self.path[1:]:
58
vertices += curve
59
codes += (len(curve))*[len(curve)+1]
60
self.codes = codes
61
self.vertices = np.array(vertices, np.float)
62
GraphicPrimitive_xydata.__init__(self, options)
63
64
def _allowed_options(self):
65
"""
66
Returns a dict of allowed options for ``bezier_path``.
67
68
EXAMPLES::
69
70
sage: from sage.plot.bezier_path import BezierPath
71
sage: list(sorted(BezierPath([[[-1,2], [14,2.3], [17,4]]], {})._allowed_options().iteritems()))
72
[('alpha', 'How transparent the line is.'),
73
('fill', 'Whether or not to fill the polygon.'),
74
('linestyle',
75
"The style of the line, which is one of 'dashed', 'dotted', 'solid',
76
'dashdot', or '--', ':', '-', '-.', respectively."),
77
('rgbcolor', 'The color as an RGB tuple.'),
78
('thickness', 'How thick the border of the polygon is.'),
79
('zorder', 'The layer level in which to draw')]
80
81
"""
82
return {'alpha':'How transparent the line is.',
83
'fill': 'Whether or not to fill the polygon.',
84
'thickness':'How thick the border of the polygon is.',
85
'rgbcolor':'The color as an RGB tuple.',
86
'zorder':'The layer level in which to draw',
87
'linestyle':"The style of the line, which is one of 'dashed',"
88
" 'dotted', 'solid', 'dashdot', or '--', ':', '-', '-.',"
89
" respectively."}
90
91
def _plot3d_options(self, options=None):
92
"""
93
Updates ``BezierPath`` options to those allowed by 3D implementation.
94
95
EXAMPLES::
96
97
sage: from sage.plot.bezier_path import BezierPath
98
sage: B = BezierPath([[(0,0),(.5,.5),(1,0)],[(.5,1),(0,0)]],{'linestyle':'dashed'})
99
sage: B._plot3d_options()
100
Traceback (most recent call last):
101
...
102
NotImplementedError: Invalid 3d line style: 'dashed'
103
sage: B = BezierPath([[(0,0),(.5,.5),(1,0)],[(.5,1),(0,0)]],{'fill':False, 'thickness':2})
104
sage: B._plot3d_options()
105
{'thickness': 2}
106
"""
107
if options == None:
108
options = dict(self.options())
109
options_3d = {}
110
if 'thickness' in options:
111
options_3d['thickness'] = options['thickness']
112
del options['thickness']
113
if 'fill' in options:
114
if options['fill']:
115
raise NotImplementedError, "Invalid 3d fill style. Must set fill to False."
116
del options['fill']
117
if 'linestyle' in options:
118
if options['linestyle'] not in ('solid', '-'):
119
raise NotImplementedError("Invalid 3d line style: '%s'"%
120
(options['linestyle']))
121
del options['linestyle']
122
options_3d.update(GraphicPrimitive_xydata._plot3d_options(self, options))
123
return options_3d
124
125
def plot3d(self, z=0, **kwds):
126
"""
127
Returns a 3D plot (Jmol) of the Bezier path. Since a ``BezierPath``
128
primitive contains only `x,y` coordinates, the path will be drawn in
129
some plane (default is `z=0`). To create a Bezier path with nonzero
130
(and nonidentical) `z` coordinates in the path and control points, use
131
the function :func:`~sage.plot.plot3d.shapes2.bezier3d` instead of
132
:func:`bezier_path`.
133
134
EXAMPLES::
135
136
sage: b = bezier_path([[(0,0),(0,1),(1,0)]])
137
sage: A = b.plot3d()
138
sage: B = b.plot3d(z=2)
139
sage: A+B
140
141
::
142
143
sage: bezier3d([[(0,0,0),(1,0,0),(0,1,0),(0,1,1)]])
144
"""
145
from sage.plot.plot3d.shapes2 import bezier3d
146
options = self._plot3d_options()
147
options.update(kwds)
148
return bezier3d([[(x,y,0) for x,y in self.path[i]] for i in range(len(self.path))], **options)
149
150
def _repr_(self):
151
"""
152
Return text representation of this Bezier path graphics primitive.
153
154
EXAMPLES::
155
156
sage: from sage.plot.bezier_path import BezierPath
157
sage: B = BezierPath([[(0,0),(.5,.5),(1,0)],[(.5,1),(0,0)]],{'linestyle':'dashed'})
158
sage: B._repr_()
159
'Bezier path from (0, 0) to (0, 0)'
160
"""
161
return "Bezier path from %s to %s"%(self.path[0][0],self.path[-1][-1])
162
163
def _render_on_subplot(self, subplot):
164
"""
165
Render this Bezier path in a subplot. This is the key function that
166
defines how this Bezier path graphics primitive is rendered in matplotlib's
167
library.
168
169
TESTS::
170
171
sage: bezier_path([[(0,1),(.5,0),(1,1)]])
172
173
::
174
175
sage: bezier_path([[(0,1),(.5,0),(1,1),(-3,5)]])
176
"""
177
from matplotlib.patches import PathPatch
178
from matplotlib.path import Path
179
from sage.plot.misc import get_matplotlib_linestyle
180
181
options = dict(self.options())
182
183
del options['alpha']
184
del options['thickness']
185
del options['rgbcolor']
186
del options['zorder']
187
del options['fill']
188
del options['linestyle']
189
190
bpath = Path(self.vertices, self.codes)
191
bpatch = PathPatch(bpath, **options)
192
options = self.options()
193
bpatch.set_linewidth(float(options['thickness']))
194
bpatch.set_fill(options['fill'])
195
bpatch.set_zorder(options['zorder'])
196
a = float(options['alpha'])
197
bpatch.set_alpha(a)
198
c = to_mpl_color(options['rgbcolor'])
199
bpatch.set_edgecolor(c)
200
bpatch.set_facecolor(c)
201
bpatch.set_linestyle(get_matplotlib_linestyle(options['linestyle'],return_type='long'))
202
subplot.add_patch(bpatch)
203
204
def get_minmax_data(self):
205
"""
206
Returns a dictionary with the bounding box data.
207
208
EXAMPLES::
209
210
sage: b = bezier_path([[(0,0),(.5,.5),(1,0)],[(.5,1),(0,0)]])
211
sage: d = b.get_minmax_data()
212
sage: d['xmin']
213
0.0
214
sage: d['xmax']
215
1.0
216
"""
217
return {'xmin': self.vertices[:,0].min(),
218
'xmax': self.vertices[:,0].max(),
219
'ymin': self.vertices[:,1].min(),
220
'ymax': self.vertices[:,1].max()}
221
222
@rename_keyword(color='rgbcolor')
223
@options(alpha=1, fill=False, thickness=1, rgbcolor=(0,0,0), zorder=2, linestyle='solid')
224
def bezier_path(path, **options):
225
"""
226
Returns a Graphics object of a Bezier path corresponding to the
227
path parameter. The path is a list of curves, and each curve is
228
a list of points. Each point is a tuple ``(x,y)``.
229
230
The first curve contains the endpoints as the first and last point
231
in the list. All other curves assume a starting point given by the
232
last entry in the preceding list, and take the last point in the list
233
as their opposite endpoint. A curve can have 0, 1 or 2 control points
234
listed between the endpoints. In the input example for path below,
235
the first and second curves have 2 control points, the third has one,
236
and the fourth has no control points:
237
238
path = [[p1, c1, c2, p2], [c3, c4, p3], [c5, p4], [p5], ...]
239
240
In the case of no control points, a straight line will be drawn
241
between the two endpoints. If one control point is supplied, then
242
the curve at each of the endpoints will be tangent to the line from
243
that endpoint to the control point. Similarly, in the case of two
244
control points, at each endpoint the curve will be tangent to the line
245
connecting that endpoint with the control point immediately after or
246
immediately preceding it in the list.
247
248
So in our example above, the curve between p1 and p2 is tangent to the
249
line through p1 and c1 at p1, and tangent to the line through p2 and c2
250
at p2. Similarly, the curve between p2 and p3 is tangent to line(p2,c3)
251
at p2 and tangent to line(p3,c4) at p3. Curve(p3,p4) is tangent to
252
line(p3,c5) at p3 and tangent to line(p4,c5) at p4. Curve(p4,p5) is a
253
straight line.
254
255
INPUT:
256
257
- ``path`` -- a list of lists of tuples (see above)
258
- ``alpha`` -- default: 1
259
- ``fill`` -- default: False
260
- ``thickness`` -- default: 1
261
- ``linestyle`` -- default: ``'solid'``, The style of the line, which is one
262
of ``'dashed'``, ``'dotted'``, ``'solid'``, ``'dashdot'``, or ``'--'``,
263
``':'``, ``'-'``, ``'-.'``, respectively.
264
- ``rbgcolor`` -- default: (0,0,0)
265
- ``zorder`` -- the layer in which to draw
266
267
EXAMPLES::
268
269
sage: path = [[(0,0),(.5,.1),(.75,3),(1,0)],[(.5,1),(.5,0)],[(.2,.5)]]
270
sage: b = bezier_path(path, linestyle='dashed', rgbcolor='green')
271
sage: b
272
273
To construct a simple curve, create a list containing a single list::
274
275
sage: path = [[(0,0),(.5,1),(1,0)]]
276
sage: curve = bezier_path(path, linestyle='dashed', rgbcolor='green')
277
sage: curve
278
279
Extra options will get passed on to :meth:`~Graphics.show`, as long as they are valid::
280
281
sage: bezier_path([[(0,1),(.5,0),(1,1)]], fontsize=50)
282
sage: bezier_path([[(0,1),(.5,0),(1,1)]]).show(fontsize=50) # These are equivalent
283
284
TESTS:
285
286
We shouldn't modify our argument, :trac:`13822`::
287
288
sage: bp = [[(1,1),(2,3),(3,3)], [(4,4),(5,5)]]
289
sage: foo = bezier_path(bp)
290
sage: bp
291
[[(1, 1), (2, 3), (3, 3)], [(4, 4), (5, 5)]]
292
293
"""
294
from sage.plot.all import Graphics
295
g = Graphics()
296
g._set_extra_kwds(g._extract_kwds_for_show(options))
297
g.add_primitive(BezierPath(path, options))
298
return g
299
300
301