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