Tkinter Tricks: Using Complex Numbers to Rotate Canvas Items
Python’s complex number (dead link) type can be used to represent coordinates in a 2-dimensional graphics system. Simply store the X coordinate as the real part, and the Y coordinate as the imaginary part.
Complex numbers provide a compact way of storing 2D coordinates, but they also make it really easy to manipulate coordinate values. For example, to translate a coordinate, just add the offset (represented as complex number). And to rotate a coordinate, all you have to do is to multiply the coordinate with a complex number representing the angle.
First, you need a complex number that represents the angle. If you have the angle, you can use the cmath.exp function:
import cmath, math cangle = cmath.exp(angle*1j) # angle in radians
Alternatively, if you have two deltas (dx, dy), you can calculate the complex angle directly from the deltas:
cangle = complex(dx, dy) cangle = cangle / abs(cangle)
(the cangle complex value can be seen as a unit vector, with one end at (0, 0)).
To rotate a coordinate around (0, 0), convert the coordinate to a complex number, and multiply it with the complex angle:
for x, y in coordinates: v = cangle * complex(x, y) print v.real, v.imag
To rotate around an arbitrary point, create a complex number corresponding to that point, subtract that number before you multiply with the angle, and add it back afterwards:
center = complex(xoffset, yoffset) for x, y in coordinates: v = cangle * (complex(x, y) - center) + center print v.real, v.imag
The following Tkinter example allows the user to use the mouse to rotate an item around a given center point.
from Tkinter import * import math c = Canvas(width=200, height=200) c.pack() # a square xy = [(50, 50), (150, 50), (150, 150), (50, 150)] polygon_item = c.create_polygon(xy) center = 100, 100 def getangle(event): dx = c.canvasx(event.x) - center[0] dy = c.canvasy(event.y) - center[1] try: return complex(dx, dy) / abs(complex(dx, dy)) except ZeroDivisionError: return 0.0 # cannot determine angle def press(event): # calculate angle at start point global start start = getangle(event) def motion(event): # calculate current angle relative to initial angle global start angle = getangle(event) / start offset = complex(center[0], center[1]) newxy = [] for x, y in xy: v = angle * (complex(x, y) - offset) + offset newxy.append(v.real) newxy.append(v.imag) c.coords(polygon_item, *newxy) c.bind("<Button-1>", press) c.bind("<B1-Motion>", motion) mainloop()
Note that for the Tkinter canvas, this only works properly for lines and polygons (all other items are always aligned with the canvas coordinate system). To rotate a rectangle, for example, you must first convert it to a polygon.