92
102
def float_scale_value(cls, value, range):
93
103
lower, upper = range
94
104
assert(upper > lower)
95
max_value = cls.max_value()
96
scaled = (value-lower) * (float(max_value) / (upper - lower))
105
scaled = (value - lower) * (float(cls.max_value) / (upper - lower))
100
109
def clip_value(cls, value):
101
return max(0, min(value, cls.max_value()))
110
return max(0, min(value, cls.max_value))
104
113
def int_scale_value(cls, value, range):
108
117
def scale_value(cls, value, range):
109
118
scaled = cls.int_scale_value(value, range)
110
119
clipped = cls.clip_value(scaled)
120
if clipped != scaled:
121
warnings.warn('One or more of of your data points has been '
122
'clipped because it is out of range.')
114
126
class SimpleData(Data):
116
129
enc_map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
118
131
def __repr__(self):
119
max_value = self.max_value()
120
132
encoded_data = []
121
133
for data in self.data:
123
135
for value in data:
124
136
if value is None:
125
137
sub_data.append('_')
126
elif value >= 0 and value <= max_value:
138
elif value >= 0 and value <= self.max_value:
127
139
sub_data.append(SimpleData.enc_map[value])
129
141
raise DataOutOfRangeException('cannot encode value: %d'
131
143
encoded_data.append(''.join(sub_data))
132
144
return 'chd=s:' + ','.join(encoded_data)
139
147
class TextData(Data):
141
151
def __repr__(self):
142
max_value = self.max_value()
143
152
encoded_data = []
144
153
for data in self.data:
146
155
for value in data:
147
156
if value is None:
148
157
sub_data.append(-1)
149
elif value >= 0 and value <= max_value:
158
elif value >= 0 and value <= self.max_value:
150
159
sub_data.append("%.1f" % float(value))
152
161
raise DataOutOfRangeException()
153
162
encoded_data.append(','.join(sub_data))
154
163
return 'chd=t:' + '|'.join(encoded_data)
161
166
def scale_value(cls, value, range):
162
167
# use float values instead of integers because we don't need an encode
287
288
LINEAR_STRIPES = 'ls'
289
290
def __init__(self, width, height, title=None, legend=None, colours=None,
290
auto_scale=True, x_range=None, y_range=None):
291
auto_scale=True, x_range=None, y_range=None):
291
292
if type(self) == Chart:
292
293
raise AbstractClassException('This is an abstract class')
293
294
assert(isinstance(width, int))
300
301
self.set_colours(colours)
302
303
# Data for scaling.
303
self.auto_scale = auto_scale # Whether to automatically scale data
304
self.x_range = x_range # (min, max) x-axis range for scaling
305
self.y_range = y_range # (min, max) y-axis range for scaling
304
self.auto_scale = auto_scale # Whether to automatically scale data
305
self.x_range = x_range # (min, max) x-axis range for scaling
306
self.y_range = y_range # (min, max) y-axis range for scaling
306
307
self.scaled_data_class = None
307
308
self.scaled_x_range = None
308
309
self.scaled_y_range = None
905
908
class GoogleOMeterChart(PieChart):
906
909
"""Inheriting from PieChart because of similar labeling"""
911
def __init__(self, *args, **kwargs):
912
PieChart.__init__(self, *args, **kwargs)
913
if self.auto_scale and not self.x_range:
914
warnings.warn('Please specify an x_range with GoogleOMeterChart, '
915
'otherwise one arrow will always be at the max.')
908
917
def type_to_url(self):
912
921
class ChartGrammar(object):
914
def __init__(self, grammar):
927
def parse(self, grammar):
915
928
self.grammar = grammar
916
929
self.chart = self.create_chart_instance()
931
for attr in self.grammar:
932
if attr in ('w', 'h', 'type', 'auto_scale', 'x_range', 'y_range'):
933
continue # These are already parsed in create_chart_instance
934
attr_func = 'parse_' + attr
935
if not hasattr(self, attr_func):
936
warnings.warn('No parser for grammar attribute "%s"' % (attr))
938
getattr(self, attr_func)(grammar[attr])
942
def parse_data(self, data):
943
self.chart.data = data
944
print self.chart.data
919
947
def get_possible_chart_types():
920
948
possible_charts = []
921
for cls_name in globals():
949
for cls_name in globals().keys():
922
950
if not cls_name.endswith('Chart'):
924
952
cls = globals()[cls_name]
925
953
# Check if it is an abstract class
955
a = cls(1, 1, auto_scale=False)
928
957
except AbstractClassException:
930
959
# Strip off "Class"
931
960
possible_charts.append(cls_name[:-5])
932
961
return possible_charts
934
def create_chart_instance(self):
963
def create_chart_instance(self, grammar=None):
965
grammar = self.grammar
966
assert(isinstance(grammar, dict)) # grammar must be a dict
935
967
assert('w' in grammar) # width is required
936
968
assert('h' in grammar) # height is required
937
969
assert('type' in grammar) # type is required
970
chart_type = grammar['type']
973
auto_scale = grammar.get('auto_scale', None)
974
x_range = grammar.get('x_range', None)
975
y_range = grammar.get('y_range', None)
938
976
types = ChartGrammar.get_possible_chart_types()
939
if grammar['type'] not in types:
977
if chart_type not in types:
940
978
raise UnknownChartType('%s is an unknown chart type. Possible '
941
'chart types are %s' % (grammar['type'], ','.join(types)))
979
'chart types are %s' % (chart_type, ','.join(types)))
980
return globals()[chart_type + 'Chart'](w, h, auto_scale=auto_scale,
981
x_range=x_range, y_range=y_range)
943
983
def download(self):