Show long cross hair cursor over image performance bad - python

When I load a 3840x2160 image with below code:
#!/usr/bin/env python3
import matplotlib.pyplot as plt
import matplotlib.image as image
import matplotlib.patches as patches
class SnaptoCursor(object):
def __init__(self, ax):
self.ax = ax
self.lx = ax.axhline(color='r') # the horiz line
self.ly = ax.axvline(color='r') # the vert line
self.x = 0
self.y = 0
def mouse_move(self, event):
if not event.inaxes:
return
x, y = event.xdata, event.ydata
# update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
self.ax.figure.canvas.draw()
img = image.imread("exam.jpg") #3840x2160
fig,ax =plt.subplots(1)
ax.imshow(img)
snap_cursor = SnaptoCursor(ax)
fig.canvas.mpl_connect('motion_notify_event', snap_cursor.mouse_move)
plt.show()
When I move the mouse, I can see the long crosshair cursor (two line actually) move very slow. I wish it move with the cursor immediately.
Welcome if any other good solution!

You are redrawing the entire canvas every time the mouse moves, this is unnecessary - you simply need to redraw the two artists and use blitting to avoid redrawing the rest. This is slightly tricky when there is something behind the relevant artists (i.e. an image or colormesh). Something like this should suffice:
class SnaptoCursor(object):
def __init__(self, ax):
# Have to draw the canvas once beforehand to cache the renderer
ax.figure.canvas.draw()
self.bg = ax.figure.canvas.copy_from_bbox(ax.bbox)
self.ax = ax
self.lx = ax.axhline(color='r') # the horiz line
self.ly = ax.axvline(color='r') # the vert line
self.x = 0
self.y = 0
def mouse_move(self, event):
if not event.inaxes:
return
x, y = event.xdata, event.ydata
# update the line positions
ax.figure.canvas.restore_region(self.bg)
self.lx.set_ydata(y)
self.ly.set_xdata(x)
self.ax.draw_artist(self.lx)
self.ax.draw_artist(self.ly)
self.ax.figure.canvas.blit(self.ax.bbox)
Although adjustments may be required depending on the backend etc.

Related

Annotate draggable Circle

I am looking to annotate a draggable circle with a number. The draggeable point is created from this class:
class DraggablePoint:
lock = None #only one can be animated at a time
def __init__(self, point):
self.point = point
self.press = None
self.background = None
def connect(self):
'connect to all the events we need'
self.cidpress = self.point.figure.canvas.mpl_connect('button_press_event', self.on_press)
self.cidrelease = self.point.figure.canvas.mpl_connect('button_release_event', self.on_release)
self.cidmotion = self.point.figure.canvas.mpl_connect('motion_notify_event', self.on_motion)
def on_press(self, event):
if event.inaxes != self.point.axes: return
if DraggablePoint.lock is not None: return
contains, attrd = self.point.contains(event)
if not contains: return
self.press = (self.point.center), event.xdata, event.ydata
DraggablePoint.lock = self
# draw everything but the selected point and store the pixel buffer
canvas = self.point.figure.canvas
axes = self.point.axes
self.point.set_animated(True)
canvas.draw()
self.background = canvas.copy_from_bbox(self.point.axes.bbox)
# now redraw just the rectangle
axes.draw_artist(self.point)
# and blit just the redrawn area
canvas.blit(axes.bbox)
def on_motion(self, event):
if DraggablePoint.lock is not self:
return
if event.inaxes != self.point.axes: return
self.point.center, xpress, ypress = self.press
dx = event.xdata - xpress
dy = event.ydata - ypress
self.point.center = (self.point.center[0]+dx, self.point.center[1]+dy)
canvas = self.point.figure.canvas
axes = self.point.axes
# restore the background region
canvas.restore_region(self.background)
# redraw just the current rectangle
axes.draw_artist(self.point)
# blit just the redrawn area
canvas.blit(axes.bbox)
def on_release(self, event):
'on release we reset the press data'
if DraggablePoint.lock is not self:
return
self.press = None
DraggablePoint.lock = None
# turn off the rect animation property and reset the background
self.point.set_animated(False)
self.background = None
# redraw the full figure
self.point.figure.canvas.draw()
def disconnect(self):
'disconnect all the stored connection ids'
self.point.figure.canvas.mpl_disconnect(self.cidpress)
self.point.figure.canvas.mpl_disconnect(self.cidrelease)
self.point.figure.canvas.mpl_disconnect(self.cidmotion)
I am plotting with this (circles.append + the last loop is the interesting code for this problem):
drs = []
circles = []
for team, color, sec_color in zip([hometeam.loc[frame], awayteam.loc[frame]], team_colors, sec_colors):
x_columns = [c for c in team.keys() if
c[-2:].lower() == '_x' and c != 'ball_x'] # column header for player x positions
y_columns = [c for c in team.keys() if
c[-2:].lower() == '_y' and c != 'ball_y'] # column header for player y positions
for x, y in zip(team[x_columns], team[y_columns]):
#if ctr == 0:
circles.append(patches.Circle((x, y), 1.4, fc=color, edgecolor=sec_color, linewidth=1.2, zorder=2))
for circ in circles:
ax.add_patch(circ)
dr = DraggablePoint(circ)
dr.connect()
drs.append(dr)
plt.show()
I can plot text at the circles center, however once I drag a circle, the text stays at the original position. Is there a way to add text to the circles, which would move alongside the circles when dragged?
Sure. Add a label, record the relative position to the circle, and update its position on motion.
import matplotlib.pyplot as plt
class DraggablePoint:
...
class LabelledDraggablePoint(DraggablePoint):
def __init__(self, point, label):
super().__init__(point)
self.label = label
x1, y1 = self.label.get_position()
x2, y2 = self.point.center
self.label_offset = (x1 - x2, y1 - y2)
def on_motion(self, event):
super().on_motion(event)
self.label.set_position(self.point.center + self.label_offset)
if __name__ == '__main__':
fig, ax = plt.subplots()
point = plt.Circle((0.5, 0.5), 0.1)
ax.add_patch(point)
ax.set_aspect('equal')
label = ax.text(0.5, 0.5, 'Lorem ipsum', ha='center', va='center')
instance = LabelledDraggablePoint(point, label)
instance.connect()
plt.show()
On a different note: the blitting is nausea inducing. You will get much smoother animations without it. I would cut all of that out.

Display y coordinate values with interactive cursor crosshairs in Python (matplotlib) [duplicate]

I have a data curve displayed in a matplotlib figure. I would like to attach a text box to the mouse cursor. That is, as the mouse cursor is moved around in the figure, the text box is moved with it. Also, I would like to be able to update the text in the text box as the cursor with attached text box is moved.
I started with the matplotlib example at https://matplotlib.org/gallery/misc/cursor_demo_sgskip.html but was unsuccessful in modification of it for my purpose. I also looked at some 3rd party packages (e.g. mpldatacursor and mplcursors); but, these did not seem appropriate for my application.
Here is some code that I was experimenting with that should illustrate what I am trying to accomplish.
# -*- coding: iso-8859-1 -*-#
#!/usr/bin/env python
# The following allows special characters to be in comments (e.g. the extended Swedish alphabet)
# coding:utf-8
import matplotlib.pyplot as plt
# For setting size and position of matplotlib figure
import matplotlib
matplotlib.use("WXAgg")
import numpy as np
class Cursor(object):
"""
Purpose: Define a cursor whose interesection will track points along a curve
"""
def __init__(self, ax):
self.ax = ax
self.lx = ax.axhline(color='k',linewidth=0.25) # the horiz line
self.ly = ax.axvline(color='k',linewidth=0.25) # the vert line
# Text location in axes coords
self.txt = ax.text(0.7, 0.9, '', transform=ax.transAxes)
def mouse_move(self, event):
'''
Purpose: respond to movement of the mouse
'''
if not event.inaxes: return
x, y = event.xdata, event.ydata
props = dict(boxstyle='round', facecolor='wheat', alpha=0.4)
self.ax.text(x, y, 'test', fontsize=8, bbox=props)
#self.ax.text(x,y,'')
#self.text(x, y, 'test', fontsize=8, bbox=props)
#ax.text(x, y, 'test', fontsize=8, bbox=props)
# Update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))
self.ax.text(x,y,'')
plt.draw()
class SnaptoCursor(object):
"""
Like Cursor but the current center of the crosshair at (x,y) will snap to the nearest
(x,y) on the curve.
For simplicity, I'm assuming x is sorted
"""
def __init__(self, ax, x, y):
self.ax = ax
self.lx = ax.axhline(color='k') # the horiz line
self.ly = ax.axvline(color='k') # the vert line
self.x = x
self.y = y
# Text location in axes coords
self.txt = ax.text(0.7, 0.9, '', transform=ax.transAxes)
def mouse_move(self, event):
"""
Purpose: Track the movement of the mouse coords and then update the position
of the intersection of the cursor cross-hairs
"""
if not event.inaxes:
return
x, y = event.xdata, event.ydata # x,y coordinates of mouse
props = dict(boxstyle='round', facecolor='wheat', alpha=0.4)
self.ax.text(x, y, 'test', fontsize=8, bbox=props)
#self.text(x, y, 'test', fontsize=8, bbox=props)
#ax.text(x, y, 'test', fontsize=8, bbox=props)
#self.ax.text(remove)
# Find closest pt on data curve to (x,y) of cross-air intersection
indx = min(np.searchsorted(self.x, [x])[0], len(self.x) - 1)
x = self.x[indx]
y = self.y[indx]
# Update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
# place a text box in upper left in axes coords
#self.ax.text(x, y, 'test', transform=ax.transAxes, fontsize=8,
# verticalalignment='top', bbox=props)
self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))
print('x=%1.2f, y=%1.2f' % (x, y))
plt.draw()
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2 * 2 * np.pi * t)
fig, ax = plt.subplots(figsize=(14,7.5))
fig.canvas.set_window_title('TS with tracking cursor')
# Need the following to set position the plot
pltManager = plt.get_current_fig_manager()
pltManager.window.SetPosition((20,20)) # pixels offset from top left corner of display
# cursor = Cursor(ax)
cursor = SnaptoCursor(ax, t, s)
plt.connect('motion_notify_event', cursor.mouse_move)
ax.plot (t, s, 'o')
plt.axis([0, 1, -1, 1])
plt.grid(axis='both')
plt.show()
The text box does "stick" with the mouse cursor but it is not erased when the cursor is moved --- this is what needs to be solved. One small step in the right direction
# -*- coding: iso-8859-1 -*-#
#!/usr/bin/env python
# The following allows special characters to be in comments (e.g. the extended Swedish alphabet)
# coding:utf-8
import matplotlib.pyplot as plt
# For setting size and position of matplotlib figure
import matplotlib
matplotlib.use("WXAgg")
import numpy as np
class SnaptoCursor(object):
"""
Like Cursor but the current center of the crosshair at (x,y) will snap to the nearest
(x,y) on the curve.
For simplicity, I'm assuming x is sorted
"""
def __init__(self, ax, x, y):
self.ax = ax
self.lx = ax.axhline(color='k') # the horiz line
self.ly = ax.axvline(color='k') # the vert line
self.tx = ax.text(0.0,0.0,'test') # the text to follow cursor
self.x = x
self.y = y
# Text location in axes coords
self.txt = ax.text(0.7, 0.9, '', transform=ax.transAxes)
def mouse_move(self, event):
"""
Purpose: Track the movement of the mouse coords and then update the position
of the intersection of the cursor cross-hairs
"""
if not event.inaxes:
return
x, y = event.xdata, event.ydata # x,y coordinates of mouse
self.tx.set_position((x,y))
# Find closest pt on data curve to (x,y) of cross-air intersection
indx = min(np.searchsorted(self.x, [x])[0], len(self.x) - 1)
x = self.x[indx]
y = self.y[indx]
# Update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))
print('x=%1.2f, y=%1.2f' % (x, y))
plt.draw()
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2 * 2 * np.pi * t)
fig, ax = plt.subplots(figsize=(14,7.5))
fig.canvas.set_window_title('TS with tracking cursor')
# Need the following to set position the plot
pltManager = plt.get_current_fig_manager()
pltManager.window.SetPosition((20,20)) # pixels offset from top left corner of display
cursor = SnaptoCursor(ax, t, s)
plt.connect('motion_notify_event', cursor.mouse_move)
ax.plot (t, s, 'o')
plt.axis([0, 1, -1, 1])
plt.grid(axis='both')
plt.show()
This code will move the text with the cursor and erase previous text. However, I am still unable to change the text as the cursor is moved! Any suggestions would be appreciated :-)
You were creating a new Text object everytime you moved the mouse. You need to create the object during __init__ and then simply update its position/text when the mouse is moved:
# -*- coding: iso-8859-1 -*-#
#!/usr/bin/env python
# The following allows special characters to be in comments (e.g. the extended Swedish alphabet)
# coding:utf-8
import matplotlib.pyplot as plt
# For setting size and position of matplotlib figure
import matplotlib
matplotlib.use("WXAgg")
import numpy as np
class Cursor(object):
"""
Purpose: Define a cursor whose interesection will track points along a curve
"""
def __init__(self, ax):
self.ax = ax
self.lx = ax.axhline(color='k',linewidth=0.25) # the horiz line
self.ly = ax.axvline(color='k',linewidth=0.25) # the vert line
# Text location in data coords
props = dict(boxstyle='round', facecolor='wheat', alpha=0.4)
self.txt = self.ax.text(0, 0, '', fontsize=8, bbox=props)
def mouse_move(self, event):
'''
Purpose: respond to movement of the mouse
'''
if not event.inaxes: return
x, y = event.xdata, event.ydata
self.txt.set_position((x,y))
# Update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))
plt.draw()
class SnaptoCursor(object):
"""
Like Cursor but the current center of the crosshair at (x,y) will snap to the nearest
(x,y) on the curve.
For simplicity, I'm assuming x is sorted
"""
def __init__(self, ax, x, y):
self.ax = ax
self.lx = ax.axhline(color='k') # the horiz line
self.ly = ax.axvline(color='k') # the vert line
self.x = x
self.y = y
# Text location in data coords
props = dict(boxstyle='round', facecolor='wheat', alpha=0.4)
self.txt = self.ax.text(0, 0, '', fontsize=8, bbox=props)
def mouse_move(self, event):
"""
Purpose: Track the movement of the mouse coords and then update the position
of the intersection of the cursor cross-hairs
"""
if not event.inaxes:
return
x, y = event.xdata, event.ydata # x,y coordinates of mouse
self.txt.set_position((x,y))
# Find closest pt on data curve to (x,y) of cross-air intersection
indx = min(np.searchsorted(self.x, [x])[0], len(self.x) - 1)
x = self.x[indx]
y = self.y[indx]
# Update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
# place a text box in upper left in axes coords
#self.ax.text(x, y, 'test', transform=ax.transAxes, fontsize=8,
# verticalalignment='top', bbox=props)
self.txt.set_text('x=%1.2f, y=%1.2f' % (x, y))
print('x=%1.2f, y=%1.2f' % (x, y))
self.ax.figure.canvas.draw_idle()
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2 * 2 * np.pi * t)
fig, ax = plt.subplots(figsize=(14,7.5))
fig.canvas.set_window_title('TS with tracking cursor')
# cursor = Cursor(ax)
cursor = SnaptoCursor(ax, t, s)
plt.connect('motion_notify_event', cursor.mouse_move)
ax.plot (t, s, 'o')
plt.axis([0, 1, -1, 1])
plt.grid(axis='both')
plt.show()
Here is a slightly edited version of Diziet Asahithat's code that answers my question:
# -*- coding: iso-8859-1 -*-#
#!/usr/bin/env python
# The following allows special characters to be in comments (e.g. the extended Swedish alphabet)
# coding:utf-8
import matplotlib.pyplot as plt
# For setting size and position of matplotlib figure
import matplotlib
matplotlib.use("WXAgg")
import numpy as np
class SnaptoCursor(object):
"""
Like normal cursor but the current center of the crosshair at (x,y) will snap to the nearest
(x,y) on the curve.
For simplicity, I'm assuming x is sorted
"""
def __init__(self, ax, x, y):
self.ax = ax
self.lx = ax.axhline(color='k') # the horiz line
self.ly = ax.axvline(color='k') # the vert line
self.x = x
self.y = y
# Text location in data coords (this is required!)
props = dict(boxstyle='round', facecolor='wheat', alpha=0.4)
self.txt = self.ax.text(0, 0, '', fontsize=8, bbox=props)
def mouse_move(self, event):
"""
Purpose: Track the movement of the mouse coords and then update the position
of the intersection of the cursor cross-hairs along with the text box
"""
if not event.inaxes:
return
x, y = event.xdata, event.ydata # x,y coordinates of mouse
# Update the position of the text in the box attached to the cursor
self.txt.set_position((x+0.02,y)) # place the text in the box
# Place the center of the cross-hairs
indx = min(np.searchsorted(self.x, [x])[0], len(self.x) - 1)
x = self.x[indx]
y = self.y[indx]
# Update the line positions
self.lx.set_ydata(y)
self.ly.set_xdata(x)
# Place text in the text box
self.txt.set_text('Test\n x=%1.2f, y=%1.2f' % (x, y))
#print('x=%1.2f, y=%1.2f' % (x, y))
self.ax.figure.canvas.draw_idle()
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2 * 2 * np.pi * t)
fig, ax = plt.subplots(figsize=(14.5,7.2))
ax.set_ylim(-1,+1)
fig.canvas.set_window_title('TS with tracking cursor')
# Need the following to set position the plot
pltManager = plt.get_current_fig_manager()
pltManager.window.SetPosition((20,20)) # pixels offset from top left corner of display
cursor = SnaptoCursor(ax, t, s)
plt.connect('motion_notify_event', cursor.mouse_move)
ax.plot (t, s, 'o')
plt.axis([0, 1, -1, 1])
plt.grid(axis='both')
plt.show()
Thanks very much for your efforts DA :-)

Python Matplotlib: reduce render time for interactive plot

I've got the following code that produces a plot that can interactively be modified. Clicking / holding the left mouse button sets the marker position, Holding the right button and moving the mouse moves the plotted data in direction x and using the mouse wheel zooms in/out. Additionally, resizing the window calls figure.tight_layout() so that the size of the axes is adapted to the window size.
# coding=utf-8
from __future__ import division
from Tkinter import *
import matplotlib
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from numpy import arange, sin, pi
matplotlib.use('TkAgg')
class PlotFrame(Frame):
def __init__(self, master, **ops):
Frame.__init__(self, master, **ops)
self.figure = Figure()
self.axes_main = self.figure.add_subplot(111)
for i in range(10):
t = arange(0, 300, 0.01)
s = sin(0.02 * pi * (t + 10 * i))
self.axes_main.plot(t, s)
self.plot = FigureCanvasTkAgg(self.figure, master=self)
self.plot.show()
self.plot.get_tk_widget().pack(fill=BOTH, expand=1)
self.dragging = False
self.dragging_button = None
self.mouse_pos = [0, 0]
self.marker = self.figure.axes[0].plot((0, 0), (-1, 1), 'black', linewidth=3)[0]
self.plot.mpl_connect('button_press_event', self.on_button_press)
self.plot.mpl_connect('button_release_event', self.on_button_release)
self.plot.mpl_connect('motion_notify_event', self.on_mouse_move)
self.plot.mpl_connect('scroll_event', self.on_mouse_scroll)
self.plot.mpl_connect("resize_event", self.on_resize)
def on_resize(self, _):
self.figure.tight_layout()
def axes_size(self):
pos = self.axes_main.get_position()
bbox = self.figure.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted())
width, height = bbox.width * self.figure.dpi, bbox.height * self.figure.dpi
axis_size = [(pos.x1 - pos.x0) * width, (pos.y1 - pos.y0) * height]
return axis_size
def on_button_press(self, event):
# right mouse button clicked
if not self.dragging and event.button in (1, 3):
self.dragging = True
self.dragging_button = event.button
self.mouse_pos = [event.x, event.y]
# left mouse button clicked
if event.button == 1 and event.xdata is not None:
self.move_marker(event.xdata)
def on_button_release(self, event):
if self.dragging and self.dragging_button == event.button:
self.dragging = False
def on_mouse_move(self, event):
if self.dragging and self.dragging_button == 3:
dx = event.x - self.mouse_pos[0]
self.mouse_pos = [event.x, event.y]
x_min, x_max = self.figure.axes[0].get_xlim()
x_range = x_max - x_min
x_factor = x_range / self.axes_size()[0]
self.figure.axes[0].set_xlim([x_min - dx * x_factor, x_max - dx * x_factor])
self.plot.draw()
elif self.dragging and self.dragging_button == 1:
self.move_marker(event.xdata)
def on_mouse_scroll(self, event):
if event.xdata is None:
return
zoom_direction = -1 if event.button == 'up' else 1
zoom_factor = 1 + .4 * zoom_direction
x_min, x_max = self.figure.axes[0].get_xlim()
min = event.xdata + (x_min - event.xdata) * zoom_factor
max = event.xdata + (x_max - event.xdata) * zoom_factor
self.figure.axes[0].set_xlim([min, max])
self.plot.draw()
def move_marker(self, x_position):
y_min, y_max = self.figure.axes[0].get_ylim()
self.marker.set_data((x_position, x_position), (y_min, y_max))
self.plot.draw()
if __name__ == '__main__':
gui = Tk()
vf = PlotFrame(gui)
vf.pack(fill=BOTH, expand=1)
gui.mainloop()
The implementation works fine, but rendering is really slow when displaying a lot of lines. How can I make rendering faster? As you can see in the implementation above, the whole plot is drawn completely every time anything changes which shouldn't be necessary. My thoughts on this:
Resizing the window: draw everything
Zooming: draw everything
Moving the marker: just redraw the marker (one line) instead of drawing everything
Moving the plot in x direction: move the pixels currently displayed in the plot left/right and only draw pixels that are moved into the visible area
Drawing everything when resizing/zooming is fine for me, but I really need faster drawing of the latter two modifications. I already looked into matplotlib's animations, but as far as I understood, they won't help in my case. Any help is greatly appreciated, thanks!
The solution seems to be to cache elements that get redrawn as you said:
One major thing that gets redrawn is the background:
# cache the background
background = fig.canvas.copy_from_bbox(ax.bbox)
After caching restore it using restore region then just re-draw the points/line at every call you need
# restore background
fig.canvas.restore_region(background)
# redraw just the points
ax.draw_artist(points)
# fill in the axes rectangle
fig.canvas.blit(ax.bbox)
To optimize drawing blitting can be used. With it only given artists (those that were changed) will be rendered instead of the whole figure.
Motplotlib uses that technique internally in the animation module. You can use Animation class in it as a reference to implement the same behaviour in your code. Look at the _blit_draw() and several related functions after it in the sources.

draw horizontal bars on the same line

I have to draw a gantt resource type of chart.
Idea is to draw several horizontal bars on the same line (corresponding to a resource) each length represented by start date and and date
this is the expected result:
Altogether we have around 200 resources and max. 50 task for each to display, so performance is important.
Any idea?
In addition the tasks should be draggable by mouse.
An solution (Fat GUI (pyQt, wxwidget, tkinter, ...) or web based Flask, web2py, etc) is OK
Actually, I'm gonna cheat and post you something straight from the Matplotlib Documentation. This should get you started with draggable objects in mpl. you'll have to come up with your own dynamic object creation code...
full credit to the guys over at mpl:
# draggable rectangle with the animation blit techniques; see
# http://www.scipy.org/Cookbook/Matplotlib/Animations
import numpy as np
import matplotlib.pyplot as plt
class DraggableRectangle:
lock = None # only one can be animated at a time
def __init__(self, rect):
self.rect = rect
self.press = None
self.background = None
def connect(self):
'connect to all the events we need'
self.cidpress = self.rect.figure.canvas.mpl_connect(
'button_press_event', self.on_press)
self.cidrelease = self.rect.figure.canvas.mpl_connect(
'button_release_event', self.on_release)
self.cidmotion = self.rect.figure.canvas.mpl_connect(
'motion_notify_event', self.on_motion)
def on_press(self, event):
'on button press we will see if the mouse is over us and store some data'
if event.inaxes != self.rect.axes: return
if DraggableRectangle.lock is not None: return
contains, attrd = self.rect.contains(event)
if not contains: return
print('event contains', self.rect.xy)
x0, y0 = self.rect.xy
self.press = x0, y0, event.xdata, event.ydata
DraggableRectangle.lock = self
# draw everything but the selected rectangle and store the pixel buffer
canvas = self.rect.figure.canvas
axes = self.rect.axes
self.rect.set_animated(True)
canvas.draw()
self.background = canvas.copy_from_bbox(self.rect.axes.bbox)
# now redraw just the rectangle
axes.draw_artist(self.rect)
# and blit just the redrawn area
canvas.blit(axes.bbox)
def on_motion(self, event):
'on motion we will move the rect if the mouse is over us'
if DraggableRectangle.lock is not self:
return
if event.inaxes != self.rect.axes: return
x0, y0, xpress, ypress = self.press
dx = event.xdata - xpress
dy = event.ydata - ypress
self.rect.set_x(x0+dx)
self.rect.set_y(y0+dy)
canvas = self.rect.figure.canvas
axes = self.rect.axes
# restore the background region
canvas.restore_region(self.background)
# redraw just the current rectangle
axes.draw_artist(self.rect)
# blit just the redrawn area
canvas.blit(axes.bbox)
def on_release(self, event):
'on release we reset the press data'
if DraggableRectangle.lock is not self:
return
self.press = None
DraggableRectangle.lock = None
# turn off the rect animation property and reset the background
self.rect.set_animated(False)
self.background = None
# redraw the full figure
self.rect.figure.canvas.draw()
def disconnect(self):
'disconnect all the stored connection ids'
self.rect.figure.canvas.mpl_disconnect(self.cidpress)
self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
self.rect.figure.canvas.mpl_disconnect(self.cidmotion)
fig = plt.figure()
ax = fig.add_subplot(111)
rects = ax.bar(range(10), 20*np.random.rand(10))
drs = []
for rect in rects:
dr = DraggableRectangle(rect)
dr.connect()
drs.append(dr)
plt.show()

Get data from Python plot with matplotlib then save to array

The first block of code in this answer allows the user to generate a matplotlib figure and by clicking on the graph, it is possible to display the x- and y- coordinates of the graph after each click. How can I save these coordinates to 5 decimal places, say, into a numpy array (X for the x-coordinates and Y for the y-coordinates)? I'm not really sure how to start this (and it's probably trivial), but here is the code:
import numpy as np
import matplotlib.pyplot as plt
X = []
Y = []
class DataCursor(object):
text_template = 'x: %0.2f\ny: %0.2f'
x, y = 0.0, 0.0
xoffset, yoffset = -20, 20
text_template = 'x: %0.2f\ny: %0.2f'
def __init__(self, ax):
self.ax = ax
self.annotation = ax.annotate(self.text_template,
xy=(self.x, self.y), xytext=(self.xoffset, self.yoffset),
textcoords='offset points', ha='right', va='bottom',
bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0')
)
self.annotation.set_visible(False)
def __call__(self, event):
self.event = event
self.x, self.y = event.mouseevent.xdata, event.mouseevent.ydata
if self.x is not None:
self.annotation.xy = self.x, self.y
self.annotation.set_text(self.text_template % (self.x, self.y))
self.annotation.set_visible(True)
event.canvas.draw()
fig = plt.figure()
line, = plt.plot(range(10), 'ro-')
fig.canvas.mpl_connect('pick_event', DataCursor(plt.gca()))
line.set_picker(5) # Tolerance in points
plt.show()
It sounds like you want plt.ginput().
As a quick example:
fig, ax = plt.subplots()
ax.plot(range(10), 'ro-')
points = plt.ginput(n=4)
print points
np.savetxt('yourfilename', points)
plt.show()
I think you can do this by using list members in DataCursor:
def __init__(self, ax):
...
self.mouseX = []
self.mouseY = []
In your call, you would then store the X and Y for each event into these members:
def __call__(self, event):
...
self.mouseX.append(self.x)
self.mouseY.append(self.y)
You would then pass this to mpl_connect like this:
DC = DataCursor(plt.gca())
fig.canvas.mpl_connect('pick_event', DC)
...
print DC.mouseX, DC.mouseY
I have illustrated the principle here, but I don't see why this couldn't be applied to numpy arrays as well.

Categories

Resources