7 .. sectionauthor:: Jörg Lehmann <joergl@users.sourceforge.net>
13 The path module allows one to construct PostScript-like *paths*, which are one
14 of the main building blocks for the generation of drawings. A PostScript path is
15 an arbitrary shape consisting of straight lines, arc segments and cubic Bézier
16 curves. Such a path does not have to be connected but may also comprise several
17 disconnected segments, which will be called *subpaths* in the following.
19 XXX example for paths and subpaths (figure)
21 Usually, a path is constructed by passing a list of the path primitives
22 :class:`moveto`, :class:`lineto`, :class:`curveto`, etc., to the constructor of
23 the :class:`path` class. The following code snippet, for instance, defines a
24 path *p* that consists of a straight line from the point :math:`(0, 0)` to the
25 point :math:`(1, 1)` ::
28 p = path.path(path.moveto(0, 0), path.lineto(1, 1))
30 Equivalently, one can also use the predefined :class:`path` subclass
31 :class:`line` and write ::
33 p = path.line(0, 0, 1, 1)
35 While already some geometrical operations can be performed with this path (see
36 next section), another PyX object is needed in order to actually being able to
37 draw the path, namely an instance of the :class:`canvas` class. By convention,
38 we use the name *c* for this instance::
42 In order to draw the path on the canvas, we use the :meth:`stroke` method of the
43 :class:`canvas` class, i.e., ::
46 c.writeEPSfile("line")
48 To complete the example, we have added a :meth:`writeEPSfile` call, which writes
49 the contents of the canvas to the file :file:`line.eps`. Note that an extension
50 :file:`.eps` is added automatically, if not already present in the given
51 filename. Similarly, if you want to generate a PDF file instead, use ::
53 c.writePDFfile("line")
55 As a second example, let us define a path which consists of more than one
58 cross = path.path(path.moveto(0, 0), path.rlineto(1, 1),
59 path.moveto(1, 0), path.rlineto(-1, 1))
61 The first subpath is again a straight line from :math:`(0, 0)` to :math:`(1,
62 1)`, with the only difference that we now have used the :class:`rlineto` class,
63 whose arguments count relative from the last point in the path. The second
64 :class:`moveto` instance opens a new subpath starting at the point :math:`(1,
65 0)` and ending at :math:`(0, 1)`. Note that although both lines intersect at the
66 point :math:`(1/2, 1/2)`, they count as disconnected subpaths. The general rule
67 is that each occurrence of a :class:`moveto` instance opens a new subpath. This
68 means that if one wants to draw a rectangle, one should not use ::
70 rect1 = path.path(path.moveto(0, 0), path.lineto(0, 1),
71 path.moveto(0, 1), path.lineto(1, 1),
72 path.moveto(1, 1), path.lineto(1, 0),
73 path.moveto(1, 0), path.lineto(0, 0))
75 which would construct a rectangle out of four disconnected subpaths (see Fig.
76 :ref:`fig_rects`\ a). In a better solution (see Fig. :ref:`fig_rects`\ b), the
77 pen is not lifted between the first and the last point:
85 Rectangle consisting of (a) four separate lines, (b) one open path, and (c) one closed path. (d) Filling a path always closes it automatically.
89 rect2 = path.path(path.moveto(0, 0), path.lineto(0, 1),
90 path.lineto(1, 1), path.lineto(1, 0),
93 However, as one can see in the lower left corner of Fig. :ref:`fig_rects`\ b,
94 the rectangle is still incomplete. It needs to be closed, which can be done
95 explicitly by using for the last straight line of the rectangle (from the point
96 :math:`(0, 1)` back to the origin at :math:`(0, 0)`) the :class:`closepath`
99 rect3 = path.path(path.moveto(0, 0), path.lineto(0, 1),
100 path.lineto(1, 1), path.lineto(1, 0),
103 The :class:`closepath` directive adds a straight line from the current point to
104 the first point of the current subpath and furthermore *closes* the sub path,
105 i.e., it joins the beginning and the end of the line segment. This results in
106 the intended rectangle shown in Fig. :ref:`fig_rects`\ c. Note that filling the
107 path implicitly closes every open subpath, as is shown for a single subpath in
108 Fig. :ref:`fig_rects`\ d), which results from ::
110 c.stroke(rect2, [deco.filled([color.grey(0.95)])])
112 Here, we supply as second argument of the :meth:`stroke` method a list which in
113 the present case only consists of a single element, namely the so called
114 decorator :class:`deco.filled`. As it name says, this decorator specifies that
115 the path is not only being stroked but also filled with the given color. More
116 information about decorators, styles and other attributes which can be passed as
117 elements of the list can be found in Sect. :ref:`graphics_attributes`. More
118 details on the available path elements can be found in Sect.
119 :ref:`path_pathitem`.
121 To conclude this section, we should not forget to mention that rectangles are,
122 of course, predefined in PyX, so above we could have as well written ::
124 rect2 = path.rect(0, 0, 1, 1)
126 Here, the first two arguments specify the origin of the rectangle while the
127 second two arguments define its width and height, respectively. For more details
128 on the predefined paths, we refer the reader to Sect. :ref:`path_predefined`.
134 Often, one wants to perform geometrical operations with a path before placing it
135 on a canvas by stroking or filling it. For instance, one might want to
136 intersect one path with another one, split the paths at the intersection points,
137 and then join the segments together in a new way. PyX supports such tasks by
138 means of a number of path methods, which we will introduce in the following.
140 Suppose you want to draw the radii to the intersection points of a circle with a
141 straight line. This task can be done using the following code which results in
142 Fig. :ref:`fig_radii`
145 .. include:: radii.py
152 Example: Intersection of circle with line yielding two radii.
154 Here, the basic elements, a circle around the point :math:`(0, 0)` with radius
155 :math:`2` and a straight line, are defined. Then, passing the *line*, to the
156 :meth:`intersect` method of *circle*, we obtain a tuple of parameter values of
157 the intersection points. The first element of the tuple is a list of parameter
158 values for the path whose :meth:`intersect` method has been called, the second
159 element is the corresponding list for the path passed as argument to this
160 method. In the present example, we only need one list of parameter values,
161 namely *isects_circle*. Using the :meth:`at` path method to obtain the point
162 corresponding to the parameter value, we draw the radii for the different
165 Another powerful feature of PyX is its ability to split paths at a given set of
166 parameters. For instance, in order to fill in the previous example the segment
167 of the circle delimited by the straight line (cf. Fig. :ref:`fig_radii2`), one
168 first has to construct a path corresponding to the outline of this segment. The
169 following code snippet yields this *segment* ::
171 arc1, arc2 = circle.split(isects_circle)
172 if arc1.arclen() < arc2.arclen():
178 line1, line2, line3 = line.split(isects_line)
180 segment = line2 << arc
186 Example: Intersection of circle with line yielding radii and circle segment.
188 Here, we first split the circle using the :meth:`split` method passing the list
189 of parameters obtained above. Since the circle is closed, this yields two arc
190 segments. We then use the :meth:`arclen`, which returns the arc length of the
191 path, to find the shorter of the two arcs. Before splitting the line, we have to
192 take into account that the :meth:`split` method only accepts a sorted list of
193 parameters. Finally, we join the straight line and the arc segment. For this, we
194 make use of the ``<<`` operator, which not only adds the paths (which could be
195 done using ``line2 + arc``), but also joins the last subpath of *line2* and the
196 first one of *arc*. Thus, *segment* consists of only a single subpath and
197 filling works as expected.
199 An important issue when operating on paths is the parametrisation used.
200 Internally, PyX uses a parametrisation which uses an interval of length
201 :math:`1` for each path element of a path. For instance, for a simple straight
202 line, the possible parameter values range from :math:`0` to :math:`1`,
203 corresponding to the first and last point, respectively, of the line. Appending
204 another straight line, would extend this range to a maximal value of :math:`2`.
206 However, the situation becomes more complicated if more complex objects like a
207 circle are involved. Then, one could be tempted to assume that again the
208 parameter value ranges from :math:`0` to :math:`1`, because the predefined
209 circle consists just of one :class:`arc` together with a :class:`closepath`
210 element. However, this is not the case: the actual range is much larger. The
211 reason for this behaviour lies in the internal path handling of PyX: Before
212 performing any non-trivial geometrical operation with a path, it will
213 automatically be converted into an instance of the :class:`normpath` class (see
214 also Sect. :class:`path.normpath`). These so generated paths are already separated
215 in their subpaths and only contain straight lines and Bézier curve segments.
216 Thus, as is easily imaginable, they are much simpler to deal with.
218 XXX explain normpathparams and things like p.begin(), p.end()-1,
220 A more geometrical way of accessing a point on the path is to use the arc length
221 of the path segment from the first point of the path to the given point. Thus,
222 all PyX path methods that accept a parameter value also allow the user to pass
223 an arc length. For instance, ::
228 pt1 = path.circle(0, 0, r).at(r*pi)
229 pt2 = path.circle(0, 0, r).at(r*3*pi/2)
231 c.stroke(path.path(path.moveto(*pt1), path.lineto(*pt2)))
233 will draw a straight line from a point at angle :math:`180` degrees (in radians
234 :math:`\pi`) to another point at angle :math:`270` degrees (in radians
235 :math:`3\pi/2`) on a circle with radius :math:`r=2`. Note however, that the
236 mapping arc length :math:`\to` point is in general discontinuous at the begin
237 and the end of a subpath, and thus PyX does not guarantee any particular result
238 for this boundary case.
240 More information on the available path methods can be found in Sect.
244 .. _graphics_attributes:
246 Attributes: Styles and Decorations
247 ==================================
249 Attributes define properties of a given object when it is being used. Typically,
250 there are different kind of attributes which are usually orthogonal to each
251 other, while for one type of attribute, several choices are possible. An example
252 is the stroking of a path. There, linewidth and linestyle are different kind of
253 attributes. The linewidth might be normal, thin, thick, etc, and the linestyle
254 might be solid, dashed etc.
256 Attributes always occur in lists passed as an optional keyword argument to a
257 method or a function. Usually, attributes are the first keyword argument, so one
258 can just pass the list without specifying the keyword. Again, for the path
259 example, a typical call looks like ::
261 c.stroke(path, [style.linewidth.Thick, style.linestyle.dashed])
263 Here, we also encounter another feature of PyX's attribute system. For many
264 attributes useful default values are stored as member variables of the actual
265 attribute. For instance, ``style.linewidth.Thick`` is equivalent to
266 ``style.linewidth(0.04, type="w", unit="cm")``, that is :math:`0.04` width cm
267 (see Sect. :mod:`unit` for more information about PyX's unit system).
269 Another important feature of PyX attributes is what is call attributed merging.
270 A trivial example is the following::
272 # the following two lines are equivalent
273 c.stroke(path, [style.linewidth.Thick, style.linewidth.thin])
274 c.stroke(path, [style.linewidth.thin])
276 Here, the ``style.linewidth.thin`` attribute overrides the preceding
277 ``style.linewidth.Thick`` declaration. This is especially important in more
278 complex cases where PyXdefines default attributes for a certain operation. When
279 calling the corresponding methods with an attribute list, this list is appended
280 to the list of defaults. This way, the user can easily override certain
281 defaults, while leaving the other default values intact. In addition, every
282 attribute kind defines a special clear attribute, which allows to selectively
283 delete a default value. For path stroking this looks like ::
285 # the following two lines are equivalent
286 c.stroke(path, [style.linewidth.Thick, style.linewidth.clear])
289 The clear attribute is also provided by the base classes of the various styles.
290 For instance, :class:`style.strokestyle.clear` clears all strokestyle subclasses
291 and thus :class:`style.linewidth` and :class:`style.linestyle`. Since all
292 attributes derive from :class:`attr.attr`, you can remove all defaults using
293 ``attr.clear``. An overview over the most important attribute typesprovided by
294 PyX is given in the following table.
296 +----------------------------+---------------------------------+------------------------------------+
297 | Attribute category | description | examples |
298 +============================+=================================+====================================+
299 | :class:`deco.deco` | decorator specifying the way | :class:`deco.stroked`, |
300 | | the path is drawn | :class:`deco.filled`, |
301 | | | :class:`deco.arrow` |
302 +----------------------------+---------------------------------+------------------------------------+
303 | :class:`style.strokestyle` | style used for path stroking | :class:`style.linecap`, |
304 | | | :class:`style.linejoin`, |
305 | | | :class:`style.miterlimit`, |
306 | | | :class:`style.dash`, |
307 | | | :class:`style.linestyle`, |
308 | | | :class:`style.linewidth`, |
309 | | | :class:`color.color` |
310 +----------------------------+---------------------------------+------------------------------------+
311 | :class:`style.fillstyle` | style used for path filling | :class:`color.color`, |
312 | | | :class:`pattern.pattern` |
313 +----------------------------+---------------------------------+------------------------------------+
314 | :class:`style.filltype` | type of path filling | ``style.filltype.nonzero_winding`` |
316 | | | ``style.filltype.even_odd`` |
317 +----------------------------+---------------------------------+------------------------------------+
318 | :class:`deformer.deformer` | operations changing the shape | :class:`deformer.cycloid`, |
319 | | of the path | :class:`deformer.smoothed` |
320 +----------------------------+---------------------------------+------------------------------------+
321 | :class:`text.textattr` | attributes used for typesetting | :class:`text.halign`, |
322 | | | :class:`text.valign`, |
323 | | | :class:`text.mathmode`, |
324 | | | :class:`text.phantom`, |
325 | | | :class:`text.size`, |
326 | | | :class:`text.parbox` |
327 +----------------------------+---------------------------------+------------------------------------+
328 | :class:`trafo.trafo` | ransformations applied when | :class:`trafo.mirror`, |
329 | | drawing object | :class:`trafo.rotate`, |
330 | | | :class:`trafo.scale`, |
331 | | | :class:`trafo.slant`, |
332 | | | :class:`trafo.translate` |
333 +----------------------------+---------------------------------+------------------------------------+
335 XXX specify which classes in the table are in fact instances
337 Note that operations usually allow for certain attribute categories only. For
338 example when stroking a path, text attributes are not allowed, while stroke
339 attributes and decorators are. Some attributes might belong to several attribute
340 categories like colours, which are both, stroke and fill attributes.
342 Last, we discuss another important feature of PyX's attribute system. In order
343 to allow the easy customisation of predefined attributes, it is possible to
344 create a modified attribute by calling of an attribute instance, thereby
345 specifying new parameters. A typical example is to modify the way a path is
346 stroked or filled by constructing appropriate :class:`deco.stroked` or
347 :class:`deco.filled` instances. For instance, the code ::
349 c.stroke(path, [deco.filled([color.rgb.green])])
351 draws a path filled in green with a black outline. Here, ``deco.filled`` is
352 already an instance which is modified to fill with the given color. Note that
353 an equivalent version would be ::
355 c.draw(path, [deco.stroked, deco.filled([color.rgb.green])])
357 In particular, you can see that :class:`deco.stroked` is already an attribute
358 instance, since otherwise you were not allowed to pass it as a parameter to the
359 draw method. Another example where the modification of a decorator is useful
360 are arrows. For instance, the following code draws an arrow head with a more
361 acute angle (compared to the default value of :math:`45` degrees)::
363 c.stroke(path, [deco.earrow(angle=30)])
365 XXX changeable attributes