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 file instead, use ::
56 c.writePDFfile("line")
58 As a second example, let us define a path which consists of more than one
61 cross = path.path(path.moveto(0, 0), path.rlineto(1, 1),
62 path.moveto(1, 0), path.rlineto(-1, 1))
64 The first subpath is again a straight line from :math:`(0, 0)` to :math:`(1,
65 1)`, with the only difference that we now have used the :class:`rlineto` class,
66 whose arguments count relative from the last point in the path. The second
67 :class:`moveto` instance opens a new subpath starting at the point :math:`(1,
68 0)` and ending at :math:`(0, 1)`. Note that although both lines intersect at the
69 point :math:`(1/2, 1/2)`, they count as disconnected subpaths. The general rule
70 is that each occurrence of a :class:`moveto` instance opens a new subpath. This
71 means that if one wants to draw a rectangle, one should not use ::
73 rect1 = path.path(path.moveto(0, 0), path.lineto(0, 1),
74 path.moveto(0, 1), path.lineto(1, 1),
75 path.moveto(1, 1), path.lineto(1, 0),
76 path.moveto(1, 0), path.lineto(0, 0))
78 which would construct a rectangle out of four disconnected subpaths (see Fig.
79 :ref:`fig_rects` a). In a better solution (see Fig. :ref:`fig_rects` b), the
80 pen is not lifted between the first and the last point:
88 Rectangle consisting of (a) four separate lines, (b) one open path, and (c) one closed path. (d) Filling a path always closes it automatically.
92 rect2 = path.path(path.moveto(0, 0), path.lineto(0, 1),
93 path.lineto(1, 1), path.lineto(1, 0),
96 However, as one can see in the lower left corner of Fig. :ref:`fig_rects` b,
97 the rectangle is still incomplete. It needs to be closed, which can be done
98 explicitly by using for the last straight line of the rectangle (from the point
99 :math:`(0, 1)` back to the origin at :math:`(0, 0)`) the :class:`closepath`
102 rect3 = path.path(path.moveto(0, 0), path.lineto(0, 1),
103 path.lineto(1, 1), path.lineto(1, 0),
106 The :class:`closepath` directive adds a straight line from the current point to
107 the first point of the current subpath and furthermore *closes* the sub path,
108 i.e., it joins the beginning and the end of the line segment. This results in
109 the intended rectangle shown in Fig. :ref:`fig_rects` c. Note that filling the
110 path implicitly closes every open subpath, as is shown for a single subpath in
111 Fig. :ref:`fig_rects` d), which results from ::
113 c.stroke(rect2, [deco.filled([color.grey(0.5)])])
115 Here, we supply as second argument of the :meth:`stroke` method a list which in
116 the present case only consists of a single element, namely the so called
117 decorator :class:`deco.filled`. As its name says, this decorator specifies that
118 the path is not only being stroked but also filled with the given color. More
119 information about decorators, styles and other attributes which can be passed as
120 elements of the list can be found in Sect. :ref:`graphics_attributes`. More
121 details on the available path elements can be found in Sect.
122 :ref:`path_pathitem`.
124 To conclude this section, we should not forget to mention that rectangles are,
125 of course, predefined in PyX, so above we could have as well written ::
127 rect2 = path.rect(0, 0, 1, 1)
129 Here, the first two arguments specify the origin of the rectangle while the
130 second two arguments define its width and height, respectively. For more details
131 on the predefined paths, we refer the reader to Sect. :ref:`path_predefined`.
137 Often, one wants to perform geometrical operations with a path before placing it
138 on a canvas by stroking or filling it. For instance, one might want to
139 intersect one path with another one, split the paths at the intersection points,
140 and then join the segments together in a new way. PyX supports such tasks by
141 means of a number of path methods, which we will introduce in the following.
143 Suppose you want to draw the radii to the intersection points of a circle with a
144 straight line. This task can be done using the following code which results in
145 Fig. :ref:`fig_radii`
148 .. include:: radii.py
155 Example: Intersection of circle with line yielding two radii
157 Here, the basic elements, a circle around the point :math:`(0, 0)` with radius
158 :math:`2` and a straight line, are defined. Then, passing the *line*, to the
159 :meth:`intersect` method of *circle*, we obtain a tuple of parameter values of
160 the intersection points. The first element of the tuple is a list of parameter
161 values for the path whose :meth:`intersect` method has been called, the second
162 element is the corresponding list for the path passed as argument to this
163 method. In the present example, we only need one list of parameter values,
164 namely *isects_circle*. Using the :meth:`at` path method to obtain the point
165 corresponding to the parameter value, we draw the radii for the different
168 Another powerful feature of PyX is its ability to split paths at a given set of
169 parameters. For instance, in order to fill in the previous example the segment
170 of the circle delimited by the straight line (cf. Fig. :ref:`fig_radii2`), one
171 first has to construct a path corresponding to the outline of this segment. The
172 following code snippet yields this *segment* ::
174 arc1, arc2 = circle.split(isects_circle)
175 if arc1.arclen() < arc2.arclen():
181 line1, line2, line3 = line.split(isects_line)
183 segment = line2 << arc
189 Example: Intersection of circle with line yielding radii and circle segment
191 Here, we first split the circle using the :meth:`split` method passing the list
192 of parameters obtained above. Since the circle is closed, this yields two arc
193 segments. We then use the :meth:`arclen`, which returns the arc length of the
194 path, to find the shorter of the two arcs. Before splitting the line, we have to
195 take into account that the :meth:`split` method only accepts a sorted list of
196 parameters. Finally, we join the straight line and the arc segment. For this, we
197 make use of the ``<<`` operator, which not only adds the paths (which could be
198 done using ``line2 + arc``), but also joins the last subpath of *line2* and the
199 first one of *arc*. Thus, *segment* consists of only a single subpath and
200 filling works as expected.
202 An important issue when operating on paths is the parametrisation used.
203 Internally, PyX uses a parametrisation which uses an interval of length
204 :math:`1` for each path element of a path. For instance, for a simple straight
205 line, the possible parameter values range from :math:`0` to :math:`1`,
206 corresponding to the first and last point, respectively, of the line. Appending
207 another straight line, would extend this range to a maximal value of :math:`2`.
209 However, the situation becomes more complicated if more complex objects like a
210 circle are involved. Then, one could be tempted to assume that again the
211 parameter value ranges from :math:`0` to :math:`1`, because the predefined
212 circle consists just of one :class:`arc` together with a :class:`closepath`
213 element. However, this is not the case: the actual range is much larger. The
214 reason for this behaviour lies in the internal path handling of PyX: Before
215 performing any non-trivial geometrical operation on a path, it will
216 automatically be converted into an instance of the :class:`normpath` class (see
217 also Sect. :class:`path.normpath`). These so generated paths are already separated
218 in their subpaths and only contain straight lines and Bézier curve segments.
219 XXX explain normpathparams and things like p.begin(), p.end()-1,
221 A more geometrical way of accessing a point on the path is to use the arc length
222 of the path segment from the first point of the path to the given point. Thus,
223 all PyX path methods that accept a parameter value also allow the user to pass
224 an arc length. For instance, ::
229 pt1 = path.circle(0, 0, r).at(r*pi)
230 pt2 = path.circle(0, 0, r).at(r*3*pi/2)
232 c.stroke(path.path(path.moveto(*pt1), path.lineto(*pt2)))
234 will draw a straight line from a point at angle :math:`180` degrees (in radians
235 :math:`\pi`) to another point at angle :math:`270` degrees (in radians
236 :math:`3\pi/2`) on a circle with radius :math:`r=2`. Note however, that the
237 mapping from an arc length to a point is in general discontinuous at the beginning
238 and the end of a subpath, and thus PyX does not guarantee any particular result
239 for this boundary case.
241 More information on the available path methods can be found in Sect. :ref:`postscript_like_paths`.
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 kinds 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 thin, normal, 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. :ref:`module_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 PyX defines 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 i.e. :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 types provided 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.fillrule.nonzero_winding`` |
316 | | | ``style.fillrule.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` | transformations applied when | :class:`trafo.mirror`, |
329 | | drawing object | :class:`trafo.rotate`, |
330 | | | :class:`trafo.scale`, |
331 | | | :class:`trafo.slant`, |
332 | | | :class:`trafo.translate` |
333 +----------------------------+---------------------------------+------------------------------------+
337 specify which classes in the table are in fact instances
339 Note that operations usually allow for certain attribute categories only. For
340 example when stroking a path, text attributes are not allowed, while stroke
341 attributes and decorators are. Some attributes might belong to several attribute
342 categories like colours, which are both, stroke and fill attributes.
344 Last, we discuss another important feature of PyX's attribute system. In order
345 to allow the easy customisation of predefined attributes, it is possible to
346 create a modified attribute by calling of an attribute instance, thereby
347 specifying new parameters. A typical example is to modify the way a path is
348 stroked or filled by constructing appropriate :class:`deco.stroked` or
349 :class:`deco.filled` instances. For instance, the code ::
351 c.stroke(path, [deco.filled([color.rgb.green])])
353 draws a path filled in green with a black outline. Here, ``deco.filled`` is
354 already an instance which is modified to fill with the given color. Note that
355 an equivalent version would be ::
357 c.draw(path, [deco.stroked, deco.filled([color.rgb.green])])
359 In particular, you can see that :class:`deco.stroked` is already an attribute
360 instance, since otherwise you were not allowed to pass it as a parameter to the
361 draw method. Another example where the modification of a decorator is useful
362 are arrows. For instance, the following code draws an arrow head with a more
363 acute angle (compared to the default value of :math:`45` degrees)::
365 c.stroke(path, [deco.earrow(angle=30)])
369 changeable attributes