Tkinter: Scaling items on a canvas
Solution 1
The Scaling of text is not "really" a scaling of text. It is (at least with the Canvas
-widget) a change of font.
From the documentation :
This method will not change the size of a text item, but may move it.
Edit:
This behaviour also applies to images (created with Tkinter.Canvas.create_image(...)
) and windows (created with Tkinter.Canvas.create_window(...)
). As the documentation defines scaling as
.scale(tagOrId, xOffset, yOffset, xScale, yScale)Please note the italic part for how scaling works.Scale all objects according to their distance from a point P=(xOffset, yOffset). The scale factors xScale and yScale are based on a value of 1.0, which means no scaling. Every point in the objects selected by tagOrId is moved so that its x distance from P is multiplied by xScale and its y distance is multiplied by yScale.
End Edit
If you want to perform an increase of font-size you need to configure all the text elements by yourself using the itemconfigure
method.
To Implement it in a very functional way you could use tags
to identify all text elements. Increasing can happen not only absolute but also relatively if you first get the fontsize (itemcget
) and then increase it depending on scale factor.
Solution 2
I know this post is old but I solved a similar question so I thought I would post a solution for the problem as well.
The idea is to add a tag to the text item and then reconfigure it each time the zoomer is called. Note, this solution does not change the linux zoom (zoomerP)
import tkinter as tk
import random
pressed = False
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.canvas = tk.Canvas(self, width=400, height=400, background="bisque")
self.xsb = tk.Scrollbar(self, orient="horizontal", command=self.canvas.xview)
self.ysb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
self.canvas.configure(scrollregion=(0,0,1000,1000))
self.xsb.grid(row=1, column=0, sticky="ew")
self.ysb.grid(row=0, column=1, sticky="ns")
self.canvas.grid(row=0, column=0, sticky="nsew")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.fontSize = 10
#Plot some rectangles
for n in range(50):
x0 = random.randint(0, 900)
y0 = random.randint(50, 900)
x1 = x0 + random.randint(50, 100)
y1 = y0 + random.randint(50,100)
color = ("red", "orange", "yellow", "green", "blue")[random.randint(0,4)]
self.canvas.create_rectangle(x0,y0,x1,y1, outline="black", fill=color, activefill="black", tags=n)
self.canvas.create_text(50,10, anchor="nw", text="Click and drag to move the canvas\nScroll to zoom.", font = ("Helvetica", int(self.fontSize)), tags="text")
# This is what enables using the mouse:
self.canvas.bind("<ButtonPress-1>", self.move_start)
self.canvas.bind("<B1-Motion>", self.move_move)
self.canvas.bind("<ButtonPress-2>", self.pressed2)
self.canvas.bind("<Motion>", self.move_move2)
#linux scroll
self.canvas.bind("<Button-4>", self.zoomerP)
self.canvas.bind("<Button-5>", self.zoomerM)
#windows scroll
self.canvas.bind("<MouseWheel>",self.zoomer)
# Hack to make zoom work on Windows
root.bind_all("<MouseWheel>",self.zoomer)
#move
def move_start(self, event):
self.canvas.scan_mark(event.x, event.y)
def move_move(self, event):
self.canvas.scan_dragto(event.x, event.y, gain=1)
#move
def pressed2(self, event):
global pressed
pressed = not pressed
self.canvas.scan_mark(event.x, event.y)
def move_move2(self, event):
if pressed:
self.canvas.scan_dragto(event.x, event.y, gain=1)
#windows zoom
def zoomer(self,event):
if (event.delta > 0):
self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
self.fontSize = self.fontSize * 1.1
elif (event.delta < 0):
self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
self.fontSize = self.fontSize * 0.9
self.canvas.configure(scrollregion = self.canvas.bbox("all"))
for child_widget in self.canvas.find_withtag("text"):
self.canvas.itemconfigure(child_widget, font=("Helvetica", int(self.fontSize)))
print(self.fontSize)
#linux zoom
def zoomerP(self,event):
self.canvas.scale("all", event.x, event.y, 1.1, 1.1)
self.canvas.configure(scrollregion = self.canvas.bbox("all"))
def zoomerM(self,event):
self.canvas.scale("all", event.x, event.y, 0.9, 0.9)
self.canvas.configure(scrollregion = self.canvas.bbox("all"))
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
HaggarTheHorrible
Updated on June 26, 2022Comments
-
HaggarTheHorrible almost 2 years
I was trying to understand how the scaling of canvas works.
Take for example, the following code. Why is that
canvas.scale("all", ...)
, which is bind to mouse wheel, is scaling all the rectangles and not the text as well.How can I achieve scaling of text along with the rectangles?
import Tkinter as tk import random pressed = False class Example(tk.Frame): def __init__(self, root): tk.Frame.__init__(self, root) self.canvas = tk.Canvas(self, width=400, height=400, background="bisque") self.xsb = tk.Scrollbar(self, orient="horizontal", command=self.canvas.xview) self.ysb = tk.Scrollbar(self, orient="vertical", command=self.canvas.yview) self.canvas.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set) self.canvas.configure(scrollregion=(0,0,1000,1000)) self.xsb.grid(row=1, column=0, sticky="ew") self.ysb.grid(row=0, column=1, sticky="ns") self.canvas.grid(row=0, column=0, sticky="nsew") self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(0, weight=1) #Plot some rectangles for n in range(50): x0 = random.randint(0, 900) y0 = random.randint(50, 900) x1 = x0 + random.randint(50, 100) y1 = y0 + random.randint(50,100) color = ("red", "orange", "yellow", "green", "blue")[random.randint(0,4)] self.canvas.create_rectangle(x0,y0,x1,y1, outline="black", fill=color, activefill="black", tags=n) self.canvas.create_text(50,10, anchor="nw", text="Click and drag to move the canvas\nScroll to zoom.") # This is what enables using the mouse: self.canvas.bind("<ButtonPress-1>", self.move_start) self.canvas.bind("<B1-Motion>", self.move_move) self.canvas.bind("<ButtonPress-2>", self.pressed2) self.canvas.bind("<Motion>", self.move_move2) #linux scroll self.canvas.bind("<Button-4>", self.zoomerP) self.canvas.bind("<Button-5>", self.zoomerM) #windows scroll self.canvas.bind("<MouseWheel>",self.zoomer) # Hack to make zoom work on Windows root.bind_all("<MouseWheel>",self.zoomer) #move def move_start(self, event): self.canvas.scan_mark(event.x, event.y) def move_move(self, event): self.canvas.scan_dragto(event.x, event.y, gain=1) #move def pressed2(self, event): global pressed pressed = not pressed self.canvas.scan_mark(event.x, event.y) def move_move2(self, event): if pressed: self.canvas.scan_dragto(event.x, event.y, gain=1) #windows zoom def zoomer(self,event): if (event.delta > 0): self.canvas.scale("all", event.x, event.y, 1.1, 1.1) elif (event.delta < 0): self.canvas.scale("all", event.x, event.y, 0.9, 0.9) self.canvas.configure(scrollregion = self.canvas.bbox("all")) #linux zoom def zoomerP(self,event): self.canvas.scale("all", event.x, event.y, 1.1, 1.1) self.canvas.configure(scrollregion = self.canvas.bbox("all")) def zoomerM(self,event): self.canvas.scale("all", event.x, event.y, 0.9, 0.9) self.canvas.configure(scrollregion = self.canvas.bbox("all")) if __name__ == "__main__": root = tk.Tk() Example(root).pack(fill="both", expand=True) root.mainloop()