|
1 | | -import collections |
2 | | -from datetime import date |
3 | | -from functools import partial |
4 | | -from itertools import chain |
5 | | -import inspect |
6 | | -import json |
7 | | -import os |
8 | | -import time |
| 1 | +"PyFlot" |
9 | 2 |
|
10 | | - |
11 | | -def update(d, u): |
12 | | - """ |
13 | | - Recursively update nested dicts |
14 | | -
|
15 | | - Credit: Alex Martelli |
16 | | - """ |
17 | | - for k, v in u.iteritems(): |
18 | | - if isinstance(v, collections.Mapping): |
19 | | - r = update(d.get(k, {}), v) |
20 | | - d[k] = r |
21 | | - else: |
22 | | - d[k] = u[k] |
23 | | - return d |
24 | | - |
25 | | - |
26 | | -class MissingDataException(Exception): |
27 | | - """Exception raised when a series does not contain |
28 | | - any data points""" |
29 | | - |
30 | | - |
31 | | -class DuplicateLabelException(Exception): |
32 | | - """Exception raised when an attempt is made to |
33 | | - label a new series with a label already in use""" |
34 | | - |
35 | | - |
36 | | -LINE_TYPES = ('bars', 'line', 'points') |
37 | | - |
38 | | - |
39 | | -class Flot(object): |
40 | | - """ |
41 | | - Represents a ``flot`` graph |
42 | | -
|
43 | | - This is a Python representation of a flot graph with the |
44 | | - goal preserving the flot attribute names and organization |
45 | | - of the options. A Flot instance will allow you to |
46 | | - use your Python data structures as is and will handle |
47 | | - the details of converting to valid JSON with items |
48 | | - formatted properly for ``flot``. (Handy for time series |
49 | | - for example) |
50 | | - """ |
51 | | - |
52 | | - def __init__(self): |
53 | | - self._series = [] |
54 | | - self._options = {} |
55 | | - |
56 | | - #apply any options specified starting with the top |
57 | | - #of the inheritance chain |
58 | | - bases = list(inspect.getmro(self.__class__)) |
59 | | - bases.reverse() |
60 | | - for base in bases: |
61 | | - if hasattr(base, 'options'): |
62 | | - update(self._options, base.options) |
63 | | - |
64 | | - @property |
65 | | - def series_json(self): |
66 | | - """ |
67 | | - Returns a string with each data series |
68 | | - associated with this graph formatted as JSON, |
69 | | - suitable for passing to the ``$.plot`` method. |
70 | | - """ |
71 | | - return json.dumps([self.prepare_series(s) for s in self._series]) |
72 | | - |
73 | | - @property |
74 | | - def options_json(self): |
75 | | - """ |
76 | | - Returns a JSON string representing the global options |
77 | | - for this graph in a format suitable for passing to |
78 | | - the ``$.plot`` method as the options parameter. |
79 | | - """ |
80 | | - return json.dumps(self._options) |
81 | | - |
82 | | - def __getattr__(self, value): |
83 | | - """ |
84 | | - add_bars |
85 | | - add_line |
86 | | - add_points |
87 | | -
|
88 | | - provides shortcut methods for adding series using a particular line type |
89 | | - """ |
90 | | - if value.startswith('add_'): |
91 | | - if not value.split('_')[1] in LINE_TYPES: |
92 | | - raise AttributeError |
93 | | - return partial(self.add_series_type, value[4:]) |
94 | | - |
95 | | - def add_series_type(self, line_type, series, label=None, **kwargs): |
96 | | - method = getattr(self, 'add_series') |
97 | | - return method(series, label, **{line_type: True}) |
98 | | - |
99 | | - def add_series(self, series, label=None, **kwargs): |
100 | | - """ |
101 | | - A series is a list of pairs (2-tuples) |
102 | | -
|
103 | | - Optional Args: |
104 | | - bars |
105 | | - line |
106 | | - points - for each of these present as keyword arguments, |
107 | | - their value should be a dict representing the |
108 | | - line type options relative to their type. |
109 | | - Alternatively, if the value is `True` the option |
110 | | - for showing the line type {'show': True} will |
111 | | - be set for the options for this line type. |
112 | | - """ |
113 | | - if not series: |
114 | | - raise MissingDataException |
115 | | - |
116 | | - #detect time series |
117 | | - testatom = series[0][0] |
118 | | - if isinstance(testatom, date): |
119 | | - |
120 | | - series = [(int(time.mktime(ts.timetuple()) * 1000), val) \ |
121 | | - for ts, val in series] |
122 | | - self._options['xaxis'] = {'mode': 'time'} |
123 | | - |
124 | | - new_series = {'data': series} |
125 | | - if label and label in [x.get('label', None) for x in self._series]: |
126 | | - raise DuplicateLabelException |
127 | | - elif label: |
128 | | - new_series.update(label=label) |
129 | | - for line_type in LINE_TYPES: |
130 | | - if line_type in kwargs: |
131 | | - if isinstance(kwargs[line_type], collections.Mapping): |
132 | | - new_series.update({line_type: kwargs[line_type]}) |
133 | | - else: |
134 | | - new_series.update({line_type: {'show': True}}) |
135 | | - self._series.append(new_series) |
136 | | - |
137 | | - #def add_time_series(self, series, label=None, **kwargs): |
138 | | - #""" |
139 | | - #A specialized form of ``add_series`` for adding time-series data. |
140 | | - |
141 | | - #Flot requires times to be specified in Javascript timestamp format. |
142 | | - #This convenience function lets you pass datetime instances and handles |
143 | | - #the conversion. It also sets the correct option to indicate to ``flot`` |
144 | | - #that the graph should be treated as a time series |
145 | | - #""" |
146 | | - #_series = [(int(time.mktime(ts.timetuple()) * 1000), val) \ |
147 | | - #for ts, val in series] |
148 | | - #self._options['xaxis'] = {'mode': 'time'} |
149 | | - #return self.add_series(_series, label, **kwargs) |
150 | | - |
151 | | - def calculate_bar_width(self): |
152 | | - slices = max([len(s['data']) for s in self._series]) |
153 | | - xs = [pair[0] for pair in chain(*[s['data'] for s in self._series])] |
154 | | - xmin, xmax = (min(xs), max(xs)) |
155 | | - w = xmax - xmin |
156 | | - return float(w)/slices |
157 | | - |
158 | | - def get_test_page(self): |
159 | | - """Renders a test page""" |
160 | | - templatefile = open(os.path.join( |
161 | | - os.path.dirname(os.path.abspath(__file__)), |
162 | | - 'templates', |
163 | | - 'test_page.html')) |
164 | | - template = templatefile.read() |
165 | | - template = template.replace("{{ graph.series_json|safe }}", |
166 | | - self.series_json) |
167 | | - template = template.replace("{{ graph.options_json|safe }}", |
168 | | - self.options_json) |
169 | | - out = open(os.path.join(os.getcwd(), 'testgraph.html'), 'w') |
170 | | - out.write(template) |
171 | | - out.close() |
172 | | - |
173 | | - def prepare_series(self, series): |
174 | | - if 'bars' in series: |
175 | | - w = self.calculate_bar_width() |
176 | | - if w: |
177 | | - series['bars']['barWidth'] = w |
178 | | - return series |
| 3 | +from pyflot.graph import Flot, MissingDataException, DuplicateLabelException |
0 commit comments