Skip to content

Commit ceb41d1

Browse files
author
Brian Luft
committed
moves main code out of __init__ into graph.py
1 parent 774f0b4 commit ceb41d1

File tree

4 files changed

+181
-178
lines changed

4 files changed

+181
-178
lines changed

pyflot/__init__.py

Lines changed: 2 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -1,178 +1,3 @@
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"
92

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

pyflot/devserver.py

Whitespace-only changes.

pyflot/graph.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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
9+
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

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def read(fname):
1414
license = "MIT",
1515
keywords = "flot graphs charts javascript data timeseries time series",
1616
#url = "http://packages.python.org/an_example_pypi_project",
17-
packages=['pyflot', 'tests'],
17+
packages=['pyflot'],
1818
long_description=read('README.rst'),
1919
classifiers=[
2020
"Development Status :: 3 - Alpha",

0 commit comments

Comments
 (0)