|
1 import os |
|
2 |
|
3 from logilab.common import flatten |
|
4 |
|
5 from cubicweb.web.views import baseviews |
|
6 |
|
7 def plot_selector(cls, req, rset, *args, **kwargs): |
|
8 """accept result set with at least one line and two columns of result |
|
9 all columns after second must be of numerical types""" |
|
10 if rset is None: |
|
11 return 0 |
|
12 if not len(rset): |
|
13 return 0 |
|
14 if len(rset.rows[0]) < 2: |
|
15 return 0 |
|
16 for etype in rset.description[0]: |
|
17 if etype not in ('Int', 'Float'): |
|
18 return 0 |
|
19 return 1 |
|
20 |
|
21 try: |
|
22 import matplotlib |
|
23 import sys |
|
24 if 'matplotlib.backends' not in sys.modules: |
|
25 matplotlib.use('Agg') |
|
26 from matplotlib.ticker import FormatStrFormatter |
|
27 from pylab import figure, show |
|
28 except ImportError: |
|
29 pass |
|
30 else: |
|
31 class PlotView(baseviews.AnyRsetView): |
|
32 id = 'plot' |
|
33 title = _('generic plot') |
|
34 binary = True |
|
35 content_type = 'image/png' |
|
36 _plot_count = 0 |
|
37 __selectors__ = (plot_selector,) |
|
38 |
|
39 def call(self, width=None, height=None): |
|
40 # compute dimensions |
|
41 if width is None: |
|
42 if 'width' in self.req.form: |
|
43 width = int(self.req.form['width']) |
|
44 else: |
|
45 width = 500 |
|
46 |
|
47 if height is None: |
|
48 if 'height' in self.req.form: |
|
49 height = int(self.req.form['height']) |
|
50 else: |
|
51 height = 400 |
|
52 dpi = 100. |
|
53 |
|
54 # compute data |
|
55 abscisses = [row[0] for row in self.rset] |
|
56 courbes = [] |
|
57 nbcols = len(self.rset.rows[0]) |
|
58 for col in range(1,nbcols): |
|
59 courbe = [row[col] for row in self.rset] |
|
60 courbes.append(courbe) |
|
61 if not courbes: |
|
62 raise Exception('no data') |
|
63 # plot data |
|
64 fig = figure(figsize=(width/dpi, height/dpi), dpi=dpi) |
|
65 ax = fig.add_subplot(111) |
|
66 colors = 'brgybrgy' |
|
67 try: |
|
68 float(abscisses[0]) |
|
69 xlabels = None |
|
70 except ValueError: |
|
71 xlabels = abscisses |
|
72 abscisses = range(len(xlabels)) |
|
73 for idx,courbe in enumerate(courbes): |
|
74 ax.plot(abscisses, courbe, '%sv-' % colors[idx], label=self.rset.description[0][idx+1]) |
|
75 ax.autoscale_view() |
|
76 alldata = flatten(courbes) |
|
77 m, M = min(alldata or [0]), max(alldata or [1]) |
|
78 if m is None: m = 0 |
|
79 if M is None: M = 0 |
|
80 margin = float(M-m)/10 |
|
81 ax.set_ylim(m-margin, M+margin) |
|
82 ax.grid(True) |
|
83 ax.legend(loc='best') |
|
84 if xlabels is not None: |
|
85 ax.set_xticks(abscisses) |
|
86 ax.set_xticklabels(xlabels) |
|
87 try: |
|
88 fig.autofmt_xdate() |
|
89 except AttributeError: |
|
90 # XXX too old version of matplotlib. Ignore safely. |
|
91 pass |
|
92 |
|
93 # save plot |
|
94 filename = self.build_figname() |
|
95 fig.savefig(filename, dpi=100) |
|
96 img = open(filename, 'rb') |
|
97 self.w(img.read()) |
|
98 img.close() |
|
99 os.remove(filename) |
|
100 |
|
101 def build_figname(self): |
|
102 self.__class__._plot_count += 1 |
|
103 return '/tmp/burndown_chart_%s_%d.png' % (self.config.appid, self.__class__._plot_count) |