/+junk/pygooglechart-py3k

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/%2Bjunk/pygooglechart-py3k
2 by gak
small changes
1
"""
3 by gak
branched to 0.1.0
2
PyGoogleChart - A complete Python wrapper for the Google Chart API
2 by gak
small changes
3
4
http://pygooglechart.slowchop.com/
5
6
Copyright 2007 Gerald Kaszuba
7
8
This program is free software: you can redistribute it and/or modify
9
it under the terms of the GNU General Public License as published by
10
the Free Software Foundation, either version 3 of the License, or
11
(at your option) any later version.
12
13
This program is distributed in the hope that it will be useful,
14
but WITHOUT ANY WARRANTY; without even the implied warranty of
15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
GNU General Public License for more details.
17
18
You should have received a copy of the GNU General Public License
19
along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21
"""
22
1 by gak
initial import
23
import os
24
import urllib
7 by gak
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.
25
import urllib2
1 by gak
initial import
26
import math
27
import random
28
import re
29
30
# Helper variables and functions
31
# -----------------------------------------------------------------------------
32
33
reo_colour = re.compile('^([A-Fa-f0-9]{2,2}){3,4}$')
34
7 by gak
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.
35
1 by gak
initial import
36
def _check_colour(colour):
37
    if not reo_colour.match(colour):
38
        raise InvalidParametersException('Colours need to be in ' \
39
            'RRGGBB or RRGGBBAA format. One of your colours has %s' % \
40
            colour)
41
42
# Exception Classes
43
# -----------------------------------------------------------------------------
44
7 by gak
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.
45
1 by gak
initial import
46
class PyGoogleChartException(Exception):
47
    pass
48
7 by gak
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.
49
1 by gak
initial import
50
class DataOutOfRangeException(PyGoogleChartException):
51
    pass
52
7 by gak
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.
53
1 by gak
initial import
54
class UnknownDataTypeException(PyGoogleChartException):
55
    pass
56
7 by gak
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.
57
1 by gak
initial import
58
class NoDataGivenException(PyGoogleChartException):
59
    pass
60
7 by gak
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.
61
1 by gak
initial import
62
class InvalidParametersException(PyGoogleChartException):
63
    pass
64
7 by gak
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.
65
66
class BadContentTypeException(PyGoogleChartException):
67
    pass
68
69
1 by gak
initial import
70
# Data Classes
71
# -----------------------------------------------------------------------------
72
7 by gak
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.
73
1 by gak
initial import
74
class Data(object):
7 by gak
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.
75
1 by gak
initial import
76
    def __init__(self, data):
77
        assert(type(self) != Data)  # This is an abstract class
78
        self.data = data
79
7 by gak
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.
80
1 by gak
initial import
81
class SimpleData(Data):
82
    enc_map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
7 by gak
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.
83
1 by gak
initial import
84
    def __repr__(self):
85
        encoded_data = []
86
        for data in self.data:
87
            sub_data = []
88
            for value in data:
89
                if value is None:
90
                    sub_data.append('_')
91
                elif value >= 0 and value <= SimpleData.max_value:
92
                    sub_data.append(SimpleData.enc_map[value])
93
                else:
94
                    raise DataOutOfRangeException()
95
            encoded_data.append(''.join(sub_data))
96
        return 'chd=s:' + ','.join(encoded_data)
7 by gak
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.
97
1 by gak
initial import
98
    @staticmethod
99
    def max_value():
100
        return 61
101
7 by gak
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.
102
1 by gak
initial import
103
class TextData(Data):
7 by gak
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.
104
1 by gak
initial import
105
    def __repr__(self):
106
        encoded_data = []
107
        for data in self.data:
108
            sub_data = []
109
            for value in data:
110
                if value is None:
111
                    sub_data.append(-1)
112
                elif value >= 0 and value <= TextData.max_value:
113
                    sub_data.append(str(float(value)))
114
                else:
115
                    raise DataOutOfRangeException()
116
            encoded_data.append(','.join(sub_data))
117
        return 'chd=t:' + '|'.join(encoded_data)
7 by gak
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.
118
1 by gak
initial import
119
    @staticmethod
120
    def max_value():
121
        return 100
122
7 by gak
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.
123
1 by gak
initial import
124
class ExtendedData(Data):
125
    enc_map = \
126
        'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'
7 by gak
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.
127
1 by gak
initial import
128
    def __repr__(self):
129
        encoded_data = []
130
        enc_size = len(ExtendedData.enc_map)
131
        for data in self.data:
132
            sub_data = []
133
            for value in data:
134
                if value is None:
135
                    sub_data.append('__')
136
                elif value >= 0 and value <= ExtendedData.max_value:
137
                    first, second = divmod(int(value), enc_size)
138
                    sub_data.append('%s%s' % (
139
                        ExtendedData.enc_map[first],
140
                        ExtendedData.enc_map[second]))
141
                else:
142
                    raise DataOutOfRangeException( \
143
                        'Item #%i "%s" is out of range' % (data.index(value), \
144
                        value))
145
            encoded_data.append(''.join(sub_data))
146
        return 'chd=e:' + ','.join(encoded_data)
7 by gak
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.
147
1 by gak
initial import
148
    @staticmethod
149
    def max_value():
150
        return 4095
151
152
# Axis Classes
153
# -----------------------------------------------------------------------------
154
7 by gak
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.
155
1 by gak
initial import
156
class Axis(object):
157
    BOTTOM = 'x'
158
    TOP = 't'
159
    LEFT = 'y'
160
    RIGHT = 'r'
161
    TYPES = (BOTTOM, TOP, LEFT, RIGHT)
7 by gak
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.
162
1 by gak
initial import
163
    def __init__(self, axis, **kw):
164
        assert(axis in Axis.TYPES)
165
        self.has_style = False
166
        self.index = None
167
        self.positions = None
7 by gak
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.
168
1 by gak
initial import
169
    def set_index(self, index):
170
        self.index = index
7 by gak
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.
171
1 by gak
initial import
172
    def set_positions(self, positions):
173
        self.positions = positions
7 by gak
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.
174
1 by gak
initial import
175
    def set_style(self, colour, font_size=None, alignment=None):
176
        _check_colour(colour)
177
        self.colour = colour
178
        self.font_size = font_size
179
        self.alignment = alignment
180
        self.has_style = True
7 by gak
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.
181
1 by gak
initial import
182
    def style_to_url(self):
183
        bits = []
184
        bits.append(str(self.index))
185
        bits.append(self.colour)
186
        if self.font_size is not None:
187
            bits.append(str(self.font_size))
188
            if self.alignment is not None:
189
                bits.append(str(self.alignment))
190
        return ','.join(bits)
7 by gak
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.
191
1 by gak
initial import
192
    def positions_to_url(self):
193
        bits = []
194
        bits.append(str(self.index))
7 by gak
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.
195
        bits += [str(a) for a in self.positions]
1 by gak
initial import
196
        return ','.join(bits)
197
7 by gak
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.
198
1 by gak
initial import
199
class LabelAxis(Axis):
7 by gak
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.
200
1 by gak
initial import
201
    def __init__(self, axis, values, **kwargs):
202
        Axis.__init__(self, axis, **kwargs)
7 by gak
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.
203
        self.values = [str(a) for a in values]
204
1 by gak
initial import
205
    def __repr__(self):
206
        return '%i:|%s' % (self.index, '|'.join(self.values))
207
7 by gak
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.
208
1 by gak
initial import
209
class RangeAxis(Axis):
7 by gak
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.
210
1 by gak
initial import
211
    def __init__(self, axis, low, high, **kwargs):
212
        Axis.__init__(self, axis, **kwargs)
213
        self.low = low
214
        self.high = high
7 by gak
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.
215
1 by gak
initial import
216
    def __repr__(self):
217
        return '%i,%s,%s' % (self.index, self.low, self.high)
218
219
# Chart Classes
220
# -----------------------------------------------------------------------------
221
7 by gak
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.
222
1 by gak
initial import
223
class Chart(object):
7 by gak
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.
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
    """
1 by gak
initial import
229
230
    BASE_URL = 'http://chart.apis.google.com/chart?'
231
    BACKGROUND = 'bg'
232
    CHART = 'c'
233
    SOLID = 's'
234
    LINEAR_GRADIENT = 'lg'
235
    LINEAR_STRIPES = 'ls'
236
237
    def __init__(self, width, height, title=None, legend=None, colours=None):
238
        assert(type(self) != Chart)  # This is an abstract class
239
        assert(isinstance(width, int))
240
        assert(isinstance(height, int))
241
        self.width = width
242
        self.height = height
243
        self.data = []
244
        self.set_title(title)
245
        self.set_legend(legend)
246
        self.set_colours(colours)
247
        self.fill_types = {
248
            Chart.BACKGROUND: None,
249
            Chart.CHART: None,
250
        }
251
        self.fill_area = {
252
            Chart.BACKGROUND: None,
253
            Chart.CHART: None,
254
        }
255
        self.axis = {
256
            Axis.TOP: None,
257
            Axis.BOTTOM: None,
258
            Axis.LEFT: None,
259
            Axis.RIGHT: None,
260
        }
261
        self.markers = []
262
263
    # URL generation
264
    # -------------------------------------------------------------------------
265
266
    def get_url(self):
267
        url_bits = self.get_url_bits()
268
        return self.BASE_URL + '&'.join(url_bits)
269
270
    def get_url_bits(self):
271
        url_bits = []
272
        # required arguments
273
        url_bits.append(self.type_to_url())
274
        url_bits.append('chs=%ix%i' % (self.width, self.height))
275
        url_bits.append(self.data_to_url())
276
        # optional arguments
277
        if self.title:
278
            url_bits.append('chtt=%s' % self.title)
279
        if self.legend:
280
            url_bits.append('chdl=%s' % '|'.join(self.legend))
281
        if self.colours:
282
            url_bits.append('chco=%s' % ','.join(self.colours))
283
        ret = self.fill_to_url()
284
        if ret:
285
            url_bits.append(ret)
286
        ret = self.axis_to_url()
287
        if ret:
288
            url_bits.append(ret)
289
        if self.markers:
290
            url_bits.append(self.markers_to_url())
291
        return url_bits
292
2 by gak
small changes
293
    # Downloading
294
    # -------------------------------------------------------------------------
7 by gak
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.
295
5 by gak
fixes
296
    def download(self, file_name):
7 by gak
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.
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
2 by gak
small changes
303
        open(file_name, 'wb').write(urllib.urlopen(self.get_url()).read())
304
1 by gak
initial import
305
    # Simple settings
306
    # -------------------------------------------------------------------------
307
308
    def set_title(self, title):
309
        if title:
310
            self.title = urllib.quote(title)
311
        else:
312
            self.title = None
313
314
    def set_legend(self, legend):
315
        # legend needs to be a list, tuple or None
316
        assert(isinstance(legend, list) or isinstance(legend, tuple) or
317
            legend is None)
318
        if legend:
7 by gak
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.
319
            self.legend = [urllib.quote(a) for a in legend]
1 by gak
initial import
320
        else:
321
            self.legend = None
322
323
    # Chart colours
324
    # -------------------------------------------------------------------------
325
326
    def set_colours(self, colours):
327
        # colours needs to be a list, tuple or None
328
        assert(isinstance(colours, list) or isinstance(colours, tuple) or
329
            colours is None)
330
        # make sure the colours are in the right format
331
        if colours:
332
            for col in colours:
333
                _check_colour(col)
334
        self.colours = colours
335
336
    # Background/Chart colours
337
    # -------------------------------------------------------------------------
338
339
    def fill_solid(self, area, colour):
340
        assert(area in (Chart.BACKGROUND, Chart.CHART))
341
        _check_colour(colour)
342
        self.fill_area[area] = colour
343
        self.fill_types[area] = Chart.SOLID
344
345
    def _check_fill_linear(self, angle, *args):
346
        assert(isinstance(args, list) or isinstance(args, tuple))
347
        assert(angle >= 0 and angle <= 90)
348
        assert(len(args) % 2 == 0)
349
        args = list(args)  # args is probably a tuple and we need to mutate
350
        for a in xrange(len(args) / 2):
351
            col = args[a * 2]
352
            offset = args[a * 2 + 1]
353
            _check_colour(col)
354
            assert(offset >= 0 and offset <= 1)
355
            args[a * 2 + 1] = str(args[a * 2 + 1])
356
        return args
357
358
    def fill_linear_gradient(self, area, angle, *args):
359
        assert(area in (Chart.BACKGROUND, Chart.CHART))
360
        args = self._check_fill_linear(angle, *args)
361
        self.fill_types[area] = Chart.LINEAR_GRADIENT
362
        self.fill_area[area] = ','.join([str(angle)] + args)
363
364
    def fill_linear_stripes(self, area, angle, *args):
365
        assert(area in (Chart.BACKGROUND, Chart.CHART))
366
        args = self._check_fill_linear(angle, *args)
367
        self.fill_types[area] = Chart.LINEAR_STRIPES
368
        self.fill_area[area] = ','.join([str(angle)] + args)
369
370
    def fill_to_url(self):
371
        areas = []
372
        for area in (Chart.BACKGROUND, Chart.CHART):
373
            if self.fill_types[area]:
374
                areas.append('%s,%s,%s' % (area, self.fill_types[area], \
375
                    self.fill_area[area]))
376
        if areas:
377
            return 'chf=' + '|'.join(areas)
378
379
    # Data
380
    # -------------------------------------------------------------------------
381
382
    def data_class_detection(self, data):
383
        """
384
        Detects and returns the data type required based on the range of the
385
        data given. The data given must be lists of numbers within a list.
386
        """
387
        assert(isinstance(data, list) or isinstance(data, tuple))
388
        max_value = None
389
        for a in data:
390
            assert(isinstance(a, list) or isinstance(a, tuple))
391
            if max_value is None or max(a) > max_value:
392
                max_value = max(a)
393
        for data_class in (SimpleData, TextData, ExtendedData):
394
            if max_value <= data_class.max_value():
395
                return data_class
396
        raise DataOutOfRangeException()
397
398
    def add_data(self, data):
399
        self.data.append(data)
7 by gak
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.
400
        return len(self.data) - 1  # return the "index" of the data set
1 by gak
initial import
401
402
    def data_to_url(self, data_class=None):
403
        if not data_class:
404
            data_class = self.data_class_detection(self.data)
405
        if not issubclass(data_class, Data):
406
            raise UnknownDataTypeException()
407
        return repr(data_class(self.data))
408
409
    # Axis Labels
410
    # -------------------------------------------------------------------------
411
412
    def set_axis_labels(self, axis, values):
413
        assert(axis in Axis.TYPES)
414
        self.axis[axis] = LabelAxis(axis, values)
415
416
    def set_axis_range(self, axis, low, high):
417
        assert(axis in Axis.TYPES)
418
        self.axis[axis] = RangeAxis(axis, low, high)
419
420
    def set_axis_positions(self, axis, positions):
421
        assert(axis in Axis.TYPES)
422
        if not self.axis[axis]:
423
            raise InvalidParametersException('Please create an axis first')
424
        self.axis[axis].set_positions(positions)
425
426
    def set_axis_style(self, axis, colour, font_size=None, alignment=None):
427
        assert(axis in Axis.TYPES)
428
        if not self.axis[axis]:
429
            raise InvalidParametersException('Please create an axis first')
430
        self.axis[axis].set_style(colour, font_size, alignment)
431
432
    def axis_to_url(self):
433
        available_axis = []
434
        label_axis = []
435
        range_axis = []
436
        positions = []
437
        styles = []
438
        index = -1
439
        for position, axis in self.axis.items():
440
            if not axis:
441
                continue
442
            index += 1
443
            axis.set_index(index)
444
            available_axis.append(position)
445
            if isinstance(axis, RangeAxis):
446
                range_axis.append(repr(axis))
447
            if isinstance(axis, LabelAxis):
448
                label_axis.append(repr(axis))
449
            if axis.positions:
450
                positions.append(axis.positions_to_url())
451
            if axis.has_style:
452
                styles.append(axis.style_to_url())
453
        if not available_axis:
454
            return
455
        url_bits = []
456
        url_bits.append('chxt=%s' % ','.join(available_axis))
457
        if label_axis:
458
            url_bits.append('chxl=%s' % '|'.join(label_axis))
459
        if range_axis:
460
            url_bits.append('chxr=%s' % '|'.join(range_axis))
461
        if positions:
462
            url_bits.append('chxp=%s' % '|'.join(positions))
463
        if styles:
464
            url_bits.append('chxs=%s' % '|'.join(styles))
465
        return '&'.join(url_bits)
466
467
    # Markers, Ranges and Fill area (chm)
468
    # -------------------------------------------------------------------------
469
470
    def markers_to_url(self):
7 by gak
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.
471
        return 'chm=%s' % '|'.join([','.join(a) for a in self.markers])
1 by gak
initial import
472
473
    def add_marker(self, index, point, marker_type, colour, size):
474
        self.markers.append((marker_type, colour, str(index), str(point), \
475
            str(size)))
476
477
    def add_horizontal_range(self, colour, start, stop):
478
        self.markers.append(('r', colour, '1', str(start), str(stop)))
479
480
    def add_vertical_range(self, colour, start, stop):
481
        self.markers.append(('R', colour, '1', str(start), str(stop)))
482
483
    def add_fill_range(self, colour, index_start, index_end):
484
        self.markers.append(('b', colour, str(index_start), str(index_end), \
485
            '1'))
486
487
    def add_fill_simple(self, colour):
488
        self.markers.append(('B', colour, '1', '1', '1'))
489
7 by gak
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.
490
1 by gak
initial import
491
class ScatterChart(Chart):
7 by gak
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.
492
1 by gak
initial import
493
    def __init__(self, *args, **kwargs):
494
        Chart.__init__(self, *args, **kwargs)
7 by gak
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.
495
1 by gak
initial import
496
    def type_to_url(self):
497
        return 'cht=s'
498
499
500
class LineChart(Chart):
7 by gak
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.
501
1 by gak
initial import
502
    def __init__(self, *args, **kwargs):
7 by gak
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.
503
        assert(type(self) != LineChart)  # This is an abstract class
1 by gak
initial import
504
        Chart.__init__(self, *args, **kwargs)
505
        self.line_styles = {}
506
        self.grid = None
7 by gak
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.
507
1 by gak
initial import
508
    def set_line_style(self, index, thickness=1, line_segment=None, \
509
            blank_segment=None):
510
        value = []
511
        value.append(str(thickness))
512
        if line_segment:
513
            value.append(str(line_segment))
514
            value.append(str(blank_segment))
515
        self.line_styles[index] = value
7 by gak
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.
516
1 by gak
initial import
517
    def set_grid(self, x_step, y_step, line_segment=1, \
518
            blank_segment=0):
519
        self.grid = '%s,%s,%s,%s' % (x_step, y_step, line_segment, \
520
            blank_segment)
7 by gak
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.
521
1 by gak
initial import
522
    def get_url_bits(self):
523
        url_bits = Chart.get_url_bits(self)
524
        if self.line_styles:
525
            style = []
526
            # for index, values in self.line_style.items():
527
            for index in xrange(max(self.line_styles) + 1):
528
                if index in self.line_styles:
529
                    values = self.line_styles[index]
530
                else:
531
                    values = ('1', )
532
                style.append(','.join(values))
533
            url_bits.append('chls=%s' % '|'.join(style))
534
        if self.grid:
535
            url_bits.append('chg=%s' % self.grid)
536
        return url_bits
537
7 by gak
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.
538
1 by gak
initial import
539
class SimpleLineChart(LineChart):
7 by gak
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.
540
1 by gak
initial import
541
    def type_to_url(self):
542
        return 'cht=lc'
543
7 by gak
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.
544
1 by gak
initial import
545
class XYLineChart(LineChart):
7 by gak
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.
546
1 by gak
initial import
547
    def type_to_url(self):
548
        return 'cht=lxy'
549
7 by gak
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.
550
1 by gak
initial import
551
class BarChart(Chart):
7 by gak
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.
552
1 by gak
initial import
553
    def __init__(self, *args, **kwargs):
554
        assert(type(self) != BarChart)  # This is an abstract class
555
        Chart.__init__(self, *args, **kwargs)
556
        self.bar_width = None
7 by gak
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.
557
1 by gak
initial import
558
    def set_bar_width(self, bar_width):
559
        self.bar_width = bar_width
7 by gak
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.
560
1 by gak
initial import
561
    def get_url_bits(self):
562
        url_bits = Chart.get_url_bits(self)
563
        url_bits.append('chbh=%i' % self.bar_width)
564
        return url_bits
565
7 by gak
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.
566
1 by gak
initial import
567
class StackedHorizontalBarChart(BarChart):
7 by gak
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.
568
1 by gak
initial import
569
    def type_to_url(self):
570
        return 'cht=bhs'
571
7 by gak
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.
572
1 by gak
initial import
573
class StackedVerticalBarChart(BarChart):
7 by gak
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.
574
1 by gak
initial import
575
    def type_to_url(self):
576
        return 'cht=bvs'
577
7 by gak
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.
578
1 by gak
initial import
579
class GroupedBarChart(BarChart):
7 by gak
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.
580
1 by gak
initial import
581
    def __init__(self, *args, **kwargs):
582
        assert(type(self) != GroupedBarChart)  # This is an abstract class
583
        BarChart.__init__(self, *args, **kwargs)
584
        self.bar_spacing = None
7 by gak
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.
585
1 by gak
initial import
586
    def set_bar_spacing(self, spacing):
587
        self.bar_spacing = spacing
7 by gak
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.
588
1 by gak
initial import
589
    def get_url_bits(self):
590
        # Skip 'BarChart.get_url_bits' and call Chart directly so the parent
591
        # doesn't add "chbh" before we do.
592
        url_bits = Chart.get_url_bits(self)
593
        if self.bar_spacing is not None:
594
            if self.bar_width is None:
595
                raise InvalidParametersException('Bar width is required to ' \
596
                    'be set when setting spacing')
597
            url_bits.append('chbh=%i,%i' % (self.bar_width, self.bar_spacing))
598
        else:
599
            url_bits.append('chbh=%i' % self.bar_width)
600
        return url_bits
601
7 by gak
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.
602
1 by gak
initial import
603
class GroupedHorizontalBarChart(GroupedBarChart):
7 by gak
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.
604
1 by gak
initial import
605
    def type_to_url(self):
606
        return 'cht=bhg'
607
7 by gak
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.
608
1 by gak
initial import
609
class GroupedVerticalBarChart(GroupedBarChart):
7 by gak
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.
610
1 by gak
initial import
611
    def type_to_url(self):
612
        return 'cht=bvg'
613
7 by gak
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.
614
1 by gak
initial import
615
class PieChart(Chart):
7 by gak
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.
616
1 by gak
initial import
617
    def __init__(self, *args, **kwargs):
618
        assert(type(self) != PieChart)  # This is an abstract class
619
        Chart.__init__(self, *args, **kwargs)
620
        self.pie_labels = []
7 by gak
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.
621
1 by gak
initial import
622
    def set_pie_labels(self, labels):
623
        self.pie_labels = labels
7 by gak
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.
624
1 by gak
initial import
625
    def get_url_bits(self):
626
        url_bits = Chart.get_url_bits(self)
627
        if self.pie_labels:
628
            url_bits.append('chl=%s' % '|'.join(self.pie_labels))
629
        return url_bits
630
7 by gak
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.
631
1 by gak
initial import
632
class PieChart2D(PieChart):
7 by gak
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.
633
1 by gak
initial import
634
    def type_to_url(self):
635
        return 'cht=p'
636
7 by gak
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.
637
1 by gak
initial import
638
class PieChart3D(PieChart):
7 by gak
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.
639
1 by gak
initial import
640
    def type_to_url(self):
641
        return 'cht=p3'
642
7 by gak
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.
643
1 by gak
initial import
644
class VennChart(Chart):
7 by gak
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.
645
1 by gak
initial import
646
    def type_to_url(self):
647
        return 'cht=v'
648
7 by gak
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.
649
1 by gak
initial import
650
def test():
651
    chart = GroupedVerticalBarChart(320, 200)
652
    chart = PieChart2D(320, 200)
653
    chart = ScatterChart(320, 200)
654
    chart = SimpleLineChart(320, 200)
7 by gak
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.
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)]
1 by gak
initial import
658
#    chart.set_bar_width(50)
659
#    chart.set_bar_spacing(0)
660
    chart.add_data(sine_data)
661
    chart.add_data(random_data)
662
    chart.add_data(random_data2)
663
#    chart.set_line_style(1, thickness=2)
664
#    chart.set_line_style(2, line_segment=10, blank_segment=5)
665
#    chart.set_title('heloooo')
666
#    chart.set_legend(('sine wave', 'random * x'))
667
#    chart.set_colours(('ee2000', 'DDDDAA', 'fF03f2'))
668
#    chart.fill_solid(Chart.BACKGROUND, '123456')
669
#    chart.fill_linear_gradient(Chart.CHART, 20, '004070', 1, '300040', 0,
670
#        'aabbcc00', 0.5)
671
#    chart.fill_linear_stripes(Chart.CHART, 20, '204070', .2, '300040', .2,
672
#        'aabbcc00', 0.2)
673
    chart.set_axis_range(Axis.LEFT, 0, 10)
674
    chart.set_axis_range(Axis.RIGHT, 5, 30)
675
    chart.set_axis_labels(Axis.BOTTOM, [1, 25, 95])
676
    chart.set_axis_positions(Axis.BOTTOM, [1, 25, 95])
677
    chart.set_axis_style(Axis.BOTTOM, 'FFFFFF', 15)
678
679
#    chart.set_pie_labels(('apples', 'oranges', 'bananas'))
680
681
#    chart.set_grid(10, 10)
682
683
#    for a in xrange(0, 100, 10):
684
#        chart.add_marker(1, a, 'a', 'AACA20', 10)
685
686
    chart.add_horizontal_range('00A020', .2, .5)
687
    chart.add_vertical_range('00c030', .2, .4)
688
689
    chart.add_fill_simple('303030A0')
690
7 by gak
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.
691
    chart.download('test.png')
2 by gak
small changes
692
1 by gak
initial import
693
    url = chart.get_url()
694
    print url
695
    if 0:
696
        data = urllib.urlopen(chart.get_url()).read()
697
        open('meh.png', 'wb').write(data)
698
        os.system('start meh.png')
699
7 by gak
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.
700
1 by gak
initial import
701
if __name__ == '__main__':
702
    test()