from collections import OrderedDict from dashboard.widget_pool import WidgetError from datetime import timedelta from dateutil import parser from django.conf import settings from django.utils import dateformat from django.utils.translation import ugettext_lazy as _ from transport.taxonomies import term_itemcount class TermCountChartWidget(object): """ A horizontal bar chart used to display number of entries for each term of a taxonomy. Settings: title: Name of the widget taxonomy: Slug of the taxonomy count: Maximum number of terms to display. If this is >0, then only the countth most used terms are displayed, and all others are aggregated under 'Others' other_label: Optional label to use instead of 'Others' """ template_name = 'hid/widgets/chart.html' javascript = [ 'flot/jquery.flot.js', 'flot/jquery.flot.resize.js', 'hid/widgets/chart.js' ] def _fetch_counts(self, taxonomy, count, start, end, other_label, exclude_categories=None): """ Given a taxonomy, fetch the count per term. Args: taxonomy (str): Taxonomy slug count (int): If >0, maximum number of rows to returns. If the data has more terms, all other terms are aggregated under an 'others' section start (datetime or None): If not None, the start of the time period to get the count for end (datetime or None): If not None, the start of the time period to get the count for other_label (str): Label for the 'Others' section """ itemcount = None if start is not None and end is not None: itemcount = term_itemcount( taxonomy, start_time=start, end_time=end ) else: itemcount = term_itemcount(taxonomy) if exclude_categories is not None: itemcount = [t for t in itemcount if t['name'] not in exclude_categories] itemcount.sort(key=lambda k: int(k['count']), reverse=True) if count > 0: head = itemcount[0:count-1] tail = itemcount[count-1:] else: head = itemcount tail = [] head.sort(key=lambda k: k['long_name']) counts = OrderedDict() for item in head: counts[item['long_name']] = item['count'] if len(tail) > 0: agg = 0 for item in tail: agg = agg + item['count'] counts[other_label] = agg return counts def _create_axis_values(self, counts): """ Given a dictionary of label to value, create the Y and X axis values to be used in flot. Args: - chart: A dictionary of label to value Returns: A tuple containing (X Axis data, Y Axis data) """ yticks = [] values = [] index = len(counts) for label, value in counts.items(): yticks.append([index, label]) values.append([value, index]) index -= 1 return values, yticks def _create_date_range_label(self, start, end): """ Create a label to display a date range. The dates are formatter such that: - If either start or end include hours/minutes/seconds that are not 00:00:00 then the full date time is displayed; - If both start and end have zero hours/minutes/seconds then only the day is displayed, and the end day is set to the previous day (to show an inclusive range); Args: start (datetime): Start date time end (datetime): End date time Returns: str: Label to use for the date range. """ if not start.time() and not end.time(): start_str = dateformat.format(start, settings.SHORT_DATE_FORMAT) end_str = dateformat.format(end - timedelta(days=1), settings.SHORT_DATE_FORMAT) else: start_str = dateformat.format(start, settings.SHORT_DATETIME_FORMAT), end_str = dateformat.format(end, settings.SHORT_DATETIME_FORMAT) if start_str == end_str: return _('%(date)s') % {'date': start_str} else: return _('%(start)s - %(end)s') % { 'start': start_str, 'end': end_str } def get_context_data(self, **kwargs): title = kwargs.get('title', _('(missing title)')) taxonomy = kwargs.get('taxonomy') count = kwargs.get('count', 0) other_label = kwargs.get('other_label', 'Others') periods = kwargs.get('periods', []) exclude_categories = kwargs.get('exclude_categories') if len(periods) > 1: raise WidgetError('Only one time period is currently supported') if len(periods) == 1: try: start_time = parser.parse(periods[0]['start_time']) end_time = parser.parse(periods[0]['end_time']) except ValueError: raise WidgetError('Error parsing start/end time') legend = { 'show': True, 'noColumns': 1, 'position': 'ne', 'labelBoxBorderColor': 'white', 'backgroundColor': 'white' } label = self._create_date_range_label(start_time, end_time) else: start_time = None end_time = None legend = {'show': False} label = '' counts = self._fetch_counts( taxonomy, count, start_time, end_time, other_label, exclude_categories ) (values, yticks) = self._create_axis_values(counts) return { 'title': title, 'options': { 'series': { 'bars': { 'show': True, 'fillColor': '#f29e30' }, 'color': 'transparent' }, 'bars': { 'horizontal': True, 'barWidth': 0.6, 'align': 'center', 'fill': True, 'lineWidth': 0, }, 'yaxis': { 'ticks': yticks, 'tickLength': 0, 'color': '#333333', 'font': { 'size': 12, 'style': 'normal', 'weight': 'normal', 'family': 'sans-serif' } }, 'xaxis': { 'autoscaleMargin': 0.1 }, 'grid': { 'hoverable': True, 'borderWidth': 0, 'margin': 10, 'labelMargin': 20, 'backgroundColor': '#fafafa' }, 'legend': legend }, 'data': [{ 'label': label, 'color': '#f29e30', 'data': values }] }