Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help with Zoom Python
#1
Here is my code, and I want when I scale my grid, it will scale where the cursor is and smoothly move to the middle of the screen. Similar to how AutoCAD does it. Basically I want the cursor to be my zoom focus. For now it scales by making the center of the canvas the zoom focus.

import tkinter as tk
import json


def create_grid(canvas, grid_size, color="black"):
    """Draws a grid on the canvas."""
    canvas.delete("grid")
    max_width = max_columns * grid_size
    max_height = max_rows * grid_size

    for x in range(0, max_width + 1, grid_size):
        canvas.create_line(x, 0, x, max_height, fill=color, dash=(1, 3), tags="grid")
    for y in range(0, max_height + 1, grid_size):
        canvas.create_line(0, y, max_width, y, fill=color, dash=(1, 3), tags="grid")

    canvas.config(scrollregion=(0, 0, max_width, max_height))  # Limit scrolling


def add_text_to_grid(canvas, row, col, text, font=("Arial", 12)):
    """Adds a text element aligned to the grid, within limits."""
    if 0 <= row < max_rows and 0 <= col < max_columns:
        x = col * grid_size
        y = row * grid_size
        item = canvas.create_text(x, y, text=text, font=font, tags="grid_text", anchor="center")
        text_items.append({"item": item, "row": row, "col": col})
    else:
        print("Cannot place text outside the grid limits.")


def update_text_positions():
    """Updates text positions when the grid size changes."""
    for text in text_items:
        row, col = text["row"], text["col"]
        if 0 <= row < max_rows and 0 <= col < max_columns:
            x = col * grid_size
            y = row * grid_size
            canvas.coords(text["item"], x, y)


def on_mouse_wheel(event):
    """Zooms in or out with the mouse wheel, centered at the cursor."""
    global grid_size, offset_x, offset_y

    # Get cursor position relative to canvas
    cursor_x = canvas.canvasx(event.x)
    cursor_y = canvas.canvasy(event.y)

    # Calculate zoom factor
    if event.delta > 0:  # Scroll up
        new_grid_size = max(grid_size - 5, 10)  # Minimum grid size is 10
    else:  # Scroll down
        new_grid_size = min(grid_size + 5, 191)  # Maximum grid size is 191

    # Zoom adjustment ratio
    if new_grid_size != grid_size:
        zoom_ratio = new_grid_size / grid_size

        # Adjust offsets to keep the cursor centered
        offset_x = cursor_x - (cursor_x - offset_x) * zoom_ratio
        offset_y = cursor_y - (cursor_y - offset_y) * zoom_ratio

        grid_size = new_grid_size

    create_grid(canvas, grid_size)
    update_text_positions()

    # Update canvas view
    canvas.xview_moveto(offset_x / (max_columns * grid_size))
    canvas.yview_moveto(offset_y / (max_rows * grid_size))


def on_mouse_drag_start(event):
    """Start dragging the canvas."""
    global start_x, start_y
    start_x = event.x
    start_y = event.y


def on_mouse_drag(event):
    """Handle dragging the canvas."""
    global start_x, start_y, offset_x, offset_y

    dx = start_x - event.x
    dy = start_y - event.y

    # Update offsets with boundaries
    max_width = max_columns * grid_size - canvas.winfo_width()
    max_height = max_rows * grid_size - canvas.winfo_height()

    offset_x = max(0, min(offset_x + dx, max_width))
    offset_y = max(0, min(offset_y + dy, max_height))

    canvas.xview_moveto(offset_x / (max_columns * grid_size))
    canvas.yview_moveto(offset_y / (max_rows * grid_size))

    # Update starting position for smooth dragging
    start_x = event.x
    start_y = event.y


def save_settings():
    """Saves the grid size to a settings file."""
    data = {"grid_size": grid_size}
    with open("settings.json", "w") as f:
        json.dump(data, f)


def load_settings():
    """Loads the grid size from a settings file."""
    try:
        with open("settings.json", "r") as f:
            data = json.load(f)
            return data.get("grid_size", 100)  # Default grid size is 100
    except FileNotFoundError:
        return 100


# Initialize variables
grid_size = load_settings()
text_items = []
start_x = start_y = 0
offset_x = offset_y = 0

# Define maximum rows and columns for the grid
max_rows = 101  # Maximum number of rows
max_columns = 192  # Maximum number of columns

# Create the main window and canvas
window = tk.Tk()
window.title("Zoomable Grid with Cursor-Centered Zoom")

# Set the window to start maximized
window.state("zoomed")

canvas = tk.Canvas(window, bg="white")
canvas.pack(fill="both", expand=True)

# Create grid and add elements
create_grid(canvas, grid_size)
add_text_to_grid(canvas, 4, 5, "Center")  # Aligned to row 4, column 5
add_text_to_grid(canvas, 2, 1, "Random")  # Aligned to row 2, column 1

# Bind events
window.protocol("WM_DELETE_WINDOW", lambda: (save_settings(), window.destroy()))
canvas.bind("<Control-MouseWheel>", on_mouse_wheel)
canvas.bind("<ButtonPress-1>", on_mouse_drag_start)
canvas.bind("<B1-Motion>", on_mouse_drag)

window.mainloop()
deanhystad write Jan-09-2025, 08:50 PM:
Please post all code, output and errors (it it's entirety) between their respective tags. Refer to BBCode help topic on how to post. Use the "Preview Post" button to make sure the code is presented as you expect before hitting the "Post Reply/Thread" button.
Reply
#2
        # Adjust offsets to keep the cursor centered
        offset_x = offset_x + cursor_x * (zoom_ratio - 1)
        offset_y = offset_y + cursor_y * (zoom_ratio - 1)
Note that this stops working when shrinking the grid moves a corner of the inside the view window.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Spyder console zoom in not working? Dionysis 2 1,576 Feb-06-2024, 03:31 PM
Last Post: paul18fr
  How to zoom on/follow MOUTH in FACE? (OPENCV Face Detection) buzzdarkyear 2 2,587 Jan-12-2022, 12:31 AM
Last Post: buzzdarkyear
  Functions to make an image zoom in until a response is made sawilliams 1 2,706 Aug-02-2018, 08:06 AM
Last Post: Larz60+

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020