Version 8 (modified by 16 years ago) ( diff ) | ,
---|
Creating bitmap charts in Django with the ReportLab library
This describes a simple way to create dynamic bitmap charts in Django.
ReportLab's PDF library is well known, but less people are aware that it contains a mature graphics package able to create most kinds of business charts, as well as custom graphical widgets. We've been serving GIF and PNG charts for many years. Doing this within Django has turned out to be trivial; here's an example.
Installation
You will need the ReportLab library from http://www.reportlab.org/
Also make sure you have compiled the _rl_accel and _renderPM packages. The latter is a sophisticated bitmap renderer with anti-aliasing, font handling and so on. ReportLab's download page has a checker script to tell you what extensions are installed.
Creating a Horizontal Bar chart
The reportlab/graphics framework provides a Drawing object and many Shape objects, which include primitive shapes, and high-level objects like charts and legends. You can easily write your own too if you want to make dynamic bitmap buttons or fancy dashboard apps.
The usual pattern we follow is to create a Drawing class with all of your widgets, some sensible default data, and any visual settings you want. Here is such a class, which has a bar chart and a string for the chart title.
# mycharts.py from reportlab.graphics.shapes import Drawing, String from reportlab.graphics.charts.barcharts import HorizontalBarChart class MyBarChartDrawing(Drawing): def __init__(self, width=400, height=200, *args, **kw): apply(Drawing.__init__,(self,width,height)+args,kw) self.add(HorizontalBarChart(), name='chart') self.add(String(200,180,'Hello World'), name='title') #set any shapes, fonts, colors you want here. We'll just #set a title font and place the chart within the drawing self.chart.x = 20 self.chart.y = 20 self.chart.width = self.width - 20 self.chart.height = self.height - 40 self.title.fontName = 'Helvetica-Bold' self.title.fontSize = 12 self.chart.data = [[100,150,200,235]] if __name__=='__main__': #use the standard 'save' method to save barchart.gif, barchart.pdf etc #for quick feedback while working. MyBarChartDrawing().save(formats=['gif','png','jpg','pdf'],outDir='.',fnRoot='barchart')
Paste this into a file and execute it; you should get 4 chart files written to disk in different formats.
Integrating into Django
Now we add a view to our views.py. This will examine the request for any dynamic parameters, since there's not much point serving a chart that doesn't vary. We'll allow the user to pass in 4 things as GET or POST parameters: a title, a comma-separated list of numbers, and the overall width and height of the image. Everything has a default in our Drawing class anyway, so we only pass through parameters which are present.
You then ask the Drawing to render itself to your favourite bitmap format and generate a response with the right content-type
def barchart(request): #instantiate a drawing object import mycharts d = mycharts.MyBarChartDrawing() #extract the request params of interest. #I suggest having a default for everything. if request.has_key('height'): d.height = int(request['height']) if request.has_key('width'): d.width = int(request['width']) if request.has_key('numbers'): strNumbers = request['numbers'] numbers = map(int, strNumbers.split(',')) d.chart.data = [numbers] #bar charts take a list-of-lists for data if request.has_key('title'): d.title.text = request['title'] #get a GIF (or PNG, JPG, or whatever) binaryStuff = d.asString('gif') return HttpResponse(binaryStuff, 'image/gif')
Finally, you need a URL mapping. In this case I have added this:
(r'^charts/bar/$', 'myapp.views.barchart'),
Now you can start Django, point your browser at the URL with no arguments, and should see the chart. Then try out a few parameters such as
and watch it redraw.
Creating a Line chart
# mycharts.py from reportlab.graphics.shapes import Drawing, String from reportlab.graphics.charts.lineplots import LinePlot from reportlab.graphics.charts.lineplots import ScatterPlot from reportlab.lib import colors from reportlab.graphics.charts.legends import Legend from reportlab.graphics.charts.textlabels import Label from reportlab.graphics.widgets.markers import makeMarker class MyLineChartDrawing(Drawing): def __init__(self, width=600, height=400, *args, **kw): apply(Drawing.__init__,(self,width,height)+args,kw) self.add(LinePlot(), name='chart') self.add(String(200,180,'Hello World'), name='title') #set any shapes, fonts, colors you want here. We'll just #set a title font and place the chart within the drawing. #pick colors for all the lines, do as many as you need self.chart.x = 20 self.chart.y = 30 self.chart.width = self.width - 100 self.chart.height = self.height - 75 self.chart.lines[0].strokeColor = colors.blue self.chart.lines[1].strokeColor = colors.green self.chart.lines[2].strokeColor = colors.yellow self.chart.lines[3].strokeColor = colors.red self.chart.lines[4].strokeColor = colors.black self.chart.lines[5].strokeColor = colors.orange self.chart.lines[6].strokeColor = colors.cyan self.chart.lines[7].strokeColor = colors.magenta self.chart.lines[8].strokeColor = colors.brown self.chart.fillColor = colors.white self.title.fontName = 'Times-Roman' self.title.fontSize = 18 self.chart.data = [((0, 50), (100,100), (200,200), (250,210), (300,300), (400,500))] self.chart.xValueAxis.labels.fontSize = 12 self.chart.xValueAxis.forceZero = 0 self.chart.xValueAxis.gridEnd = 115 self.chart.xValueAxis.tickDown = 3 self.chart.xValueAxis.visibleGrid = 1 self.chart.yValueAxis.tickLeft = 3 self.chart.yValueAxis.labels.fontName = 'Times-Roman' self.chart.yValueAxis.labels.fontSize = 12 self.title.x = self.width/2 self.title.y = 0 self.title.textAnchor ='middle' self.add(Legend(),name='Legend') self.Legend.fontName = 'Times-Roman' self.Legend.fontSize = 12 self.Legend.x = self.width self.Legend.y = 85 self.Legend.dxTextSpace = 5 self.Legend.dy = 5 self.Legend.dx = 5 self.Legend.deltay = 5 self.Legend.alignment ='right' self.add(Label(),name='XLabel') self.XLabel.fontName = 'Times-Roman' self.XLabel.fontSize = 12 self.XLabel.x = 85 self.XLabel.y = 5 self.XLabel.textAnchor ='middle' #self.XLabel.height = 20 self.XLabel._text = "" self.add(Label(),name='YLabel') self.YLabel.fontName = 'Times-Roman' self.YLabel.fontSize = 12 self.YLabel.x = 2 self.YLabel.y = 80 self.YLabel.angle = 90 self.YLabel.textAnchor ='middle' self.YLabel._text = "" self.chart.yValueAxis.forceZero = 1 self.chart.xValueAxis.forceZero = 1
Integrating into Django
Now we add a view to our views.py.
def linechart(request): #instantiate a drawing object d = MyLineChartDrawing() #extract the request params of interest. #I suggest having a default for everything. d.height = 300 d.chart.height = 300 d.width = 300 d.chart.width = 300 d.title.text = request.session.get('Some custom title') d.XLabel._text = request.session.get('X Axis Labell') d.YLabel._text = request.session.get('Y Axis Label') d.chart.data = [((1,1), (2,2), (2.5,1), (3,3), (4,5)),((1,2), (2,3), (2.5,2), (3.5,5), (4,6))] labels = ["Label One","Label Two"] if labels: cnt=0 # set colors in the legend d.Legend.colorNamePairs = [] for label in labels: d.Legend.colorNamePairs.append((d.chart.lines[cnt].strokeColor,label)) cnt=cnt+1 #get a GIF (or PNG, JPG, or whatever) binaryStuff = d.asString('gif') return HttpResponse(binaryStuff, 'image/gif')
Finally, you need a URL mapping. In this case I have added this:
(r'^charts/line/$', 'myapp.views.linechart'),
Now you can start Django, point your browser at the URL with no arguments, and should see the chart. Then try out a few parameters such as
and watch it redraw.
Tips
The above approach will let you drive a chart by generating img tags with all parameters embedded in them. This will also let the world use you as a chart server, so you might want to implement some authentication on the chart requests.
Very often you are plotting data which mostly exists on the server side. In this case it's inefficient to encode it all in a long URL in the HTML then have this posted back to make the chart. For example, if you run an ISP and are plotting server uptime statistics, presumably the URL or parameters would encode the customer and server ID, and you'd look up all the numeric data or that chart in your view code in a database query.
If you cannot always create a sensible chart from the input, it may be a good idea to create an error handler which returns a rectangular drawing with an error message, because this will usually be called inside an <img> tag and the browser won't display any HTML error messages if it was expecting an image.
Disclaimer
The author is pretty new to Django and if there are nicer ways to do this I would welcome feedback...
Learning more about the chart library
The available chart types, widgets and properties are covered in the two Graphics manuals on this page. Enjoy!
If you have questions about the charts rather than the Django integration, ask on the reportlab-users list: