Animate like Google Finance charts in Matplotlib?
I just started toying around with Matplotlib's Animation capabilities in order to produce a Google Finance looking chart.
I combined two examples I found on the project website (Draggable rectangle exercise, api example code: date_demo.py) and tweaked them a bit to come up with the code listed at the bottom.
While it doesn't look too bad, I would like the top chart (master) update dynamically as the bottom chart (slave) selection is moved around, and not only when the bottom sel开发者_如何学运维ection is released. How can I do this? I tried to move the self.rect.figure.canvas.draw()
bit to the on_motion
method, but it seems to interfere with the blit stuff as the bottom selection won't render properly.
So I would assume the solution would be to do the intelligent animation for the bottom chart, i.e., the blit-ing bit, while the top chart is just re-drawn altogether. The issue is that the only way I can redraw anything is through the re-drawing the whole canvas, and this would include the bottom chart. I did find the draw()
method for matplotlib.axes
, but I can't get it to work. As I said above, preferably I would like to just re-draw the top chart while the bottom one is blit-ed the clever way. Does anyone know how to do this?
Here is my code so far. Please excuse the code, it's a bit untidy.
import datetime
import numpy as np
import sys
import time
import wx
import matplotlib
from matplotlib.figure import Figure
import matplotlib.dates as mdates
import matplotlib.ticker as mtickers
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
import matplotlib.patches as mpatches
class DraggableRectangle:
lock = None
def __init__(self, rect, master, xMin, xMax):
self.rect = rect
self.press = None
self.background = None
self.xMax = xMax
self.xMin = xMin
self.master = master
def connect(self):
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):
if event.inaxes != self.rect.axes: return
if DraggableRectangle.lock is not None: return
contains, attrd = self.rect.contains(event)
if not contains: return
x0, y0 = self.rect.xy
self.press = x0, y0, event.xdata, event.ydata
DraggableRectangle.lock = self
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)
axes.draw_artist(self.rect)
canvas.blit(axes.bbox)
def on_motion(self, event):
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 = 0
if x0+dx > self.xMax:
self.rect.set_x(self.xMax)
elif x0+dx < self.xMin:
self.rect.set_x(self.xMin)
else:
self.rect.set_x(x0+dx)
self.rect.set_y(y0+dy)
canvas = self.rect.figure.canvas
axes = self.rect.axes
canvas.restore_region(self.background)
self.master.set_xlim(self.rect.get_x(), self.rect.get_x() + 92)
axes.draw_artist(self.rect)
canvas.blit(axes.bbox)
def on_release(self, event):
if DraggableRectangle.lock is not self: return
self.press = None
DraggableRectangle.lock = None
self.rect.set_animated(False)
self.background = None
self.rect.figure.canvas.draw()
def disconnect(self):
self.rect.figure.canvas.mpl_disconnect(self.cidpress)
self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
self.rect.figure.canvas.mpl_disconnect(self.cidmotion)
class MplCanvasFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, title='First Chart', size=(800, 700))
datafile = matplotlib.get_example_data('goog.npy')
r = np.load(datafile).view(np.recarray)
datesFloat = matplotlib.dates.date2num(r.date)
figure = Figure()
xMaxDatetime = r.date[len(r.date)-1]
xMinDatetime = r.date[0]
xMaxFloat = datesFloat[len(datesFloat)-1]
xMinFloat = datesFloat[0]
yMin = min(r.adj_close) // 5 * 5
yMax = (1 + max(r.adj_close) // 5) * 5
master = figure.add_subplot(211)
master.plot(datesFloat, r.adj_close)
master.xaxis.set_minor_locator(mdates.MonthLocator())
master.xaxis.set_major_locator(mdates.MonthLocator(bymonth=(1,4,7,10)))
master.xaxis.set_major_formatter(mdates.DateFormatter('%b-%y'))
master.set_xlim(datesFloat[120], datesFloat[120]+92)
master.yaxis.set_minor_locator(mtickers.MultipleLocator(50))
master.yaxis.set_major_locator(mtickers.MultipleLocator(100))
master.set_ylim(yMin, yMax)
master.set_position([0.05,0.20,0.92,0.75])
master.xaxis.grid(True, which='minor')
master.yaxis.grid(True, which='minor')
slave = figure.add_subplot(212, yticks=[])
slave.plot(datesFloat, r.adj_close)
slave.xaxis.set_minor_locator(mdates.MonthLocator())
slave.xaxis.set_major_locator(mdates.YearLocator())
slave.xaxis.set_major_formatter(mdates.DateFormatter('%b-%y'))
slave.set_xlim(xMinDatetime, xMaxDatetime)
slave.set_ylim(yMin, yMax)
slave.set_position([0.05,0.05,0.92,0.10])
rectangle = mpatches.Rectangle((datesFloat[120], yMin), 92, yMax-yMin, facecolor='yellow', alpha = 0.4)
slave.add_patch(rectangle)
canvas = FigureCanvas(self, -1, figure)
drag = DraggableRectangle(rectangle, master, xMinFloat, xMaxFloat - 92)
drag.connect()
app = wx.PySimpleApp()
frame = MplCanvasFrame()
frame.Show(True)
app.MainLoop()
I had a chance to work on this this morning (we are having a 2nd blizzard for the last 3 days). You are right, if you try to redraw the entire figure in the on_motion, it messes up the animation of the yellow rectangle. The key is to also blit the line on the master sub plot.
Try this code out:
import datetime
import numpy as np
import sys
import time
import wx
import matplotlib
from matplotlib.figure import Figure
import matplotlib.dates as mdates
import matplotlib.ticker as mtickers
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
import matplotlib.patches as mpatches
class DraggableRectangle:
lock = None
def __init__(self, rect, master, xMin, xMax):
self.rect = rect
self.press = None
self.slave_background = None
self.master_background = None
self.xMax = xMax
self.xMin = xMin
self.master = master
self.master_line, = self.master.get_lines()
def connect(self):
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):
if event.inaxes != self.rect.axes: return
if DraggableRectangle.lock is not None: return
contains, attrd = self.rect.contains(event)
if not contains: return
x0, y0 = self.rect.xy
self.press = x0, y0, event.xdata, event.ydata
DraggableRectangle.lock = self
canvas = self.rect.figure.canvas
axes = self.rect.axes
# set up our animated elements
self.rect.set_animated(True)
self.master_line.set_animated(True)
self.master.xaxis.set_visible(False) #we are not animating this
canvas.draw()
# backgrounds for restoring on animation
self.slave_background = canvas.copy_from_bbox(self.rect.axes.bbox)
self.master_background = canvas.copy_from_bbox(self.master.axes.bbox)
axes.draw_artist(self.rect)
canvas.blit(axes.bbox)
def on_motion(self, event):
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 = 0
if x0+dx > self.xMax:
self.rect.set_x(self.xMax)
elif x0+dx < self.xMin:
self.rect.set_x(self.xMin)
else:
self.rect.set_x(x0+dx)
self.rect.set_y(y0+dy)
canvas = self.rect.figure.canvas
axes = self.rect.axes
# restore backgrounds
canvas.restore_region(self.slave_background)
canvas.restore_region(self.master_background)
# set our limits for animated line
self.master.set_xlim(self.rect.get_x(), self.rect.get_x() + 92)
# draw yellow box
axes.draw_artist(self.rect)
canvas.blit(axes.bbox)
#draw line
self.master.axes.draw_artist(self.master_line)
canvas.blit(self.master.axes.bbox)
def on_release(self, event):
if DraggableRectangle.lock is not self: return
self.press = None
DraggableRectangle.lock = None
# unanimate rect and lines
self.rect.set_animated(False)
self.master_line.set_animated(False)
self.slave_background = None
self.master_background = None
# redraw whole figure
self.master.xaxis.set_visible(True)
self.rect.figure.canvas.draw()
def disconnect(self):
self.rect.figure.canvas.mpl_disconnect(self.cidpress)
self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
self.rect.figure.canvas.mpl_disconnect(self.cidmotion)
class MplCanvasFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, title='First Chart', size=(800, 700))
datafile = matplotlib.get_example_data('goog.npy')
r = np.load(datafile).view(np.recarray)
datesFloat = matplotlib.dates.date2num(r.date)
figure = Figure()
xMaxDatetime = r.date[len(r.date)-1]
xMinDatetime = r.date[0]
xMaxFloat = datesFloat[len(datesFloat)-1]
xMinFloat = datesFloat[0]
yMin = min(r.adj_close) // 5 * 5
yMax = (1 + max(r.adj_close) // 5) * 5
master = figure.add_subplot(211)
master.plot(datesFloat, r.adj_close)
master.xaxis.set_minor_locator(mdates.MonthLocator())
master.xaxis.set_major_locator(mdates.MonthLocator(bymonth=(1,4,7,10)))
master.xaxis.set_major_formatter(mdates.DateFormatter('%b-%y'))
master.set_xlim(datesFloat[120], datesFloat[120]+92)
master.yaxis.set_minor_locator(mtickers.MultipleLocator(50))
master.yaxis.set_major_locator(mtickers.MultipleLocator(100))
master.set_ylim(yMin, yMax)
master.set_position([0.05,0.20,0.92,0.75])
master.xaxis.grid(True, which='minor')
master.yaxis.grid(True, which='minor')
slave = figure.add_subplot(212, yticks=[])
slave.plot(datesFloat, r.adj_close)
slave.xaxis.set_minor_locator(mdates.MonthLocator())
slave.xaxis.set_major_locator(mdates.YearLocator())
slave.xaxis.set_major_formatter(mdates.DateFormatter('%b-%y'))
slave.set_xlim(xMinDatetime, xMaxDatetime)
slave.set_ylim(yMin, yMax)
slave.set_position([0.05,0.05,0.92,0.10])
rectangle = mpatches.Rectangle((datesFloat[120], yMin), 92, yMax-yMin, facecolor='yellow', alpha = 0.4)
slave.add_patch(rectangle)
canvas = FigureCanvas(self, -1, figure)
drag = DraggableRectangle(rectangle, master, xMinFloat, xMaxFloat - 92)
drag.connect()
app = wx.PySimpleApp()
frame = MplCanvasFrame()
frame.Show(True)
app.MainLoop()
精彩评论