"Interactive" GUI 中的图形

"Interactive" figure in a GUI

我已经用 Bryan-Oakley 的 answer 的修改版本更新了初始脚本。它现在有 2 个 canvas,1 个带有可拖动矩形,1 个带有绘图。如果可能的话,我希望矩形沿着绘图上的 x 轴拖动?

import tkinter as tk     # python 3
# import Tkinter as tk   # python 2
import numpy as np
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
# Implement the default Matplotlib key bindings.
from matplotlib.backend_bases import key_press_handler
from matplotlib.figure import Figure


class Example(tk.Frame):
"""Illustrate how to drag items on a Tkinter canvas"""

def __init__(self, parent):
    tk.Frame.__init__(self, parent)


    fig = Figure(figsize=(5, 4), dpi=100)
    t = np.arange(0, 3, .01)
    fig.add_subplot(111).plot(t, 2 * np.sin(2 * np.pi * t))
    
    #create a canvas
    self.canvas = tk.Canvas(width=200, height=300)
    self.canvas.pack(fill="both", expand=True)
    
    canvas = FigureCanvasTkAgg(fig, master=root)  # A tk.DrawingArea.
    canvas.draw()
    canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)

    # this data is used to keep track of an
    # item being dragged
    self._drag_data = {"x": 0, "y": 0, "item": None}

    # create a movable object
    self.create_token(100, 150, "black")

    # add bindings for clicking, dragging and releasing over
    # any object with the "token" tag
    self.canvas.tag_bind("token", "<ButtonPress-1>", self.drag_start)
    self.canvas.tag_bind("token", "<ButtonRelease-1>", self.drag_stop)
    self.canvas.tag_bind("token", "<B1-Motion>", self.drag)

def create_token(self, x, y, color):
    """Create a token at the given coordinate in the given color"""
    self.canvas.create_rectangle(
        x - 5,
        y - 100,
        x + 5,
        y + 100,
        outline=color,
        fill=color,
        tags=("token",),
    )

def drag_start(self, event):
    """Begining drag of an object"""
    # record the item and its location
    self._drag_data["item"] = self.canvas.find_closest(event.x, event.y)[0]
    self._drag_data["x"] = event.x
    self._drag_data["y"] = event.y

def drag_stop(self, event):
    """End drag of an object"""
    # reset the drag information
    self._drag_data["item"] = None
    self._drag_data["x"] = 0
    self._drag_data["y"] = 0

def drag(self, event):
    """Handle dragging of an object"""
    # compute how much the mouse has moved
    delta_x = event.x - self._drag_data["x"]
    delta_y = 0
    # move the object the appropriate amount
    self.canvas.move(self._drag_data["item"], delta_x, delta_y)
    # record the new position
    self._drag_data["x"] = event.x
    self._drag_data["y"] = event.y

if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

非常感谢任何帮助。

我不确定如何用 tkinter 或 pyQt 来做,但我知道如何用 PyGame 做这样的事情,这是 python 的另一种 GUI 解决方案。希望这个例子对你有帮助:

import pygame

SCREEN_WIDTH = 430
SCREEN_HEIGHT = 410

WHITE = (255, 255, 255)
RED   = (255,   0,   0)

FPS = 30

pygame.init()

screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))

rectangle = pygame.rect.Rect(176, 134, 17, 170)
rectangle_draging = False

clock = pygame.time.Clock()

running = True

while running:

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:            
                if rectangle.collidepoint(event.pos):
                    rectangle_draging = True
                    mouse_x, mouse_y = event.pos
                    offset_x = rectangle.x - mouse_x
                    offset_y = rectangle.y - mouse_y

        elif event.type == pygame.MOUSEBUTTONUP:
            if event.button == 1:            
                rectangle_draging = False
                print("Line is at: (", rectangle.x, ";", rectangle.y,")")

        elif event.type == pygame.MOUSEMOTION:
            if rectangle_draging:
                mouse_x, mouse_y = event.pos
                rectangle.x = mouse_x + offset_x
                rectangle.y = mouse_y + offset_y

    screen.fill(WHITE)

    pygame.draw.rect(screen, RED, rectangle)

    pygame.display.flip()

    clock.tick(FPS)

pygame.quit()

当你四处移动线条时,控制台会打印它所在的位置。

您的代码的问题是您创建了两个 canvases,一个用于 matplotlib 图,一个用于可拖动的矩形,而您希望两者相同。

为了解决这个问题,我将问题的当前代码与编辑前的代码合并,所以整个 matplotlib 图现在嵌入到 Tkinter 中 window。我对 DraggableLine class 所做的关键修改是它现在将 canvas 作为参数。

import tkinter as tk     # python 3
# import Tkinter as tk   # python 2
import numpy as np
import matplotlib.lines as lines
from matplotlib.backends.backend_tkagg import (
    FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure

class DraggableLine:
    def __init__(self, ax, canvas, XorY):
        self.ax = ax
        self.c = canvas

        self.XorY = XorY

        x = [XorY, XorY]
        y = [-2, 2]
        self.line = lines.Line2D(x, y, color='red', picker=5)
        self.ax.add_line(self.line)
        self.c.draw_idle()
        self.sid = self.c.mpl_connect('pick_event', self.clickonline)

    def clickonline(self, event):
        if event.artist == self.line:
            self.follower = self.c.mpl_connect("motion_notify_event", self.followmouse)
            self.releaser = self.c.mpl_connect("button_press_event", self.releaseonclick)

    def followmouse(self, event):
        self.line.set_xdata([event.xdata, event.xdata])
        self.c.draw_idle()

    def releaseonclick(self, event):
        self.XorY = self.line.get_xdata()[0]

        self.c.mpl_disconnect(self.releaser)
        self.c.mpl_disconnect(self.follower)



class Example(tk.Frame):
    """Illustrate how to drag items on a Tkinter canvas"""

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)


        fig = Figure(figsize=(5, 4), dpi=100)
        t = np.arange(0, 3, .01)
        ax = fig.add_subplot(111)
        ax.plot(t, 2 * np.sin(2 * np.pi * t))

        # create the canvas
        canvas = FigureCanvasTkAgg(fig, master=root)  # A tk.DrawingArea.
        canvas.draw()
        canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)

        self.line = DraggableLine(ax, canvas, 0.1)


if __name__ == "__main__":
    root = tk.Tk()
    Example(root).pack(fill="both", expand=True)
    root.mainloop()