| 1 | This describes a simple way to create dynamic bitmap charts in Django. |
| 2 | |
| 3 | !ReportLab's PDF library is well known, but less people are aware that it contains a |
| 4 | mature graphics package able to create most kinds of businss charts, as well as custom graphical widgets. |
| 5 | 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. |
| 6 | |
| 7 | = Installation = |
| 8 | You will need the !ReportLab library from http://www.reportlab.org/ |
| 9 | |
| 10 | Also make sure you have compiled the ''_rl_accel'' and ''_renderPM'' packages. The latter |
| 11 | is a sophisticated bitmap renderer with anti-aliasing, font handling and so on. |
| 12 | ReportLab's download page has a checker script to tell you what extensions |
| 13 | are installed. |
| 14 | |
| 15 | = Creating a chart = |
| 16 | The reportlab/graphics framework provides a Drawing object and many Shape objects, |
| 17 | which include primitive shapes, and high-level objects like charts and legends. You |
| 18 | can easily write your own too if you want to make dynamic bitmap buttons or fancy |
| 19 | dashboard apps. |
| 20 | |
| 21 | The usual pattern we follow is to create a Drawing class with all of your widgets, |
| 22 | some sensible default data, and any visual settings you want. Here is such a class, |
| 23 | which has a bar chart and a string for the chart title. |
| 24 | |
| 25 | {{{ |
| 26 | # mycharts.py |
| 27 | from reportlab.graphics.shapes import Drawing, String |
| 28 | from reportlab.graphics.charts.barcharts import HorizontalBarChart |
| 29 | |
| 30 | class MyBarChartDrawing(Drawing): |
| 31 | def __init__(self, width=400, height=200, *args, **kw): |
| 32 | apply(Drawing.__init__,(self,width,height)+args,kw) |
| 33 | self.add(HorizontalBarChart(), name='chart') |
| 34 | |
| 35 | self.add(String(200,180,'Hello World'), name='title') |
| 36 | |
| 37 | #set any shapes, fonts, colors you want here. We'll just |
| 38 | #set a title font and place the chart within the drawing |
| 39 | self.chart.x = 20 |
| 40 | self.chart.y = 20 |
| 41 | self.chart.width = self.width - 20 |
| 42 | self.chart.height = self.height - 40 |
| 43 | |
| 44 | self.title.fontName = 'Helvetica-Bold' |
| 45 | self.title.fontSize = 12 |
| 46 | |
| 47 | self.chart.data = [[100,150,200,235]] |
| 48 | |
| 49 | |
| 50 | if __name__=='__main__': |
| 51 | #use the standard 'save' method to save barchart.gif, barchart.pdf etc |
| 52 | #for quick feedback while working. |
| 53 | MyBarChartDrawing().save(formats=['gif','png','jpg','pdf'],outDir='.',fnRoot='barchart') |
| 54 | }}} |
| 55 | |
| 56 | |
| 57 | Paste this into a file and execute it; you should get 4 chart files written to |
| 58 | disk in different formats. |
| 59 | |
| 60 | = Integrating into Django = |
| 61 | |
| 62 | Now we add a view to our ''views.py''. This will examine the request |
| 63 | for any dynamic parameters, since there's not much point serving |
| 64 | a chart that doesn't vary. We'll allow the user to pass in 4 |
| 65 | things as GET or POST parameters: a title, a comma-separated list of numbers, |
| 66 | and the overall width and height of the image. Everything has a default in |
| 67 | our Drawing class anyway, so we only pass through parameters which are |
| 68 | present. |
| 69 | |
| 70 | You then ask the Drawing to render itself to your favourite bitmap format |
| 71 | and generate a response with the right content-type |
| 72 | |
| 73 | {{{ |
| 74 | def barchart(request): |
| 75 | |
| 76 | #instantiate a drawing object |
| 77 | import mycharts |
| 78 | d = mycharts.MyBarChartDrawing() |
| 79 | |
| 80 | #extract the request params of interest. |
| 81 | #I suggest having a default for everything. |
| 82 | if request.has_key('height'): |
| 83 | d.height = int(request['height']) |
| 84 | if request.has_key('width'): |
| 85 | d.height = int(request['width']) |
| 86 | |
| 87 | if request.has_key('numbers'): |
| 88 | strNumbers = request['numbers'] |
| 89 | numbers = map(int, strNumbers.split(',')) |
| 90 | d.chart.data = [numbers] #bar charts take a list-of-lists for data |
| 91 | |
| 92 | if request.has_key('title'): |
| 93 | d.title.text = request['title'] |
| 94 | |
| 95 | |
| 96 | #get a GIF (or PNG, JPG, or whatever) |
| 97 | binaryStuff = d.asString('gif') |
| 98 | return HttpResponse(binaryStuff, 'image/gif') |
| 99 | }}} |
| 100 | |
| 101 | Finally, you need a URL mapping. In this case I have added this: |
| 102 | |
| 103 | {{{ |
| 104 | (r'^charts/bar/$', 'myapp.views.barchart'), |
| 105 | |
| 106 | }}} |
| 107 | |
| 108 | Now you can start Django, point your browser at the URL with no arguments, |
| 109 | and should see the chart. Then try out a few parameters such as |
| 110 | http://localhost:8000/charts/bar?title=awesome |
| 111 | and watch it redraw. |
| 112 | |
| 113 | = Tips = |
| 114 | The above approach will let you drive a chart by generating |
| 115 | img tags with all parameters embedded in them. This will also let the world |
| 116 | use you as a chart server, so you might want to implement some authentication |
| 117 | on the chart requests. |
| 118 | |
| 119 | Very often you are plotting data which mostly exists on the server side. |
| 120 | In this case it's inefficient to encode it all in a long URL in the HTML |
| 121 | then have this posted back to make the chart. For example, |
| 122 | if you run an ISP and are plotting server uptime statistics, presumably |
| 123 | the URL or parameters would encode the customer and server ID, and you'd |
| 124 | look up all the numeric data or that chart in your view code in a database query. |
| 125 | |
| 126 | If you cannot always create a sensible chart from the input, it |
| 127 | may be a good idea to create an error handler which returns a rectangular |
| 128 | drawing with an error message, because this will usually be called inside an <img> tag |
| 129 | and the browser won't display any HTML error messages if it was expecting an image. |
| 130 | |
| 131 | = Learning More = |
| 132 | The available chart types, widgets and properties are covered in the two |
| 133 | Graphics manuals on this page. Enjoy! |
| 134 | http://www.reportlab.org/os_documentation.html |
| 135 | |
| 136 | If you have questions about the charts rather than the Django integration, |
| 137 | ask on the reportlab-users list: |
| 138 | http://two.pairlist.net/mailman/listinfo/reportlab-users |