321
287
self.fill_types = {
322
288
Chart.BACKGROUND: None,
323
289
Chart.CHART: None,
326
291
self.fill_area = {
327
292
Chart.BACKGROUND: None,
328
293
Chart.CHART: None,
332
296
self.markers = []
333
self.line_styles = {}
335
self.title_colour = None
336
self.title_font_size = None
339
299
# -------------------------------------------------------------------------
341
def get_url(self, data_class=None):
342
return self.BASE_URL + '?' + self.get_url_extension(data_class)
344
def get_url_extension(self, data_class=None):
345
url_bits = self.get_url_bits(data_class=data_class)
346
return '&'.join(url_bits)
348
def get_url_bits(self, data_class=None):
302
url_bits = self.get_url_bits()
303
return self.BASE_URL + '&'.join(url_bits)
305
def get_url_bits(self):
350
307
# required arguments
351
308
url_bits.append(self.type_to_url())
352
309
url_bits.append('chs=%ix%i' % (self.width, self.height))
353
url_bits.append(self.data_to_url(data_class=data_class))
310
url_bits.append(self.data_to_url())
354
311
# optional arguments
356
313
url_bits.append('chtt=%s' % self.title)
357
if self.title_colour and self.title_font_size:
358
url_bits.append('chts=%s,%s' % (self.title_colour, \
359
self.title_font_size))
361
url_bits.append('chdl=%s' % '%7c'.join(self.legend))
362
if self.legend_position:
363
url_bits.append('chdlp=%s' % (self.legend_position))
315
url_bits.append('chdl=%s' % '|'.join(self.legend))
365
url_bits.append('chco=%s' % ','.join(self.colours))
366
if self.colours_within_series:
367
url_bits.append('chco=%s' % '%7c'.join(self.colours_within_series))
317
url_bits.append('chco=%s' % ','.join(self.colours))
368
318
ret = self.fill_to_url()
370
320
url_bits.append(ret)
371
321
ret = self.axis_to_url()
375
url_bits.append(self.markers_to_url())
378
for index in range(max(self.line_styles) + 1):
379
if index in self.line_styles:
380
values = self.line_styles[index]
383
style.append(','.join(values))
384
url_bits.append('chls=%s' % '%7c'.join(style))
386
url_bits.append('chg=%s' % self.grid)
325
url_bits.append(self.markers_to_url())
390
329
# -------------------------------------------------------------------------
392
def download(self, file_name, use_post=True):
394
opener = urllib.request.urlopen(self.BASE_URL, self.get_url_extension())
396
opener = urllib.request.urlopen(self.get_url())
331
def download(self, file_name):
332
opener = urllib2.urlopen(self.get_url())
398
334
if opener.headers['content-type'] != 'image/png':
399
335
raise BadContentTypeException('Server responded with a ' \
400
336
'content-type of %s' % opener.headers['content-type'])
402
open(file_name, 'wb').write(opener.read())
338
open(file_name, 'wb').write(urllib.urlopen(self.get_url()).read())
404
340
# Simple settings
405
341
# -------------------------------------------------------------------------
407
343
def set_title(self, title):
409
self.title = urllib.parse.quote(title)
345
self.title = urllib.quote(title)
411
347
self.title = None
413
def set_title_style(self, colour=None, font_size=None):
414
if not colour is None:
415
_check_colour(colour)
416
if not colour and not font_size:
418
self.title_colour = colour or '333333'
419
self.title_font_size = font_size or 13.5
421
349
def set_legend(self, legend):
422
350
"""legend needs to be a list, tuple or None"""
423
351
assert(isinstance(legend, list) or isinstance(legend, tuple) or
426
self.legend = [urllib.parse.quote(a) for a in legend]
354
self.legend = [urllib.quote(a) for a in legend]
428
356
self.legend = None
430
def set_legend_position(self, legend_position):
432
self.legend_position = urllib.parse.quote(legend_position)
434
self.legend_position = None
437
359
# -------------------------------------------------------------------------
677
574
url_bits.append('chxt=%s' % ','.join(available_axis))
679
url_bits.append('chxl=%s' % '%7c'.join(label_axis))
576
url_bits.append('chxl=%s' % '|'.join(label_axis))
681
url_bits.append('chxr=%s' % '%7c'.join(range_axis))
578
url_bits.append('chxr=%s' % '|'.join(range_axis))
683
url_bits.append('chxp=%s' % '%7c'.join(positions))
580
url_bits.append('chxp=%s' % '|'.join(positions))
685
url_bits.append('chxs=%s' % '%7c'.join(styles))
582
url_bits.append('chxs=%s' % '|'.join(styles))
686
583
return '&'.join(url_bits)
688
585
# Markers, Ranges and Fill area (chm)
689
586
# -------------------------------------------------------------------------
691
def markers_to_url(self):
692
return 'chm=%s' % '%7c'.join([','.join(a) for a in self.markers])
588
def markers_to_url(self):
589
return 'chm=%s' % '|'.join([','.join(a) for a in self.markers])
694
def add_marker(self, index, point, marker_type, colour, size, priority=0):
591
def add_marker(self, index, point, marker_type, colour, size):
695
592
self.markers.append((marker_type, colour, str(index), str(point), \
696
str(size), str(priority)))
698
595
def add_horizontal_range(self, colour, start, stop):
699
self.markers.append(('r', colour, '0', str(start), str(stop)))
701
def add_data_line(self, colour, data_set, size, priority=0):
702
self.markers.append(('D', colour, str(data_set), '0', str(size), \
705
def add_marker_text(self, string, colour, data_set, data_point, size, \
707
self.markers.append((str(string), colour, str(data_set), \
708
str(data_point), str(size), str(priority)))
596
self.markers.append(('r', colour, '1', str(start), str(stop)))
710
598
def add_vertical_range(self, colour, start, stop):
711
self.markers.append(('R', colour, '0', str(start), str(stop)))
599
self.markers.append(('R', colour, '1', str(start), str(stop)))
713
601
def add_fill_range(self, colour, index_start, index_end):
714
602
self.markers.append(('b', colour, str(index_start), str(index_end), \
948
826
yield ('y', dataset)
951
class RadarChart(Chart):
953
def type_to_url(self):
957
class SplineRadarChart(RadarChart):
959
def type_to_url(self):
963
class MapChart(Chart):
965
def __init__(self, *args, **kwargs):
966
Chart.__init__(self, *args, **kwargs)
967
self.geo_area = 'world'
969
self.__areas = ('africa', 'asia', 'europe', 'middle_east',
970
'south_america', 'usa', 'world')
972
'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AN', 'AO', 'AQ', 'AR',
973
'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF',
974
'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BR', 'BS', 'BT',
975
'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI',
976
'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CX', 'CY', 'CZ',
977
'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER',
978
'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD',
979
'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR',
980
'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU',
981
'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE',
982
'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR',
983
'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT',
984
'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK',
985
'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV',
986
'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL',
987
'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH',
988
'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE',
989
'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH',
990
'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'ST', 'SV', 'SY',
991
'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN',
992
'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'US', 'UY',
993
'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE',
994
'YT', 'ZA', 'ZM', 'ZW')
996
def type_to_url(self):
999
def set_codes(self, codes):
1000
'''Set the country code map for the data.
1001
Codes given in a list.
1012
if cc in self.__ccodes:
1015
raise UnknownCountryCodeException(cc)
1017
self.codes = codemap
1019
def set_geo_area(self, area):
1020
'''Sets the geo area for the map.
1031
if area in self.__areas:
1032
self.geo_area = area
1034
raise UnknownChartType('Unknown chart type for maps: %s' %area)
1036
def get_url_bits(self, data_class=None):
1037
url_bits = Chart.get_url_bits(self, data_class=data_class)
1038
url_bits.append('chtm=%s' % self.geo_area)
1040
url_bits.append('chld=%s' % ''.join(self.codes))
1043
def add_data_dict(self, datadict):
1044
'''Sets the data and country codes via a dictionary.
1046
i.e. {'DE': 50, 'GB': 30, 'AT': 70}
1049
self.set_codes(datadict.keys())
1050
self.add_data(datadict.values())
1053
class GoogleOMeterChart(PieChart):
1054
"""Inheriting from PieChart because of similar labeling"""
1056
def __init__(self, *args, **kwargs):
1057
PieChart.__init__(self, *args, **kwargs)
1058
if self.auto_scale and not self.x_range:
1059
warnings.warn('Please specify an x_range with GoogleOMeterChart, '
1060
'otherwise one arrow will always be at the max.')
1062
def type_to_url(self):
1066
class QRChart(Chart):
1068
def __init__(self, *args, **kwargs):
1069
Chart.__init__(self, *args, **kwargs)
1070
self.encoding = None
1071
self.ec_level = None
1074
def type_to_url(self):
1077
def data_to_url(self, data_class=None):
1079
raise NoDataGivenException()
1080
return 'chl=%s' % urllib.parse.quote(self.data[0])
1082
def get_url_bits(self, data_class=None):
1083
url_bits = Chart.get_url_bits(self, data_class=data_class)
1085
url_bits.append('choe=%s' % self.encoding)
1087
url_bits.append('chld=%s%%7c%s' % (self.ec_level, self.margin))
1090
def set_encoding(self, encoding):
1091
self.encoding = encoding
1093
def set_ec(self, level, margin):
1094
self.ec_level = level
1095
self.margin = margin
1098
class ChartGrammar(object):
1104
def parse(self, grammar):
1105
self.grammar = grammar
1106
self.chart = self.create_chart_instance()
1108
for attr in self.grammar:
1109
if attr in ('w', 'h', 'type', 'auto_scale', 'x_range', 'y_range'):
1110
continue # These are already parsed in create_chart_instance
1111
attr_func = 'parse_' + attr
1112
if not hasattr(self, attr_func):
1113
warnings.warn('No parser for grammar attribute "%s"' % (attr))
1115
getattr(self, attr_func)(grammar[attr])
1119
def parse_data(self, data):
1120
self.chart.data = data
1123
def get_possible_chart_types():
1124
possible_charts = []
1125
for cls_name in globals().keys():
1126
if not cls_name.endswith('Chart'):
1128
cls = globals()[cls_name]
1129
# Check if it is an abstract class
1131
a = cls(1, 1, auto_scale=False)
1133
except AbstractClassException:
1136
possible_charts.append(cls_name[:-5])
1137
return possible_charts
1139
def create_chart_instance(self, grammar=None):
1141
grammar = self.grammar
1142
assert(isinstance(grammar, dict)) # grammar must be a dict
1143
assert('w' in grammar) # width is required
1144
assert('h' in grammar) # height is required
1145
assert('type' in grammar) # type is required
1146
chart_type = grammar['type']
1149
auto_scale = grammar.get('auto_scale', None)
1150
x_range = grammar.get('x_range', None)
1151
y_range = grammar.get('y_range', None)
1152
types = ChartGrammar.get_possible_chart_types()
1153
if chart_type not in types:
1154
raise UnknownChartType('%s is an unknown chart type. Possible '
1155
'chart types are %s' % (chart_type, ','.join(types)))
1156
return globals()[chart_type + 'Chart'](w, h, auto_scale=auto_scale,
1157
x_range=x_range, y_range=y_range)
830
chart = GroupedVerticalBarChart(320, 200)
831
chart = PieChart2D(320, 200)
832
chart = ScatterChart(320, 200)
833
chart = SimpleLineChart(320, 200)
834
sine_data = [math.sin(float(a) / 10) * 2000 + 2000 for a in xrange(100)]
835
random_data = [a * random.random() * 30 for a in xrange(40)]
836
random_data2 = [random.random() * 4000 for a in xrange(10)]
837
# chart.set_bar_width(50)
838
# chart.set_bar_spacing(0)
839
chart.add_data(sine_data)
840
chart.add_data(random_data)
841
chart.add_data(random_data2)
842
# chart.set_line_style(1, thickness=2)
843
# chart.set_line_style(2, line_segment=10, blank_segment=5)
844
# chart.set_title('heloooo')
845
# chart.set_legend(('sine wave', 'random * x'))
846
# chart.set_colours(('ee2000', 'DDDDAA', 'fF03f2'))
847
# chart.fill_solid(Chart.BACKGROUND, '123456')
848
# chart.fill_linear_gradient(Chart.CHART, 20, '004070', 1, '300040', 0,
850
# chart.fill_linear_stripes(Chart.CHART, 20, '204070', .2, '300040', .2,
852
axis_left_index = chart.set_axis_range(Axis.LEFT, 0, 10)
853
axis_left_index = chart.set_axis_range(Axis.LEFT, 0, 10)
854
axis_left_index = chart.set_axis_range(Axis.LEFT, 0, 10)
855
axis_right_index = chart.set_axis_range(Axis.RIGHT, 5, 30)
856
axis_bottom_index = chart.set_axis_labels(Axis.BOTTOM, [1, 25, 95])
857
chart.set_axis_positions(axis_bottom_index, [1, 25, 95])
858
chart.set_axis_style(axis_bottom_index, '003050', 15)
860
# chart.set_pie_labels(('apples', 'oranges', 'bananas'))
862
# chart.set_grid(10, 10)
864
# for a in xrange(0, 100, 10):
865
# chart.add_marker(1, a, 'a', 'AACA20', 10)
867
chart.add_horizontal_range('00A020', .2, .5)
868
chart.add_vertical_range('00c030', .2, .4)
870
chart.add_fill_simple('303030A0')
872
chart.download('test.png')
874
url = chart.get_url()
877
data = urllib.urlopen(chart.get_url()).read()
878
open('meh.png', 'wb').write(data)
879
os.system('start meh.png')
882
if __name__ == '__main__':