/+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-16 00:49:44 UTC
  • Revision ID: git-v1:a68673d9fd21ae68718dabc05bbbfd494f0c6d5c
Added helper script

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
29
30
# Helper variables and functions
30
31
# -----------------------------------------------------------------------------
31
32
 
 
33
__version__ = '0.1.2'
 
34
 
32
35
reo_colour = re.compile('^([A-Fa-f0-9]{2,2}){3,4}$')
33
36
 
 
37
 
34
38
def _check_colour(colour):
35
39
    if not reo_colour.match(colour):
36
40
        raise InvalidParametersException('Colours need to be in ' \
40
44
# Exception Classes
41
45
# -----------------------------------------------------------------------------
42
46
 
 
47
 
43
48
class PyGoogleChartException(Exception):
44
49
    pass
45
50
 
 
51
 
46
52
class DataOutOfRangeException(PyGoogleChartException):
47
53
    pass
48
54
 
 
55
 
49
56
class UnknownDataTypeException(PyGoogleChartException):
50
57
    pass
51
58
 
 
59
 
52
60
class NoDataGivenException(PyGoogleChartException):
53
61
    pass
54
62
 
 
63
 
55
64
class InvalidParametersException(PyGoogleChartException):
56
65
    pass
57
66
 
 
67
 
 
68
class BadContentTypeException(PyGoogleChartException):
 
69
    pass
 
70
 
 
71
 
58
72
# Data Classes
59
73
# -----------------------------------------------------------------------------
60
74
 
 
75
 
61
76
class Data(object):
 
77
 
62
78
    def __init__(self, data):
63
79
        assert(type(self) != Data)  # This is an abstract class
64
80
        self.data = data
65
81
 
 
82
 
66
83
class SimpleData(Data):
67
84
    enc_map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
 
85
 
68
86
    def __repr__(self):
69
87
        encoded_data = []
70
88
        for data in self.data:
78
96
                    raise DataOutOfRangeException()
79
97
            encoded_data.append(''.join(sub_data))
80
98
        return 'chd=s:' + ','.join(encoded_data)
 
99
 
81
100
    @staticmethod
82
101
    def max_value():
83
102
        return 61
84
103
 
 
104
 
85
105
class TextData(Data):
 
106
 
86
107
    def __repr__(self):
87
108
        encoded_data = []
88
109
        for data in self.data:
96
117
                    raise DataOutOfRangeException()
97
118
            encoded_data.append(','.join(sub_data))
98
119
        return 'chd=t:' + '|'.join(encoded_data)
 
120
 
99
121
    @staticmethod
100
122
    def max_value():
101
123
        return 100
102
124
 
 
125
 
103
126
class ExtendedData(Data):
104
127
    enc_map = \
105
128
        'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'
 
129
 
106
130
    def __repr__(self):
107
131
        encoded_data = []
108
132
        enc_size = len(ExtendedData.enc_map)
122
146
                        value))
123
147
            encoded_data.append(''.join(sub_data))
124
148
        return 'chd=e:' + ','.join(encoded_data)
 
149
 
125
150
    @staticmethod
126
151
    def max_value():
127
152
        return 4095
129
154
# Axis Classes
130
155
# -----------------------------------------------------------------------------
131
156
 
 
157
 
132
158
class Axis(object):
133
159
    BOTTOM = 'x'
134
160
    TOP = 't'
135
161
    LEFT = 'y'
136
162
    RIGHT = 'r'
137
163
    TYPES = (BOTTOM, TOP, LEFT, RIGHT)
138
 
    def __init__(self, axis, **kw):
139
 
        assert(axis in Axis.TYPES)
 
164
 
 
165
    def __init__(self, axis_index, axis_type, **kw):
 
166
        assert(axis_type in Axis.TYPES)
140
167
        self.has_style = False
141
 
        self.index = None
 
168
        self.axis_index = axis_index
 
169
        self.axis_type = axis_type
142
170
        self.positions = None
143
 
    def set_index(self, index):
144
 
        self.index = index
 
171
 
 
172
    def set_index(self, axis_index):
 
173
        self.axis_index = axis_index
 
174
 
145
175
    def set_positions(self, positions):
146
176
        self.positions = positions
 
177
 
147
178
    def set_style(self, colour, font_size=None, alignment=None):
148
179
        _check_colour(colour)
149
180
        self.colour = colour
150
181
        self.font_size = font_size
151
182
        self.alignment = alignment
152
183
        self.has_style = True
 
184
 
153
185
    def style_to_url(self):
154
186
        bits = []
155
 
        bits.append(str(self.index))
 
187
        bits.append(str(self.axis_index))
156
188
        bits.append(self.colour)
157
189
        if self.font_size is not None:
158
190
            bits.append(str(self.font_size))
159
191
            if self.alignment is not None:
160
192
                bits.append(str(self.alignment))
161
193
        return ','.join(bits)
 
194
 
162
195
    def positions_to_url(self):
163
196
        bits = []
164
 
        bits.append(str(self.index))
165
 
        bits += [ str(a) for a in self.positions ]
 
197
        bits.append(str(self.axis_index))
 
198
        bits += [str(a) for a in self.positions]
166
199
        return ','.join(bits)
167
200
 
 
201
 
168
202
class LabelAxis(Axis):
169
 
    def __init__(self, axis, values, **kwargs):
170
 
        Axis.__init__(self, axis, **kwargs)
171
 
        self.values = [ str(a) for a in values ]
 
203
 
 
204
    def __init__(self, axis_index, axis_type, values, **kwargs):
 
205
        Axis.__init__(self, axis_index, axis_type, **kwargs)
 
206
        self.values = [str(a) for a in values]
 
207
 
172
208
    def __repr__(self):
173
 
        return '%i:|%s' % (self.index, '|'.join(self.values))
 
209
        return '%i:|%s' % (self.axis_index, '|'.join(self.values))
 
210
 
174
211
 
175
212
class RangeAxis(Axis):
176
 
    def __init__(self, axis, low, high, **kwargs):
177
 
        Axis.__init__(self, axis, **kwargs)
 
213
 
 
214
    def __init__(self, axis_index, axis_type, low, high, **kwargs):
 
215
        Axis.__init__(self, axis_index, axis_type, **kwargs)
178
216
        self.low = low
179
217
        self.high = high
 
218
 
180
219
    def __repr__(self):
181
 
        return '%i,%s,%s' % (self.index, self.low, self.high)
 
220
        return '%i,%s,%s' % (self.axis_index, self.low, self.high)
182
221
 
183
222
# Chart Classes
184
223
# -----------------------------------------------------------------------------
185
224
 
 
225
 
186
226
class Chart(object):
 
227
    """Abstract class for all chart types.
 
228
 
 
229
    width are height specify the dimensions of the image. title sets the title
 
230
    of the chart. legend requires a list that corresponds to datasets.
 
231
    """
187
232
 
188
233
    BASE_URL = 'http://chart.apis.google.com/chart?'
189
234
    BACKGROUND = 'bg'
210
255
            Chart.BACKGROUND: None,
211
256
            Chart.CHART: None,
212
257
        }
213
 
        self.axis = {
214
 
            Axis.TOP: None,
215
 
            Axis.BOTTOM: None,
216
 
            Axis.LEFT: None,
217
 
            Axis.RIGHT: None,
218
 
        }
 
258
#        self.axis = {
 
259
#            Axis.TOP: None,
 
260
#            Axis.BOTTOM: None,
 
261
#            Axis.LEFT: None,
 
262
#            Axis.RIGHT: None,
 
263
#        }
 
264
        self.axis = []
219
265
        self.markers = []
220
266
 
221
267
    # URL generation
250
296
 
251
297
    # Downloading
252
298
    # -------------------------------------------------------------------------
 
299
 
253
300
    def download(self, file_name):
 
301
        opener = urllib2.urlopen(self.get_url())
 
302
 
 
303
        if opener.headers['content-type'] != 'image/png':
 
304
            raise BadContentTypeException('Server responded with a ' \
 
305
                'content-type of %s' % opener.headers['content-type'])
 
306
 
254
307
        open(file_name, 'wb').write(urllib.urlopen(self.get_url()).read())
255
308
 
256
309
    # Simple settings
267
320
        assert(isinstance(legend, list) or isinstance(legend, tuple) or
268
321
            legend is None)
269
322
        if legend:
270
 
            self.legend = [ urllib.quote(a) for a in legend ]
 
323
            self.legend = [urllib.quote(a) for a in legend]
271
324
        else:
272
325
            self.legend = None
273
326
 
348
401
 
349
402
    def add_data(self, data):
350
403
        self.data.append(data)
 
404
        return len(self.data) - 1  # return the "index" of the data set
351
405
 
352
406
    def data_to_url(self, data_class=None):
353
407
        if not data_class:
359
413
    # Axis Labels
360
414
    # -------------------------------------------------------------------------
361
415
 
362
 
    def set_axis_labels(self, axis, values):
363
 
        assert(axis in Axis.TYPES)
364
 
        self.axis[axis] = LabelAxis(axis, values)
365
 
 
366
 
    def set_axis_range(self, axis, low, high):
367
 
        assert(axis in Axis.TYPES)
368
 
        self.axis[axis] = RangeAxis(axis, low, high)
369
 
 
370
 
    def set_axis_positions(self, axis, positions):
371
 
        assert(axis in Axis.TYPES)
372
 
        if not self.axis[axis]:
373
 
            raise InvalidParametersException('Please create an axis first')
374
 
        self.axis[axis].set_positions(positions)
375
 
 
376
 
    def set_axis_style(self, axis, colour, font_size=None, alignment=None):
377
 
        assert(axis in Axis.TYPES)
378
 
        if not self.axis[axis]:
379
 
            raise InvalidParametersException('Please create an axis first')
380
 
        self.axis[axis].set_style(colour, font_size, alignment)
 
416
    def set_axis_labels(self, axis_type, values):
 
417
        assert(axis_type in Axis.TYPES)
 
418
        values = [ urllib.quote(a) for a in values ]
 
419
        axis_index = len(self.axis)
 
420
        axis = LabelAxis(axis_index, axis_type, values)
 
421
        self.axis.append(axis)
 
422
        return axis_index
 
423
 
 
424
    def set_axis_range(self, axis_type, low, high):
 
425
        assert(axis_type in Axis.TYPES)
 
426
        axis_index = len(self.axis)
 
427
        axis = RangeAxis(axis_index, axis_type, low, high)
 
428
        self.axis.append(axis)
 
429
        return axis_index
 
430
 
 
431
    def set_axis_positions(self, axis_index, positions):
 
432
        try:
 
433
            self.axis[axis_index].set_positions(positions)
 
434
        except IndexError:
 
435
            raise InvalidParametersException('Axis index %i has not been ' \
 
436
                'created' % axis)
 
437
 
 
438
    def set_axis_style(self, axis_index, colour, font_size=None, \
 
439
            alignment=None):
 
440
        try:
 
441
            self.axis[axis_index].set_style(colour, font_size, alignment)
 
442
        except IndexError:
 
443
            raise InvalidParametersException('Axis index %i has not been ' \
 
444
                'created' % axis)
381
445
 
382
446
    def axis_to_url(self):
383
447
        available_axis = []
386
450
        positions = []
387
451
        styles = []
388
452
        index = -1
389
 
        for position, axis in self.axis.items():
390
 
            if not axis:
391
 
                continue
392
 
            index += 1
393
 
            axis.set_index(index)
394
 
            available_axis.append(position)
 
453
        for axis in self.axis:
 
454
            available_axis.append(axis.axis_type)
395
455
            if isinstance(axis, RangeAxis):
396
456
                range_axis.append(repr(axis))
397
457
            if isinstance(axis, LabelAxis):
418
478
    # -------------------------------------------------------------------------
419
479
 
420
480
    def markers_to_url(self):
421
 
        return 'chm=%s' % '|'.join([ ','.join(a) for a in self.markers ])
 
481
        return 'chm=%s' % '|'.join([','.join(a) for a in self.markers])
422
482
 
423
483
    def add_marker(self, index, point, marker_type, colour, size):
424
484
        self.markers.append((marker_type, colour, str(index), str(point), \
437
497
    def add_fill_simple(self, colour):
438
498
        self.markers.append(('B', colour, '1', '1', '1'))
439
499
 
 
500
 
440
501
class ScatterChart(Chart):
 
502
 
441
503
    def __init__(self, *args, **kwargs):
442
504
        Chart.__init__(self, *args, **kwargs)
 
505
 
443
506
    def type_to_url(self):
444
507
        return 'cht=s'
445
508
 
446
509
 
447
510
class LineChart(Chart):
 
511
 
448
512
    def __init__(self, *args, **kwargs):
 
513
        assert(type(self) != LineChart)  # This is an abstract class
449
514
        Chart.__init__(self, *args, **kwargs)
450
515
        self.line_styles = {}
451
516
        self.grid = None
 
517
 
452
518
    def set_line_style(self, index, thickness=1, line_segment=None, \
453
519
            blank_segment=None):
454
520
        value = []
457
523
            value.append(str(line_segment))
458
524
            value.append(str(blank_segment))
459
525
        self.line_styles[index] = value
 
526
 
460
527
    def set_grid(self, x_step, y_step, line_segment=1, \
461
528
            blank_segment=0):
462
529
        self.grid = '%s,%s,%s,%s' % (x_step, y_step, line_segment, \
463
530
            blank_segment)
 
531
 
464
532
    def get_url_bits(self):
465
533
        url_bits = Chart.get_url_bits(self)
466
534
        if self.line_styles:
477
545
            url_bits.append('chg=%s' % self.grid)
478
546
        return url_bits
479
547
 
 
548
 
480
549
class SimpleLineChart(LineChart):
 
550
 
481
551
    def type_to_url(self):
482
552
        return 'cht=lc'
483
553
 
 
554
 
484
555
class XYLineChart(LineChart):
 
556
 
485
557
    def type_to_url(self):
486
558
        return 'cht=lxy'
487
559
 
 
560
 
488
561
class BarChart(Chart):
 
562
 
489
563
    def __init__(self, *args, **kwargs):
490
564
        assert(type(self) != BarChart)  # This is an abstract class
491
565
        Chart.__init__(self, *args, **kwargs)
492
566
        self.bar_width = None
 
567
 
493
568
    def set_bar_width(self, bar_width):
494
569
        self.bar_width = bar_width
 
570
 
495
571
    def get_url_bits(self):
496
572
        url_bits = Chart.get_url_bits(self)
497
573
        url_bits.append('chbh=%i' % self.bar_width)
498
574
        return url_bits
499
575
 
 
576
 
500
577
class StackedHorizontalBarChart(BarChart):
 
578
 
501
579
    def type_to_url(self):
502
580
        return 'cht=bhs'
503
581
 
 
582
 
504
583
class StackedVerticalBarChart(BarChart):
 
584
 
505
585
    def type_to_url(self):
506
586
        return 'cht=bvs'
507
587
 
 
588
 
508
589
class GroupedBarChart(BarChart):
 
590
 
509
591
    def __init__(self, *args, **kwargs):
510
592
        assert(type(self) != GroupedBarChart)  # This is an abstract class
511
593
        BarChart.__init__(self, *args, **kwargs)
512
594
        self.bar_spacing = None
 
595
 
513
596
    def set_bar_spacing(self, spacing):
514
597
        self.bar_spacing = spacing
 
598
 
515
599
    def get_url_bits(self):
516
600
        # Skip 'BarChart.get_url_bits' and call Chart directly so the parent
517
601
        # doesn't add "chbh" before we do.
525
609
            url_bits.append('chbh=%i' % self.bar_width)
526
610
        return url_bits
527
611
 
 
612
 
528
613
class GroupedHorizontalBarChart(GroupedBarChart):
 
614
 
529
615
    def type_to_url(self):
530
616
        return 'cht=bhg'
531
617
 
 
618
 
532
619
class GroupedVerticalBarChart(GroupedBarChart):
 
620
 
533
621
    def type_to_url(self):
534
622
        return 'cht=bvg'
535
623
 
 
624
 
536
625
class PieChart(Chart):
 
626
 
537
627
    def __init__(self, *args, **kwargs):
538
628
        assert(type(self) != PieChart)  # This is an abstract class
539
629
        Chart.__init__(self, *args, **kwargs)
540
630
        self.pie_labels = []
 
631
 
541
632
    def set_pie_labels(self, labels):
542
 
        self.pie_labels = labels
 
633
        self.pie_labels = [urllib.quote(a) for a in labels]
 
634
 
543
635
    def get_url_bits(self):
544
636
        url_bits = Chart.get_url_bits(self)
545
637
        if self.pie_labels:
546
638
            url_bits.append('chl=%s' % '|'.join(self.pie_labels))
547
639
        return url_bits
548
640
 
 
641
 
549
642
class PieChart2D(PieChart):
 
643
 
550
644
    def type_to_url(self):
551
645
        return 'cht=p'
552
646
 
 
647
 
553
648
class PieChart3D(PieChart):
 
649
 
554
650
    def type_to_url(self):
555
651
        return 'cht=p3'
556
652
 
 
653
 
557
654
class VennChart(Chart):
 
655
 
558
656
    def type_to_url(self):
559
657
        return 'cht=v'
560
658
 
 
659
 
561
660
def test():
562
661
    chart = GroupedVerticalBarChart(320, 200)
563
662
    chart = PieChart2D(320, 200)
564
663
    chart = ScatterChart(320, 200)
565
664
    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) ]
 
665
    sine_data = [math.sin(float(a) / 10) * 2000 + 2000 for a in xrange(100)]
 
666
    random_data = [a * random.random() * 30 for a in xrange(40)]
 
667
    random_data2 = [random.random() * 4000 for a in xrange(10)]
569
668
#    chart.set_bar_width(50)
570
669
#    chart.set_bar_spacing(0)
571
670
    chart.add_data(sine_data)
581
680
#        'aabbcc00', 0.5)
582
681
#    chart.fill_linear_stripes(Chart.CHART, 20, '204070', .2, '300040', .2,
583
682
#        'aabbcc00', 0.2)
584
 
    chart.set_axis_range(Axis.LEFT, 0, 10)
585
 
    chart.set_axis_range(Axis.RIGHT, 5, 30)
586
 
    chart.set_axis_labels(Axis.BOTTOM, [1, 25, 95])
587
 
    chart.set_axis_positions(Axis.BOTTOM, [1, 25, 95])
588
 
    chart.set_axis_style(Axis.BOTTOM, 'FFFFFF', 15)
 
683
    axis_left_index = chart.set_axis_range(Axis.LEFT, 0, 10)
 
684
    axis_left_index = chart.set_axis_range(Axis.LEFT, 0, 10)
 
685
    axis_left_index = chart.set_axis_range(Axis.LEFT, 0, 10)
 
686
    axis_right_index = chart.set_axis_range(Axis.RIGHT, 5, 30)
 
687
    axis_bottom_index = chart.set_axis_labels(Axis.BOTTOM, [1, 25, 95])
 
688
    chart.set_axis_positions(axis_bottom_index, [1, 25, 95])
 
689
    chart.set_axis_style(axis_bottom_index, '003050', 15)
589
690
 
590
691
#    chart.set_pie_labels(('apples', 'oranges', 'bananas'))
591
692
 
599
700
 
600
701
    chart.add_fill_simple('303030A0')
601
702
 
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')
 
703
    chart.download('test.png')
610
704
 
611
705
    url = chart.get_url()
612
706
    print url
615
709
        open('meh.png', 'wb').write(data)
616
710
        os.system('start meh.png')
617
711
 
 
712
 
618
713
if __name__ == '__main__':
619
714
    test()
620