web/views/plots.py
changeset 1891 dd7c1d7715e7
parent 1888 f36d43f00f32
child 1892 fb80d9c434e5
equal deleted inserted replaced
1889:874a055c373b 1891:dd7c1d7715e7
    57         if x is None or y is None:
    57         if x is None or y is None:
    58             continue
    58             continue
    59         filtered.append( (x, y) )
    59         filtered.append( (x, y) )
    60     return sorted(filtered)
    60     return sorted(filtered)
    61 
    61 
       
    62 def datetime2ticks(date):
       
    63     return time.mktime(date.timetuple()) * 1000
       
    64 
       
    65 class FlotPlotWidget(object):
       
    66     """PlotRenderer widget using Flot"""
       
    67     onload = u'''
       
    68 %(plotdefs)s
       
    69 jQuery.plot(jQuery("#%(figid)s"), [%(plotdata)s],
       
    70     {points: {show: true},
       
    71      lines: {show: true},
       
    72      grid: {hoverable: true},
       
    73      xaxis: {mode: %(mode)s}});
       
    74 jQuery('#%(figid)s').bind('plothover', onPlotHover);
       
    75 '''
       
    76     
       
    77     def __init__(self, labels, plots, timemode=False):
       
    78         self.labels = labels
       
    79         self.plots = plots # list of list of couples
       
    80         self.timemode = timemode
       
    81 
       
    82     def dump_plot(self, plot):
       
    83         # XXX for now, the only way that we have to customize properly
       
    84         #     datetime labels on tooltips is to insert an additional column
       
    85         #     cf. function onPlotHover in cubicweb.flot.js
       
    86         if self.timemode:
       
    87             plot = [(datetime2ticks(x), y, datetime2ticks(x)) for x,y in plot]
       
    88         return dumps(plot)
       
    89     
       
    90     def render(self, req, width=500, height=400, w=None):
       
    91         # XXX IE requires excanvas.js
       
    92         req.add_js( ('jquery.flot.js', 'cubicweb.flot.js') )
       
    93         figid = u'figure%s' % make_uid('foo')
       
    94         plotdefs = []
       
    95         plotdata = []
       
    96         w(u'<div id="%s" style="width: %spx; height: %spx;"></div>' %
       
    97           (figid, width, height))
       
    98         for idx, (label, plot) in enumerate(zip(self.labels, self.plots)):
       
    99             plotid = '%s_%s' % (figid, idx)
       
   100             plotdefs.append('var %s = %s;' % (plotid, self.dump_plot(plot)))
       
   101             plotdata.append("{label: '%s', data: %s}" % (label, plotid))
       
   102         req.html_headers.add_onload(self.onload %
       
   103                                     {'plotdefs': '\n'.join(plotdefs),
       
   104                                      'figid': figid,
       
   105                                      'plotdata': ','.join(plotdata),
       
   106                                      'mode': self.timemode and "'time'" or 'null'})
       
   107     
       
   108 
    62 class PlotView(baseviews.AnyRsetView):
   109 class PlotView(baseviews.AnyRsetView):
    63     id = 'plot'
   110     id = 'plot'
    64     title = _('generic plot')
   111     title = _('generic plot')
    65     __select__ = at_least_two_columns() & all_columns_are_numbers()
   112     __select__ = at_least_two_columns() & all_columns_are_numbers()
    66     mode = 'null' # null or time, meant for jquery.flot.js
   113     timemode = False
    67 
       
    68     def _build_abscissa(self):
       
    69         return [row[0] for row in self.rset]
       
    70 
       
    71     def _build_data(self, abscissa, plot):
       
    72         return filterout_nulls(abscissa, plot)
       
    73 
   114 
    74     def call(self, width=500, height=400):
   115     def call(self, width=500, height=400):
    75         # XXX add excanvas.js if IE
       
    76         self.req.add_js( ('jquery.flot.js', 'cubicweb.flot.js') )
       
    77         # prepare data
   116         # prepare data
    78         abscissa = self._build_abscissa()
   117         rqlst = self.rset.syntax_tree()
       
   118         # XXX try to make it work with unions
       
   119         varnames = [var.name for var in rqlst.children[0].get_selected_variables()][1:]
       
   120         abscissa = [row[0] for row in self.rset]
    79         plots = []
   121         plots = []
    80         nbcols = len(self.rset.rows[0])
   122         nbcols = len(self.rset.rows[0])
    81         for col in xrange(1, nbcols):
   123         for col in xrange(1, nbcols):
    82             plots.append([row[col] for row in self.rset])
   124             data = [row[col] for row in self.rset]
    83         # plot data
   125             plots.append(filterout_nulls(abscissa, plot))
    84         plotuid = 'plot%s' % make_uid('foo')
   126         plotwidget = FlotPlotWidget(varnames, plots, timemode=self.timemode)
    85         self.w(u'<div id="%s" style="width: %spx; height: %spx;"></div>' %
   127         plotwidget.render(self.req, width, height, w=self.w)
    86                (plotuid, width, height))
       
    87         rqlst = self.rset.syntax_tree()
       
    88         # XXX try to make it work with unions
       
    89         varnames = [var.name for var in rqlst.children[0].get_selected_variables()][1:]
       
    90         plotdefs = []
       
    91         plotdata = []
       
    92         for idx, (varname, plot) in enumerate(zip(varnames, plots)):
       
    93             plotid = '%s_%s' % (plotuid, idx)
       
    94             data = self._build_data(abscissa, plot)
       
    95             plotdefs.append('var %s = %s;' % (plotid, dumps(data)))
       
    96             plotdata.append("{label: '%s', data: %s}" % (varname, plotid))
       
    97         self.req.html_headers.add_onload('''
       
    98 %(plotdefs)s
       
    99 jQuery.plot(jQuery("#%(plotuid)s"), [%(plotdata)s],
       
   100     {points: {show: true},
       
   101      lines: {show: true},
       
   102      grid: {hoverable: true},
       
   103      xaxis: {mode: %(mode)s}});
       
   104 jQuery('#%(plotuid)s').bind('plothover', onPlotHover);
       
   105 ''' % {'plotdefs': '\n'.join(plotdefs),
       
   106        'plotuid': plotuid,
       
   107        'plotdata': ','.join(plotdata),
       
   108        'mode': self.mode})
       
   109 
   128 
   110 
   129 
   111 class TimeSeriePlotView(PlotView):
   130 class TimeSeriePlotView(PlotView):
   112     id = 'plot'
       
   113     title = _('generic plot')
       
   114     __select__ = at_least_two_columns() & columns_are_date_then_numbers()
   131     __select__ = at_least_two_columns() & columns_are_date_then_numbers()
   115     mode = '"time"'
   132     timemode = True
   116 
       
   117     def _build_abscissa(self):
       
   118         abscissa = [time.mktime(row[0].timetuple()) * 1000
       
   119                     for row in self.rset]
       
   120         return abscissa
       
   121 
       
   122     def _build_data(self, abscissa, plot):
       
   123         data = []
       
   124         # XXX find a way to get rid of the 3rd column and find 'mode' in JS
       
   125         for x, y in filterout_nulls(abscissa, plot):
       
   126             data.append( (x, y, x) )
       
   127         return data
       
   128 
   133 
   129 try:
   134 try:
   130     from GChartWrapper import Pie, Pie3D
   135     from GChartWrapper import Pie, Pie3D
   131 except ImportError:
   136 except ImportError:
   132     pass
   137     pass