1#!/usr/bin/env python3
2
3"""Implementation of the BitHistogram widget."""
4
5import collections
6from datetime import timedelta
7
8from django.template.loader import render_to_string
9
10import chart.alg.settings
11from chart.reports.widget import Widget
12from chart.plotviewer.hardcopy import make_bit_histogram
13from chart.common.xml import to_html
14from chart.common import traits
15from chart.plotviewer.plot_utils import DataPoint
16from chart.common.path import Path
17
18# Force orbital stats if the report duration is over this
19ORBITAL_STATS_DURATION = timedelta(days=3)
20
21DEFAULT_WIDTH = 1000
22DEFAULT_HEIGHT = 600
23
24
25class HistogramWidget(Widget):
26 """Render a histogram for a list of data points.
27 """
28
29 name = 'bit-histogram'
30
31 thumbnail = 'widgets/bithistogram.png'
32
33 options = collections.OrderedDict([
34 ('title', {'type': 'string',
35 'optional': True}),
36 ('filename', {'type': 'string',
37 'optional': True}),
38 ('field', {
39 'type': 'field',
40 'optional': True,
41 'description': (
42 'Timeseries data point to plot. Either a datapoint or an '
43 'event+property must be specified')}),
44 ('width', {'type': 'uint',
45 'default': DEFAULT_WIDTH,
46 'unit': 'pixels'}),
47 ('height', {'type': 'uint',
48 'default': DEFAULT_HEIGHT,
49 'unit': 'pixels'}),
50 ('label-fontsize', {'type': 'uint',
51 'unit': 'pt',
52 'default': 10,
53 'description': 'Font size for axis labels'}),
54 ])
55
56 document_options = collections.OrderedDict([
57 ('sid', {'type': 'sid'}),
58 ('sensing_start', {'type': 'datetime'}),
59 ('sensing_stop', {'type': 'datetime'})])
60
61 def __str__(self):
62 """String representation of this widget, appears in log file."""
63
64 if hasattr(self, 'title') and self.title is not None:
65 # Note, this function can get called in an exception handler during widget
66 # construction so don't assume anything is available
67 return 'BitHistogram({title})'.format(title=self.config.get('title', ''))
68
69 else:
70 return 'BitHistogram'
71
72 def pre_html(self, document):
73 """Insert ourselves into the List of Figures widget."""
74
75 c = self.config
76 # document.figures.append(c['title'])
77
78 # Build an automatic title if the user didn't supply one
79 if 'title' not in c:
80 c['title'] = 'Bit histogram of {field}'.format(field=c['field'].name)
81
82 # Make sure we appear in the LoF widget
83 document.figures.append(c['title'])
84
85 def html(self, document):
86 """Render ourselves."""
87
88 c = self.config
89 dc = document.config
90
91 # If the user didn't supply a filename we deduce one
92 if 'filename' not in c:
93 # `anon_counts` gives the number of anonymous images already created
94 # for this report
95 if 'bithistogram' not in document.anon_counts:
96 document.anon_counts['bithistogram'] = 1
97
98 else:
99 document.anon_counts['bithistogram'] += 1
100
101 filename = Path('BITHISTOGRAM_{cc}.png'.format(cc=document.anon_counts['bithistogram']))
102
103 else:
104 filename = Path(c['filename'])
105
106 filename = document.theme.mod_filename(document, filename)
107
108 dp = DataPoint(sid=dc['sid'],
109 table=c['field'].table,
110 field=c['field'])
111
112 fig = make_bit_histogram(dc['sid'],
113 dc['sensing_start'],
114 dc['sensing_stop'],
115 dp,
116 c['width'],
117 c['height'],
118 c['title'],
119 c['label-fontsize'])
120 fig.savefig(str(filename))
121
122 url = ''
123
124 # Render graph detail page
125 summary_filename = Path(str(filename) + '.html')
126 with summary_filename.open('w') as summary:
127 summary.write(
128 render_to_string(
129 'widgets/bit-histogram.html',
130 {'css': chart.alg.settings.REPORT_CSS_BASENAME,
131 'src': filename,
132 'datapoint_info': [c['field']],
133 'config': self.make_html_config(),
134 'url': url,
135 'elem': to_html(self.elem),
136 # why does the next line throw a unicode error here without escaping?
137 'title': traits.to_htmlstr(c['title'])}))
138
139 document.append_figure(filename=filename,
140 zoom_filename=filename,
141 title=c['title'],
142 width=c['width'],
143 height=c['height'],
144 widget=self)