/+junk/pygooglechart-py3k

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/%2Bjunk/pygooglechart-py3k
22 by gak
- pygooglechart.py converted to unix line breaks
1
"""
35 by gak
- Initial "grammar" code
2
pygooglechart - A complete Python wrapper for the Google Chart API
22 by gak
- pygooglechart.py converted to unix line breaks
3
4
http://pygooglechart.slowchop.com/
5
58 by gak
- Version bump to 0.3.0
6
Copyright 2007-2009 Gerald Kaszuba
22 by gak
- pygooglechart.py converted to unix line breaks
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
"""
52 by gak
- Few fixes for python 3.0
22
from __future__ import division
22 by gak
- pygooglechart.py converted to unix line breaks
23
24
import os
25
import urllib
26
import urllib2
27
import math
28
import random
29
import re
36 by gak
- Really added initial unit tests
30
import warnings
31
import copy
22 by gak
- pygooglechart.py converted to unix line breaks
32
33
# Helper variables and functions
34
# -----------------------------------------------------------------------------
35
58 by gak
- Version bump to 0.3.0
36
__version__ = '0.3.0'
35 by gak
- Initial "grammar" code
37
__author__ = 'Gerald Kaszuba'
22 by gak
- pygooglechart.py converted to unix line breaks
38
39
reo_colour = re.compile('^([A-Fa-f0-9]{2,2}){3,4}$')
40
41
def _check_colour(colour):
42
    if not reo_colour.match(colour):
43
        raise InvalidParametersException('Colours need to be in ' \
44
            'RRGGBB or RRGGBBAA format. One of your colours has %s' % \
45
            colour)
46
36 by gak
- Really added initial unit tests
47
48
def _reset_warnings():
49
    """Helper function to reset all warnings. Used by the unit tests."""
50
    globals()['__warningregistry__'] = None
51
52
22 by gak
- pygooglechart.py converted to unix line breaks
53
# Exception Classes
54
# -----------------------------------------------------------------------------
55
56
57
class PyGoogleChartException(Exception):
58
    pass
59
60
61
class DataOutOfRangeException(PyGoogleChartException):
62
    pass
63
64
65
class UnknownDataTypeException(PyGoogleChartException):
66
    pass
67
68
69
class NoDataGivenException(PyGoogleChartException):
70
    pass
71
72
73
class InvalidParametersException(PyGoogleChartException):
74
    pass
75
76
77
class BadContentTypeException(PyGoogleChartException):
78
    pass
79
80
35 by gak
- Initial "grammar" code
81
class AbstractClassException(PyGoogleChartException):
82
    pass
83
84
85
class UnknownChartType(PyGoogleChartException):
86
    pass
87
57 by gak
- #23 MapChart extensions (thanks to Andreas Schawo)
88
class UnknownCountryCodeException(PyGoogleChartException):
89
    pass
35 by gak
- Initial "grammar" code
90
22 by gak
- pygooglechart.py converted to unix line breaks
91
# Data Classes
92
# -----------------------------------------------------------------------------
93
94
95
class Data(object):
96
97
    def __init__(self, data):
35 by gak
- Initial "grammar" code
98
        if type(self) == Data:
99
            raise AbstractClassException('This is an abstract class')
22 by gak
- pygooglechart.py converted to unix line breaks
100
        self.data = data
101
25 by gak
Autoscale fixes and refactoring by Grahm Ullrich
102
    @classmethod
103
    def float_scale_value(cls, value, range):
104
        lower, upper = range
34 by gak
- initial unit tests
105
        assert(upper > lower)
52 by gak
- Few fixes for python 3.0
106
        scaled = (value - lower) * (cls.max_value / (upper - lower))
25 by gak
Autoscale fixes and refactoring by Grahm Ullrich
107
        return scaled
108
109
    @classmethod
110
    def clip_value(cls, value):
36 by gak
- Really added initial unit tests
111
        return max(0, min(value, cls.max_value))
25 by gak
Autoscale fixes and refactoring by Grahm Ullrich
112
113
    @classmethod
114
    def int_scale_value(cls, value, range):
34 by gak
- initial unit tests
115
        return int(round(cls.float_scale_value(value, range)))
25 by gak
Autoscale fixes and refactoring by Grahm Ullrich
116
117
    @classmethod
118
    def scale_value(cls, value, range):
119
        scaled = cls.int_scale_value(value, range)
120
        clipped = cls.clip_value(scaled)
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
121
        Data.check_clip(scaled, clipped)
122
        return clipped
123
124
    @staticmethod
125
    def check_clip(scaled, clipped):
36 by gak
- Really added initial unit tests
126
        if clipped != scaled:
127
            warnings.warn('One or more of of your data points has been '
128
                'clipped because it is out of range.')
22 by gak
- pygooglechart.py converted to unix line breaks
129
33 by gak
pep8 fixes, version bump
130
22 by gak
- pygooglechart.py converted to unix line breaks
131
class SimpleData(Data):
33 by gak
pep8 fixes, version bump
132
36 by gak
- Really added initial unit tests
133
    max_value = 61
22 by gak
- pygooglechart.py converted to unix line breaks
134
    enc_map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
135
136
    def __repr__(self):
137
        encoded_data = []
138
        for data in self.data:
139
            sub_data = []
140
            for value in data:
141
                if value is None:
142
                    sub_data.append('_')
36 by gak
- Really added initial unit tests
143
                elif value >= 0 and value <= self.max_value:
22 by gak
- pygooglechart.py converted to unix line breaks
144
                    sub_data.append(SimpleData.enc_map[value])
145
                else:
146
                    raise DataOutOfRangeException('cannot encode value: %d'
147
                                                  % value)
148
            encoded_data.append(''.join(sub_data))
149
        return 'chd=s:' + ','.join(encoded_data)
150
33 by gak
pep8 fixes, version bump
151
22 by gak
- pygooglechart.py converted to unix line breaks
152
class TextData(Data):
153
36 by gak
- Really added initial unit tests
154
    max_value = 100
155
22 by gak
- pygooglechart.py converted to unix line breaks
156
    def __repr__(self):
157
        encoded_data = []
158
        for data in self.data:
159
            sub_data = []
160
            for value in data:
161
                if value is None:
162
                    sub_data.append(-1)
36 by gak
- Really added initial unit tests
163
                elif value >= 0 and value <= self.max_value:
22 by gak
- pygooglechart.py converted to unix line breaks
164
                    sub_data.append("%.1f" % float(value))
165
                else:
166
                    raise DataOutOfRangeException()
167
            encoded_data.append(','.join(sub_data))
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
168
        return 'chd=t:' + '%7c'.join(encoded_data)
22 by gak
- pygooglechart.py converted to unix line breaks
169
170
    @classmethod
171
    def scale_value(cls, value, range):
25 by gak
Autoscale fixes and refactoring by Grahm Ullrich
172
        # use float values instead of integers because we don't need an encode
173
        # map index
33 by gak
pep8 fixes, version bump
174
        scaled = cls.float_scale_value(value, range)
25 by gak
Autoscale fixes and refactoring by Grahm Ullrich
175
        clipped = cls.clip_value(scaled)
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
176
        Data.check_clip(scaled, clipped)
25 by gak
Autoscale fixes and refactoring by Grahm Ullrich
177
        return clipped
22 by gak
- pygooglechart.py converted to unix line breaks
178
33 by gak
pep8 fixes, version bump
179
22 by gak
- pygooglechart.py converted to unix line breaks
180
class ExtendedData(Data):
34 by gak
- initial unit tests
181
36 by gak
- Really added initial unit tests
182
    max_value = 4095
22 by gak
- pygooglechart.py converted to unix line breaks
183
    enc_map = \
184
        'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.'
185
186
    def __repr__(self):
187
        encoded_data = []
188
        enc_size = len(ExtendedData.enc_map)
189
        for data in self.data:
190
            sub_data = []
191
            for value in data:
192
                if value is None:
193
                    sub_data.append('__')
36 by gak
- Really added initial unit tests
194
                elif value >= 0 and value <= self.max_value:
22 by gak
- pygooglechart.py converted to unix line breaks
195
                    first, second = divmod(int(value), enc_size)
196
                    sub_data.append('%s%s' % (
197
                        ExtendedData.enc_map[first],
198
                        ExtendedData.enc_map[second]))
199
                else:
200
                    raise DataOutOfRangeException( \
201
                        'Item #%i "%s" is out of range' % (data.index(value), \
202
                        value))
203
            encoded_data.append(''.join(sub_data))
204
        return 'chd=e:' + ','.join(encoded_data)
205
206
207
# Axis Classes
208
# -----------------------------------------------------------------------------
209
210
211
class Axis(object):
34 by gak
- initial unit tests
212
22 by gak
- pygooglechart.py converted to unix line breaks
213
    BOTTOM = 'x'
214
    TOP = 't'
215
    LEFT = 'y'
216
    RIGHT = 'r'
217
    TYPES = (BOTTOM, TOP, LEFT, RIGHT)
218
219
    def __init__(self, axis_index, axis_type, **kw):
220
        assert(axis_type in Axis.TYPES)
221
        self.has_style = False
222
        self.axis_index = axis_index
223
        self.axis_type = axis_type
224
        self.positions = None
225
226
    def set_index(self, axis_index):
227
        self.axis_index = axis_index
228
229
    def set_positions(self, positions):
230
        self.positions = positions
231
232
    def set_style(self, colour, font_size=None, alignment=None):
233
        _check_colour(colour)
234
        self.colour = colour
235
        self.font_size = font_size
236
        self.alignment = alignment
237
        self.has_style = True
238
239
    def style_to_url(self):
240
        bits = []
241
        bits.append(str(self.axis_index))
242
        bits.append(self.colour)
243
        if self.font_size is not None:
244
            bits.append(str(self.font_size))
245
            if self.alignment is not None:
246
                bits.append(str(self.alignment))
247
        return ','.join(bits)
248
249
    def positions_to_url(self):
250
        bits = []
251
        bits.append(str(self.axis_index))
252
        bits += [str(a) for a in self.positions]
253
        return ','.join(bits)
254
255
256
class LabelAxis(Axis):
257
258
    def __init__(self, axis_index, axis_type, values, **kwargs):
259
        Axis.__init__(self, axis_index, axis_type, **kwargs)
260
        self.values = [str(a) for a in values]
261
262
    def __repr__(self):
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
263
        return '%i:%%7c%s' % (self.axis_index, '%7c'.join(self.values))
22 by gak
- pygooglechart.py converted to unix line breaks
264
265
266
class RangeAxis(Axis):
267
268
    def __init__(self, axis_index, axis_type, low, high, **kwargs):
269
        Axis.__init__(self, axis_index, axis_type, **kwargs)
270
        self.low = low
271
        self.high = high
272
273
    def __repr__(self):
274
        return '%i,%s,%s' % (self.axis_index, self.low, self.high)
275
276
# Chart Classes
277
# -----------------------------------------------------------------------------
278
279
280
class Chart(object):
281
    """Abstract class for all chart types.
282
283
    width are height specify the dimensions of the image. title sets the title
284
    of the chart. legend requires a list that corresponds to datasets.
285
    """
286
287
    BASE_URL = 'http://chart.apis.google.com/chart?'
288
    BACKGROUND = 'bg'
289
    CHART = 'c'
30 by gak
Added zero line to bar charts
290
    ALPHA = 'a'
291
    VALID_SOLID_FILL_TYPES = (BACKGROUND, CHART, ALPHA)
22 by gak
- pygooglechart.py converted to unix line breaks
292
    SOLID = 's'
293
    LINEAR_GRADIENT = 'lg'
294
    LINEAR_STRIPES = 'ls'
295
296
    def __init__(self, width, height, title=None, legend=None, colours=None,
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
297
            auto_scale=True, x_range=None, y_range=None,
298
            colours_within_series=None):
35 by gak
- Initial "grammar" code
299
        if type(self) == Chart:
300
            raise AbstractClassException('This is an abstract class')
22 by gak
- pygooglechart.py converted to unix line breaks
301
        assert(isinstance(width, int))
302
        assert(isinstance(height, int))
303
        self.width = width
304
        self.height = height
305
        self.data = []
306
        self.set_title(title)
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
307
        self.set_title_style(None, None)
22 by gak
- pygooglechart.py converted to unix line breaks
308
        self.set_legend(legend)
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
309
        self.set_legend_position(None)
22 by gak
- pygooglechart.py converted to unix line breaks
310
        self.set_colours(colours)
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
311
        self.set_colours_within_series(colours_within_series)
22 by gak
- pygooglechart.py converted to unix line breaks
312
313
        # Data for scaling.
36 by gak
- Really added initial unit tests
314
        self.auto_scale = auto_scale  # Whether to automatically scale data
315
        self.x_range = x_range  # (min, max) x-axis range for scaling
316
        self.y_range = y_range  # (min, max) y-axis range for scaling
22 by gak
- pygooglechart.py converted to unix line breaks
317
        self.scaled_data_class = None
318
        self.scaled_x_range = None
319
        self.scaled_y_range = None
320
321
        self.fill_types = {
322
            Chart.BACKGROUND: None,
323
            Chart.CHART: None,
30 by gak
Added zero line to bar charts
324
            Chart.ALPHA: None,
22 by gak
- pygooglechart.py converted to unix line breaks
325
        }
326
        self.fill_area = {
327
            Chart.BACKGROUND: None,
328
            Chart.CHART: None,
30 by gak
Added zero line to bar charts
329
            Chart.ALPHA: None,
22 by gak
- pygooglechart.py converted to unix line breaks
330
        }
331
        self.axis = []
332
        self.markers = []
27 by gak
grids and line styles are not only restricted to line chart types
333
        self.line_styles = {}
334
        self.grid = None
22 by gak
- pygooglechart.py converted to unix line breaks
335
336
    # URL generation
337
    # -------------------------------------------------------------------------
338
25 by gak
Autoscale fixes and refactoring by Grahm Ullrich
339
    def get_url(self, data_class=None):
340
        url_bits = self.get_url_bits(data_class=data_class)
22 by gak
- pygooglechart.py converted to unix line breaks
341
        return self.BASE_URL + '&'.join(url_bits)
342
25 by gak
Autoscale fixes and refactoring by Grahm Ullrich
343
    def get_url_bits(self, data_class=None):
22 by gak
- pygooglechart.py converted to unix line breaks
344
        url_bits = []
345
        # required arguments
346
        url_bits.append(self.type_to_url())
347
        url_bits.append('chs=%ix%i' % (self.width, self.height))
25 by gak
Autoscale fixes and refactoring by Grahm Ullrich
348
        url_bits.append(self.data_to_url(data_class=data_class))
22 by gak
- pygooglechart.py converted to unix line breaks
349
        # optional arguments
350
        if self.title:
351
            url_bits.append('chtt=%s' % self.title)
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
352
        if self.title_colour and self.title_font_size:
353
            url_bits.append('chts=%s,%s' % (self.title_colour, \
354
                self.title_font_size))
22 by gak
- pygooglechart.py converted to unix line breaks
355
        if self.legend:
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
356
            url_bits.append('chdl=%s' % '%7c'.join(self.legend))
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
357
        if self.legend_position:
358
            url_bits.append('chdlp=%s' % (self.legend_position))
22 by gak
- pygooglechart.py converted to unix line breaks
359
        if self.colours:
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
360
            url_bits.append('chco=%s' % ','.join(self.colours))            
361
        if self.colours_within_series:
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
362
            url_bits.append('chco=%s' % '%7c'.join(self.colours_within_series))
22 by gak
- pygooglechart.py converted to unix line breaks
363
        ret = self.fill_to_url()
364
        if ret:
365
            url_bits.append(ret)
366
        ret = self.axis_to_url()
367
        if ret:
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
368
            url_bits.append(ret)                    
22 by gak
- pygooglechart.py converted to unix line breaks
369
        if self.markers:
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
370
            url_bits.append(self.markers_to_url())        
27 by gak
grids and line styles are not only restricted to line chart types
371
        if self.line_styles:
372
            style = []
373
            for index in xrange(max(self.line_styles) + 1):
374
                if index in self.line_styles:
375
                    values = self.line_styles[index]
376
                else:
377
                    values = ('1', )
378
                style.append(','.join(values))
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
379
            url_bits.append('chls=%s' % '%7c'.join(style))
27 by gak
grids and line styles are not only restricted to line chart types
380
        if self.grid:
381
            url_bits.append('chg=%s' % self.grid)
22 by gak
- pygooglechart.py converted to unix line breaks
382
        return url_bits
383
384
    # Downloading
385
    # -------------------------------------------------------------------------
386
387
    def download(self, file_name):
388
        opener = urllib2.urlopen(self.get_url())
389
390
        if opener.headers['content-type'] != 'image/png':
391
            raise BadContentTypeException('Server responded with a ' \
392
                'content-type of %s' % opener.headers['content-type'])
393
41 by gak
Fixed bug where the module would download twice (#7) (Evan Lezar)
394
        open(file_name, 'wb').write(opener.read())
22 by gak
- pygooglechart.py converted to unix line breaks
395
396
    # Simple settings
397
    # -------------------------------------------------------------------------
398
399
    def set_title(self, title):
400
        if title:
401
            self.title = urllib.quote(title)
402
        else:
403
            self.title = None
404
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
405
    def set_title_style(self, colour, font_size):
406
        if not colour is None:
407
            _check_colour(colour)
408
        self.title_colour = colour
409
        self.title_font_size = font_size
410
22 by gak
- pygooglechart.py converted to unix line breaks
411
    def set_legend(self, legend):
412
        """legend needs to be a list, tuple or None"""
413
        assert(isinstance(legend, list) or isinstance(legend, tuple) or
414
            legend is None)
415
        if legend:
416
            self.legend = [urllib.quote(a) for a in legend]
417
        else:
418
            self.legend = None
419
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
420
    def set_legend_position(self, legend_position):
421
        if legend_position:
422
            self.legend_position = urllib.quote(legend_position)
423
        else:    
424
            self.legend_position = None
425
22 by gak
- pygooglechart.py converted to unix line breaks
426
    # Chart colours
427
    # -------------------------------------------------------------------------
428
429
    def set_colours(self, colours):
430
        # colours needs to be a list, tuple or None
431
        assert(isinstance(colours, list) or isinstance(colours, tuple) or
432
            colours is None)
433
        # make sure the colours are in the right format
434
        if colours:
435
            for col in colours:
436
                _check_colour(col)
437
        self.colours = colours
438
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
439
    def set_colours_within_series(self, colours):
440
        # colours needs to be a list, tuple or None
441
        assert(isinstance(colours, list) or isinstance(colours, tuple) or
442
            colours is None)
443
        # make sure the colours are in the right format
444
        if colours:
445
            for col in colours:
446
                _check_colour(col)
447
        self.colours_within_series = colours        
448
22 by gak
- pygooglechart.py converted to unix line breaks
449
    # Background/Chart colours
450
    # -------------------------------------------------------------------------
451
452
    def fill_solid(self, area, colour):
30 by gak
Added zero line to bar charts
453
        assert(area in Chart.VALID_SOLID_FILL_TYPES)
22 by gak
- pygooglechart.py converted to unix line breaks
454
        _check_colour(colour)
455
        self.fill_area[area] = colour
456
        self.fill_types[area] = Chart.SOLID
457
458
    def _check_fill_linear(self, angle, *args):
459
        assert(isinstance(args, list) or isinstance(args, tuple))
460
        assert(angle >= 0 and angle <= 90)
461
        assert(len(args) % 2 == 0)
462
        args = list(args)  # args is probably a tuple and we need to mutate
53 by gak
Fixed a deprecation warning
463
        for a in xrange(int(len(args) / 2)):
22 by gak
- pygooglechart.py converted to unix line breaks
464
            col = args[a * 2]
465
            offset = args[a * 2 + 1]
466
            _check_colour(col)
467
            assert(offset >= 0 and offset <= 1)
468
            args[a * 2 + 1] = str(args[a * 2 + 1])
469
        return args
470
471
    def fill_linear_gradient(self, area, angle, *args):
30 by gak
Added zero line to bar charts
472
        assert(area in Chart.VALID_SOLID_FILL_TYPES)
22 by gak
- pygooglechart.py converted to unix line breaks
473
        args = self._check_fill_linear(angle, *args)
474
        self.fill_types[area] = Chart.LINEAR_GRADIENT
475
        self.fill_area[area] = ','.join([str(angle)] + args)
476
477
    def fill_linear_stripes(self, area, angle, *args):
30 by gak
Added zero line to bar charts
478
        assert(area in Chart.VALID_SOLID_FILL_TYPES)
22 by gak
- pygooglechart.py converted to unix line breaks
479
        args = self._check_fill_linear(angle, *args)
480
        self.fill_types[area] = Chart.LINEAR_STRIPES
481
        self.fill_area[area] = ','.join([str(angle)] + args)
482
483
    def fill_to_url(self):
484
        areas = []
30 by gak
Added zero line to bar charts
485
        for area in (Chart.BACKGROUND, Chart.CHART, Chart.ALPHA):
22 by gak
- pygooglechart.py converted to unix line breaks
486
            if self.fill_types[area]:
487
                areas.append('%s,%s,%s' % (area, self.fill_types[area], \
488
                    self.fill_area[area]))
489
        if areas:
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
490
            return 'chf=' + '%7c'.join(areas)
22 by gak
- pygooglechart.py converted to unix line breaks
491
492
    # Data
493
    # -------------------------------------------------------------------------
494
495
    def data_class_detection(self, data):
496
        """Determines the appropriate data encoding type to give satisfactory
497
        resolution (http://code.google.com/apis/chart/#chart_data).
498
        """
499
        assert(isinstance(data, list) or isinstance(data, tuple))
500
        if not isinstance(self, (LineChart, BarChart, ScatterChart)):
501
            # From the link above:
502
            #   Simple encoding is suitable for all other types of chart
503
            #   regardless of size.
504
            return SimpleData
505
        elif self.height < 100:
506
            # The link above indicates that line and bar charts less
507
            # than 300px in size can be suitably represented with the
508
            # simple encoding. I've found that this isn't sufficient,
509
            # e.g. examples/line-xy-circle.png. Let's try 100px.
510
            return SimpleData
511
        else:
512
            return ExtendedData
513
39 by gak
- Bug fixed when automatic scaling is on and None values are in a data set (#5) (Alec Thomas)
514
    def _filter_none(self, data):
515
        return [r for r in data if r is not None]
516
22 by gak
- pygooglechart.py converted to unix line breaks
517
    def data_x_range(self):
518
        """Return a 2-tuple giving the minimum and maximum x-axis
519
        data range.
520
        """
521
        try:
39 by gak
- Bug fixed when automatic scaling is on and None values are in a data set (#5) (Alec Thomas)
522
            lower = min([min(self._filter_none(s))
523
                         for type, s in self.annotated_data()
22 by gak
- pygooglechart.py converted to unix line breaks
524
                         if type == 'x'])
39 by gak
- Bug fixed when automatic scaling is on and None values are in a data set (#5) (Alec Thomas)
525
            upper = max([max(self._filter_none(s))
526
                         for type, s in self.annotated_data()
22 by gak
- pygooglechart.py converted to unix line breaks
527
                         if type == 'x'])
528
            return (lower, upper)
529
        except ValueError:
530
            return None     # no x-axis datasets
531
532
    def data_y_range(self):
533
        """Return a 2-tuple giving the minimum and maximum y-axis
534
        data range.
535
        """
536
        try:
39 by gak
- Bug fixed when automatic scaling is on and None values are in a data set (#5) (Alec Thomas)
537
            lower = min([min(self._filter_none(s))
538
                         for type, s in self.annotated_data()
22 by gak
- pygooglechart.py converted to unix line breaks
539
                         if type == 'y'])
39 by gak
- Bug fixed when automatic scaling is on and None values are in a data set (#5) (Alec Thomas)
540
            upper = max([max(self._filter_none(s)) + 1
541
                         for type, s in self.annotated_data()
22 by gak
- pygooglechart.py converted to unix line breaks
542
                         if type == 'y'])
543
            return (lower, upper)
544
        except ValueError:
545
            return None     # no y-axis datasets
546
547
    def scaled_data(self, data_class, x_range=None, y_range=None):
548
        """Scale `self.data` as appropriate for the given data encoding
549
        (data_class) and return it.
550
551
        An optional `y_range` -- a 2-tuple (lower, upper) -- can be
552
        given to specify the y-axis bounds. If not given, the range is
553
        inferred from the data: (0, <max-value>) presuming no negative
554
        values, or (<min-value>, <max-value>) if there are negative
555
        values.  `self.scaled_y_range` is set to the actual lower and
556
        upper scaling range.
557
558
        Ditto for `x_range`. Note that some chart types don't have x-axis
559
        data.
560
        """
561
        self.scaled_data_class = data_class
562
563
        # Determine the x-axis range for scaling.
564
        if x_range is None:
565
            x_range = self.data_x_range()
566
            if x_range and x_range[0] > 0:
40 by gak
- Fixed a bug with auto-scaling, where the minimum was always 0. (#6) (Rohit Jenveja)
567
                x_range = (x_range[0], x_range[1])
22 by gak
- pygooglechart.py converted to unix line breaks
568
        self.scaled_x_range = x_range
569
570
        # Determine the y-axis range for scaling.
571
        if y_range is None:
572
            y_range = self.data_y_range()
573
            if y_range and y_range[0] > 0:
40 by gak
- Fixed a bug with auto-scaling, where the minimum was always 0. (#6) (Rohit Jenveja)
574
                y_range = (y_range[0], y_range[1])
22 by gak
- pygooglechart.py converted to unix line breaks
575
        self.scaled_y_range = y_range
576
577
        scaled_data = []
578
        for type, dataset in self.annotated_data():
579
            if type == 'x':
580
                scale_range = x_range
581
            elif type == 'y':
582
                scale_range = y_range
583
            elif type == 'marker-size':
584
                scale_range = (0, max(dataset))
39 by gak
- Bug fixed when automatic scaling is on and None values are in a data set (#5) (Alec Thomas)
585
            scaled_dataset = []
586
            for v in dataset:
587
                if v is None:
588
                    scaled_dataset.append(None)
589
                else:
590
                    scaled_dataset.append(
591
                        data_class.scale_value(v, scale_range))
592
            scaled_data.append(scaled_dataset)
22 by gak
- pygooglechart.py converted to unix line breaks
593
        return scaled_data
594
595
    def add_data(self, data):
596
        self.data.append(data)
597
        return len(self.data) - 1  # return the "index" of the data set
598
599
    def data_to_url(self, data_class=None):
600
        if not data_class:
601
            data_class = self.data_class_detection(self.data)
602
        if not issubclass(data_class, Data):
603
            raise UnknownDataTypeException()
604
        if self.auto_scale:
605
            data = self.scaled_data(data_class, self.x_range, self.y_range)
606
        else:
607
            data = self.data
608
        return repr(data_class(data))
609
29 by gak
Added Google-o-meter chart
610
    def annotated_data(self):
611
        for dataset in self.data:
612
            yield ('x', dataset)
613
22 by gak
- pygooglechart.py converted to unix line breaks
614
    # Axis Labels
615
    # -------------------------------------------------------------------------
616
617
    def set_axis_labels(self, axis_type, values):
618
        assert(axis_type in Axis.TYPES)
47 by gak
- Fixed bug with automatic scaling and pie charts
619
        values = [urllib.quote(str(a)) for a in values]
22 by gak
- pygooglechart.py converted to unix line breaks
620
        axis_index = len(self.axis)
621
        axis = LabelAxis(axis_index, axis_type, values)
622
        self.axis.append(axis)
623
        return axis_index
624
625
    def set_axis_range(self, axis_type, low, high):
626
        assert(axis_type in Axis.TYPES)
627
        axis_index = len(self.axis)
628
        axis = RangeAxis(axis_index, axis_type, low, high)
629
        self.axis.append(axis)
630
        return axis_index
631
632
    def set_axis_positions(self, axis_index, positions):
633
        try:
634
            self.axis[axis_index].set_positions(positions)
635
        except IndexError:
636
            raise InvalidParametersException('Axis index %i has not been ' \
637
                'created' % axis)
638
639
    def set_axis_style(self, axis_index, colour, font_size=None, \
640
            alignment=None):
641
        try:
642
            self.axis[axis_index].set_style(colour, font_size, alignment)
643
        except IndexError:
644
            raise InvalidParametersException('Axis index %i has not been ' \
645
                'created' % axis)
646
647
    def axis_to_url(self):
648
        available_axis = []
649
        label_axis = []
650
        range_axis = []
651
        positions = []
652
        styles = []
653
        index = -1
654
        for axis in self.axis:
655
            available_axis.append(axis.axis_type)
656
            if isinstance(axis, RangeAxis):
657
                range_axis.append(repr(axis))
658
            if isinstance(axis, LabelAxis):
659
                label_axis.append(repr(axis))
660
            if axis.positions:
661
                positions.append(axis.positions_to_url())
662
            if axis.has_style:
663
                styles.append(axis.style_to_url())
664
        if not available_axis:
665
            return
666
        url_bits = []
667
        url_bits.append('chxt=%s' % ','.join(available_axis))
668
        if label_axis:
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
669
            url_bits.append('chxl=%s' % '%7c'.join(label_axis))
22 by gak
- pygooglechart.py converted to unix line breaks
670
        if range_axis:
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
671
            url_bits.append('chxr=%s' % '%7c'.join(range_axis))
22 by gak
- pygooglechart.py converted to unix line breaks
672
        if positions:
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
673
            url_bits.append('chxp=%s' % '%7c'.join(positions))
22 by gak
- pygooglechart.py converted to unix line breaks
674
        if styles:
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
675
            url_bits.append('chxs=%s' % '%7c'.join(styles))
22 by gak
- pygooglechart.py converted to unix line breaks
676
        return '&'.join(url_bits)
677
678
    # Markers, Ranges and Fill area (chm)
679
    # -------------------------------------------------------------------------
680
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
681
    def markers_to_url(self):        
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
682
        return 'chm=%s' % '%7c'.join([','.join(a) for a in self.markers])
22 by gak
- pygooglechart.py converted to unix line breaks
683
31 by gak
Added priority to shape markers
684
    def add_marker(self, index, point, marker_type, colour, size, priority=0):
22 by gak
- pygooglechart.py converted to unix line breaks
685
        self.markers.append((marker_type, colour, str(index), str(point), \
31 by gak
Added priority to shape markers
686
            str(size), str(priority)))
22 by gak
- pygooglechart.py converted to unix line breaks
687
688
    def add_horizontal_range(self, colour, start, stop):
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
689
        self.markers.append(('r', colour, '0', str(start), str(stop)))
690
691
    def add_data_line(self, colour, data_set, size, priority=0):
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
692
        self.markers.append(('D', colour, str(data_set), '0', str(size), \
693
            str(priority)))
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
694
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
695
    def add_marker_text(self, string, colour, data_set, data_point, size, \
696
            priority=0):
697
        self.markers.append((str(string), colour, str(data_set), \
698
            str(data_point), str(size), str(priority)))        
22 by gak
- pygooglechart.py converted to unix line breaks
699
700
    def add_vertical_range(self, colour, start, stop):
37 by gak
- Added "colours within series" option to chart. (chco=xxx|xxx) (Steve Brandt)
701
        self.markers.append(('R', colour, '0', str(start), str(stop)))
22 by gak
- pygooglechart.py converted to unix line breaks
702
703
    def add_fill_range(self, colour, index_start, index_end):
704
        self.markers.append(('b', colour, str(index_start), str(index_end), \
705
            '1'))
706
707
    def add_fill_simple(self, colour):
708
        self.markers.append(('B', colour, '1', '1', '1'))
709
27 by gak
grids and line styles are not only restricted to line chart types
710
    # Line styles
711
    # -------------------------------------------------------------------------
22 by gak
- pygooglechart.py converted to unix line breaks
712
713
    def set_line_style(self, index, thickness=1, line_segment=None, \
714
            blank_segment=None):
715
        value = []
716
        value.append(str(thickness))
717
        if line_segment:
718
            value.append(str(line_segment))
719
            value.append(str(blank_segment))
720
        self.line_styles[index] = value
721
27 by gak
grids and line styles are not only restricted to line chart types
722
    # Grid
723
    # -------------------------------------------------------------------------
724
22 by gak
- pygooglechart.py converted to unix line breaks
725
    def set_grid(self, x_step, y_step, line_segment=1, \
726
            blank_segment=0):
727
        self.grid = '%s,%s,%s,%s' % (x_step, y_step, line_segment, \
728
            blank_segment)
729
27 by gak
grids and line styles are not only restricted to line chart types
730
731
class ScatterChart(Chart):
732
733
    def type_to_url(self):
734
        return 'cht=s'
735
736
    def annotated_data(self):
737
        yield ('x', self.data[0])
738
        yield ('y', self.data[1])
739
        if len(self.data) > 2:
740
            # The optional third dataset is relative sizing for point
741
            # markers.
742
            yield ('marker-size', self.data[2])
743
33 by gak
pep8 fixes, version bump
744
27 by gak
grids and line styles are not only restricted to line chart types
745
class LineChart(Chart):
746
747
    def __init__(self, *args, **kwargs):
35 by gak
- Initial "grammar" code
748
        if type(self) == LineChart:
749
            raise AbstractClassException('This is an abstract class')
27 by gak
grids and line styles are not only restricted to line chart types
750
        Chart.__init__(self, *args, **kwargs)
751
22 by gak
- pygooglechart.py converted to unix line breaks
752
753
class SimpleLineChart(LineChart):
754
755
    def type_to_url(self):
756
        return 'cht=lc'
757
758
    def annotated_data(self):
759
        # All datasets are y-axis data.
760
        for dataset in self.data:
761
            yield ('y', dataset)
762
33 by gak
pep8 fixes, version bump
763
22 by gak
- pygooglechart.py converted to unix line breaks
764
class SparkLineChart(SimpleLineChart):
765
766
    def type_to_url(self):
767
        return 'cht=ls'
768
33 by gak
pep8 fixes, version bump
769
22 by gak
- pygooglechart.py converted to unix line breaks
770
class XYLineChart(LineChart):
771
772
    def type_to_url(self):
773
        return 'cht=lxy'
774
775
    def annotated_data(self):
776
        # Datasets alternate between x-axis, y-axis.
777
        for i, dataset in enumerate(self.data):
778
            if i % 2 == 0:
779
                yield ('x', dataset)
780
            else:
781
                yield ('y', dataset)
782
33 by gak
pep8 fixes, version bump
783
22 by gak
- pygooglechart.py converted to unix line breaks
784
class BarChart(Chart):
785
786
    def __init__(self, *args, **kwargs):
35 by gak
- Initial "grammar" code
787
        if type(self) == BarChart:
788
            raise AbstractClassException('This is an abstract class')
22 by gak
- pygooglechart.py converted to unix line breaks
789
        Chart.__init__(self, *args, **kwargs)
790
        self.bar_width = None
30 by gak
Added zero line to bar charts
791
        self.zero_lines = {}
22 by gak
- pygooglechart.py converted to unix line breaks
792
793
    def set_bar_width(self, bar_width):
794
        self.bar_width = bar_width
795
30 by gak
Added zero line to bar charts
796
    def set_zero_line(self, index, zero_line):
797
        self.zero_lines[index] = zero_line
798
799
    def get_url_bits(self, data_class=None, skip_chbh=False):
25 by gak
Autoscale fixes and refactoring by Grahm Ullrich
800
        url_bits = Chart.get_url_bits(self, data_class=data_class)
30 by gak
Added zero line to bar charts
801
        if not skip_chbh and self.bar_width is not None:
22 by gak
- pygooglechart.py converted to unix line breaks
802
            url_bits.append('chbh=%i' % self.bar_width)
30 by gak
Added zero line to bar charts
803
        zero_line = []
804
        if self.zero_lines:
805
            for index in xrange(max(self.zero_lines) + 1):
806
                if index in self.zero_lines:
807
                    zero_line.append(str(self.zero_lines[index]))
808
                else:
809
                    zero_line.append('0')
810
            url_bits.append('chp=%s' % ','.join(zero_line))
22 by gak
- pygooglechart.py converted to unix line breaks
811
        return url_bits
812
813
814
class StackedHorizontalBarChart(BarChart):
815
816
    def type_to_url(self):
817
        return 'cht=bhs'
818
819
820
class StackedVerticalBarChart(BarChart):
821
822
    def type_to_url(self):
823
        return 'cht=bvs'
824
825
    def annotated_data(self):
826
        for dataset in self.data:
827
            yield ('y', dataset)
828
829
830
class GroupedBarChart(BarChart):
831
832
    def __init__(self, *args, **kwargs):
35 by gak
- Initial "grammar" code
833
        if type(self) == GroupedBarChart:
834
            raise AbstractClassException('This is an abstract class')
22 by gak
- pygooglechart.py converted to unix line breaks
835
        BarChart.__init__(self, *args, **kwargs)
836
        self.bar_spacing = None
837
        self.group_spacing = None
838
839
    def set_bar_spacing(self, spacing):
840
        """Set spacing between bars in a group."""
841
        self.bar_spacing = spacing
842
843
    def set_group_spacing(self, spacing):
844
        """Set spacing between groups of bars."""
845
        self.group_spacing = spacing
846
25 by gak
Autoscale fixes and refactoring by Grahm Ullrich
847
    def get_url_bits(self, data_class=None):
22 by gak
- pygooglechart.py converted to unix line breaks
848
        # Skip 'BarChart.get_url_bits' and call Chart directly so the parent
849
        # doesn't add "chbh" before we do.
30 by gak
Added zero line to bar charts
850
        url_bits = BarChart.get_url_bits(self, data_class=data_class,
851
            skip_chbh=True)
22 by gak
- pygooglechart.py converted to unix line breaks
852
        if self.group_spacing is not None:
853
            if self.bar_spacing is None:
33 by gak
pep8 fixes, version bump
854
                raise InvalidParametersException('Bar spacing is required ' \
855
                    'to be set when setting group spacing')
22 by gak
- pygooglechart.py converted to unix line breaks
856
            if self.bar_width is None:
857
                raise InvalidParametersException('Bar width is required to ' \
858
                    'be set when setting bar spacing')
859
            url_bits.append('chbh=%i,%i,%i'
860
                % (self.bar_width, self.bar_spacing, self.group_spacing))
861
        elif self.bar_spacing is not None:
862
            if self.bar_width is None:
863
                raise InvalidParametersException('Bar width is required to ' \
864
                    'be set when setting bar spacing')
865
            url_bits.append('chbh=%i,%i' % (self.bar_width, self.bar_spacing))
27 by gak
grids and line styles are not only restricted to line chart types
866
        elif self.bar_width:
22 by gak
- pygooglechart.py converted to unix line breaks
867
            url_bits.append('chbh=%i' % self.bar_width)
868
        return url_bits
869
870
871
class GroupedHorizontalBarChart(GroupedBarChart):
872
873
    def type_to_url(self):
874
        return 'cht=bhg'
875
876
877
class GroupedVerticalBarChart(GroupedBarChart):
878
879
    def type_to_url(self):
880
        return 'cht=bvg'
881
882
    def annotated_data(self):
883
        for dataset in self.data:
884
            yield ('y', dataset)
885
886
887
class PieChart(Chart):
888
889
    def __init__(self, *args, **kwargs):
35 by gak
- Initial "grammar" code
890
        if type(self) == PieChart:
891
            raise AbstractClassException('This is an abstract class')
22 by gak
- pygooglechart.py converted to unix line breaks
892
        Chart.__init__(self, *args, **kwargs)
893
        self.pie_labels = []
36 by gak
- Really added initial unit tests
894
        if self.y_range:
895
            warnings.warn('y_range is not used with %s.' % \
896
                (self.__class__.__name__))
22 by gak
- pygooglechart.py converted to unix line breaks
897
898
    def set_pie_labels(self, labels):
899
        self.pie_labels = [urllib.quote(a) for a in labels]
900
25 by gak
Autoscale fixes and refactoring by Grahm Ullrich
901
    def get_url_bits(self, data_class=None):
902
        url_bits = Chart.get_url_bits(self, data_class=data_class)
22 by gak
- pygooglechart.py converted to unix line breaks
903
        if self.pie_labels:
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
904
            url_bits.append('chl=%s' % '%7c'.join(self.pie_labels))
22 by gak
- pygooglechart.py converted to unix line breaks
905
        return url_bits
906
907
    def annotated_data(self):
908
        # Datasets are all y-axis data. However, there should only be
909
        # one dataset for pie charts.
910
        for dataset in self.data:
36 by gak
- Really added initial unit tests
911
            yield ('x', dataset)
22 by gak
- pygooglechart.py converted to unix line breaks
912
47 by gak
- Fixed bug with automatic scaling and pie charts
913
    def scaled_data(self, data_class, x_range=None, y_range=None):
914
        if not x_range:
915
            x_range = [0, sum(self.data[0])]
916
        return Chart.scaled_data(self, data_class, x_range, self.y_range)
917
22 by gak
- pygooglechart.py converted to unix line breaks
918
919
class PieChart2D(PieChart):
920
921
    def type_to_url(self):
922
        return 'cht=p'
923
924
925
class PieChart3D(PieChart):
926
927
    def type_to_url(self):
928
        return 'cht=p3'
929
930
931
class VennChart(Chart):
932
933
    def type_to_url(self):
934
        return 'cht=v'
935
936
    def annotated_data(self):
937
        for dataset in self.data:
938
            yield ('y', dataset)
939
940
26 by gak
Initial radar chart implementation
941
class RadarChart(Chart):
942
943
    def type_to_url(self):
944
        return 'cht=r'
945
33 by gak
pep8 fixes, version bump
946
28 by gak
Added map chart type
947
class SplineRadarChart(RadarChart):
948
949
    def type_to_url(self):
950
        return 'cht=rs'
951
952
953
class MapChart(Chart):
954
955
    def __init__(self, *args, **kwargs):
956
        Chart.__init__(self, *args, **kwargs)
957
        self.geo_area = 'world'
958
        self.codes = []
58 by gak
- Version bump to 0.3.0
959
        self.__areas = ('africa', 'asia', 'europe', 'middle_east',
960
            'south_america', 'usa', 'world')
961
        self.__ccodes = (
962
            'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AN', 'AO', 'AQ', 'AR',
963
            'AS', 'AT', 'AU', 'AW', 'AX', 'AZ', 'BA', 'BB', 'BD', 'BE', 'BF',
964
            'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BR', 'BS', 'BT',
965
            'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI',
966
            'CK', 'CL', 'CM', 'CN', 'CO', 'CR', 'CU', 'CV', 'CX', 'CY', 'CZ',
967
            'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER',
968
            'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD',
969
            'GE', 'GF', 'GG', 'GH', 'GI', 'GL', 'GM', 'GN', 'GP', 'GQ', 'GR',
970
            'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU',
971
            'ID', 'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE',
972
            'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI', 'KM', 'KN', 'KP', 'KR',
973
            'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT',
974
            'LU', 'LV', 'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK',
975
            'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS', 'MT', 'MU', 'MV',
976
            'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL',
977
            'NO', 'NP', 'NR', 'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH',
978
            'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY', 'QA', 'RE',
979
            'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH',
980
            'SI', 'SJ', 'SK', 'SL', 'SM', 'SN', 'SO', 'SR', 'ST', 'SV', 'SY',
981
            'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL', 'TM', 'TN',
982
            'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'US', 'UY',
983
            'UZ', 'VA', 'VC', 'VE', 'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE',
984
            'YT', 'ZA', 'ZM', 'ZW')
57 by gak
- #23 MapChart extensions (thanks to Andreas Schawo)
985
        
28 by gak
Added map chart type
986
    def type_to_url(self):
987
        return 'cht=t'
988
989
    def set_codes(self, codes):
57 by gak
- #23 MapChart extensions (thanks to Andreas Schawo)
990
        '''Set the country code map for the data.
991
        Codes given in a list.
992
993
        i.e. DE - Germany
994
             AT - Austria
995
             US - United States
996
        '''
997
998
        codemap = ''
999
        
1000
        for cc in codes:
1001
            cc = cc.upper()
1002
            if cc in self.__ccodes:
1003
                codemap += cc
1004
            else:
1005
                raise UnknownCountryCodeException(cc)
1006
            
1007
        self.codes = codemap
1008
1009
    def set_geo_area(self, area):
1010
        '''Sets the geo area for the map.
1011
1012
        * africa
1013
        * asia
1014
        * europe
1015
        * middle_east
1016
        * south_america
1017
        * usa
1018
        * world
1019
        '''
1020
        
1021
        if area in self.__areas:
1022
            self.geo_area = area
1023
        else:
1024
            raise UnknownChartType('Unknown chart type for maps: %s' %area)
28 by gak
Added map chart type
1025
1026
    def get_url_bits(self, data_class=None):
1027
        url_bits = Chart.get_url_bits(self, data_class=data_class)
1028
        url_bits.append('chtm=%s' % self.geo_area)
1029
        if self.codes:
1030
            url_bits.append('chld=%s' % ''.join(self.codes))
1031
        return url_bits
1032
57 by gak
- #23 MapChart extensions (thanks to Andreas Schawo)
1033
    def add_data_dict(self, datadict):
1034
        '''Sets the data and country codes via a dictionary.
1035
1036
        i.e. {'DE': 50, 'GB': 30, 'AT': 70}
1037
        '''
1038
1039
        self.set_codes(datadict.keys())
1040
        self.add_data(datadict.values())
1041
29 by gak
Added Google-o-meter chart
1042
1043
class GoogleOMeterChart(PieChart):
1044
    """Inheriting from PieChart because of similar labeling"""
1045
36 by gak
- Really added initial unit tests
1046
    def __init__(self, *args, **kwargs):
1047
        PieChart.__init__(self, *args, **kwargs)
1048
        if self.auto_scale and not self.x_range:
1049
            warnings.warn('Please specify an x_range with GoogleOMeterChart, '
1050
                'otherwise one arrow will always be at the max.')
1051
29 by gak
Added Google-o-meter chart
1052
    def type_to_url(self):
1053
        return 'cht=gom'
1054
26 by gak
Initial radar chart implementation
1055
38 by gak
- Added support for QR Code chart (#8)
1056
class QRChart(Chart):
1057
1058
    def __init__(self, *args, **kwargs):
1059
        Chart.__init__(self, *args, **kwargs)
1060
        self.encoding = None
1061
        self.ec_level = None
1062
        self.margin = None
1063
1064
    def type_to_url(self):
1065
        return 'cht=qr'
1066
1067
    def data_to_url(self, data_class=None):
1068
        if not self.data:
1069
            raise NoDataGivenException()
1070
        return 'chl=%s' % urllib.quote(self.data[0])
1071
1072
    def get_url_bits(self, data_class=None):
1073
        url_bits = Chart.get_url_bits(self, data_class=data_class)
1074
        if self.encoding:
1075
            url_bits.append('choe=%s' % self.encoding)
1076
        if self.ec_level:
50 by gak
- All pipe "|" characters are now encoded to %7C (Tom Payne)
1077
            url_bits.append('chld=%s%%7c%s' % (self.ec_level, self.margin))
38 by gak
- Added support for QR Code chart (#8)
1078
        return url_bits
1079
1080
    def set_encoding(self, encoding):
1081
        self.encoding = encoding
1082
1083
    def set_ec(self, level, margin):
1084
        self.ec_level = level
1085
        self.margin = margin
1086
1087
35 by gak
- Initial "grammar" code
1088
class ChartGrammar(object):
1089
36 by gak
- Really added initial unit tests
1090
    def __init__(self):
1091
        self.grammar = None
1092
        self.chart = None
1093
1094
    def parse(self, grammar):
35 by gak
- Initial "grammar" code
1095
        self.grammar = grammar
1096
        self.chart = self.create_chart_instance()
1097
36 by gak
- Really added initial unit tests
1098
        for attr in self.grammar:
1099
            if attr in ('w', 'h', 'type', 'auto_scale', 'x_range', 'y_range'):
1100
                continue  # These are already parsed in create_chart_instance
1101
            attr_func = 'parse_' + attr
1102
            if not hasattr(self, attr_func):
1103
                warnings.warn('No parser for grammar attribute "%s"' % (attr))
1104
                continue
1105
            getattr(self, attr_func)(grammar[attr])
1106
1107
        return self.chart
1108
1109
    def parse_data(self, data):
1110
        self.chart.data = data
1111
35 by gak
- Initial "grammar" code
1112
    @staticmethod
1113
    def get_possible_chart_types():
1114
        possible_charts = []
36 by gak
- Really added initial unit tests
1115
        for cls_name in globals().keys():
35 by gak
- Initial "grammar" code
1116
            if not cls_name.endswith('Chart'):
1117
                continue
1118
            cls = globals()[cls_name]
1119
            # Check if it is an abstract class
1120
            try:
36 by gak
- Really added initial unit tests
1121
                a = cls(1, 1, auto_scale=False)
1122
                del a
35 by gak
- Initial "grammar" code
1123
            except AbstractClassException:
1124
                continue
1125
            # Strip off "Class"
1126
            possible_charts.append(cls_name[:-5])
1127
        return possible_charts
1128
36 by gak
- Really added initial unit tests
1129
    def create_chart_instance(self, grammar=None):
1130
        if not grammar:
1131
            grammar = self.grammar
1132
        assert(isinstance(grammar, dict))  # grammar must be a dict
35 by gak
- Initial "grammar" code
1133
        assert('w' in grammar)  # width is required
1134
        assert('h' in grammar)  # height is required
1135
        assert('type' in grammar)  # type is required
36 by gak
- Really added initial unit tests
1136
        chart_type = grammar['type']
1137
        w = grammar['w']
1138
        h = grammar['h']
1139
        auto_scale = grammar.get('auto_scale', None)
1140
        x_range = grammar.get('x_range', None)
1141
        y_range = grammar.get('y_range', None)
35 by gak
- Initial "grammar" code
1142
        types = ChartGrammar.get_possible_chart_types()
36 by gak
- Really added initial unit tests
1143
        if chart_type not in types:
35 by gak
- Initial "grammar" code
1144
            raise UnknownChartType('%s is an unknown chart type. Possible '
36 by gak
- Really added initial unit tests
1145
                'chart types are %s' % (chart_type, ','.join(types)))
1146
        return globals()[chart_type + 'Chart'](w, h, auto_scale=auto_scale,
1147
            x_range=x_range, y_range=y_range)
35 by gak
- Initial "grammar" code
1148
1149
    def download(self):
1150
        pass
1151