/+junk/pygooglechart-py3k

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/%2Bjunk/pygooglechart-py3k

« back to all changes in this revision

Viewing changes to pygooglechart.py

  • Committer: Gustav Hartvigsson
  • Date: 2011-01-03 21:57:12 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20110103215712-1yeiw9tl7oiwh8w1
forgot the the the images in the examples folder...

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
 
4
4
http://pygooglechart.slowchop.com/
5
5
 
6
 
Copyright 2007-2008 Gerald Kaszuba
 
6
Copyright 2007-2009 Gerald Kaszuba
7
7
 
8
8
This program is free software: you can redistribute it and/or modify
9
9
it under the terms of the GNU General Public License as published by
19
19
along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
20
 
21
21
"""
 
22
from __future__ import division
22
23
 
23
24
import os
24
25
import urllib
25
 
import urllib2
 
26
import urllib.request, urllib.error
26
27
import math
27
28
import random
28
29
import re
32
33
# Helper variables and functions
33
34
# -----------------------------------------------------------------------------
34
35
 
35
 
__version__ = '0.2.1'
 
36
__version__ = '0.3.0'
36
37
__author__ = 'Gerald Kaszuba'
37
38
 
38
39
reo_colour = re.compile('^([A-Fa-f0-9]{2,2}){3,4}$')
84
85
class UnknownChartType(PyGoogleChartException):
85
86
    pass
86
87
 
 
88
class UnknownCountryCodeException(PyGoogleChartException):
 
89
    pass
87
90
 
88
91
# Data Classes
89
92
# -----------------------------------------------------------------------------
100
103
    def float_scale_value(cls, value, range):
101
104
        lower, upper = range
102
105
        assert(upper > lower)
103
 
        scaled = (value - lower) * (float(cls.max_value) / (upper - lower))
 
106
        scaled = (value - lower) * (cls.max_value / (upper - lower))
104
107
        return scaled
105
108
 
106
109
    @classmethod
162
165
                else:
163
166
                    raise DataOutOfRangeException()
164
167
            encoded_data.append(','.join(sub_data))
165
 
        return 'chd=t:' + '|'.join(encoded_data)
 
168
        return 'chd=t:' + '%7c'.join(encoded_data)
166
169
 
167
170
    @classmethod
168
171
    def scale_value(cls, value, range):
257
260
        self.values = [str(a) for a in values]
258
261
 
259
262
    def __repr__(self):
260
 
        return '%i:|%s' % (self.axis_index, '|'.join(self.values))
 
263
        return '%i:%%7c%s' % (self.axis_index, '%7c'.join(self.values))
261
264
 
262
265
 
263
266
class RangeAxis(Axis):
281
284
    of the chart. legend requires a list that corresponds to datasets.
282
285
    """
283
286
 
284
 
    BASE_URL = 'http://chart.apis.google.com/chart?'
 
287
    BASE_URL = 'http://chart.apis.google.com/chart'
285
288
    BACKGROUND = 'bg'
286
289
    CHART = 'c'
287
290
    ALPHA = 'a'
301
304
        self.height = height
302
305
        self.data = []
303
306
        self.set_title(title)
 
307
        self.set_title_style(None, None)
304
308
        self.set_legend(legend)
305
309
        self.set_legend_position(None)
306
310
        self.set_colours(colours)
328
332
        self.markers = []
329
333
        self.line_styles = {}
330
334
        self.grid = None
 
335
        self.title_colour = None
 
336
        self.title_font_size = None
331
337
 
332
338
    # URL generation
333
339
    # -------------------------------------------------------------------------
334
 
 
 
340
        
335
341
    def get_url(self, data_class=None):
 
342
        return self.BASE_URL + '?' + self.get_url_extension(data_class)
 
343
    
 
344
    def get_url_extension(self, data_class=None):
336
345
        url_bits = self.get_url_bits(data_class=data_class)
337
 
        return self.BASE_URL + '&'.join(url_bits)
 
346
        return '&'.join(url_bits)
338
347
 
339
348
    def get_url_bits(self, data_class=None):
340
349
        url_bits = []
345
354
        # optional arguments
346
355
        if self.title:
347
356
            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))
348
360
        if self.legend:
349
 
            url_bits.append('chdl=%s' % '|'.join(self.legend))
 
361
            url_bits.append('chdl=%s' % '%7c'.join(self.legend))
350
362
        if self.legend_position:
351
363
            url_bits.append('chdlp=%s' % (self.legend_position))
352
364
        if self.colours:
353
365
            url_bits.append('chco=%s' % ','.join(self.colours))            
354
366
        if self.colours_within_series:
355
 
            url_bits.append('chco=%s' % '|'.join(self.colours_within_series))
 
367
            url_bits.append('chco=%s' % '%7c'.join(self.colours_within_series))
356
368
        ret = self.fill_to_url()
357
369
        if ret:
358
370
            url_bits.append(ret)
363
375
            url_bits.append(self.markers_to_url())        
364
376
        if self.line_styles:
365
377
            style = []
366
 
            for index in xrange(max(self.line_styles) + 1):
 
378
            for index in range(max(self.line_styles) + 1):
367
379
                if index in self.line_styles:
368
380
                    values = self.line_styles[index]
369
381
                else:
370
382
                    values = ('1', )
371
383
                style.append(','.join(values))
372
 
            url_bits.append('chls=%s' % '|'.join(style))
 
384
            url_bits.append('chls=%s' % '%7c'.join(style))
373
385
        if self.grid:
374
386
            url_bits.append('chg=%s' % self.grid)
375
387
        return url_bits
377
389
    # Downloading
378
390
    # -------------------------------------------------------------------------
379
391
 
380
 
    def download(self, file_name):
381
 
        opener = urllib2.urlopen(self.get_url())
 
392
    def download(self, file_name, use_post=True):
 
393
        if use_post:
 
394
            opener = urllib.request.urlopen(self.BASE_URL, self.get_url_extension())
 
395
        else:
 
396
            opener = urllib.request.urlopen(self.get_url())
382
397
 
383
398
        if opener.headers['content-type'] != 'image/png':
384
399
            raise BadContentTypeException('Server responded with a ' \
385
400
                'content-type of %s' % opener.headers['content-type'])
386
401
 
387
 
        open(file_name, 'wb').write(urllib.urlopen(self.get_url()).read())
 
402
        open(file_name, 'wb').write(opener.read())
388
403
 
389
404
    # Simple settings
390
405
    # -------------------------------------------------------------------------
391
406
 
392
407
    def set_title(self, title):
393
408
        if title:
394
 
            self.title = urllib.quote(title)
 
409
            self.title = urllib.parse.quote(title)
395
410
        else:
396
411
            self.title = None
397
412
 
 
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:
 
417
            return
 
418
        self.title_colour = colour or '333333'
 
419
        self.title_font_size = font_size or 13.5
 
420
 
398
421
    def set_legend(self, legend):
399
422
        """legend needs to be a list, tuple or None"""
400
423
        assert(isinstance(legend, list) or isinstance(legend, tuple) or
401
424
            legend is None)
402
425
        if legend:
403
 
            self.legend = [urllib.quote(a) for a in legend]
 
426
            self.legend = [urllib.parse.quote(a) for a in legend]
404
427
        else:
405
428
            self.legend = None
406
429
 
407
430
    def set_legend_position(self, legend_position):
408
431
        if legend_position:
409
 
            self.legend_position = urllib.quote(legend_position)
 
432
            self.legend_position = urllib.parse.quote(legend_position)
410
433
        else:    
411
434
            self.legend_position = None
412
435
 
447
470
        assert(angle >= 0 and angle <= 90)
448
471
        assert(len(args) % 2 == 0)
449
472
        args = list(args)  # args is probably a tuple and we need to mutate
450
 
        for a in xrange(len(args) / 2):
 
473
        for a in range(int(len(args) / 2)):
451
474
            col = args[a * 2]
452
475
            offset = args[a * 2 + 1]
453
476
            _check_colour(col)
474
497
                areas.append('%s,%s,%s' % (area, self.fill_types[area], \
475
498
                    self.fill_area[area]))
476
499
        if areas:
477
 
            return 'chf=' + '|'.join(areas)
 
500
            return 'chf=' + '%7c'.join(areas)
478
501
 
479
502
    # Data
480
503
    # -------------------------------------------------------------------------
551
574
        if x_range is None:
552
575
            x_range = self.data_x_range()
553
576
            if x_range and x_range[0] > 0:
554
 
                x_range = (0, x_range[1])
 
577
                x_range = (x_range[0], x_range[1])
555
578
        self.scaled_x_range = x_range
556
579
 
557
580
        # Determine the y-axis range for scaling.
558
581
        if y_range is None:
559
582
            y_range = self.data_y_range()
560
583
            if y_range and y_range[0] > 0:
561
 
                y_range = (0, y_range[1])
 
584
                y_range = (y_range[0], y_range[1])
562
585
        self.scaled_y_range = y_range
563
586
 
564
587
        scaled_data = []
603
626
 
604
627
    def set_axis_labels(self, axis_type, values):
605
628
        assert(axis_type in Axis.TYPES)
606
 
        values = [urllib.quote(a) for a in values]
 
629
        values = [urllib.parse.quote(str(a)) for a in values]
607
630
        axis_index = len(self.axis)
608
631
        axis = LabelAxis(axis_index, axis_type, values)
609
632
        self.axis.append(axis)
653
676
        url_bits = []
654
677
        url_bits.append('chxt=%s' % ','.join(available_axis))
655
678
        if label_axis:
656
 
            url_bits.append('chxl=%s' % '|'.join(label_axis))
 
679
            url_bits.append('chxl=%s' % '%7c'.join(label_axis))
657
680
        if range_axis:
658
 
            url_bits.append('chxr=%s' % '|'.join(range_axis))
 
681
            url_bits.append('chxr=%s' % '%7c'.join(range_axis))
659
682
        if positions:
660
 
            url_bits.append('chxp=%s' % '|'.join(positions))
 
683
            url_bits.append('chxp=%s' % '%7c'.join(positions))
661
684
        if styles:
662
 
            url_bits.append('chxs=%s' % '|'.join(styles))
 
685
            url_bits.append('chxs=%s' % '%7c'.join(styles))
663
686
        return '&'.join(url_bits)
664
687
 
665
688
    # Markers, Ranges and Fill area (chm)
666
689
    # -------------------------------------------------------------------------
667
690
 
668
691
    def markers_to_url(self):        
669
 
        return 'chm=%s' % '|'.join([','.join(a) for a in self.markers])
 
692
        return 'chm=%s' % '%7c'.join([','.join(a) for a in self.markers])
670
693
 
671
694
    def add_marker(self, index, point, marker_type, colour, size, priority=0):
672
695
        self.markers.append((marker_type, colour, str(index), str(point), \
676
699
        self.markers.append(('r', colour, '0', str(start), str(stop)))
677
700
 
678
701
    def add_data_line(self, colour, data_set, size, priority=0):
679
 
        self.markers.append(('D', colour, str(data_set), '0', str(size), str(priority)))
 
702
        self.markers.append(('D', colour, str(data_set), '0', str(size), \
 
703
            str(priority)))
680
704
 
681
 
    def add_marker_text(self, string, colour, data_set, data_point, size, priority=0):
682
 
        self.markers.append((str(string), colour, str(data_set), str(data_point), str(size), str(priority)))        
 
705
    def add_marker_text(self, string, colour, data_set, data_point, size, \
 
706
            priority=0):
 
707
        self.markers.append((str(string), colour, str(data_set), \
 
708
            str(data_point), str(size), str(priority)))        
683
709
 
684
710
    def add_vertical_range(self, colour, start, stop):
685
711
        self.markers.append(('R', colour, '0', str(start), str(stop)))
786
812
            url_bits.append('chbh=%i' % self.bar_width)
787
813
        zero_line = []
788
814
        if self.zero_lines:
789
 
            for index in xrange(max(self.zero_lines) + 1):
 
815
            for index in range(max(self.zero_lines) + 1):
790
816
                if index in self.zero_lines:
791
817
                    zero_line.append(str(self.zero_lines[index]))
792
818
                else:
880
906
                (self.__class__.__name__))
881
907
 
882
908
    def set_pie_labels(self, labels):
883
 
        self.pie_labels = [urllib.quote(a) for a in labels]
 
909
        self.pie_labels = [urllib.parse.quote(a) for a in labels]
884
910
 
885
911
    def get_url_bits(self, data_class=None):
886
912
        url_bits = Chart.get_url_bits(self, data_class=data_class)
887
913
        if self.pie_labels:
888
 
            url_bits.append('chl=%s' % '|'.join(self.pie_labels))
 
914
            url_bits.append('chl=%s' % '%7c'.join(self.pie_labels))
889
915
        return url_bits
890
916
 
891
917
    def annotated_data(self):
894
920
        for dataset in self.data:
895
921
            yield ('x', dataset)
896
922
 
 
923
    def scaled_data(self, data_class, x_range=None, y_range=None):
 
924
        if not x_range:
 
925
            x_range = [0, sum(self.data[0])]
 
926
        return Chart.scaled_data(self, data_class, x_range, self.y_range)
 
927
 
897
928
 
898
929
class PieChart2D(PieChart):
899
930
 
935
966
        Chart.__init__(self, *args, **kwargs)
936
967
        self.geo_area = 'world'
937
968
        self.codes = []
938
 
 
 
969
        self.__areas = ('africa', 'asia', 'europe', 'middle_east',
 
970
            'south_america', 'usa', 'world')
 
971
        self.__ccodes = (
 
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')
 
995
        
939
996
    def type_to_url(self):
940
997
        return 'cht=t'
941
998
 
942
999
    def set_codes(self, codes):
943
 
        self.codes = codes
 
1000
        '''Set the country code map for the data.
 
1001
        Codes given in a list.
 
1002
 
 
1003
        i.e. DE - Germany
 
1004
             AT - Austria
 
1005
             US - United States
 
1006
        '''
 
1007
 
 
1008
        codemap = ''
 
1009
        
 
1010
        for cc in codes:
 
1011
            cc = cc.upper()
 
1012
            if cc in self.__ccodes:
 
1013
                codemap += cc
 
1014
            else:
 
1015
                raise UnknownCountryCodeException(cc)
 
1016
            
 
1017
        self.codes = codemap
 
1018
 
 
1019
    def set_geo_area(self, area):
 
1020
        '''Sets the geo area for the map.
 
1021
 
 
1022
        * africa
 
1023
        * asia
 
1024
        * europe
 
1025
        * middle_east
 
1026
        * south_america
 
1027
        * usa
 
1028
        * world
 
1029
        '''
 
1030
        
 
1031
        if area in self.__areas:
 
1032
            self.geo_area = area
 
1033
        else:
 
1034
            raise UnknownChartType('Unknown chart type for maps: %s' %area)
944
1035
 
945
1036
    def get_url_bits(self, data_class=None):
946
1037
        url_bits = Chart.get_url_bits(self, data_class=data_class)
949
1040
            url_bits.append('chld=%s' % ''.join(self.codes))
950
1041
        return url_bits
951
1042
 
 
1043
    def add_data_dict(self, datadict):
 
1044
        '''Sets the data and country codes via a dictionary.
 
1045
 
 
1046
        i.e. {'DE': 50, 'GB': 30, 'AT': 70}
 
1047
        '''
 
1048
 
 
1049
        self.set_codes(datadict.keys())
 
1050
        self.add_data(datadict.values())
 
1051
 
952
1052
 
953
1053
class GoogleOMeterChart(PieChart):
954
1054
    """Inheriting from PieChart because of similar labeling"""
977
1077
    def data_to_url(self, data_class=None):
978
1078
        if not self.data:
979
1079
            raise NoDataGivenException()
980
 
        return 'chl=%s' % urllib.quote(self.data[0])
 
1080
        return 'chl=%s' % urllib.parse.quote(self.data[0])
981
1081
 
982
1082
    def get_url_bits(self, data_class=None):
983
1083
        url_bits = Chart.get_url_bits(self, data_class=data_class)
984
1084
        if self.encoding:
985
1085
            url_bits.append('choe=%s' % self.encoding)
986
1086
        if self.ec_level:
987
 
            url_bits.append('chld=%s|%s' % (self.ec_level, self.margin))
 
1087
            url_bits.append('chld=%s%%7c%s' % (self.ec_level, self.margin))
988
1088
        return url_bits
989
1089
 
990
1090
    def set_encoding(self, encoding):