1#Copyright ReportLab Europe Ltd. 2000-2017
2#see license.txt for license details
3#history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/graphics/charts/piecharts.py
4# experimental pie chart script.  Two types of pie - one is a monolithic
5#widget with all top-level properties, the other delegates most stuff to
6#a wedges collection whic lets you customize the group or every individual
7#wedge.
8
9__version__='3.3.0'
10__doc__="""Basic Pie Chart class.
11
12This permits you to customize and pop out individual wedges;
13supports elliptical and circular pies.
14"""
15
16import copy, functools
17from math import sin, cos, pi
18
19from reportlab.lib import colors
20from reportlab.lib.validators import isColor, isNumber, isListOfNumbersOrNone,\
21                                    isListOfNumbers, isColorOrNone, isString,\
22                                    isListOfStringsOrNone, OneOf, SequenceOf,\
23                                    isBoolean, isListOfColors, isNumberOrNone,\
24                                    isNoneOrListOfNoneOrStrings, isTextAnchor,\
25                                    isNoneOrListOfNoneOrNumbers, isBoxAnchor,\
26                                    isStringOrNone, NoneOr, EitherOr,\
27                                    isNumberInRange
28from reportlab.graphics.widgets.markers import uSymbol2Symbol, isSymbol
29from reportlab.lib.attrmap import *
30from reportlab.pdfgen.canvas import Canvas
31from reportlab.graphics.shapes import Group, Drawing, Ellipse, Wedge, String, STATE_DEFAULTS, ArcPath, Polygon, Rect, PolyLine, Line
32from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder
33from reportlab.graphics.charts.areas import PlotArea
34from reportlab.graphics.charts.legends import _objStr
35from reportlab.graphics.charts.textlabels import Label
36from reportlab import xrange, ascii, cmp
37
38_ANGLE2BOXANCHOR={0:'w', 45:'sw', 90:'s', 135:'se', 180:'e', 225:'ne', 270:'n', 315: 'nw', -45: 'nw'}
39_ANGLE2RBOXANCHOR={0:'e', 45:'ne', 90:'n', 135:'nw', 180:'w', 225:'sw', 270:'s', 315: 'se', -45: 'se'}
40
41_ANGLELO    = 1e-7
42_ANGLEHI    = 360.0 - _ANGLELO
43
44class WedgeLabel(Label):
45    def _checkDXY(self,ba):
46        pass
47    def _getBoxAnchor(self):
48        ba = self.boxAnchor
49        if ba in ('autox','autoy'):
50            na = (int((self._pmv%360)/45.)*45)%360
51            if not (na % 90): # we have a right angle case
52                da = (self._pmv - na) % 360
53                if abs(da)>5:
54                    na += (da>0 and 45 or -45)
55            ba = (getattr(self,'_anti',None) and _ANGLE2RBOXANCHOR or _ANGLE2BOXANCHOR)[na]
56            self._checkDXY(ba)
57        return ba
58
59class WedgeProperties(PropHolder):
60    """This holds descriptive information about the wedges in a pie chart.
61
62    It is not to be confused with the 'wedge itself'; this just holds
63    a recipe for how to format one, and does not allow you to hack the
64    angles.  It can format a genuine Wedge object for you with its
65    format method.
66    """
67    _attrMap = AttrMap(
68        strokeWidth = AttrMapValue(isNumber,desc='Width of the wedge border'),
69        fillColor = AttrMapValue(isColorOrNone,desc='Filling color of the wedge'),
70        strokeColor = AttrMapValue(isColorOrNone,desc='Color of the wedge border'),
71        strokeDashArray = AttrMapValue(isListOfNumbersOrNone,desc='Style of the wedge border, expressed as a list of lengths of alternating dashes and blanks'),
72        strokeLineCap = AttrMapValue(OneOf(0,1,2),desc="Line cap 0=butt, 1=round & 2=square"),
73        strokeLineJoin = AttrMapValue(OneOf(0,1,2),desc="Line join 0=miter, 1=round & 2=bevel"),
74        strokeMiterLimit = AttrMapValue(isNumber,desc='Miter limit control miter line joins'),
75        popout = AttrMapValue(isNumber,desc="How far of centre a wedge to pop"),
76        fontName = AttrMapValue(isString,desc='Name of the font of the label text'),
77        fontSize = AttrMapValue(isNumber,desc='Size of the font of the label text in points'),
78        fontColor = AttrMapValue(isColorOrNone,desc='Color of the font of the label text'),
79        labelRadius = AttrMapValue(isNumber,desc='Distance between the center of the label box and the center of the pie, expressed in times the radius of the pie'),
80        label_dx = AttrMapValue(isNumber,desc='X Offset of the label'),
81        label_dy = AttrMapValue(isNumber,desc='Y Offset of the label'),
82        label_angle = AttrMapValue(isNumber,desc='Angle of the label, default (0) is horizontal, 90 is vertical, 180 is upside down'),
83        label_boxAnchor = AttrMapValue(isBoxAnchor,desc='Anchoring point of the label'),
84        label_boxStrokeColor = AttrMapValue(isColorOrNone,desc='Border color for the label box'),
85        label_boxStrokeWidth = AttrMapValue(isNumber,desc='Border width for the label box'),
86        label_boxFillColor = AttrMapValue(isColorOrNone,desc='Filling color of the label box'),
87        label_strokeColor = AttrMapValue(isColorOrNone,desc='Border color for the label text'),
88        label_strokeWidth = AttrMapValue(isNumber,desc='Border width for the label text'),
89        label_text = AttrMapValue(isStringOrNone,desc='Text of the label'),
90        label_leading = AttrMapValue(isNumberOrNone,desc=''),
91        label_width = AttrMapValue(isNumberOrNone,desc='Width of the label'),
92        label_maxWidth = AttrMapValue(isNumberOrNone,desc='Maximum width the label can grow to'),
93        label_height = AttrMapValue(isNumberOrNone,desc='Height of the label'),
94        label_textAnchor = AttrMapValue(isTextAnchor,desc='Maximum height the label can grow to'),
95        label_visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
96        label_topPadding = AttrMapValue(isNumber,'Padding at top of box'),
97        label_leftPadding = AttrMapValue(isNumber,'Padding at left of box'),
98        label_rightPadding = AttrMapValue(isNumber,'Padding at right of box'),
99        label_bottomPadding = AttrMapValue(isNumber,'Padding at bottom of box'),
100        label_simple_pointer = AttrMapValue(isBoolean,'Set to True for simple pointers'),
101        label_pointer_strokeColor = AttrMapValue(isColorOrNone,desc='Color of indicator line'),
102        label_pointer_strokeWidth = AttrMapValue(isNumber,desc='StrokeWidth of indicator line'),
103        label_pointer_elbowLength = AttrMapValue(isNumber,desc='Length of final indicator line segment'),
104        label_pointer_edgePad = AttrMapValue(isNumber,desc='pad between pointer label and box'),
105        label_pointer_piePad = AttrMapValue(isNumber,desc='pad between pointer label and pie'),
106        swatchMarker = AttrMapValue(NoneOr(isSymbol), desc="None or makeMarker('Diamond') ...",advancedUsage=1),
107        visible = AttrMapValue(isBoolean,'Set to false to skip displaying'),
108        shadingAmount = AttrMapValue(isNumberOrNone,desc='amount by which to shade fillColor'),
109        shadingAngle = AttrMapValue(isNumber,desc='shading changes at multiple of this angle (in degrees)'),
110        shadingDirection = AttrMapValue(OneOf('normal','anti'),desc="Whether shading is at start or end of wedge/sector"),
111        shadingKind = AttrMapValue(OneOf(None,'lighten','darken'),desc="use colors.Whiter or Blacker"),
112        )
113
114    def __init__(self):
115        self.strokeWidth = 0
116        self.fillColor = None
117        self.strokeColor = STATE_DEFAULTS["strokeColor"]
118        self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"]
119        self.strokeLineJoin = 1
120        self.strokeLineCap = 0
121        self.strokeMiterLimit = 0
122        self.popout = 0
123        self.fontName = STATE_DEFAULTS["fontName"]
124        self.fontSize = STATE_DEFAULTS["fontSize"]
125        self.fontColor = STATE_DEFAULTS["fillColor"]
126        self.labelRadius = 1.2
127        self.label_dx = self.label_dy = self.label_angle = 0
128        self.label_text = None
129        self.label_topPadding = self.label_leftPadding = self.label_rightPadding = self.label_bottomPadding = 0
130        self.label_boxAnchor = 'autox'
131        self.label_boxStrokeColor = None    #boxStroke
132        self.label_boxStrokeWidth = 0.5 #boxStrokeWidth
133        self.label_boxFillColor = None
134        self.label_strokeColor = None
135        self.label_strokeWidth = 0.1
136        self.label_leading =    self.label_width = self.label_maxWidth = self.label_height = None
137        self.label_textAnchor = 'start'
138        self.label_simple_pointer = 0
139        self.label_visible = 1
140        self.label_pointer_strokeColor = colors.black
141        self.label_pointer_strokeWidth = 0.5
142        self.label_pointer_elbowLength = 3
143        self.label_pointer_edgePad = 2
144        self.label_pointer_piePad = 3
145        self.visible = 1
146        self.shadingKind = None
147        self.shadingAmount = 0.5
148        self.shadingAngle = 2.0137
149        self.shadingDirection = 'normal'    #or 'anti'
150
151def _addWedgeLabel(self,text,angle,labelX,labelY,wedgeStyle,labelClass=WedgeLabel):
152    # now draw a label
153    if self.simpleLabels:
154        theLabel = String(labelX, labelY, text)
155        if not self.sideLabels:
156            theLabel.textAnchor = "middle"
157        else:
158            if (abs(angle) < 90 ) or (angle >270 and angle<450) or (-450< angle <-270):
159                theLabel.textAnchor = "start"
160            else:
161                theLabel.textAnchor = "end"
162        theLabel._pmv = angle
163        theLabel._simple_pointer = 0
164    else:
165        theLabel = labelClass()
166        theLabel._pmv = angle
167        theLabel.x = labelX
168        theLabel.y = labelY
169        theLabel.dx = wedgeStyle.label_dx
170        if not self.sideLabels:
171            theLabel.dy = wedgeStyle.label_dy
172            theLabel.boxAnchor = wedgeStyle.label_boxAnchor
173        else:
174            if wedgeStyle.fontSize is None:
175                sideLabels_dy = self.fontSize / 2.5
176            else:
177                sideLabels_dy = wedgeStyle.fontSize / 2.5
178            if wedgeStyle.label_dy is None:
179                theLabel.dy = sideLabels_dy
180            else:
181                theLabel.dy = wedgeStyle.label_dy + sideLabels_dy
182            if (abs(angle) < 90 ) or (angle >270 and angle<450) or (-450< angle <-270):
183                theLabel.boxAnchor = 'w'
184            else:
185                theLabel.boxAnchor = 'e'
186        theLabel.angle = wedgeStyle.label_angle
187        theLabel.boxStrokeColor = wedgeStyle.label_boxStrokeColor
188        theLabel.boxStrokeWidth = wedgeStyle.label_boxStrokeWidth
189        theLabel.boxFillColor = wedgeStyle.label_boxFillColor
190        theLabel.strokeColor = wedgeStyle.label_strokeColor
191        theLabel.strokeWidth = wedgeStyle.label_strokeWidth
192        _text = wedgeStyle.label_text
193        if _text is None: _text = text
194        theLabel._text = _text
195        theLabel.leading = wedgeStyle.label_leading
196        theLabel.width = wedgeStyle.label_width
197        theLabel.maxWidth = wedgeStyle.label_maxWidth
198        theLabel.height = wedgeStyle.label_height
199        theLabel.textAnchor = wedgeStyle.label_textAnchor
200        theLabel.visible = wedgeStyle.label_visible
201        theLabel.topPadding = wedgeStyle.label_topPadding
202        theLabel.leftPadding = wedgeStyle.label_leftPadding
203        theLabel.rightPadding = wedgeStyle.label_rightPadding
204        theLabel.bottomPadding = wedgeStyle.label_bottomPadding
205        theLabel._simple_pointer = wedgeStyle.label_simple_pointer
206    theLabel.fontSize = wedgeStyle.fontSize
207    theLabel.fontName = wedgeStyle.fontName
208    theLabel.fillColor = wedgeStyle.fontColor
209    return theLabel
210
211def _fixLabels(labels,n):
212    if labels is None:
213        labels = [''] * n
214    else:
215        i = n-len(labels)
216        if i>0: labels = list(labels)+['']*i
217    return labels
218
219class AbstractPieChart(PlotArea):
220
221    def makeSwatchSample(self, rowNo, x, y, width, height):
222        baseStyle = self.slices
223        styleIdx = rowNo % len(baseStyle)
224        style = baseStyle[styleIdx]
225        strokeColor = getattr(style, 'strokeColor', getattr(baseStyle,'strokeColor',None))
226        fillColor = getattr(style, 'fillColor', getattr(baseStyle,'fillColor',None))
227        strokeDashArray = getattr(style, 'strokeDashArray', getattr(baseStyle,'strokeDashArray',None))
228        strokeWidth = getattr(style, 'strokeWidth', getattr(baseStyle, 'strokeWidth',None))
229        swatchMarker = getattr(style, 'swatchMarker', getattr(baseStyle, 'swatchMarker',None))
230        if swatchMarker:
231            return uSymbol2Symbol(swatchMarker,x+width/2.,y+height/2.,fillColor)
232        return Rect(x,y,width,height,strokeWidth=strokeWidth,strokeColor=strokeColor,
233                    strokeDashArray=strokeDashArray,fillColor=fillColor)
234
235    def getSeriesName(self,i,default=None):
236        '''return series name i or default'''
237        try:
238            text = _objStr(self.labels[i])
239        except:
240            text = default
241        if not self.simpleLabels:
242            _text = getattr(self.slices[i],'label_text','')
243            if _text is not None: text = _text
244        return text
245
246def boundsOverlap(P,Q):
247    return not(P[0]>Q[2]-1e-2 or Q[0]>P[2]-1e-2 or P[1]>(0.5*(Q[1]+Q[3]))-1e-2 or Q[1]>(0.5*(P[1]+P[3]))-1e-2)
248
249def _findOverlapRun(B,i,wrap):
250    '''find overlap run containing B[i]'''
251    n = len(B)
252    R = [i]
253    while 1:
254        i = R[-1]
255        j = (i+1)%n
256        if j in R or not boundsOverlap(B[i],B[j]): break
257        R.append(j)
258    while 1:
259        i = R[0]
260        j = (i-1)%n
261        if j in R or not boundsOverlap(B[i],B[j]): break
262        R.insert(0,j)
263    return R
264
265def findOverlapRun(B,wrap=1):
266    '''determine a set of overlaps in bounding boxes B or return None'''
267    n = len(B)
268    if n>1:
269        for i in xrange(n-1):
270            R = _findOverlapRun(B,i,wrap)
271            if len(R)>1: return R
272    return None
273
274def fixLabelOverlaps(L, sideLabels=False, mult0=1.0):
275    nL = len(L)
276    if nL<2: return
277    B = [l._origdata['bounds'] for l in L]
278    OK = 1
279    RP = []
280    iter = 0
281    mult0 = float(mult0 + 0)
282    mult = mult0
283
284    if not sideLabels:
285        while iter<30:
286            R = findOverlapRun(B)
287            if not R: break
288            nR = len(R)
289            if nR==nL: break
290            if not [r for r in RP if r in R]:
291                mult = mult0
292            da = 0
293            r0 = R[0]
294            rL = R[-1]
295            bi = B[r0]
296            taa = aa = _360(L[r0]._pmv)
297            for r in R[1:]:
298                b = B[r]
299                da = max(da,min(b[2]-bi[0],bi[2]-b[0]))
300                bi = b
301                aa += L[r]._pmv
302            aa = aa/float(nR)
303            utaa = abs(L[rL]._pmv-taa)
304            ntaa = _360(utaa)
305            da *= mult*(nR-1)/ntaa
306
307            for r in R:
308                l = L[r]
309                orig = l._origdata
310                angle = l._pmv = _360(l._pmv+da*(_360(l._pmv)-aa))
311                rad = angle/_180_pi
312                l.x = orig['cx'] + orig['rx']*cos(rad)
313                l.y = orig['cy'] + orig['ry']*sin(rad)
314                B[r] = l.getBounds()
315            RP = R
316            mult *= 1.05
317            iter += 1
318
319    else:
320        while iter<30:
321            R = findOverlapRun(B)
322            if not R: break
323            nR = len(R)
324            if nR == nL: break
325            l1 = L[-1]
326            orig1 = l1._origdata
327            bounds1 = orig1['bounds']
328            for i,r in enumerate(R):
329                l = L[r]
330                orig = l._origdata
331                bounds = orig['bounds']
332                diff1 = 0
333                diff2 = 0
334                if not i == nR-1:
335                    if not bounds == bounds1:
336                        if bounds[3]>bounds1[1] and bounds1[1]<bounds[1]:
337                            diff1 = bounds[3]-bounds1[1]
338                        if bounds1[3]>bounds[1] and bounds[1]<bounds1[1]:
339                            diff2 = bounds1[3]-bounds[1]
340                        if diff1 > diff2:
341                            l.y +=0.5*(bounds1[3]-bounds1[1])
342                        elif diff2 >= diff1:
343                            l.y -= 0.5*(bounds1[3]-bounds1[1])
344                    B[r] = l.getBounds()
345            iter += 1
346
347def intervalIntersection(A,B):
348    x,y = max(min(A),min(B)),min(max(A),max(B))
349    if x>=y: return None
350    return x,y
351
352def _makeSideArcDefs(sa,direction):
353    sa %= 360
354    if 90<=sa<270:
355        if direction=='clockwise':
356            a = (0,90,sa),(1,-90,90),(0,-360+sa,-90)
357        else:
358            a = (0,sa,270),(1,270,450),(0,450,360+sa)
359    else:
360        offs = sa>=270 and 360 or 0
361        if direction=='clockwise':
362            a = (1,offs-90,sa),(0,offs-270,offs-90),(1,-360+sa,offs-270)
363        else:
364            a = (1,sa,offs+90),(0,offs+90,offs+270),(1,offs+270,360+sa)
365    return tuple([a for a in a if a[1]<a[2]])
366
367def _keyFLA(x,y):
368    return cmp(y[1]-y[0],x[1]-x[0])
369_keyFLA = functools.cmp_to_key(_keyFLA)
370
371def _findLargestArc(xArcs,side):
372    a = [a[1] for a in xArcs if a[0]==side and a[1] is not None]
373    if not a: return None
374    if len(a)>1: a.sort(key=_keyFLA)
375    return a[0]
376
377def _fPLSide(l,width,side=None):
378    data = l._origdata
379    if side is None:
380        li = data['li']
381        ri = data['ri']
382        if li is None:
383            side = 1
384            i = ri
385        elif ri is None:
386            side = 0
387            i = li
388        elif li[1]-li[0]>ri[1]-ri[0]:
389            side = 0
390            i = li
391        else:
392            side = 1
393            i = ri
394    w = data['width']
395    edgePad = data['edgePad']
396    if not side:    #on left
397        l._pmv = 180
398        l.x = edgePad+w
399        i = data['li']
400    else:
401        l._pmv = 0
402        l.x = width - w - edgePad
403        i = data['ri']
404    mid = data['mid'] = (i[0]+i[1])*0.5
405    data['smid'] = sin(mid/_180_pi)
406    data['cmid'] = cos(mid/_180_pi)
407    data['side'] = side
408    return side,w
409
410#key functions
411def _fPLCF(a,b):
412    return cmp(b._origdata['smid'],a._origdata['smid'])
413_fPLCF = functools.cmp_to_key(_fPLCF)
414
415def _arcCF(a):
416    return a[1]
417
418def _fixPointerLabels(n,L,x,y,width,height,side=None):
419    LR = [],[]
420    mlr = [0,0]
421    for l in L:
422        i,w = _fPLSide(l,width,side)
423        LR[i].append(l)
424        mlr[i] = max(w,mlr[i])
425    mul = 1
426    G = n*[None]
427    mel = 0
428    hh = height*0.5
429    yhh = y+hh
430    m = max(mlr)
431    for i in (0,1):
432        T = LR[i]
433        if T:
434            B = []
435            aB = B.append
436            S = []
437            aS = S.append
438            T.sort(key=_fPLCF)
439            p = 0
440            yh = y+height
441            for l in T:
442                data = l._origdata
443                inc = x+mul*(m-data['width'])
444                l.x += inc
445                G[data['index']] = l
446                ly = yhh+data['smid']*hh
447                b = data['bounds']
448                b2 = (b[3]-b[1])*0.5
449                if ly+b2>yh: ly = yh-b2
450                if ly-b2<y: ly = y+b2
451                data['bounds'] = b = (b[0],ly-b2,b[2],ly+b2)
452                aB(b)
453                l.y = ly
454                aS(max(0,yh-ly-b2))
455                yh = ly-b2
456                p = max(p,data['edgePad']+data['piePad'])
457                mel = max(mel,abs(data['smid']*(hh+data['elbowLength']))-hh)
458            aS(yh-y)
459
460            iter = 0
461            nT = len(T)
462            while iter<30:
463                R = findOverlapRun(B,wrap=0)
464                if not R: break
465                nR = len(R)
466                if nR==nT: break
467                j0 = R[0]
468                j1 = R[-1]
469                jl = j1+1
470                sAbove = sum(S[:j0+1])
471                sFree = sAbove+sum(S[jl:])
472                sNeed = sum([b[3]-b[1] for b in B[j0:jl]])+jl-j0-(B[j0][3]-B[j1][1])
473                if sNeed>sFree: break
474                yh = B[j0][3]+sAbove*sNeed/sFree
475                for r in R:
476                    l = T[r]
477                    data = l._origdata
478                    b = data['bounds']
479                    b2 = (b[3]-b[1])*0.5
480                    yh -= 0.5
481                    ly = l.y = yh-b2
482                    B[r] = data['bounds'] = (b[0],ly-b2,b[2],yh)
483                    yh = ly - b2 - 0.5
484            mlr[i] = m+p
485        mul = -1
486    return G, mlr[0], mlr[1], mel
487
488def theta0(data, direction):
489    fac = (2*pi)/sum(data)
490    rads = [d*fac for d in data]
491
492    r0 = 0
493    hrads = []
494    for r in rads:
495        hrads.append(r0+r*0.5)
496        r0 += r
497
498    vstar = len(data)*1e6
499    rstar = 0
500    delta = pi/36.0
501    for i in range(36):
502        r = i*delta
503        v = sum([abs(sin(r+a)) for a in hrads])
504        if v < vstar:
505            if direction == 'clockwise':
506                rstar=-r
507            else:
508                rstar=r
509            vstar = v
510    return rstar*180/pi
511
512
513class AngleData(float):
514    '''use this to carry the data along with the angle'''
515    def __new__(cls,angle,data):
516        self = float.__new__(cls,angle)
517        self._data = data
518        return self
519
520class Pie(AbstractPieChart):
521    _attrMap = AttrMap(BASE=AbstractPieChart,
522        data = AttrMapValue(isListOfNumbers, desc='List of numbers defining wedge sizes; need not sum to 1'),
523        labels = AttrMapValue(isListOfStringsOrNone, desc="Optional list of labels to use for each data point"),
524        startAngle = AttrMapValue(isNumber, desc="Angle of first slice; 0 is due East"),
525        direction = AttrMapValue(OneOf('clockwise', 'anticlockwise'), desc="'clockwise' or 'anticlockwise'"),
526        slices = AttrMapValue(None, desc="Collection of wedge descriptor objects"),
527        simpleLabels = AttrMapValue(isBoolean, desc="If true(default) use a simple String not an advanced WedgeLabel. A WedgeLabel is customisable using the properties prefixed label_ in the collection slices."),
528        other_threshold = AttrMapValue(isNumber, desc='A value for doing threshholding, not used yet.',advancedUsage=1),
529        checkLabelOverlap = AttrMapValue(EitherOr((isNumberInRange(0.05,1),isBoolean)), desc="If true check and attempt to fix\n standard label overlaps(default off)",advancedUsage=1),
530        pointerLabelMode = AttrMapValue(OneOf(None,'LeftRight','LeftAndRight'), desc='',advancedUsage=1),
531        sameRadii = AttrMapValue(isBoolean, desc="If true make x/y radii the same(default off)",advancedUsage=1),
532        orderMode = AttrMapValue(OneOf('fixed','alternate'),advancedUsage=1),
533        xradius = AttrMapValue(isNumberOrNone, desc="X direction Radius"),
534        yradius = AttrMapValue(isNumberOrNone, desc="Y direction Radius"),
535        innerRadiusFraction = AttrMapValue(isNumberOrNone, desc="fraction of radii to start wedges at"),
536        wedgeRecord = AttrMapValue(None, desc="callable(wedge,*args,**kwds)",advancedUsage=1),
537        sideLabels = AttrMapValue(isBoolean, desc="If true attempt to make piechart with labels along side and pointers"),
538        sideLabelsOffset = AttrMapValue(isNumber, desc="The fraction of the pie width that the labels are situated at from the edges of the pie"),
539        )
540    other_threshold=None
541
542    def __init__(self,**kwd):
543        PlotArea.__init__(self)
544        self.x = 0
545        self.y = 0
546        self.width = 100
547        self.height = 100
548        self.data = [1,2.3,1.7,4.2]
549        self.labels = None  # or list of strings
550        self.startAngle = 90
551        self.direction = "clockwise"
552        self.simpleLabels = 1
553        self.checkLabelOverlap = 0
554        self.pointerLabelMode = None
555        self.sameRadii = False
556        self.orderMode = 'fixed'
557        self.xradius = self.yradius = self.innerRadiusFraction = None
558        self.sideLabels = 0
559        self.sideLabelsOffset = 0.1
560
561        self.slices = TypedPropertyCollection(WedgeProperties)
562        self.slices[0].fillColor = colors.darkcyan
563        self.slices[1].fillColor = colors.blueviolet
564        self.slices[2].fillColor = colors.blue
565        self.slices[3].fillColor = colors.cyan
566        self.slices[4].fillColor = colors.pink
567        self.slices[5].fillColor = colors.magenta
568        self.slices[6].fillColor = colors.yellow
569
570    def demo(self):
571        d = Drawing(200, 100)
572
573        pc = Pie()
574        pc.x = 50
575        pc.y = 10
576        pc.width = 100
577        pc.height = 80
578        pc.data = [10,20,30,40,50,60]
579        pc.labels = ['a','b','c','d','e','f']
580
581        pc.slices.strokeWidth=0.5
582        pc.slices[3].popout = 10
583        pc.slices[3].strokeWidth = 2
584        pc.slices[3].strokeDashArray = [2,2]
585        pc.slices[3].labelRadius = 1.75
586        pc.slices[3].fontColor = colors.red
587        pc.slices[0].fillColor = colors.darkcyan
588        pc.slices[1].fillColor = colors.blueviolet
589        pc.slices[2].fillColor = colors.blue
590        pc.slices[3].fillColor = colors.cyan
591        pc.slices[4].fillColor = colors.aquamarine
592        pc.slices[5].fillColor = colors.cadetblue
593        pc.slices[6].fillColor = colors.lightcoral
594
595        d.add(pc)
596        return d
597
598    def makePointerLabels(self,angles,plMode):
599        class PL:
600            def __init__(self,centerx,centery,xradius,yradius,data,lu=0,ru=0):
601                self.centerx = centerx
602                self.centery = centery
603                self.xradius = xradius
604                self.yradius = yradius
605                self.data = data
606                self.lu = lu
607                self.ru = ru
608
609        labelX = self.width-2
610        labelY = self.height
611        n = nr = nl = maxW = sumH = 0
612        styleCount = len(self.slices)
613        L=[]
614        L_add = L.append
615        refArcs = _makeSideArcDefs(self.startAngle,self.direction)
616        for i, A in angles:
617            if A[1] is None: continue
618            sn = self.getSeriesName(i,'')
619            if not sn: continue
620            style = self.slices[i%styleCount]
621            if not style.label_visible or not style.visible: continue
622            n += 1
623            l=_addWedgeLabel(self,sn,180,labelX,labelY,style,labelClass=WedgeLabel)
624            L_add(l)
625            b = l.getBounds()
626            w = b[2]-b[0]
627            h = b[3]-b[1]
628            ri = [(a[0],intervalIntersection(A,(a[1],a[2]))) for a in refArcs]
629            li = _findLargestArc(ri,0)
630            ri = _findLargestArc(ri,1)
631            if li and ri:
632                if plMode=='LeftAndRight':
633                    if li[1]-li[0]<ri[1]-ri[0]:
634                        li = None
635                    else:
636                        ri = None
637                else:
638                    if li[1]-li[0]<0.02*(ri[1]-ri[0]):
639                        li = None
640                    elif (li[1]-li[0])*0.02>ri[1]-ri[0]:
641                        ri = None
642            if ri: nr += 1
643            if li: nl += 1
644            l._origdata = dict(bounds=b,width=w,height=h,li=li,ri=ri,index=i,edgePad=style.label_pointer_edgePad,piePad=style.label_pointer_piePad,elbowLength=style.label_pointer_elbowLength)
645            maxW = max(w,maxW)
646            sumH += h+2
647
648        if not n:   #we have no labels
649            xradius = self.width*0.5
650            yradius = self.height*0.5
651            centerx = self.x+xradius
652            centery = self.y+yradius
653            if self.xradius: xradius = self.xradius
654            if self.yradius: yradius = self.yradius
655            if self.sameRadii: xradius=yradius=min(xradius,yradius)
656            return PL(centerx,centery,xradius,yradius,[])
657
658        aonR = nr==n
659        if sumH<self.height and (aonR or nl==n):
660            side=int(aonR)
661        else:
662            side=None
663        G,lu,ru,mel = _fixPointerLabels(len(angles),L,self.x,self.y,self.width,self.height,side=side)
664        if plMode=='LeftAndRight':
665            lu = ru = max(lu,ru)
666        x0 = self.x+lu
667        x1 = self.x+self.width-ru
668        xradius = (x1-x0)*0.5
669        yradius = self.height*0.5-mel
670        centerx = x0+xradius
671        centery = self.y+yradius+mel
672        if self.xradius: xradius = self.xradius
673        if self.yradius: yradius = self.yradius
674        if self.sameRadii: xradius=yradius=min(xradius,yradius)
675        return PL(centerx,centery,xradius,yradius,G,lu,ru)
676
677    def normalizeData(self,keepData=False):
678        data = list(map(abs,self.data))
679        s = self._sum = float(sum(data))
680        f = 360./s if s!=0 else 1
681        if keepData:
682            return [AngleData(f*x,x) for x in data]
683        else:
684            return [f*x for x in data]
685
686    def makeAngles(self):
687        wr = getattr(self,'wedgeRecord',None)
688        if self.sideLabels:
689            startAngle = theta0(self.data, self.direction)
690            self.slices.label_visible = 1
691        else:
692            startAngle = self.startAngle % 360
693        whichWay = self.direction == "clockwise" and -1 or 1
694        D = [a for a in enumerate(self.normalizeData(keepData=wr))]
695        if self.orderMode=='alternate' and not self.sideLabels:
696            W = [a for a in D if abs(a[1])>=1e-5]
697            W.sort(key=_arcCF)
698            T = [[],[]]
699            i = 0
700            while W:
701                if i<2:
702                    a = W.pop(0)
703                else:
704                    a = W.pop(-1)
705                T[i%2].append(a)
706                i += 1
707                i %= 4
708            T[1].reverse()
709            D = T[0]+T[1] + [a for a in D if abs(a[1])<1e-5]
710        A = []
711        a = A.append
712        for i, angle in D:
713            endAngle = (startAngle + (angle * whichWay))
714            if abs(angle)>=_ANGLELO:
715                if startAngle >= endAngle:
716                    aa = endAngle,startAngle
717                else:
718                    aa = startAngle,endAngle
719            else:
720                aa = startAngle, None
721            if wr:
722                aa = (AngleData(aa[0],angle._data),aa[1])
723            startAngle = endAngle
724            a((i,aa))
725        return A
726
727    def makeWedges(self):
728        angles = self.makeAngles()
729        #Checking to see whether there are too many wedges packed in too small a space
730        halfAngles = []
731        for i,(a1,a2) in angles:
732            if a2 is None:
733                halfAngle = a1
734            else:
735                halfAngle = 0.5*(a2+a1)
736            halfAngles.append(halfAngle)
737        sideLabels = self.sideLabels
738        n = len(angles)
739        labels = _fixLabels(self.labels,n)
740        wr = getattr(self,'wedgeRecord',None)
741
742        self._seriesCount = n
743        styleCount = len(self.slices)
744
745        plMode = self.pointerLabelMode
746        if sideLabels:
747            plMode = None
748        if plMode:
749            checkLabelOverlap = False
750            PL=self.makePointerLabels(angles,plMode)
751            xradius = PL.xradius
752            yradius = PL.yradius
753            centerx = PL.centerx
754            centery = PL.centery
755            PL_data = PL.data
756            gSN = lambda i: ''
757        else:
758            xradius = self.width*0.5
759            yradius = self.height*0.5
760            centerx = self.x + xradius
761            centery = self.y + yradius
762            if self.xradius: xradius = self.xradius
763            if self.yradius: yradius = self.yradius
764            if self.sameRadii: xradius=yradius=min(xradius,yradius)
765            checkLabelOverlap = self.checkLabelOverlap
766            gSN = lambda i: self.getSeriesName(i,'')
767
768        g = Group()
769        g_add = g.add
770        L = []
771        L_add = L.append
772
773        innerRadiusFraction = self.innerRadiusFraction
774
775
776        for i,(a1,a2) in angles:
777            if a2 is None: continue
778            #if we didn't use %stylecount here we'd end up with the later wedges
779            #all having the default style
780            wedgeStyle = self.slices[i%styleCount]
781            if not wedgeStyle.visible: continue
782            aa = abs(a2-a1)
783
784            # is it a popout?
785            cx, cy = centerx, centery
786            text = gSN(i)
787            popout = wedgeStyle.popout
788            if text or popout:
789                averageAngle = (a1+a2)/2.0
790                aveAngleRadians = averageAngle/_180_pi
791                cosAA = cos(aveAngleRadians)
792                sinAA = sin(aveAngleRadians)
793                if popout and aa<_ANGLEHI:
794                    # pop out the wedge
795                    cx = centerx + popout*cosAA
796                    cy = centery + popout*sinAA
797
798            if innerRadiusFraction:
799                theWedge = Wedge(cx, cy, xradius, a1, a2, yradius=yradius,
800                        radius1=xradius*innerRadiusFraction,yradius1=yradius*innerRadiusFraction)
801            else:
802                if aa>=_ANGLEHI:
803                    theWedge = Ellipse(cx, cy, xradius, yradius)
804                else:
805                    theWedge = Wedge(cx, cy, xradius, a1, a2, yradius=yradius)
806
807
808            theWedge.fillColor = wedgeStyle.fillColor
809            theWedge.strokeColor = wedgeStyle.strokeColor
810            theWedge.strokeWidth = wedgeStyle.strokeWidth
811            theWedge.strokeLineJoin = wedgeStyle.strokeLineJoin
812            theWedge.strokeLineCap = wedgeStyle.strokeLineCap
813            theWedge.strokeMiterLimit = wedgeStyle.strokeMiterLimit
814            theWedge.strokeDashArray = wedgeStyle.strokeDashArray
815
816            shader = wedgeStyle.shadingKind
817            if shader:
818                nshades = aa / float(wedgeStyle.shadingAngle)
819                if nshades > 1:
820                    shader = colors.Whiter if shader=='lighten' else colors.Blacker
821                    nshades = 1+int(nshades)
822                    shadingAmount = 1-wedgeStyle.shadingAmount
823                    if wedgeStyle.shadingDirection=='normal':
824                        dsh = (1-shadingAmount)/float(nshades-1)
825                        shf1 = shadingAmount
826                    else:
827                        dsh = (shadingAmount-1)/float(nshades-1)
828                        shf1 = 1
829                    shda = (a2-a1)/float(nshades)
830                    shsc = wedgeStyle.fillColor
831                    theWedge.fillColor = None
832                    for ish in xrange(nshades):
833                        sha1 = a1 + ish*shda
834                        sha2 = a1 + (ish+1)*shda
835                        shc = shader(shsc,shf1 + dsh*ish)
836                        if innerRadiusFraction:
837                            shWedge = Wedge(cx, cy, xradius, sha1, sha2, yradius=yradius,
838                                    radius1=xradius*innerRadiusFraction,yradius1=yradius*innerRadiusFraction)
839                        else:
840                            shWedge = Wedge(cx, cy, xradius, sha1, sha2, yradius=yradius)
841                        shWedge.fillColor = shc
842                        shWedge.strokeColor = None
843                        shWedge.strokeWidth = 0
844                        g_add(shWedge)
845
846            g_add(theWedge)
847            if wr:
848                wr(theWedge,value=a1._data,label=text)
849            if wedgeStyle.label_visible:
850                if not sideLabels:
851                    if text:
852                        labelRadius = wedgeStyle.labelRadius
853                        rx = xradius*labelRadius
854                        ry = yradius*labelRadius
855                        labelX = cx + rx*cosAA
856                        labelY = cy + ry*sinAA
857                        l = _addWedgeLabel(self,text,averageAngle,labelX,labelY,wedgeStyle)
858                        L_add(l)
859                        if not plMode and l._simple_pointer:
860                            l._aax = cx+xradius*cosAA
861                            l._aay = cy+yradius*sinAA
862                        if checkLabelOverlap:
863                            l._origdata = { 'x': labelX, 'y':labelY, 'angle': averageAngle,
864                                            'rx': rx, 'ry':ry, 'cx':cx, 'cy':cy,
865                                            'bounds': l.getBounds(), 'angles':(a1,a2),
866                                            }
867                    elif plMode and PL_data:
868                        l = PL_data[i]
869                        if l:
870                            data = l._origdata
871                            sinM = data['smid']
872                            cosM = data['cmid']
873                            lX = cx + xradius*cosM
874                            lY = cy + yradius*sinM
875                            lpel = wedgeStyle.label_pointer_elbowLength
876                            lXi = lX + lpel*cosM
877                            lYi = lY + lpel*sinM
878                            L_add(PolyLine((lX,lY,lXi,lYi,l.x,l.y),
879                                    strokeWidth=wedgeStyle.label_pointer_strokeWidth,
880                                    strokeColor=wedgeStyle.label_pointer_strokeColor))
881                            L_add(l)
882                else:
883                    if text:
884                        slices_popout = self.slices.popout
885                        m=0
886                        for n, angle in angles:
887                            if self.slices[n].fillColor:
888                                m += 1
889                            else:
890                                r = n%m
891                                self.slices[n].fillColor = self.slices[r].fillColor
892                                self.slices[n].popout = self.slices[r].popout
893                        for j in range(0,m-1):
894                            if self.slices[j].popout > slices_popout:
895                                slices_popout = self.slices[j].popout
896                        labelRadius = wedgeStyle.labelRadius
897                        ry = yradius*labelRadius
898                        if (abs(averageAngle) < 90 ) or (averageAngle >270 and averageAngle <450) or (-450<
899                                averageAngle <-270):
900                            labelX = (1+self.sideLabelsOffset)*self.width + self.x + slices_popout
901                            rx = 0
902                        else:
903                            labelX = self.x - (self.sideLabelsOffset)*self.width - slices_popout
904                            rx = 0
905                        labelY = cy + ry*sinAA
906                        l = _addWedgeLabel(self,text,averageAngle,labelX,labelY,wedgeStyle)
907                        L_add(l)
908                        if not plMode:
909                            l._aax = cx+xradius*cosAA
910                            l._aay = cy+yradius*sinAA
911                        if checkLabelOverlap:
912                            l._origdata = { 'x': labelX, 'y':labelY, 'angle': averageAngle,
913                                            'rx': rx, 'ry':ry, 'cx':cx, 'cy':cy,
914                                            'bounds': l.getBounds(),
915                                            }
916                        x1,y1,x2,y2 = l.getBounds()
917
918        if checkLabelOverlap and L:
919            fixLabelOverlaps(L, sideLabels, mult0=checkLabelOverlap)
920        for l in L: g_add(l)
921
922        if not plMode:
923            for l in L:
924                if l._simple_pointer and not sideLabels:
925                    g_add(Line(l.x,l.y,l._aax,l._aay,
926                        strokeWidth=wedgeStyle.label_pointer_strokeWidth,
927                        strokeColor=wedgeStyle.label_pointer_strokeColor))
928                elif sideLabels:
929                    x1,y1,x2,y2 = l.getBounds()
930                    #add pointers
931                    if l.x == (1+self.sideLabelsOffset)*self.width + self.x:
932                        g_add(Line(l._aax,l._aay,0.5*(l._aax+l.x),l.y+(0.25*(y2-y1)),
933                            strokeWidth=wedgeStyle.label_pointer_strokeWidth,
934                            strokeColor=wedgeStyle.label_pointer_strokeColor))
935                        g_add(Line(0.5*(l._aax+l.x),l.y+(0.25*(y2-y1)),l.x,l.y+(0.25*(y2-y1)),
936                            strokeWidth=wedgeStyle.label_pointer_strokeWidth,
937                            strokeColor=wedgeStyle.label_pointer_strokeColor))
938                    else:
939                        g_add(Line(l._aax,l._aay,0.5*(l._aax+l.x),l.y+(0.25*(y2-y1)),
940                            strokeWidth=wedgeStyle.label_pointer_strokeWidth,
941                            strokeColor=wedgeStyle.label_pointer_strokeColor))
942                        g_add(Line(0.5*(l._aax+l.x),l.y+(0.25*(y2-y1)),l.x,l.y+(0.25*(y2-y1)),
943                            strokeWidth=wedgeStyle.label_pointer_strokeWidth,
944                            strokeColor=wedgeStyle.label_pointer_strokeColor))
945
946        return g
947
948    def draw(self):
949        G = self.makeBackground()
950        w = self.makeWedges()
951        if G: return Group(G,w)
952        return w
953
954class LegendedPie(Pie):
955    """Pie with a two part legend (one editable with swatches, one hidden without swatches)."""
956
957    _attrMap = AttrMap(BASE=Pie,
958        drawLegend = AttrMapValue(isBoolean, desc="If true then create and draw legend"),
959        legend1 = AttrMapValue(None, desc="Handle to legend for pie"),
960        legendNumberFormat = AttrMapValue(None, desc="Formatting routine for number on right hand side of legend."),
961        legendNumberOffset = AttrMapValue(isNumber, desc="Horizontal space between legend and numbers on r/hand side"),
962        pieAndLegend_colors = AttrMapValue(isListOfColors, desc="Colours used for both swatches and pie"),
963        legend_names = AttrMapValue(isNoneOrListOfNoneOrStrings, desc="Names used in legend (or None)"),
964        legend_data = AttrMapValue(isNoneOrListOfNoneOrNumbers, desc="Numbers used on r/hand side of legend (or None)"),
965        leftPadding = AttrMapValue(isNumber, desc='Padding on left of drawing'),
966        rightPadding = AttrMapValue(isNumber, desc='Padding on right of drawing'),
967        topPadding = AttrMapValue(isNumber, desc='Padding at top of drawing'),
968        bottomPadding = AttrMapValue(isNumber, desc='Padding at bottom of drawing'),
969        )
970
971    def __init__(self):
972        Pie.__init__(self)
973        self.x = 0
974        self.y = 0
975        self.height = 100
976        self.width = 100
977        self.data = [38.4, 20.7, 18.9, 15.4, 6.6]
978        self.labels = None
979        self.direction = 'clockwise'
980        PCMYKColor, black = colors.PCMYKColor, colors.black
981        self.pieAndLegend_colors = [PCMYKColor(11,11,72,0,spotName='PANTONE 458 CV'),
982                                    PCMYKColor(100,65,0,30,spotName='PANTONE 288 CV'),
983                                    PCMYKColor(11,11,72,0,spotName='PANTONE 458 CV',density=75),
984                                    PCMYKColor(100,65,0,30,spotName='PANTONE 288 CV',density=75),
985                                    PCMYKColor(11,11,72,0,spotName='PANTONE 458 CV',density=50),
986                                    PCMYKColor(100,65,0,30,spotName='PANTONE 288 CV',density=50)]
987
988        #Allows us up to six 'wedges' to be coloured
989        self.slices[0].fillColor=self.pieAndLegend_colors[0]
990        self.slices[1].fillColor=self.pieAndLegend_colors[1]
991        self.slices[2].fillColor=self.pieAndLegend_colors[2]
992        self.slices[3].fillColor=self.pieAndLegend_colors[3]
993        self.slices[4].fillColor=self.pieAndLegend_colors[4]
994        self.slices[5].fillColor=self.pieAndLegend_colors[5]
995
996        self.slices.strokeWidth = 0.75
997        self.slices.strokeColor = black
998
999        legendOffset = 17
1000        self.legendNumberOffset = 51
1001        self.legendNumberFormat = '%.1f%%'
1002        self.legend_data = self.data
1003
1004        #set up the legends
1005        from reportlab.graphics.charts.legends import Legend
1006        self.legend1 = Legend()
1007        self.legend1.x = self.width+legendOffset
1008        self.legend1.y = self.height
1009        self.legend1.deltax = 5.67
1010        self.legend1.deltay = 14.17
1011        self.legend1.dxTextSpace = 11.39
1012        self.legend1.dx = 5.67
1013        self.legend1.dy = 5.67
1014        self.legend1.columnMaximum = 7
1015        self.legend1.alignment = 'right'
1016        self.legend_names = ['AAA:','AA:','A:','BBB:','NR:']
1017        for f in range(len(self.data)):
1018            self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f], self.legend_names[f]))
1019        self.legend1.fontName = "Helvetica-Bold"
1020        self.legend1.fontSize = 6
1021        self.legend1.strokeColor = black
1022        self.legend1.strokeWidth = 0.5
1023
1024        self._legend2 = Legend()
1025        self._legend2.dxTextSpace = 0
1026        self._legend2.dx = 0
1027        self._legend2.alignment = 'right'
1028        self._legend2.fontName = "Helvetica-Oblique"
1029        self._legend2.fontSize = 6
1030        self._legend2.strokeColor = self.legend1.strokeColor
1031
1032        self.leftPadding = 5
1033        self.rightPadding = 5
1034        self.topPadding = 5
1035        self.bottomPadding = 5
1036        self.drawLegend = 1
1037
1038    def draw(self):
1039        if self.drawLegend:
1040            self.legend1.colorNamePairs = []
1041            self._legend2.colorNamePairs = []
1042        for f in range(len(self.data)):
1043            if self.legend_names == None:
1044                self.slices[f].fillColor = self.pieAndLegend_colors[f]
1045                self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f], None))
1046            else:
1047                try:
1048                    self.slices[f].fillColor = self.pieAndLegend_colors[f]
1049                    self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f], self.legend_names[f]))
1050                except IndexError:
1051                    self.slices[f].fillColor = self.pieAndLegend_colors[f%len(self.pieAndLegend_colors)]
1052                    self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f%len(self.pieAndLegend_colors)], self.legend_names[f]))
1053            if self.legend_data != None:
1054                ldf = self.legend_data[f]
1055                lNF = self.legendNumberFormat
1056                if ldf is None or lNF is None:
1057                    pass
1058                elif isinstance(lNF,str):
1059                    ldf = lNF % ldf
1060                elif hasattr(lNF,'__call__'):
1061                    ldf = lNF(ldf)
1062                else:
1063                    raise ValueError("Unknown formatter type %s, expected string or function" % ascii(self.legendNumberFormat))
1064                self._legend2.colorNamePairs.append((None,ldf))
1065        p = Pie.draw(self)
1066        if self.drawLegend:
1067            p.add(self.legend1)
1068            #hide from user - keeps both sides lined up!
1069            self._legend2.x = self.legend1.x+self.legendNumberOffset
1070            self._legend2.y = self.legend1.y
1071            self._legend2.deltax = self.legend1.deltax
1072            self._legend2.deltay = self.legend1.deltay
1073            self._legend2.dy = self.legend1.dy
1074            self._legend2.columnMaximum = self.legend1.columnMaximum
1075            p.add(self._legend2)
1076        p.shift(self.leftPadding, self.bottomPadding)
1077        return p
1078
1079    def _getDrawingDimensions(self):
1080        tx = self.rightPadding
1081        if self.drawLegend:
1082            tx += self.legend1.x+self.legendNumberOffset #self._legend2.x
1083            tx += self._legend2._calculateMaxWidth(self._legend2.colorNamePairs)
1084        ty = self.bottomPadding+self.height+self.topPadding
1085        return (tx,ty)
1086
1087    def demo(self, drawing=None):
1088        if not drawing:
1089            tx,ty = self._getDrawingDimensions()
1090            drawing = Drawing(tx, ty)
1091        drawing.add(self.draw())
1092        return drawing
1093
1094from reportlab.graphics.charts.utils3d import _getShaded, _2rad, _360, _pi_2, _2pi, _180_pi
1095class Wedge3dProperties(PropHolder):
1096    """This holds descriptive information about the wedges in a pie chart.
1097
1098    It is not to be confused with the 'wedge itself'; this just holds
1099    a recipe for how to format one, and does not allow you to hack the
1100    angles.  It can format a genuine Wedge object for you with its
1101    format method.
1102    """
1103    _attrMap = AttrMap(
1104        fillColor = AttrMapValue(isColorOrNone,desc=''),
1105        fillColorShaded = AttrMapValue(isColorOrNone,desc=''),
1106        fontColor = AttrMapValue(isColorOrNone,desc=''),
1107        fontName = AttrMapValue(isString,desc=''),
1108        fontSize = AttrMapValue(isNumber,desc=''),
1109        label_angle = AttrMapValue(isNumber,desc=''),
1110        label_bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'),
1111        label_boxAnchor = AttrMapValue(isBoxAnchor,desc=''),
1112        label_boxFillColor = AttrMapValue(isColorOrNone,desc=''),
1113        label_boxStrokeColor = AttrMapValue(isColorOrNone,desc=''),
1114        label_boxStrokeWidth = AttrMapValue(isNumber,desc=''),
1115        label_dx = AttrMapValue(isNumber,desc=''),
1116        label_dy = AttrMapValue(isNumber,desc=''),
1117        label_height = AttrMapValue(isNumberOrNone,desc=''),
1118        label_leading = AttrMapValue(isNumberOrNone,desc=''),
1119        label_leftPadding = AttrMapValue(isNumber,'padding at left of box'),
1120        label_maxWidth = AttrMapValue(isNumberOrNone,desc=''),
1121        label_rightPadding = AttrMapValue(isNumber,'padding at right of box'),
1122        label_simple_pointer = AttrMapValue(isBoolean,'set to True for simple pointers'),
1123        label_strokeColor = AttrMapValue(isColorOrNone,desc=''),
1124        label_strokeWidth = AttrMapValue(isNumber,desc=''),
1125        label_text = AttrMapValue(isStringOrNone,desc=''),
1126        label_textAnchor = AttrMapValue(isTextAnchor,desc=''),
1127        label_topPadding = AttrMapValue(isNumber,'padding at top of box'),
1128        label_visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
1129        label_width = AttrMapValue(isNumberOrNone,desc=''),
1130        labelRadius = AttrMapValue(isNumber,desc=''),
1131        popout = AttrMapValue(isNumber,desc=''),
1132        shading = AttrMapValue(isNumber,desc=''),
1133        strokeColor = AttrMapValue(isColorOrNone,desc=''),
1134        strokeColorShaded = AttrMapValue(isColorOrNone,desc=''),
1135        strokeDashArray = AttrMapValue(isListOfNumbersOrNone,desc=''),
1136        strokeWidth = AttrMapValue(isNumber,desc=''),
1137        visible = AttrMapValue(isBoolean,'set to false to skip displaying'),
1138        )
1139
1140    def __init__(self):
1141        self.strokeWidth = 0
1142        self.shading = 0.3
1143        self.visible = 1
1144        self.strokeColorShaded = self.fillColorShaded = self.fillColor = None
1145        self.strokeColor = STATE_DEFAULTS["strokeColor"]
1146        self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"]
1147        self.popout = 0
1148        self.fontName = STATE_DEFAULTS["fontName"]
1149        self.fontSize = STATE_DEFAULTS["fontSize"]
1150        self.fontColor = STATE_DEFAULTS["fillColor"]
1151        self.labelRadius = 1.2
1152        self.label_dx = self.label_dy = self.label_angle = 0
1153        self.label_text = None
1154        self.label_topPadding = self.label_leftPadding = self.label_rightPadding = self.label_bottomPadding = 0
1155        self.label_boxAnchor = 'autox'
1156        self.label_boxStrokeColor = None    #boxStroke
1157        self.label_boxStrokeWidth = 0.5 #boxStrokeWidth
1158        self.label_boxFillColor = None
1159        self.label_strokeColor = None
1160        self.label_strokeWidth = 0.1
1161        self.label_leading =    self.label_width = self.label_maxWidth = self.label_height = None
1162        self.label_textAnchor = 'start'
1163        self.label_visible = 1
1164        self.label_simple_pointer = 0
1165
1166class _SL3D:
1167    def __init__(self,lo,hi):
1168        if lo<0:
1169            lo += 360
1170            hi += 360
1171        self.lo = lo
1172        self.hi = hi
1173        self.mid = (lo+hi)*0.5
1174        self.not360 = abs(hi-lo) < _ANGLEHI
1175
1176    def __str__(self):
1177        return '_SL3D(%.2f,%.2f)' % (self.lo,self.hi)
1178
1179def _keyS3D(a,b):
1180    return -cmp(a[0],b[0])
1181_keyS3D = functools.cmp_to_key(_keyS3D)
1182
1183_270r = _2rad(270)
1184class Pie3d(Pie):
1185    _attrMap = AttrMap(BASE=Pie,
1186        perspective = AttrMapValue(isNumber, desc='A flattening parameter.'),
1187        depth_3d = AttrMapValue(isNumber, desc='depth of the pie.'),
1188        angle_3d = AttrMapValue(isNumber, desc='The view angle.'),
1189        )
1190    perspective = 70
1191    depth_3d = 25
1192    angle_3d = 180
1193
1194    def _popout(self,i):
1195        return self._sl3d[i].not360 and self.slices[i].popout or 0
1196
1197    def CX(self, i,d ):
1198        return self._cx+(d and self._xdepth_3d or 0)+self._popout(i)*cos(_2rad(self._sl3d[i].mid))
1199    def CY(self,i,d):
1200        return self._cy+(d and self._ydepth_3d or 0)+self._popout(i)*sin(_2rad(self._sl3d[i].mid))
1201    def OX(self,i,o,d):
1202        return self.CX(i,d)+self._radiusx*cos(_2rad(o))
1203    def OY(self,i,o,d):
1204        return self.CY(i,d)+self._radiusy*sin(_2rad(o))
1205
1206    def rad_dist(self,a):
1207        _3dva = self._3dva
1208        return min(abs(a-_3dva),abs(a-_3dva+360))
1209
1210    def __init__(self):
1211        Pie.__init__(self)
1212        self.slices = TypedPropertyCollection(Wedge3dProperties)
1213        self.slices[0].fillColor = colors.darkcyan
1214        self.slices[1].fillColor = colors.blueviolet
1215        self.slices[2].fillColor = colors.blue
1216        self.slices[3].fillColor = colors.cyan
1217        self.slices[4].fillColor = colors.azure
1218        self.slices[5].fillColor = colors.crimson
1219        self.slices[6].fillColor = colors.darkviolet
1220        self.xradius = self.yradius = None
1221        self.width = 300
1222        self.height = 200
1223        self.data = [12.50,20.10,2.00,22.00,5.00,18.00,13.00]
1224
1225    def _fillSide(self,L,i,angle,strokeColor,strokeWidth,fillColor):
1226        rd = self.rad_dist(angle)
1227        if rd<self.rad_dist(self._sl3d[i].mid):
1228            p = [self.CX(i,0),self.CY(i,0),
1229                self.CX(i,1),self.CY(i,1),
1230                self.OX(i,angle,1),self.OY(i,angle,1),
1231                self.OX(i,angle,0),self.OY(i,angle,0)]
1232            L.append((rd,Polygon(p, strokeColor=strokeColor, fillColor=fillColor,strokeWidth=strokeWidth,strokeLineJoin=1)))
1233
1234    def draw(self):
1235        slices = self.slices
1236        _3d_angle = self.angle_3d
1237        _3dva = self._3dva = _360(_3d_angle+90)
1238        a0 = _2rad(_3dva)
1239        depth_3d = self.depth_3d
1240        self._xdepth_3d = cos(a0)*depth_3d
1241        self._ydepth_3d = sin(a0)*depth_3d
1242        self._cx = self.x+self.width/2.0
1243        self._cy = self.y+(self.height - self._ydepth_3d)/2.0
1244        radiusx = radiusy = self._cx-self.x
1245        if self.xradius: radiusx = self.xradius
1246        if self.yradius: radiusy = self.yradius
1247        self._radiusx = radiusx
1248        self._radiusy = radiusy = (1.0 - self.perspective/100.0)*radiusy
1249        data = self.normalizeData()
1250        sum = self._sum
1251
1252        CX = self.CX
1253        CY = self.CY
1254        OX = self.OX
1255        OY = self.OY
1256        rad_dist = self.rad_dist
1257        _fillSide = self._fillSide
1258        self._seriesCount = n = len(data)
1259        _sl3d = self._sl3d = []
1260        g = Group()
1261        last = _360(self.startAngle)
1262        a0 = self.direction=='clockwise' and -1 or 1
1263        for v in data:
1264            v *= a0
1265            angle1, angle0 = last, v+last
1266            last = angle0
1267            if a0>0: angle0, angle1 = angle1, angle0
1268            _sl3d.append(_SL3D(angle0,angle1))
1269
1270        labels = _fixLabels(self.labels,n)
1271        a0 = _3d_angle
1272        a1 = _3d_angle+180
1273        T = []
1274        S = []
1275        L = []
1276
1277        class WedgeLabel3d(WedgeLabel):
1278            _ydepth_3d = self._ydepth_3d
1279            def _checkDXY(self,ba):
1280                if ba[0]=='n':
1281                    if not hasattr(self,'_ody'):
1282                        self._ody = self.dy
1283                        self.dy = -self._ody + self._ydepth_3d
1284
1285        checkLabelOverlap = self.checkLabelOverlap
1286
1287        for i in range(n):
1288            style = slices[i]
1289            if not style.visible: continue
1290            sl = _sl3d[i]
1291            lo = angle0 = sl.lo
1292            hi = angle1 = sl.hi
1293            aa = abs(hi-lo)
1294            if aa<_ANGLELO: continue
1295            fillColor = _getShaded(style.fillColor,style.fillColorShaded,style.shading)
1296            strokeColor = _getShaded(style.strokeColor,style.strokeColorShaded,style.shading) or fillColor
1297            strokeWidth = style.strokeWidth
1298            cx0 = CX(i,0)
1299            cy0 = CY(i,0)
1300            cx1 = CX(i,1)
1301            cy1 = CY(i,1)
1302            if depth_3d:
1303                #background shaded pie bottom
1304                g.add(Wedge(cx1,cy1,radiusx, lo, hi,yradius=radiusy,
1305                                strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor,
1306                                strokeLineJoin=1))
1307                #connect to top
1308                if lo < a0 < hi: angle0 = a0
1309                if lo < a1 < hi: angle1 = a1
1310                p = ArcPath(strokeColor=strokeColor, fillColor=fillColor,strokeWidth=strokeWidth,strokeLineJoin=1)
1311                p.addArc(cx1,cy1,radiusx,angle0,angle1,yradius=radiusy,moveTo=1)
1312                p.lineTo(OX(i,angle1,0),OY(i,angle1,0))
1313                p.addArc(cx0,cy0,radiusx,angle0,angle1,yradius=radiusy,reverse=1)
1314                p.closePath()
1315                if angle0<=_3dva and angle1>=_3dva:
1316                    rd = 0
1317                else:
1318                    rd = min(rad_dist(angle0),rad_dist(angle1))
1319                S.append((rd,p))
1320                _fillSide(S,i,lo,strokeColor,strokeWidth,fillColor)
1321                _fillSide(S,i,hi,strokeColor,strokeWidth,fillColor)
1322
1323            #bright shaded top
1324            fillColor = style.fillColor
1325            strokeColor = style.strokeColor or fillColor
1326            T.append(Wedge(cx0,cy0,radiusx,lo,hi,yradius=radiusy,
1327                            strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor,strokeLineJoin=1))
1328            if aa>=_ANGLEHI:
1329                theWedge = Ellipse(cx0, cy0, radiusx, radiusy,
1330                            strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor,strokeLineJoin=1)
1331            else:
1332                theWedge = Wedge(cx0,cy0,radiusx,lo,hi,yradius=radiusy,
1333                            strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor,strokeLineJoin=1)
1334            T.append(theWedge)
1335
1336            text = labels[i]
1337            if style.label_visible and text:
1338                rat = style.labelRadius
1339                self._radiusx *= rat
1340                self._radiusy *= rat
1341                mid = sl.mid
1342                labelX = OX(i,mid,0)
1343                labelY = OY(i,mid,0)
1344                l=_addWedgeLabel(self,text,mid,labelX,labelY,style,labelClass=WedgeLabel3d)
1345                L.append(l)
1346                if checkLabelOverlap:
1347                    l._origdata = { 'x': labelX, 'y':labelY, 'angle': mid,
1348                                    'rx': self._radiusx, 'ry':self._radiusy, 'cx':CX(i,0), 'cy':CY(i,0),
1349                                    'bounds': l.getBounds(),
1350                                    }
1351                self._radiusx = radiusx
1352                self._radiusy = radiusy
1353
1354        S.sort(key=_keyS3D)
1355        if checkLabelOverlap and L:
1356            fixLabelOverlaps(L,self.sideLabels)
1357        for x in ([s[1] for s in S]+T+L):
1358            g.add(x)
1359        return g
1360
1361    def demo(self):
1362        d = Drawing(200, 100)
1363
1364        pc = Pie()
1365        pc.x = 50
1366        pc.y = 10
1367        pc.width = 100
1368        pc.height = 80
1369        pc.data = [10,20,30,40,50,60]
1370        pc.labels = ['a','b','c','d','e','f']
1371
1372        pc.slices.strokeWidth=0.5
1373        pc.slices[3].popout = 10
1374        pc.slices[3].strokeWidth = 2
1375        pc.slices[3].strokeDashArray = [2,2]
1376        pc.slices[3].labelRadius = 1.75
1377        pc.slices[3].fontColor = colors.red
1378        pc.slices[0].fillColor = colors.darkcyan
1379        pc.slices[1].fillColor = colors.blueviolet
1380        pc.slices[2].fillColor = colors.blue
1381        pc.slices[3].fillColor = colors.cyan
1382        pc.slices[4].fillColor = colors.aquamarine
1383        pc.slices[5].fillColor = colors.cadetblue
1384        pc.slices[6].fillColor = colors.lightcoral
1385        self.slices[1].visible = 0
1386        self.slices[3].visible = 1
1387        self.slices[4].visible = 1
1388        self.slices[5].visible = 1
1389        self.slices[6].visible = 0
1390
1391        d.add(pc)
1392        return d
1393
1394
1395def sample0a():
1396    "Make a degenerated pie chart with only one slice."
1397
1398    d = Drawing(400, 200)
1399
1400    pc = Pie()
1401    pc.x = 150
1402    pc.y = 50
1403    pc.data = [10]
1404    pc.labels = ['a']
1405    pc.slices.strokeWidth=1#0.5
1406
1407    d.add(pc)
1408
1409    return d
1410
1411
1412def sample0b():
1413    "Make a degenerated pie chart with only one slice."
1414
1415    d = Drawing(400, 200)
1416
1417    pc = Pie()
1418    pc.x = 150
1419    pc.y = 50
1420    pc.width = 120
1421    pc.height = 100
1422    pc.data = [10]
1423    pc.labels = ['a']
1424    pc.slices.strokeWidth=1#0.5
1425
1426    d.add(pc)
1427
1428    return d
1429
1430
1431def sample1():
1432    "Make a typical pie chart with with one slice treated in a special way."
1433
1434    d = Drawing(400, 200)
1435
1436    pc = Pie()
1437    pc.x = 150
1438    pc.y = 50
1439    pc.data = [10, 20, 30, 40, 50, 60]
1440    pc.labels = ['a', 'b', 'c', 'd', 'e', 'f']
1441
1442    pc.slices.strokeWidth=1#0.5
1443    pc.slices[3].popout = 20
1444    pc.slices[3].strokeWidth = 2
1445    pc.slices[3].strokeDashArray = [2,2]
1446    pc.slices[3].labelRadius = 1.75
1447    pc.slices[3].fontColor = colors.red
1448
1449    d.add(pc)
1450
1451    return d
1452
1453
1454def sample2():
1455    "Make a pie chart with nine slices."
1456
1457    d = Drawing(400, 200)
1458
1459    pc = Pie()
1460    pc.x = 125
1461    pc.y = 25
1462    pc.data = [0.31, 0.148, 0.108,
1463               0.076, 0.033, 0.03,
1464               0.019, 0.126, 0.15]
1465    pc.labels = ['1', '2', '3', '4', '5', '6', '7', '8', 'X']
1466
1467    pc.width = 150
1468    pc.height = 150
1469    pc.slices.strokeWidth=1#0.5
1470
1471    pc.slices[0].fillColor = colors.steelblue
1472    pc.slices[1].fillColor = colors.thistle
1473    pc.slices[2].fillColor = colors.cornflower
1474    pc.slices[3].fillColor = colors.lightsteelblue
1475    pc.slices[4].fillColor = colors.aquamarine
1476    pc.slices[5].fillColor = colors.cadetblue
1477    pc.slices[6].fillColor = colors.lightcoral
1478    pc.slices[7].fillColor = colors.tan
1479    pc.slices[8].fillColor = colors.darkseagreen
1480
1481    d.add(pc)
1482
1483    return d
1484
1485
1486def sample3():
1487    "Make a pie chart with a very slim slice."
1488
1489    d = Drawing(400, 200)
1490
1491    pc = Pie()
1492    pc.x = 125
1493    pc.y = 25
1494
1495    pc.data = [74, 1, 25]
1496
1497    pc.width = 150
1498    pc.height = 150
1499    pc.slices.strokeWidth=1#0.5
1500    pc.slices[0].fillColor = colors.steelblue
1501    pc.slices[1].fillColor = colors.thistle
1502    pc.slices[2].fillColor = colors.cornflower
1503
1504    d.add(pc)
1505
1506    return d
1507
1508
1509def sample4():
1510    "Make a pie chart with several very slim slices."
1511
1512    d = Drawing(400, 200)
1513
1514    pc = Pie()
1515    pc.x = 125
1516    pc.y = 25
1517
1518    pc.data = [74, 1, 1, 1, 1, 22]
1519
1520    pc.width = 150
1521    pc.height = 150
1522    pc.slices.strokeWidth=1#0.5
1523    pc.slices[0].fillColor = colors.steelblue
1524    pc.slices[1].fillColor = colors.thistle
1525    pc.slices[2].fillColor = colors.cornflower
1526    pc.slices[3].fillColor = colors.lightsteelblue
1527    pc.slices[4].fillColor = colors.aquamarine
1528    pc.slices[5].fillColor = colors.cadetblue
1529
1530    d.add(pc)
1531
1532    return d
1533
1534def sample5():
1535    "Make a pie with side labels."
1536
1537    d = Drawing(400, 200)
1538
1539    pc = Pie()
1540    pc.x = 125
1541    pc.y = 25
1542
1543    pc.data = [7, 1, 1, 1, 1, 2]
1544    pc.labels = ['example1', 'example2', 'example3', 'example4', 'example5', 'example6']
1545    pc.sideLabels = 1
1546
1547    pc.width = 150
1548    pc.height = 150
1549    pc.slices.strokeWidth=1#0.5
1550    pc.slices[0].fillColor = colors.steelblue
1551    pc.slices[1].fillColor = colors.thistle
1552    pc.slices[2].fillColor = colors.cornflower
1553    pc.slices[3].fillColor = colors.lightsteelblue
1554    pc.slices[4].fillColor = colors.aquamarine
1555    pc.slices[5].fillColor = colors.cadetblue
1556
1557    d.add(pc)
1558
1559    return d
1560
1561def sample6():
1562
1563    "Illustrates the pie moving to leave space for the left labels"
1564
1565    d = Drawing(400, 200)
1566
1567    pc = Pie()
1568    "The x value of the pie chart is 0"
1569    pc.x = 0
1570    pc.y = 25
1571
1572    pc.data = [74, 1, 1, 1, 1, 22]
1573    pc.labels = ['example1', 'example2', 'example3', 'example4', 'example5', 'example6']
1574    pc.sideLabels = 1
1575
1576    pc.width = 150
1577    pc.height = 150
1578    pc.slices.strokeWidth=1#0.5
1579    pc.slices[0].fillColor = colors.steelblue
1580    pc.slices[1].fillColor = colors.thistle
1581    pc.slices[2].fillColor = colors.cornflower
1582    pc.slices[3].fillColor = colors.lightsteelblue
1583    pc.slices[4].fillColor = colors.aquamarine
1584    pc.slices[5].fillColor = colors.cadetblue
1585
1586    l = Line(0,0,0,200)
1587
1588    d.add(pc)
1589    d.add(l)
1590
1591    return d
1592
1593def sample7():
1594
1595    "Case with overlapping pointers"
1596
1597    d = Drawing(400, 200)
1598
1599    pc = Pie()
1600    pc.y = 50
1601    pc.x = 150
1602    pc.width = 100
1603    pc.height = 100
1604
1605    pc.data = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
1606    pc.labels = ['example1', 'example2', 'example3', 'example4', 'example5', 'example6', 'example7',
1607                'example8', 'example9', 'example10', 'example11', 'example12', 'example13', 'example14',
1608                'example15', 'example16', 'example17', 'example18', 'example19', 'example20', 'example21',
1609                'example22', 'example23', 'example24', 'example25', 'example26', 'example27', 'example28']
1610    pc.sideLabels = 1
1611    pc.checkLabelOverlap = 1
1612    pc.simpleLabels = 0
1613
1614
1615    pc.slices.strokeWidth=1#0.5
1616    pc.slices[0].fillColor = colors.steelblue
1617    pc.slices[1].fillColor = colors.thistle
1618    pc.slices[2].fillColor = colors.cornflower
1619    pc.slices[3].fillColor = colors.lightsteelblue
1620    pc.slices[4].fillColor = colors.aquamarine
1621    pc.slices[5].fillColor = colors.cadetblue
1622
1623    d.add(pc)
1624
1625    return d
1626
1627def sample8():
1628
1629    "Case with overlapping labels"
1630    "Labels overlap if they do not belong to adjacent pie slices due to nature of checkLabelOverlap"
1631
1632    d = Drawing(400, 200)
1633
1634    pc = Pie()
1635    pc.y = 50
1636    pc.x = 150
1637    pc.width = 100
1638    pc.height = 100
1639
1640    pc.data = [1, 1, 1, 1, 1, 30, 50, 1, 1, 1, 1, 1, 1, 40,20,10]
1641    pc.labels = ['example1', 'example2', 'example3', 'example4', 'example5', 'example6', 'example7',
1642                'example8', 'example9', 'example10', 'example11', 'example12', 'example13', 'example14',
1643                'example15', 'example16']
1644    pc.sideLabels = 1
1645    pc.checkLabelOverlap = 1
1646
1647    pc.slices.strokeWidth=1#0.5
1648    pc.slices[0].fillColor = colors.steelblue
1649    pc.slices[1].fillColor = colors.thistle
1650    pc.slices[2].fillColor = colors.cornflower
1651    pc.slices[3].fillColor = colors.lightsteelblue
1652    pc.slices[4].fillColor = colors.aquamarine
1653    pc.slices[5].fillColor = colors.cadetblue
1654
1655    d.add(pc)
1656
1657    return d
1658
1659def sample9():
1660
1661    "Case with overlapping labels"
1662    "Labels overlap if they do not belong to adjacent pies due to nature of checkLabelOverlap"
1663
1664    d = Drawing(400, 200)
1665
1666    pc = Pie()
1667    pc.x = 125
1668    pc.y = 50
1669
1670    pc.data = [41, 20, 40, 15, 20, 30, 50, 15, 25, 35, 25, 20, 30, 40, 20, 30]
1671    pc.labels = ['example1', 'example2', 'example3', 'example4', 'example5', 'example6', 'example7',
1672                'example8', 'example9', 'example10', 'example11', 'example12', 'example13', 'example14',
1673                'example15', 'example16']
1674    pc.sideLabels = 1
1675    pc.checkLabelOverlap = 1
1676
1677    pc.width = 100
1678    pc.height = 100
1679    pc.slices.strokeWidth=1#0.5
1680    pc.slices[0].fillColor = colors.steelblue
1681    pc.slices[1].fillColor = colors.thistle
1682    pc.slices[2].fillColor = colors.cornflower
1683    pc.slices[3].fillColor = colors.lightsteelblue
1684    pc.slices[4].fillColor = colors.aquamarine
1685    pc.slices[5].fillColor = colors.cadetblue
1686
1687    d.add(pc)
1688
1689    return d
1690
1691if __name__=='__main__':
1692    """Normally nobody will execute this
1693
1694    It's helpful for reportlab developers to put a 'main' block in to execute
1695    the most recently edited feature.
1696    """
1697    import sys
1698    from reportlab.graphics import renderPDF
1699    argv = sys.argv[1:] or ['7']
1700    for a in argv:
1701        name = a if a.startswith('sample') else 'sample%s' % a
1702        drawing = globals()[name]()
1703        renderPDF.drawToFile(drawing, '%s.pdf' % name)
1704
1705
1706
1707