Way back in the day when I was working in Google developer relations, we released one of my favorite tools: the Google Charts API. It was an image API which, with the right set of query parameters, could produce everything from bar charts to "Google-o-meters". The team even added custom map markers to the API at one point. I pointedly asked team "do you promise not to deprecate this?" before using the map marker output in Google Maps API demos, since Google had started getting deprecation-happy. They promised not to, but alas, just a few years later, the API was indeed deprecated!
In honor of the fallen API, I created a sample app this week that serves a simple Charts API, currently outputting just bar charts and pie charts.
To output the charts, I surveyed the landscape of Python charting options and decided to go with matplotlib, since I have at least some familiarity with it, plus it even has a documentation page showing usage in Flask.
Figure vs pyplot
When using matplotlib in Jupyter notebooks, the standard approach is to use pylot
like so:
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.pie(data["values"], labels=data.get("labels"))
However, the documentation discourages use of pyplot
in web servers and instead recommends using the Figure
class, like so:
from matplotlib.figure import Figure
fig = Figure()
axes = fig.subplots(squeeze=False)[0][0]
axes.pie(data["values"], labels=data.get("labels"))
From Figure to PNG response
The next piece of the puzzle is turning the Figure
object into a Flask Response
containing a binary PNG file. I first save the figure into a BytesIO
buffer from Python's io
module, then seek to the beginning of that buffer, and finally use send_file
to return the data with the correct mime-type.
from io import BytesIO
# Save it to a temporary buffer.
buf = BytesIO()
fig.savefig(buf, format="png")
buf.seek(0)
return send_file(buf, download_name="chart.png", mimetype="image/png")
You can see it all together in __init__.py, which also uses the APIFlask framework to parameterize and validate the routes.
No comments:
Post a Comment