File: graphics.tex

package info (click to toggle)
pyx 0.9-4
  • links: PTS
  • area: main
  • in suites: lenny
  • size: 2,060 kB
  • ctags: 2,665
  • sloc: python: 15,205; makefile: 142; ansic: 131
file content (390 lines) | stat: -rw-r--r-- 16,627 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
\chapter{Basic graphics}

\sectionauthor{J\"org Lehmann}{joergl@users.sourceforge.net} 

\label{graphics}

\section{Introduction}

The path module allows one to construct PostScript-like
\textit{paths}, which are one of the main building blocks for the
generation of drawings. A PostScript path is an arbitrary shape
consisting of straight lines, arc segments and cubic B\'ezier curves.
Such a path does not have to be connected but may also comprise
several disconnected segments, which will be called \textit{subpaths}
in the following. 

XXX example for paths and subpaths (figure)

Usually, a path is constructed by passing a list of the path
primitives \class{moveto}, \class{lineto}, \class{curveto}, etc., to the
constructor of the \class{path} class. The following code snippet, for
instance, defines a path \var{p} that consists of a straight line
from the point $(0, 0)$ to the point $(1, 1)$
\begin{verbatim}
from pyx import *
p = path.path(path.moveto(0, 0), path.lineto(1, 1))
\end{verbatim}
Equivalently, one can also use the predefined \class{path} subclass
\class{line} and write
\begin{verbatim}
p = path.line(0, 0, 1, 1)
\end{verbatim}

While already some geometrical operations can be performed with this
path (see next section), another \PyX{} object is needed in order to
actually being able to draw the path, namely an instance of the
\class{canvas} class. By convention, we use the name \var{c} for this
instance:
\begin{verbatim}
c = canvas.canvas()
\end{verbatim}
In order to draw the path on the canvas, we use the \method{stroke()} method
of the \class{canvas} class, i.e.,
\begin{verbatim}
c.stroke(p)
c.writeEPSfile("line")
\end{verbatim}
To complete the example, we have added a \method{writeEPSfile()} call,
which writes the contents of the canvas to the file \file{line.eps}.
Note that an extension \file{.eps} is added automatically, if not
already present in the given filename. Similarly, if you want to 
generate a PDF file instead, use
\begin{verbatim}
c.writePDFfile("line")
\end{verbatim}

As a second example, let us define a path which consists of more than 
one subpath:
\begin{verbatim}
cross = path.path(path.moveto(0, 0), path.rlineto(1, 1),
                  path.moveto(1, 0), path.rlineto(-1, 1))
\end{verbatim}
The first subpath is again a straight line from $(0, 0)$ to $(1, 1)$,
with the only difference that we now have used the \class{rlineto}
class, whose arguments count relative from the last point in the path.
The second \class{moveto} instance opens a new subpath starting at the
point $(1, 0)$ and ending at $(0, 1)$. Note that although both lines
intersect at the point $(1/2, 1/2)$, they count as disconnected
subpaths.  The general rule is that each occurrence of a \class{moveto}
instance opens a new subpath. This means that if one wants to draw a
rectangle, one should not use
\begin{verbatim}
rect1 = path.path(path.moveto(0, 0), path.lineto(0, 1),
                  path.moveto(0, 1), path.lineto(1, 1),
                  path.moveto(1, 1), path.lineto(1, 0),
                  path.moveto(1, 0), path.lineto(0, 0))
\end{verbatim}
which would construct a rectangle out of four disconnected
subpaths (see Fig.~\ref{fig:rects}a). In a better solution (see
Fig.~\ref{fig:rects}b), the pen is not lifted between the first and
the last point:
%
\begin{figure}
\centerline{\includegraphics{rects}}
\caption{Rectangle consisting of (a) four separate lines, (b) one open
  path, and (c) one closed path. (d) Filling a
  path always closes it automatically.}
\label{fig:rects}
\end{figure}
%
\begin{verbatim}
rect2 = path.path(path.moveto(0, 0), path.lineto(0, 1), 
                  path.lineto(1, 1), path.lineto(1, 0),
                  path.lineto(0, 0))
\end{verbatim}
However, as one can see in the lower left corner of
Fig.~\ref{fig:rects}b, the rectangle is still incomplete.  It needs to
be closed, which can  be done explicitly by using for the last straight
line of the rectangle (from the point $(0, 1)$ back to the origin at $(0, 0)$)
the \class{closepath} directive:
\begin{verbatim}
rect3 = path.path(path.moveto(0, 0), path.lineto(0, 1), 
                  path.lineto(1, 1), path.lineto(1, 0),
                  path.closepath())
\end{verbatim}
The \class{closepath} directive adds a straight line from the current
point to the first point of the current subpath and furthermore
\textit{closes} the sub path, i.e., it joins the beginning and the end
of the line segment. This results in the intended rectangle shown in
Fig.~\ref{fig:rects}c. Note that filling the path implicitly closes
every open subpath, as is shown for a single subpath in
Fig.~\ref{fig:rects}d), which results from
\begin{verbatim}
c.stroke(rect2, [deco.filled([color.grey(0.95)])])
\end{verbatim}
Here, we supply as second argument of the \method{stroke()} method a
list which in the present case only consists of a single element,
namely the so called decorator \class{deco.filled}. As it name says,
this decorator specifies that the path is not only being stroked but
also filled with the given color. More information about decorators,
styles and other attributes which can be passed as elements of the
list can be found in Sect.~\ref{graphics:attributes}.  More details on
the available path elements can be found in Sect.~\ref{path:pathitem}.

To conclude this section, we should not forget to mention that
rectangles are, of course, predefined in \PyX{}, so above we could
have as well written
\begin{verbatim}
rect2 = path.rect(0, 0, 1, 1)
\end{verbatim}
Here, the first two arguments specify the origin of the rectangle
while the second two arguments define its width and height,
respectively. For more details on the predefined paths, we
refer the reader to Sect.~\ref{path:predefined}.

\section{Path operations}

Often, one wants to perform geometrical operations with a path before
placing it on a canvas by stroking or filling it.  For instance, one
might want to intersect one path with another one, split the paths at
the intersection points, and then join the segments together in a new
way. \PyX{} supports such tasks by means of a number of path methods,
which we will introduce in the following.

Suppose you want to draw the radii to the intersection points of a
circle with a straight line. This task can be done using the following
code which results in Fig.~\ref{fig:radii}
\verbatiminput{radii.py}
\begin{figure}
\centerline{\includegraphics{radii}}
\caption{Example: Intersection of circle with line yielding two radii.}
\label{fig:radii}
\end{figure}
Here, the basic elements, a circle around the point $(0, 0)$ with
radius $2$ and a straight line, are defined. Then, passing the \var{line}, to
the \method{intersect()} method of \var{circle}, we obtain a tuple of
parameter values of the intersection points. The first element of the
tuple is a list of parameter values for the path whose
\method{intersect()} method has been called, the second element is the
corresponding list for the path passed as argument to this method. In
the present example, we only need one list of parameter values, namely
\var{isects_circle}.  Using the \method{at()} path method to obtain
the point corresponding to the parameter value, we draw the radii for
the different intersection points. 

Another powerful feature of \PyX{} is its ability to split paths at a
given set of parameters. For instance, in order to fill in the
previous example the segment of the circle delimited by the straight
line (cf.\ Fig.~\ref{fig:radii2}), one first has to construct a path
corresponding to the outline of this segment. The following code
snippet yields this \var{segment}
\begin{verbatim}
arc1, arc2 = circle.split(isects_circle)
if arc1.arclen() < arc2.arclen():
    arc = arc1
else:
    arc = arc2

isects_line.sort()
line1, line2, line3 = line.split(isects_line)

segment = line2 << arc
\end{verbatim}
\begin{figure}
\centerline{\includegraphics{radii2}}
\caption{Example: Intersection of circle with line yielding radii and
  circle segment.}
\label{fig:radii2}
\end{figure}
Here, we first split the circle using the \method{split()} method passing
the list of parameters obtained above. Since the circle is closed,
this yields two arc segments. We then use the \method{arclen()}, which
returns the arc length of the path, to find the shorter of the two
arcs. Before splitting the line, we have to take into account that
the \method{split()} method only accepts a sorted list of parameters.
Finally, we join the straight line and the arc segment. For
this, we make use of the \verb|<<| operator, which not only adds
the paths (which could be done using \samp{line2 + arc}), but also
joins the last subpath of \var{line2} and the first one of
\var{arc}. Thus, \var{segment} consists of only a single subpath
and filling works as expected.

An important issue when operating on paths is the parametrisation
used. Internally, \PyX{} uses a parametrisation which uses an interval
of length $1$ for each path element of a path. For instance, for a
simple straight line, the possible parameter values range from $0$ to
$1$, corresponding to the first and last point, respectively, of the
line. Appending another straight line, would extend this range to a
maximal value of $2$. 

However, the situation becomes more complicated if more complex
objects like a circle are involved. Then, one could be tempted to
assume that again the parameter value ranges from $0$ to $1$, because
the predefined circle consists just of one \class{arc} together with a
\class{closepath} element. However, this is not the case: the actual
range is much larger. The reason for this behaviour lies in the
internal path handling of \PyX: Before performing any non-trivial
geometrical operation with a path, it will automatically be converted
into an instance of the \class{normpath} class (see also
Sect.~\ref{path:normpath}). These so generated paths are already
separated in their subpaths and only contain straight lines and
B\'ezier curve segments. Thus, as is easily imaginable, they are much
simpler to deal with.

XXX explain normpathparams and things like p.begin(), p.end()-1,

A more geometrical way of accessing a point on the path is to use the
arc length of the path segment from the first point of the path to the
given point. Thus, all \PyX{} path methods that accept a parameter
value also allow the user to pass an arc length. For instance,
\begin{verbatim}
from math import pi

r = 2
pt1 = path.circle(0, 0, r).at(r*pi)
pt2 = path.circle(0, 0, r).at(r*3*pi/2)

c.stroke(path.path(path.moveto(*pt1), path.lineto(*pt2)))
\end{verbatim}
will draw a straight line from a point at angle $180$ degrees (in
radians $\pi$) to another point at angle $270$ degrees (in radians
$3\pi/2$) on a circle with radius $r=2$. Note however, that the mapping arc
length $\to$ point is in general discontinuous at the begin and the
end of a subpath, and thus \PyX{} does not guarantee any particular
result for this boundary case.

More information on the available path methods can be found 
in Sect.~\ref{path:path}. 

\section{Attributes: Styles and Decorations}

\label{graphics:attributes}

Attributes define properties of a given object when it is being used.
Typically, there are different kind of attributes which are usually
orthogonal to each other, while for one type of attribute, several
choices are possible. An example is the stroking of a path. There,
linewidth and linestyle are different kind of attributes. The linewidth
might be normal, thin, thick, etc, and the linestyle might be solid,
dashed etc.

Attributes always occur in lists passed as an optional keyword argument
to a method or a function. Usually, attributes are the first keyword
argument, so one can just pass the list without specifying the keyword.
Again, for the path example, a typical call looks like

\begin{verbatim}
c.stroke(path, [style.linewidth.Thick, style.linestyle.dashed])
\end{verbatim}

Here, we also encounter another feature of \PyX's attribute system. For
many attributes useful default values are stored as member variables of
the actual attribute. For instance, \code{style.linewidth.Thick} is
equivalent to \code{style.linewidth(0.04, type="w", unit="cm")}, that is
$0.04$ width cm (see Sect.~\ref{unit} for more information about
\PyX's unit system). 

Another important feature of \PyX{} attributes is what is call attributed
merging. A trivial example is the following:
\begin{verbatim}
# the following two lines are equivalent
c.stroke(path, [style.linewidth.Thick, style.linewidth.thin])
c.stroke(path, [style.linewidth.thin])
\end{verbatim}
Here, the \code{style.linewidth.thin} attribute overrides the preceding
\code{style.linewidth.Thick} declaration. This is especially important
in more complex cases where \PyX defines default attributes for a
certain operation. When calling the corresponding methods with an
attribute list, this list is appended to the list of defaults.
This way, the user can easily override certain defaults, while leaving
the other default values intact. In addition, every attribute kind
defines a special clear attribute, which allows to selectively delete
a default value. For path stroking this looks like
\begin{verbatim}
# the following two lines are equivalent
c.stroke(path, [style.linewidth.Thick, style.linewidth.clear])
c.stroke(path)
\end{verbatim}
The clear attribute is also provided by the base classes of 
the various styles. For instance, \class{style.strokestyle.clear}
clears all strokestyle subclasses and thus \class{style.linewidth} and
\class{style.linestyle}. Since all attributes derive from
\class{attr.attr}, you can remove all defaults using
\code{attr.clear}. An overview over the most important attribute typesprovided 
by PyX is given in the following table.
\medskip
\begin{center}
\begin{tabular}{l|l|p{0.3\linewidth}}
Attribute category & description & examples\\
\hline
\class{deco.deco} & decorator specifying the way the path is drawn & 
\class{deco.stroked}\newline
\class{deco.filled}\newline
\class{deco.arrow}
\\
\class{style.strokestyle} & style used for path stroking &
\class{style.linecap}\newline
\class{style.linejoin}\newline
\class{style.miterlimit}\newline 
\class{style.dash}\newline 
\class{style.linestyle}\newline
\class{style.linewidth}\newline
\class{color.color}
\\
\class{style.fillstyle} & style used for path filling &
\class{color.color}\newline\class{pattern.pattern}
\\
\class{deformer.deformer} &
operations changing the shape of the path
&
\class{deformer.cycloid}\newline
\class{deformer.smoothed}
\\
\class{text.textattr} & attributes used for typesetting &
\class{text.halign}\newline
\class{text.valign}\newline
\class{text.mathmode}\newline
\class{text.phantom}\newline
\class{text.size}\newline
\class{text.parbox}
\\
\class{trafo.trafo}
& transformations applied when drawing object
&
\class{trafo.mirror}\newline
\class{trafo.rotate}\newline
\class{trafo.scale}\newline
\class{trafo.slant}\newline
\class{trafo.translate}
\end{tabular}
\end{center}
\medskip

XXX specify which classes in the table are in fact instances

Note that operations usually allow for certain attribute categories
only. For example when stroking a path, text attributes are not
allowed, while stroke attributes and decorators are. Some attributes
might belong to several attribute categories like colours, which are
both, stroke and fill attributes.

Last, we discuss another important feature of \PyX's attribute system.
In order to allow the easy customisation of predefined attributes, it
is possible to create a modified attribute by calling of an attribute
instance, thereby specifying new parameters. A typical example is to
modify the way a path is stroked or filled by constructing appropriate
\class{deco.stroked} or \class{deco.filled} instances.
For instance, the code
\begin{verbatim}
c.stroke(path, [deco.filled([color.rgb.green])])
\end{verbatim}
draws a path filled in green with a black outline. Here,
\code{deco.filled} is already an instance which is modified to fill 
with the given color. Note that an equivalent version would
be
\begin{verbatim}
c.draw(path, [deco.stroked, deco.filled([color.rgb.green])])
\end{verbatim}
In particular, you can see that \class{deco.stroked} is already an
attribute instance, since otherwise you were not allowed to pass
it as a parameter to the draw method. Another example where 
the modification of a decorator is useful are arrows. For instance, the following
code draws an arrow head with a more acute angle (compared to the
default value of $45$ degrees):
\begin{verbatim}
c.stroke(path, [deco.earrow(angle=30)])
\end{verbatim}


XXX changeable attributes