/+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 05:31:11 UTC
  • Revision ID: git-v1:606b8fe55157458136af5c54683fe3079a000b92
Added GPL license file "COPYING"

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