Related
I am trying to implement the solution presented by #Joe Kington in the following stack overflow thread:
Hiding lines after showing a pyplot figure
I have 16 lines I am trying to plot with this method, each of which have their own unique color and label. While #Joe Kington's solution works fine to get the plot to display, I cannot transfer his interactive method to my code with the interactive functionality of clicking on the legend entries to hide a line. That is, my code plots my data without error but there is no interactive functionality occurring.
I have tried:
setting the unique labels to be raw strings and regular strings
setting up every line to plot to be iterated over as in #Joe Kington's solution
using smaller legend labels
rearranging blocks of my code in the event that the functions defined in #Joe Kington's solution are out of scope
#Joe Kington's solution is below, which works fine:
import numpy as np
import matplotlib.pyplot as plt
def main():
x = np.arange(10)
fig, ax = plt.subplots()
for i in range(1, 31):
ax.plot(x, i * x, label=r'$y={}x$'.format(i))
ax.legend(loc='upper left', bbox_to_anchor=(1.05, 1),
ncol=2, borderaxespad=0)
fig.subplots_adjust(right=0.55)
fig.suptitle('Right-click to hide all\nMiddle-click to show all',
va='top', size='large')
leg = interactive_legend()
return fig, ax, leg
def interactive_legend(ax=None):
if ax is None:
ax = plt.gca()
if ax.legend_ is None:
ax.legend()
return InteractiveLegend(ax.get_legend())
class InteractiveLegend(object):
def __init__(self, legend):
self.legend = legend
self.fig = legend.axes.figure
self.lookup_artist, self.lookup_handle = self._build_lookups(legend)
self._setup_connections()
self.update()
def _setup_connections(self):
for artist in self.legend.texts + self.legend.legendHandles:
artist.set_picker(10) # 10 points tolerance
self.fig.canvas.mpl_connect('pick_event', self.on_pick)
self.fig.canvas.mpl_connect('button_press_event', self.on_click)
def _build_lookups(self, legend):
labels = [t.get_text() for t in legend.texts]
handles = legend.legendHandles
label2handle = dict(zip(labels, handles))
handle2text = dict(zip(handles, legend.texts))
lookup_artist = {}
lookup_handle = {}
for artist in legend.axes.get_children():
if artist.get_label() in labels:
handle = label2handle[artist.get_label()]
lookup_handle[artist] = handle
lookup_artist[handle] = artist
lookup_artist[handle2text[handle]] = artist
lookup_handle.update(zip(handles, handles))
lookup_handle.update(zip(legend.texts, handles))
return lookup_artist, lookup_handle
def on_pick(self, event):
handle = event.artist
if handle in self.lookup_artist:
artist = self.lookup_artist[handle]
artist.set_visible(not artist.get_visible())
self.update()
def on_click(self, event):
if event.button == 3:
visible = False
elif event.button == 2:
visible = True
else:
return
for artist in self.lookup_artist.values():
artist.set_visible(visible)
self.update()
def update(self):
for artist in self.lookup_artist.values():
handle = self.lookup_handle[artist]
if artist.get_visible():
handle.set_visible(True)
else:
handle.set_visible(False)
self.fig.canvas.draw()
def show(self):
plt.show()
if __name__ == '__main__':
fig, ax, leg = main()
plt.show()
Here is a snippet of my code that isn't working that is defined in a module plotting in a function called main() (I have other modifications I need to make such as changing axis labels, unique colors, etc.):
import numpy as np
import matplotlib.pyplot as plt
def main(t,n,timeSpan):
colors = ['blue','red','green','black','magenta','teal','orange','chartreuse','purple','sienna','goldenrod','lightgray','olive','cyan','maroon','pink']
labels = [r'foo1',r'bar2',r'foo3',r'bar4',r'foo5',r'bar6',r'foo7',r'bar8',r'foo9',r'bar10',r'foo11',r'bar12',r'foo13',r'bar14',r'foo15',r'bar16']
fig, ax = plt.subplots(figsize=(15,8))
for i in range(0, len(colors)):
ax.plot(t, n[:,i], color = colors[i], label = labels[i])
ax.set_xlabel('x label')
ax.set_ylabel('y label')
ax.set_xlim([0,timeSpan])
ax.set_ylim([0,110])
ax.legend(loc='upper left', bbox_to_anchor=(1.05, 1),
ncol=2, borderaxespad=0)
fig.subplots_adjust(right=0.55)
fig.suptitle('Title\n(Right-click to hide all\nMiddle-click to show all)',
va='top', size='large')
leg = interactive_legend()
plt.show()
def interactive_legend(ax=None):
if ax is None:
ax = plt.gca()
if ax.legend_ is None:
ax.legend()
return InteractiveLegend(ax.get_legend())
class InteractiveLegend(object):
def __init__(self, legend):
self.legend = legend
self.fig = legend.axes.figure
self.lookup_artist, self.lookup_handle = self._build_lookups(legend)
self._setup_connections()
self.update()
def _setup_connections(self):
for artist in self.legend.texts + self.legend.legendHandles:
artist.set_picker(10) # 10 points tolerance
self.fig.canvas.mpl_connect('pick_event', self.on_pick)
self.fig.canvas.mpl_connect('button_press_event', self.on_click)
def _build_lookups(self, legend):
labels = [t.get_text() for t in legend.texts]
handles = legend.legendHandles
label2handle = dict(zip(labels, handles))
handle2text = dict(zip(handles, legend.texts))
lookup_artist = {}
lookup_handle = {}
for artist in legend.axes.get_children():
if artist.get_label() in labels:
handle = label2handle[artist.get_label()]
lookup_handle[artist] = handle
lookup_artist[handle] = artist
lookup_artist[handle2text[handle]] = artist
lookup_handle.update(zip(handles, handles))
lookup_handle.update(zip(legend.texts, handles))
return lookup_artist, lookup_handle
def on_pick(self, event):
handle = event.artist
if handle in self.lookup_artist:
artist = self.lookup_artist[handle]
artist.set_visible(not artist.get_visible())
self.update()
def on_click(self, event):
if event.button == 3:
visible = False
elif event.button == 2:
visible = True
else:
return
for artist in self.lookup_artist.values():
artist.set_visible(visible)
self.update()
def update(self):
for artist in self.lookup_artist.values():
handle = self.lookup_handle[artist]
if artist.get_visible():
handle.set_visible(True)
else:
handle.set_visible(False)
self.fig.canvas.draw()
def show(self):
plt.show()
t = np.arange(1000)
n = np.zeros([1000,16])
for i in range(0, 1000):
for j in range(0, 16):
n[i][j] = t[i] * j
timeSpan = 1000
# if __name__ == "__main__":
# main(t,n,timeSpan)
colors = ['blue','red','green','black','magenta','teal','orange','chartreuse','purple','sienna','goldenrod','lightgray','olive','cyan','maroon','pink']
labels = [r'foo1',r'bar2',r'foo3',r'bar4',r'foo5',r'bar6',r'foo7',r'bar8',r'foo9',r'bar10',r'foo11',r'bar12',r'foo13',r'bar14',r'foo15',r'bar16']
fig, ax = plt.subplots(figsize=(15,8))
for i in range(0, len(colors)):
ax.plot(t, n[:,i], color = colors[i], label = labels[i])
ax.set_xlabel('x label')
ax.set_ylabel('y label')
ax.set_xlim([0,timeSpan])
ax.set_ylim([0,110])
ax.legend(loc='upper left', bbox_to_anchor=(1.05, 1),
ncol=2, borderaxespad=0)
fig.subplots_adjust(right=0.55)
fig.suptitle('Title\n(Right-click to hide all\nMiddle-click to show all)',
va='top', size='large')
leg = interactive_legend()
plt.show()
I have determined that when I leave the code as is it works, but when I comment out lines 113-131 and call main() from lines 111 and 112 insead, it no longer works. Is this a scope issue? Not sure why the way I am calling the interactive_legend() function determines whether it works or not.
I'm not quite getting how to create a class for animating data. Here is the gist:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x = np.arange(100).reshape((100, 1))
y = np.random.randn(100, 1)
xy = np.hstack((x, y))
class PlotData:
def __init__(self):
fig, ax = plt.subplots()
fig.set_size_inches((11, 9))
self.fig = fig
self.ax = ax
self.ln0, = ax.plot([], [])
def init(self):
self.ln0.set_data([], [])
return(self.ln0, )
def update(self, frame_no):
data = xy[0:frame_no + 1]
self.ln0.set_data(data[:, 0], data[:, 1])
return(self.ln0, )
if __name__ == '__main__':
my_plot = PlotData()
anim = animation.FuncAnimation(my_plot.fig, my_plot.update,
init_func=my_plot.init, blit=True,
frames=99, interval=50)
plt.show()
This only produces the init method output but not the update, so ends up a blank plot with no animation. What is going on?
For me your code works perfectly fine. The only problem is that most of the data are outside of the plotting limits. If you adjust your plot limits like this:
class PlotData:
def __init__(self):
fig, ax = plt.subplots(figsize = (11,9))
self.fig = fig
self.ax = ax
self.ax.set_xlim([0,100])
self.ax.set_ylim([-3,3])
self.ln0, = ax.plot([], [])
The line is animated just fine. If you want that the x- and y-limits are adjusted automatically, see this question on how to do it. However, if I recall correctly, this will only work properly with blit=False.
I want to create a program that allows the user to first enter points in a Matplotlib plot and then creates a Voronoi Diagram using those points.
Figured out the 2 parts, but not the connection. How do I pass on the points that the user entered and use it in the Voronoi part? (I just need to know how to change the points = np.random.rand(20,2) to the points the user entered.)
Plotting the points:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlim([0, 10])
ax.set_ylim([0, 10])
def onclick(event):
print('x=%d, y=%d, xdata=%f, ydata=%f' %
(event.x, event.y, event.xdata, event.ydata))
plt.plot(event.xdata, event.ydata, 'bo')
fig.canvas.draw()
cid = fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()`
Creating the Voronoi Diagram:
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Voronoi, voronoi_plot_2d
fig = plt.figure()
points = np.random.rand(20,2)
vor = Voronoi(points)
voronoi_plot_2d(vor)
plt.show()
You may use a class to store the points and later plot the diagram in that same plot. In the following example, you would place your points with the left mouse button and plot the voronoi diagram clicking the right mouse button. If you later want to add new points to the plot, you can do that.
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Voronoi, voronoi_plot_2d
fig, ax = plt.subplots()
ax.set_xlim([0, 10])
ax.set_ylim([0, 10])
class V():
def __init__(self, ax = None):
self.ax = ax
if not self.ax: self.ax = plt.gca()
tx = "left click to place points\nright click to plot voronoi diagram"
self.text = self.ax.text(0.1,0.9, tx, transform=self.ax.transAxes,
ha="left", va="top")
self.cid = fig.canvas.mpl_connect('button_press_event', self.onclick)
self.points = []
def onclick(self, event):
self.text.set_visible(False)
if event.button == 1:
print('x=%d, y=%d, xdata=%f, ydata=%f' %
(event.x, event.y, event.xdata, event.ydata))
plt.plot(event.xdata, event.ydata, 'bo')
self.points.append((event.xdata, event.ydata))
else:
self.voronoi()
fig.canvas.draw()
def voronoi(self):
self.ax.clear()
vor = Voronoi(self.points)
voronoi_plot_2d(vor, ax=self.ax)
v = V(ax=ax)
plt.show()
You can just save your x-y pairs in a list and convert it into an array later:
import matplotlib.pyplot as plt
import numpy as np
from scipy.spatial import Voronoi, voronoi_plot_2d
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlim([0, 10])
ax.set_ylim([0, 10])
points = []
def onclick(event):
print('x=%d, y=%d, xdata=%f, ydata=%f' %
(event.x, event.y, event.xdata, event.ydata))
points.append([event.xdata,event.ydata])
plt.plot(event.xdata, event.ydata, 'bo')
fig.canvas.draw()
cid = fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()
fig2 = plt.figure()
vor = Voronoi(np.array(points))
voronoi_plot_2d(vor)
plt.show()
As long as the user clicks in the plot, points are added to the list, until the user closes the original figure. After that a new figure is created and the Voronoi plot is drawn. Hope this helps.
I am trying to move a dot to a particular location on the graph dynamically based on real-time data. I'm basically plotting the graph, emitting a signal from a worker thread which calls the 'move_dot' function. It works, however it is slow. I can only call one frame per second. I'm using the MPL widget in pythonxy. I am also using Windows. Is there a way to speed this up?
Here is the code:
from PyQt4 import QtGui
import ui_sof_test #Gui File
import sys
from matplotlib.ticker import AutoMinorLocator
class Gui(QtGui.QMainWindow, ui_sof_test.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self) # This is defined in ui_pumptest.py file automatically
self.mpl_plot(0)
self.move_dot()
cursorplot = 0
def move_dot(self, x = 100, y = 5, color = 'r'):
fig = self.mplwidget_3.figure
par = fig.add_subplot(111)
ax3 = par.twinx()
plty = fig.gca()
plty.yaxis.set_visible(False)
ax3.plot(x, y, color, marker = 'o', linewidth = 1)
fig.canvas.draw()
#ax3.cla()
def mpl_plot(self, plot_page, replot = 0): #Data stored in lists
fig = self.mplwidget_3.figure #Add a figure
#Clears Figure if data is replotted
if replot == 1:
fig.clf()
plty = fig.gca()
plty.yaxis.set_visible(False)
par0 = fig.add_subplot(111)
#Add Axes
plt = par0.twinx()
#Plot Chart
plt.hold(False)
plt.plot([0,100,200,300,400,500], [1,3,2,4,7,5], 'b', linestyle = "dashed", linewidth = 1)
#Plot Factory Power
minorLocatorx = AutoMinorLocator()
plt.xaxis.set_minor_locator(minorLocatorx)
plt.tick_params(which='both', width= 0.5)
plt.tick_params(which='major', length=7)
plt.tick_params(which='minor', length=4, color='k')
#Plot y axis minor tick marks
minorLocatory = AutoMinorLocator()
plt.yaxis.set_minor_locator(minorLocatory)
plt.tick_params(which='both', width= 0.5)
plt.tick_params(which='major', length=7)
plt.tick_params(which='minor', length=4, color='k')
plt.minorticks_on()
#Make Border of Chart White
fig.set_facecolor('white')
#Plot Grid
plt.grid(b=True, which='both', color='k', linestyle='-')
#Manually make vertical gridlines. Above line doesn't make vertical lines for some reason
for xmaj in plt.xaxis.get_majorticklocs():
plt.axvline(x=xmaj, color = 'k',ls='-')
for xmin in plt.xaxis.get_minorticklocs():
plt.axvline(x=xmin, color = 'k', ls='-')
#Set Scales
plt.yaxis.tick_left()
# Set Axes Colors
plt.tick_params(axis='y', colors='b')
# Set Chart Labels
plt.yaxis.set_label_position("left")
plt.set_xlabel(" ")
plt.set_ylabel(" " , color = 'b')
fig.canvas.draw()
self.move_dot()
def main():
app = QtGui.QApplication(sys.argv) # A new instance of QApplication
form = Gui() # We set the form to be our ExampleApp (design)
form.show() # Show the form
app.exec_() # and execute the. app
if __name__ == '__main__': # if we're running file directly and not importing it
main() # run the main function
In MATLAB, one can use datacursormode to add annotation to a graph when user mouses over. Is there such thing in matplotlib? Or I need to write my own event using matplotlib.text.Annotation?
Late Edit / Shameless Plug: This is now available (with much more functionality) as mpldatacursor. Calling mpldatacursor.datacursor() will enable it for all matplotlib artists (including basic support for z-values in images, etc).
As far as I know, there isn't one already implemented, but it's not too hard to write something similar:
import matplotlib.pyplot as plt
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
# xdata, ydata = event.artist.get_data()
# self.x, self.y = xdata[event.ind], ydata[event.ind]
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
As it seems like at least a few people are using this, I've added an updated version below.
The new version has a simpler usage and a lot more documentation (i.e. a tiny bit, at least).
Basically you'd use it similar to this:
plt.figure()
plt.subplot(2,1,1)
line1, = plt.plot(range(10), 'ro-')
plt.subplot(2,1,2)
line2, = plt.plot(range(10), 'bo-')
DataCursor([line1, line2])
plt.show()
The main differences are that a) there's no need to manually call line.set_picker(...), b) there's no need to manually call fig.canvas.mpl_connect, and c) this version handles multiple axes and multiple figures.
from matplotlib import cbook
class DataCursor(object):
"""A simple data cursor widget that displays the x,y location of a
matplotlib artist when it is selected."""
def __init__(self, artists, tolerance=5, offsets=(-20, 20),
template='x: %0.2f\ny: %0.2f', display_all=False):
"""Create the data cursor and connect it to the relevant figure.
"artists" is the matplotlib artist or sequence of artists that will be
selected.
"tolerance" is the radius (in points) that the mouse click must be
within to select the artist.
"offsets" is a tuple of (x,y) offsets in points from the selected
point to the displayed annotation box
"template" is the format string to be used. Note: For compatibility
with older versions of python, this uses the old-style (%)
formatting specification.
"display_all" controls whether more than one annotation box will
be shown if there are multiple axes. Only one will be shown
per-axis, regardless.
"""
self.template = template
self.offsets = offsets
self.display_all = display_all
if not cbook.iterable(artists):
artists = [artists]
self.artists = artists
self.axes = tuple(set(art.axes for art in self.artists))
self.figures = tuple(set(ax.figure for ax in self.axes))
self.annotations = {}
for ax in self.axes:
self.annotations[ax] = self.annotate(ax)
for artist in self.artists:
artist.set_picker(tolerance)
for fig in self.figures:
fig.canvas.mpl_connect('pick_event', self)
def annotate(self, ax):
"""Draws and hides the annotation box for the given axis "ax"."""
annotation = ax.annotate(self.template, xy=(0, 0), ha='right',
xytext=self.offsets, textcoords='offset points', va='bottom',
bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0')
)
annotation.set_visible(False)
return annotation
def __call__(self, event):
"""Intended to be called through "mpl_connect"."""
# Rather than trying to interpolate, just display the clicked coords
# This will only be called if it's within "tolerance", anyway.
x, y = event.mouseevent.xdata, event.mouseevent.ydata
annotation = self.annotations[event.artist.axes]
if x is not None:
if not self.display_all:
# Hide any other annotation boxes...
for ann in self.annotations.values():
ann.set_visible(False)
# Update the annotation in the current axis..
annotation.xy = x, y
annotation.set_text(self.template % (x, y))
annotation.set_visible(True)
event.canvas.draw()
if __name__ == '__main__':
import matplotlib.pyplot as plt
plt.figure()
plt.subplot(2,1,1)
line1, = plt.plot(range(10), 'ro-')
plt.subplot(2,1,2)
line2, = plt.plot(range(10), 'bo-')
DataCursor([line1, line2])
plt.show()