| | 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 |