axis drawing after data drawing (better solution needed)
[PyX/mjg.git] / pyx / trafo.py
blobd40dcda1a4623a4286c5431bb9633ae9ea92a846
1 #!/usr/bin/env python
4 # Copyright (C) 2002 Jörg Lehmann <joergl@users.sourceforge.net>
5 # Copyright (C) 2002 André Wobst <wobsta@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 import math
24 import base, bbox, unit
26 # TODO:
27 # - switch to affine space description (i.e. represent transformation by
28 # 3x3 matrix (cf. PLRM Sect. 4.3.3)? Cooler!
30 # some helper routines
32 def _rmatrix(angle):
33 phi = math.pi*angle/180.0
35 return (( math.cos(phi), math.sin(phi)),
36 (-math.sin(phi), math.cos(phi)))
38 def _rvector(angle, x, y):
39 phi = math.pi*angle/180.0
41 return ((1-math.cos(phi))*x + math.sin(phi) *y,
42 -math.sin(phi) *x + (1-math.cos(phi))*y)
45 def _mmatrix(angle):
46 phi = math.pi*angle/180.0
48 return ( (math.cos(phi)*math.cos(phi)-math.sin(phi)*math.sin(phi),
49 -2*math.sin(phi)*math.cos(phi) ),
50 (-2*math.sin(phi)*math.cos(phi),
51 math.sin(phi)*math.sin(phi)-math.cos(phi)*math.cos(phi) ) )
53 def _det(matrix):
54 return matrix[0][0]*matrix[1][1] - matrix[0][1]*matrix[1][0]
56 # Exception
58 class UndefinedResultError(ArithmeticError):
59 pass
61 # trafo: affine transformations
63 class _trafo(base.PSOp):
65 """affine transformation (coordinates in constructor in pts)
67 Note that though the coordinates in the constructor are in
68 pts (which is useful for internal purposes), all other
69 methods only accept units in the standard user notation.
71 """
73 def __init__(self, matrix=((1,0),(0,1)), vector=(0,0)):
74 if _det(matrix)==0:
75 raise UndefinedResultError, "transformation matrix must not be singular"
76 else:
77 self.matrix=matrix
78 self.vector = vector
80 def __mul__(self, other):
81 if isinstance(other, _trafo):
82 matrix = ( ( self.matrix[0][0]*other.matrix[0][0] +
83 self.matrix[0][1]*other.matrix[1][0],
84 self.matrix[0][0]*other.matrix[0][1] +
85 self.matrix[0][1]*other.matrix[1][1] ),
86 ( self.matrix[1][0]*other.matrix[0][0] +
87 self.matrix[1][1]*other.matrix[1][0],
88 self.matrix[1][0]*other.matrix[0][1] +
89 self.matrix[1][1]*other.matrix[1][1] )
92 vector = ( self.matrix[0][0]*other.vector[0] +
93 self.matrix[1][0]*other.vector[1] +
94 self.vector[0],
95 self.matrix[0][1]*other.vector[0] +
96 self.matrix[1][1]*other.vector[1] +
97 self.vector[1] )
99 return _trafo(matrix=matrix, vector=vector)
100 else:
101 raise NotImplementedError, "can only multiply two transformations"
103 def __str__(self):
104 return "[%f %f %f %f %f %f] concat\n" % \
105 ( self.matrix[0][0], self.matrix[0][1],
106 self.matrix[1][0], self.matrix[1][1],
107 self.vector[0], self.vector[1] )
109 def write(self, file):
110 file.write("[%f %f %f %f %f %f] concat\n" % \
111 ( self.matrix[0][0], self.matrix[0][1],
112 self.matrix[1][0], self.matrix[1][1],
113 self.vector[0], self.vector[1] ) )
115 def bbox(self):
116 return bbox.bbox()
118 def _apply(self, x, y):
119 """apply transformation to point (x,y) (coordinates in pts)"""
120 return (self.matrix[0][0]*x +
121 self.matrix[1][0]*y +
122 self.vector[0],
123 self.matrix[0][1]*x +
124 self.matrix[1][1]*y +
125 self.vector[1])
127 def apply(self, x, y):
128 # before the transformation, we first have to convert to
129 # our internal unit (i.e. pts)
130 tx, ty = self._apply(unit.topt(x), unit.topt(y))
132 # the end result can be converted back to general lengths
133 return (unit.t_pt(tx), unit.t_pt(ty))
135 def inverse(self):
136 det = _det(self.matrix) # shouldn't be zero, but
137 try:
138 matrix = ( ( self.matrix[1][1]/det, -self.matrix[0][1]/det),
139 (-self.matrix[1][0]/det, self.matrix[0][0]/det)
141 except ZeroDivisionError:
142 raise UndefinedResultError, "transformation matrix must not be singular"
143 return _trafo(matrix=matrix) * \
144 _trafo(vector=(-self.vector[0], -self.vector[1]))
146 def mirror(self, angle):
147 return mirroring(angle)*self
149 def _rotate(self, angle, x=None, y=None):
150 return _rotation(angle, x, y)*self
152 def rotate(self, angle, x=None, y=None):
153 return rotation(angle, x, y)*self
155 def _scale(self, sx, sy=None, x=None, y=None):
156 return _scaling(sx, sy, x, y)*self
158 def scale(self, sx, sy=None, x=None, y=None):
159 return scaling(sx, sy, x, y)*self
161 def _translate(self, x, y):
162 return _translation(x,y)*self
164 def translate(self, x, y):
165 return translation(x, y)*self
168 class trafo(_trafo):
170 """affine transformation"""
172 def __init__(self, matrix=((1,0),(0,1)), vector=(0,0)):
173 _trafo.__init__(self,
174 matrix,
175 (unit.topt(vector[0]), unit.topt(vector[1])))
179 # some standard transformations
182 class mirroring(trafo):
183 def __init__(self,angle=0):
184 trafo.__init__(self, matrix=_mmatrix(angle))
187 class _rotation(_trafo):
188 def __init__(self, angle, x=None, y=None):
189 vector = 0, 0
190 if x is not None or y is not None:
191 if x is None or y is None:
192 raise (UndefinedResultError,
193 "either specify both x and y or none of them")
194 vector=_rvector(angle, x, y)
196 _trafo.__init__(self,
197 matrix=_rmatrix(angle),
198 vector=vector)
201 class rotation(_trafo):
202 def __init__(self, angle, x=None, y=None):
203 vector = 0, 0
204 if x is not None or y is not None:
205 if x is None or y is None:
206 raise (UndefinedResultError,
207 "either specify both x and y or none of them")
208 vector=_rvector(angle, unit.topt(x), unit.topt(y))
210 _trafo.__init__(self,
211 matrix=_rmatrix(angle),
212 vector=vector)
215 class _scaling(_trafo):
216 def __init__(self, sx, sy=None, x=None, y=None):
217 sy=sy or sx
218 if not sx or not sy:
219 raise (UndefinedResultError,
220 "one scaling factor is 0")
221 vector = 0, 0
222 if x is not None or y is not None:
223 if x is None or y is None:
224 raise (UndefinedResultError,
225 "either specify both x and y or none of them")
226 vector=(1-sx)*x, (1-sy)*y
228 _trafo.__init__(self, matrix=((sx,0),(0,sy)), vector=vector)
231 class scaling(trafo):
232 def __init__(self, sx, sy=None, x=None, y=None):
233 sy=sy or sx
234 if not sx or not sy:
235 raise (UndefinedResultError,
236 "one scaling factor is 0")
237 vector = 0, 0
238 if x is not None or y is not None:
239 if x is None or y is None:
240 raise (UndefinedResultError,
241 "either specify both x and y or none of them")
242 vector=(1-sx)*x, (1-sy)*y
244 trafo.__init__(self, matrix=((sx,0),(0,sy)), vector=vector)
247 class _translation(_trafo):
248 def __init__(self, x, y):
249 _trafo.__init__(self, vector=(x, y))
252 class translation(trafo):
253 def __init__(self, x, y):
254 trafo.__init__(self, vector=(x, y))