/+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: gak
  • Date: 2007-12-13 22:35:59 UTC
  • Revision ID: git-v1:a7ad1afe3f08b7de8b3a3cbdc7165d4f88ddbe92
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
 
23
23
import os
24
24
import urllib
 
25
import urllib2
25
26
import math
26
27
import random
27
28
import re
31
32
 
32
33
reo_colour = re.compile('^([A-Fa-f0-9]{2,2}){3,4}$')
33
34
 
 
35
 
34
36
def _check_colour(colour):
35
37
    if not reo_colour.match(colour):
36
38
        raise InvalidParametersException('Colours need to be in ' \
40
42
# Exception Classes
41
43
# -----------------------------------------------------------------------------
42
44
 
 
45
 
43
46
class PyGoogleChartException(Exception):
44
47
    pass
45
48
 
 
49
 
46
50
class DataOutOfRangeException(PyGoogleChartException):
47
51
    pass
48
52
 
 
53
 
49
54
class UnknownDataTypeException(PyGoogleChartException):
50
55
    pass
51
56
 
 
57
 
52
58
class NoDataGivenException(PyGoogleChartException):
53
59
    pass
54
60
 
 
61
 
55
62
class InvalidParametersException(PyGoogleChartException):
56
63
    pass
57
64
 
 
65
 
 
66
class BadContentTypeException(PyGoogleChartException):
 
67
    pass
 
68
 
 
69
 
58
70
# Data Classes
59
71
# -----------------------------------------------------------------------------
60
72
 
 
73
 
61
74
class Data(object):
 
75
 
62
76
    def __init__(self, data):
63
77
        assert(type(self) != Data)  # This is an abstract class
64
78
        self.data = data
65
79
 
 
80
 
66
81
class SimpleData(Data):
67
82
    enc_map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
 
83
 
68
84
    def __repr__(self):
69
85
        encoded_data = []
70
86
        for data in self.data:
78
94
                    raise DataOutOfRangeException()
79
95
            encoded_data.append(''.join(sub_data))
80
96
        return 'chd=s:' + ','.join(encoded_data)
 
97
 
81
98
    @staticmethod
82
99
    def max_value():
83
100
        return 61
84
101
 
 
102
 
85
103
class TextData(Data):
 
104
 
86
105
    def __repr__(self):
87
106
        encoded_data = []
88
107
        for data in self.data:
96
115
                    raise DataOutOfRangeException()
97
116
            encoded_data.append(','.join(sub_data))
98
117
        return 'chd=t:' + '|'.join(encoded_data)
 
118
 
99
119
    @staticmethod
100
120
    def max_value():
101
121
        return 100
102
122
 
 
123
 
103
124
class ExtendedData(Data):
104
125
    enc_map = \
105
126
        'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'
 
127
 
106
128
    def __repr__(self):
107
129
        encoded_data = []
108
130
        enc_size = len(ExtendedData.enc_map)
122
144
                        value))
123
145
            encoded_data.append(''.join(sub_data))
124
146
        return 'chd=e:' + ','.join(encoded_data)
 
147
 
125
148
    @staticmethod
126
149
    def max_value():
127
150
        return 4095
129
152
# Axis Classes
130
153
# -----------------------------------------------------------------------------
131
154
 
 
155
 
132
156
class Axis(object):
133
157
    BOTTOM = 'x'
134
158
    TOP = 't'
135
159
    LEFT = 'y'
136
160
    RIGHT = 'r'
137
161
    TYPES = (BOTTOM, TOP, LEFT, RIGHT)
 
162
 
138
163
    def __init__(self, axis, **kw):
139
164
        assert(axis in Axis.TYPES)
140
165
        self.has_style = False
141
166
        self.index = None
142
167
        self.positions = None
 
168
 
143
169
    def set_index(self, index):
144
170
        self.index = index
 
171
 
145
172
    def set_positions(self, positions):
146
173
        self.positions = positions
 
174
 
147
175
    def set_style(self, colour, font_size=None, alignment=None):
148
176
        _check_colour(colour)
149
177
        self.colour = colour
150
178
        self.font_size = font_size
151
179
        self.alignment = alignment
152
180
        self.has_style = True
 
181
 
153
182
    def style_to_url(self):
154
183
        bits = []
155
184
        bits.append(str(self.index))
159
188
            if self.alignment is not None:
160
189
                bits.append(str(self.alignment))
161
190
        return ','.join(bits)
 
191
 
162
192
    def positions_to_url(self):
163
193
        bits = []
164
194
        bits.append(str(self.index))
165
 
        bits += [ str(a) for a in self.positions ]
 
195
        bits += [str(a) for a in self.positions]
166
196
        return ','.join(bits)
167
197
 
 
198
 
168
199
class LabelAxis(Axis):
 
200
 
169
201
    def __init__(self, axis, values, **kwargs):
170
202
        Axis.__init__(self, axis, **kwargs)
171
 
        self.values = [ str(a) for a in values ]
 
203
        self.values = [str(a) for a in values]
 
204
 
172
205
    def __repr__(self):
173
206
        return '%i:|%s' % (self.index, '|'.join(self.values))
174
207
 
 
208
 
175
209
class RangeAxis(Axis):
 
210
 
176
211
    def __init__(self, axis, low, high, **kwargs):
177
212
        Axis.__init__(self, axis, **kwargs)
178
213
        self.low = low
179
214
        self.high = high
 
215
 
180
216
    def __repr__(self):
181
217
        return '%i,%s,%s' % (self.index, self.low, self.high)
182
218
 
183
219
# Chart Classes
184
220
# -----------------------------------------------------------------------------
185
221
 
 
222
 
186
223
class Chart(object):
 
224
    """Abstract class for all chart types.
 
225
 
 
226
    width are height specify the dimensions of the image. title sets the title
 
227
    of the chart. legend requires a list that corresponds to datasets.
 
228
    """
187
229
 
188
230
    BASE_URL = 'http://chart.apis.google.com/chart?'
189
231
    BACKGROUND = 'bg'
250
292
 
251
293
    # Downloading
252
294
    # -------------------------------------------------------------------------
 
295
 
253
296
    def download(self, file_name):
 
297
        opener = urllib2.urlopen(self.get_url())
 
298
 
 
299
        if opener.headers['content-type'] != 'image/png':
 
300
            raise BadContentTypeException('Server responded with a ' \
 
301
                'content-type of %s' % opener.headers['content-type'])
 
302
 
254
303
        open(file_name, 'wb').write(urllib.urlopen(self.get_url()).read())
255
304
 
256
305
    # Simple settings
267
316
        assert(isinstance(legend, list) or isinstance(legend, tuple) or
268
317
            legend is None)
269
318
        if legend:
270
 
            self.legend = [ urllib.quote(a) for a in legend ]
 
319
            self.legend = [urllib.quote(a) for a in legend]
271
320
        else:
272
321
            self.legend = None
273
322
 
348
397
 
349
398
    def add_data(self, data):
350
399
        self.data.append(data)
 
400
        return len(self.data) - 1  # return the "index" of the data set
351
401
 
352
402
    def data_to_url(self, data_class=None):
353
403
        if not data_class:
418
468
    # -------------------------------------------------------------------------
419
469
 
420
470
    def markers_to_url(self):
421
 
        return 'chm=%s' % '|'.join([ ','.join(a) for a in self.markers ])
 
471
        return 'chm=%s' % '|'.join([','.join(a) for a in self.markers])
422
472
 
423
473
    def add_marker(self, index, point, marker_type, colour, size):
424
474
        self.markers.append((marker_type, colour, str(index), str(point), \
437
487
    def add_fill_simple(self, colour):
438
488
        self.markers.append(('B', colour, '1', '1', '1'))
439
489
 
 
490
 
440
491
class ScatterChart(Chart):
 
492
 
441
493
    def __init__(self, *args, **kwargs):
442
494
        Chart.__init__(self, *args, **kwargs)
 
495
 
443
496
    def type_to_url(self):
444
497
        return 'cht=s'
445
498
 
446
499
 
447
500
class LineChart(Chart):
 
501
 
448
502
    def __init__(self, *args, **kwargs):
 
503
        assert(type(self) != LineChart)  # This is an abstract class
449
504
        Chart.__init__(self, *args, **kwargs)
450
505
        self.line_styles = {}
451
506
        self.grid = None
 
507
 
452
508
    def set_line_style(self, index, thickness=1, line_segment=None, \
453
509
            blank_segment=None):
454
510
        value = []
457
513
            value.append(str(line_segment))
458
514
            value.append(str(blank_segment))
459
515
        self.line_styles[index] = value
 
516
 
460
517
    def set_grid(self, x_step, y_step, line_segment=1, \
461
518
            blank_segment=0):
462
519
        self.grid = '%s,%s,%s,%s' % (x_step, y_step, line_segment, \
463
520
            blank_segment)
 
521
 
464
522
    def get_url_bits(self):
465
523
        url_bits = Chart.get_url_bits(self)
466
524
        if self.line_styles:
477
535
            url_bits.append('chg=%s' % self.grid)
478
536
        return url_bits
479
537
 
 
538
 
480
539
class SimpleLineChart(LineChart):
 
540
 
481
541
    def type_to_url(self):
482
542
        return 'cht=lc'
483
543
 
 
544
 
484
545
class XYLineChart(LineChart):
 
546
 
485
547
    def type_to_url(self):
486
548
        return 'cht=lxy'
487
549
 
 
550
 
488
551
class BarChart(Chart):
 
552
 
489
553
    def __init__(self, *args, **kwargs):
490
554
        assert(type(self) != BarChart)  # This is an abstract class
491
555
        Chart.__init__(self, *args, **kwargs)
492
556
        self.bar_width = None
 
557
 
493
558
    def set_bar_width(self, bar_width):
494
559
        self.bar_width = bar_width
 
560
 
495
561
    def get_url_bits(self):
496
562
        url_bits = Chart.get_url_bits(self)
497
563
        url_bits.append('chbh=%i' % self.bar_width)
498
564
        return url_bits
499
565
 
 
566
 
500
567
class StackedHorizontalBarChart(BarChart):
 
568
 
501
569
    def type_to_url(self):
502
570
        return 'cht=bhs'
503
571
 
 
572
 
504
573
class StackedVerticalBarChart(BarChart):
 
574
 
505
575
    def type_to_url(self):
506
576
        return 'cht=bvs'
507
577
 
 
578
 
508
579
class GroupedBarChart(BarChart):
 
580
 
509
581
    def __init__(self, *args, **kwargs):
510
582
        assert(type(self) != GroupedBarChart)  # This is an abstract class
511
583
        BarChart.__init__(self, *args, **kwargs)
512
584
        self.bar_spacing = None
 
585
 
513
586
    def set_bar_spacing(self, spacing):
514
587
        self.bar_spacing = spacing
 
588
 
515
589
    def get_url_bits(self):
516
590
        # Skip 'BarChart.get_url_bits' and call Chart directly so the parent
517
591
        # doesn't add "chbh" before we do.
525
599
            url_bits.append('chbh=%i' % self.bar_width)
526
600
        return url_bits
527
601
 
 
602
 
528
603
class GroupedHorizontalBarChart(GroupedBarChart):
 
604
 
529
605
    def type_to_url(self):
530
606
        return 'cht=bhg'
531
607
 
 
608
 
532
609
class GroupedVerticalBarChart(GroupedBarChart):
 
610
 
533
611
    def type_to_url(self):
534
612
        return 'cht=bvg'
535
613
 
 
614
 
536
615
class PieChart(Chart):
 
616
 
537
617
    def __init__(self, *args, **kwargs):
538
618
        assert(type(self) != PieChart)  # This is an abstract class
539
619
        Chart.__init__(self, *args, **kwargs)
540
620
        self.pie_labels = []
 
621
 
541
622
    def set_pie_labels(self, labels):
542
623
        self.pie_labels = labels
 
624
 
543
625
    def get_url_bits(self):
544
626
        url_bits = Chart.get_url_bits(self)
545
627
        if self.pie_labels:
546
628
            url_bits.append('chl=%s' % '|'.join(self.pie_labels))
547
629
        return url_bits
548
630
 
 
631
 
549
632
class PieChart2D(PieChart):
 
633
 
550
634
    def type_to_url(self):
551
635
        return 'cht=p'
552
636
 
 
637
 
553
638
class PieChart3D(PieChart):
 
639
 
554
640
    def type_to_url(self):
555
641
        return 'cht=p3'
556
642
 
 
643
 
557
644
class VennChart(Chart):
 
645
 
558
646
    def type_to_url(self):
559
647
        return 'cht=v'
560
648
 
 
649
 
561
650
def test():
562
651
    chart = GroupedVerticalBarChart(320, 200)
563
652
    chart = PieChart2D(320, 200)
564
653
    chart = ScatterChart(320, 200)
565
654
    chart = SimpleLineChart(320, 200)
566
 
    sine_data = [ math.sin(float(a) / 10) * 2000 + 2000 for a in xrange(100) ]
567
 
    random_data = [ a * random.random() * 30 for a in xrange(40) ]
568
 
    random_data2 = [ random.random() * 4000 for a in xrange(10) ]
 
655
    sine_data = [math.sin(float(a) / 10) * 2000 + 2000 for a in xrange(100)]
 
656
    random_data = [a * random.random() * 30 for a in xrange(40)]
 
657
    random_data2 = [random.random() * 4000 for a in xrange(10)]
569
658
#    chart.set_bar_width(50)
570
659
#    chart.set_bar_spacing(0)
571
660
    chart.add_data(sine_data)
599
688
 
600
689
    chart.add_fill_simple('303030A0')
601
690
 
602
 
 
603
 
    chart = SimpleLineChart(320, 200)
604
 
    data = [ 1, 5, 30, 10, 25 ]
605
 
    chart.add_data(data)
606
 
    chart.set_title('Hello World!')
607
 
    chart.set_axis_range(Axis.LEFT, 0, 10)
608
 
    print chart.get_url()
609
 
    chart.download('hello.png')
 
691
    chart.download('test.png')
610
692
 
611
693
    url = chart.get_url()
612
694
    print url
615
697
        open('meh.png', 'wb').write(data)
616
698
        os.system('start meh.png')
617
699
 
 
700
 
618
701
if __name__ == '__main__':
619
702
    test()
620