2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2002-2003 Jörg Lehmann <joergl@users.sourceforge.net>
6 # Copyright (C) 2002-2003 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 import bbox
, path
, unit
, trafo
, helper
29 class BoxCrossError(Exception): pass
33 def __init__(self
, corners
=None, center
=None):
34 self
.corners
= corners
36 if self
.center
is None:
39 def _ensurecenter(self
):
40 if self
.center
is None:
42 for corn
in self
.corners
:
43 self
.center
= [self
.center
[0] + corn
[0], self
.center
[1] + corn
[1]]
44 self
.center
= [self
.center
[0]/len(self
.corners
), self
.center
[1]/len(self
.corners
)]
46 def path(self
, centerradius
=None, bezierradius
=None, beziersoftness
=1):
48 if centerradius
is not None and self
.center
is not None:
49 r
= unit
.topt(unit
.length(centerradius
, default_type
="v"))
50 pathels
.append(path
._arc
(self
.center
[0], self
.center
[1], r
, 0, 360))
51 pathels
.append(path
.closepath())
52 if bezierradius
is None:
53 pathels
.append(path
._moveto
(self
.corners
[0][0], self
.corners
[0][1]))
54 for x
, y
in self
.corners
[1:]:
55 pathels
.append(path
._lineto
(x
, y
))
56 pathels
.append(path
.closepath())
58 # curved box plotting by Michael Schindler
60 # make beziersoftnes a list of length l
61 if helper
.issequence(beziersoftness
):
62 if not (len(beziersoftness
) == l
): raise ValueError
64 beziersoftness
= [float(beziersoftness
)]*l
65 # make bezierradius a list (lenght l) of 2-tuples
66 if helper
.issequence(bezierradius
):
67 r
= list(bezierradius
)
68 if len(bezierradius
) == l
:
69 for oner
, i
in zip(r
, range(l
)):
70 if helper
.issequence(oner
):
72 r
[i
] = [unit
.topt(oner
[0]), unit
.topt(oner
[1])]
73 else: raise ValueError
75 r
[i
] = [unit
.topt(oner
)]*2
76 else: raise ValueError
78 r
= [[unit
.topt(bezierradius
)]*2]*l
82 n
= math
.sqrt(v
[0] * v
[0] + v
[1] * v
[1])
83 return v
[0] / n
, v
[1] / n
84 d1
= normed(self
.corners
[(i
- 1 + l
) % l
][0] - c
[0],
85 self
.corners
[(i
- 1 + l
) % l
][1] - c
[1])
86 d2
= normed(self
.corners
[(i
+ 1 + l
) % l
][0] - c
[0],
87 self
.corners
[(i
+ 1 + l
) % l
][1] - c
[1])
88 dc
= normed(d1
[0] + d2
[0], d1
[1] + d2
[1])
89 f
= 0.3192 * beziersoftness
[i
]
90 g
= (15.0 * f
+ math
.sqrt(-15.0*f
*f
+ 24.0*f
))/12.0
91 f1
= c
[0] + f
* d1
[0] * r
[i
][0], c
[1] + f
* d1
[1] * r
[i
][0]
92 f2
= c
[0] + f
* d2
[0] * r
[i
][1], c
[1] + f
* d2
[1] * r
[i
][1]
93 g1
= c
[0] + g
* d1
[0] * r
[i
][0], c
[1] + g
* d1
[1] * r
[i
][0]
94 g2
= c
[0] + g
* d2
[0] * r
[i
][1], c
[1] + g
* d2
[1] * r
[i
][1]
95 d1
= c
[0] + d1
[0] * r
[i
][0], c
[1] + d1
[1] * r
[i
][0]
96 d2
= c
[0] + d2
[0] * r
[i
][1], c
[1] + d2
[1] * r
[i
][1]
97 e
= 0.5 * (f1
[0] + f2
[0]), 0.5 * (f1
[1] + f2
[1])
99 pathels
.append(path
._lineto
(*d1
))
101 pathels
.append(path
._moveto
(*d1
))
102 pathels
.append(path
._curveto
(*(g1
+ f1
+ e
)))
103 pathels
.append(path
._curveto
(*(f2
+ g2
+ d2
)))
104 pathels
.append(path
.closepath())
105 return path
.path(*pathels
)
107 def transform(self
, *trafos
):
109 if self
.center
is not None:
110 self
.center
= trafo
._apply
(*self
.center
)
111 self
.corners
= [trafo
._apply
(*point
) for point
in self
.corners
]
113 def reltransform(self
, *trafos
):
114 if self
.center
is not None:
115 trafos
= ([trafo
._translate
(-self
.center
[0], -self
.center
[1])] +
117 [trafo
._translate
(self
.center
[0], self
.center
[1])])
118 self
.transform(*trafos
)
120 def successivepointnumbers(self
):
121 return [i
and (i
- 1, i
) or (len(self
.corners
) - 1, 0) for i
in range(len(self
.corners
))]
123 def successivepoints(self
):
124 return [(self
.corners
[i
], self
.corners
[j
]) for i
, j
in self
.successivepointnumbers()]
126 def _circlealignlinevector(self
, a
, dx
, dy
, ex
, ey
, fx
, fy
, epsilon
=1e-10):
128 gx
, gy
= ex
- fx
, ey
- fy
# direction vector
129 if gx
*gx
+ gy
*gy
< epsilon
: # zero line length
130 return None # no solution -> return None
131 rsplit
= (dx
*gx
+ dy
*gy
) * 1.0 / (gx
*gx
+ gy
*gy
)
132 bx
, by
= dx
- gx
* rsplit
, dy
- gy
* rsplit
133 if bx
*bx
+ by
*by
< epsilon
: # zero projection
134 return None # no solution -> return None
135 if bx
*gy
- by
*gx
< 0: # half space
136 return None # no solution -> return None
137 sfactor
= math
.sqrt((dx
*dx
+ dy
*dy
) / (bx
*bx
+ by
*by
))
138 bx
, by
= a
* bx
* sfactor
, a
* by
* sfactor
139 alpha
= ((bx
+cx
-ex
)*dy
- (by
+cy
-ey
)*dx
) * 1.0 / (gy
*dx
- gx
*dy
)
140 if alpha
> 0 - epsilon
and alpha
< 1 + epsilon
:
141 beta
= ((ex
-bx
-cx
)*gy
- (ey
-by
-cy
)*gx
) * 1.0 / (gx
*dy
- gy
*dx
)
142 return beta
*dx
, beta
*dy
# valid solution -> return align tuple
143 # crossing point at the line, but outside a valid range
145 return 0 # crossing point outside e
146 return 1 # crossing point outside f
148 def _linealignlinevector(self
, a
, dx
, dy
, ex
, ey
, fx
, fy
, epsilon
=1e-10):
150 gx
, gy
= ex
- fx
, ey
- fy
# direction vector
151 if gx
*gx
+ gy
*gy
< epsilon
: # zero line length
152 return None # no solution -> return None
153 if gy
*dx
- gx
*dy
< -epsilon
: # half space
154 return None # no solution -> return None
155 if dx
*gx
+ dy
*gy
> epsilon
or dx
*gx
+ dy
*gy
< -epsilon
:
156 if dx
*gx
+ dy
*gy
< 0: # angle bigger 90 degree
157 return 0 # use point e
158 return 1 # use point f
159 # a and g are othorgonal
160 alpha
= ((a
*dx
+cx
-ex
)*dy
- (a
*dy
+cy
-ey
)*dx
) * 1.0 / (gy
*dx
- gx
*dy
)
161 if alpha
> 0 - epsilon
and alpha
< 1 + epsilon
:
162 beta
= ((ex
-a
*dx
-cx
)*gy
- (ey
-a
*dy
-cy
)*gx
) * 1.0 / (gx
*dy
- gy
*dx
)
163 return beta
*dx
, beta
*dy
# valid solution -> return align tuple
164 # crossing point at the line, but outside a valid range
166 return 0 # crossing point outside e
167 return 1 # crossing point outside f
169 def _circlealignpointvector(self
, a
, dx
, dy
, px
, py
, epsilon
=1e-10):
173 p
= 2 * ((px
-cx
)*dx
+ (py
-cy
)*dy
)
174 q
= ((px
-cx
)*(px
-cx
) + (py
-cy
)*(py
-cy
) - a
*a
)
178 alpha
= - p
/ 2 + math
.sqrt(p
*p
/4 - q
)
180 alpha
= - p
/ 2 - math
.sqrt(p
*p
/4 - q
)
181 return alpha
*dx
, alpha
*dy
183 def _linealignpointvector(self
, a
, dx
, dy
, px
, py
):
185 beta
= (a
*dx
+cx
-px
)*dy
- (a
*dy
+cy
-py
)*dx
186 return a
*dx
- beta
*dy
- px
+ cx
, a
*dy
+ beta
*dx
- py
+ cy
188 def _alignvector(self
, a
, dx
, dy
, alignlinevector
, alignpointvector
):
189 n
= math
.sqrt(dx
* dx
+ dy
* dy
)
190 dx
, dy
= dx
/ n
, dy
/ n
191 linevectors
= map(lambda (p1
, p2
), self
=self
, a
=a
, dx
=dx
, dy
=dy
, alignlinevector
=alignlinevector
:
192 alignlinevector(a
, dx
, dy
, *(p1
+ p2
)), self
.successivepoints())
193 for linevector
in linevectors
:
194 if type(linevector
) is types
.TupleType
:
196 for i
, j
in self
.successivepointnumbers():
197 l1
, l2
= linevectors
[i
], linevectors
[j
]
198 if (l1
is not None or l2
is not None) and (l1
== 1 or l1
is None) and (l2
== 0 or l2
is None):
199 return alignpointvector(a
, dx
, dy
, *self
.successivepoints()[j
][0])
202 def _circlealignvector(self
, a
, dx
, dy
):
203 return self
._alignvector
(a
, dx
, dy
, self
._circlealignlinevector
, self
._circlealignpointvector
)
205 def _linealignvector(self
, a
, dx
, dy
):
206 return self
._alignvector
(a
, dx
, dy
, self
._linealignlinevector
, self
._linealignpointvector
)
208 def circlealignvector(self
, a
, dx
, dy
):
209 return map(unit
.t_pt
, self
._circlealignvector
(unit
.topt(a
), dx
, dy
))
211 def linealignvector(self
, a
, dx
, dy
):
212 return map(unit
.t_pt
, self
._linealignvector
(unit
.topt(a
), dx
, dy
))
214 def _circlealign(self
, *args
):
215 self
.transform(trafo
._translate
(*self
._circlealignvector
(*args
)))
218 def _linealign(self
, *args
):
219 self
.transform(trafo
._translate
(*self
._linealignvector
(*args
)))
222 def circlealign(self
, *args
):
223 self
.transform(trafo
.translate(*self
.circlealignvector(*args
)))
226 def linealign(self
, *args
):
227 self
.transform(trafo
.translate(*self
.linealignvector(*args
)))
230 def _extent(self
, dx
, dy
):
231 n
= math
.sqrt(dx
* dx
+ dy
* dy
)
232 dx
, dy
= dx
/ n
, dy
/ n
233 oldcenter
= self
.center
234 if self
.center
is None:
236 x1
, y1
= self
._linealignvector
(0, dx
, dy
)
237 x2
, y2
= self
._linealignvector
(0, -dx
, -dy
)
238 self
.center
= oldcenter
239 return (x1
-x2
)*dx
+ (y1
-y2
)*dy
241 def extent(self
, dx
, dy
):
242 return unit
.t_pt(self
._extent
(dx
, dy
))
244 def _pointdistance(self
, x
, y
):
246 for p1
, p2
in self
.successivepoints():
247 gx
, gy
= p2
[0] - p1
[0], p2
[1] - p1
[1]
248 if gx
* gx
+ gy
* gy
< 1e-10:
249 dx
, dy
= p1
[0] - x
, p1
[1] - y
251 a
= (gx
* (x
- p1
[0]) + gy
* (y
- p1
[1])) / (gx
* gx
+ gy
* gy
)
253 dx
, dy
= p1
[0] - x
, p1
[1] - y
255 dx
, dy
= p2
[0] - x
, p2
[1] - y
257 dx
, dy
= x
- p1
[0] - a
* gx
, y
- p1
[1] - a
* gy
258 new
= math
.sqrt(dx
* dx
+ dy
* dy
)
259 if result
is None or new
< result
:
263 def pointdistance(self
, x
, y
):
264 return unit
.t_pt(self
._pointdistance
(unit
.topt(x
), unit
.topt(y
)))
266 def _boxdistance(self
, other
, epsilon
=1e-10):
267 # XXX: boxes crossing and distance calculation is O(N^2)
268 for p1
, p2
in self
.successivepoints():
269 for p3
, p4
in other
.successivepoints():
270 a
= (p4
[1] - p3
[1]) * (p3
[0] - p1
[0]) - (p4
[0] - p3
[0]) * (p3
[1] - p1
[1])
271 b
= (p2
[1] - p1
[1]) * (p3
[0] - p1
[0]) - (p2
[0] - p1
[0]) * (p3
[1] - p1
[1])
272 c
= (p2
[0] - p1
[0]) * (p4
[1] - p3
[1]) - (p2
[1] - p1
[1]) * (p4
[0] - p3
[0])
273 if (abs(c
) > 1e-10 and
274 a
/ c
> -epsilon
and a
/ c
< 1 + epsilon
and
275 b
/ c
> -epsilon
and b
/ c
< 1 + epsilon
):
278 for x
, y
in other
.corners
:
279 new
= self
._pointdistance
(x
, y
)
280 if result
is None or new
< result
:
282 for x
, y
in self
.corners
:
283 new
= other
._pointdistance
(x
, y
)
284 if result
is None or new
< result
:
288 def boxdistance(self
, other
):
289 return unit
.t_pt(self
._boxdistance
(other
))
292 return bbox
._bbox
(min([x
[0] for x
in self
.corners
]),
293 min([x
[1] for x
in self
.corners
]),
294 max([x
[0] for x
in self
.corners
]),
295 max([x
[1] for x
in self
.corners
]))
298 def _genericalignequal(method
, polygons
, a
, dx
, dy
):
301 v
= method(p
, a
, dx
, dy
)
302 if vec
is None or vec
[0] * dx
+ vec
[1] * dy
< v
[0] * dx
+ v
[1] * dy
:
305 p
.transform(trafo
._translate
(*vec
))
308 def _circlealignequal(polygons
, *args
):
309 _genericalignequal(_polygon
._circlealignvector
, polygons
, *args
)
311 def _linealignequal(polygons
, *args
):
312 _genericalignequal(_polygon
._linealignvector
, polygons
, *args
)
314 def circlealignequal(polygons
, a
, *args
):
315 _circlealignequal(polygons
, unit
.topt(a
), *args
)
317 def linealignequal(polygons
, a
, *args
):
318 _linealignequal(polygons
, unit
.topt(a
), *args
)
321 def _tile(polygons
, a
, dx
, dy
):
322 maxextent
= polygons
[0]._extent
(dx
, dy
)
323 for p
in polygons
[1:]:
324 extent
= p
._extent
(dx
, dy
)
325 if extent
> maxextent
:
329 p
.transform(trafo
._translate
(d
*dx
, d
*dy
))
333 def tile(polygons
, a
, dx
, dy
):
334 _tile(polygons
, unit
.topt(a
), dx
, dy
)
337 class polygon(_polygon
):
339 def __init__(self
, corners
=None, center
=None, **args
):
340 corners
= [[unit
.topt(x
) for x
in corner
] for corner
in corners
]
341 if center
is not None:
342 center
= map(unit
.topt
, center
)
343 _polygon
.__init
__(self
, corners
=corners
, center
=center
, **args
)
346 class _rect(_polygon
):
348 def __init__(self
, x
, y
, width
, height
, relcenter
=(0, 0), abscenter
=(0, 0),
349 corners
=helper
.nodefault
, center
=helper
.nodefault
, **args
):
350 if corners
!= helper
.nodefault
or center
!= helper
.nodefault
:
352 _polygon
.__init
__(self
, corners
=((x
, y
),
354 (x
+ width
, y
+ height
),
356 center
=(x
+ relcenter
[0] * width
+ abscenter
[0],
357 y
+ relcenter
[1] * height
+ abscenter
[1]),
363 def __init__(self
, x
, y
, width
, height
, relcenter
=(0, 0), abscenter
=(0, 0), **args
):
364 _rect
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), unit
.topt(width
), unit
.topt(height
),
365 relcenter
=relcenter
, abscenter
=(unit
.topt(abscenter
[0]), unit
.topt(abscenter
[1])), **args
)