04 January 2014

Posted by: Duncan
Tags: Matplotlib | Django | Python

Matplotlib, the superb graphing package from the sadly-missed John Hunter, is an essential tool for anyone involved with data visualisation. But it does have a few small drawbacks, one of which is the absence of streaming to a browser.

However, as the image and associated button below demonstrate, it is not only possible but surprisingly easy to get around this limitation;



The approach used here would work with any webserver but the example below is for Django.

The key is to redirect output of the Matplotlib plot function into a cStringIO.StringIO (Python 2) or io.BytesIO (Python 3) object and dump that into the Django response object. This solution is, IMO, a lot nicer than the usual approach of using PIL as cStringIO/io is part of the standard Python library and so the additional image processing package is not needed.

Here is the code in full;

import sys
from django.http import HttpResponse
import matplotlib as mpl
mpl.use('Agg') # Required to redirect locally
import matplotlib.pyplot as plt
import numpy as np
from numpy.random import rand
try:
    # Python 2
    import cStringIO
except ImportError:
    # Python 3
    import io

def get_image(request):
   """
   This is an example script from the Matplotlib website, just to show 
   a working sample >>>
   """
   N = 50
   x = np.random.rand(N)
   y = np.random.rand(N)
   colors = np.random.rand(N)
   area = np.pi * (15 * np.random.rand(N))**2 # 0 to 15 point radiuses
   plt.scatter(x, y, s=area, c=colors, alpha=0.5)
   """
   Now the redirect into the cStringIO or BytesIO object >>>
   """
   if cStringIO in sys.modules:
      f = cStringIO.StringIO()   # Python 2
   else:
      f = io.BytesIO()           # Python 3
   plt.savefig(f, format="png", facecolor=(0.95,0.95,0.95))
   plt.clf()
   """
   Add the contents of the StringIO or BytesIO object to the response, matching the
   mime type with the plot format (in this case, PNG) and return >>>
   """
   return HttpResponse(f.getvalue(), content_type="image/png")

NB: the matplotlib.use('Agg') directive specifies the back-end renderer to use, in this case the Anti-Grain Geometry high-quality image renderer. Other renderers are available. However, a back-end renderer must be specified otherwise the redirect will fail.

That's all that's needed. This approach would work with any web framework; the only difference would be to adapt the response format accordingly.

Links