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