#Copyright ReportLab Europe Ltd. 2000-2004 #see license.txt for license details #history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/piecharts.py # experimental pie chart script. Two types of pie - one is a monolithic #widget with all top-level properties, the other delegates most stuff to #a wedges collection whic lets you customize the group or every individual #wedge. """Basic Pie Chart class. This permits you to customize and pop out individual wedges; supports elliptical and circular pies. """ __version__=''' $Id$ ''' import copy from math import sin, cos, pi from reportlab.lib import colors from reportlab.lib.validators import isColor, isNumber, isListOfNumbersOrNone,\ isListOfNumbers, isColorOrNone, isString,\ isListOfStringsOrNone, OneOf, SequenceOf,\ isBoolean, isListOfColors, isNumberOrNone,\ isNoneOrListOfNoneOrStrings, isTextAnchor,\ isNoneOrListOfNoneOrNumbers, isBoxAnchor,\ isStringOrNone from reportlab.lib.attrmap import * from reportlab.pdfgen.canvas import Canvas from reportlab.graphics.shapes import Group, Drawing, Ellipse, Wedge, String, STATE_DEFAULTS, ArcPath, Polygon from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder from textlabels import Label _ANGLE2BOXANCHOR={0:'w', 45:'sw', 90:'s', 135:'se', 180:'e', 225:'ne', 270:'n', 315: 'nw', -45: 'nw'} class WedgeLabel(Label): def _checkDXY(self,ba): pass def _getBoxAnchor(self): na = (int((self._pmv%360)/45.)*45)%360 if not (na % 90): # we have a right angle case da = (self._pmv - na) % 360 if abs(da)>5: na = na + (da>0 and 45 or -45) ba = _ANGLE2BOXANCHOR[na] self._checkDXY(ba) return ba class WedgeProperties(PropHolder): """This holds descriptive information about the wedges in a pie chart. It is not to be confused with the 'wedge itself'; this just holds a recipe for how to format one, and does not allow you to hack the angles. It can format a genuine Wedge object for you with its format method. """ _attrMap = AttrMap( strokeWidth = AttrMapValue(isNumber), fillColor = AttrMapValue(isColorOrNone), strokeColor = AttrMapValue(isColorOrNone), strokeDashArray = AttrMapValue(isListOfNumbersOrNone), popout = AttrMapValue(isNumber), fontName = AttrMapValue(isString), fontSize = AttrMapValue(isNumber), fontColor = AttrMapValue(isColorOrNone), labelRadius = AttrMapValue(isNumber), label_dx = AttrMapValue(isNumber), label_dy = AttrMapValue(isNumber), label_angle = AttrMapValue(isNumber), label_boxAnchor = AttrMapValue(isBoxAnchor), label_boxStrokeColor = AttrMapValue(isColorOrNone), label_boxStrokeWidth = AttrMapValue(isNumber), label_boxFillColor = AttrMapValue(isColorOrNone), label_strokeColor = AttrMapValue(isColorOrNone), label_strokeWidth = AttrMapValue(isNumber), label_text = AttrMapValue(isStringOrNone), label_leading = AttrMapValue(isNumberOrNone), label_width = AttrMapValue(isNumberOrNone), label_maxWidth = AttrMapValue(isNumberOrNone), label_height = AttrMapValue(isNumberOrNone), label_textAnchor = AttrMapValue(isTextAnchor), label_visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"), label_topPadding = AttrMapValue(isNumber,'padding at top of box'), label_leftPadding = AttrMapValue(isNumber,'padding at left of box'), label_rightPadding = AttrMapValue(isNumber,'padding at right of box'), label_bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'), ) def __init__(self): self.strokeWidth = 0 self.fillColor = None self.strokeColor = STATE_DEFAULTS["strokeColor"] self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"] self.popout = 0 self.fontName = STATE_DEFAULTS["fontName"] self.fontSize = STATE_DEFAULTS["fontSize"] self.fontColor = STATE_DEFAULTS["fillColor"] self.labelRadius = 1.2 self.label_dx = self.label_dy = self.label_angle = 0 self.label_text = None self.label_topPadding = self.label_leftPadding = self.label_rightPadding = self.label_bottomPadding = 0 self.label_boxAnchor = 'c' self.label_boxStrokeColor = None #boxStroke self.label_boxStrokeWidth = 0.5 #boxStrokeWidth self.label_boxFillColor = None self.label_strokeColor = None self.label_strokeWidth = 0.1 self.label_leading = self.label_width = self.label_maxWidth = self.label_height = None self.label_textAnchor = 'start' self.label_visible = 1 def _addWedgeLabel(self,text,add,angle,labelX,labelY,wedgeStyle,labelClass=WedgeLabel): # now draw a label if self.simpleLabels: theLabel = String(labelX, labelY, text) theLabel.textAnchor = "middle" else: theLabel = labelClass() theLabel._pmv = angle theLabel.x = labelX theLabel.y = labelY theLabel.dx = wedgeStyle.label_dx theLabel.dy = wedgeStyle.label_dy theLabel.angle = wedgeStyle.label_angle theLabel.boxAnchor = wedgeStyle.label_boxAnchor theLabel.boxStrokeColor = wedgeStyle.label_boxStrokeColor theLabel.boxStrokeWidth = wedgeStyle.label_boxStrokeWidth theLabel.boxFillColor = wedgeStyle.label_boxFillColor theLabel.strokeColor = wedgeStyle.label_strokeColor theLabel.strokeWidth = wedgeStyle.label_strokeWidth _text = wedgeStyle.label_text if _text is None: _text = text theLabel._text = _text theLabel.leading = wedgeStyle.label_leading theLabel.width = wedgeStyle.label_width theLabel.maxWidth = wedgeStyle.label_maxWidth theLabel.height = wedgeStyle.label_height theLabel.textAnchor = wedgeStyle.label_textAnchor theLabel.visible = wedgeStyle.label_visible theLabel.topPadding = wedgeStyle.label_topPadding theLabel.leftPadding = wedgeStyle.label_leftPadding theLabel.rightPadding = wedgeStyle.label_rightPadding theLabel.bottomPadding = wedgeStyle.label_bottomPadding theLabel.fontSize = wedgeStyle.fontSize theLabel.fontName = wedgeStyle.fontName theLabel.fillColor = wedgeStyle.fontColor add(theLabel) def _fixLabels(labels,n): if labels is None: labels = [''] * n else: i = n-len(labels) if i>0: labels = labels + ['']*i return labels class Pie(Widget): _attrMap = AttrMap( x = AttrMapValue(isNumber, desc='X position of the chart within its container.'), y = AttrMapValue(isNumber, desc='Y position of the chart within its container.'), width = AttrMapValue(isNumber, desc='width of pie bounding box. Need not be same as width.'), height = AttrMapValue(isNumber, desc='height of pie bounding box. Need not be same as height.'), data = AttrMapValue(isListOfNumbers, desc='list of numbers defining wedge sizes; need not sum to 1'), labels = AttrMapValue(isListOfStringsOrNone, desc="optional list of labels to use for each data point"), startAngle = AttrMapValue(isNumber, desc="angle of first slice; like the compass, 0 is due North"), direction = AttrMapValue( OneOf('clockwise', 'anticlockwise'), desc="'clockwise' or 'anticlockwise'"), slices = AttrMapValue(None, desc="collection of wedge descriptor objects"), simpleLabels = AttrMapValue(isBoolean, desc="If true(default) use String not super duper WedgeLabel"), other_threshold = AttrMapValue(isNumber, desc='A value for doing thresh holding, not used yet.'), ) other_threshold=None def __init__(self): self.x = 0 self.y = 0 self.width = 100 self.height = 100 self.data = [1] self.labels = None # or list of strings self.startAngle = 90 self.direction = "clockwise" self.simpleLabels = 1 self.slices = TypedPropertyCollection(WedgeProperties) self.slices[0].fillColor = colors.darkcyan self.slices[1].fillColor = colors.blueviolet self.slices[2].fillColor = colors.blue self.slices[3].fillColor = colors.cyan def demo(self): d = Drawing(200, 100) pc = Pie() pc.x = 50 pc.y = 10 pc.width = 100 pc.height = 80 pc.data = [10,20,30,40,50,60] pc.labels = ['a','b','c','d','e','f'] pc.slices.strokeWidth=0.5 pc.slices[3].popout = 10 pc.slices[3].strokeWidth = 2 pc.slices[3].strokeDashArray = [2,2] pc.slices[3].labelRadius = 1.75 pc.slices[3].fontColor = colors.red pc.slices[0].fillColor = colors.darkcyan pc.slices[1].fillColor = colors.blueviolet pc.slices[2].fillColor = colors.blue pc.slices[3].fillColor = colors.cyan pc.slices[4].fillColor = colors.aquamarine pc.slices[5].fillColor = colors.cadetblue pc.slices[6].fillColor = colors.lightcoral d.add(pc) return d def normalizeData(self): from operator import add data = self.data self._sum = sum = float(reduce(add,data,0)) return abs(sum)>=1e-8 and map(lambda x,f=360./sum: f*x, data) or len(data)*[0] def makeWedges(self): # normalize slice data normData = self.normalizeData() n = len(normData) labels = _fixLabels(self.labels,n) xradius = self.width/2.0 yradius = self.height/2.0 centerx = self.x + xradius centery = self.y + yradius if self.direction == "anticlockwise": whichWay = 1 else: whichWay = -1 g = Group() i = 0 styleCount = len(self.slices) startAngle = self.startAngle #% 360 for angle in normData: endAngle = (startAngle + (angle * whichWay)) #% 360 if abs(startAngle-endAngle)>=1e-5: if startAngle < endAngle: a1 = startAngle a2 = endAngle else: a1 = endAngle a2 = startAngle #if we didn't use %stylecount here we'd end up with the later wedges #all having the default style wedgeStyle = self.slices[i%styleCount] # is it a popout? cx, cy = centerx, centery if wedgeStyle.popout <> 0: # pop out the wedge averageAngle = (a1+a2)/2.0 aveAngleRadians = averageAngle * pi/180.0 popdistance = wedgeStyle.popout cx = centerx + popdistance * cos(aveAngleRadians) cy = centery + popdistance * sin(aveAngleRadians) if n > 1: theWedge = Wedge(cx, cy, xradius, a1, a2, yradius=yradius) elif n==1: theWedge = Ellipse(cx, cy, xradius, yradius) theWedge.fillColor = wedgeStyle.fillColor theWedge.strokeColor = wedgeStyle.strokeColor theWedge.strokeWidth = wedgeStyle.strokeWidth theWedge.strokeDashArray = wedgeStyle.strokeDashArray g.add(theWedge) text = labels[i] if text: averageAngle = (a1+a2)/2.0 aveAngleRadians = averageAngle*pi/180.0 labelRadius = wedgeStyle.labelRadius labelX = cx + (0.5 * self.width * cos(aveAngleRadians) * labelRadius) labelY = cy + (0.5 * self.height * sin(aveAngleRadians) * labelRadius) _addWedgeLabel(self,text,g.add,averageAngle,labelX,labelY,wedgeStyle) startAngle = endAngle i = i + 1 return g def draw(self): g = Group() g.add(self.makeWedges()) return g class LegendedPie(Pie): """Pie with a two part legend (one editable with swatches, one hidden without swatches).""" _attrMap = AttrMap(BASE=Pie, drawLegend = AttrMapValue(isBoolean, desc="If true then create and draw legend"), legend1 = AttrMapValue(None, desc="Handle to legend for pie"), legendNumberFormat = AttrMapValue(None, desc="Formatting routine for number on right hand side of legend."), legendNumberOffset = AttrMapValue(isNumber, desc="Horizontal space between legend and numbers on r/hand side"), pieAndLegend_colors = AttrMapValue(isListOfColors, desc="Colours used for both swatches and pie"), legend_names = AttrMapValue(isNoneOrListOfNoneOrStrings, desc="Names used in legend (or None)"), legend_data = AttrMapValue(isNoneOrListOfNoneOrNumbers, desc="Numbers used on r/hand side of legend (or None)"), leftPadding = AttrMapValue(isNumber, desc='Padding on left of drawing'), rightPadding = AttrMapValue(isNumber, desc='Padding on right of drawing'), topPadding = AttrMapValue(isNumber, desc='Padding at top of drawing'), bottomPadding = AttrMapValue(isNumber, desc='Padding at bottom of drawing'), ) def __init__(self): Pie.__init__(self) self.x = 0 self.y = 0 self.height = 100 self.width = 100 self.data = [38.4, 20.7, 18.9, 15.4, 6.6] self.labels = None self.direction = 'clockwise' PCMYKColor, black = colors.PCMYKColor, colors.black self.pieAndLegend_colors = [PCMYKColor(11,11,72,0,spotName='PANTONE 458 CV'), PCMYKColor(100,65,0,30,spotName='PANTONE 288 CV'), PCMYKColor(11,11,72,0,spotName='PANTONE 458 CV',density=75), PCMYKColor(100,65,0,30,spotName='PANTONE 288 CV',density=75), PCMYKColor(11,11,72,0,spotName='PANTONE 458 CV',density=50), PCMYKColor(100,65,0,30,spotName='PANTONE 288 CV',density=50)] #Allows us up to six 'wedges' to be coloured self.slices[0].fillColor=self.pieAndLegend_colors[0] self.slices[1].fillColor=self.pieAndLegend_colors[1] self.slices[2].fillColor=self.pieAndLegend_colors[2] self.slices[3].fillColor=self.pieAndLegend_colors[3] self.slices[4].fillColor=self.pieAndLegend_colors[4] self.slices[5].fillColor=self.pieAndLegend_colors[5] self.slices.strokeWidth = 0.75 self.slices.strokeColor = black legendOffset = 17 self.legendNumberOffset = 51 self.legendNumberFormat = '%.1f%%' self.legend_data = self.data #set up the legends from reportlab.graphics.charts.legends import Legend self.legend1 = Legend() self.legend1.x = self.width+legendOffset self.legend1.y = self.height self.legend1.deltax = 5.67 self.legend1.deltay = 14.17 self.legend1.dxTextSpace = 11.39 self.legend1.dx = 5.67 self.legend1.dy = 5.67 self.legend1.columnMaximum = 7 self.legend1.alignment = 'right' self.legend_names = ['AAA:','AA:','A:','BBB:','NR:'] for f in range(0,len(self.data)): self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f], self.legend_names[f])) self.legend1.fontName = "Helvetica-Bold" self.legend1.fontSize = 6 self.legend1.strokeColor = black self.legend1.strokeWidth = 0.5 self._legend2 = Legend() self._legend2.dxTextSpace = 0 self._legend2.dx = 0 self._legend2.alignment = 'right' self._legend2.fontName = "Helvetica-Oblique" self._legend2.fontSize = 6 self._legend2.strokeColor = self.legend1.strokeColor self.leftPadding = 5 self.rightPadding = 5 self.topPadding = 5 self.bottomPadding = 5 self.drawLegend = 1 def draw(self): if self.drawLegend: self.legend1.colorNamePairs = [] self._legend2.colorNamePairs = [] for f in range(0,len(self.data)): if self.legend_names == None: self.slices[f].fillColor = self.pieAndLegend_colors[f] self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f], None)) else: try: self.slices[f].fillColor = self.pieAndLegend_colors[f] self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f], self.legend_names[f])) except IndexError: self.slices[f].fillColor = self.pieAndLegend_colors[f%len(self.pieAndLegend_colors)] self.legend1.colorNamePairs.append((self.pieAndLegend_colors[f%len(self.pieAndLegend_colors)], self.legend_names[f])) if self.legend_data != None: ldf = self.legend_data[f] lNF = self.legendNumberFormat from types import StringType if ldf is None or lNF is None: pass elif type(lNF) is StringType: ldf = lNF % ldf elif callable(lNF): ldf = lNF(ldf) else: p = self.legend_names[f] if self.legend_data != None: ldf = self.legend_data[f] lNF = self.legendNumberFormat if ldf is None or lNF is None: pass elif type(lNF) is StringType: ldf = lNF % ldf elif callable(lNF): ldf = lNF(ldf) else: msg = "Unknown formatter type %s, expected string or function" % self.legendNumberFormat raise Exception, msg self._legend2.colorNamePairs.append((None,ldf)) p = Pie.draw(self) if self.drawLegend: p.add(self.legend1) #hide from user - keeps both sides lined up! self._legend2.x = self.legend1.x+self.legendNumberOffset self._legend2.y = self.legend1.y self._legend2.deltax = self.legend1.deltax self._legend2.deltay = self.legend1.deltay self._legend2.dy = self.legend1.dy self._legend2.columnMaximum = self.legend1.columnMaximum p.add(self._legend2) p.shift(self.leftPadding, self.bottomPadding) return p def _getDrawingDimensions(self): tx = self.rightPadding if self.drawLegend: tx = tx+self.legend1.x+self.legendNumberOffset #self._legend2.x tx = tx + self._legend2._calculateMaxWidth(self._legend2.colorNamePairs) ty = self.bottomPadding+self.height+self.topPadding return (tx,ty) def demo(self, drawing=None): if not drawing: tx,ty = self._getDrawingDimensions() drawing = Drawing(tx, ty) drawing.add(self.draw()) return drawing from utils3d import _getShaded, _2rad, _360, _pi_2, _2pi class Wedge3dProperties(PropHolder): """This holds descriptive information about the wedges in a pie chart. It is not to be confused with the 'wedge itself'; this just holds a recipe for how to format one, and does not allow you to hack the angles. It can format a genuine Wedge object for you with its format method. """ _attrMap = AttrMap( fillColor = AttrMapValue(isColorOrNone), fillColorShaded = AttrMapValue(isColorOrNone), fontColor = AttrMapValue(isColorOrNone), fontName = AttrMapValue(isString), fontSize = AttrMapValue(isNumber), label_angle = AttrMapValue(isNumber), label_bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'), label_boxAnchor = AttrMapValue(isBoxAnchor), label_boxFillColor = AttrMapValue(isColorOrNone), label_boxStrokeColor = AttrMapValue(isColorOrNone), label_boxStrokeWidth = AttrMapValue(isNumber), label_dx = AttrMapValue(isNumber), label_dy = AttrMapValue(isNumber), label_height = AttrMapValue(isNumberOrNone), label_leading = AttrMapValue(isNumberOrNone), label_leftPadding = AttrMapValue(isNumber,'padding at left of box'), label_maxWidth = AttrMapValue(isNumberOrNone), label_rightPadding = AttrMapValue(isNumber,'padding at right of box'), label_strokeColor = AttrMapValue(isColorOrNone), label_strokeWidth = AttrMapValue(isNumber), label_text = AttrMapValue(isStringOrNone), label_textAnchor = AttrMapValue(isTextAnchor), label_topPadding = AttrMapValue(isNumber,'padding at top of box'), label_visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"), label_width = AttrMapValue(isNumberOrNone), labelRadius = AttrMapValue(isNumber), popout = AttrMapValue(isNumber), shading = AttrMapValue(isNumber), strokeColor = AttrMapValue(isColorOrNone), strokeColorShaded = AttrMapValue(isColorOrNone), strokeDashArray = AttrMapValue(isListOfNumbersOrNone), strokeWidth = AttrMapValue(isNumber), visible = AttrMapValue(isBoolean,'set to false to skip displaying'), ) def __init__(self): self.strokeWidth = 0 self.shading = 0.3 self.visible = 1 self.strokeColorShaded = self.fillColorShaded = self.fillColor = None self.strokeColor = STATE_DEFAULTS["strokeColor"] self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"] self.popout = 0 self.fontName = STATE_DEFAULTS["fontName"] self.fontSize = STATE_DEFAULTS["fontSize"] self.fontColor = STATE_DEFAULTS["fillColor"] self.labelRadius = 1.2 self.label_dx = self.label_dy = self.label_angle = 0 self.label_text = None self.label_topPadding = self.label_leftPadding = self.label_rightPadding = self.label_bottomPadding = 0 self.label_boxAnchor = 'c' self.label_boxStrokeColor = None #boxStroke self.label_boxStrokeWidth = 0.5 #boxStrokeWidth self.label_boxFillColor = None self.label_strokeColor = None self.label_strokeWidth = 0.1 self.label_leading = self.label_width = self.label_maxWidth = self.label_height = None self.label_textAnchor = 'start' self.label_visible = 1 class _SL3D: def __init__(self,lo,hi): if lo<0: lo += 360 hi += 360 self.lo = lo self.hi = hi self.mid = (lo+hi)*0.5 def __str__(self): return '_SL3D(%.2f,%.2f)' % (self.lo,self.hi) _270r = _2rad(270) class Pie3d(Pie): _attrMap = AttrMap(BASE=Pie, perspective = AttrMapValue(isNumber, desc='A flattening parameter.'), depth_3d = AttrMapValue(isNumber, desc='depth of the pie.'), angle_3d = AttrMapValue(isNumber, desc='The view angle.'), ) perspective = 70 depth_3d = 25 angle_3d = 180 def _popout(self,i): return self.slices[i].popout or 0 def CX(self, i,d ): return self._cx+(d and self._xdepth_3d or 0)+self._popout(i)*cos(_2rad(self._sl3d[i].mid)) def CY(self,i,d): return self._cy+(d and self._ydepth_3d or 0)+self._popout(i)*sin(_2rad(self._sl3d[i].mid)) def OX(self,i,o,d): return self.CX(i,d)+self._radiusx*cos(_2rad(o)) def OY(self,i,o,d): return self.CY(i,d)+self._radiusy*sin(_2rad(o)) def rad_dist(self,a): _3dva = self._3dva return min(abs(a-_3dva),abs(a-_3dva+360)) def __init__(self): self.x = 0 self.y = 0 self.width = 300 self.height = 200 self.data = [12.50,20.10,2.00,22.00,5.00,18.00,13.00] self.labels = None # or list of strings self.startAngle = 90 self.direction = "clockwise" self.simpleLabels = 1 self.slices = TypedPropertyCollection(Wedge3dProperties) self.slices[0].fillColor = colors.darkcyan self.slices[1].fillColor = colors.blueviolet self.slices[2].fillColor = colors.blue self.slices[3].fillColor = colors.cyan self.slices[4].fillColor = colors.azure self.slices[5].fillColor = colors.crimson self.slices[6].fillColor = colors.darkviolet def _fillSide(self,L,i,angle,strokeColor,strokeWidth,fillColor): rd = self.rad_dist(angle) if rd0: angle0, angle1 = angle1, angle0 _sl3d.append(_SL3D(angle0,angle1)) #print '%d: %.2f %.2f --> %s' %(len(_sl3d)-1,angle0,angle1,_sl3d[-1]) labels = _fixLabels(self.labels,n) a0 = _3d_angle a1 = _3d_angle+180 T = [] S = [] L = [] class WedgeLabel3d(WedgeLabel): def _checkDXY(self,ba): if ba[0]=='n': if not hasattr(self,'_ody'): self._ody = self.dy self.dy = -self._ody + self._ydepth_3d WedgeLabel3d._ydepth_3d = self._ydepth_3d for i in xrange(n): style = slices[i] if not style.visible: continue sl = _sl3d[i] lo = angle0 = sl.lo hi = angle1 = sl.hi if abs(hi-lo)<=1e-7: continue fillColor = _getShaded(style.fillColor,style.fillColorShaded,style.shading) strokeColor = _getShaded(style.strokeColor,style.strokeColorShaded,style.shading) or fillColor strokeWidth = style.strokeWidth cx0 = CX(i,0) cy0 = CY(i,0) cx1 = CX(i,1) cy1 = CY(i,1) #background shaded pie bottom g.add(Wedge(cx1,cy1,radiusx, lo, hi,yradius=radiusy, strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor, strokeLineJoin=1)) #connect to top if lo < a0 < hi: angle0 = a0 if lo < a1 < hi: angle1 = a1 if 1: p = ArcPath(strokeColor=strokeColor, fillColor=fillColor,strokeWidth=strokeWidth,strokeLineJoin=1) p.addArc(cx1,cy1,radiusx,angle0,angle1,yradius=radiusy,moveTo=1) p.lineTo(OX(i,angle1,0),OY(i,angle1,0)) p.addArc(cx0,cy0,radiusx,angle0,angle1,yradius=radiusy,reverse=1) p.closePath() if angle0<=_3dva and angle1>=_3dva: rd = 0 else: rd = min(rad_dist(angle0),rad_dist(angle1)) S.append((rd,p)) _fillSide(S,i,lo,strokeColor,strokeWidth,fillColor) _fillSide(S,i,hi,strokeColor,strokeWidth,fillColor) #bright shaded top fillColor = style.fillColor strokeColor = style.strokeColor or fillColor T.append(Wedge(cx0,cy0,radiusx,lo,hi,yradius=radiusy, strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor,strokeLineJoin=1)) text = labels[i] if text: rat = style.labelRadius self._radiusx *= rat self._radiusy *= rat mid = sl.mid _addWedgeLabel(self,text,L.append,mid,OX(i,mid,0),OY(i,mid,0),style,labelClass=WedgeLabel3d) self._radiusx = radiusx self._radiusy = radiusy S.sort(lambda a,b: -cmp(a[0],b[0])) map(g.add,map(lambda x:x[1],S)+T+L) return g def demo(self): d = Drawing(200, 100) pc = Pie() pc.x = 50 pc.y = 10 pc.width = 100 pc.height = 80 pc.data = [10,20,30,40,50,60] pc.labels = ['a','b','c','d','e','f'] pc.slices.strokeWidth=0.5 pc.slices[3].popout = 10 pc.slices[3].strokeWidth = 2 pc.slices[3].strokeDashArray = [2,2] pc.slices[3].labelRadius = 1.75 pc.slices[3].fontColor = colors.red pc.slices[0].fillColor = colors.darkcyan pc.slices[1].fillColor = colors.blueviolet pc.slices[2].fillColor = colors.blue pc.slices[3].fillColor = colors.cyan pc.slices[4].fillColor = colors.aquamarine pc.slices[5].fillColor = colors.cadetblue pc.slices[6].fillColor = colors.lightcoral self.slices[1].visible = 0 self.slices[3].visible = 1 self.slices[4].visible = 1 self.slices[5].visible = 1 self.slices[6].visible = 0 d.add(pc) return d def sample0a(): "Make a degenerated pie chart with only one slice." d = Drawing(400, 200) pc = Pie() pc.x = 150 pc.y = 50 pc.data = [10] pc.labels = ['a'] pc.slices.strokeWidth=1#0.5 d.add(pc) return d def sample0b(): "Make a degenerated pie chart with only one slice." d = Drawing(400, 200) pc = Pie() pc.x = 150 pc.y = 50 pc.width = 120 pc.height = 100 pc.data = [10] pc.labels = ['a'] pc.slices.strokeWidth=1#0.5 d.add(pc) return d def sample1(): "Make a typical pie chart with with one slice treated in a special way." d = Drawing(400, 200) pc = Pie() pc.x = 150 pc.y = 50 pc.data = [10, 20, 30, 40, 50, 60] pc.labels = ['a', 'b', 'c', 'd', 'e', 'f'] pc.slices.strokeWidth=1#0.5 pc.slices[3].popout = 20 pc.slices[3].strokeWidth = 2 pc.slices[3].strokeDashArray = [2,2] pc.slices[3].labelRadius = 1.75 pc.slices[3].fontColor = colors.red d.add(pc) return d def sample2(): "Make a pie chart with nine slices." d = Drawing(400, 200) pc = Pie() pc.x = 125 pc.y = 25 pc.data = [0.31, 0.148, 0.108, 0.076, 0.033, 0.03, 0.019, 0.126, 0.15] pc.labels = ['1', '2', '3', '4', '5', '6', '7', '8', 'X'] pc.width = 150 pc.height = 150 pc.slices.strokeWidth=1#0.5 pc.slices[0].fillColor = colors.steelblue pc.slices[1].fillColor = colors.thistle pc.slices[2].fillColor = colors.cornflower pc.slices[3].fillColor = colors.lightsteelblue pc.slices[4].fillColor = colors.aquamarine pc.slices[5].fillColor = colors.cadetblue pc.slices[6].fillColor = colors.lightcoral pc.slices[7].fillColor = colors.tan pc.slices[8].fillColor = colors.darkseagreen d.add(pc) return d def sample3(): "Make a pie chart with a very slim slice." d = Drawing(400, 200) pc = Pie() pc.x = 125 pc.y = 25 pc.data = [74, 1, 25] pc.width = 150 pc.height = 150 pc.slices.strokeWidth=1#0.5 pc.slices[0].fillColor = colors.steelblue pc.slices[1].fillColor = colors.thistle pc.slices[2].fillColor = colors.cornflower d.add(pc) return d def sample4(): "Make a pie chart with several very slim slices." d = Drawing(400, 200) pc = Pie() pc.x = 125 pc.y = 25 pc.data = [74, 1, 1, 1, 1, 22] pc.width = 150 pc.height = 150 pc.slices.strokeWidth=1#0.5 pc.slices[0].fillColor = colors.steelblue pc.slices[1].fillColor = colors.thistle pc.slices[2].fillColor = colors.cornflower pc.slices[3].fillColor = colors.lightsteelblue pc.slices[4].fillColor = colors.aquamarine pc.slices[5].fillColor = colors.cadetblue d.add(pc) return d