I have been writing a program to show quotes from Yahoo Finance service. After a few searches I know Matplotlib has matplotlib.widgets.Cursor to do the task, here is the example code. It’s not the kind of cursor we want, the cursor in such program must to snap its horizontal line to the price in figure.

So this snap version example could fit the need. This cursor manually draw the cursor. It works fine if your figure only have one or two data lines. If you use something like matplotlib.finance.CandleSticks, which plot many things on your figure. You will see the lag of movement of your cursor.

The first example has a way to deal with that, it’s called Blit, you can read more about it at this page. Basically, you save the rendered image, every time you need to draw your cursor, you restore that saved image, then you draw your cursor. That would save a lot of time. There is a another example code for Blit.

I wrote my own example as following:

#!/usr/bin/env python


import datetime

import matplotlib
matplotlib.use('GTKAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas
import matplotlib.finance as finance
import matplotlib.mlab as mlab

from numpy import searchsorted, array

try:
  import pygtk
  pygtk.require("2.0")
except:
  print "You need to install pyGTK or GTKv2 or set your PYTHONPATH correctly"
  sys.exit(1)

import gtk


# Modified from http://matplotlib.sourceforge.net/examples/pylab_examples/cursor_demo.html
class SnaptoCursor:

    def __init__(self, ax, x, y, useblit=True):

        self.ax = ax
        self.lx = None
        self.ly = None
        self.x = x
        self.y = y
        self.bg = None
        self.useblit = useblit

    def mouse_move(self, event):

        if not event.inaxes:
          return

        ax = event.inaxes
        minx, maxx = ax.get_xlim()
        miny, maxy = ax.get_ylim()

        if self.useblit and self.bg is None:
          self.bg = ax.figure.canvas.copy_from_bbox(ax.bbox)
        ax.figure.canvas.restore_region(self.bg)

        x, y = event.xdata, event.ydata

        indx = searchsorted(self.x, [x])[0]
        if indx == len(self.x):
          indx = len(self.x) - 1
        x = self.x[indx]
        y = self.y[indx]
        # update the line positions
        if self.lx is not None:
          self.lx.set_data((minx, maxx), (y, y))
          self.ly.set_data((x, x), (miny, maxy))
        else:
          color = 'b-' if self.useblit else 'r-'
          self.lx, = ax.plot((minx, maxx), (y, y), color)  # the horiz line
          self.ly, = ax.plot((x, x), (miny, maxy), color)  # the vert line

        if self.useblit:
          ax.draw_artist(self.lx)
          ax.draw_artist(self.ly)
          ax.figure.canvas.blit(ax.bbox)
        else:
          ax.figure.canvas.draw()


def create_figure(quotes):

  f = Figure(figsize=(5,4), dpi=100)

  a = f.add_subplot(111)
  canvas = FigureCanvas(f)  # a gtk.DrawingArea
  canvas.set_size_request(800,300)

  a.xaxis_date()

  finance.candlestick(a, quotes, width=0.5)

  return f


def main():

  win = gtk.Window()
  win.connect('destroy', gtk.main_quit)
  win.set_title('Cursors')

  vbox = gtk.VBox()
  win.add(vbox)

  # Get data from Yahoo Finance
  enddate = datetime.date.today()
  startdate = enddate + datetime.timedelta(days=-72)
  quotes = finance.quotes_historical_yahoo('GOOG', startdate, enddate)

  qa = array(quotes)

  f = create_figure(quotes)
  a = f.gca()
  vbox.pack_start(gtk.Label('No Blit'), False, False)
  vbox.pack_start(f.canvas)

  cursor1 = SnaptoCursor(a, qa[:,0], qa[:,2], useblit=False)
  f.canvas.mpl_connect('motion_notify_event', cursor1.mouse_move)

  f = create_figure(quotes)
  a = f.gca()
  vbox.pack_start(gtk.Label('Use Blit'), False, False)
  vbox.pack_start(f.canvas)

  cursor2 = SnaptoCursor(a, qa[:,0], qa[:,2], useblit=True)
  f.canvas.mpl_connect('motion_notify_event', cursor2.mouse_move)

  win.show_all()
  gtk.main()


if __name__ == '__main__':

  main()

A quick screenshot12:

[1]http://xs1144.xs.to/xs1144/09434/matplotlib-cursor371.png is gone.
[2]http://xs1144.xs.to/xs1144/09434/matplotlib-cursor661.png is gone.