Package GChartWrapper :: Module GChart'
[hide private]
[frames] | no frames]

Source Code for Module GChartWrapper.GChart'

  1  # -*- coding: utf-8 -*- 
  2  ################################################################################ 
  3  #  GChartWrapper - v0.7 
  4  #  Copyright (C) 2008  Justin Quick <justquick@gmail.com> 
  5  # 
  6  #  This program is free software; you can redistribute it and/or modify 
  7  #  it under the terms of the GNU General Public License version 3 as published 
  8  #  by the Free Software Foundation. 
  9  # 
 10  #  Thanks to anyone who does anything for this project. 
 11  #  If you have even the smallest revision, please email me at above address. 
 12  ################################################################################ 
 13  """ 
 14  GChartWrapper - Google Chart API Wrapper 
 15   
 16  The wrapper can render the URL of the Google chart based on your parameters. 
 17  With the chart you can render an HTML img tag to insert into webpages on the fly, 
 18  show it directly in a webbrowser, or save the chart PNG to disk. New versions 
 19  can generate PIL PngImage instances. 
 20   
 21  Example 
 22   
 23      >>> G = GChart('lc',['simpleisbetterthancomplexcomplexisbetterthancomplicated']) 
 24      >>> G.title('The Zen of Python','00cc00',36) 
 25      >>> G.color('00cc00') 
 26      >>> G 
 27      '''http://chart.apis.google.com/chart? 
 28          chd=s:simpleisbetterthancomplexcomplexisbetterthancomplicated 
 29          &chco=00cc00 
 30          &chts=00cc00,36 
 31          &chs=300x150 
 32          &cht=lc 
 33          &chtt=The+Zen+of+Python''' 
 34      >>> G.image() # PIL instance 
 35      <PngImagePlugin.PngImageFile instance at 0xb79fe2ac> 
 36      >>> G.show() # Webbrowser open 
 37      True 
 38      >>> G.save('tmp.png') # Save to disk 
 39      'tmp.png' 
 40   
 41  See tests.py for unit test and other examples 
 42  """ 
 43  __all__ = ['Sparkline', 'Map', 'HorizontalBarStack', 'VerticalBarStack', 'QRCode', 
 44       'Line', 'GChart', 'HorizontalBarGroup', 'Scatter', 'Pie3D', 'Pie', 'Meter', 
 45       'Radar', 'VerticalBarGroup', 'LineXY', 'Venn', 'PieC','Pin','Text','Note','Bubble'] 
 46  __version__ = 0.7 
 47  from GChartWrapper.constants import * 
 48  from GChartWrapper.constants import _print 
 49  from GChartWrapper.encoding import Encoder 
 50  from webbrowser import open as webopen 
 51  from copy import copy 
 52   
 53  try: 
 54      from sha import new as new_sha 
 55  except ImportError: 
 56      from hashlib import sha1 
57 - def new_sha(astr):
58 return sha1(bytes(astr,'utf-8'))
59
60 -def lookup_color(color):
61 color = color.lower() 62 if color in COLOR_MAP: 63 return COLOR_MAP[color] 64 return color
65
66 -def color_args(args, *indexes):
67 args = list(args) 68 for i in indexes: 69 args[i] = lookup_color(args[i]) 70 return args
71
72 -class Dict(object):
73 """ 74 Abstract class for all dictionary operations 75 """
76 - def __init__(self, *args, **kwargs):
77 self.data = dict(*args, **kwargs)
78 - def __repr__(self): return '<GChartWrapper.%s>'%self.__class__.__name__
79 - def __cmp__(self, dict): return cmp(self.data, dict)
80 - def __len__(self): return len(self.data)
81 - def __getitem__(self, key):
82 if key in self.data: 83 return self.data[key] 84 raise KeyError(key)
85 - def __setitem__(self, key, item): self.data[key] = item
86 - def __delitem__(self, key): del self.data[key]
87 - def __contains__(self, key): return key in self.data
88 - def __iter__(self):
89 for key in self.keys(): yield key
90 - def keys(self): return self.data.keys()
91 - def items(self): return self.data.items()
92 - def values(self): return self.data.values()
93 - def has_key(self, key): return self.data.has_key(key)
94 - def update(self, **kwargs):
95 if kwargs: self.data.update(kwargs)
96 - def pop(self, key, *args): return self.data.pop(key, *args)
97 - def popitem(self): return self.data.popitem()
98
99 -class Axes(Dict):
100 """ 101 Axes attribute dictionary storage 102 103 Use this class via GChart(...).axes 104 Methods are taken one at a time, like so: 105 106 >>> G.axes.type('xy') 107 >>> G.axes.label('Label1') # X Axis 108 >>> G.axes.label('Label2') # Y Axis 109 """
110 - def __init__(self, parent):
111 self.parent = parent 112 self.labels,self.positions,self.ranges,self.styles = [],[],[],[] 113 self.data = {} 114 Dict.__init__(self)
115
116 - def type(self, atype):
117 """ 118 Define the type of axes you wish to use 119 atype must be one of x,t,y,r 120 call the rest of the axes functions in the corresponding order that you declare the type 121 APIPARAM: chxt 122 """ 123 for char in atype: 124 assert char in 'xtyr', 'Invalid axes type: %s'%char 125 if not ',' in atype: 126 atype = ','.join(atype) 127 self.data['chxt'] = atype 128 return self.parent
129
130 - def label(self, *args):
131 """ 132 Label each axes one at a time 133 args are of the form <label 1>,...,<label n> 134 APIPARAM: chxl 135 """ 136 label = '|'.join(map(str,args)) 137 id = len(self.labels) 138 self.labels.append( str('%d:|%s'%(id,label)).replace('None','') ) 139 return self.parent
140
141 - def position(self, *args):
142 """ 143 Set the label position of each axis, one at a time 144 args are of the form <label position 1>,...,<label position n> 145 APIPARAM: chxp 146 """ 147 position = ','.join(map(str,args)) 148 id = len(self.positions) 149 self.positions.append( str('%d,%s'%(id,position)).replace('None','') ) 150 return self.parent
151
152 - def range(self, *args):
153 """ 154 Set the range of each axis, one at a time 155 args are of the form <start of range>,<end of range> 156 APIPARAM: chxr 157 """ 158 self.ranges.append('%d,%s,%s'%(len(self.ranges), args[0], args[1])) 159 return self.parent
160
161 - def style(self, *args):
162 """ 163 Add style to your axis, one at a time 164 args are of the form <axis color>,<font size>,<alignment>,<drawing control>,<tick mark color> 165 APIPARAM: chxs 166 """ 167 id = str(len(self.styles)) 168 args = color_args(args, 0) 169 self.styles.append(','.join([id]+list(map(str,args)))) 170 return self.parent
171
172 - def render(self):
173 """Render the axes data into the dict data""" 174 if self.labels: 175 self.data['chxl'] = '|'.join(self.labels) 176 if self.styles: 177 self.data['chxs'] = '|'.join(self.styles) 178 if self.positions: 179 self.data['chxp'] = '|'.join(self.positions) 180 if self.ranges: 181 self.data['chxr'] = '|'.join(self.ranges) 182 return self.data
183
184 -class GChart(Dict):
185 """Main chart class 186 187 Chart type must be valid for cht parameter 188 Dataset can be any python iterable and be multi dimensional 189 Kwargs will be put into chart API params if valid"""
190 - def __init__(self, ctype=None, dataset=[], **kwargs):
191 self.lines,self.fills,self.markers,self.scales = [],[],[],[] 192 self._geo,self._ld = '','' 193 self._dataset = dataset 194 self.data = {} 195 Dict.__init__(self) 196 if ctype: 197 self.check_type(ctype) 198 self.data['cht'] = ctype 199 self._encoding = kwargs.pop('encoding', None) 200 self._scale = kwargs.pop('scale', None) 201 self.apiurl = kwargs.pop('apiurl', APIURL) 202 for k,v in kwargs.items(): 203 assert k in APIPARAMS, 'Invalid chart parameter: %s'%k 204 self.data[k] = v 205 self.axes = Axes(self)
206 207 ################### 208 # Callables 209 ###################
210 - def map(self, geo, country_codes):
211 """ 212 Creates a map of the defined geography with the given country/state codes 213 Geography choices are africa, asia, europe, middle_east, south_america, and world 214 ISO country codes can be found at http://code.google.com/apis/chart/isocodes.html 215 US state codes can be found at http://code.google.com/apis/chart/statecodes.html 216 APIPARAMS: chtm & chld 217 """ 218 assert geo in GEO, 'Geograpic area %s not recognized'%geo 219 self._geo = geo 220 self._ld = country_codes 221 return self
222
223 - def level_data(self, *args):
224 """ 225 Just used in QRCode for the moment 226 args are error_correction,margin_size 227 APIPARAM: chld 228 """ 229 assert args[0].lower() in 'lmqh', 'Unknown EC level %s'%level 230 self.data['chld'] = '%s|%s'%args 231 return self
232
233 - def bar(self, *args):
234 """ 235 For bar charts, specify bar thickness and spacing with the args 236 args are <bar width>,<space between bars>,<space between groups> 237 bar width can be relative or absolute, see the official doc 238 APIPARAM: chbh 239 """ 240 self.data['chbh'] = ','.join(map(str,args)) 241 return self
242
243 - def encoding(self, encoding):
244 """ 245 Specifies the encoding to be used for the Encoder 246 Must be one of 'simple','text', or 'extended' 247 """ 248 self._encoding = encoding 249 return self
250
251 - def output_encoding(self, encoding):
252 """ 253 Output encoding to use for QRCode encoding 254 Must be one of 'Shift_JIS','UTF-8', or 'ISO-8859-1' 255 APIPARAM: choe 256 """ 257 assert encoding in ('Shift_JIS','UTF-8','ISO-8859-1'),\ 258 'Unknown encoding %s'%encoding 259 self.data['choe'] = encoding 260 return self
261
262 - def scale(self, *args):
263 """ 264 Scales the data down to the given size 265 args must be in the form of <min>,<max> 266 will only work with text encoding 267 APIPARAM: chds 268 """ 269 self._scale = ['%s,%s'%args] 270 return self
271
272 - def dataset(self, data):
273 """ 274 Update the chart's dataset, can be two dimensional or contain string data 275 """ 276 self._dataset = data 277 return self
278
279 - def marker(self, *args):
280 """ 281 Defines markers one at a time for your graph 282 args are of the form <marker type>,<color>,<data set index>,<data point>,<size>,<priority> 283 see the official developers doc for the complete spec 284 APIPARAM: chm 285 """ 286 if len(args[0]) == 1: 287 assert args[0] in MARKERS, 'Invalid marker type: %s'%args[0] 288 assert len(args) <= 6, 'Incorrect arguments %s'%str(args) 289 args = color_args(args, 1) 290 self.markers.append(','.join(map(str,args)) ) 291 return self
292
293 - def line(self, *args):
294 """ 295 Called one at a time for each dataset 296 args are of the form <data set n line thickness>,<length of line segment>,<length of blank segment> 297 APIPARAM: chls 298 """ 299 self.lines.append(','.join(['%.1f'%x for x in map(float,args)])) 300 return self
301
302 - def fill(self, *args):
303 """ 304 Apply a solid fill to your chart 305 args are of the form <fill type>,<fill style>,... 306 fill type must be one of c,bg,a 307 fill style must be one of s,lg,ls 308 the rest of the args refer to the particular style, refer to the official doc 309 APIPARAM: chf 310 """ 311 assert args[0] in ('c','bg','a'), 'Fill type must be bg/c/a not %s'%args[0] 312 assert args[1] in ('s','lg','ls'), 'Fill style must be s/lg/ls not %s'%args[1] 313 if len(args) == 3: 314 args = color_args(args, 2) 315 else: 316 args = color_args(args, 3,5) 317 self.fills.append(','.join(map(str,args))) 318 return self
319
320 - def grid(self, *args):
321 """ 322 Apply a grid to your chart 323 args are of the form <x axis step size>,<y axis step size>,<length of line segment>,<length of blank segment> 324 APIPARAM: chg 325 """ 326 grids = map(str,map(float,args)) 327 self.data['chg'] = ','.join(grids).replace('None','') 328 return self
329
330 - def color(self, *args):
331 """ 332 Add a color for each dataset 333 args are of the form <color 1>,...<color n> 334 APIPARAM: chco 335 """ 336 args = color_args(args, *range(len(args))) 337 self.data['chco'] = ','.join(args) 338 return self
339
340 - def type(self, type):
341 """ 342 Set the chart type, either Google API type or regular name 343 APIPARAM: cht 344 """ 345 self.data['cht'] = self.check_type(str(type)) 346 return self
347
348 - def label(self, *args):
349 """ 350 Add a simple label to your chart 351 call each time for each dataset 352 APIPARAM: chl 353 """ 354 if self.data['cht'] == 'qr': 355 self.data['chl'] = ''.join(map(str,args)) 356 else: 357 self.data['chl'] = '|'.join(map(str,args)) 358 return self
359
360 - def legend(self, *args):
361 """ 362 Add a legend to your chart 363 call each time for each dataset 364 APIPARAM: chdl 365 """ 366 self.data['chdl'] = '|'.join(args) 367 return self
368
369 - def legend_pos(self, pos):
370 """ 371 Define a position for your legend to occupy 372 pos must be one of b,t,r,l 373 APIPARAM: chdlp 374 """ 375 assert pos in 'btrl', 'Unknown legend position: %s'%pos 376 self.data['chdlp'] = str(pos) 377 return self
378
379 - def title(self, title, *args):
380 """ 381 Add a title to your chart 382 args are optional style params of the form <color>,<font size> 383 APIPARAMS: chtt,chts 384 """ 385 self.data['chtt'] = title 386 if args: 387 args = color_args(args, 0) 388 self.data['chts'] = ','.join(map(str,args)) 389 return self
390
391 - def size(self,*args):
392 """ 393 Set the size of the chart, args are width,height and can be tuple 394 APIPARAM: chs 395 """ 396 if len(args) == 2: 397 x,y = map(int,args) 398 else: 399 x,y = map(int,args[0]) 400 self.check_size(x,y) 401 self.data['chs'] = '%dx%d'%(x,y) 402 return self
403
404 - def margin(self, *args):
405 """ 406 Set the margins of your chart 407 args are of the form <left margin>,<right margin>,<top margin>,<bottom margin>[,<legend width>,<legend height>] 408 the legend args are optional 409 APIPARAM: chma 410 """ 411 if len(args) == 4: 412 self.data['chma'] = ','.join(map(str,args)) 413 elif len(args) == 6: 414 self.data['chma'] = ','.join(map(str,args[:4]))+'|'+','.join(map(str,args[4:])) 415 else: 416 raise ValueError('Margin arguments must be either 4 or 6 items')
417
418 - def render(self):
419 """ 420 Renders the chart context and axes into the dict data 421 """ 422 self.data.update(self.axes.render()) 423 encoder = Encoder(self._encoding) 424 if not 'chs' in self.data: 425 self.data['chs'] = '300x150' 426 else: 427 size = self.data['chs'].split('x') 428 assert len(size) == 2, 'Invalid size, must be in the format WxH' 429 self.check_size(*map(int,size)) 430 assert 'cht' in self.data, 'No chart type defined, use type method' 431 self.data['cht'] = self.check_type(self.data['cht']) 432 if ('any' in dir(self._dataset) and self._dataset.any()) or self._dataset: 433 self.data['chd'] = encoder.encode(self._dataset) 434 elif not 'choe' in self.data: 435 assert 'chd' in self.data, 'You must have a dataset, or use chd' 436 if self._scale: 437 assert self.data['chd'].startswith('t:'), 'You must use text encoding with chds' 438 self.data['chds'] = ','.join(self._scale) 439 if self._geo and self._ld: 440 self.data['chtm'] = self._geo 441 self.data['chld'] = self._ld 442 if self.lines: 443 self.data['chls'] = '|'.join(self.lines) 444 if self.markers: 445 self.data['chm'] = '|'.join(self.markers) 446 if self.fills: 447 self.data['chf'] = '|'.join(self.fills)
448 449 ################### 450 # Checks 451 ###################
452 - def check_size(self,x,y):
453 """ 454 Make sure the chart size fits the standards 455 """ 456 assert x <= 1000, 'Width larger than 1,000' 457 assert y <= 1000, 'Height larger than 1,000' 458 assert x*y <= 300000, 'Resolution larger than 300,000'
459
460 - def check_type(self, type):
461 """Check to see if the type is either in TYPES or fits type name 462 463 Returns proper type 464 """ 465 if type in TYPES: 466 return type 467 tdict = dict(zip(TYPES,TYPES)) 468 tdict['line'] = 'lc' 469 tdict['bar'] = 'bvs' 470 tdict['pie'] = 'p' 471 tdict['venn'] = 'v' 472 tdict['scater'] = 's' 473 assert type in tdict, 'Invalid chart type: %s'%type 474 return tdict[type]
475 476 ##################### 477 # Convience Functions 478 #####################
479 - def getname(self):
480 """ 481 Gets the name of the chart, if it exists 482 """ 483 return self.data.get('chtt','')
484
485 - def getdata(self):
486 """ 487 Returns the decoded dataset from chd param 488 """ 489 #XXX: Why again? not even sure decode works well 490 return Encoder(self._encoding).decode(self.data['chd'])
491
492 - def _parts(self):
493 return ('%s=%s'%(k,QUOTE(v)) for k,v in self.data.items() if v)
494
495 - def __str__(self):
496 """ 497 Returns the rendered URL of the chart 498 """ 499 self.render() 500 return self.apiurl + '&'.join(self._parts()).replace(' ','+')
501
502 - def url(self):
503 """ 504 Uses str, AND enforces replacing spaces w/ pluses 505 """ 506 return self.__str__()
507
508 - def show(self, *args, **kwargs):
509 """ 510 Shows the chart URL in a webbrowser 511 512 Other arguments passed to webbrowser.open 513 """ 514 return webopen(str(self), *args, **kwargs)
515
516 - def save(self, fname=None):
517 """ 518 Download the chart from the URL into a filename as a PNG 519 520 The filename defaults to the chart title (chtt) if any 521 """ 522 if not fname: 523 fname = self.getname() 524 assert fname != None, 'You must specify a filename to save to' 525 if not fname.endswith('.png'): 526 fname += '.png' 527 try: 528 urlretrieve(str(self), fname) 529 except: 530 raise IOError('Problem saving chart to file: %s'%fname) 531 return fname
532
533 - def img(self, **kwargs):
534 """ 535 Returns an XHTML <img/> tag of the chart 536 537 kwargs can be other img tag attributes, which are strictly enforced 538 uses strict escaping on the url, necessary for proper XHTML 539 """ 540 safe = 'src="%s" ' % self.url().replace('&','&amp;').replace('<', '&lt;')\ 541 .replace('>', '&gt;').replace('"', '&quot;').replace( "'", '&#39;') 542 for item in kwargs.items(): 543 if not item[0] in IMGATTRS: 544 raise AttributeError('Invalid img tag attribute: %s'%item[0]) 545 safe += '%s="%s" '%item 546 return '<img %s/>'%safe
547
548 - def urlopen(self):
549 """ 550 Grabs readable PNG file pointer 551 """ 552 return urlopen(self.__str__())
553
554 - def image(self):
555 """ 556 Returns a PngImageFile instance of the chart 557 558 You must have PIL installed for this to work 559 """ 560 try: 561 import Image 562 except ImportError: 563 raise ImportError('You must install PIL to fetch image objects') 564 try: 565 from cStringIO import StringIO 566 except ImportError: 567 from StringIO import StringIO 568 return Image.open(StringIO(self.urlopen().read()))
569
570 - def write(self, fp):
571 """ 572 Writes out PNG image data in chunks to file pointer fp 573 574 fp must support w or wb 575 """ 576 urlfp = self.urlopen().fp 577 while 1: 578 try: 579 fp.write(urlfp.next()) 580 except StopIteration: 581 return
582
583 - def checksum(self):
584 """ 585 Returns the SHA1 hexdigest of the chart URL param parts 586 587 good for unittesting... 588 """ 589 self.render() 590 items = [(k,self.data[k]) for k in sorted(self.data)] 591 return new_sha(str(items)).hexdigest()
592 593 # Now a whole mess of convenience classes 594 # *for those of us who dont speak API*
595 -class Meter(GChart):
596 - def __init__(self, dataset, **kwargs):
597 # we can do this to other charts with preferred settings 598 kwargs['encoding'] = 'text' 599 GChart.__init__(self, 'gom', dataset, **kwargs)
600
601 -class QRCode(GChart):
602 - def __init__(self, content='', **kwargs):
603 kwargs['choe'] = 'UTF-8' 604 if type(content) in (type(''),): 605 kwargs['chl'] = QUOTE(content) 606 else: 607 kwargs['chl'] = QUOTE(content[0]) 608 GChart.__init__(self, 'qr', None, **kwargs)
609
610 -class Line(GChart):
611 - def __init__(self, dataset, **kwargs):
612 GChart.__init__(self, 'lc', dataset, **kwargs)
613
614 -class LineXY(GChart):
615 - def __init__(self, dataset, **kwargs):
616 GChart.__init__(self, 'lxy', dataset, **kwargs)
617
618 -class HorizontalBarStack(GChart):
619 - def __init__(self, dataset, **kwargs):
620 GChart.__init__(self, 'bhs', dataset, **kwargs)
621
622 -class VerticalBarStack(GChart):
623 - def __init__(self, dataset, **kwargs):
624 GChart.__init__(self, 'bvs', dataset, **kwargs)
625
626 -class HorizontalBarGroup(GChart):
627 - def __init__(self, dataset, **kwargs):
628 GChart.__init__(self, 'bhg', dataset, **kwargs)
629
630 -class VerticalBarGroup(GChart):
631 - def __init__(self, dataset, **kwargs):
632 GChart.__init__(self, 'bvg', dataset, **kwargs)
633
634 -class Pie(GChart):
635 - def __init__(self, dataset, **kwargs):
636 GChart.__init__(self, 'p', dataset, **kwargs)
637
638 -class Pie3D(GChart):
639 - def __init__(self, dataset, **kwargs):
640 GChart.__init__(self, 'p3', dataset, **kwargs)
641
642 -class Venn(GChart):
643 - def __init__(self, dataset, **kwargs):
644 GChart.__init__(self, 'v', dataset, **kwargs)
645
646 -class Scatter(GChart):
647 - def __init__(self, dataset, **kwargs):
648 GChart.__init__(self, 's', dataset, **kwargs)
649
650 -class Sparkline(GChart):
651 - def __init__(self, dataset, **kwargs):
652 GChart.__init__(self, 'ls', dataset, **kwargs)
653
654 -class Radar(GChart):
655 - def __init__(self, dataset, **kwargs):
656 GChart.__init__(self, 'r', dataset, **kwargs)
657
658 -class Map(GChart):
659 - def __init__(self, dataset, **kwargs):
660 GChart.__init__(self, 't', dataset, **kwargs)
661
662 -class PieC(GChart):
663 - def __init__(self, dataset, **kwargs):
664 GChart.__init__(self, 'pc', dataset, **kwargs)
665 666 ######################################## 667 # Now for something completely different 668 ########################################
669 -class Text(GChart):
670 - def render(self): pass
671 - def __init__(self, *args):
672 GChart.__init__(self) 673 self.data['chst'] = 'd_text_outline' 674 args = list(map(str, color_args(args, 0, 3))) 675 assert args[2] in 'lrh', 'Invalid text alignment' 676 assert args[4] in '_b', 'Invalid font style' 677 self.data['chld'] = '|'.join(args)\ 678 .replace('\r\n','|').replace('\r','|').replace('\n','|').replace(' ','+')
679
680 -class Pin(GChart):
681 - def render(self): pass
682 - def __init__(self, type, *args):
683 GChart.__init__(self) 684 assert type in PIN_TYPES, 'Invalid type' 685 if type == "pin_letter": 686 args = color_args(args, 1,2) 687 elif type == 'pin_icon': 688 args = color_args(args, 1) 689 assert args[0] in PIN_ICONS, 'Invalid icon name' 690 elif type == 'xpin_letter': 691 args = color_args(args, 2,3,4) 692 assert args[0] in PIN_SHAPES, 'Invalid pin shape' 693 if not args[0].startswith('pin_'): 694 args[0] = 'pin_%s'%args[0] 695 elif type == 'xpin_icon': 696 args = color_args(args, 2,3) 697 assert args[0] in PIN_SHAPES, 'Invalid pin shape' 698 if not args[0].startswith('pin_'): 699 args[0] = 'pin_%s'%args[0] 700 assert args[1] in PIN_ICONS, 'Invalid icon name' 701 elif type == 'spin': 702 args = color_args(args, 2) 703 self.data['chst'] = 'd_map_%s'%type 704 self.data['chld'] = '|'.join(map(str, args))\ 705 .replace('\r\n','|').replace('\r','|').replace('\n','|').replace(' ','+')
706 - def shadow(self):
707 image = copy(self) 708 chsts = self.data['chst'].split('_') 709 chsts[-1] = 'shadow' 710 image.data['chst'] = '_'.join(chsts) 711 return image
712
713 -class Note(GChart):
714 - def render(self): pass
715 - def __init__(self, *args):
716 GChart.__init__(self) 717 assert args[0] in NOTE_TYPES,'Invalid note type' 718 assert args[1] in NOTE_IMAGES,'Invalid note image' 719 if args[0].find('note')>-1: 720 self.data['chst'] = 'd_f%s'%args[0] 721 args = color_args(args, 3) 722 else: 723 self.data['chst'] = 'd_%s'%args[0] 724 assert args[2] in NOTE_WEATHERS,'Invalid weather' 725 args = args[1:] 726 self.data['chld'] = '|'.join(map(str, args))\ 727 .replace('\r\n','|').replace('\r','|').replace('\n','|').replace(' ','+')
728
729 -class Bubble(GChart):
730 - def render(self): pass
731 - def __init__(self, type, *args):
732 GChart.__init__(self) 733 assert type in BUBBLE_TYPES, 'Invalid type' 734 if type in ('icon_text_small','icon_text_big'): 735 args = color_args(args, 3,4) 736 assert args[0] in BUBBLE_SICONS,'Invalid icon type' 737 elif type == 'icon_texts_big': 738 args = color_args(args, 2,3) 739 assert args[0] in BUBBLE_LICONS,'Invalid icon type' 740 elif type == 'texts_big': 741 args = color_args(args, 1,2) 742 self.data['chst'] = 'd_bubble_%s'%type 743 self.data['chld'] = '|'.join(map(str, args))\ 744 .replace('\r\n','|').replace('\r','|').replace('\n','|').replace(' ','+')
745 - def shadow(self):
746 image = copy(self) 747 image.data['chst'] = '%s_shadow'%self.data['chst'] 748 return image
749 750 if __name__=='__main__': 751 from tests import test 752 test() 753