1
#Copyright ReportLab Europe Ltd. 2000-2004
2
#see license.txt for license details
3
#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/graphics/charts/barcharts.py
4
"""This module defines a variety of Bar Chart components.
6
The basic flavors are Side-by-side, available in horizontal and
9
Stacked and percentile bar charts to follow...
11
__version__=''' $Id$ '''
14
from types import FunctionType, StringType
16
from reportlab.lib import colors
17
from reportlab.lib.validators import isNumber, isColor, isColorOrNone, isListOfStrings, SequenceOf, isBoolean, isNoneOrShape
18
from reportlab.lib.formatters import Formatter
19
from reportlab.lib.attrmap import AttrMap, AttrMapValue
20
from reportlab.pdfbase.pdfmetrics import stringWidth
21
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder
22
from reportlab.graphics.shapes import Line, Rect, Group, Drawing, NotImplementedError
23
from reportlab.graphics.charts.axes import XCategoryAxis, YValueAxis
24
from reportlab.graphics.charts.axes import YCategoryAxis, XValueAxis
25
from reportlab.graphics.charts.textlabels import BarChartLabel, NA_Label, NoneOrInstanceOfNA_Label
26
from reportlab.graphics.charts.areas import PlotArea
28
class BarChartProperties(PropHolder):
30
strokeColor = AttrMapValue(isColorOrNone, desc='Color of the bar border.'),
31
fillColor = AttrMapValue(isColorOrNone, desc='Color of the bar interior area.'),
32
strokeWidth = AttrMapValue(isNumber, desc='Width of the bar border.'),
33
symbol = AttrMapValue(None, desc='A widget to be used instead of a normal bar.'),
37
self.strokeColor = None
38
self.fillColor = colors.blue
39
self.strokeWidth = 0.5
43
class BarChart(PlotArea):
44
"Abstract base class, unusable by itself."
46
_attrMap = AttrMap(BASE=PlotArea,
47
useAbsolute = AttrMapValue(isNumber, desc='Flag to use absolute spacing values.'),
48
barWidth = AttrMapValue(isNumber, desc='The width of an individual bar.'),
49
groupSpacing = AttrMapValue(isNumber, desc='Width between groups of bars.'),
50
barSpacing = AttrMapValue(isNumber, desc='Width between individual bars.'),
51
bars = AttrMapValue(None, desc='Handle of the individual bars.'),
52
valueAxis = AttrMapValue(None, desc='Handle of the value axis.'),
53
categoryAxis = AttrMapValue(None, desc='Handle of the category axis.'),
54
data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) numbers.'),
55
barLabels = AttrMapValue(None, desc='Handle to the list of bar labels.'),
56
barLabelFormat = AttrMapValue(None, desc='Formatting string or function used for bar labels.'),
57
barLabelCallOut = AttrMapValue(None, desc='Callout function(label)\nlabel._callOutInfo = (self,g,rowNo,colNo,x,y,width,height,x00,y00,x0,y0)'),
58
barLabelArray = AttrMapValue(None, desc='explicit array of bar label values, must match size of data if present.'),
59
reversePlotOrder = AttrMapValue(isBoolean, desc='If true, reverse common category plot order.'),
60
naLabel = AttrMapValue(NoneOrInstanceOfNA_Label, desc='Label to use for N/A values.'),
61
annotations = AttrMapValue(None, desc='list of callables, will be called with self, xscale, yscale.'),
65
assert self.__class__.__name__ not in ('BarChart','BarChart3D'), 'Abstract Class %s Instantiated' % self.__class__.__name__
68
self.categoryAxis = YCategoryAxis()
69
self.valueAxis = XValueAxis()
71
self.categoryAxis = XCategoryAxis()
72
self.valueAxis = YValueAxis()
74
PlotArea.__init__(self)
76
self.reversePlotOrder = 0
79
# this defines two series of 3 points. Just an example.
80
self.data = [(100,110,120,130),
83
# control bar spacing. is useAbsolute = 1 then
84
# the next parameters are in points; otherwise
85
# they are 'proportions' and are normalized to
86
# fit the available space. Half a barSpacing
87
# is allocated at the beginning and end of the
89
self.useAbsolute = 0 #- not done yet
94
self.barLabels = TypedPropertyCollection(BarChartLabel)
95
self.barLabels.boxAnchor = 'c'
96
self.barLabels.textAnchor = 'middle'
97
self.barLabelFormat = None
98
self.barLabelArray = None
99
# this says whether the origin is inside or outside
100
# the bar - +10 means put the origin ten points
101
# above the tip of the bar if value > 0, or ten
102
# points inside if bar value < 0. This is different
103
# to label dx/dy which are not dependent on the
105
self.barLabels.nudge = 0
107
# if you have multiple series, by default they butt
110
# we really need some well-designed default lists of
111
# colors e.g. from Tufte. These will be used in a
112
# cycle to set the fill color of each series.
113
self.bars = TypedPropertyCollection(BarChartProperties)
114
## self.bars.symbol = None
115
self.bars.strokeWidth = 1
116
self.bars.strokeColor = colors.black
118
self.bars[0].fillColor = colors.red
119
self.bars[1].fillColor = colors.green
120
self.bars[2].fillColor = colors.blue
121
self.naLabel = None#NA_Label()
125
"""Shows basic use of a bar chart"""
126
if self.__class__.__name__=='BarChart':
127
raise NotImplementedError, 'Abstract Class BarChart has no demo'
128
drawing = Drawing(200, 100)
129
bc = self.__class__()
133
def _getConfigureData(self):
134
cA = self.categoryAxis
136
if cA.style not in ('parallel','parallel_3d'):
138
data = max(map(len,_data))*[0]
140
for i in xrange(len(d)):
141
data[i] = data[i] + (d[i] or 0)
142
data = list(_data) + [data]
143
self._configureData = data
145
def _getMinMax(self):
146
'''Attempt to return the data range'''
147
self._getConfigureData()
148
self.valueAxis._setRange(self._configureData)
149
return self.valueAxis._valueMin, self.valueAxis._valueMax
151
def _drawBegin(self,org,length):
152
'''Position and configure value axis, return crossing value'''
154
vA.setPosition(self.x, self.y, length)
155
self._getConfigureData()
156
vA.configure(self._configureData)
158
# if zero is in chart, put the other axis there, otherwise use low
159
crossesAt = vA.scale(0)
160
if crossesAt > org+length or crossesAt<org:
164
def _drawFinish(self):
165
'''finalize the drawing of a barchart'''
166
cA = self.categoryAxis
168
cA.configure(self._configureData)
169
self.calcBarPositions()
171
g.add(self.makeBackground())
173
# ensure any axes have correct spacing set
174
# for grids. It sucks that we have to do
176
if self._flipXY == 0:
178
vA.gridEnd = cA._x+cA._length
180
cA.gridEnd = vA._y+vA._length
183
cA.gridEnd = vA._x+vA._length
185
vA.gridEnd = cA._y+cA._length
187
cA.makeGrid(g,parent=self)
188
vA.makeGrid(g,parent=self)
189
g.add(self.makeBars())
192
for a in getattr(self,'annotations',()): g.add(a(self,cA.scale,vA.scale))
193
del self._configureData
197
def calcBarPositions(self):
198
"""Works out where they go. default vertical.
200
Sets an attribute _barPositions which is a list of
201
lists of (x, y, width, height) matching the data.
204
flipXY = self._flipXY
209
cA = self.categoryAxis
213
seriesCount = self._seriesCount = len(data)
214
self._rowLength = rowLength = max(map(len,data))
215
groupSpacing, barSpacing, barWidth = self.groupSpacing, self.barSpacing, self.barWidth
216
style = self.categoryAxis.style
217
if style=='parallel':
218
groupWidth = groupSpacing+(seriesCount*barWidth)+(seriesCount-1)*barSpacing
219
bGap = barWidth+barSpacing
221
accum = rowLength*[0]
222
groupWidth = groupSpacing+barWidth
224
self._groupWidth = groupWidth
225
useAbsolute = self.useAbsolute
228
# bar dimensions are absolute
231
# bar dimensions are normalized to fit. How wide
232
# notionally is one group of bars?
233
availWidth = cScale(0)[1]
234
normFactor = availWidth/float(groupWidth)
236
print '%d series, %d points per series' % (seriesCount, self._rowLength)
237
print 'width = %d group + (%d bars * %d barWidth) + (%d gaps * %d interBar) = %d total' % (
238
groupSpacing, seriesCount, barWidth,
239
seriesCount-1, barSpacing, groupWidth)
241
# 'Baseline' correction...
244
vm, vM = vA._valueMin, vA._valueMax
248
baseLine = vScale(vm)
250
baseLine = vScale(vM)
251
self._baseLine = baseLine
253
COLUMNS = range(max(map(len,data)))
257
self._normFactor = normFactor
258
width = self.barWidth*normFactor
259
self._barPositions = []
260
reversePlotOrder = self.reversePlotOrder
261
for rowNo in range(seriesCount):
264
xVal = seriesCount-1 - rowNo
267
xVal = 0.5*groupSpacing+xVal*bGap
268
for colNo in COLUMNS:
269
datum = data[rowNo][colNo]
273
x = groupWidth*_cScale(colNo) + xVal + org
275
(g, gW) = cScale(colNo)
276
x = g + normFactor*xVal
282
if style not in ('parallel','parallel_3d'):
283
y = vScale(accum[colNo])
284
if y<baseLine: y = baseLine
285
accum[colNo] = accum[colNo] + datum
289
height = vScale(datum) - y
290
if -1e-8<height<=1e-8:
292
if datum<-1e-8: height = -1e-8
293
barRow.append(flipXY and (y,x,height,width) or (x, y, width, height))
295
self._barPositions.append(barRow)
298
def _getLabelText(self, rowNo, colNo):
299
'''return formatted label text'''
300
labelFmt = self.barLabelFormat
303
elif labelFmt == 'values':
304
labelText = self.barLabelArray[rowNo][colNo]
305
elif type(labelFmt) is StringType:
306
labelText = labelFmt % self.data[rowNo][colNo]
307
elif callable(labelFmt):
308
labelText = labelFmt(self.data[rowNo][colNo])
310
msg = "Unknown formatter type %s, expected string or function" % labelFmt
314
def _labelXY(self,label,x,y,width,height):
315
'Compute x, y for a label'
317
return x + width + (width>=0 and 1 or -1) * label.nudge, y + 0.5*height
319
return x + 0.5*width, y + height + (height>=0 and 1 or -1) * label.nudge
321
def _addBarLabel(self, g, rowNo, colNo, x, y, width, height):
322
text = self._getLabelText(rowNo,colNo)
324
self._addLabel(text, self.barLabels[(rowNo, colNo)], g, rowNo, colNo, x, y, width, height)
326
def _addNABarLabel(self, g, rowNo, colNo, x, y, width, height):
330
v = self.valueAxis._valueMax<=0 and -1e-8 or 1e-8
331
if width is None: width = v
332
if height is None: height = v
333
self._addLabel(na.text, na, g, rowNo, colNo, x, y, width, height)
335
def _addLabel(self, text, label, g, rowNo, colNo, x, y, width, height):
337
labelWidth = stringWidth(text, label.fontName, label.fontSize)
338
x0, y0 = self._labelXY(label,x,y,width,height)
339
flipXY = self._flipXY
344
label._pmv = pm #the plus minus val
345
fixedEnd = getattr(label,'fixedEnd', None)
346
if fixedEnd is not None:
347
v = fixedEnd._getValue(self,pm)
360
fixedStart = getattr(label,'fixedStart', None)
361
if fixedStart is not None:
362
v = fixedStart._getValue(self,pm)
377
label.setOrigin(x0+dx, y0+dy)
379
sC, sW = label.lineStrokeColor, label.lineStrokeWidth
380
if sC and sW: g.insert(0,Line(x00,y00,x0,y0, strokeColor=sC, strokeWidth=sW))
382
alx = getattr(self,'barLabelCallOut',None)
384
label._callOutInfo = (self,g,rowNo,colNo,x,y,width,height,x00,y00,x0,y0)
386
del label._callOutInfo
388
def _makeBar(self,g,x,y,width,height,rowNo,style):
389
r = Rect(x, y, width, height)
390
r.strokeWidth = style.strokeWidth
391
r.fillColor = style.fillColor
392
r.strokeColor = style.strokeColor
395
def _makeBars(self,g,lg):
396
lenData = len(self.data)
398
for rowNo in range(lenData):
399
row = self._barPositions[rowNo]
400
styleCount = len(bars)
401
styleIdx = rowNo % styleCount
402
rowStyle = bars[styleIdx]
403
for colNo in range(len(row)):
405
style = bars.has_key((styleIdx,colNo)) and bars[(styleIdx,colNo)] or rowStyle
406
(x, y, width, height) = barPos
407
if None in (width,height):
408
self._addNABarLabel(lg,rowNo,colNo,x,y,width,height)
411
# Draw a rectangular symbol for each data item,
412
# or a normal colored rectangle.
414
if hasattr(style, 'symbol'):
415
symbol = copy.deepcopy(style.symbol)
416
elif hasattr(self.bars, 'symbol'):
417
symbol = self.bars.symbol
423
symbol.height = height
425
elif abs(width)>1e-7 and abs(height)>=1e-7 and (style.fillColor is not None or style.strokeColor is not None):
426
self._makeBar(g,x,y,width,height,rowNo,style)
428
self._addBarLabel(lg,rowNo,colNo,x,y,width,height)
437
def _desiredCategoryAxisLength(self):
438
'''for dynamically computing the desired category axis length'''
439
style = self.categoryAxis.style
442
m = max(map(len,data))
443
if style=='parallel':
444
groupWidth = (n-1)*self.barSpacing+n*self.barWidth
446
groupWidth = self.barWidth
447
return m*(self.groupSpacing+groupWidth)
450
cA, vA = self.categoryAxis, self.valueAxis
451
if vA: ovAjA, vA.joinAxis = vA.joinAxis, cA
452
if cA: ocAjA, cA.joinAxis = cA.joinAxis, vA
454
cA.setPosition(self._drawBegin(self.x,self.width), self.y, self.height)
456
cA.setPosition(self.x, self._drawBegin(self.y,self.height), self.width)
457
return self._drawFinish()
459
class VerticalBarChart(BarChart):
460
"Vertical bar chart with multiple side-by-side bars."
463
class HorizontalBarChart(BarChart):
464
"Horizontal bar chart with multiple side-by-side bars."
468
def __init__(self, cmp=None):
473
self._data.append(what)
479
self._data.sort(self._cmp)
481
class BarChart3D(BarChart):
482
_attrMap = AttrMap(BASE=BarChart,
483
theta_x = AttrMapValue(isNumber, desc='dx/dz'),
484
theta_y = AttrMapValue(isNumber, desc='dy/dz'),
485
zDepth = AttrMapValue(isNumber, desc='depth of an individual series'),
486
zSpace = AttrMapValue(isNumber, desc='z gap around series'),
493
def calcBarPositions(self):
494
BarChart.calcBarPositions(self)
495
seriesCount = self._seriesCount
497
if zDepth is None: zDepth = self.barWidth
499
if zSpace is None: zSpace = self.barSpacing
500
if self.categoryAxis.style=='parallel_3d':
501
_3d_depth = seriesCount*zDepth+(seriesCount+1)*zSpace
503
_3d_depth = zDepth + 2*zSpace
504
_3d_depth *= self._normFactor
505
self._3d_dx = self.theta_x*_3d_depth
506
self._3d_dy = self.theta_y*_3d_depth
508
def _calc_z0(self,rowNo):
510
if zDepth is None: zDepth = self.barWidth
512
if zSpace is None: zSpace = self.barSpacing
513
if self.categoryAxis.style=='parallel_3d':
514
z0 = self._normFactor*(rowNo*(zDepth+zSpace)+zSpace)
516
z0 = self._normFactor*zSpace
519
def _makeBar(self,g,x,y,width,height,rowNo,style):
521
if zDepth is None: zDepth = self.barWidth
523
if zSpace is None: zSpace = self.barSpacing
524
z0 = self._calc_z0(rowNo)
525
z1 = z0 + zDepth*self._normFactor
535
g.add((0,z0,z1,x,y,width,height,rowNo,style))
537
def _addBarLabel(self, g, rowNo, colNo, x, y, width, height):
538
z0 = self._calc_z0(rowNo)
540
if zSpace is None: zSpace = self.barSpacing
548
g.add((1,z0,z1,x,y,width,height,rowNo,colNo))
551
from utils3d import _draw_3d_bar
552
fg = _FakeGroup(cmp=self._cmpZ)
553
self._makeBars(fg,fg)
556
theta_x = self.theta_x
557
theta_y = self.theta_y
560
z0,z1,x,y,width,height,rowNo,colNo = t[1:]
561
BarChart._addBarLabel(self,g,rowNo,colNo,x,y,width,height)
563
z0,z1,x,y,width,height,rowNo,style = t[1:]
565
_draw_3d_bar(g, x, x+width, y, y+height, dz*theta_x, dz*theta_y,
566
fillColor=style.fillColor, fillColorShaded=None,
567
strokeColor=style.strokeColor, strokeWidth=style.strokeWidth,
571
class VerticalBarChart3D(BarChart3D,VerticalBarChart):
572
_cmpZ=lambda self,a,b:cmp((-a[1],a[3],a[0],-a[4]),(-b[1],b[3],b[0],-b[4]))
574
class HorizontalBarChart3D(BarChart3D,HorizontalBarChart):
575
_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]
579
"A slightly pathologic bar chart with only TWO data items."
581
drawing = Drawing(400, 200)
585
bc = VerticalBarChart()
592
bc.strokeColor = colors.black
594
bc.valueAxis.valueMin = 0
595
bc.valueAxis.valueMax = 60
596
bc.valueAxis.valueStep = 15
598
bc.categoryAxis.labels.boxAnchor = 'ne'
599
bc.categoryAxis.labels.dx = 8
600
bc.categoryAxis.labels.dy = -2
601
bc.categoryAxis.labels.angle = 30
602
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
610
"A pathologic bar chart with only ONE data item."
612
drawing = Drawing(400, 200)
616
bc = VerticalBarChart()
622
bc.strokeColor = colors.black
624
bc.valueAxis.valueMin = 0
625
bc.valueAxis.valueMax = 50
626
bc.valueAxis.valueStep = 15
628
bc.categoryAxis.labels.boxAnchor = 'ne'
629
bc.categoryAxis.labels.dx = 8
630
bc.categoryAxis.labels.dy = -2
631
bc.categoryAxis.labels.angle = 30
632
bc.categoryAxis.categoryNames = ['Jan-99']
640
"A really pathologic bar chart with NO data items at all!"
642
drawing = Drawing(400, 200)
646
bc = VerticalBarChart()
652
bc.strokeColor = colors.black
654
bc.valueAxis.valueMin = 0
655
bc.valueAxis.valueMax = 60
656
bc.valueAxis.valueStep = 15
658
bc.categoryAxis.labels.boxAnchor = 'ne'
659
bc.categoryAxis.labels.dx = 8
660
bc.categoryAxis.labels.dy = -2
661
bc.categoryAxis.categoryNames = []
669
"Sample of multi-series bar chart."
671
drawing = Drawing(400, 200)
674
(13, 5, 20, 22, 37, 45, 19, 4),
675
(14, 6, 21, 23, 38, 46, 20, 5)
678
bc = VerticalBarChart()
684
bc.strokeColor = colors.black
686
bc.valueAxis.valueMin = 0
687
bc.valueAxis.valueMax = 60
688
bc.valueAxis.valueStep = 15
690
bc.categoryAxis.labels.boxAnchor = 'ne'
691
bc.categoryAxis.labels.dx = 8
692
bc.categoryAxis.labels.dy = -2
693
bc.categoryAxis.labels.angle = 30
695
catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ')
696
catNames = map(lambda n:n+'-99', catNames)
697
bc.categoryAxis.categoryNames = catNames
704
"Sample of multi-series bar chart."
706
data = [(2.4, -5.7, 2, 5, 9.2),
707
(0.6, -4.9, -3, 4, 6.8)
710
labels = ("Q3 2000", "Year to Date", "12 months",
711
"Annualised\n3 years", "Since 07.10.99")
713
drawing = Drawing(400, 200)
715
bc = VerticalBarChart()
726
bc.valueAxis.valueMin = -15
727
bc.valueAxis.valueMax = +15
728
bc.valueAxis.valueStep = 5
729
bc.valueAxis.labels.fontName = 'Helvetica'
730
bc.valueAxis.labels.fontSize = 8
731
bc.valueAxis.labels.boxAnchor = 'n' # irrelevant (becomes 'c')
732
bc.valueAxis.labels.textAnchor = 'middle'
734
bc.categoryAxis.categoryNames = labels
735
bc.categoryAxis.labels.fontName = 'Helvetica'
736
bc.categoryAxis.labels.fontSize = 8
737
bc.categoryAxis.labels.dy = -60
745
"Sample of multi-series bar chart."
747
data = [(2.4, -5.7, 2, 5, 9.2),
748
(0.6, -4.9, -3, 4, 6.8)
751
labels = ("Q3 2000", "Year to Date", "12 months",
752
"Annualised\n3 years", "Since 07.10.99")
754
drawing = Drawing(400, 200)
756
bc = VerticalBarChart()
767
bc.valueAxis.valueMin = -15
768
bc.valueAxis.valueMax = +15
769
bc.valueAxis.valueStep = 5
770
bc.valueAxis.labels.fontName = 'Helvetica'
771
bc.valueAxis.labels.fontSize = 8
772
bc.valueAxis.labels.boxAnchor = 'n' # irrelevant (becomes 'c')
773
bc.valueAxis.labels.textAnchor = 'middle'
775
bc.categoryAxis.categoryNames = labels
776
bc.categoryAxis.labels.fontName = 'Helvetica'
777
bc.categoryAxis.labels.fontSize = 8
778
bc.categoryAxis.labels.dy = -60
786
"Sample of multi-series bar chart."
788
data = [(2.4, -5.7, 2, 5, 9.99),
789
(0.6, -4.9, -3, 4, 9.99)
792
labels = ("Q3 2000", "Year to Date", "12 months",
793
"Annualised\n3 years", "Since 07.10.99")
795
drawing = Drawing(400, 200)
797
bc = VerticalBarChart()
808
bc.valueAxis.valueMin = -15
809
bc.valueAxis.valueMax = +15
810
bc.valueAxis.valueStep = 5
811
bc.valueAxis.labels.fontName = 'Helvetica'
812
bc.valueAxis.labels.fontSize = 8
814
bc.categoryAxis.categoryNames = labels
815
bc.categoryAxis.labels.fontName = 'Helvetica'
816
bc.categoryAxis.labels.fontSize = 8
817
bc.valueAxis.labels.boxAnchor = 'n'
818
bc.valueAxis.labels.textAnchor = 'middle'
819
bc.categoryAxis.labels.dy = -60
821
bc.barLabels.nudge = 10
823
bc.barLabelFormat = '%0.2f'
826
bc.barLabels.boxAnchor = 'n' # irrelevant (becomes 'c')
827
bc.barLabels.fontName = 'Helvetica'
828
bc.barLabels.fontSize = 6
836
"Faked horizontal bar chart using a vertical real one (deprecated)."
838
names = ("UK Equities", "US Equities", "European Equities", "Japanese Equities",
839
"Pacific (ex Japan) Equities", "Emerging Markets Equities",
840
"UK Bonds", "Overseas Bonds", "UK Index-Linked", "Cash")
842
series1 = (-1.5, 0.3, 0.5, 1.0, 0.8, 0.7, 0.4, 0.1, 1.0, 0.3)
843
series2 = (0.0, 0.33, 0.55, 1.1, 0.88, 0.77, 0.44, 0.11, 1.10, 0.33)
845
assert len(names) == len(series1), "bad data"
846
assert len(names) == len(series2), "bad data"
848
drawing = Drawing(400, 200)
850
bc = VerticalBarChart()
856
bc.bars.fillColor = colors.green
858
bc.barLabelFormat = '%0.2f'
861
bc.barLabels.boxAnchor = 'w' # irrelevant (becomes 'c')
862
bc.barLabels.angle = 90
863
bc.barLabels.fontName = 'Helvetica'
864
bc.barLabels.fontSize = 6
865
bc.barLabels.nudge = 10
867
bc.valueAxis.visible = 0
868
bc.valueAxis.valueMin = -2
869
bc.valueAxis.valueMax = +2
870
bc.valueAxis.valueStep = 1
872
bc.categoryAxis.tickUp = 0
873
bc.categoryAxis.tickDown = 0
874
bc.categoryAxis.categoryNames = names
875
bc.categoryAxis.labels.angle = 90
876
bc.categoryAxis.labels.boxAnchor = 'w'
877
bc.categoryAxis.labels.dx = 0
878
bc.categoryAxis.labels.dy = -125
879
bc.categoryAxis.labels.fontName = 'Helvetica'
880
bc.categoryAxis.labels.fontSize = 6
883
g.translate(100, 175)
892
"A bar chart showing value axis region starting at *exactly* zero."
894
drawing = Drawing(400, 200)
898
bc = VerticalBarChart()
905
bc.strokeColor = colors.black
907
bc.valueAxis.valueMin = 0
908
bc.valueAxis.valueMax = 60
909
bc.valueAxis.valueStep = 15
911
bc.categoryAxis.labels.boxAnchor = 'n'
912
bc.categoryAxis.labels.dy = -5
913
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
921
"A bar chart showing value axis region starting *below* zero."
923
drawing = Drawing(400, 200)
927
bc = VerticalBarChart()
934
bc.strokeColor = colors.black
936
bc.valueAxis.valueMin = -10
937
bc.valueAxis.valueMax = 60
938
bc.valueAxis.valueStep = 15
940
bc.categoryAxis.labels.boxAnchor = 'n'
941
bc.categoryAxis.labels.dy = -5
942
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
950
"A bar chart showing value axis region staring *above* zero."
952
drawing = Drawing(400, 200)
956
bc = VerticalBarChart()
963
bc.strokeColor = colors.black
965
bc.valueAxis.valueMin = 10
966
bc.valueAxis.valueMax = 60
967
bc.valueAxis.valueStep = 15
969
bc.categoryAxis.labels.boxAnchor = 'n'
970
bc.categoryAxis.labels.dy = -5
971
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
979
"A bar chart showing value axis region entirely *below* zero."
981
drawing = Drawing(400, 200)
985
bc = VerticalBarChart()
992
bc.strokeColor = colors.black
994
bc.valueAxis.valueMin = -30
995
bc.valueAxis.valueMax = -10
996
bc.valueAxis.valueStep = 15
998
bc.categoryAxis.labels.boxAnchor = 'n'
999
bc.categoryAxis.labels.dy = -5
1000
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1009
##dataSample5 = [(10, 20), (20, 30), (30, 40), (40, 50), (50, 60)]
1010
##dataSample5 = [(10, 60), (20, 50), (30, 40), (40, 30), (50, 20)]
1011
dataSample5 = [(10, 60), (20, 50), (30, 40), (40, 30)]
1014
"A simple bar chart with no expressed spacing attributes."
1016
drawing = Drawing(400, 200)
1020
bc = VerticalBarChart()
1026
bc.strokeColor = colors.black
1028
bc.valueAxis.valueMin = 0
1029
bc.valueAxis.valueMax = 60
1030
bc.valueAxis.valueStep = 15
1032
bc.categoryAxis.labels.boxAnchor = 'n'
1033
bc.categoryAxis.labels.dy = -5
1034
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1042
"A simple bar chart with proportional spacing."
1044
drawing = Drawing(400, 200)
1048
bc = VerticalBarChart()
1054
bc.strokeColor = colors.black
1058
bc.groupSpacing = 20
1061
bc.valueAxis.valueMin = 0
1062
bc.valueAxis.valueMax = 60
1063
bc.valueAxis.valueStep = 15
1065
bc.categoryAxis.labels.boxAnchor = 'n'
1066
bc.categoryAxis.labels.dy = -5
1067
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1075
"Make sampe simple bar chart but with absolute spacing."
1077
drawing = Drawing(400, 200)
1081
bc = VerticalBarChart()
1087
bc.strokeColor = colors.black
1094
bc.valueAxis.valueMin = 0
1095
bc.valueAxis.valueMax = 60
1096
bc.valueAxis.valueStep = 15
1098
bc.categoryAxis.labels.boxAnchor = 'n'
1099
bc.categoryAxis.labels.dy = -5
1100
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1108
"Make sampe simple bar chart but with absolute spacing."
1110
drawing = Drawing(400, 200)
1114
bc = VerticalBarChart()
1120
bc.strokeColor = colors.black
1124
bc.groupSpacing = 20
1127
bc.valueAxis.valueMin = 0
1128
bc.valueAxis.valueMax = 60
1129
bc.valueAxis.valueStep = 15
1131
bc.categoryAxis.labels.boxAnchor = 'n'
1132
bc.categoryAxis.labels.dy = -5
1133
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1141
"Make sampe simple bar chart but with absolute spacing."
1143
drawing = Drawing(400, 200)
1147
bc = VerticalBarChart()
1153
bc.strokeColor = colors.black
1160
bc.valueAxis.valueMin = 0
1161
bc.valueAxis.valueMax = 60
1162
bc.valueAxis.valueStep = 15
1164
bc.categoryAxis.labels.boxAnchor = 'n'
1165
bc.categoryAxis.labels.dy = -5
1166
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1174
"Make sampe simple bar chart but with absolute spacing."
1176
drawing = Drawing(400, 200)
1180
bc = VerticalBarChart()
1186
bc.strokeColor = colors.black
1190
bc.groupSpacing = 20
1193
bc.valueAxis.valueMin = 0
1194
bc.valueAxis.valueMax = 60
1195
bc.valueAxis.valueStep = 15
1197
bc.categoryAxis.labels.boxAnchor = 'n'
1198
bc.categoryAxis.labels.dy = -5
1199
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1206
# Horizontal samples
1209
"Make a slightly pathologic bar chart with only TWO data items."
1211
drawing = Drawing(400, 200)
1215
bc = HorizontalBarChart()
1222
bc.strokeColor = colors.black
1224
bc.valueAxis.valueMin = 0
1225
bc.valueAxis.valueMax = 60
1226
bc.valueAxis.valueStep = 15
1228
bc.categoryAxis.labels.boxAnchor = 'se'
1229
bc.categoryAxis.labels.angle = 30
1230
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1238
"Make a pathologic bar chart with only ONE data item."
1240
drawing = Drawing(400, 200)
1244
bc = HorizontalBarChart()
1250
bc.strokeColor = colors.black
1252
bc.valueAxis.valueMin = 0
1253
bc.valueAxis.valueMax = 50
1254
bc.valueAxis.valueStep = 15
1256
bc.categoryAxis.labels.boxAnchor = 'se'
1257
bc.categoryAxis.labels.angle = 30
1258
bc.categoryAxis.categoryNames = ['Jan-99']
1266
"Make a really pathologic bar chart with NO data items at all!"
1268
drawing = Drawing(400, 200)
1272
bc = HorizontalBarChart()
1278
bc.strokeColor = colors.black
1280
bc.valueAxis.valueMin = 0
1281
bc.valueAxis.valueMax = 60
1282
bc.valueAxis.valueStep = 15
1284
bc.categoryAxis.labels.boxAnchor = 'se'
1285
bc.categoryAxis.labels.angle = 30
1286
bc.categoryAxis.categoryNames = []
1294
"Sample of multi-series bar chart."
1296
drawing = Drawing(400, 200)
1299
(13, 5, 20, 22, 37, 45, 19, 4),
1300
(14, 6, 21, 23, 38, 46, 20, 5)
1303
bc = HorizontalBarChart()
1309
bc.strokeColor = colors.black
1311
bc.valueAxis.valueMin = 0
1312
bc.valueAxis.valueMax = 60
1313
bc.valueAxis.valueStep = 15
1315
bc.categoryAxis.labels.boxAnchor = 'e'
1316
catNames = string.split('Jan Feb Mar Apr May Jun Jul Aug', ' ')
1317
catNames = map(lambda n:n+'-99', catNames)
1318
bc.categoryAxis.categoryNames = catNames
1319
drawing.add(bc, 'barchart')
1325
"Sample of multi-series bar chart."
1327
data = [(2.4, -5.7, 2, 5, 9.2),
1328
(0.6, -4.9, -3, 4, 6.8)
1331
labels = ("Q3 2000", "Year to Date", "12 months",
1332
"Annualised\n3 years", "Since 07.10.99")
1334
drawing = Drawing(400, 200)
1336
bc = HorizontalBarChart()
1344
bc.groupSpacing = 10
1347
bc.valueAxis.valueMin = -15
1348
bc.valueAxis.valueMax = +15
1349
bc.valueAxis.valueStep = 5
1350
bc.valueAxis.labels.fontName = 'Helvetica'
1351
bc.valueAxis.labels.fontSize = 8
1352
bc.valueAxis.labels.boxAnchor = 'n' # irrelevant (becomes 'c')
1353
bc.valueAxis.labels.textAnchor = 'middle'
1354
bc.valueAxis.configure(bc.data)
1356
bc.categoryAxis.categoryNames = labels
1357
bc.categoryAxis.labels.fontName = 'Helvetica'
1358
bc.categoryAxis.labels.fontSize = 8
1359
bc.categoryAxis.labels.dx = -150
1367
"Sample of multi-series bar chart."
1369
data = [(2.4, -5.7, 2, 5, 9.2),
1370
(0.6, -4.9, -3, 4, 6.8)
1373
labels = ("Q3 2000", "Year to Date", "12 months",
1374
"Annualised\n3 years", "Since 07.10.99")
1376
drawing = Drawing(400, 200)
1378
bc = HorizontalBarChart()
1386
bc.groupSpacing = 10
1389
bc.valueAxis.valueMin = -15
1390
bc.valueAxis.valueMax = +15
1391
bc.valueAxis.valueStep = 5
1392
bc.valueAxis.labels.fontName = 'Helvetica'
1393
bc.valueAxis.labels.fontSize = 8
1394
bc.valueAxis.labels.boxAnchor = 'n' # irrelevant (becomes 'c')
1395
bc.valueAxis.labels.textAnchor = 'middle'
1397
bc.categoryAxis.categoryNames = labels
1398
bc.categoryAxis.labels.fontName = 'Helvetica'
1399
bc.categoryAxis.labels.fontSize = 8
1400
bc.categoryAxis.labels.dx = -150
1408
"Sample of multi-series bar chart."
1410
data = [(2.4, -5.7, 2, 5, 9.99),
1411
(0.6, -4.9, -3, 4, 9.99)
1414
labels = ("Q3 2000", "Year to Date", "12 months",
1415
"Annualised\n3 years", "Since 07.10.99")
1417
drawing = Drawing(400, 200)
1419
bc = HorizontalBarChart()
1427
bc.groupSpacing = 10
1430
bc.valueAxis.valueMin = -15
1431
bc.valueAxis.valueMax = +15
1432
bc.valueAxis.valueStep = 5
1433
bc.valueAxis.labels.fontName = 'Helvetica'
1434
bc.valueAxis.labels.fontSize = 8
1435
bc.valueAxis.labels.boxAnchor = 'n'
1436
bc.valueAxis.labels.textAnchor = 'middle'
1438
bc.categoryAxis.categoryNames = labels
1439
bc.categoryAxis.labels.fontName = 'Helvetica'
1440
bc.categoryAxis.labels.fontSize = 8
1441
bc.categoryAxis.labels.dx = -150
1443
bc.barLabels.nudge = 10
1445
bc.barLabelFormat = '%0.2f'
1448
bc.barLabels.boxAnchor = 'n' # irrelevant (becomes 'c')
1449
bc.barLabels.fontName = 'Helvetica'
1450
bc.barLabels.fontSize = 6
1458
"A really horizontal bar chart (compared to the equivalent faked one)."
1460
names = ("UK Equities", "US Equities", "European Equities", "Japanese Equities",
1461
"Pacific (ex Japan) Equities", "Emerging Markets Equities",
1462
"UK Bonds", "Overseas Bonds", "UK Index-Linked", "Cash")
1464
series1 = (-1.5, 0.3, 0.5, 1.0, 0.8, 0.7, 0.4, 0.1, 1.0, 0.3)
1465
series2 = (0.0, 0.33, 0.55, 1.1, 0.88, 0.77, 0.44, 0.11, 1.10, 0.33)
1467
assert len(names) == len(series1), "bad data"
1468
assert len(names) == len(series2), "bad data"
1470
drawing = Drawing(400, 200)
1472
bc = HorizontalBarChart()
1477
bc.data = (series1,)
1478
bc.bars.fillColor = colors.green
1480
bc.barLabelFormat = '%0.2f'
1483
bc.barLabels.boxAnchor = 'w' # irrelevant (becomes 'c')
1484
bc.barLabels.fontName = 'Helvetica'
1485
bc.barLabels.fontSize = 6
1486
bc.barLabels.nudge = 10
1488
bc.valueAxis.visible = 0
1489
bc.valueAxis.valueMin = -2
1490
bc.valueAxis.valueMax = +2
1491
bc.valueAxis.valueStep = 1
1493
bc.categoryAxis.tickLeft = 0
1494
bc.categoryAxis.tickRight = 0
1495
bc.categoryAxis.categoryNames = names
1496
bc.categoryAxis.labels.boxAnchor = 'w'
1497
bc.categoryAxis.labels.dx = -170
1498
bc.categoryAxis.labels.fontName = 'Helvetica'
1499
bc.categoryAxis.labels.fontSize = 6
1508
"A bar chart showing value axis region starting at *exactly* zero."
1510
drawing = Drawing(400, 200)
1514
bc = HorizontalBarChart()
1521
bc.strokeColor = colors.black
1523
bc.valueAxis.valueMin = 0
1524
bc.valueAxis.valueMax = 60
1525
bc.valueAxis.valueStep = 15
1527
bc.categoryAxis.labels.boxAnchor = 'e'
1528
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1536
"A bar chart showing value axis region starting *below* zero."
1538
drawing = Drawing(400, 200)
1542
bc = HorizontalBarChart()
1549
bc.strokeColor = colors.black
1551
bc.valueAxis.valueMin = -10
1552
bc.valueAxis.valueMax = 60
1553
bc.valueAxis.valueStep = 15
1555
bc.categoryAxis.labels.boxAnchor = 'e'
1556
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1564
"A bar chart showing value axis region starting *above* zero."
1566
drawing = Drawing(400, 200)
1570
bc = HorizontalBarChart()
1577
bc.strokeColor = colors.black
1579
bc.valueAxis.valueMin = 10
1580
bc.valueAxis.valueMax = 60
1581
bc.valueAxis.valueStep = 15
1583
bc.categoryAxis.labels.boxAnchor = 'e'
1584
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1592
"A bar chart showing value axis region entirely *below* zero."
1594
drawing = Drawing(400, 200)
1598
bc = HorizontalBarChart()
1605
bc.strokeColor = colors.black
1607
bc.valueAxis.valueMin = -30
1608
bc.valueAxis.valueMax = -10
1609
bc.valueAxis.valueStep = 15
1611
bc.categoryAxis.labels.boxAnchor = 'e'
1612
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1619
dataSample5 = [(10, 60), (20, 50), (30, 40), (40, 30)]
1622
"A simple bar chart with no expressed spacing attributes."
1624
drawing = Drawing(400, 200)
1628
bc = HorizontalBarChart()
1634
bc.strokeColor = colors.black
1636
bc.valueAxis.valueMin = 0
1637
bc.valueAxis.valueMax = 60
1638
bc.valueAxis.valueStep = 15
1640
bc.categoryAxis.labels.boxAnchor = 'e'
1641
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1649
"A simple bar chart with proportional spacing."
1651
drawing = Drawing(400, 200)
1655
bc = HorizontalBarChart()
1661
bc.strokeColor = colors.black
1665
bc.groupSpacing = 20
1668
bc.valueAxis.valueMin = 0
1669
bc.valueAxis.valueMax = 60
1670
bc.valueAxis.valueStep = 15
1672
bc.categoryAxis.labels.boxAnchor = 'e'
1673
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1681
"A simple bar chart with absolute spacing."
1683
drawing = Drawing(400, 200)
1687
bc = HorizontalBarChart()
1693
bc.strokeColor = colors.black
1700
bc.valueAxis.valueMin = 0
1701
bc.valueAxis.valueMax = 60
1702
bc.valueAxis.valueStep = 15
1704
bc.categoryAxis.labels.boxAnchor = 'e'
1705
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1713
"Simple bar chart with absolute spacing."
1715
drawing = Drawing(400, 200)
1719
bc = HorizontalBarChart()
1725
bc.strokeColor = colors.black
1729
bc.groupSpacing = 20
1732
bc.valueAxis.valueMin = 0
1733
bc.valueAxis.valueMax = 60
1734
bc.valueAxis.valueStep = 15
1736
bc.categoryAxis.labels.boxAnchor = 'e'
1737
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1745
"Simple bar chart with absolute spacing."
1747
drawing = Drawing(400, 200)
1751
bc = HorizontalBarChart()
1757
bc.strokeColor = colors.black
1764
bc.valueAxis.valueMin = 0
1765
bc.valueAxis.valueMax = 60
1766
bc.valueAxis.valueStep = 15
1768
bc.categoryAxis.labels.boxAnchor = 'e'
1769
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1777
"Simple bar chart with absolute spacing."
1779
drawing = Drawing(400, 200)
1783
bc = HorizontalBarChart()
1789
bc.strokeColor = colors.black
1793
bc.groupSpacing = 20
1796
bc.valueAxis.valueMin = 0
1797
bc.valueAxis.valueMax = 60
1798
bc.valueAxis.valueStep = 15
1800
bc.categoryAxis.labels.boxAnchor = 'e'
1801
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1807
def sampleSymbol1():
1808
"Simple bar chart using symbol attribute."
1810
drawing = Drawing(400, 200)
1814
bc = VerticalBarChart()
1820
bc.strokeColor = colors.black
1823
bc.groupSpacing = 15
1826
bc.valueAxis.valueMin = 0
1827
bc.valueAxis.valueMax = 60
1828
bc.valueAxis.valueStep = 15
1830
bc.categoryAxis.labels.boxAnchor = 'e'
1831
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1833
from reportlab.graphics.widgets.grids import ShadedRect
1835
sym1.fillColorStart = colors.black
1836
sym1.fillColorEnd = colors.blue
1837
sym1.orientation = 'horizontal'
1838
sym1.strokeWidth = 0
1841
sym2.fillColorStart = colors.black
1842
sym2.fillColorEnd = colors.pink
1843
sym2.orientation = 'horizontal'
1844
sym2.strokeWidth = 0
1847
sym3.fillColorStart = colors.blue
1848
sym3.fillColorEnd = colors.white
1849
sym3.orientation = 'vertical'
1850
sym3.cylinderMode = 1
1851
sym3.strokeWidth = 0
1853
bc.bars.symbol = sym1
1854
bc.bars[2].symbol = sym2
1855
bc.bars[3].symbol = sym3
1861
def sampleStacked1():
1862
"Simple bar chart using symbol attribute."
1864
drawing = Drawing(400, 200)
1868
bc = VerticalBarChart()
1869
bc.categoryAxis.style = 'stacked'
1875
bc.strokeColor = colors.black
1878
bc.groupSpacing = 15
1879
bc.valueAxis.valueMin = 0
1881
bc.categoryAxis.labels.boxAnchor = 'e'
1882
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1884
from reportlab.graphics.widgets.grids import ShadedRect
1885
bc.bars.symbol = ShadedRect()
1886
bc.bars.symbol.fillColorStart = colors.red
1887
bc.bars.symbol.fillColorEnd = colors.white
1888
bc.bars.symbol.orientation = 'vertical'
1889
bc.bars.symbol.cylinderMode = 1
1890
bc.bars.symbol.strokeWidth = 0
1892
bc.bars[1].symbol = ShadedRect()
1893
bc.bars[1].symbol.fillColorStart = colors.magenta
1894
bc.bars[1].symbol.fillColorEnd = colors.white
1895
bc.bars[1].symbol.orientation = 'vertical'
1896
bc.bars[1].symbol.cylinderMode = 1
1897
bc.bars[1].symbol.strokeWidth = 0
1899
bc.bars[2].symbol = ShadedRect()
1900
bc.bars[2].symbol.fillColorStart = colors.green
1901
bc.bars[2].symbol.fillColorEnd = colors.white
1902
bc.bars[2].symbol.orientation = 'vertical'
1903
bc.bars[2].symbol.cylinderMode = 1
1904
bc.bars[2].symbol.strokeWidth = 0
1906
bc.bars[3].symbol = ShadedRect()
1907
bc.bars[3].symbol.fillColorStart = colors.blue
1908
bc.bars[3].symbol.fillColorEnd = colors.white
1909
bc.bars[3].symbol.orientation = 'vertical'
1910
bc.bars[3].symbol.cylinderMode = 1
1911
bc.bars[3].symbol.strokeWidth = 0
1917
#class version of function sampleH5c4 above
1918
class SampleH5c4(Drawing):
1919
"Simple bar chart with absolute spacing."
1921
def __init__(self,width=400,height=200,*args,**kw):
1922
apply(Drawing.__init__,(self,width,height)+args,kw)
1923
bc = HorizontalBarChart()
1928
bc.data = dataSample5
1929
bc.strokeColor = colors.black
1933
bc.groupSpacing = 20
1936
bc.valueAxis.valueMin = 0
1937
bc.valueAxis.valueMax = 60
1938
bc.valueAxis.valueStep = 15
1940
bc.categoryAxis.labels.boxAnchor = 'e'
1941
bc.categoryAxis.categoryNames = ['Ying', 'Yang']
1943
self.add(bc,name='HBC')