Tkinter: Scaling items on a canvas

11,762

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)

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.

Please note the italic part for how scaling works.

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()
Share:
11,762
HaggarTheHorrible
Author by

HaggarTheHorrible

Updated on June 26, 2022

Comments

  • HaggarTheHorrible
    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()