0

I am developing an application which lets user Zoom a part of the graph based on their selection. I am able to get the initial x, y coordinates(x0, y0) and also the final x, y coordinates(x1, y1). But completely clueless why the selection area is not showing up.

from Tkinter import *
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

root = Tk()
graph = Figure(figsize=(5,4), dpi=100)
ax = graph.add_subplot(111)
plot = ax.plot([1,2,3,4],[5,6,2,8])
canvas = FigureCanvasTkAgg(graph, master=root)
canvas.show()
canvas.get_tk_widget().grid(column=2, row=1, rowspan=2, sticky=(N, S, E, W))

class Zoom(object):
    def __init__(self):
        self.graph = Figure(figsize=(5,4), dpi=100)
        self.ax = graph.add_subplot(111)
        self.rect = ax.patch
        self.rect.set_facecolor('green')
        self.ax.plot([1,2,3,4],[5,6,2,8])
        self.is_pressed = False
        self.x0 = None
        self.y0 = None
        self.x1 = None
        self.y1 = None
        self.aid = graph.canvas.mpl_connect('button_press_event', self.on_press)
        self.bid = graph.canvas.mpl_connect('button_release_event', self.on_release)
        self.cid = graph.canvas.mpl_connect('motion_notify_event', self.on_motion)

    def on_press(self, event):
        print 'press'
        self.is_pressed = True
        self.x0 = event.xdata
        self.y0 = event.ydata
        print(self.x1, self.x0)
        print(self.y1, self.y0)

    def on_motion(self, event):
        if self.is_pressed is True:
            print 'panning'
            self.x1 = event.xdata
            self.y1 = event.ydata
            print(self.x1, self.x0)
            print(self.y1, self.y0)
            self.rect.set_width(self.x1 - self.x0)
            self.rect.set_height(self.y1 - self.y0)
            self.rect.set_xy((self.x0, self.y0))
            self.rect.set_linestyle('dashed')
            self.ax.figure.canvas.draw()

    def on_release(self, event):
        print 'release'
        self.is_pressed = False
        self.x1 = event.xdata
        self.y1 = event.ydata
        print(self.x1, self.x0)
        print(self.y1, self.y0)
        self.rect.set_width(self.x1 - self.x0)
        self.rect.set_height(self.y1 - self.y0)
        self.rect.set_xy((self.x0, self.y0))
        self.rect.set_linestyle('solid')
        self.ax.figure.canvas.draw()

my_object = Zoom()
root.mainloop()

I have taken help from this question Matplotlib: draw a selection area in the shape of a rectangle with the mouse The output I am getting is

press
(0.0, 1.4007056451612905)
(0.0, 6.9296116504854366)

panning
(1.4007056451612905, 1.4007056451612905)
(6.8932038834951452, 6.9296116504854366)
panning
(None, 1.4007056451612905)
(None, 6.9296116504854366)
panning
(None, 1.4007056451612905)
(None, 6.9296116504854366)

release
(None, 1.4007056451612905)
(None, 6.9296116504854366)
Community
  • 1
  • 1
Riq
  • 182
  • 1
  • 2
  • 17

2 Answers2

3

It works for me:

from Tkinter import *
from matplotlib.figure import *
import matplotlib

matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

root = Tk()
graph = Figure(figsize=(5,4), dpi=100)
ax = graph.add_subplot(111)
plot = ax.plot([1,2,3,4],[5,6,2,8])
canvas = FigureCanvasTkAgg(graph, master=root)
canvas.show()
canvas.get_tk_widget().grid(column=2, row=1, rowspan=2, sticky=(N, S, E, W))

class Zoom(object):
    def __init__(self):
        self.graph = Figure(figsize=(5,4), dpi=100)
        self.ax = graph.add_subplot(111)

        # should be Rectangle((0,0),0,0)
        self.rect = Rectangle((10,10),100,100)
        self.ax.add_patch(self.rect)

        self.ax.plot([1,2,3,4],[5,6,2,8])
        self.is_pressed = False
        self.x0 = 0.0
        self.y0 = 0.0
        self.x1 = 0.0
        self.y1 = 0.0
        self.aid = graph.canvas.mpl_connect('button_press_event', self.on_press)
        self.bid = graph.canvas.mpl_connect('button_release_event', self.on_release)
        self.cid = graph.canvas.mpl_connect('motion_notify_event', self.on_motion)

    def on_press(self, event):
        self.is_pressed = True
        if event.xdata is not None and event.ydata is not None:
            self.x0, self.y0 = event.xdata, event.ydata

            print 'press:', self.x0, self.y0

            # only remove old rectangle
            self.rect.set_width(0)
            self.rect.set_height(0)
            self.rect.set_xy((self.x0, self.y0))
            self.ax.figure.canvas.draw()

            # color and linestyle for future motion 
            self.rect.set_facecolor('red')
            self.rect.set_linestyle('dashed')

    def on_motion(self, event):
        if self.is_pressed:
            if event.xdata is not None and event.ydata is not None:
                self.x1, self.y1 = event.xdata, event.ydata
                self.rect.set_width(self.x1 - self.x0)
                self.rect.set_height(self.y1 - self.y0)
                self.rect.set_xy((self.x0, self.y0))
                self.ax.figure.canvas.draw()
                print 'rect:', self.x0, self.y0, self.x1, self.y1, (self.x1-self.x0), (self.y1-self.y0)

    def on_release(self, event):
        self.is_pressed = False
        print 'release:', event.xdata, event.ydata

        # change only color and linestyle

        #self.rect.set_width(self.x1 - self.x0)
        #self.rect.set_height(self.y1 - self.y0)
        #self.rect.set_xy((self.x0, self.y0))

        self.rect.set_facecolor('blue')
        self.rect.set_linestyle('solid')
        self.ax.figure.canvas.draw()

my_object = Zoom()
root.mainloop()
furas
  • 134,197
  • 12
  • 106
  • 148
  • Really impressive research!!! Can you please tell me why it was not working without `Rectangle`? And why you have #commented out `on_release` attributes such as `set_width, set_height, set_xy`? +1 for your hardwork. – Riq Jul 15 '14 at 04:20
  • Maybe it needs new object `Rectangle` to show it. Maybe previously it was not rectangle but background or something else. I don't need `set_width, set_height, set_xy` in `on_release` because `on_motion` do all job - it always get most current coordinates. Beside I can release button when mouse is outside graph (on margin) and then I don't need this coordinates - especially that xdata, ydata will be None. – furas Jul 15 '14 at 04:50
2

You need to update these as the event changes:

def on_motion(self, event):
    if self.is_pressed is True:
        self.x1 = event.xdata
        self.y1 = event.ydata
        self.rect.set_width(1)
        self.rect.set_height(1)
        self.rect.set_xy((2.5, 5))
        self.rect.set_linestyle('dashed')
        self.ax.figure.canvas.draw()

def on_release(self, event):
    print 'release'
    self.is_pressed = False
    self.x1 = event.xdata
    self.y1 = event.ydata
    self.rect.set_width(1)
    self.rect.set_height(1)
    self.rect.set_xy((2.5, 5))
    self.rect.set_linestyle('solid')
    self.ax.figure.canvas.draw()

As is they will continually draw a fixed size rectangle at the coordinates (2.5, 5) with a width and height of 1.

Similar to the question you were looking at something like this works.

    def on_press(self, event):
        print('press')
        self.is_pressed = True
        self.x0 = event.xdata
        self.y0 = event.ydata

    def on_motion(self, event):
        self.x1, self.y1 = event.xdata, event.ydata
        if (self.is_pressed is True and
                self.x1 is not None and
                self.y1 is not None):
            self.rect.set_width(self.x1 - self.x0)
            self.rect.set_height(self.y1 - self.y0)
            self.rect.set_xy((self.x0, self.y0))
            self.rect.set_linestyle('dashed')
            self.ax.figure.canvas.draw()

    def on_release(self, event):
        print('release')
        self.is_pressed = False
        self.x1, self.y1 = event.xdata, event.ydata
        try:
            self.rect.set_width(self.x1 - self.x0)
            self.rect.set_height(self.y1 - self.y0)
            self.rect.set_xy((self.x0, self.y0))
        except TypeError:
            if (self.x1 is None or self.y1 is None):
                return
            else:
                raise
        self.rect.set_linestyle('solid')
        self.ax.figure.canvas.draw()

Notice that the rectangle dimensions are taken from the event. I added the guard to prevent mishaps where the event does not properly interpret the coordinate.


@furas makes a good points in the comments, it is a good idea to initialize your coordinates to floats.

    self.x0 = 0.0
    self.y0 = 0.0
    self.x1 = 0.0
    self.y1 = 0.0
MrAlias
  • 1,316
  • 15
  • 26
  • Thanks for your reply. But I did the way you have said & its generating an error `self.rect.set_width(self.x1 - self.x0) TypeError: unsupported operand type(s) for -: 'NoneType' and 'float' ` – Riq Jul 15 '14 at 01:46
  • Which value is `None`? Are you actually setting `self.y0` and `self.x0` in the `on_press` function? Are you clicking inside of the canvas? – MrAlias Jul 15 '14 at 01:49
  • @Mahasish Shome you set `self.x1=None` (and other too) in `__init__` - use `self.x1=0` – furas Jul 15 '14 at 01:50
  • @furas: I have tried with `0` Now I am getting something like this `release Exception in Tkinter callback Traceback (most recent call last): File "C:/Users/VAIO/test.pyw", line 52, in on_release self.rect.set_width(self.x1 - self.x0) TypeError: unsupported operand type(s) for -: 'NoneType' and 'float'` – Riq Jul 15 '14 at 01:55
  • @furas: I have changed all the `self.x0 = 0, self.x1 = 0, self.y0 = 0, self.y1 = 0`. I don't understand why is it generating `TypeError` – Riq Jul 15 '14 at 01:58
  • I think its irrelevant with `self.x0, self.x1` because I have `#commented out` that part but still the same error is generating. There is some problem with `self.rect.set_width(self.x1 - self.x0)` – Riq Jul 15 '14 at 02:01
  • @MahasishShome commenting it out will still leave those values as `None`. What does `print(self.x1, self.x0)` return if you place it above the part of code that is failing? – MrAlias Jul 15 '14 at 02:04
  • @MrAlias: This time without any error, the output is `press release (3.3664314516129035, 1.317540322580645)` I think the values are beyond the limit what `float` can accomodate. – Riq Jul 15 '14 at 02:08
  • @MahasishShome [I don't think floats are you problem](http://stackoverflow.com/questions/3477283/maximum-float-in-python) :) I'm happy it worked for you though. – MrAlias Jul 15 '14 at 02:14
  • I tried `print '1:', self.x1, self.y1` in `on_motion` and it gives me `1: None None` – furas Jul 15 '14 at 02:14
  • 1
    But `event.x, event.y` give correct values in `on_motion`. – furas Jul 15 '14 at 02:18
  • @furas: I think if you change `self.x0 = 0.0` then I am getting output of `print '1:', self.x1, self.y1` as `press 1: 1.60483870968 6.98422330097 1: 1.62752016129 6.94781553398 1: 1.68800403226 6.83859223301`. But still confused why selection area is not showing up. – Riq Jul 15 '14 at 02:19
  • Moment I close Python Shell but I didn't save code with modifiactions :) – furas Jul 15 '14 at 02:21
  • @MrAlias @furas : I have printed all the values at different function & it seems from second `on_motion` callback `x1, y1` is becoming `None` for some weird reason. Please check my edited question – Riq Jul 15 '14 at 02:56
  • @MahasishShome this might be a problem with you backend. I have yet to get TkAgg to work for me, and the above code works for me with Qt4Agg. The rectangle follows my mouse with the correct size and all. – MrAlias Jul 15 '14 at 02:59
  • When you move mouse on margin then `xdata`, `ydata` give `None` but `x`, `y` give values. I have moving rectangle but it use coordinates from graph and they can be different then mouse coordinates - and tehy use different scale for `x` then for `y`. – furas Jul 15 '14 at 03:12
  • `xdata`, `ydata` gives correct coordinates but they has to be checked if they are not `None`. – furas Jul 15 '14 at 03:29