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