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