229 |
229 |
'table': pygal.Bar,
|
230 |
230 |
}[self.chart_type](config=pygal.Config(style=copy.copy(style)))
|
231 |
231 |
|
|
232 |
x_labels, y_labels, data = self.parse_response(response, chart)
|
|
233 |
chart.x_labels = x_labels
|
|
234 |
self.prepare_chart(chart, width, height)
|
|
235 |
|
|
236 |
if chart.axis_count == 1:
|
|
237 |
if self.hide_null_values:
|
|
238 |
data = self.hide_values(chart, data)
|
|
239 |
if self.sort_order != 'none':
|
|
240 |
data = self.sort_values(chart, data)
|
|
241 |
if chart.compute_sum and self.chart_type == 'table':
|
|
242 |
data = self.add_total_to_line_table(chart, data)
|
|
243 |
self.add_data_to_chart(chart, data, y_labels)
|
|
244 |
|
|
245 |
return chart
|
|
246 |
|
|
247 |
def parse_response(self, response, chart):
|
232 |
248 |
# normalize axis to have a fake axis when there are no dimensions and
|
233 |
249 |
# always a x axis when there is a single dimension.
|
234 |
250 |
data = response['data']
|
... | ... | |
261 |
277 |
else:
|
262 |
278 |
chart.axis_count = 2
|
263 |
279 |
|
264 |
|
# hide/sort values
|
265 |
|
if chart.axis_count == 1 and (self.sort_order != 'none' or self.hide_null_values):
|
266 |
|
if self.sort_order == 'alpha':
|
267 |
|
tmp_items = sorted(zip(x_labels, data), key=lambda x: x[0])
|
268 |
|
elif self.sort_order == 'asc':
|
269 |
|
tmp_items = sorted(zip(x_labels, data), key=lambda x: (x[1] or 0))
|
270 |
|
elif self.sort_order == 'desc':
|
271 |
|
tmp_items = sorted(zip(x_labels, data), key=lambda x: (x[1] or 0), reverse=True)
|
272 |
|
else:
|
273 |
|
tmp_items = zip(x_labels, data)
|
274 |
|
tmp_x_labels = []
|
275 |
|
tmp_data = []
|
276 |
|
for label, value in tmp_items:
|
277 |
|
if self.hide_null_values and not value:
|
278 |
|
continue
|
279 |
|
tmp_x_labels.append(label)
|
280 |
|
tmp_data.append(value)
|
281 |
|
x_labels = tmp_x_labels
|
282 |
|
data = tmp_data
|
|
280 |
chart.show_legend = bool(len(response['axis']) > 1)
|
|
281 |
chart.compute_sum = bool(response.get('measure') == 'integer' and chart.axis_count > 0)
|
283 |
282 |
|
|
283 |
formatter = self.get_value_formatter(response.get('unit'), response.get('measure'))
|
|
284 |
if formatter:
|
|
285 |
chart.config.value_formatter = formatter
|
|
286 |
|
|
287 |
return x_labels, y_labels, data
|
|
288 |
|
|
289 |
def prepare_chart(self, chart, width, height):
|
284 |
290 |
chart.config.margin = 0
|
285 |
291 |
if width:
|
286 |
292 |
chart.config.width = width
|
... | ... | |
289 |
295 |
if width or height:
|
290 |
296 |
chart.config.explicit_size = True
|
291 |
297 |
chart.config.js = [os.path.join(settings.STATIC_URL, 'js/pygal-tooltips.js')]
|
292 |
|
chart.x_labels = x_labels
|
293 |
298 |
|
294 |
|
chart.show_legend = bool(len(response['axis']) > 1)
|
295 |
299 |
chart.truncate_legend = 30
|
296 |
300 |
# matplotlib tab10 palette
|
297 |
301 |
chart.config.style.colors = (
|
... | ... | |
302 |
306 |
if self.chart_type == 'dot':
|
303 |
307 |
chart.show_legend = False
|
304 |
308 |
# use a single colour for dots
|
305 |
|
chart.config.style.colors = ('#1f77b4',) * len(x_labels)
|
|
309 |
chart.config.style.colors = ('#1f77b4',) * len(chart.x_labels)
|
306 |
310 |
|
307 |
|
chart.compute_sum = bool(response.get('measure') == 'integer')
|
308 |
|
if chart.compute_sum and self.chart_type == 'table':
|
309 |
|
if chart.axis_count < 2: # workaround pygal
|
310 |
|
chart.compute_sum = False
|
311 |
|
if chart.axis_count == 1:
|
312 |
|
data.append(sum(data))
|
313 |
|
x_labels.append(gettext('Total'))
|
|
311 |
if self.chart_type != 'pie':
|
|
312 |
if width and width < 500:
|
|
313 |
chart.legend_at_bottom = True
|
|
314 |
if self.chart_type == 'horizontal-bar':
|
|
315 |
# truncate labels
|
|
316 |
chart.x_labels = [pygal.util.truncate(x, 15) for x in chart.x_labels]
|
|
317 |
else:
|
|
318 |
chart.show_legend = True
|
|
319 |
if width and width < 500:
|
|
320 |
chart.truncate_legend = 15
|
314 |
321 |
|
|
322 |
@staticmethod
|
|
323 |
def hide_values(chart, data):
|
|
324 |
x_labels, new_data = zip(*[(label, value) for label, value in zip(chart.x_labels, data) if value])
|
|
325 |
chart.x_labels = list(x_labels)
|
|
326 |
return list(new_data)
|
|
327 |
|
|
328 |
def sort_values(self, chart, data):
|
|
329 |
if self.sort_order == 'alpha':
|
|
330 |
tmp_items = sorted(zip(chart.x_labels, data), key=lambda x: x[0])
|
|
331 |
elif self.sort_order == 'asc':
|
|
332 |
tmp_items = sorted(zip(chart.x_labels, data), key=lambda x: (x[1] or 0))
|
|
333 |
elif self.sort_order == 'desc':
|
|
334 |
tmp_items = sorted(zip(chart.x_labels, data), key=lambda x: (x[1] or 0), reverse=True)
|
|
335 |
x_labels, sorted_data = zip(*[(label, value) for label, value in tmp_items])
|
|
336 |
chart.x_labels = list(x_labels)
|
|
337 |
return list(sorted_data)
|
|
338 |
|
|
339 |
@staticmethod
|
|
340 |
def add_total_to_line_table(chart, data):
|
|
341 |
# workaround pygal
|
|
342 |
chart.compute_sum = False
|
|
343 |
data.append(sum(data))
|
|
344 |
chart.x_labels.append(gettext('Total'))
|
|
345 |
return data
|
|
346 |
|
|
347 |
def add_data_to_chart(self, chart, data, y_labels):
|
315 |
348 |
if self.chart_type != 'pie':
|
316 |
349 |
for i, serie_label in enumerate(y_labels):
|
317 |
350 |
if chart.axis_count < 2:
|
318 |
351 |
values = data
|
319 |
352 |
else:
|
320 |
|
values = [data[i][j] for j in range(len(x_labels))]
|
|
353 |
values = [data[i][j] for j in range(len(chart.x_labels))]
|
321 |
354 |
chart.add(serie_label, values)
|
322 |
|
if width and width < 500:
|
323 |
|
chart.legend_at_bottom = True
|
324 |
|
if self.chart_type == 'horizontal-bar':
|
325 |
|
# truncate labels
|
326 |
|
chart.x_labels = [pygal.util.truncate(x, 15) for x in chart.x_labels]
|
327 |
355 |
else:
|
328 |
356 |
# pie, create a serie by data, to get different colours
|
329 |
357 |
values = data
|
330 |
|
for label, value in zip(x_labels, values):
|
|
358 |
for label, value in zip(chart.x_labels, values):
|
331 |
359 |
if not value:
|
332 |
360 |
continue
|
333 |
361 |
chart.add(label, value)
|
334 |
|
chart.show_legend = True
|
335 |
|
if width and width < 500:
|
336 |
|
chart.truncate_legend = 15
|
337 |
362 |
|
338 |
|
if response.get('unit') == 'seconds' or response.get('measure') == 'duration':
|
|
363 |
@staticmethod
|
|
364 |
def get_value_formatter(unit, measure):
|
|
365 |
if unit == 'seconds' or measure == 'duration':
|
339 |
366 |
def format_duration(value):
|
340 |
367 |
if value is None:
|
341 |
368 |
return '-'
|
... | ... | |
357 |
384 |
else:
|
358 |
385 |
value = _('Less than an hour')
|
359 |
386 |
return force_text(value)
|
360 |
|
chart.config.value_formatter = format_duration
|
361 |
|
elif response.get('measure') == 'percent':
|
|
387 |
return format_duration
|
|
388 |
elif measure == 'percent':
|
362 |
389 |
percent_formatter = lambda x: '{:.1f}%'.format(x)
|
363 |
|
chart.config.value_formatter = percent_formatter
|
364 |
|
|
365 |
|
return chart
|
|
390 |
return percent_formatter
|
366 |
|
-
|