piecharts.py 65 KB

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