From 4e756d78effaad747ef9305f2ecba81d2163f9e8 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Thu, 3 Dec 2020 15:08:17 +0100 Subject: [PATCH 1/3] dataviz: split get_chart into several methods (#48865) --- combo/apps/dataviz/models.py | 115 +++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 45 deletions(-) diff --git a/combo/apps/dataviz/models.py b/combo/apps/dataviz/models.py index 8629de38..7da16722 100644 --- a/combo/apps/dataviz/models.py +++ b/combo/apps/dataviz/models.py @@ -229,6 +229,22 @@ class ChartNgCell(CellBase): 'table': pygal.Bar, }[self.chart_type](config=pygal.Config(style=copy.copy(style))) + x_labels, y_labels, data = self.parse_response(response, chart) + chart.x_labels = x_labels + self.prepare_chart(chart, width, height) + + if chart.axis_count == 1: + if self.hide_null_values: + data = self.hide_values(chart, data) + if self.sort_order != 'none': + data = self.sort_values(chart, data) + if chart.compute_sum and self.chart_type == 'table': + data = self.add_total_to_line_table(chart, data) + self.add_data_to_chart(chart, data, y_labels) + + return chart + + def parse_response(self, response, chart): # normalize axis to have a fake axis when there are no dimensions and # always a x axis when there is a single dimension. data = response['data'] @@ -261,26 +277,16 @@ class ChartNgCell(CellBase): else: chart.axis_count = 2 - # hide/sort values - if chart.axis_count == 1 and (self.sort_order != 'none' or self.hide_null_values): - if self.sort_order == 'alpha': - tmp_items = sorted(zip(x_labels, data), key=lambda x: x[0]) - elif self.sort_order == 'asc': - tmp_items = sorted(zip(x_labels, data), key=lambda x: (x[1] or 0)) - elif self.sort_order == 'desc': - tmp_items = sorted(zip(x_labels, data), key=lambda x: (x[1] or 0), reverse=True) - else: - tmp_items = zip(x_labels, data) - tmp_x_labels = [] - tmp_data = [] - for label, value in tmp_items: - if self.hide_null_values and not value: - continue - tmp_x_labels.append(label) - tmp_data.append(value) - x_labels = tmp_x_labels - data = tmp_data + chart.show_legend = bool(len(response['axis']) > 1) + chart.compute_sum = bool(response.get('measure') == 'integer' and chart.axis_count > 0) + formatter = self.get_value_formatter(response.get('unit'), response.get('measure')) + if formatter: + chart.config.value_formatter = formatter + + return x_labels, y_labels, data + + def prepare_chart(self, chart, width, height): chart.config.margin = 0 if width: chart.config.width = width @@ -289,9 +295,7 @@ class ChartNgCell(CellBase): if width or height: chart.config.explicit_size = True chart.config.js = [os.path.join(settings.STATIC_URL, 'js/pygal-tooltips.js')] - chart.x_labels = x_labels - chart.show_legend = bool(len(response['axis']) > 1) chart.truncate_legend = 30 # matplotlib tab10 palette chart.config.style.colors = ( @@ -302,40 +306,63 @@ class ChartNgCell(CellBase): if self.chart_type == 'dot': chart.show_legend = False # use a single colour for dots - chart.config.style.colors = ('#1f77b4',) * len(x_labels) + chart.config.style.colors = ('#1f77b4',) * len(chart.x_labels) - chart.compute_sum = bool(response.get('measure') == 'integer') - if chart.compute_sum and self.chart_type == 'table': - if chart.axis_count < 2: # workaround pygal - chart.compute_sum = False - if chart.axis_count == 1: - data.append(sum(data)) - x_labels.append(gettext('Total')) + if self.chart_type != 'pie': + if width and width < 500: + chart.legend_at_bottom = True + if self.chart_type == 'horizontal-bar': + # truncate labels + chart.x_labels = [pygal.util.truncate(x, 15) for x in chart.x_labels] + else: + chart.show_legend = True + if width and width < 500: + chart.truncate_legend = 15 + @staticmethod + def hide_values(chart, data): + x_labels, new_data = zip(*[(label, value) for label, value in zip(chart.x_labels, data) if value]) + chart.x_labels = list(x_labels) + return list(new_data) + + def sort_values(self, chart, data): + if self.sort_order == 'alpha': + tmp_items = sorted(zip(chart.x_labels, data), key=lambda x: x[0]) + elif self.sort_order == 'asc': + tmp_items = sorted(zip(chart.x_labels, data), key=lambda x: (x[1] or 0)) + elif self.sort_order == 'desc': + tmp_items = sorted(zip(chart.x_labels, data), key=lambda x: (x[1] or 0), reverse=True) + x_labels, sorted_data = zip(*[(label, value) for label, value in tmp_items]) + chart.x_labels = list(x_labels) + return list(sorted_data) + + @staticmethod + def add_total_to_line_table(chart, data): + # workaround pygal + chart.compute_sum = False + data.append(sum(data)) + chart.x_labels.append(gettext('Total')) + return data + + def add_data_to_chart(self, chart, data, y_labels): if self.chart_type != 'pie': for i, serie_label in enumerate(y_labels): if chart.axis_count < 2: values = data else: - values = [data[i][j] for j in range(len(x_labels))] + values = [data[i][j] for j in range(len(chart.x_labels))] chart.add(serie_label, values) - if width and width < 500: - chart.legend_at_bottom = True - if self.chart_type == 'horizontal-bar': - # truncate labels - chart.x_labels = [pygal.util.truncate(x, 15) for x in chart.x_labels] else: # pie, create a serie by data, to get different colours values = data - for label, value in zip(x_labels, values): + for label, value in zip(chart.x_labels, values): if not value: continue chart.add(label, value) - chart.show_legend = True - if width and width < 500: - chart.truncate_legend = 15 - if response.get('unit') == 'seconds' or response.get('measure') == 'duration': + @staticmethod + def get_value_formatter(unit, measure): + if unit == 'seconds' or measure == 'duration': def format_duration(value): if value is None: return '-' @@ -357,9 +384,7 @@ class ChartNgCell(CellBase): else: value = _('Less than an hour') return force_text(value) - chart.config.value_formatter = format_duration - elif response.get('measure') == 'percent': + return format_duration + elif measure == 'percent': percent_formatter = lambda x: '{:.1f}%'.format(x) - chart.config.value_formatter = percent_formatter - - return chart + return percent_formatter -- 2.20.1