web/views/plots.py
changeset 0 b97547f5f1fa
child 742 99115e029dca
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     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)