barcharts.py 71 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434
  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/barcharts.py
  4. __version__='3.3.0'
  5. __doc__="""This module defines a variety of Bar Chart components.
  6. The basic flavors are stacked and side-by-side, available in horizontal and
  7. vertical versions.
  8. """
  9. import copy, functools
  10. from ast import literal_eval
  11. from reportlab.lib import colors
  12. from reportlab.lib.validators import isNumber, isNumberOrNone, isColor, isColorOrNone, isString,\
  13. isListOfStrings, SequenceOf, isBoolean, isNoneOrShape, isStringOrNone,\
  14. NoneOr, isListOfNumbersOrNone, EitherOr, OneOf, isInt
  15. from reportlab.lib.utils import flatten, isStr, yieldNoneSplits
  16. from reportlab.graphics.widgets.markers import uSymbol2Symbol, isSymbol
  17. from reportlab.lib.formatters import Formatter
  18. from reportlab.lib.attrmap import AttrMap, AttrMapValue
  19. from reportlab.pdfbase.pdfmetrics import stringWidth
  20. from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder, tpcGetItem
  21. from reportlab.graphics.shapes import Line, Rect, Group, Drawing, NotImplementedError, PolyLine
  22. from reportlab.graphics.charts.axes import XCategoryAxis, YValueAxis, YCategoryAxis, XValueAxis
  23. from reportlab.graphics.charts.textlabels import BarChartLabel, NA_Label, NoneOrInstanceOfNA_Label
  24. from reportlab.graphics.charts.areas import PlotArea
  25. from reportlab.graphics.charts.legends import _objStr
  26. from reportlab import cmp
  27. class BarChartProperties(PropHolder):
  28. _attrMap = AttrMap(
  29. strokeColor = AttrMapValue(isColorOrNone, desc='Color of the bar border.'),
  30. fillColor = AttrMapValue(isColorOrNone, desc='Color of the bar interior area.'),
  31. strokeWidth = AttrMapValue(isNumber, desc='Width of the bar border.'),
  32. strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array of a line.'),
  33. symbol = AttrMapValue(None, desc='A widget to be used instead of a normal bar.',advancedUsage=1),
  34. name = AttrMapValue(isString, desc='Text to be associated with a bar (eg seriesname)'),
  35. swatchMarker = AttrMapValue(NoneOr(isSymbol), desc="None or makeMarker('Diamond') ...",advancedUsage=1),
  36. minDimen = AttrMapValue(isNumberOrNone, desc='minimum width/height that will be drawn.'),
  37. isLine = AttrMapValue(NoneOr(isBoolean), desc='if this bar should be drawn as a line'),
  38. )
  39. def __init__(self):
  40. self.strokeColor = None
  41. self.fillColor = colors.blue
  42. self.strokeWidth = 0.5
  43. self.symbol = None
  44. self.strokeDashArray = None
  45. # Bar chart classes.
  46. class BarChart(PlotArea):
  47. "Abstract base class, unusable by itself."
  48. _attrMap = AttrMap(BASE=PlotArea,
  49. useAbsolute = AttrMapValue(EitherOr((isBoolean,EitherOr((isString,isNumber)))), desc='Flag to use absolute spacing values; use string of gsb for finer control\n(g=groupSpacing,s=barSpacing,b=barWidth).',advancedUsage=1),
  50. barWidth = AttrMapValue(isNumber, desc='The width of an individual bar.'),
  51. groupSpacing = AttrMapValue(isNumber, desc='Width between groups of bars.'),
  52. barSpacing = AttrMapValue(isNumber, desc='Width between individual bars.'),
  53. bars = AttrMapValue(None, desc='Handle of the individual bars.'),
  54. valueAxis = AttrMapValue(None, desc='Handle of the value axis.'),
  55. categoryAxis = AttrMapValue(None, desc='Handle of the category axis.'),
  56. data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) numbers.'),
  57. barLabels = AttrMapValue(None, desc='Handle to the list of bar labels.'),
  58. barLabelFormat = AttrMapValue(None, desc='Formatting string or function used for bar labels. Can be a list or list of lists of such.'),
  59. barLabelCallOut = AttrMapValue(None, desc='Callout function(label)\nlabel._callOutInfo = (self,g,rowNo,colNo,x,y,width,height,x00,y00,x0,y0)',advancedUsage=1),
  60. barLabelArray = AttrMapValue(None, desc='explicit array of bar label values, must match size of data if present.'),
  61. reversePlotOrder = AttrMapValue(isBoolean, desc='If true, reverse common category plot order.',advancedUsage=1),
  62. naLabel = AttrMapValue(NoneOrInstanceOfNA_Label, desc='Label to use for N/A values.',advancedUsage=1),
  63. annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.'),
  64. categoryLabelBarSize = AttrMapValue(isNumber, desc='width to leave for a category label to go between categories.'),
  65. categoryLabelBarOrder = AttrMapValue(OneOf('first','last','auto'), desc='where any label bar should appear first/last'),
  66. barRecord = AttrMapValue(None, desc='callable(bar,label=labelText,value=value,**kwds) to record bar information', advancedUsage=1),
  67. zIndexOverrides = AttrMapValue(isStringOrNone, desc='''None (the default ie use old z ordering scheme) or a ',' separated list of key=value (int/float) for new zIndex ordering. If used defaults are
  68. background=0,
  69. categoryAxis=1,
  70. valueAxis=2,
  71. bars=3,
  72. barLabels=4,
  73. categoryAxisGrid=5,
  74. valueAxisGrid=6,
  75. annotations=7'''),
  76. categoryNALabel = AttrMapValue(NoneOrInstanceOfNA_Label, desc='Label to use for a group of N/A values.',advancedUsage=1),
  77. seriesOrder = AttrMapValue(SequenceOf(SequenceOf(isInt,emptyOK=0,NoneOK=0,lo=1),emptyOK=0,NoneOK=1,lo=1),"dynamic 'mixed' category style case"),
  78. )
  79. def makeSwatchSample(self, rowNo, x, y, width, height):
  80. baseStyle = self.bars
  81. styleIdx = rowNo % len(baseStyle)
  82. style = baseStyle[styleIdx]
  83. strokeColor = getattr(style, 'strokeColor', getattr(baseStyle,'strokeColor',None))
  84. fillColor = getattr(style, 'fillColor', getattr(baseStyle,'fillColor',None))
  85. strokeDashArray = getattr(style, 'strokeDashArray', getattr(baseStyle,'strokeDashArray',None))
  86. strokeWidth = getattr(style, 'strokeWidth', getattr(style, 'strokeWidth',None))
  87. swatchMarker = getattr(style, 'swatchMarker', getattr(baseStyle, 'swatchMarker',None))
  88. if swatchMarker:
  89. return uSymbol2Symbol(swatchMarker,x+width/2.,y+height/2.,fillColor)
  90. elif getattr(style,'isLine',False):
  91. yh2 = y+height/2.
  92. if hasattr(style, 'symbol'):
  93. S = style.symbol
  94. elif hasattr(baseStyle, 'symbol'):
  95. S = baseStyle.symbol
  96. else:
  97. S = None
  98. L = Line(x,yh2, x+width, yh2,
  99. strokeColor=style.strokeColor or style.fillColor,
  100. strokeWidth=style.strokeWidth,
  101. strokeDashArray = style.strokeDashArray)
  102. if S: S = uSymbol2Symbol(S,x+width/2.,yh2,style.strokeColor or style.fillColor)
  103. if S and L:
  104. g = Group()
  105. g.add(L)
  106. g.add(S)
  107. return g
  108. return S or L
  109. else:
  110. return Rect(x,y,width,height,strokeWidth=strokeWidth,strokeColor=strokeColor,
  111. strokeDashArray=strokeDashArray,fillColor=fillColor)
  112. def getSeriesName(self,i,default=None):
  113. '''return series name i or default'''
  114. return _objStr(getattr(self.bars[i],'name',default))
  115. def __init__(self):
  116. assert self.__class__.__name__ not in ('BarChart','BarChart3D'), 'Abstract Class %s Instantiated' % self.__class__.__name__
  117. if self._flipXY:
  118. self.categoryAxis = YCategoryAxis()
  119. self.valueAxis = XValueAxis()
  120. else:
  121. self.categoryAxis = XCategoryAxis()
  122. self.valueAxis = YValueAxis()
  123. self.categoryAxis._attrMap['style'].validate = OneOf('stacked','parallel','parallel_3d','mixed')
  124. PlotArea.__init__(self)
  125. self.barSpacing = 0
  126. self.reversePlotOrder = 0
  127. # this defines two series of 3 points. Just an example.
  128. self.data = [(100,110,120,130),
  129. (70, 80, 85, 90)]
  130. # control bar spacing. is useAbsolute = 1 then
  131. # the next parameters are in points; otherwise
  132. # they are 'proportions' and are normalized to
  133. # fit the available space. Half a barSpacing
  134. # is allocated at the beginning and end of the
  135. # chart.
  136. self.useAbsolute = 0 #- not done yet
  137. self.barWidth = 10
  138. self.groupSpacing = 5
  139. self.barSpacing = 0
  140. self.barLabels = TypedPropertyCollection(BarChartLabel)
  141. self.barLabels.boxAnchor = 'c'
  142. self.barLabels.textAnchor = 'middle'
  143. self.barLabelFormat = None
  144. self.barLabelArray = None
  145. # this says whether the origin is inside or outside
  146. # the bar - +10 means put the origin ten points
  147. # above the tip of the bar if value > 0, or ten
  148. # points inside if bar value < 0. This is different
  149. # to label dx/dy which are not dependent on the
  150. # sign of the data.
  151. self.barLabels.nudge = 0
  152. # if you have multiple series, by default they butt
  153. # together.
  154. # we really need some well-designed default lists of
  155. # colors e.g. from Tufte. These will be used in a
  156. # cycle to set the fill color of each series.
  157. self.bars = TypedPropertyCollection(BarChartProperties)
  158. self.bars.strokeWidth = 1
  159. self.bars.strokeColor = colors.black
  160. self.bars.strokeDashArray = None
  161. self.bars[0].fillColor = colors.red
  162. self.bars[1].fillColor = colors.green
  163. self.bars[2].fillColor = colors.blue
  164. self.naLabel = self.categoryNALabel = None
  165. self.zIndexOverrides = None
  166. def demo(self):
  167. """Shows basic use of a bar chart"""
  168. if self.__class__.__name__=='BarChart':
  169. raise NotImplementedError('Abstract Class BarChart has no demo')
  170. drawing = Drawing(200, 100)
  171. bc = self.__class__()
  172. drawing.add(bc)
  173. return drawing
  174. def getSeriesOrder(self):
  175. bs = getattr(self,'seriesOrder',None)
  176. n = len(self.data)
  177. if not bs:
  178. R = [(ss,) for ss in range(n)]
  179. else:
  180. bars = self.bars
  181. unseen = set(range(n))
  182. lines = set()
  183. R = []
  184. for s in bs:
  185. g = {ss for ss in s if 0<=ss<=n}
  186. gl = {ss for ss in g if bars.checkAttr(ss,'isLine',False)}
  187. if gl:
  188. g -= gl
  189. lines |= gl
  190. unseen -= gl
  191. if g:
  192. R.append(tuple(g))
  193. unseen -= g
  194. if unseen:
  195. R.extend((ss,) for ss in sorted(unseen))
  196. if lines:
  197. R.extend((ss,) for ss in sorted(lines))
  198. self._seriesOrder = R
  199. def _getConfigureData(self):
  200. cAStyle = self.categoryAxis.style
  201. data = self.data
  202. cc = max(list(map(len,data))) #category count
  203. _data = data
  204. if cAStyle not in ('parallel','parallel_3d'):
  205. #stacked or mixed
  206. data = []
  207. def _accumulate(*D):
  208. pdata = max((len(d) for d in D))*[0]
  209. ndata = pdata[:]
  210. for d in D:
  211. for i,v in enumerate(d):
  212. v = v or 0
  213. if v<=-1e-6:
  214. ndata[i] += v
  215. else:
  216. pdata[i] += v
  217. data.append(ndata)
  218. data.append(pdata)
  219. if cAStyle=='stacked':
  220. _accumulate(*_data)
  221. else:
  222. self.getSeriesOrder()
  223. for b in self._seriesOrder:
  224. _accumulate(*(_data[j] for j in b))
  225. self._configureData = data
  226. def _getMinMax(self):
  227. '''Attempt to return the data range'''
  228. self._getConfigureData()
  229. self.valueAxis._setRange(self._configureData)
  230. return self.valueAxis._valueMin, self.valueAxis._valueMax
  231. def _drawBegin(self,org,length):
  232. '''Position and configure value axis, return crossing value'''
  233. vA = self.valueAxis
  234. vA.setPosition(self.x, self.y, length)
  235. self._getConfigureData()
  236. vA.configure(self._configureData)
  237. # if zero is in chart, put the other axis there, otherwise use org
  238. crossesAt = vA.scale(0)
  239. return crossesAt if vA.forceZero or (crossesAt>=org and crossesAt<=org+length) else org
  240. def _drawFinish(self):
  241. '''finalize the drawing of a barchart'''
  242. cA = self.categoryAxis
  243. vA = self.valueAxis
  244. cA.configure(self._configureData)
  245. self.calcBarPositions()
  246. g = Group()
  247. zIndex = getattr(self,'zIndexOverrides',None)
  248. if not zIndex:
  249. g.add(self.makeBackground())
  250. cAdgl = getattr(cA,'drawGridLast',False)
  251. vAdgl = getattr(vA,'drawGridLast',False)
  252. if not cAdgl: cA.makeGrid(g,parent=self, dim=vA.getGridDims)
  253. if not vAdgl: vA.makeGrid(g,parent=self, dim=cA.getGridDims)
  254. g.add(self.makeBars())
  255. g.add(cA)
  256. g.add(vA)
  257. if cAdgl: cA.makeGrid(g,parent=self, dim=vA.getGridDims)
  258. if vAdgl: vA.makeGrid(g,parent=self, dim=cA.getGridDims)
  259. for a in getattr(self,'annotations',()): g.add(a(self,cA.scale,vA.scale))
  260. else:
  261. Z=dict(
  262. background=0,
  263. categoryAxis=1,
  264. valueAxis=2,
  265. bars=3,
  266. barLabels=4,
  267. categoryAxisGrid=5,
  268. valueAxisGrid=6,
  269. annotations=7,
  270. )
  271. for z in zIndex.strip().split(','):
  272. z = z.strip()
  273. if not z: continue
  274. try:
  275. k,v=z.split('=')
  276. except:
  277. raise ValueError('Badly formatted zIndex clause %r in %r\nallowed variables are\n%s' % (z,zIndex,'\n'.join(['%s=%r'% (k,Z[k]) for k in sorted(Z.keys())])))
  278. if k not in Z:
  279. raise ValueError('Unknown zIndex variable %r in %r\nallowed variables are\n%s' % (k,Z,'\n'.join(['%s=%r'% (k,Z[k]) for k in sorted(Z.keys())])))
  280. try:
  281. v = literal_eval(v) #only constants allowed
  282. assert isinstance(v,(float,int))
  283. except:
  284. raise ValueError('Bad zIndex value %r in clause %r of zIndex\nallowed variables are\n%s' % (v,z,zIndex,'\n'.join(['%s=%r'% (k,Z[k]) for k in sorted(Z.keys())])))
  285. Z[k] = v
  286. Z = [(v,k) for k,v in Z.items()]
  287. Z.sort()
  288. b = self.makeBars()
  289. bl = b.contents.pop(-1)
  290. for v,k in Z:
  291. if k=='background':
  292. g.add(self.makeBackground())
  293. elif k=='categoryAxis':
  294. g.add(cA)
  295. elif k=='categoryAxisGrid':
  296. cA.makeGrid(g,parent=self, dim=vA.getGridDims)
  297. elif k=='valueAxis':
  298. g.add(vA)
  299. elif k=='valueAxisGrid':
  300. vA.makeGrid(g,parent=self, dim=cA.getGridDims)
  301. elif k=='bars':
  302. g.add(b)
  303. elif k=='barLabels':
  304. g.add(bl)
  305. elif k=='annotations':
  306. for a in getattr(self,'annotations',()): g.add(a(self,cA.scale,vA.scale))
  307. del self._configureData
  308. return g
  309. def calcBarPositions(self):
  310. """Works out where they go. default vertical.
  311. Sets an attribute _barPositions which is a list of
  312. lists of (x, y, width, height) matching the data.
  313. """
  314. flipXY = self._flipXY
  315. if flipXY:
  316. org = self.y
  317. else:
  318. org = self.x
  319. cA = self.categoryAxis
  320. cScale = cA.scale
  321. data = self.data
  322. seriesCount = self._seriesCount = len(data)
  323. self._rowLength = rowLength = max(list(map(len,data)))
  324. wG = self.groupSpacing
  325. barSpacing = self.barSpacing
  326. barWidth = self.barWidth
  327. clbs = getattr(self,'categoryLabelBarSize',0)
  328. clbo = getattr(self,'categoryLabelBarOrder','auto')
  329. if clbo=='auto': clbo = flipXY and 'last' or 'first'
  330. clbo = clbo=='first'
  331. style = cA.style
  332. bars = self.bars
  333. lineCount = sum((int(bars.checkAttr(_,'isLine',False)) for _ in range(seriesCount)))
  334. seriesMLineCount = seriesCount - lineCount
  335. if style=='mixed':
  336. ss = self._seriesOrder
  337. barsPerGroup = len(ss) - lineCount
  338. wB = barsPerGroup*barWidth
  339. wS = (barsPerGroup-1)*barSpacing
  340. if barsPerGroup>1:
  341. bGapB = barWidth
  342. bGapS = barSpacing
  343. else:
  344. bGapB = bGapS = 0
  345. accumNeg = barsPerGroup*rowLength*[0]
  346. accumPos = accumNeg[:]
  347. elif style in ('parallel','parallel_3d'):
  348. barsPerGroup = 1
  349. wB = seriesMLineCount*barWidth
  350. wS = (seriesMLineCount-1)*barSpacing
  351. bGapB = barWidth
  352. bGapS = barSpacing
  353. else:
  354. barsPerGroup = seriesMLineCount
  355. accumNeg = rowLength*[0]
  356. accumPos = accumNeg[:]
  357. wB = barWidth
  358. wS = bGapB = bGapS = 0
  359. self._groupWidth = groupWidth = wG+wB+wS
  360. useAbsolute = self.useAbsolute
  361. if useAbsolute:
  362. if not isinstance(useAbsolute,str):
  363. useAbsolute = 7 #all three are fixed
  364. else:
  365. useAbsolute = 0 + 1*('b' in useAbsolute)+2*('g' in useAbsolute)+4*('s' in useAbsolute)
  366. else:
  367. useAbsolute = 0
  368. aW0 = float(cScale(0)[1])
  369. aW = aW0 - clbs
  370. if useAbsolute==0: #case 0 all are free
  371. self._normFactor = fB = fG = fS = aW/groupWidth
  372. elif useAbsolute==7: #all fixed
  373. fB = fG = fS = 1.0
  374. _cscale = cA._scale
  375. elif useAbsolute==1: #case 1 barWidth is fixed
  376. fB = 1.0
  377. fG = fS = (aW-wB)/(wG+wS)
  378. elif useAbsolute==2: #groupspacing is fixed
  379. fG=1.0
  380. fB = fS = (aW-wG)/(wB+wS)
  381. elif useAbsolute==3: #groupspacing & barwidth are fixed
  382. fB = fG = 1.0
  383. fS = (aW-wG-wB)/wS if wS else 0
  384. elif useAbsolute==4: #barspacing is fixed
  385. fS=1.0
  386. fG = fB = (aW-wS)/(wG+wB)
  387. elif useAbsolute==5: #barspacing & barWidth are fixed
  388. fS = fB = 1.0
  389. fG = (aW-wB-wS)/wG
  390. elif useAbsolute==6: #barspacing & groupspacing are fixed
  391. fS = fG = 1
  392. fB = (aW-wS-wG)/wB
  393. self._normFactorB = fB
  394. self._normFactorG = fG
  395. self._normFactorS = fS
  396. # 'Baseline' correction...
  397. vA = self.valueAxis
  398. vScale = vA.scale
  399. vm, vM = vA._valueMin, vA._valueMax
  400. if vm <= 0 <= vM:
  401. baseLine = vScale(0)
  402. elif 0 < vm:
  403. baseLine = vScale(vm)
  404. elif vM < 0:
  405. baseLine = vScale(vM)
  406. self._baseLine = baseLine
  407. width = barWidth*fB
  408. offs = 0.5*wG*fG
  409. bGap = bGapB*fB+bGapS*fS
  410. if clbs:
  411. if clbo: #the lable bar comes first
  412. lbpf = (offs+clbs/6.0)/aW0
  413. offs += clbs
  414. else:
  415. lbpf = (offs+wB*fB+wS*fS+clbs/6.0)/aW0
  416. cA.labels.labelPosFrac = lbpf
  417. self._barPositions = []
  418. aBP = self._barPositions.append
  419. reversePlotOrder = self.reversePlotOrder
  420. def _addBar(colNo, accx):
  421. # Ufff...
  422. if useAbsolute==7:
  423. x = groupWidth*_cscale(colNo) + xVal + org
  424. else:
  425. (g, _) = cScale(colNo)
  426. x = g + xVal
  427. datum = row[colNo]
  428. if datum is None:
  429. height = None
  430. y = baseLine
  431. else:
  432. if style not in ('parallel','parallel_3d') and not isLine:
  433. if datum<=-1e-6:
  434. y = vScale(accumNeg[accx])
  435. if y>baseLine: y = baseLine
  436. accumNeg[accx] += datum
  437. datum = accumNeg[accx]
  438. else:
  439. y = vScale(accumPos[accx])
  440. if y<baseLine: y = baseLine
  441. accumPos[accx] += datum
  442. datum = accumPos[accx]
  443. else:
  444. y = baseLine
  445. height = vScale(datum) - y
  446. if -1e-8<height<=1e-8:
  447. height = 1e-8
  448. if datum<-1e-8: height = -1e-8
  449. barRow.append(flipXY and (y,x,height,width) or (x,y,width,height))
  450. if style!='mixed':
  451. lineSeen = 0
  452. for rowNo, row in enumerate(data): #iterate over the separate series
  453. barRow = []
  454. xVal = barsPerGroup - 1 - rowNo if reversePlotOrder else rowNo
  455. xVal = offs + xVal*bGap
  456. isLine = bars.checkAttr(rowNo, 'isLine', False)
  457. if isLine:
  458. lineSeen += 1
  459. xVal = offs+(seriesMLineCount-1)*bGap*0.5
  460. else:
  461. xVal -= lineSeen*bGap
  462. for colNo in range(rowLength): #iterate over categories
  463. _addBar(colNo,colNo)
  464. aBP(barRow)
  465. else:
  466. lineSeen = 0
  467. for sb,sg in enumerate(self._seriesOrder): #the sub bar nos and series groups
  468. style = 'parallel' if len(sg)<=1 else 'stacked'
  469. for rowNo in sg: #the individual series
  470. xVal = barsPerGroup - 1 - sb if reversePlotOrder else sb
  471. xVal = offs + xVal*bGap
  472. barRow = []
  473. row = data[rowNo]
  474. isLine = bars.checkAttr(rowNo, 'isLine', False)
  475. if isLine:
  476. lineSeen += 1
  477. xVal = offs+(barsPerGroup-1)*bGap*0.5
  478. else:
  479. xVal -= lineSeen*bGap
  480. for colNo in range(rowLength): #iterate over categories
  481. _addBar(colNo,colNo*barsPerGroup + sb)
  482. aBP(barRow)
  483. def _getLabelText(self, rowNo, colNo):
  484. '''return formatted label text'''
  485. labelFmt = self.barLabelFormat
  486. if isinstance(labelFmt,(list,tuple)):
  487. labelFmt = labelFmt[rowNo]
  488. if isinstance(labelFmt,(list,tuple)):
  489. labelFmt = labelFmt[colNo]
  490. if labelFmt is None:
  491. labelText = None
  492. elif labelFmt == 'values':
  493. labelText = self.barLabelArray[rowNo][colNo]
  494. elif isStr(labelFmt):
  495. labelText = labelFmt % self.data[rowNo][colNo]
  496. elif hasattr(labelFmt,'__call__'):
  497. labelText = labelFmt(self.data[rowNo][colNo])
  498. else:
  499. msg = "Unknown formatter type %s, expected string or function" % labelFmt
  500. raise Exception(msg)
  501. return labelText
  502. def _labelXY(self,label,x,y,width,height):
  503. 'Compute x, y for a label'
  504. nudge = label.nudge
  505. bt = getattr(label,'boxTarget','normal')
  506. anti = bt=='anti'
  507. if anti: nudge = -nudge
  508. pm = value = height
  509. if anti: value = 0
  510. a = x + 0.5*width
  511. nudge = (height>=0 and 1 or -1)*nudge
  512. if bt=='mid':
  513. b = y+height*0.5
  514. elif bt=='hi':
  515. if value>=0:
  516. b = y + value + nudge
  517. else:
  518. b = y - nudge
  519. pm = -pm
  520. elif bt=='lo':
  521. if value<=0:
  522. b = y + value + nudge
  523. else:
  524. b = y - nudge
  525. pm = -pm
  526. else:
  527. b = y + value + nudge
  528. label._pmv = pm #the plus minus val
  529. return a,b,pm
  530. def _addBarLabel(self, g, rowNo, colNo, x, y, width, height):
  531. text = self._getLabelText(rowNo,colNo)
  532. if text:
  533. self._addLabel(text, self.barLabels[(rowNo, colNo)], g, rowNo, colNo, x, y, width, height)
  534. def _addNABarLabel(self, g, rowNo, colNo, x, y, width, height, calcOnly=False, na=None):
  535. if na is None: na = self.naLabel
  536. if na and na.text:
  537. na = copy.copy(na)
  538. v = self.valueAxis._valueMax<=0 and -1e-8 or 1e-8
  539. if width is None: width = v
  540. if height is None: height = v
  541. return self._addLabel(na.text, na, g, rowNo, colNo, x, y, width, height, calcOnly=calcOnly)
  542. def _addLabel(self, text, label, g, rowNo, colNo, x, y, width, height, calcOnly=False):
  543. if label.visible:
  544. labelWidth = stringWidth(text, label.fontName, label.fontSize)
  545. flipXY = self._flipXY
  546. if flipXY:
  547. y0, x0, pm = self._labelXY(label,y,x,height,width)
  548. else:
  549. x0, y0, pm = self._labelXY(label,x,y,width,height)
  550. fixedEnd = getattr(label,'fixedEnd', None)
  551. if fixedEnd is not None:
  552. v = fixedEnd._getValue(self,pm)
  553. x00, y00 = x0, y0
  554. if flipXY:
  555. x0 = v
  556. else:
  557. y0 = v
  558. else:
  559. if flipXY:
  560. x00 = x0
  561. y00 = y+height/2.0
  562. else:
  563. x00 = x+width/2.0
  564. y00 = y0
  565. fixedStart = getattr(label,'fixedStart', None)
  566. if fixedStart is not None:
  567. v = fixedStart._getValue(self,pm)
  568. if flipXY:
  569. x00 = v
  570. else:
  571. y00 = v
  572. if pm<0:
  573. if flipXY:
  574. dx = -2*label.dx
  575. dy = 0
  576. else:
  577. dy = -2*label.dy
  578. dx = 0
  579. else:
  580. dy = dx = 0
  581. if calcOnly: return x0+dx, y0+dy
  582. label.setOrigin(x0+dx, y0+dy)
  583. label.setText(text)
  584. sC, sW = label.lineStrokeColor, label.lineStrokeWidth
  585. if sC and sW: g.insert(0,Line(x00,y00,x0,y0, strokeColor=sC, strokeWidth=sW))
  586. g.add(label)
  587. alx = getattr(self,'barLabelCallOut',None)
  588. if alx:
  589. label._callOutInfo = (self,g,rowNo,colNo,x,y,width,height,x00,y00,x0,y0)
  590. alx(label)
  591. del label._callOutInfo
  592. def _makeBar(self,g,x,y,width,height,rowNo,style):
  593. r = Rect(x, y, width, height)
  594. r.strokeWidth = style.strokeWidth
  595. r.fillColor = style.fillColor
  596. r.strokeColor = style.strokeColor
  597. if style.strokeDashArray:
  598. r.strokeDashArray = style.strokeDashArray
  599. g.add(r)
  600. def _makeBars(self,g,lg):
  601. bars = self.bars
  602. br = getattr(self,'barRecord',None)
  603. BP = self._barPositions
  604. flipXY = self._flipXY
  605. catNAL = self.categoryNALabel
  606. catNNA = {}
  607. if catNAL:
  608. CBL = []
  609. rowNoL = len(self.data) - 1
  610. #find all the categories that have at least one value
  611. for rowNo, row in enumerate(BP):
  612. for colNo, (x, y, width, height) in enumerate(row):
  613. if None not in (width,height):
  614. catNNA[colNo] = 1
  615. lines = [].append
  616. lineSyms = [].append
  617. for rowNo, row in enumerate(BP):
  618. styleCount = len(bars)
  619. styleIdx = rowNo % styleCount
  620. rowStyle = bars[styleIdx]
  621. isLine = bars.checkAttr(rowNo, 'isLine', False)
  622. linePts = [].append
  623. for colNo, (x,y,width,height) in enumerate(row):
  624. style = (styleIdx,colNo) in bars and bars[(styleIdx,colNo)] or rowStyle
  625. if None in (width,height):
  626. if not catNAL or colNo in catNNA:
  627. self._addNABarLabel(lg,rowNo,colNo,x,y,width,height)
  628. elif catNAL and colNo not in CBL:
  629. r0 = self._addNABarLabel(lg,rowNo,colNo,x,y,width,height,True,catNAL)
  630. if r0:
  631. x, y, width, height = BP[rowNoL][colNo]
  632. r1 = self._addNABarLabel(lg,rowNoL,colNo,x,y,width,height,True,catNAL)
  633. x = (r0[0]+r1[0])/2.0
  634. y = (r0[1]+r1[1])/2.0
  635. self._addNABarLabel(lg,rowNoL,colNo,x,y,0.0001,0.0001,na=catNAL)
  636. CBL.append(colNo)
  637. if isLine: linePts(None)
  638. continue
  639. # Draw a rectangular symbol for each data item,
  640. # or a normal colored rectangle.
  641. symbol = None
  642. if hasattr(style, 'symbol'):
  643. symbol = copy.deepcopy(style.symbol)
  644. elif hasattr(self.bars, 'symbol'):
  645. symbol = self.bars.symbol
  646. minDimen=getattr(style,'minDimen',None)
  647. if minDimen:
  648. if flipXY:
  649. if width<0:
  650. width = min(-style.minDimen,width)
  651. else:
  652. width = max(style.minDimen,width)
  653. else:
  654. if height<0:
  655. height = min(-style.minDimen,height)
  656. else:
  657. height = max(style.minDimen,height)
  658. if isLine:
  659. if not flipXY:
  660. yL = y + height
  661. xL = x + width*0.5
  662. else:
  663. xL = x + width
  664. yL = y + height*0.5
  665. linePts(xL)
  666. linePts(yL)
  667. if symbol:
  668. sym = uSymbol2Symbol(tpcGetItem(symbol,colNo),xL,yL,style.strokeColor or style.fillColor)
  669. if sym: lineSyms(sym)
  670. elif symbol:
  671. symbol.x = x
  672. symbol.y = y
  673. symbol.width = width
  674. symbol.height = height
  675. g.add(symbol)
  676. elif abs(width)>1e-7 and abs(height)>=1e-7 and (style.fillColor is not None or style.strokeColor is not None):
  677. self._makeBar(g,x,y,width,height,rowNo,style)
  678. if br: br(g.contents[-1],label=self._getLabelText(rowNo,colNo),value=self.data[rowNo][colNo],rowNo=rowNo,colNo=colNo)
  679. self._addBarLabel(lg,rowNo,colNo,x,y,width,height)
  680. for linePts in yieldNoneSplits(linePts.__self__):
  681. if linePts:
  682. lines(PolyLine(linePts,
  683. strokeColor=rowStyle.strokeColor or rowStyle.fillColor,
  684. strokeWidth=rowStyle.strokeWidth,
  685. strokeDashArray = rowStyle.strokeDashArray))
  686. for pl in lines.__self__:
  687. g.add(pl)
  688. for sym in lineSyms.__self__:
  689. g.add(sym)
  690. def _computeLabelPosition(self, text, label, rowNo, colNo, x, y, width, height):
  691. if label.visible:
  692. labelWidth = stringWidth(text, label.fontName, label.fontSize)
  693. flipXY = self._flipXY
  694. if flipXY:
  695. y0, x0, pm = self._labelXY(label,y,x,height,width)
  696. else:
  697. x0, y0, pm = self._labelXY(label,x,y,width,height)
  698. fixedEnd = getattr(label,'fixedEnd', None)
  699. if fixedEnd is not None:
  700. v = fixedEnd._getValue(self,pm)
  701. x00, y00 = x0, y0
  702. if flipXY:
  703. x0 = v
  704. else:
  705. y0 = v
  706. else:
  707. if flipXY:
  708. x00 = x0
  709. y00 = y+height/2.0
  710. else:
  711. x00 = x+width/2.0
  712. y00 = y0
  713. fixedStart = getattr(label,'fixedStart', None)
  714. if fixedStart is not None:
  715. v = fixedStart._getValue(self,pm)
  716. if flipXY:
  717. x00 = v
  718. else:
  719. y00 = v
  720. if pm<0:
  721. if flipXY:
  722. dx = -2*label.dx
  723. dy = 0
  724. else:
  725. dy = -2*label.dy
  726. dx = 0
  727. else:
  728. dy = dx = 0
  729. label.setOrigin(x0+dx, y0+dy)
  730. label.setText(text)
  731. return pm,label.getBounds()
  732. def _computeBarPositions(self):
  733. """Information function, can be called by charts which want to with space around bars"""
  734. cA, vA = self.categoryAxis, self.valueAxis
  735. if vA: vA.joinAxis = cA
  736. if cA: cA.joinAxis = vA
  737. if self._flipXY:
  738. cA.setPosition(self._drawBegin(self.x,self.width), self.y, self.height)
  739. else:
  740. cA.setPosition(self.x, self._drawBegin(self.y,self.height), self.width)
  741. cA.configure(self._configureData)
  742. self.calcBarPositions()
  743. def _computeMaxSpace(self,size,required):
  744. '''helper for madmen who want to put stuff inside their barcharts
  745. basically after _computebarPositions we slide a line of length size
  746. down the bar profile on either side of the bars to find the
  747. maximum space. If the space at any point is >= required then we're
  748. done. Otherwise we return the largest space location and amount.
  749. '''
  750. flipXY = self._flipXY
  751. self._computeBarPositions()
  752. lenData = len(self.data)
  753. BP = self._barPositions
  754. C = []
  755. aC = C.append
  756. if flipXY:
  757. lo = self.x
  758. hi = lo + self.width
  759. end = self.y+self.height
  760. for bp in BP:
  761. for x, y, w, h in bp:
  762. v = x+w
  763. z = y+h
  764. aC((min(y,z),max(y,z), min(x,v) - lo, hi - max(x,v)))
  765. else:
  766. lo = self.y
  767. hi = lo + self.height
  768. end = self.x+self.width
  769. for bp in BP:
  770. for x, y, w, h in bp:
  771. v = y+h
  772. z = x+w
  773. aC((min(x,z), max(x,z), min(y,v) - lo, hi - max(y,v)))
  774. C.sort()
  775. R = [C[0]]
  776. for c in C:
  777. r = R[-1]
  778. if r[0]<c[1] and c[0]<r[1]: #merge overlapping space
  779. R[-1] = (min(r[0],c[0]),max(r[1],c[1]),min(r[2],c[2]),min(r[3],c[3]))
  780. else:
  781. R.append(c)
  782. C = R
  783. maxS = -0x7fffffff
  784. maxP = None
  785. nC = len(C)
  786. for i,ci in enumerate(C):
  787. v0 = ci[0]
  788. v1 = v0+size
  789. if v1>end: break
  790. j = i
  791. alo = ahi = 0x7fffffff
  792. while j<nC and C[j][1]<=v1:
  793. alo = min(C[j][2],alo)
  794. ahi = min(C[j][3],ahi)
  795. j += 1
  796. if alo>ahi:
  797. if alo>maxS:
  798. maxS = alo
  799. maxP = flipXY and (lo,v0,lo+alo,v0+size,0) or (v0,lo,v0+size,lo+alo,0)
  800. if maxS >= required: break
  801. elif ahi>maxS:
  802. maxS = ahi
  803. maxP = flipXY and (hi-ahi,v0,hi,v0+size,1) or (v0,hi-ahi,v0+size,hi,1)
  804. if maxS >= required: break
  805. return maxS, maxP
  806. def _computeSimpleBarLabelPositions(self):
  807. """Information function, can be called by charts which want to mess with labels"""
  808. cA, vA = self.categoryAxis, self.valueAxis
  809. if vA: vA.joinAxis = cA
  810. if cA: cA.joinAxis = vA
  811. if self._flipXY:
  812. cA.setPosition(self._drawBegin(self.x,self.width), self.y, self.height)
  813. else:
  814. cA.setPosition(self.x, self._drawBegin(self.y,self.height), self.width)
  815. cA.configure(self._configureData)
  816. self.calcBarPositions()
  817. bars = self.bars
  818. R = [].append
  819. BP = self._barPositions
  820. for rowNo, row in enumerate(BP):
  821. C = [].append
  822. for colNo, (x, y, width, height) in enumerate(row):
  823. if None in (width,height):
  824. na = self.naLabel
  825. if na and na.text:
  826. na = copy.copy(na)
  827. v = self.valueAxis._valueMax<=0 and -1e-8 or 1e-8
  828. if width is None: width = v
  829. if height is None: height = v
  830. C(self._computeLabelPosition(na.text, na, rowNo, colNo, x, y, width, height))
  831. else:
  832. C(None)
  833. else:
  834. text = self._getLabelText(rowNo,colNo)
  835. if text:
  836. C(self._computeLabelPosition(text, self.barLabels[(rowNo, colNo)], rowNo, colNo, x, y, width, height))
  837. else:
  838. C(None)
  839. R(C.__self__)
  840. return R.__self__
  841. def makeBars(self):
  842. g = Group()
  843. lg = Group()
  844. self._makeBars(g,lg)
  845. g.add(lg)
  846. return g
  847. def _desiredCategoryAxisLength(self):
  848. '''for dynamically computing the desired category axis length'''
  849. style = self.categoryAxis.style
  850. data = self.data
  851. n = len(data)
  852. m = max(list(map(len,data)))
  853. if style=='parallel':
  854. groupWidth = (n-1)*self.barSpacing+n*self.barWidth
  855. else:
  856. groupWidth = self.barWidth
  857. return m*(self.groupSpacing+groupWidth)
  858. def draw(self):
  859. cA, vA = self.categoryAxis, self.valueAxis
  860. if vA: vA.joinAxis = cA
  861. if cA: cA.joinAxis = vA
  862. if self._flipXY:
  863. cA.setPosition(self._drawBegin(self.x,self.width), self.y, self.height)
  864. else:
  865. cA.setPosition(self.x, self._drawBegin(self.y,self.height), self.width)
  866. return self._drawFinish()
  867. class VerticalBarChart(BarChart):
  868. "Vertical bar chart with multiple side-by-side bars."
  869. _flipXY = 0
  870. class HorizontalBarChart(BarChart):
  871. "Horizontal bar chart with multiple side-by-side bars."
  872. _flipXY = 1
  873. class _FakeGroup:
  874. def __init__(self, cmp=None):
  875. self._data = []
  876. self._key = functools.cmp_to_key(cmp)
  877. def add(self,what):
  878. self._data.append(what)
  879. def value(self):
  880. return self._data
  881. def sort(self):
  882. self._data.sort(key=self._key)
  883. class BarChart3D(BarChart):
  884. _attrMap = AttrMap(BASE=BarChart,
  885. theta_x = AttrMapValue(isNumber, desc='dx/dz'),
  886. theta_y = AttrMapValue(isNumber, desc='dy/dz'),
  887. zDepth = AttrMapValue(isNumber, desc='depth of an individual series'),
  888. zSpace = AttrMapValue(isNumber, desc='z gap around series'),
  889. )
  890. theta_x = .5
  891. theta_y = .5
  892. zDepth = None
  893. zSpace = None
  894. def calcBarPositions(self):
  895. BarChart.calcBarPositions(self)
  896. seriesCount = self._seriesCount
  897. zDepth = self.zDepth
  898. if zDepth is None: zDepth = self.barWidth
  899. zSpace = self.zSpace
  900. if zSpace is None: zSpace = self.barSpacing
  901. if self.categoryAxis.style=='parallel_3d':
  902. _3d_depth = seriesCount*zDepth+(seriesCount+1)*zSpace
  903. else:
  904. _3d_depth = zDepth + 2*zSpace
  905. _3d_depth *= self._normFactor
  906. self._3d_dx = self.theta_x*_3d_depth
  907. self._3d_dy = self.theta_y*_3d_depth
  908. def _calc_z0(self,rowNo):
  909. zDepth = self.zDepth
  910. if zDepth is None: zDepth = self.barWidth
  911. zSpace = self.zSpace
  912. if zSpace is None: zSpace = self.barSpacing
  913. if self.categoryAxis.style=='parallel_3d':
  914. z0 = self._normFactor*(rowNo*(zDepth+zSpace)+zSpace)
  915. else:
  916. z0 = self._normFactor*zSpace
  917. return z0
  918. def _makeBar(self,g,x,y,width,height,rowNo,style):
  919. zDepth = self.zDepth
  920. if zDepth is None: zDepth = self.barWidth
  921. zSpace = self.zSpace
  922. if zSpace is None: zSpace = self.barSpacing
  923. z0 = self._calc_z0(rowNo)
  924. z1 = z0 + zDepth*self._normFactor
  925. if width<0:
  926. x += width
  927. width = -width
  928. x += z0*self.theta_x
  929. y += z0*self.theta_y
  930. if self._flipXY:
  931. y += zSpace
  932. else:
  933. x += zSpace
  934. g.add((0,z0,z1,x,y,width,height,rowNo,style))
  935. def _addBarLabel(self, g, rowNo, colNo, x, y, width, height):
  936. z0 = self._calc_z0(rowNo)
  937. zSpace = self.zSpace
  938. if zSpace is None: zSpace = self.barSpacing
  939. z1 = z0
  940. x += z0*self.theta_x
  941. y += z0*self.theta_y
  942. if self._flipXY:
  943. y += zSpace
  944. else:
  945. x += zSpace
  946. g.add((1,z0,z1,x,y,width,height,rowNo,colNo))
  947. def makeBars(self):
  948. from reportlab.graphics.charts.utils3d import _draw_3d_bar
  949. fg = _FakeGroup(cmp=self._cmpZ)
  950. self._makeBars(fg,fg)
  951. fg.sort()
  952. g = Group()
  953. theta_x = self.theta_x
  954. theta_y = self.theta_y
  955. fg_value = fg.value()
  956. cAStyle = self.categoryAxis.style
  957. if cAStyle=='stacked':
  958. fg_value=fg_value.reverse()
  959. elif cAStyle=='mixed':
  960. fg_value = [_[1] for _ in sorted((((t[1],t[2],t[3],t[4]),t) for t in fg_value))]
  961. for t in fg_value:
  962. if t[0]==0:
  963. z0,z1,x,y,width,height,rowNo,style = t[1:]
  964. dz = z1 - z0
  965. _draw_3d_bar(g, x, x+width, y, y+height, dz*theta_x, dz*theta_y,
  966. fillColor=style.fillColor, fillColorShaded=None,
  967. strokeColor=style.strokeColor, strokeWidth=style.strokeWidth,
  968. shading=0.45)
  969. for t in fg_value:
  970. if t==1:
  971. z0,z1,x,y,width,height,rowNo,colNo = t[1:]
  972. BarChart._addBarLabel(self,g,rowNo,colNo,x,y,width,height)
  973. return g
  974. class VerticalBarChart3D(BarChart3D,VerticalBarChart):
  975. _cmpZ=lambda self,a,b:cmp((-a[1],a[3],a[0],-a[4]),(-b[1],b[3],b[0],-b[4]))
  976. class HorizontalBarChart3D(BarChart3D,HorizontalBarChart):
  977. _cmpZ = lambda self,a,b: cmp((-a[1],a[4],a[0],-a[3]),(-b[1],b[4],b[0],-b[3])) #t, z0, z1, x, y = a[:5]
  978. # Vertical samples.
  979. def sampleV0a():
  980. "A slightly pathologic bar chart with only TWO data items."
  981. drawing = Drawing(400, 200)
  982. data = [(13, 20)]
  983. bc = VerticalBarChart()
  984. bc.x = 50
  985. bc.y = 50
  986. bc.height = 125
  987. bc.width = 300
  988. bc.data = data
  989. bc.strokeColor = colors.black
  990. bc.valueAxis.valueMin = 0
  991. bc.valueAxis.valueMax = 60
  992. bc.valueAxis.valueStep = 15
  993. bc.categoryAxis.labels.boxAnchor = 'ne'
  994. bc.categoryAxis.labels.dx = 8
  995. bc.categoryAxis.labels.dy = -2
  996. bc.categoryAxis.labels.angle = 30
  997. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  998. drawing.add(bc)
  999. return drawing
  1000. def sampleV0b():
  1001. "A pathologic bar chart with only ONE data item."
  1002. drawing = Drawing(400, 200)
  1003. data = [(42,)]
  1004. bc = VerticalBarChart()
  1005. bc.x = 50
  1006. bc.y = 50
  1007. bc.height = 125
  1008. bc.width = 300
  1009. bc.data = data
  1010. bc.strokeColor = colors.black
  1011. bc.valueAxis.valueMin = 0
  1012. bc.valueAxis.valueMax = 50
  1013. bc.valueAxis.valueStep = 15
  1014. bc.categoryAxis.labels.boxAnchor = 'ne'
  1015. bc.categoryAxis.labels.dx = 8
  1016. bc.categoryAxis.labels.dy = -2
  1017. bc.categoryAxis.labels.angle = 30
  1018. bc.categoryAxis.categoryNames = ['Jan-99']
  1019. drawing.add(bc)
  1020. return drawing
  1021. def sampleV0c():
  1022. "A really pathologic bar chart with NO data items at all!"
  1023. drawing = Drawing(400, 200)
  1024. data = [()]
  1025. bc = VerticalBarChart()
  1026. bc.x = 50
  1027. bc.y = 50
  1028. bc.height = 125
  1029. bc.width = 300
  1030. bc.data = data
  1031. bc.strokeColor = colors.black
  1032. bc.valueAxis.valueMin = 0
  1033. bc.valueAxis.valueMax = 60
  1034. bc.valueAxis.valueStep = 15
  1035. bc.categoryAxis.labels.boxAnchor = 'ne'
  1036. bc.categoryAxis.labels.dx = 8
  1037. bc.categoryAxis.labels.dy = -2
  1038. bc.categoryAxis.categoryNames = []
  1039. drawing.add(bc)
  1040. return drawing
  1041. def sampleV1():
  1042. "Sample of multi-series bar chart."
  1043. drawing = Drawing(400, 200)
  1044. data = [
  1045. (13, 5, 20, 22, 37, 45, 19, 4),
  1046. (14, 6, 21, 23, 38, 46, 20, 5)
  1047. ]
  1048. bc = VerticalBarChart()
  1049. bc.x = 50
  1050. bc.y = 50
  1051. bc.height = 125
  1052. bc.width = 300
  1053. bc.data = data
  1054. bc.strokeColor = colors.black
  1055. bc.valueAxis.valueMin = 0
  1056. bc.valueAxis.valueMax = 60
  1057. bc.valueAxis.valueStep = 15
  1058. bc.categoryAxis.labels.boxAnchor = 'ne'
  1059. bc.categoryAxis.labels.dx = 8
  1060. bc.categoryAxis.labels.dy = -2
  1061. bc.categoryAxis.labels.angle = 30
  1062. catNames = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ')
  1063. catNames = [n+'-99' for n in catNames]
  1064. bc.categoryAxis.categoryNames = catNames
  1065. drawing.add(bc)
  1066. return drawing
  1067. def sampleV2a():
  1068. "Sample of multi-series bar chart."
  1069. data = [(2.4, -5.7, 2, 5, 9.2),
  1070. (0.6, -4.9, -3, 4, 6.8)
  1071. ]
  1072. labels = ("Q3 2000", "Year to Date", "12 months",
  1073. "Annualised\n3 years", "Since 07.10.99")
  1074. drawing = Drawing(400, 200)
  1075. bc = VerticalBarChart()
  1076. bc.x = 50
  1077. bc.y = 50
  1078. bc.height = 120
  1079. bc.width = 300
  1080. bc.data = data
  1081. bc.barSpacing = 0
  1082. bc.groupSpacing = 10
  1083. bc.barWidth = 10
  1084. bc.valueAxis.valueMin = -15
  1085. bc.valueAxis.valueMax = +15
  1086. bc.valueAxis.valueStep = 5
  1087. bc.valueAxis.labels.fontName = 'Helvetica'
  1088. bc.valueAxis.labels.fontSize = 8
  1089. bc.valueAxis.labels.boxAnchor = 'n' # irrelevant (becomes 'c')
  1090. bc.valueAxis.labels.textAnchor = 'middle'
  1091. bc.categoryAxis.categoryNames = labels
  1092. bc.categoryAxis.labels.fontName = 'Helvetica'
  1093. bc.categoryAxis.labels.fontSize = 8
  1094. bc.categoryAxis.labels.dy = -60
  1095. drawing.add(bc)
  1096. return drawing
  1097. def sampleV2b():
  1098. "Sample of multi-series bar chart."
  1099. data = [(2.4, -5.7, 2, 5, 9.2),
  1100. (0.6, -4.9, -3, 4, 6.8)
  1101. ]
  1102. labels = ("Q3 2000", "Year to Date", "12 months",
  1103. "Annualised\n3 years", "Since 07.10.99")
  1104. drawing = Drawing(400, 200)
  1105. bc = VerticalBarChart()
  1106. bc.x = 50
  1107. bc.y = 50
  1108. bc.height = 120
  1109. bc.width = 300
  1110. bc.data = data
  1111. bc.barSpacing = 5
  1112. bc.groupSpacing = 10
  1113. bc.barWidth = 10
  1114. bc.valueAxis.valueMin = -15
  1115. bc.valueAxis.valueMax = +15
  1116. bc.valueAxis.valueStep = 5
  1117. bc.valueAxis.labels.fontName = 'Helvetica'
  1118. bc.valueAxis.labels.fontSize = 8
  1119. bc.valueAxis.labels.boxAnchor = 'n' # irrelevant (becomes 'c')
  1120. bc.valueAxis.labels.textAnchor = 'middle'
  1121. bc.categoryAxis.categoryNames = labels
  1122. bc.categoryAxis.labels.fontName = 'Helvetica'
  1123. bc.categoryAxis.labels.fontSize = 8
  1124. bc.categoryAxis.labels.dy = -60
  1125. drawing.add(bc)
  1126. return drawing
  1127. def sampleV2c():
  1128. "Sample of multi-series bar chart."
  1129. data = [(2.4, -5.7, 2, 5, 9.99),
  1130. (0.6, -4.9, -3, 4, 9.99)
  1131. ]
  1132. labels = ("Q3 2000", "Year to Date", "12 months",
  1133. "Annualised\n3 years", "Since 07.10.99")
  1134. drawing = Drawing(400, 200)
  1135. bc = VerticalBarChart()
  1136. bc.x = 50
  1137. bc.y = 50
  1138. bc.height = 120
  1139. bc.width = 300
  1140. bc.data = data
  1141. bc.barSpacing = 2
  1142. bc.groupSpacing = 10
  1143. bc.barWidth = 10
  1144. bc.valueAxis.valueMin = -15
  1145. bc.valueAxis.valueMax = +15
  1146. bc.valueAxis.valueStep = 5
  1147. bc.valueAxis.labels.fontName = 'Helvetica'
  1148. bc.valueAxis.labels.fontSize = 8
  1149. bc.categoryAxis.categoryNames = labels
  1150. bc.categoryAxis.labels.fontName = 'Helvetica'
  1151. bc.categoryAxis.labels.fontSize = 8
  1152. bc.valueAxis.labels.boxAnchor = 'n'
  1153. bc.valueAxis.labels.textAnchor = 'middle'
  1154. bc.categoryAxis.labels.dy = -60
  1155. bc.barLabels.nudge = 10
  1156. bc.barLabelFormat = '%0.2f'
  1157. bc.barLabels.dx = 0
  1158. bc.barLabels.dy = 0
  1159. bc.barLabels.boxAnchor = 'n' # irrelevant (becomes 'c')
  1160. bc.barLabels.fontName = 'Helvetica'
  1161. bc.barLabels.fontSize = 6
  1162. drawing.add(bc)
  1163. return drawing
  1164. def sampleV3():
  1165. "Faked horizontal bar chart using a vertical real one (deprecated)."
  1166. names = ("UK Equities", "US Equities", "European Equities", "Japanese Equities",
  1167. "Pacific (ex Japan) Equities", "Emerging Markets Equities",
  1168. "UK Bonds", "Overseas Bonds", "UK Index-Linked", "Cash")
  1169. series1 = (-1.5, 0.3, 0.5, 1.0, 0.8, 0.7, 0.4, 0.1, 1.0, 0.3)
  1170. series2 = (0.0, 0.33, 0.55, 1.1, 0.88, 0.77, 0.44, 0.11, 1.10, 0.33)
  1171. assert len(names) == len(series1), "bad data"
  1172. assert len(names) == len(series2), "bad data"
  1173. drawing = Drawing(400, 200)
  1174. bc = VerticalBarChart()
  1175. bc.x = 0
  1176. bc.y = 0
  1177. bc.height = 100
  1178. bc.width = 150
  1179. bc.data = (series1,)
  1180. bc.bars.fillColor = colors.green
  1181. bc.barLabelFormat = '%0.2f'
  1182. bc.barLabels.dx = 0
  1183. bc.barLabels.dy = 0
  1184. bc.barLabels.boxAnchor = 'w' # irrelevant (becomes 'c')
  1185. bc.barLabels.angle = 90
  1186. bc.barLabels.fontName = 'Helvetica'
  1187. bc.barLabels.fontSize = 6
  1188. bc.barLabels.nudge = 10
  1189. bc.valueAxis.visible = 0
  1190. bc.valueAxis.valueMin = -2
  1191. bc.valueAxis.valueMax = +2
  1192. bc.valueAxis.valueStep = 1
  1193. bc.categoryAxis.tickUp = 0
  1194. bc.categoryAxis.tickDown = 0
  1195. bc.categoryAxis.categoryNames = names
  1196. bc.categoryAxis.labels.angle = 90
  1197. bc.categoryAxis.labels.boxAnchor = 'w'
  1198. bc.categoryAxis.labels.dx = 0
  1199. bc.categoryAxis.labels.dy = -125
  1200. bc.categoryAxis.labels.fontName = 'Helvetica'
  1201. bc.categoryAxis.labels.fontSize = 6
  1202. g = Group(bc)
  1203. g.translate(100, 175)
  1204. g.rotate(-90)
  1205. drawing.add(g)
  1206. return drawing
  1207. def sampleV4a():
  1208. "A bar chart showing value axis region starting at *exactly* zero."
  1209. drawing = Drawing(400, 200)
  1210. data = [(13, 20)]
  1211. bc = VerticalBarChart()
  1212. bc.x = 50
  1213. bc.y = 50
  1214. bc.height = 125
  1215. bc.width = 300
  1216. bc.data = data
  1217. bc.strokeColor = colors.black
  1218. bc.valueAxis.valueMin = 0
  1219. bc.valueAxis.valueMax = 60
  1220. bc.valueAxis.valueStep = 15
  1221. bc.categoryAxis.labels.boxAnchor = 'n'
  1222. bc.categoryAxis.labels.dy = -5
  1223. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1224. drawing.add(bc)
  1225. return drawing
  1226. def sampleV4b():
  1227. "A bar chart showing value axis region starting *below* zero."
  1228. drawing = Drawing(400, 200)
  1229. data = [(13, 20)]
  1230. bc = VerticalBarChart()
  1231. bc.x = 50
  1232. bc.y = 50
  1233. bc.height = 125
  1234. bc.width = 300
  1235. bc.data = data
  1236. bc.strokeColor = colors.black
  1237. bc.valueAxis.valueMin = -10
  1238. bc.valueAxis.valueMax = 60
  1239. bc.valueAxis.valueStep = 15
  1240. bc.categoryAxis.labels.boxAnchor = 'n'
  1241. bc.categoryAxis.labels.dy = -5
  1242. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1243. drawing.add(bc)
  1244. return drawing
  1245. def sampleV4c():
  1246. "A bar chart showing value axis region staring *above* zero."
  1247. drawing = Drawing(400, 200)
  1248. data = [(13, 20)]
  1249. bc = VerticalBarChart()
  1250. bc.x = 50
  1251. bc.y = 50
  1252. bc.height = 125
  1253. bc.width = 300
  1254. bc.data = data
  1255. bc.strokeColor = colors.black
  1256. bc.valueAxis.valueMin = 10
  1257. bc.valueAxis.valueMax = 60
  1258. bc.valueAxis.valueStep = 15
  1259. bc.categoryAxis.labels.boxAnchor = 'n'
  1260. bc.categoryAxis.labels.dy = -5
  1261. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1262. drawing.add(bc)
  1263. return drawing
  1264. def sampleV4d():
  1265. "A bar chart showing value axis region entirely *below* zero."
  1266. drawing = Drawing(400, 200)
  1267. data = [(-13, -20)]
  1268. bc = VerticalBarChart()
  1269. bc.x = 50
  1270. bc.y = 50
  1271. bc.height = 125
  1272. bc.width = 300
  1273. bc.data = data
  1274. bc.strokeColor = colors.black
  1275. bc.valueAxis.valueMin = -30
  1276. bc.valueAxis.valueMax = -10
  1277. bc.valueAxis.valueStep = 15
  1278. bc.categoryAxis.labels.boxAnchor = 'n'
  1279. bc.categoryAxis.labels.dy = -5
  1280. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1281. drawing.add(bc)
  1282. return drawing
  1283. ###
  1284. ##dataSample5 = [(10, 20), (20, 30), (30, 40), (40, 50), (50, 60)]
  1285. ##dataSample5 = [(10, 60), (20, 50), (30, 40), (40, 30), (50, 20)]
  1286. dataSample5 = [(10, 60), (20, 50), (30, 40), (40, 30)]
  1287. def sampleV5a():
  1288. "A simple bar chart with no expressed spacing attributes."
  1289. drawing = Drawing(400, 200)
  1290. data = dataSample5
  1291. bc = VerticalBarChart()
  1292. bc.x = 50
  1293. bc.y = 50
  1294. bc.height = 125
  1295. bc.width = 300
  1296. bc.data = data
  1297. bc.strokeColor = colors.black
  1298. bc.valueAxis.valueMin = 0
  1299. bc.valueAxis.valueMax = 60
  1300. bc.valueAxis.valueStep = 15
  1301. bc.categoryAxis.labels.boxAnchor = 'n'
  1302. bc.categoryAxis.labels.dy = -5
  1303. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1304. drawing.add(bc)
  1305. return drawing
  1306. def sampleV5b():
  1307. "A simple bar chart with proportional spacing."
  1308. drawing = Drawing(400, 200)
  1309. data = dataSample5
  1310. bc = VerticalBarChart()
  1311. bc.x = 50
  1312. bc.y = 50
  1313. bc.height = 125
  1314. bc.width = 300
  1315. bc.data = data
  1316. bc.strokeColor = colors.black
  1317. bc.useAbsolute = 0
  1318. bc.barWidth = 40
  1319. bc.groupSpacing = 20
  1320. bc.barSpacing = 10
  1321. bc.valueAxis.valueMin = 0
  1322. bc.valueAxis.valueMax = 60
  1323. bc.valueAxis.valueStep = 15
  1324. bc.categoryAxis.labels.boxAnchor = 'n'
  1325. bc.categoryAxis.labels.dy = -5
  1326. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1327. drawing.add(bc)
  1328. return drawing
  1329. def sampleV5c1():
  1330. "Make sampe simple bar chart but with absolute spacing."
  1331. drawing = Drawing(400, 200)
  1332. data = dataSample5
  1333. bc = VerticalBarChart()
  1334. bc.x = 50
  1335. bc.y = 50
  1336. bc.height = 125
  1337. bc.width = 300
  1338. bc.data = data
  1339. bc.strokeColor = colors.black
  1340. bc.useAbsolute = 1
  1341. bc.barWidth = 40
  1342. bc.groupSpacing = 0
  1343. bc.barSpacing = 0
  1344. bc.valueAxis.valueMin = 0
  1345. bc.valueAxis.valueMax = 60
  1346. bc.valueAxis.valueStep = 15
  1347. bc.categoryAxis.labels.boxAnchor = 'n'
  1348. bc.categoryAxis.labels.dy = -5
  1349. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1350. drawing.add(bc)
  1351. return drawing
  1352. def sampleV5c2():
  1353. "Make sampe simple bar chart but with absolute spacing."
  1354. drawing = Drawing(400, 200)
  1355. data = dataSample5
  1356. bc = VerticalBarChart()
  1357. bc.x = 50
  1358. bc.y = 50
  1359. bc.height = 125
  1360. bc.width = 300
  1361. bc.data = data
  1362. bc.strokeColor = colors.black
  1363. bc.useAbsolute = 1
  1364. bc.barWidth = 40
  1365. bc.groupSpacing = 20
  1366. bc.barSpacing = 0
  1367. bc.valueAxis.valueMin = 0
  1368. bc.valueAxis.valueMax = 60
  1369. bc.valueAxis.valueStep = 15
  1370. bc.categoryAxis.labels.boxAnchor = 'n'
  1371. bc.categoryAxis.labels.dy = -5
  1372. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1373. drawing.add(bc)
  1374. return drawing
  1375. def sampleV5c3():
  1376. "Make sampe simple bar chart but with absolute spacing."
  1377. drawing = Drawing(400, 200)
  1378. data = dataSample5
  1379. bc = VerticalBarChart()
  1380. bc.x = 50
  1381. bc.y = 50
  1382. bc.height = 125
  1383. bc.width = 300
  1384. bc.data = data
  1385. bc.strokeColor = colors.black
  1386. bc.useAbsolute = 1
  1387. bc.barWidth = 40
  1388. bc.groupSpacing = 0
  1389. bc.barSpacing = 10
  1390. bc.valueAxis.valueMin = 0
  1391. bc.valueAxis.valueMax = 60
  1392. bc.valueAxis.valueStep = 15
  1393. bc.categoryAxis.labels.boxAnchor = 'n'
  1394. bc.categoryAxis.labels.dy = -5
  1395. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1396. drawing.add(bc)
  1397. return drawing
  1398. def sampleV5c4():
  1399. "Make sampe simple bar chart but with absolute spacing."
  1400. drawing = Drawing(400, 200)
  1401. data = dataSample5
  1402. bc = VerticalBarChart()
  1403. bc.x = 50
  1404. bc.y = 50
  1405. bc.height = 125
  1406. bc.width = 300
  1407. bc.data = data
  1408. bc.strokeColor = colors.black
  1409. bc.useAbsolute = 1
  1410. bc.barWidth = 40
  1411. bc.groupSpacing = 20
  1412. bc.barSpacing = 10
  1413. bc.valueAxis.valueMin = 0
  1414. bc.valueAxis.valueMax = 60
  1415. bc.valueAxis.valueStep = 15
  1416. bc.categoryAxis.labels.boxAnchor = 'n'
  1417. bc.categoryAxis.labels.dy = -5
  1418. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1419. drawing.add(bc)
  1420. return drawing
  1421. # Horizontal samples
  1422. def sampleH0a():
  1423. "Make a slightly pathologic bar chart with only TWO data items."
  1424. drawing = Drawing(400, 200)
  1425. data = [(13, 20)]
  1426. bc = HorizontalBarChart()
  1427. bc.x = 50
  1428. bc.y = 50
  1429. bc.height = 125
  1430. bc.width = 300
  1431. bc.data = data
  1432. bc.strokeColor = colors.black
  1433. bc.valueAxis.valueMin = 0
  1434. bc.valueAxis.valueMax = 60
  1435. bc.valueAxis.valueStep = 15
  1436. bc.categoryAxis.labels.boxAnchor = 'se'
  1437. bc.categoryAxis.labels.angle = 30
  1438. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1439. drawing.add(bc)
  1440. return drawing
  1441. def sampleH0b():
  1442. "Make a pathologic bar chart with only ONE data item."
  1443. drawing = Drawing(400, 200)
  1444. data = [(42,)]
  1445. bc = HorizontalBarChart()
  1446. bc.x = 50
  1447. bc.y = 50
  1448. bc.height = 125
  1449. bc.width = 300
  1450. bc.data = data
  1451. bc.strokeColor = colors.black
  1452. bc.valueAxis.valueMin = 0
  1453. bc.valueAxis.valueMax = 50
  1454. bc.valueAxis.valueStep = 15
  1455. bc.categoryAxis.labels.boxAnchor = 'se'
  1456. bc.categoryAxis.labels.angle = 30
  1457. bc.categoryAxis.categoryNames = ['Jan-99']
  1458. drawing.add(bc)
  1459. return drawing
  1460. def sampleH0c():
  1461. "Make a really pathologic bar chart with NO data items at all!"
  1462. drawing = Drawing(400, 200)
  1463. data = [()]
  1464. bc = HorizontalBarChart()
  1465. bc.x = 50
  1466. bc.y = 50
  1467. bc.height = 125
  1468. bc.width = 300
  1469. bc.data = data
  1470. bc.strokeColor = colors.black
  1471. bc.valueAxis.valueMin = 0
  1472. bc.valueAxis.valueMax = 60
  1473. bc.valueAxis.valueStep = 15
  1474. bc.categoryAxis.labels.boxAnchor = 'se'
  1475. bc.categoryAxis.labels.angle = 30
  1476. bc.categoryAxis.categoryNames = []
  1477. drawing.add(bc)
  1478. return drawing
  1479. def sampleH1():
  1480. "Sample of multi-series bar chart."
  1481. drawing = Drawing(400, 200)
  1482. data = [
  1483. (13, 5, 20, 22, 37, 45, 19, 4),
  1484. (14, 6, 21, 23, 38, 46, 20, 5)
  1485. ]
  1486. bc = HorizontalBarChart()
  1487. bc.x = 50
  1488. bc.y = 50
  1489. bc.height = 125
  1490. bc.width = 300
  1491. bc.data = data
  1492. bc.strokeColor = colors.black
  1493. bc.valueAxis.valueMin = 0
  1494. bc.valueAxis.valueMax = 60
  1495. bc.valueAxis.valueStep = 15
  1496. bc.categoryAxis.labels.boxAnchor = 'e'
  1497. catNames = 'Jan Feb Mar Apr May Jun Jul Aug'.split(' ')
  1498. catNames = [n+'-99' for n in catNames]
  1499. bc.categoryAxis.categoryNames = catNames
  1500. drawing.add(bc, 'barchart')
  1501. return drawing
  1502. def sampleH2a():
  1503. "Sample of multi-series bar chart."
  1504. data = [(2.4, -5.7, 2, 5, 9.2),
  1505. (0.6, -4.9, -3, 4, 6.8)
  1506. ]
  1507. labels = ("Q3 2000", "Year to Date", "12 months",
  1508. "Annualised\n3 years", "Since 07.10.99")
  1509. drawing = Drawing(400, 200)
  1510. bc = HorizontalBarChart()
  1511. bc.x = 80
  1512. bc.y = 50
  1513. bc.height = 120
  1514. bc.width = 300
  1515. bc.data = data
  1516. bc.barSpacing = 0
  1517. bc.groupSpacing = 10
  1518. bc.barWidth = 10
  1519. bc.valueAxis.valueMin = -15
  1520. bc.valueAxis.valueMax = +15
  1521. bc.valueAxis.valueStep = 5
  1522. bc.valueAxis.labels.fontName = 'Helvetica'
  1523. bc.valueAxis.labels.fontSize = 8
  1524. bc.valueAxis.labels.boxAnchor = 'n' # irrelevant (becomes 'c')
  1525. bc.valueAxis.labels.textAnchor = 'middle'
  1526. bc.valueAxis.configure(bc.data)
  1527. bc.categoryAxis.categoryNames = labels
  1528. bc.categoryAxis.labels.fontName = 'Helvetica'
  1529. bc.categoryAxis.labels.fontSize = 8
  1530. bc.categoryAxis.labels.dx = -150
  1531. drawing.add(bc)
  1532. return drawing
  1533. def sampleH2b():
  1534. "Sample of multi-series bar chart."
  1535. data = [(2.4, -5.7, 2, 5, 9.2),
  1536. (0.6, -4.9, -3, 4, 6.8)
  1537. ]
  1538. labels = ("Q3 2000", "Year to Date", "12 months",
  1539. "Annualised\n3 years", "Since 07.10.99")
  1540. drawing = Drawing(400, 200)
  1541. bc = HorizontalBarChart()
  1542. bc.x = 80
  1543. bc.y = 50
  1544. bc.height = 120
  1545. bc.width = 300
  1546. bc.data = data
  1547. bc.barSpacing = 5
  1548. bc.groupSpacing = 10
  1549. bc.barWidth = 10
  1550. bc.valueAxis.valueMin = -15
  1551. bc.valueAxis.valueMax = +15
  1552. bc.valueAxis.valueStep = 5
  1553. bc.valueAxis.labels.fontName = 'Helvetica'
  1554. bc.valueAxis.labels.fontSize = 8
  1555. bc.valueAxis.labels.boxAnchor = 'n' # irrelevant (becomes 'c')
  1556. bc.valueAxis.labels.textAnchor = 'middle'
  1557. bc.categoryAxis.categoryNames = labels
  1558. bc.categoryAxis.labels.fontName = 'Helvetica'
  1559. bc.categoryAxis.labels.fontSize = 8
  1560. bc.categoryAxis.labels.dx = -150
  1561. drawing.add(bc)
  1562. return drawing
  1563. def sampleH2c():
  1564. "Sample of multi-series bar chart."
  1565. data = [(2.4, -5.7, 2, 5, 9.99),
  1566. (0.6, -4.9, -3, 4, 9.99)
  1567. ]
  1568. labels = ("Q3 2000", "Year to Date", "12 months",
  1569. "Annualised\n3 years", "Since 07.10.99")
  1570. drawing = Drawing(400, 200)
  1571. bc = HorizontalBarChart()
  1572. bc.x = 80
  1573. bc.y = 50
  1574. bc.height = 120
  1575. bc.width = 300
  1576. bc.data = data
  1577. bc.barSpacing = 2
  1578. bc.groupSpacing = 10
  1579. bc.barWidth = 10
  1580. bc.valueAxis.valueMin = -15
  1581. bc.valueAxis.valueMax = +15
  1582. bc.valueAxis.valueStep = 5
  1583. bc.valueAxis.labels.fontName = 'Helvetica'
  1584. bc.valueAxis.labels.fontSize = 8
  1585. bc.valueAxis.labels.boxAnchor = 'n'
  1586. bc.valueAxis.labels.textAnchor = 'middle'
  1587. bc.categoryAxis.categoryNames = labels
  1588. bc.categoryAxis.labels.fontName = 'Helvetica'
  1589. bc.categoryAxis.labels.fontSize = 8
  1590. bc.categoryAxis.labels.dx = -150
  1591. bc.barLabels.nudge = 10
  1592. bc.barLabelFormat = '%0.2f'
  1593. bc.barLabels.dx = 0
  1594. bc.barLabels.dy = 0
  1595. bc.barLabels.boxAnchor = 'n' # irrelevant (becomes 'c')
  1596. bc.barLabels.fontName = 'Helvetica'
  1597. bc.barLabels.fontSize = 6
  1598. drawing.add(bc)
  1599. return drawing
  1600. def sampleH3():
  1601. "A really horizontal bar chart (compared to the equivalent faked one)."
  1602. names = ("UK Equities", "US Equities", "European Equities", "Japanese Equities",
  1603. "Pacific (ex Japan) Equities", "Emerging Markets Equities",
  1604. "UK Bonds", "Overseas Bonds", "UK Index-Linked", "Cash")
  1605. series1 = (-1.5, 0.3, 0.5, 1.0, 0.8, 0.7, 0.4, 0.1, 1.0, 0.3)
  1606. series2 = (0.0, 0.33, 0.55, 1.1, 0.88, 0.77, 0.44, 0.11, 1.10, 0.33)
  1607. assert len(names) == len(series1), "bad data"
  1608. assert len(names) == len(series2), "bad data"
  1609. drawing = Drawing(400, 200)
  1610. bc = HorizontalBarChart()
  1611. bc.x = 100
  1612. bc.y = 20
  1613. bc.height = 150
  1614. bc.width = 250
  1615. bc.data = (series1,)
  1616. bc.bars.fillColor = colors.green
  1617. bc.barLabelFormat = '%0.2f'
  1618. bc.barLabels.dx = 0
  1619. bc.barLabels.dy = 0
  1620. bc.barLabels.boxAnchor = 'w' # irrelevant (becomes 'c')
  1621. bc.barLabels.fontName = 'Helvetica'
  1622. bc.barLabels.fontSize = 6
  1623. bc.barLabels.nudge = 10
  1624. bc.valueAxis.visible = 0
  1625. bc.valueAxis.valueMin = -2
  1626. bc.valueAxis.valueMax = +2
  1627. bc.valueAxis.valueStep = 1
  1628. bc.categoryAxis.tickLeft = 0
  1629. bc.categoryAxis.tickRight = 0
  1630. bc.categoryAxis.categoryNames = names
  1631. bc.categoryAxis.labels.boxAnchor = 'w'
  1632. bc.categoryAxis.labels.dx = -170
  1633. bc.categoryAxis.labels.fontName = 'Helvetica'
  1634. bc.categoryAxis.labels.fontSize = 6
  1635. g = Group(bc)
  1636. drawing.add(g)
  1637. return drawing
  1638. def sampleH4a():
  1639. "A bar chart showing value axis region starting at *exactly* zero."
  1640. drawing = Drawing(400, 200)
  1641. data = [(13, 20)]
  1642. bc = HorizontalBarChart()
  1643. bc.x = 50
  1644. bc.y = 50
  1645. bc.height = 125
  1646. bc.width = 300
  1647. bc.data = data
  1648. bc.strokeColor = colors.black
  1649. bc.valueAxis.valueMin = 0
  1650. bc.valueAxis.valueMax = 60
  1651. bc.valueAxis.valueStep = 15
  1652. bc.categoryAxis.labels.boxAnchor = 'e'
  1653. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1654. drawing.add(bc)
  1655. return drawing
  1656. def sampleH4b():
  1657. "A bar chart showing value axis region starting *below* zero."
  1658. drawing = Drawing(400, 200)
  1659. data = [(13, 20)]
  1660. bc = HorizontalBarChart()
  1661. bc.x = 50
  1662. bc.y = 50
  1663. bc.height = 125
  1664. bc.width = 300
  1665. bc.data = data
  1666. bc.strokeColor = colors.black
  1667. bc.valueAxis.valueMin = -10
  1668. bc.valueAxis.valueMax = 60
  1669. bc.valueAxis.valueStep = 15
  1670. bc.categoryAxis.labels.boxAnchor = 'e'
  1671. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1672. drawing.add(bc)
  1673. return drawing
  1674. def sampleH4c():
  1675. "A bar chart showing value axis region starting *above* zero."
  1676. drawing = Drawing(400, 200)
  1677. data = [(13, 20)]
  1678. bc = HorizontalBarChart()
  1679. bc.x = 50
  1680. bc.y = 50
  1681. bc.height = 125
  1682. bc.width = 300
  1683. bc.data = data
  1684. bc.strokeColor = colors.black
  1685. bc.valueAxis.valueMin = 10
  1686. bc.valueAxis.valueMax = 60
  1687. bc.valueAxis.valueStep = 15
  1688. bc.categoryAxis.labels.boxAnchor = 'e'
  1689. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1690. drawing.add(bc)
  1691. return drawing
  1692. def sampleH4d():
  1693. "A bar chart showing value axis region entirely *below* zero."
  1694. drawing = Drawing(400, 200)
  1695. data = [(-13, -20)]
  1696. bc = HorizontalBarChart()
  1697. bc.x = 50
  1698. bc.y = 50
  1699. bc.height = 125
  1700. bc.width = 300
  1701. bc.data = data
  1702. bc.strokeColor = colors.black
  1703. bc.valueAxis.valueMin = -30
  1704. bc.valueAxis.valueMax = -10
  1705. bc.valueAxis.valueStep = 15
  1706. bc.categoryAxis.labels.boxAnchor = 'e'
  1707. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1708. drawing.add(bc)
  1709. return drawing
  1710. dataSample5 = [(10, 60), (20, 50), (30, 40), (40, 30)]
  1711. def sampleH5a():
  1712. "A simple bar chart with no expressed spacing attributes."
  1713. drawing = Drawing(400, 200)
  1714. data = dataSample5
  1715. bc = HorizontalBarChart()
  1716. bc.x = 50
  1717. bc.y = 50
  1718. bc.height = 125
  1719. bc.width = 300
  1720. bc.data = data
  1721. bc.strokeColor = colors.black
  1722. bc.valueAxis.valueMin = 0
  1723. bc.valueAxis.valueMax = 60
  1724. bc.valueAxis.valueStep = 15
  1725. bc.categoryAxis.labels.boxAnchor = 'e'
  1726. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1727. drawing.add(bc)
  1728. return drawing
  1729. def sampleH5b():
  1730. "A simple bar chart with proportional spacing."
  1731. drawing = Drawing(400, 200)
  1732. data = dataSample5
  1733. bc = HorizontalBarChart()
  1734. bc.x = 50
  1735. bc.y = 50
  1736. bc.height = 125
  1737. bc.width = 300
  1738. bc.data = data
  1739. bc.strokeColor = colors.black
  1740. bc.useAbsolute = 0
  1741. bc.barWidth = 40
  1742. bc.groupSpacing = 20
  1743. bc.barSpacing = 10
  1744. bc.valueAxis.valueMin = 0
  1745. bc.valueAxis.valueMax = 60
  1746. bc.valueAxis.valueStep = 15
  1747. bc.categoryAxis.labels.boxAnchor = 'e'
  1748. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1749. drawing.add(bc)
  1750. return drawing
  1751. def sampleH5c1():
  1752. "A simple bar chart with absolute spacing."
  1753. drawing = Drawing(400, 200)
  1754. data = dataSample5
  1755. bc = HorizontalBarChart()
  1756. bc.x = 50
  1757. bc.y = 50
  1758. bc.height = 125
  1759. bc.width = 300
  1760. bc.data = data
  1761. bc.strokeColor = colors.black
  1762. bc.useAbsolute = 1
  1763. bc.barWidth = 10
  1764. bc.groupSpacing = 0
  1765. bc.barSpacing = 0
  1766. bc.valueAxis.valueMin = 0
  1767. bc.valueAxis.valueMax = 60
  1768. bc.valueAxis.valueStep = 15
  1769. bc.categoryAxis.labels.boxAnchor = 'e'
  1770. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1771. drawing.add(bc)
  1772. return drawing
  1773. def sampleH5c2():
  1774. "Simple bar chart with absolute spacing."
  1775. drawing = Drawing(400, 200)
  1776. data = dataSample5
  1777. bc = HorizontalBarChart()
  1778. bc.x = 50
  1779. bc.y = 50
  1780. bc.height = 125
  1781. bc.width = 300
  1782. bc.data = data
  1783. bc.strokeColor = colors.black
  1784. bc.useAbsolute = 1
  1785. bc.barWidth = 10
  1786. bc.groupSpacing = 20
  1787. bc.barSpacing = 0
  1788. bc.valueAxis.valueMin = 0
  1789. bc.valueAxis.valueMax = 60
  1790. bc.valueAxis.valueStep = 15
  1791. bc.categoryAxis.labels.boxAnchor = 'e'
  1792. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1793. drawing.add(bc)
  1794. return drawing
  1795. def sampleH5c3():
  1796. "Simple bar chart with absolute spacing."
  1797. drawing = Drawing(400, 200)
  1798. data = dataSample5
  1799. bc = HorizontalBarChart()
  1800. bc.x = 50
  1801. bc.y = 20
  1802. bc.height = 155
  1803. bc.width = 300
  1804. bc.data = data
  1805. bc.strokeColor = colors.black
  1806. bc.useAbsolute = 1
  1807. bc.barWidth = 10
  1808. bc.groupSpacing = 0
  1809. bc.barSpacing = 2
  1810. bc.valueAxis.valueMin = 0
  1811. bc.valueAxis.valueMax = 60
  1812. bc.valueAxis.valueStep = 15
  1813. bc.categoryAxis.labels.boxAnchor = 'e'
  1814. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1815. drawing.add(bc)
  1816. return drawing
  1817. def sampleH5c4():
  1818. "Simple bar chart with absolute spacing."
  1819. drawing = Drawing(400, 200)
  1820. data = dataSample5
  1821. bc = HorizontalBarChart()
  1822. bc.x = 50
  1823. bc.y = 50
  1824. bc.height = 125
  1825. bc.width = 300
  1826. bc.data = data
  1827. bc.strokeColor = colors.black
  1828. bc.useAbsolute = 1
  1829. bc.barWidth = 10
  1830. bc.groupSpacing = 20
  1831. bc.barSpacing = 10
  1832. bc.valueAxis.valueMin = 0
  1833. bc.valueAxis.valueMax = 60
  1834. bc.valueAxis.valueStep = 15
  1835. bc.categoryAxis.labels.boxAnchor = 'e'
  1836. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1837. drawing.add(bc)
  1838. return drawing
  1839. def sampleSymbol1():
  1840. "Simple bar chart using symbol attribute."
  1841. drawing = Drawing(400, 200)
  1842. data = dataSample5
  1843. bc = VerticalBarChart()
  1844. bc.x = 50
  1845. bc.y = 50
  1846. bc.height = 125
  1847. bc.width = 300
  1848. bc.data = data
  1849. bc.strokeColor = colors.black
  1850. bc.barWidth = 10
  1851. bc.groupSpacing = 15
  1852. bc.barSpacing = 3
  1853. bc.valueAxis.valueMin = 0
  1854. bc.valueAxis.valueMax = 60
  1855. bc.valueAxis.valueStep = 15
  1856. bc.categoryAxis.labels.boxAnchor = 'e'
  1857. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1858. from reportlab.graphics.widgets.grids import ShadedRect
  1859. sym1 = ShadedRect()
  1860. sym1.fillColorStart = colors.black
  1861. sym1.fillColorEnd = colors.blue
  1862. sym1.orientation = 'horizontal'
  1863. sym1.strokeWidth = 0
  1864. sym2 = ShadedRect()
  1865. sym2.fillColorStart = colors.black
  1866. sym2.fillColorEnd = colors.pink
  1867. sym2.orientation = 'horizontal'
  1868. sym2.strokeWidth = 0
  1869. sym3 = ShadedRect()
  1870. sym3.fillColorStart = colors.blue
  1871. sym3.fillColorEnd = colors.white
  1872. sym3.orientation = 'vertical'
  1873. sym3.cylinderMode = 1
  1874. sym3.strokeWidth = 0
  1875. bc.bars.symbol = sym1
  1876. bc.bars[2].symbol = sym2
  1877. bc.bars[3].symbol = sym3
  1878. drawing.add(bc)
  1879. return drawing
  1880. def sampleStacked1():
  1881. "Simple bar chart using symbol attribute."
  1882. drawing = Drawing(400, 200)
  1883. data = dataSample5
  1884. bc = VerticalBarChart()
  1885. bc.categoryAxis.style = 'stacked'
  1886. bc.x = 50
  1887. bc.y = 50
  1888. bc.height = 125
  1889. bc.width = 300
  1890. bc.data = data
  1891. bc.strokeColor = colors.black
  1892. bc.barWidth = 10
  1893. bc.groupSpacing = 15
  1894. bc.valueAxis.valueMin = 0
  1895. bc.categoryAxis.labels.boxAnchor = 'e'
  1896. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1897. from reportlab.graphics.widgets.grids import ShadedRect
  1898. bc.bars.symbol = ShadedRect()
  1899. bc.bars.symbol.fillColorStart = colors.red
  1900. bc.bars.symbol.fillColorEnd = colors.white
  1901. bc.bars.symbol.orientation = 'vertical'
  1902. bc.bars.symbol.cylinderMode = 1
  1903. bc.bars.symbol.strokeWidth = 0
  1904. bc.bars[1].symbol = ShadedRect()
  1905. bc.bars[1].symbol.fillColorStart = colors.magenta
  1906. bc.bars[1].symbol.fillColorEnd = colors.white
  1907. bc.bars[1].symbol.orientation = 'vertical'
  1908. bc.bars[1].symbol.cylinderMode = 1
  1909. bc.bars[1].symbol.strokeWidth = 0
  1910. bc.bars[2].symbol = ShadedRect()
  1911. bc.bars[2].symbol.fillColorStart = colors.green
  1912. bc.bars[2].symbol.fillColorEnd = colors.white
  1913. bc.bars[2].symbol.orientation = 'vertical'
  1914. bc.bars[2].symbol.cylinderMode = 1
  1915. bc.bars[2].symbol.strokeWidth = 0
  1916. bc.bars[3].symbol = ShadedRect()
  1917. bc.bars[3].symbol.fillColorStart = colors.blue
  1918. bc.bars[3].symbol.fillColorEnd = colors.white
  1919. bc.bars[3].symbol.orientation = 'vertical'
  1920. bc.bars[3].symbol.cylinderMode = 1
  1921. bc.bars[3].symbol.strokeWidth = 0
  1922. drawing.add(bc)
  1923. return drawing
  1924. #class version of function sampleH5c4 above
  1925. class SampleH5c4(Drawing):
  1926. "Simple bar chart with absolute spacing."
  1927. def __init__(self,width=400,height=200,*args,**kw):
  1928. Drawing.__init__(self,width,height,*args,**kw)
  1929. bc = HorizontalBarChart()
  1930. bc.x = 50
  1931. bc.y = 50
  1932. bc.height = 125
  1933. bc.width = 300
  1934. bc.data = dataSample5
  1935. bc.strokeColor = colors.black
  1936. bc.useAbsolute = 1
  1937. bc.barWidth = 10
  1938. bc.groupSpacing = 20
  1939. bc.barSpacing = 10
  1940. bc.valueAxis.valueMin = 0
  1941. bc.valueAxis.valueMax = 60
  1942. bc.valueAxis.valueStep = 15
  1943. bc.categoryAxis.labels.boxAnchor = 'e'
  1944. bc.categoryAxis.categoryNames = ['Ying', 'Yang']
  1945. self.add(bc,name='HBC')
  1946. bc._computeSimpleBarLabelPositions()