/+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:
19 by gak
Added automatic scaling patch from Trent Mick
97
                    raise DataOutOfRangeException('cannot encode value: %d'
98
                                                  % value)
1 by gak
initial import
99
            encoded_data.append(''.join(sub_data))
100
        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.
101
1 by gak
initial import
102
    @staticmethod
103
    def max_value():
104
        return 61
105
19 by gak
Added automatic scaling patch from Trent Mick
106
    @classmethod
107
    def scale_value(cls, value, range):
108
        lower, upper = range
109
        max_value = cls.max_value()
110
        scaled = int(round((float(value) - lower) * max_value / upper))
111
        clipped = max(0, min(scaled, max_value))
112
        return clipped
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
113
1 by gak
initial import
114
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.
115
1 by gak
initial import
116
    def __repr__(self):
18 by gak
Trent Mick: fixed max_value check, lowered precision on TextData
117
        max_value = self.max_value()
1 by gak
initial import
118
        encoded_data = []
119
        for data in self.data:
120
            sub_data = []
121
            for value in data:
122
                if value is None:
123
                    sub_data.append(-1)
18 by gak
Trent Mick: fixed max_value check, lowered precision on TextData
124
                elif value >= 0 and value <= max_value:
125
                    sub_data.append("%.1f" % float(value))
1 by gak
initial import
126
                else:
127
                    raise DataOutOfRangeException()
128
            encoded_data.append(','.join(sub_data))
129
        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.
130
1 by gak
initial import
131
    @staticmethod
132
    def max_value():
133
        return 100
134
19 by gak
Added automatic scaling patch from Trent Mick
135
    @classmethod
136
    def scale_value(cls, value, range):
137
        lower, upper = range
138
        max_value = cls.max_value()
139
        scaled = (float(value) - lower) * max_value / upper
140
        clipped = max(0, min(scaled, max_value))
141
        return clipped
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
142
1 by gak
initial import
143
class ExtendedData(Data):
144
    enc_map = \
145
        '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.
146
1 by gak
initial import
147
    def __repr__(self):
18 by gak
Trent Mick: fixed max_value check, lowered precision on TextData
148
        max_value = self.max_value()
1 by gak
initial import
149
        encoded_data = []
150
        enc_size = len(ExtendedData.enc_map)
151
        for data in self.data:
152
            sub_data = []
153
            for value in data:
154
                if value is None:
155
                    sub_data.append('__')
18 by gak
Trent Mick: fixed max_value check, lowered precision on TextData
156
                elif value >= 0 and value <= max_value:
1 by gak
initial import
157
                    first, second = divmod(int(value), enc_size)
158
                    sub_data.append('%s%s' % (
159
                        ExtendedData.enc_map[first],
160
                        ExtendedData.enc_map[second]))
161
                else:
162
                    raise DataOutOfRangeException( \
163
                        'Item #%i "%s" is out of range' % (data.index(value), \
164
                        value))
165
            encoded_data.append(''.join(sub_data))
166
        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.
167
1 by gak
initial import
168
    @staticmethod
169
    def max_value():
170
        return 4095
171
19 by gak
Added automatic scaling patch from Trent Mick
172
    @classmethod
173
    def scale_value(cls, value, range):
174
        lower, upper = range
175
        max_value = cls.max_value()
176
        scaled = int(round((float(value) - lower) * max_value / upper))
177
        clipped = max(0, min(scaled, max_value))
178
        return clipped
179
180
1 by gak
initial import
181
# Axis Classes
182
# -----------------------------------------------------------------------------
183
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
184
1 by gak
initial import
185
class Axis(object):
186
    BOTTOM = 'x'
187
    TOP = 't'
188
    LEFT = 'y'
189
    RIGHT = 'r'
190
    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.
191
8 by gak
Refactored axis to allow multiple axis labels
192
    def __init__(self, axis_index, axis_type, **kw):
193
        assert(axis_type in Axis.TYPES)
1 by gak
initial import
194
        self.has_style = False
8 by gak
Refactored axis to allow multiple axis labels
195
        self.axis_index = axis_index
196
        self.axis_type = axis_type
1 by gak
initial import
197
        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.
198
8 by gak
Refactored axis to allow multiple axis labels
199
    def set_index(self, axis_index):
200
        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.
201
1 by gak
initial import
202
    def set_positions(self, positions):
203
        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.
204
1 by gak
initial import
205
    def set_style(self, colour, font_size=None, alignment=None):
206
        _check_colour(colour)
207
        self.colour = colour
208
        self.font_size = font_size
209
        self.alignment = alignment
210
        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.
211
1 by gak
initial import
212
    def style_to_url(self):
213
        bits = []
8 by gak
Refactored axis to allow multiple axis labels
214
        bits.append(str(self.axis_index))
1 by gak
initial import
215
        bits.append(self.colour)
216
        if self.font_size is not None:
217
            bits.append(str(self.font_size))
218
            if self.alignment is not None:
219
                bits.append(str(self.alignment))
220
        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.
221
1 by gak
initial import
222
    def positions_to_url(self):
223
        bits = []
8 by gak
Refactored axis to allow multiple axis labels
224
        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.
225
        bits += [str(a) for a in self.positions]
1 by gak
initial import
226
        return ','.join(bits)
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 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.
230
8 by gak
Refactored axis to allow multiple axis labels
231
    def __init__(self, axis_index, axis_type, values, **kwargs):
232
        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.
233
        self.values = [str(a) for a in values]
234
1 by gak
initial import
235
    def __repr__(self):
8 by gak
Refactored axis to allow multiple axis labels
236
        return '%i:|%s' % (self.axis_index, '|'.join(self.values))
1 by gak
initial import
237
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
238
1 by gak
initial import
239
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.
240
8 by gak
Refactored axis to allow multiple axis labels
241
    def __init__(self, axis_index, axis_type, low, high, **kwargs):
242
        Axis.__init__(self, axis_index, axis_type, **kwargs)
1 by gak
initial import
243
        self.low = low
244
        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.
245
1 by gak
initial import
246
    def __repr__(self):
8 by gak
Refactored axis to allow multiple axis labels
247
        return '%i,%s,%s' % (self.axis_index, self.low, self.high)
1 by gak
initial import
248
249
# Chart Classes
250
# -----------------------------------------------------------------------------
251
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
252
1 by gak
initial import
253
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.
254
    """Abstract class for all chart types.
255
256
    width are height specify the dimensions of the image. title sets the title
257
    of the chart. legend requires a list that corresponds to datasets.
258
    """
1 by gak
initial import
259
260
    BASE_URL = 'http://chart.apis.google.com/chart?'
261
    BACKGROUND = 'bg'
262
    CHART = 'c'
263
    SOLID = 's'
264
    LINEAR_GRADIENT = 'lg'
265
    LINEAR_STRIPES = 'ls'
266
19 by gak
Added automatic scaling patch from Trent Mick
267
    def __init__(self, width, height, title=None, legend=None, colours=None,
268
                 auto_scale=True, x_range=None, y_range=None):
1 by gak
initial import
269
        assert(type(self) != Chart)  # This is an abstract class
270
        assert(isinstance(width, int))
271
        assert(isinstance(height, int))
272
        self.width = width
273
        self.height = height
274
        self.data = []
275
        self.set_title(title)
276
        self.set_legend(legend)
277
        self.set_colours(colours)
19 by gak
Added automatic scaling patch from Trent Mick
278
279
        # Data for scaling.
280
        self.auto_scale = auto_scale    # Whether to automatically scale data
281
        self.x_range = x_range          # (min, max) x-axis range for scaling
282
        self.y_range = y_range          # (min, max) y-axis range for scaling
283
        self.scaled_data_class = None
284
        self.scaled_x_range = None
285
        self.scaled_y_range = None
286
1 by gak
initial import
287
        self.fill_types = {
288
            Chart.BACKGROUND: None,
289
            Chart.CHART: None,
290
        }
291
        self.fill_area = {
292
            Chart.BACKGROUND: None,
293
            Chart.CHART: None,
294
        }
8 by gak
Refactored axis to allow multiple axis labels
295
#        self.axis = {
296
#            Axis.TOP: None,
297
#            Axis.BOTTOM: None,
298
#            Axis.LEFT: None,
299
#            Axis.RIGHT: None,
300
#        }
301
        self.axis = []
1 by gak
initial import
302
        self.markers = []
303
304
    # URL generation
305
    # -------------------------------------------------------------------------
306
307
    def get_url(self):
308
        url_bits = self.get_url_bits()
309
        return self.BASE_URL + '&'.join(url_bits)
310
311
    def get_url_bits(self):
312
        url_bits = []
313
        # required arguments
314
        url_bits.append(self.type_to_url())
315
        url_bits.append('chs=%ix%i' % (self.width, self.height))
316
        url_bits.append(self.data_to_url())
317
        # optional arguments
318
        if self.title:
319
            url_bits.append('chtt=%s' % self.title)
320
        if self.legend:
321
            url_bits.append('chdl=%s' % '|'.join(self.legend))
322
        if self.colours:
323
            url_bits.append('chco=%s' % ','.join(self.colours))
324
        ret = self.fill_to_url()
325
        if ret:
326
            url_bits.append(ret)
327
        ret = self.axis_to_url()
328
        if ret:
329
            url_bits.append(ret)
330
        if self.markers:
331
            url_bits.append(self.markers_to_url())
332
        return url_bits
333
2 by gak
small changes
334
    # Downloading
335
    # -------------------------------------------------------------------------
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
336
5 by gak
fixes
337
    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.
338
        opener = urllib2.urlopen(self.get_url())
339
340
        if opener.headers['content-type'] != 'image/png':
341
            raise BadContentTypeException('Server responded with a ' \
342
                'content-type of %s' % opener.headers['content-type'])
343
2 by gak
small changes
344
        open(file_name, 'wb').write(urllib.urlopen(self.get_url()).read())
345
1 by gak
initial import
346
    # Simple settings
347
    # -------------------------------------------------------------------------
348
349
    def set_title(self, title):
350
        if title:
351
            self.title = urllib.quote(title)
352
        else:
353
            self.title = None
354
355
    def set_legend(self, legend):
356
        # legend needs to be a list, tuple or None
357
        assert(isinstance(legend, list) or isinstance(legend, tuple) or
358
            legend is None)
359
        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.
360
            self.legend = [urllib.quote(a) for a in legend]
1 by gak
initial import
361
        else:
362
            self.legend = None
363
364
    # Chart colours
365
    # -------------------------------------------------------------------------
366
367
    def set_colours(self, colours):
368
        # colours needs to be a list, tuple or None
369
        assert(isinstance(colours, list) or isinstance(colours, tuple) or
370
            colours is None)
371
        # make sure the colours are in the right format
372
        if colours:
373
            for col in colours:
374
                _check_colour(col)
375
        self.colours = colours
376
377
    # Background/Chart colours
378
    # -------------------------------------------------------------------------
379
380
    def fill_solid(self, area, colour):
381
        assert(area in (Chart.BACKGROUND, Chart.CHART))
382
        _check_colour(colour)
383
        self.fill_area[area] = colour
384
        self.fill_types[area] = Chart.SOLID
385
386
    def _check_fill_linear(self, angle, *args):
387
        assert(isinstance(args, list) or isinstance(args, tuple))
388
        assert(angle >= 0 and angle <= 90)
389
        assert(len(args) % 2 == 0)
390
        args = list(args)  # args is probably a tuple and we need to mutate
391
        for a in xrange(len(args) / 2):
392
            col = args[a * 2]
393
            offset = args[a * 2 + 1]
394
            _check_colour(col)
395
            assert(offset >= 0 and offset <= 1)
396
            args[a * 2 + 1] = str(args[a * 2 + 1])
397
        return args
398
399
    def fill_linear_gradient(self, area, angle, *args):
400
        assert(area in (Chart.BACKGROUND, Chart.CHART))
401
        args = self._check_fill_linear(angle, *args)
402
        self.fill_types[area] = Chart.LINEAR_GRADIENT
403
        self.fill_area[area] = ','.join([str(angle)] + args)
404
405
    def fill_linear_stripes(self, area, angle, *args):
406
        assert(area in (Chart.BACKGROUND, Chart.CHART))
407
        args = self._check_fill_linear(angle, *args)
408
        self.fill_types[area] = Chart.LINEAR_STRIPES
409
        self.fill_area[area] = ','.join([str(angle)] + args)
410
411
    def fill_to_url(self):
412
        areas = []
413
        for area in (Chart.BACKGROUND, Chart.CHART):
414
            if self.fill_types[area]:
415
                areas.append('%s,%s,%s' % (area, self.fill_types[area], \
416
                    self.fill_area[area]))
417
        if areas:
418
            return 'chf=' + '|'.join(areas)
419
420
    # Data
421
    # -------------------------------------------------------------------------
422
423
    def data_class_detection(self, data):
19 by gak
Added automatic scaling patch from Trent Mick
424
        """Determines the appropriate data encoding type to give satisfactory
425
        resolution (http://code.google.com/apis/chart/#chart_data).
1 by gak
initial import
426
        """
427
        assert(isinstance(data, list) or isinstance(data, tuple))
19 by gak
Added automatic scaling patch from Trent Mick
428
        if not isinstance(self, (LineChart, BarChart, ScatterChart)):
429
            # From the link above:
430
            #   Simple encoding is suitable for all other types of chart
431
            #   regardless of size.
432
            return SimpleData
433
        elif self.height < 100:
434
            # The link above indicates that line and bar charts less
435
            # than 300px in size can be suitably represented with the
436
            # simple encoding. I've found that this isn't sufficient,
437
            # e.g. examples/line-xy-circle.png. Let's try 100px.
438
            return SimpleData
439
        elif self.height < 500:
440
            return TextData
441
        else:
442
            return ExtendedData
443
444
    def data_x_range(self):
445
        """Return a 2-tuple giving the minimum and maximum x-axis 
446
        data range.
447
        """
448
        try:
449
            lower = min([min(s) for type, s in self.annotated_data()
450
                         if type == 'x'])
451
            upper = max([max(s) for type, s in self.annotated_data()
452
                         if type == 'x'])
453
            return (lower, upper)
454
        except ValueError:
455
            return None     # no x-axis datasets
456
457
    def data_y_range(self):
458
        """Return a 2-tuple giving the minimum and maximum y-axis 
459
        data range.
460
        """
461
        try:
462
            lower = min([min(s) for type, s in self.annotated_data()
463
                         if type == 'y'])
464
            upper = max([max(s) for type, s in self.annotated_data()
465
                         if type == 'y'])
466
            return (lower, upper)
467
        except ValueError:
468
            return None     # no y-axis datasets
469
470
    def scaled_data(self, data_class, x_range=None, y_range=None):
471
        """Scale `self.data` as appropriate for the given data encoding
472
        (data_class) and return it.
473
474
        An optional `y_range` -- a 2-tuple (lower, upper) -- can be
475
        given to specify the y-axis bounds. If not given, the range is
476
        inferred from the data: (0, <max-value>) presuming no negative
477
        values, or (<min-value>, <max-value>) if there are negative
478
        values.  `self.scaled_y_range` is set to the actual lower and
479
        upper scaling range.
480
481
        Ditto for `x_range`. Note that some chart types don't have x-axis
482
        data.
483
        """
484
        self.scaled_data_class = data_class
485
486
        # Determine the x-axis range for scaling.
487
        if x_range is None:
488
            x_range = self.data_x_range()
489
            if x_range and x_range[0] > 0:
490
                x_range = (0, x_range[1])
491
        self.scaled_x_range = x_range
492
493
        # Determine the y-axis range for scaling.
494
        if y_range is None:
495
            y_range = self.data_y_range()
496
            if y_range and y_range[0] > 0:
497
                y_range = (0, y_range[1])
498
        self.scaled_y_range = y_range
499
500
        scaled_data = []
501
        for type, dataset in self.annotated_data():
502
            if type == 'x':
503
                scale_range = x_range
504
            elif type == 'y':
505
                scale_range = y_range
506
            elif type == 'marker-size':
507
                scale_range = (0, max(dataset))
508
            scaled_data.append([data_class.scale_value(v, scale_range)
509
                                for v in dataset])
510
        return scaled_data
1 by gak
initial import
511
512
    def add_data(self, data):
513
        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.
514
        return len(self.data) - 1  # return the "index" of the data set
1 by gak
initial import
515
516
    def data_to_url(self, data_class=None):
517
        if not data_class:
518
            data_class = self.data_class_detection(self.data)
519
        if not issubclass(data_class, Data):
520
            raise UnknownDataTypeException()
19 by gak
Added automatic scaling patch from Trent Mick
521
        if self.auto_scale:
522
            data = self.scaled_data(data_class, self.x_range, self.y_range)
523
        else:
524
            data = self.data
525
        return repr(data_class(data))
1 by gak
initial import
526
527
    # Axis Labels
528
    # -------------------------------------------------------------------------
529
8 by gak
Refactored axis to allow multiple axis labels
530
    def set_axis_labels(self, axis_type, values):
531
        assert(axis_type in Axis.TYPES)
9 by gak
Fixed set_axis_labels to work with spaces
532
        values = [ urllib.quote(a) for a in values ]
8 by gak
Refactored axis to allow multiple axis labels
533
        axis_index = len(self.axis)
534
        axis = LabelAxis(axis_index, axis_type, values)
535
        self.axis.append(axis)
536
        return axis_index
537
538
    def set_axis_range(self, axis_type, low, high):
539
        assert(axis_type in Axis.TYPES)
540
        axis_index = len(self.axis)
541
        axis = RangeAxis(axis_index, axis_type, low, high)
542
        self.axis.append(axis)
543
        return axis_index
544
545
    def set_axis_positions(self, axis_index, positions):
546
        try:
547
            self.axis[axis_index].set_positions(positions)
548
        except IndexError:
549
            raise InvalidParametersException('Axis index %i has not been ' \
550
                'created' % axis)
551
9 by gak
Fixed set_axis_labels to work with spaces
552
    def set_axis_style(self, axis_index, colour, font_size=None, \
553
            alignment=None):
8 by gak
Refactored axis to allow multiple axis labels
554
        try:
555
            self.axis[axis_index].set_style(colour, font_size, alignment)
556
        except IndexError:
557
            raise InvalidParametersException('Axis index %i has not been ' \
558
                'created' % axis)
1 by gak
initial import
559
560
    def axis_to_url(self):
561
        available_axis = []
562
        label_axis = []
563
        range_axis = []
564
        positions = []
565
        styles = []
566
        index = -1
8 by gak
Refactored axis to allow multiple axis labels
567
        for axis in self.axis:
568
            available_axis.append(axis.axis_type)
1 by gak
initial import
569
            if isinstance(axis, RangeAxis):
570
                range_axis.append(repr(axis))
571
            if isinstance(axis, LabelAxis):
572
                label_axis.append(repr(axis))
573
            if axis.positions:
574
                positions.append(axis.positions_to_url())
575
            if axis.has_style:
576
                styles.append(axis.style_to_url())
577
        if not available_axis:
578
            return
579
        url_bits = []
580
        url_bits.append('chxt=%s' % ','.join(available_axis))
581
        if label_axis:
582
            url_bits.append('chxl=%s' % '|'.join(label_axis))
583
        if range_axis:
584
            url_bits.append('chxr=%s' % '|'.join(range_axis))
585
        if positions:
586
            url_bits.append('chxp=%s' % '|'.join(positions))
587
        if styles:
588
            url_bits.append('chxs=%s' % '|'.join(styles))
589
        return '&'.join(url_bits)
590
591
    # Markers, Ranges and Fill area (chm)
592
    # -------------------------------------------------------------------------
593
594
    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.
595
        return 'chm=%s' % '|'.join([','.join(a) for a in self.markers])
1 by gak
initial import
596
597
    def add_marker(self, index, point, marker_type, colour, size):
598
        self.markers.append((marker_type, colour, str(index), str(point), \
599
            str(size)))
600
601
    def add_horizontal_range(self, colour, start, stop):
602
        self.markers.append(('r', colour, '1', str(start), str(stop)))
603
604
    def add_vertical_range(self, colour, start, stop):
605
        self.markers.append(('R', colour, '1', str(start), str(stop)))
606
607
    def add_fill_range(self, colour, index_start, index_end):
608
        self.markers.append(('b', colour, str(index_start), str(index_end), \
609
            '1'))
610
611
    def add_fill_simple(self, colour):
612
        self.markers.append(('B', colour, '1', '1', '1'))
613
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
614
1 by gak
initial import
615
class 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.
616
1 by gak
initial import
617
    def type_to_url(self):
618
        return 'cht=s'
619
19 by gak
Added automatic scaling patch from Trent Mick
620
    def annotated_data(self):
621
        yield ('x', self.data[0])
622
        yield ('y', self.data[1])
623
        if len(self.data) > 2:
624
            # The optional third dataset is relative sizing for point
625
            # markers.
626
            yield ('marker-size', self.data[2])
1 by gak
initial import
627
628
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.
629
1 by gak
initial import
630
    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.
631
        assert(type(self) != LineChart)  # This is an abstract class
1 by gak
initial import
632
        Chart.__init__(self, *args, **kwargs)
633
        self.line_styles = {}
634
        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.
635
1 by gak
initial import
636
    def set_line_style(self, index, thickness=1, line_segment=None, \
637
            blank_segment=None):
638
        value = []
639
        value.append(str(thickness))
640
        if line_segment:
641
            value.append(str(line_segment))
642
            value.append(str(blank_segment))
643
        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.
644
1 by gak
initial import
645
    def set_grid(self, x_step, y_step, line_segment=1, \
646
            blank_segment=0):
647
        self.grid = '%s,%s,%s,%s' % (x_step, y_step, line_segment, \
648
            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.
649
1 by gak
initial import
650
    def get_url_bits(self):
651
        url_bits = Chart.get_url_bits(self)
652
        if self.line_styles:
653
            style = []
654
            # for index, values in self.line_style.items():
655
            for index in xrange(max(self.line_styles) + 1):
656
                if index in self.line_styles:
657
                    values = self.line_styles[index]
658
                else:
659
                    values = ('1', )
660
                style.append(','.join(values))
661
            url_bits.append('chls=%s' % '|'.join(style))
662
        if self.grid:
663
            url_bits.append('chg=%s' % self.grid)
664
        return url_bits
665
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
666
1 by gak
initial import
667
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.
668
1 by gak
initial import
669
    def type_to_url(self):
670
        return 'cht=lc'
671
19 by gak
Added automatic scaling patch from Trent Mick
672
    def annotated_data(self):
673
        # All datasets are y-axis data.
674
        for dataset in self.data:
675
            yield ('y', dataset)
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
676
1 by gak
initial import
677
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.
678
1 by gak
initial import
679
    def type_to_url(self):
680
        return 'cht=lxy'
681
19 by gak
Added automatic scaling patch from Trent Mick
682
    def annotated_data(self):
683
        # Datasets alternate between x-axis, y-axis.
684
        for i, dataset in enumerate(self.data):
685
            if i % 2 == 0:
686
                yield ('x', dataset)
687
            else:
688
                yield ('y', dataset)
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
689
1 by gak
initial import
690
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.
691
1 by gak
initial import
692
    def __init__(self, *args, **kwargs):
693
        assert(type(self) != BarChart)  # This is an abstract class
694
        Chart.__init__(self, *args, **kwargs)
695
        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.
696
1 by gak
initial import
697
    def set_bar_width(self, bar_width):
698
        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.
699
1 by gak
initial import
700
    def get_url_bits(self):
701
        url_bits = Chart.get_url_bits(self)
19 by gak
Added automatic scaling patch from Trent Mick
702
        if self.bar_width is not None:
703
            url_bits.append('chbh=%i' % self.bar_width)
1 by gak
initial import
704
        return url_bits
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
1 by gak
initial import
707
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.
708
1 by gak
initial import
709
    def type_to_url(self):
710
        return 'cht=bhs'
711
19 by gak
Added automatic scaling patch from Trent Mick
712
    def annotated_data(self):
713
        for dataset in self.data:
714
            yield ('x', dataset)
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks 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
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.
717
1 by gak
initial import
718
    def type_to_url(self):
719
        return 'cht=bvs'
720
19 by gak
Added automatic scaling patch from Trent Mick
721
    def annotated_data(self):
722
        for dataset in self.data:
723
            yield ('y', dataset)
724
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
725
1 by gak
initial import
726
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.
727
1 by gak
initial import
728
    def __init__(self, *args, **kwargs):
729
        assert(type(self) != GroupedBarChart)  # This is an abstract class
730
        BarChart.__init__(self, *args, **kwargs)
731
        self.bar_spacing = None
19 by gak
Added automatic scaling patch from Trent Mick
732
        self.group_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.
733
1 by gak
initial import
734
    def set_bar_spacing(self, spacing):
19 by gak
Added automatic scaling patch from Trent Mick
735
        """Set spacing between bars in a group."""
1 by gak
initial import
736
        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.
737
19 by gak
Added automatic scaling patch from Trent Mick
738
    def set_group_spacing(self, spacing):
739
        """Set spacing between groups of bars."""
740
        self.group_spacing = spacing
741
1 by gak
initial import
742
    def get_url_bits(self):
743
        # Skip 'BarChart.get_url_bits' and call Chart directly so the parent
744
        # doesn't add "chbh" before we do.
745
        url_bits = Chart.get_url_bits(self)
19 by gak
Added automatic scaling patch from Trent Mick
746
        if self.group_spacing is not None:
747
            if self.bar_spacing is None:
748
                raise InvalidParametersException('Bar spacing is required to ' \
749
                    'be set when setting group spacing')
750
            if self.bar_width is None:
751
                raise InvalidParametersException('Bar width is required to ' \
752
                    'be set when setting bar spacing')
753
            url_bits.append('chbh=%i,%i,%i'
754
                % (self.bar_width, self.bar_spacing, self.group_spacing))
755
        elif self.bar_spacing is not None:
756
            if self.bar_width is None:
757
                raise InvalidParametersException('Bar width is required to ' \
758
                    'be set when setting bar spacing')
1 by gak
initial import
759
            url_bits.append('chbh=%i,%i' % (self.bar_width, self.bar_spacing))
760
        else:
761
            url_bits.append('chbh=%i' % self.bar_width)
762
        return url_bits
763
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
764
1 by gak
initial import
765
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.
766
1 by gak
initial import
767
    def type_to_url(self):
768
        return 'cht=bhg'
769
19 by gak
Added automatic scaling patch from Trent Mick
770
    def annotated_data(self):
771
        for dataset in self.data:
772
            yield ('x', dataset)
773
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
774
1 by gak
initial import
775
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.
776
1 by gak
initial import
777
    def type_to_url(self):
778
        return 'cht=bvg'
779
19 by gak
Added automatic scaling patch from Trent Mick
780
    def annotated_data(self):
781
        for dataset in self.data:
782
            yield ('y', dataset)
783
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
784
1 by gak
initial import
785
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.
786
1 by gak
initial import
787
    def __init__(self, *args, **kwargs):
788
        assert(type(self) != PieChart)  # This is an abstract class
789
        Chart.__init__(self, *args, **kwargs)
790
        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.
791
1 by gak
initial import
792
    def set_pie_labels(self, labels):
16 by gak
Fixed pie labels and not encoded characters
793
        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.
794
1 by gak
initial import
795
    def get_url_bits(self):
796
        url_bits = Chart.get_url_bits(self)
797
        if self.pie_labels:
798
            url_bits.append('chl=%s' % '|'.join(self.pie_labels))
799
        return url_bits
800
19 by gak
Added automatic scaling patch from Trent Mick
801
    def annotated_data(self):
802
        # Datasets are all y-axis data. However, there should only be
803
        # one dataset for pie charts.
804
        for dataset in self.data:
805
            yield ('y', dataset)
806
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
807
1 by gak
initial import
808
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.
809
1 by gak
initial import
810
    def type_to_url(self):
811
        return 'cht=p'
812
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
813
1 by gak
initial import
814
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.
815
1 by gak
initial import
816
    def type_to_url(self):
817
        return 'cht=p3'
818
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
819
1 by gak
initial import
820
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.
821
1 by gak
initial import
822
    def type_to_url(self):
823
        return 'cht=v'
824
19 by gak
Added automatic scaling patch from Trent Mick
825
    def annotated_data(self):
826
        for dataset in self.data:
827
            yield ('y', dataset)
828
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
829
1 by gak
initial import
830
def test():
831
    chart = GroupedVerticalBarChart(320, 200)
832
    chart = PieChart2D(320, 200)
833
    chart = ScatterChart(320, 200)
834
    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.
835
    sine_data = [math.sin(float(a) / 10) * 2000 + 2000 for a in xrange(100)]
836
    random_data = [a * random.random() * 30 for a in xrange(40)]
837
    random_data2 = [random.random() * 4000 for a in xrange(10)]
1 by gak
initial import
838
#    chart.set_bar_width(50)
839
#    chart.set_bar_spacing(0)
840
    chart.add_data(sine_data)
841
    chart.add_data(random_data)
842
    chart.add_data(random_data2)
843
#    chart.set_line_style(1, thickness=2)
844
#    chart.set_line_style(2, line_segment=10, blank_segment=5)
845
#    chart.set_title('heloooo')
846
#    chart.set_legend(('sine wave', 'random * x'))
847
#    chart.set_colours(('ee2000', 'DDDDAA', 'fF03f2'))
848
#    chart.fill_solid(Chart.BACKGROUND, '123456')
849
#    chart.fill_linear_gradient(Chart.CHART, 20, '004070', 1, '300040', 0,
850
#        'aabbcc00', 0.5)
851
#    chart.fill_linear_stripes(Chart.CHART, 20, '204070', .2, '300040', .2,
852
#        'aabbcc00', 0.2)
8 by gak
Refactored axis to allow multiple axis labels
853
    axis_left_index = chart.set_axis_range(Axis.LEFT, 0, 10)
854
    axis_left_index = chart.set_axis_range(Axis.LEFT, 0, 10)
855
    axis_left_index = chart.set_axis_range(Axis.LEFT, 0, 10)
856
    axis_right_index = chart.set_axis_range(Axis.RIGHT, 5, 30)
857
    axis_bottom_index = chart.set_axis_labels(Axis.BOTTOM, [1, 25, 95])
858
    chart.set_axis_positions(axis_bottom_index, [1, 25, 95])
859
    chart.set_axis_style(axis_bottom_index, '003050', 15)
1 by gak
initial import
860
861
#    chart.set_pie_labels(('apples', 'oranges', 'bananas'))
862
863
#    chart.set_grid(10, 10)
864
865
#    for a in xrange(0, 100, 10):
866
#        chart.add_marker(1, a, 'a', 'AACA20', 10)
867
868
    chart.add_horizontal_range('00A020', .2, .5)
869
    chart.add_vertical_range('00c030', .2, .4)
870
871
    chart.add_fill_simple('303030A0')
872
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
873
    chart.download('test.png')
2 by gak
small changes
874
1 by gak
initial import
875
    url = chart.get_url()
876
    print url
877
    if 0:
878
        data = urllib.urlopen(chart.get_url()).read()
879
        open('meh.png', 'wb').write(data)
880
        os.system('start meh.png')
881
7 by gak
version bump, added BadContentTypeException, added a few examples, added COPYING licence, code is more PEP8 friendly, download() checks for content type and raises on bad http codes, add_data returns the index of the dataset, Line doesn't allow being an instance.
882
1 by gak
initial import
883
if __name__ == '__main__':
884
    test()