0

I've updated the initial script with a modified version of Bryan-Oakley's answer. It now has 2 canvas, 1 with the draggable rectangle, and 1 with the plot. I would like the rectangle to be dragged along the x-axis on the plot if that is possible?

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

Any help is greatly appreciated.

Saad
  • 3,340
  • 2
  • 10
  • 32
  • Do you want to integrate these matplotlib plots inside a Tkinter GUI or to recreate the same kind of draggable lines in Tkinter without using matplotlib (both are possible)? – j_4321 Jun 24 '20 at 13:49
  • I would like to integrate a matplotlib plot inside a Tkinter gui. – Thomas Fuglsang Jun 24 '20 at 18:28
  • I´ve updated the initial script with a modified version of Bryan-Oakley answer here (https://stackoverflow.com/a/6789351/7432). It now has 2 canvas. 1 with the draggable rectangle and 1 with the plot. I would like the rectangle to be dragged along the x-axis on the plot @j_4321 – Thomas Fuglsang Jun 24 '20 at 18:39

2 Answers2

1

The issue with your code is that you create two canvases, one for the matplotlib figure and one for the draggable rectangle while you want both on the same.

To solve this, I merged the current code of the question with the one before the edit, so the whole matplotlib figure is now embedded in the Tkinter window. The key modification I made to the DraggableLine class is that it now takes the canvas as an argument.

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()
j_4321
  • 15,431
  • 3
  • 34
  • 61
0

I'm not sure how to do it with tkinter or pyQt but I know how to make something like this with PyGame which is another GUI solution for python. I hope this example helps you:

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

And when you move the line around, the console prints the location that it is in.