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 |