/+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
8 by gak
Refactored axis to allow multiple axis labels
163
    def __init__(self, axis_index, axis_type, **kw):
164
        assert(axis_type in Axis.TYPES)
1 by gak
initial import
165
        self.has_style = False
8 by gak
Refactored axis to allow multiple axis labels
166
        self.axis_index = axis_index
167
        self.axis_type = axis_type
1 by gak
initial import
168
        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.
169
8 by gak
Refactored axis to allow multiple axis labels
170
    def set_index(self, axis_index):
171
        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.
172
1 by gak
initial import
173
    def set_positions(self, positions):
174
        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.
175
1 by gak
initial import
176
    def set_style(self, colour, font_size=None, alignment=None):
177
        _check_colour(colour)
178
        self.colour = colour
179
        self.font_size = font_size
180
        self.alignment = alignment
181
        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.
182
1 by gak
initial import
183
    def style_to_url(self):
184
        bits = []
8 by gak
Refactored axis to allow multiple axis labels
185
        bits.append(str(self.axis_index))
1 by gak
initial import
186
        bits.append(self.colour)
187
        if self.font_size is not None:
188
            bits.append(str(self.font_size))
189
            if self.alignment is not None:
190
                bits.append(str(self.alignment))
191
        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.
192
1 by gak
initial import
193
    def positions_to_url(self):
194
        bits = []
8 by gak
Refactored axis to allow multiple axis labels
195
        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.
196
        bits += [str(a) for a in self.positions]
1 by gak
initial import
197
        return ','.join(bits)
198
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.
199
1 by gak
initial import
200
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.
201
8 by gak
Refactored axis to allow multiple axis labels
202
    def __init__(self, axis_index, axis_type, values, **kwargs):
203
        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.
204
        self.values = [str(a) for a in values]
205
1 by gak
initial import
206
    def __repr__(self):
8 by gak
Refactored axis to allow multiple axis labels
207
        return '%i:|%s' % (self.axis_index, '|'.join(self.values))
1 by gak
initial import
208
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.
209
1 by gak
initial import
210
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.
211
8 by gak
Refactored axis to allow multiple axis labels
212
    def __init__(self, axis_index, axis_type, low, high, **kwargs):
213
        Axis.__init__(self, axis_index, axis_type, **kwargs)
1 by gak
initial import
214
        self.low = low
215
        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.
216
1 by gak
initial import
217
    def __repr__(self):
8 by gak
Refactored axis to allow multiple axis labels
218
        return '%i,%s,%s' % (self.axis_index, self.low, self.high)
1 by gak
initial import
219
220
# Chart Classes
221
# -----------------------------------------------------------------------------
222
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.
223
1 by gak
initial import
224
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.
225
    """Abstract class for all chart types.
226
227
    width are height specify the dimensions of the image. title sets the title
228
    of the chart. legend requires a list that corresponds to datasets.
229
    """
1 by gak
initial import
230
231
    BASE_URL = 'http://chart.apis.google.com/chart?'
232
    BACKGROUND = 'bg'
233
    CHART = 'c'
234
    SOLID = 's'
235
    LINEAR_GRADIENT = 'lg'
236
    LINEAR_STRIPES = 'ls'
237
238
    def __init__(self, width, height, title=None, legend=None, colours=None):
239
        assert(type(self) != Chart)  # This is an abstract class
240
        assert(isinstance(width, int))
241
        assert(isinstance(height, int))
242
        self.width = width
243
        self.height = height
244
        self.data = []
245
        self.set_title(title)
246
        self.set_legend(legend)
247
        self.set_colours(colours)
248
        self.fill_types = {
249
            Chart.BACKGROUND: None,
250
            Chart.CHART: None,
251
        }
252
        self.fill_area = {
253
            Chart.BACKGROUND: None,
254
            Chart.CHART: None,
255
        }
8 by gak
Refactored axis to allow multiple axis labels
256
#        self.axis = {
257
#            Axis.TOP: None,
258
#            Axis.BOTTOM: None,
259
#            Axis.LEFT: None,
260
#            Axis.RIGHT: None,
261
#        }
262
        self.axis = []
1 by gak
initial import
263
        self.markers = []
264
265
    # URL generation
266
    # -------------------------------------------------------------------------
267
268
    def get_url(self):
269
        url_bits = self.get_url_bits()
270
        return self.BASE_URL + '&'.join(url_bits)
271
272
    def get_url_bits(self):
273
        url_bits = []
274
        # required arguments
275
        url_bits.append(self.type_to_url())
276
        url_bits.append('chs=%ix%i' % (self.width, self.height))
277
        url_bits.append(self.data_to_url())
278
        # optional arguments
279
        if self.title:
280
            url_bits.append('chtt=%s' % self.title)
281
        if self.legend:
282
            url_bits.append('chdl=%s' % '|'.join(self.legend))
283
        if self.colours:
284
            url_bits.append('chco=%s' % ','.join(self.colours))
285
        ret = self.fill_to_url()
286
        if ret:
287
            url_bits.append(ret)
288
        ret = self.axis_to_url()
289
        if ret:
290
            url_bits.append(ret)
291
        if self.markers:
292
            url_bits.append(self.markers_to_url())
293
        return url_bits
294
2 by gak
small changes
295
    # Downloading
296
    # -------------------------------------------------------------------------
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
5 by gak
fixes
298
    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.
299
        opener = urllib2.urlopen(self.get_url())
300
301
        if opener.headers['content-type'] != 'image/png':
302
            raise BadContentTypeException('Server responded with a ' \
303
                'content-type of %s' % opener.headers['content-type'])
304
2 by gak
small changes
305
        open(file_name, 'wb').write(urllib.urlopen(self.get_url()).read())
306
1 by gak
initial import
307
    # Simple settings
308
    # -------------------------------------------------------------------------
309
310
    def set_title(self, title):
311
        if title:
312
            self.title = urllib.quote(title)
313
        else:
314
            self.title = None
315
316
    def set_legend(self, legend):
317
        # legend needs to be a list, tuple or None
318
        assert(isinstance(legend, list) or isinstance(legend, tuple) or
319
            legend is None)
320
        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.
321
            self.legend = [urllib.quote(a) for a in legend]
1 by gak
initial import
322
        else:
323
            self.legend = None
324
325
    # Chart colours
326
    # -------------------------------------------------------------------------
327
328
    def set_colours(self, colours):
329
        # colours needs to be a list, tuple or None
330
        assert(isinstance(colours, list) or isinstance(colours, tuple) or
331
            colours is None)
332
        # make sure the colours are in the right format
333
        if colours:
334
            for col in colours:
335
                _check_colour(col)
336
        self.colours = colours
337
338
    # Background/Chart colours
339
    # -------------------------------------------------------------------------
340
341
    def fill_solid(self, area, colour):
342
        assert(area in (Chart.BACKGROUND, Chart.CHART))
343
        _check_colour(colour)
344
        self.fill_area[area] = colour
345
        self.fill_types[area] = Chart.SOLID
346
347
    def _check_fill_linear(self, angle, *args):
348
        assert(isinstance(args, list) or isinstance(args, tuple))
349
        assert(angle >= 0 and angle <= 90)
350
        assert(len(args) % 2 == 0)
351
        args = list(args)  # args is probably a tuple and we need to mutate
352
        for a in xrange(len(args) / 2):
353
            col = args[a * 2]
354
            offset = args[a * 2 + 1]
355
            _check_colour(col)
356
            assert(offset >= 0 and offset <= 1)
357
            args[a * 2 + 1] = str(args[a * 2 + 1])
358
        return args
359
360
    def fill_linear_gradient(self, area, angle, *args):
361
        assert(area in (Chart.BACKGROUND, Chart.CHART))
362
        args = self._check_fill_linear(angle, *args)
363
        self.fill_types[area] = Chart.LINEAR_GRADIENT
364
        self.fill_area[area] = ','.join([str(angle)] + args)
365
366
    def fill_linear_stripes(self, area, angle, *args):
367
        assert(area in (Chart.BACKGROUND, Chart.CHART))
368
        args = self._check_fill_linear(angle, *args)
369
        self.fill_types[area] = Chart.LINEAR_STRIPES
370
        self.fill_area[area] = ','.join([str(angle)] + args)
371
372
    def fill_to_url(self):
373
        areas = []
374
        for area in (Chart.BACKGROUND, Chart.CHART):
375
            if self.fill_types[area]:
376
                areas.append('%s,%s,%s' % (area, self.fill_types[area], \
377
                    self.fill_area[area]))
378
        if areas:
379
            return 'chf=' + '|'.join(areas)
380
381
    # Data
382
    # -------------------------------------------------------------------------
383
384
    def data_class_detection(self, data):
385
        """
386
        Detects and returns the data type required based on the range of the
387
        data given. The data given must be lists of numbers within a list.
388
        """
389
        assert(isinstance(data, list) or isinstance(data, tuple))
390
        max_value = None
391
        for a in data:
392
            assert(isinstance(a, list) or isinstance(a, tuple))
393
            if max_value is None or max(a) > max_value:
394
                max_value = max(a)
395
        for data_class in (SimpleData, TextData, ExtendedData):
396
            if max_value <= data_class.max_value():
397
                return data_class
398
        raise DataOutOfRangeException()
399
400
    def add_data(self, data):
401
        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.
402
        return len(self.data) - 1  # return the "index" of the data set
1 by gak
initial import
403
404
    def data_to_url(self, data_class=None):
405
        if not data_class:
406
            data_class = self.data_class_detection(self.data)
407
        if not issubclass(data_class, Data):
408
            raise UnknownDataTypeException()
409
        return repr(data_class(self.data))
410
411
    # Axis Labels
412
    # -------------------------------------------------------------------------
413
8 by gak
Refactored axis to allow multiple axis labels
414
    def set_axis_labels(self, axis_type, values):
415
        assert(axis_type in Axis.TYPES)
9 by gak
Fixed set_axis_labels to work with spaces
416
        values = [ urllib.quote(a) for a in values ]
8 by gak
Refactored axis to allow multiple axis labels
417
        axis_index = len(self.axis)
418
        axis = LabelAxis(axis_index, axis_type, values)
419
        self.axis.append(axis)
420
        return axis_index
421
422
    def set_axis_range(self, axis_type, low, high):
423
        assert(axis_type in Axis.TYPES)
424
        axis_index = len(self.axis)
425
        axis = RangeAxis(axis_index, axis_type, low, high)
426
        self.axis.append(axis)
427
        return axis_index
428
429
    def set_axis_positions(self, axis_index, positions):
430
        try:
431
            self.axis[axis_index].set_positions(positions)
432
        except IndexError:
433
            raise InvalidParametersException('Axis index %i has not been ' \
434
                'created' % axis)
435
9 by gak
Fixed set_axis_labels to work with spaces
436
    def set_axis_style(self, axis_index, colour, font_size=None, \
437
            alignment=None):
8 by gak
Refactored axis to allow multiple axis labels
438
        try:
439
            self.axis[axis_index].set_style(colour, font_size, alignment)
440
        except IndexError:
441
            raise InvalidParametersException('Axis index %i has not been ' \
442
                'created' % axis)
1 by gak
initial import
443
444
    def axis_to_url(self):
445
        available_axis = []
446
        label_axis = []
447
        range_axis = []
448
        positions = []
449
        styles = []
450
        index = -1
8 by gak
Refactored axis to allow multiple axis labels
451
        for axis in self.axis:
452
            available_axis.append(axis.axis_type)
1 by gak
initial import
453
            if isinstance(axis, RangeAxis):
454
                range_axis.append(repr(axis))
455
            if isinstance(axis, LabelAxis):
456
                label_axis.append(repr(axis))
457
            if axis.positions:
458
                positions.append(axis.positions_to_url())
459
            if axis.has_style:
460
                styles.append(axis.style_to_url())
461
        if not available_axis:
462
            return
463
        url_bits = []
464
        url_bits.append('chxt=%s' % ','.join(available_axis))
465
        if label_axis:
466
            url_bits.append('chxl=%s' % '|'.join(label_axis))
467
        if range_axis:
468
            url_bits.append('chxr=%s' % '|'.join(range_axis))
469
        if positions:
470
            url_bits.append('chxp=%s' % '|'.join(positions))
471
        if styles:
472
            url_bits.append('chxs=%s' % '|'.join(styles))
473
        return '&'.join(url_bits)
474
475
    # Markers, Ranges and Fill area (chm)
476
    # -------------------------------------------------------------------------
477
478
    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.
479
        return 'chm=%s' % '|'.join([','.join(a) for a in self.markers])
1 by gak
initial import
480
481
    def add_marker(self, index, point, marker_type, colour, size):
482
        self.markers.append((marker_type, colour, str(index), str(point), \
483
            str(size)))
484
485
    def add_horizontal_range(self, colour, start, stop):
486
        self.markers.append(('r', colour, '1', str(start), str(stop)))
487
488
    def add_vertical_range(self, colour, start, stop):
489
        self.markers.append(('R', colour, '1', str(start), str(stop)))
490
491
    def add_fill_range(self, colour, index_start, index_end):
492
        self.markers.append(('b', colour, str(index_start), str(index_end), \
493
            '1'))
494
495
    def add_fill_simple(self, colour):
496
        self.markers.append(('B', colour, '1', '1', '1'))
497
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.
498
1 by gak
initial import
499
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.
500
1 by gak
initial import
501
    def __init__(self, *args, **kwargs):
502
        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.
503
1 by gak
initial import
504
    def type_to_url(self):
505
        return 'cht=s'
506
507
508
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.
509
1 by gak
initial import
510
    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.
511
        assert(type(self) != LineChart)  # This is an abstract class
1 by gak
initial import
512
        Chart.__init__(self, *args, **kwargs)
513
        self.line_styles = {}
514
        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.
515
1 by gak
initial import
516
    def set_line_style(self, index, thickness=1, line_segment=None, \
517
            blank_segment=None):
518
        value = []
519
        value.append(str(thickness))
520
        if line_segment:
521
            value.append(str(line_segment))
522
            value.append(str(blank_segment))
523
        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.
524
1 by gak
initial import
525
    def set_grid(self, x_step, y_step, line_segment=1, \
526
            blank_segment=0):
527
        self.grid = '%s,%s,%s,%s' % (x_step, y_step, line_segment, \
528
            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.
529
1 by gak
initial import
530
    def get_url_bits(self):
531
        url_bits = Chart.get_url_bits(self)
532
        if self.line_styles:
533
            style = []
534
            # for index, values in self.line_style.items():
535
            for index in xrange(max(self.line_styles) + 1):
536
                if index in self.line_styles:
537
                    values = self.line_styles[index]
538
                else:
539
                    values = ('1', )
540
                style.append(','.join(values))
541
            url_bits.append('chls=%s' % '|'.join(style))
542
        if self.grid:
543
            url_bits.append('chg=%s' % self.grid)
544
        return url_bits
545
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
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.
548
1 by gak
initial import
549
    def type_to_url(self):
550
        return 'cht=lc'
551
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
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.
554
1 by gak
initial import
555
    def type_to_url(self):
556
        return 'cht=lxy'
557
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.
558
1 by gak
initial import
559
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.
560
1 by gak
initial import
561
    def __init__(self, *args, **kwargs):
562
        assert(type(self) != BarChart)  # This is an abstract class
563
        Chart.__init__(self, *args, **kwargs)
564
        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.
565
1 by gak
initial import
566
    def set_bar_width(self, bar_width):
567
        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.
568
1 by gak
initial import
569
    def get_url_bits(self):
570
        url_bits = Chart.get_url_bits(self)
571
        url_bits.append('chbh=%i' % self.bar_width)
572
        return url_bits
573
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
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.
576
1 by gak
initial import
577
    def type_to_url(self):
578
        return 'cht=bhs'
579
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
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.
582
1 by gak
initial import
583
    def type_to_url(self):
584
        return 'cht=bvs'
585
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.
586
1 by gak
initial import
587
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.
588
1 by gak
initial import
589
    def __init__(self, *args, **kwargs):
590
        assert(type(self) != GroupedBarChart)  # This is an abstract class
591
        BarChart.__init__(self, *args, **kwargs)
592
        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.
593
1 by gak
initial import
594
    def set_bar_spacing(self, spacing):
595
        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.
596
1 by gak
initial import
597
    def get_url_bits(self):
598
        # Skip 'BarChart.get_url_bits' and call Chart directly so the parent
599
        # doesn't add "chbh" before we do.
600
        url_bits = Chart.get_url_bits(self)
601
        if self.bar_spacing is not None:
602
            if self.bar_width is None:
603
                raise InvalidParametersException('Bar width is required to ' \
604
                    'be set when setting spacing')
605
            url_bits.append('chbh=%i,%i' % (self.bar_width, self.bar_spacing))
606
        else:
607
            url_bits.append('chbh=%i' % self.bar_width)
608
        return url_bits
609
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
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.
612
1 by gak
initial import
613
    def type_to_url(self):
614
        return 'cht=bhg'
615
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
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.
618
1 by gak
initial import
619
    def type_to_url(self):
620
        return 'cht=bvg'
621
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.
622
1 by gak
initial import
623
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.
624
1 by gak
initial import
625
    def __init__(self, *args, **kwargs):
626
        assert(type(self) != PieChart)  # This is an abstract class
627
        Chart.__init__(self, *args, **kwargs)
628
        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.
629
1 by gak
initial import
630
    def set_pie_labels(self, labels):
631
        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.
632
1 by gak
initial import
633
    def get_url_bits(self):
634
        url_bits = Chart.get_url_bits(self)
635
        if self.pie_labels:
636
            url_bits.append('chl=%s' % '|'.join(self.pie_labels))
637
        return url_bits
638
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
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.
641
1 by gak
initial import
642
    def type_to_url(self):
643
        return 'cht=p'
644
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
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.
647
1 by gak
initial import
648
    def type_to_url(self):
649
        return 'cht=p3'
650
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.
651
1 by gak
initial import
652
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.
653
1 by gak
initial import
654
    def type_to_url(self):
655
        return 'cht=v'
656
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.
657
1 by gak
initial import
658
def test():
659
    chart = GroupedVerticalBarChart(320, 200)
660
    chart = PieChart2D(320, 200)
661
    chart = ScatterChart(320, 200)
662
    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.
663
    sine_data = [math.sin(float(a) / 10) * 2000 + 2000 for a in xrange(100)]
664
    random_data = [a * random.random() * 30 for a in xrange(40)]
665
    random_data2 = [random.random() * 4000 for a in xrange(10)]
1 by gak
initial import
666
#    chart.set_bar_width(50)
667
#    chart.set_bar_spacing(0)
668
    chart.add_data(sine_data)
669
    chart.add_data(random_data)
670
    chart.add_data(random_data2)
671
#    chart.set_line_style(1, thickness=2)
672
#    chart.set_line_style(2, line_segment=10, blank_segment=5)
673
#    chart.set_title('heloooo')
674
#    chart.set_legend(('sine wave', 'random * x'))
675
#    chart.set_colours(('ee2000', 'DDDDAA', 'fF03f2'))
676
#    chart.fill_solid(Chart.BACKGROUND, '123456')
677
#    chart.fill_linear_gradient(Chart.CHART, 20, '004070', 1, '300040', 0,
678
#        'aabbcc00', 0.5)
679
#    chart.fill_linear_stripes(Chart.CHART, 20, '204070', .2, '300040', .2,
680
#        'aabbcc00', 0.2)
8 by gak
Refactored axis to allow multiple axis labels
681
    axis_left_index = chart.set_axis_range(Axis.LEFT, 0, 10)
682
    axis_left_index = chart.set_axis_range(Axis.LEFT, 0, 10)
683
    axis_left_index = chart.set_axis_range(Axis.LEFT, 0, 10)
684
    axis_right_index = chart.set_axis_range(Axis.RIGHT, 5, 30)
685
    axis_bottom_index = chart.set_axis_labels(Axis.BOTTOM, [1, 25, 95])
686
    chart.set_axis_positions(axis_bottom_index, [1, 25, 95])
687
    chart.set_axis_style(axis_bottom_index, '003050', 15)
1 by gak
initial import
688
689
#    chart.set_pie_labels(('apples', 'oranges', 'bananas'))
690
691
#    chart.set_grid(10, 10)
692
693
#    for a in xrange(0, 100, 10):
694
#        chart.add_marker(1, a, 'a', 'AACA20', 10)
695
696
    chart.add_horizontal_range('00A020', .2, .5)
697
    chart.add_vertical_range('00c030', .2, .4)
698
699
    chart.add_fill_simple('303030A0')
700
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.
701
    chart.download('test.png')
2 by gak
small changes
702
1 by gak
initial import
703
    url = chart.get_url()
704
    print url
705
    if 0:
706
        data = urllib.urlopen(chart.get_url()).read()
707
        open('meh.png', 'wb').write(data)
708
        os.system('start meh.png')
709
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.
710
1 by gak
initial import
711
if __name__ == '__main__':
712
    test()