/+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: Gerald Kaszuba
  • Date: 2010-12-24 05:18:55 UTC
  • Revision ID: git-v1:1a633e0584a557e812fae20deceaef514a515f62
gitignore compiled py files and setup.py build dir

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
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):
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
    # -------------------------------------------------------------------------
345
351
        # optional arguments
346
352
        if self.title:
347
353
            url_bits.append('chtt=%s' % self.title)
 
354
        if self.title_colour and self.title_font_size:
 
355
            url_bits.append('chts=%s,%s' % (self.title_colour, \
 
356
                self.title_font_size))
348
357
        if self.legend:
349
 
            url_bits.append('chdl=%s' % '|'.join(self.legend))
 
358
            url_bits.append('chdl=%s' % '%7c'.join(self.legend))
350
359
        if self.legend_position:
351
360
            url_bits.append('chdlp=%s' % (self.legend_position))
352
361
        if self.colours:
353
362
            url_bits.append('chco=%s' % ','.join(self.colours))            
354
363
        if self.colours_within_series:
355
 
            url_bits.append('chco=%s' % '|'.join(self.colours_within_series))
 
364
            url_bits.append('chco=%s' % '%7c'.join(self.colours_within_series))
356
365
        ret = self.fill_to_url()
357
366
        if ret:
358
367
            url_bits.append(ret)
369
378
                else:
370
379
                    values = ('1', )
371
380
                style.append(','.join(values))
372
 
            url_bits.append('chls=%s' % '|'.join(style))
 
381
            url_bits.append('chls=%s' % '%7c'.join(style))
373
382
        if self.grid:
374
383
            url_bits.append('chg=%s' % self.grid)
375
384
        return url_bits
384
393
            raise BadContentTypeException('Server responded with a ' \
385
394
                'content-type of %s' % opener.headers['content-type'])
386
395
 
387
 
        open(file_name, 'wb').write(urllib.urlopen(self.get_url()).read())
 
396
        open(file_name, 'wb').write(opener.read())
388
397
 
389
398
    # Simple settings
390
399
    # -------------------------------------------------------------------------
395
404
        else:
396
405
            self.title = None
397
406
 
 
407
    def set_title_style(self, colour=None, font_size=None):
 
408
        if not colour is None:
 
409
            _check_colour(colour)
 
410
        if not colour and not font_size:
 
411
            return
 
412
        self.title_colour = colour or '333333'
 
413
        self.title_font_size = font_size or 13.5
 
414
 
398
415
    def set_legend(self, legend):
399
416
        """legend needs to be a list, tuple or None"""
400
417
        assert(isinstance(legend, list) or isinstance(legend, tuple) or
447
464
        assert(angle >= 0 and angle <= 90)
448
465
        assert(len(args) % 2 == 0)
449
466
        args = list(args)  # args is probably a tuple and we need to mutate
450
 
        for a in xrange(len(args) / 2):
 
467
        for a in xrange(int(len(args) / 2)):
451
468
            col = args[a * 2]
452
469
            offset = args[a * 2 + 1]
453
470
            _check_colour(col)
474
491
                areas.append('%s,%s,%s' % (area, self.fill_types[area], \
475
492
                    self.fill_area[area]))
476
493
        if areas:
477
 
            return 'chf=' + '|'.join(areas)
 
494
            return 'chf=' + '%7c'.join(areas)
478
495
 
479
496
    # Data
480
497
    # -------------------------------------------------------------------------
551
568
        if x_range is None:
552
569
            x_range = self.data_x_range()
553
570
            if x_range and x_range[0] > 0:
554
 
                x_range = (0, x_range[1])
 
571
                x_range = (x_range[0], x_range[1])
555
572
        self.scaled_x_range = x_range
556
573
 
557
574
        # Determine the y-axis range for scaling.
558
575
        if y_range is None:
559
576
            y_range = self.data_y_range()
560
577
            if y_range and y_range[0] > 0:
561
 
                y_range = (0, y_range[1])
 
578
                y_range = (y_range[0], y_range[1])
562
579
        self.scaled_y_range = y_range
563
580
 
564
581
        scaled_data = []
603
620
 
604
621
    def set_axis_labels(self, axis_type, values):
605
622
        assert(axis_type in Axis.TYPES)
606
 
        values = [urllib.quote(a) for a in values]
 
623
        values = [urllib.quote(str(a)) for a in values]
607
624
        axis_index = len(self.axis)
608
625
        axis = LabelAxis(axis_index, axis_type, values)
609
626
        self.axis.append(axis)
653
670
        url_bits = []
654
671
        url_bits.append('chxt=%s' % ','.join(available_axis))
655
672
        if label_axis:
656
 
            url_bits.append('chxl=%s' % '|'.join(label_axis))
 
673
            url_bits.append('chxl=%s' % '%7c'.join(label_axis))
657
674
        if range_axis:
658
 
            url_bits.append('chxr=%s' % '|'.join(range_axis))
 
675
            url_bits.append('chxr=%s' % '%7c'.join(range_axis))
659
676
        if positions:
660
 
            url_bits.append('chxp=%s' % '|'.join(positions))
 
677
            url_bits.append('chxp=%s' % '%7c'.join(positions))
661
678
        if styles:
662
 
            url_bits.append('chxs=%s' % '|'.join(styles))
 
679
            url_bits.append('chxs=%s' % '%7c'.join(styles))
663
680
        return '&'.join(url_bits)
664
681
 
665
682
    # Markers, Ranges and Fill area (chm)
666
683
    # -------------------------------------------------------------------------
667
684
 
668
685
    def markers_to_url(self):        
669
 
        return 'chm=%s' % '|'.join([','.join(a) for a in self.markers])
 
686
        return 'chm=%s' % '%7c'.join([','.join(a) for a in self.markers])
670
687
 
671
688
    def add_marker(self, index, point, marker_type, colour, size, priority=0):
672
689
        self.markers.append((marker_type, colour, str(index), str(point), \
676
693
        self.markers.append(('r', colour, '0', str(start), str(stop)))
677
694
 
678
695
    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)))
 
696
        self.markers.append(('D', colour, str(data_set), '0', str(size), \
 
697
            str(priority)))
680
698
 
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)))        
 
699
    def add_marker_text(self, string, colour, data_set, data_point, size, \
 
700
            priority=0):
 
701
        self.markers.append((str(string), colour, str(data_set), \
 
702
            str(data_point), str(size), str(priority)))        
683
703
 
684
704
    def add_vertical_range(self, colour, start, stop):
685
705
        self.markers.append(('R', colour, '0', str(start), str(stop)))
885
905
    def get_url_bits(self, data_class=None):
886
906
        url_bits = Chart.get_url_bits(self, data_class=data_class)
887
907
        if self.pie_labels:
888
 
            url_bits.append('chl=%s' % '|'.join(self.pie_labels))
 
908
            url_bits.append('chl=%s' % '%7c'.join(self.pie_labels))
889
909
        return url_bits
890
910
 
891
911
    def annotated_data(self):
894
914
        for dataset in self.data:
895
915
            yield ('x', dataset)
896
916
 
 
917
    def scaled_data(self, data_class, x_range=None, y_range=None):
 
918
        if not x_range:
 
919
            x_range = [0, sum(self.data[0])]
 
920
        return Chart.scaled_data(self, data_class, x_range, self.y_range)
 
921
 
897
922
 
898
923
class PieChart2D(PieChart):
899
924
 
935
960
        Chart.__init__(self, *args, **kwargs)
936
961
        self.geo_area = 'world'
937
962
        self.codes = []
938
 
 
 
963
        self.__areas = ('africa', 'asia', 'europe', 'middle_east',
 
964
            'south_america', 'usa', 'world')
 
965
        self.__ccodes = (
 
966
            'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AN', 'AO', 'AQ', 'AR',
 
967
            'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF',
 
968
            'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BR', 'BS', 'BT',
 
969
            'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI',
 
970
            'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CX', 'CY', 'CZ',
 
971
            'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER',
 
972
            'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD',
 
973
            'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR',
 
974
            'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU',
 
975
            'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE',
 
976
            'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR',
 
977
            'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT',
 
978
            'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK',
 
979
            'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV',
 
980
            'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL',
 
981
            'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH',
 
982
            'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE',
 
983
            'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH',
 
984
            'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'ST', 'SV', 'SY',
 
985
            'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN',
 
986
            'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'US', 'UY',
 
987
            'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE',
 
988
            'YT', 'ZA', 'ZM', 'ZW')
 
989
        
939
990
    def type_to_url(self):
940
991
        return 'cht=t'
941
992
 
942
993
    def set_codes(self, codes):
943
 
        self.codes = codes
 
994
        '''Set the country code map for the data.
 
995
        Codes given in a list.
 
996
 
 
997
        i.e. DE - Germany
 
998
             AT - Austria
 
999
             US - United States
 
1000
        '''
 
1001
 
 
1002
        codemap = ''
 
1003
        
 
1004
        for cc in codes:
 
1005
            cc = cc.upper()
 
1006
            if cc in self.__ccodes:
 
1007
                codemap += cc
 
1008
            else:
 
1009
                raise UnknownCountryCodeException(cc)
 
1010
            
 
1011
        self.codes = codemap
 
1012
 
 
1013
    def set_geo_area(self, area):
 
1014
        '''Sets the geo area for the map.
 
1015
 
 
1016
        * africa
 
1017
        * asia
 
1018
        * europe
 
1019
        * middle_east
 
1020
        * south_america
 
1021
        * usa
 
1022
        * world
 
1023
        '''
 
1024
        
 
1025
        if area in self.__areas:
 
1026
            self.geo_area = area
 
1027
        else:
 
1028
            raise UnknownChartType('Unknown chart type for maps: %s' %area)
944
1029
 
945
1030
    def get_url_bits(self, data_class=None):
946
1031
        url_bits = Chart.get_url_bits(self, data_class=data_class)
949
1034
            url_bits.append('chld=%s' % ''.join(self.codes))
950
1035
        return url_bits
951
1036
 
 
1037
    def add_data_dict(self, datadict):
 
1038
        '''Sets the data and country codes via a dictionary.
 
1039
 
 
1040
        i.e. {'DE': 50, 'GB': 30, 'AT': 70}
 
1041
        '''
 
1042
 
 
1043
        self.set_codes(datadict.keys())
 
1044
        self.add_data(datadict.values())
 
1045
 
952
1046
 
953
1047
class GoogleOMeterChart(PieChart):
954
1048
    """Inheriting from PieChart because of similar labeling"""
984
1078
        if self.encoding:
985
1079
            url_bits.append('choe=%s' % self.encoding)
986
1080
        if self.ec_level:
987
 
            url_bits.append('chld=%s|%s' % (self.ec_level, self.margin))
 
1081
            url_bits.append('chld=%s%%7c%s' % (self.ec_level, self.margin))
988
1082
        return url_bits
989
1083
 
990
1084
    def set_encoding(self, encoding):