"Fossies" - the Fresh Open Source Software Archive

Member "reportlab-3.5.32/src/reportlab/graphics/charts/piecharts.py" (1 Oct 2019, 64437 Bytes) of package /linux/privat/reportlab-3.5.32.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "piecharts.py" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 3.5.26_vs_3.5.28.

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