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/axes.py
4
"""Collection of axes for charts.
6
The current collection comprises axes for charts using cartesian
7
coordinate systems. All axes might have tick marks and labels.
8
There are two dichotomies for axes: one of X and Y flavours and
9
another of category and value flavours.
11
Category axes have an ordering but no metric. They are divided
12
into a number of equal-sized buckets. Their tick marks or labels,
13
if available, go BETWEEN the buckets, and the labels are placed
14
below to/left of the X/Y-axis, respectively.
16
Value axes have an ordering AND metric. They correspond to a nu-
17
meric quantity. Value axis have a real number quantity associated
18
with it. The chart tells it where to go.
19
The most basic axis divides the number line into equal spaces
20
and has tickmarks and labels associated with each; later we
21
will add variants where you can specify the sampling
24
The charts using axis tell them where the labels should be placed.
26
Axes of complementary X/Y flavours can be connected to each other
27
in various ways, i.e. with a specific reference point, like an
28
x/value axis to a y/value (or category) axis. In this case the
29
connection can be either at the top or bottom of the former or
30
at any absolute value (specified in points) or at some value of
31
the former axes in its own coordinate system.
33
__version__=''' $Id$ '''
36
from types import FunctionType, StringType, TupleType, ListType
38
from reportlab.lib.validators import isNumber, isNumberOrNone, isListOfStringsOrNone, isListOfNumbers, \
39
isListOfNumbersOrNone, isColorOrNone, OneOf, isBoolean, SequenceOf, \
41
from reportlab.lib.attrmap import *
42
from reportlab.lib import normalDate
43
from reportlab.graphics.shapes import Drawing, Line, PolyLine, Group, STATE_DEFAULTS, _textBoxLimits, _rotatedBoxLimits
44
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection
45
from reportlab.graphics.charts.textlabels import Label
46
from reportlab.graphics.charts.utils import nextRoundNumber
51
def _findMinMaxValue(V, x, default, func, special=None):
52
if type(V[0][0]) in (TupleType,ListType):
54
f=lambda T,x=x,special=special,func=func: special(T,x,func)
57
V=map(lambda e,f=f: map(f,e),V)
58
V = filter(len,map(lambda x: filter(lambda x: x is not None,x),V))
59
if len(V)==0: return default
60
return func(map(func,V))
62
def _findMin(V, x, default,special=None):
63
'''find minimum over V[i][x]'''
64
return _findMinMaxValue(V,x,default,min,special=special)
66
def _findMax(V, x, default,special=None):
67
'''find maximum over V[i][x]'''
68
return _findMinMaxValue(V,x,default,max,special=special)
71
def _get_line_pos(self,v):
79
def _cxLine(self,x,start,end):
80
x = self._get_line_pos(x)
81
return Line(x, self._y + start, x, self._y + end)
83
def _cyLine(self,y,start,end):
84
y = self._get_line_pos(y)
85
return Line(self._x + start, y, self._x + end, y)
87
def _cxLine3d(self,x,start,end,_3d_dx,_3d_dy):
88
x = self._get_line_pos(x)
91
y0, y1 = min(y0,y1),max(y0,y1)
93
return PolyLine([x,y0,x1,y0+_3d_dy,x1,y1+_3d_dy],strokeLineJoin=1)
95
def _cyLine3d(self,y,start,end,_3d_dx,_3d_dy):
96
y = self._get_line_pos(y)
99
x0, x1 = min(x0,x1),max(x0,x1)
101
return PolyLine([x0,y,x0+_3d_dx,y1,x1+_3d_dx,y1],strokeLineJoin=1)
103
def _getLineFunc(self, start, end, parent=None):
104
_3d_dx = getattr(parent,'_3d_dx',None)
105
if _3d_dx is not None:
106
_3d_dy = getattr(parent,'_3d_dy',None)
107
f = self._dataIndex and self._cyLine3d or self._cxLine3d
108
return lambda v, s=start, e=end, f=f,_3d_dx=_3d_dx,_3d_dy=_3d_dy: f(v,s,e,_3d_dx=_3d_dx,_3d_dy=_3d_dy)
110
f = self._dataIndex and self._cyLine or self._cxLine
111
return lambda v, s=start, e=end, f=f: f(v,s,e)
113
def _makeLines(self,g,start,end,strokeColor,strokeWidth,strokeDashArray,parent=None):
114
func = self._getLineFunc(start,end,parent)
115
for t in self._tickValues:
117
L.strokeColor = strokeColor
118
L.strokeWidth = strokeWidth
119
L.strokeDashArray = strokeDashArray
122
def makeGrid(self,g,dim=None,parent=None):
123
'''this is only called by a container object'''
127
s = s is None and dim[0]
128
e = e is None and dim[0]+dim[1]
129
c = self.gridStrokeColor
130
if self.visibleGrid and (s or e) and c is not None:
131
if self._dataIndex: offs = self._x
133
self._makeLines(g,s-offs,e-offs,c,self.gridStrokeWidth,self.gridStrokeDashArray,parent=parent)
136
class CategoryAxis(_AxisG):
137
"Abstract category axis, unusable in itself."
140
visible = AttrMapValue(isBoolean, desc='Display entire object, if true.'),
141
visibleAxis = AttrMapValue(isBoolean, desc='Display axis line, if true.'),
142
visibleTicks = AttrMapValue(isBoolean, desc='Display axis ticks, if true.'),
143
visibleLabels = AttrMapValue(isBoolean, desc='Display axis labels, if true.'),
144
visibleGrid = AttrMapValue(isBoolean, desc='Display axis grid, if true.'),
145
strokeWidth = AttrMapValue(isNumber, desc='Width of axis line and ticks.'),
146
strokeColor = AttrMapValue(isColorOrNone, desc='Color of axis line and ticks.'),
147
strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array used for axis line.'),
148
gridStrokeWidth = AttrMapValue(isNumber, desc='Width of grid lines.'),
149
gridStrokeColor = AttrMapValue(isColorOrNone, desc='Color of grid lines.'),
150
gridStrokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array used for grid lines.'),
151
gridStart = AttrMapValue(isNumberOrNone, desc='Start of grid lines wrt axis origin'),
152
gridEnd = AttrMapValue(isNumberOrNone, desc='End of grid lines wrt axis origin'),
153
labels = AttrMapValue(None, desc='Handle of the axis labels.'),
154
categoryNames = AttrMapValue(isListOfStringsOrNone, desc='List of category names.'),
155
joinAxis = AttrMapValue(None, desc='Join both axes if true.'),
156
joinAxisPos = AttrMapValue(isNumberOrNone, desc='Position at which to join with other axis.'),
157
reverseDirection = AttrMapValue(isBoolean, desc='If true reverse category direction.'),
158
style = AttrMapValue(OneOf('parallel','stacked','parallel_3d'),"How common category bars are plotted"),
159
labelAxisMode = AttrMapValue(OneOf('high','low','axis'), desc="Like joinAxisMode, but for the axis labels"),
163
assert self.__class__.__name__!='CategoryAxis', "Abstract Class CategoryAxis Instantiated"
164
# private properties set by methods. The initial values
165
# here are to make demos easy; they would always be
166
# overridden in real life.
175
self.visibleTicks = 1
176
self.visibleLabels = 1
180
self.strokeColor = STATE_DEFAULTS['strokeColor']
181
self.strokeDashArray = STATE_DEFAULTS['strokeDashArray']
182
self.gridStrokeWidth = 0.25
183
self.gridStrokeColor = STATE_DEFAULTS['strokeColor']
184
self.gridStrokeDashArray = STATE_DEFAULTS['strokeDashArray']
185
self.gridStart = self.gridEnd = 0
186
self.labels = TypedPropertyCollection(Label)
187
# if None, they don't get labels. If provided,
188
# you need one name per data point and they are
189
# used for label text.
190
self.categoryNames = None
192
self.joinAxisPos = None
193
self.joinAxisMode = None
194
self.labelAxisMode = 'axis'
195
self.reverseDirection = 0
196
self.style = 'parallel'
198
#various private things which need to be initialized
199
self._labelTextFormat = None
201
def setPosition(self, x, y, length):
202
# ensure floating point
205
self._length = length
208
def configure(self, multiSeries,barWidth=None):
209
self._catCount = max(map(len,multiSeries))
210
self._barWidth = barWidth or (self._length/float(self._catCount or 1))
211
self._calcTickmarkPositions()
213
def _calcTickmarkPositions(self):
214
self._tickValues = range(self._catCount+1)
222
g.add(self.makeAxis())
223
g.add(self.makeTicks())
224
g.add(self.makeTickLabels())
228
def _scale(self,idx):
229
if self.reverseDirection: idx = self._catCount-idx-1
232
def _assertYAxis(axis):
233
acn = axis.__class__.__name__
234
assert acn[0]=='Y' or acn[:4]=='AdjY', "Cannot connect to other axes (%s), but Y- ones." % acn
235
def _assertXAxis(axis):
236
acn = axis.__class__.__name__
237
assert acn[0]=='X' or acn[:11]=='NormalDateX', "Cannot connect to other axes (%s), but X- ones." % acn
239
class XCategoryAxis(CategoryAxis):
242
_attrMap = AttrMap(BASE=CategoryAxis,
243
tickUp = AttrMapValue(isNumber,
244
desc='Tick length up the axis.'),
245
tickDown = AttrMapValue(isNumber,
246
desc='Tick length down the axis.'),
247
joinAxisMode = AttrMapValue(OneOf('bottom', 'top', 'value', 'points', None),
248
desc="Mode used for connecting axis ('bottom', 'top', 'value', 'points', None)."),
254
CategoryAxis.__init__(self)
255
self.labels.boxAnchor = 'n' #north - top edge
257
# ultra-simple tick marks for now go between categories
258
# and have same line style as axis - need more
259
self.tickUp = 0 # how far into chart does tick go?
260
self.tickDown = 5 # how far below axis does tick go?
264
self.setPosition(30, 70, 140)
265
self.configure([(10,20,30,40,50)])
267
self.categoryNames = ['One','Two','Three','Four','Five']
268
# all labels top-centre aligned apart from the last
269
self.labels.boxAnchor = 'n'
270
self.labels[4].boxAnchor = 'e'
271
self.labels[4].angle = 90
273
d = Drawing(200, 100)
278
def joinToAxis(self, yAxis, mode='bottom', pos=None):
279
"Join with y-axis using some mode."
287
self._y = yAxis._y + yAxis._length
288
elif mode == 'value':
290
self._y = yAxis.scale(pos)
291
elif mode == 'points':
295
def scale(self, idx):
296
"""returns the x position and width in drawing units of the slice"""
297
return (self._x + self._scale(idx)*self._barWidth, self._barWidth)
305
jam = self.joinAxisMode
306
jap = self.joinAxisPos
307
jta = self.joinToAxis
308
if jam in ('bottom', 'top'):
310
elif jam in ('value', 'points'):
311
jta(ja, mode=jam, pos=jap)
313
if not self.visibleAxis: return g
315
axis = Line(self._x, self._y, self._x + self._length, self._y)
316
axis.strokeColor = self.strokeColor
317
axis.strokeWidth = self.strokeWidth
318
axis.strokeDashArray = self.strokeDashArray
325
if not self.visibleTicks: return g
326
if self.tickUp or self.tickDown:
327
self._makeLines(g,self.tickUp,-self.tickDown,self.strokeColor,self.strokeWidth,self.strokeDashArray)
330
def _labelAxisPos(self):
333
mode = self.labelAxisMode
337
return axis._y + axis._length
340
def makeTickLabels(self):
343
if not self.visibleLabels: return g
345
if not (self.categoryNames is None):
346
catCount = self._catCount
347
assert len(self.categoryNames) == catCount, \
348
"expected %d category names but found %d in axis. \n categories = %s" % (
349
len(self.categoryNames), catCount, repr(self.categoryNames)
351
reverseDirection = self.reverseDirection
352
barWidth = self._barWidth
353
_y = self._labelAxisPos()
356
for i in range(catCount):
357
x = _x + (i+0.5) * barWidth
358
label = self.labels[i]
359
label.setOrigin(x, _y)
360
if reverseDirection: i = catCount-i-1
361
label.setText(self.categoryNames[i])
367
class YCategoryAxis(CategoryAxis):
370
_attrMap = AttrMap(BASE=CategoryAxis,
371
tickLeft = AttrMapValue(isNumber,
372
desc='Tick length left of the axis.'),
373
tickRight = AttrMapValue(isNumber,
374
desc='Tick length right of the axis.'),
375
joinAxisMode = AttrMapValue(OneOf(('left', 'right', 'value', 'points', None)),
376
desc="Mode used for connecting axis ('left', 'right', 'value', 'points', None)."),
383
CategoryAxis.__init__(self)
384
self.labels.boxAnchor = 'e' #east - right edge
386
# ultra-simple tick marks for now go between categories
387
# and have same line style as axis - need more
388
self.tickLeft = 5 # how far left of axis does tick go?
389
self.tickRight = 0 # how far right of axis does tick go?
393
self.setPosition(50, 10, 80)
394
self.configure([(10,20,30)])
395
self.categoryNames = ['One','Two','Three']
396
# all labels top-centre aligned apart from the last
397
self.labels.boxAnchor = 'e'
398
self.labels[2].boxAnchor = 's'
399
self.labels[2].angle = 90
401
d = Drawing(200, 100)
406
def joinToAxis(self, xAxis, mode='left', pos=None):
407
"Join with x-axis using some mode."
412
self._x = xAxis._x * 1.0
413
self._y = xAxis._y * 1.0
414
elif mode == 'right':
415
self._x = (xAxis._x + xAxis._length) * 1.0
416
self._y = xAxis._y * 1.0
417
elif mode == 'value':
418
self._x = xAxis.scale(pos) * 1.0
419
self._y = xAxis._y * 1.0
420
elif mode == 'points':
422
self._y = xAxis._y * 1.0
424
def scale(self, idx):
425
"Returns the y position and width in drawing units of the slice."
426
return (self._y + self._scale(idx)*self._barWidth, self._barWidth)
431
if not self.visibleAxis:
436
jam = self.joinAxisMode
437
jap = self.joinAxisPos
438
jta = self.joinToAxis
439
if jam in ('left', 'right'):
441
elif jam in ('value', 'points'):
442
jta(ja, mode=jam, pos=jap)
444
axis = Line(self._x, self._y, self._x, self._y + self._length)
445
axis.strokeColor = self.strokeColor
446
axis.strokeWidth = self.strokeWidth
447
axis.strokeDashArray = self.strokeDashArray
454
if not self.visibleTicks: return g
455
if self.tickLeft or self.tickRight:
456
self._makeLines(g,-self.tickLeft,self.tickRight,self.strokeColor,self.strokeWidth,self.strokeDashArray)
459
def _labelAxisPos(self):
462
mode = self.labelAxisMode
466
return axis._x + axis._length
469
def makeTickLabels(self):
472
if not self.visibleTicks:
475
if not (self.categoryNames is None):
476
catCount = self._catCount
477
assert len(self.categoryNames) == catCount, \
478
"expected %d category names but found %d in axis" % (
479
len(self.categoryNames), catCount
481
reverseDirection = self.reverseDirection
482
barWidth = self._barWidth
484
_x = self._labelAxisPos()
486
for i in range(catCount):
487
y = _y + (i+0.5) * barWidth
489
label.setOrigin(_x, y)
490
if reverseDirection: i = catCount-i-1
491
label.setText(self.categoryNames[i])
498
class ValueAxis(_AxisG):
499
"Abstract value axis, unusable in itself."
502
forceZero = AttrMapValue(EitherOr((isBoolean,OneOf('near'))), desc='Ensure zero in range if true.'),
503
visible = AttrMapValue(isBoolean, desc='Display entire object, if true.'),
504
visibleAxis = AttrMapValue(isBoolean, desc='Display axis line, if true.'),
505
visibleLabels = AttrMapValue(isBoolean, desc='Display axis labels, if true.'),
506
visibleTicks = AttrMapValue(isBoolean, desc='Display axis ticks, if true.'),
507
visibleGrid = AttrMapValue(isBoolean, desc='Display axis grid, if true.'),
508
strokeWidth = AttrMapValue(isNumber, desc='Width of axis line and ticks.'),
509
strokeColor = AttrMapValue(isColorOrNone, desc='Color of axis line and ticks.'),
510
strokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array used for axis line.'),
511
gridStrokeWidth = AttrMapValue(isNumber, desc='Width of grid lines.'),
512
gridStrokeColor = AttrMapValue(isColorOrNone, desc='Color of grid lines.'),
513
gridStrokeDashArray = AttrMapValue(isListOfNumbersOrNone, desc='Dash array used for grid lines.'),
514
gridStart = AttrMapValue(isNumberOrNone, desc='Start of grid lines wrt axis origin'),
515
gridEnd = AttrMapValue(isNumberOrNone, desc='End of grid lines wrt axis origin'),
516
minimumTickSpacing = AttrMapValue(isNumber, desc='Minimum value for distance between ticks.'),
517
maximumTicks = AttrMapValue(isNumber, desc='Maximum number of ticks.'),
518
labels = AttrMapValue(None, desc='Handle of the axis labels.'),
519
labelTextFormat = AttrMapValue(None, desc='Formatting string or function used for axis labels.'),
520
labelTextPostFormat = AttrMapValue(None, desc='Extra Formatting string.'),
521
labelTextScale = AttrMapValue(isNumberOrNone, desc='Scaling for label tick values.'),
522
valueMin = AttrMapValue(isNumberOrNone, desc='Minimum value on axis.'),
523
valueMax = AttrMapValue(isNumberOrNone, desc='Maximum value on axis.'),
524
valueStep = AttrMapValue(isNumberOrNone, desc='Step size used between ticks.'),
525
valueSteps = AttrMapValue(isListOfNumbersOrNone, desc='List of step sizes used between ticks.'),
526
avoidBoundFrac = AttrMapValue(EitherOr((isNumberOrNone,SequenceOf(isNumber,emptyOK=0,lo=2,hi=2))), desc='Fraction of interval to allow above and below.'),
527
rangeRound=AttrMapValue(OneOf('none','both','ceiling','floor'),'How to round the axis limits'),
528
zrangePref = AttrMapValue(isNumberOrNone, desc='Zero range axis limit preference.'),
529
style = AttrMapValue(OneOf('normal','stacked','parallel_3d'),"How values are plotted!"),
533
assert self.__class__.__name__!='ValueAxis', 'Abstract Class ValueAxis Instantiated'
535
# private properties set by methods. The initial values
536
# here are to make demos easy; they would always be
537
# overridden in real life.
545
self.visibleLabels = 1
546
self.visibleTicks = 1
551
self.strokeColor = STATE_DEFAULTS['strokeColor']
552
self.strokeDashArray = STATE_DEFAULTS['strokeDashArray']
553
self.gridStrokeWidth = 0.25
554
self.gridStrokeColor = STATE_DEFAULTS['strokeColor']
555
self.gridStrokeDashArray = STATE_DEFAULTS['strokeDashArray']
556
self.gridStart = self.gridEnd = 0
558
self.labels = TypedPropertyCollection(Label)
559
self.labels.angle = 0
561
# how close can the ticks be?
562
self.minimumTickSpacing = 10
563
self.maximumTicks = 7
565
# a format string like '%0.2f'
566
# or a function which takes the value as an argument and returns a string
567
self._labelTextFormat = self.labelTextFormat = self.labelTextPostFormat = self.labelTextScale = None
569
# if set to None, these will be worked out for you.
570
# if you override any or all of them, your values
574
self.valueStep = None
575
self.avoidBoundFrac = None
576
self.rangeRound = 'none'
578
self.style = 'normal'
580
def setPosition(self, x, y, length):
581
# ensure floating point
584
self._length = length * 1.0
586
def configure(self, dataSeries):
587
"""Let the axis configure its scale and range based on the data.
589
Called after setPosition. Let it look at a list of lists of
590
numbers determine the tick mark intervals. If valueMin,
591
valueMax and valueStep are configured then it
592
will use them; if any of them are set to None it
593
will look at the data and make some sensible decision.
594
You may override this to build custom axes with
595
irregular intervals. It creates an internal
596
variable self._values, which is a list of numbers
599
self._setRange(dataSeries)
600
self._calcTickmarkPositions()
601
self._calcScaleFactor()
604
def _getValueStepAndTicks(self, valueMin, valueMax,cache={}):
606
K = (valueMin,valueMax)
609
self._valueMin = valueMin
610
self._valueMax = valueMax
611
T = self._calcTickPositions()
613
valueStep = T[1]-T[0]
616
self.valueStep = None
617
T = self._calcTickPositions()
620
valueStep = T[1]-T[0]
622
valueStep = self._valueStep
623
r = cache[K] = valueStep, T, valueStep*1e-8
626
def _setRange(self, dataSeries):
627
"""Set minimum and maximum axis values.
629
The dataSeries argument is assumed to be a list of data
630
vectors. Each vector is itself a list or tuple of numbers.
632
Returns a min, max tuple.
635
oMin = valueMin = self.valueMin
636
oMax = valueMax = self.valueMax
637
rangeRound = self.rangeRound
638
if valueMin is None: valueMin = self._cValueMin = _findMin(dataSeries,self._dataIndex,0)
639
if valueMax is None: valueMax = self._cValueMax = _findMax(dataSeries,self._dataIndex,0)
640
if valueMin == valueMax:
642
if oMin is None and oMax is None:
643
zrp = getattr(self,'zrangePref',0)
653
elif self.valueMin is None:
659
valueMax = 1.2*valueMax
663
valueMin = 1.2*valueMin
665
if getattr(self,'_bubblePlot',None):
666
bubbleMax = float(_findMax(dataSeries,2,0))
668
bubbleV=frac*(valueMax-valueMin)
669
self._bubbleV = bubbleV
670
self._bubbleMax = bubbleMax
671
self._bubbleRadius = frac*self._length
672
def special(T,x,func,bubbleV=bubbleV,bubbleMax=bubbleMax):
677
bubbleV *= (v/bubbleMax)**0.5
678
return func(T[x]+bubbleV,T[x]-bubbleV)
679
if oMin is None: valueMin = self._cValueMin = _findMin(dataSeries,self._dataIndex,0,special=special)
680
if oMax is None: valueMax = self._cValueMax = _findMax(dataSeries,self._dataIndex,0,special=special)
682
forceZero = self.forceZero
684
if forceZero=='near':
685
forceZero = min(abs(valueMin),abs(valueMax)) <= 5*(valueMax-valueMin)
687
if valueMax<0: valueMax=0
688
elif valueMin>0: valueMin = 0
690
abf = self.avoidBoundFrac
691
do_rr = not getattr(self,'valueSteps',None)
692
do_abf = abf and do_rr
693
if type(abf) not in (TupleType,ListType):
695
do_rr = rangeRound is not 'none' and do_rr
697
rrn = rangeRound in ['both','floor']
698
rrx = rangeRound in ['both','ceiling']
709
valueStep, T, fuzz = self._getValueStepAndTicks(valueMin, valueMax, cache)
710
fuzz = 1e-8*valueStep
711
i0 = valueStep*abf[0]
712
i1 = valueStep*abf[1]
716
if abs(v)>fuzz and fuzz < d:
717
valueMin = valueMin - (d+fuzz)
722
if abs(v)>fuzz and fuzz < d:
723
valueMax = valueMax + (d+fuzz)
727
valueStep, T, fuzz = self._getValueStepAndTicks(valueMin, valueMax, cache)
728
if rangeRound in ['both','floor'] and valueMin<T[0]-fuzz:
729
valueMin = T[0]-valueStep
731
if rangeRound in ['both','ceiling'] and valueMax>T[-1]+fuzz:
732
valueMax = T[-1]+valueStep
735
self._valueMin, self._valueMax = valueMin, valueMax
738
def _rangeAdjust(self):
739
"""Override this if you want to alter the calculated range.
741
E.g. if want a minumamum range of 30% or don't want 100%
746
def _adjustAxisTicks(self):
747
'''Override if you want to put slack at the ends of the axis
748
eg if you don't want the last tick to be at the bottom etc
752
def _calcScaleFactor(self):
753
"""Calculate the axis' scale factor.
754
This should be called only *after* the axis' range is set.
757
self._scaleFactor = self._length / float(self._valueMax - self._valueMin)
758
return self._scaleFactor
760
def _calcTickPositions(self):
761
self._calcValueStep()
763
valueMin, valueMax, valueStep = self._valueMin, self._valueMax, self._valueStep
764
fuzz = 1e-8*valueStep
765
t = int((valueMin-fuzz)/valueStep) * valueStep
766
if t >= valueMin-fuzz: P.append(t)
768
while t <= valueMax+fuzz:
773
def _calcTickmarkPositions(self):
774
"""Calculate a list of tick positions on the axis. Returns a list of numbers."""
775
self._tickValues = getattr(self,'valueSteps',None)
776
if self._tickValues: return self._tickValues
777
self._tickValues = self._calcTickPositions()
778
self._adjustAxisTicks()
779
return self._tickValues
781
def _calcValueStep(self):
782
'''Calculate _valueStep for the axis or get from valueStep.'''
783
if self.valueStep is None:
784
rawRange = self._valueMax - self._valueMin
785
rawInterval = rawRange / min(float(self.maximumTicks-1),(float(self._length)/self.minimumTickSpacing))
786
self._valueStep = nextRoundNumber(rawInterval)
788
self._valueStep = self.valueStep
790
def _allIntTicks(self):
791
for tick in self._tickValues:
793
if int(tick)!=tick: return 0
798
def makeTickLabels(self):
800
if not self.visibleLabels: return g
802
f = self._labelTextFormat # perhaps someone already set it
804
f = self.labelTextFormat or (self._allIntTicks() and '%.0f' or str)
805
elif f is str and self._allIntTicks(): f = '%.0f'
806
post = self.labelTextPostFormat
807
scl = self.labelTextScale
808
pos = [self._x, self._y]
813
for tick in self._tickValues:
814
if f and labels[i].visible:
820
if type(f) is StringType: txt = f % t
821
elif type(f) in (TupleType,ListType):
822
#it's a list, use as many items as we get
830
raise ValueError, 'Invalid labelTextFormat %s' % f
831
if post: txt = post % txt
834
apply(label.setOrigin,pos)
847
g.add(self.makeAxis())
848
g.add(self.makeTicks())
849
g.add(self.makeTickLabels())
854
class XValueAxis(ValueAxis):
857
_attrMap = AttrMap(BASE=ValueAxis,
858
tickUp = AttrMapValue(isNumber,
859
desc='Tick length up the axis.'),
860
tickDown = AttrMapValue(isNumber,
861
desc='Tick length down the axis.'),
862
joinAxis = AttrMapValue(None,
863
desc='Join both axes if true.'),
864
joinAxisMode = AttrMapValue(OneOf('bottom', 'top', 'value', 'points', None),
865
desc="Mode used for connecting axis ('bottom', 'top', 'value', 'points', None)."),
866
joinAxisPos = AttrMapValue(isNumberOrNone,
867
desc='Position at which to join with other axis.'),
870
# Indicate the dimension of the data we're interested in.
874
ValueAxis.__init__(self)
876
self.labels.boxAnchor = 'n'
884
self.joinAxisMode = None
885
self.joinAxisPos = None
889
self.setPosition(20, 50, 150)
890
self.configure([(10,20,30,40,50)])
892
d = Drawing(200, 100)
897
def joinToAxis(self, yAxis, mode='bottom', pos=None):
898
"Join with y-axis using some mode."
901
self._x = yAxis._x * 1.0
902
self._y = yAxis._y * 1.0
904
self._x = yAxis._x * 1.0
905
self._y = (yAxis._y + yAxis._length) * 1.0
906
elif mode == 'value':
907
self._x = yAxis._x * 1.0
908
self._y = yAxis.scale(pos) * 1.0
909
elif mode == 'points':
910
self._x = yAxis._x * 1.0
913
def scale(self, value):
914
"""Converts a numeric value to a Y position.
916
The chart first configures the axis, then asks it to
917
work out the x value for each point when plotting
918
lines or bars. You could override this to do
922
msg = "Axis cannot scale numbers before it is configured"
923
assert self._configured, msg
926
return self._x + self._scaleFactor * (value - self._valueMin)
932
if not self.visibleAxis:
937
jam = self.joinAxisMode
938
jap = self.joinAxisPos
939
jta = self.joinToAxis
940
if jam in ('bottom', 'top'):
942
elif jam in ('value', 'points'):
943
jta(ja, mode=jam, pos=jap)
945
axis = Line(self._x, self._y, self._x + self._length, self._y)
946
axis.strokeColor = self.strokeColor
947
axis.strokeWidth = self.strokeWidth
948
axis.strokeDashArray = self.strokeDashArray
955
if self.visibleTicks and (self.tickUp or self.tickDown):
956
self._makeLines(g,-self.tickDown,self.tickUp,self.strokeColor,self.strokeWidth,self.strokeDashArray)
959
class NormalDateXValueAxis(XValueAxis):
960
"""An X axis applying additional rules.
962
Depending on the data and some built-in rules, the axis
963
displays normalDate values as nicely formatted dates.
965
The client chart should have NormalDate X values.
968
_attrMap = AttrMap(BASE = XValueAxis,
969
bottomAxisLabelSlack = AttrMapValue(isNumber, desc="Fractional amount used to adjust label spacing"),
970
niceMonth = AttrMapValue(isBoolean, desc="Flag for displaying months 'nicely'."),
971
forceEndDate = AttrMapValue(isBoolean, desc='Flag for enforced displaying of last date value.'),
972
forceFirstDate = AttrMapValue(isBoolean, desc='Flag for enforced displaying of first date value.'),
973
xLabelFormat = AttrMapValue(None, desc="Label format string (e.g. '{mm}/{yy}') or function."),
974
dayOfWeekName = AttrMapValue(SequenceOf(isString,emptyOK=0,lo=7,hi=7), desc='Weekday names.'),
975
monthName = AttrMapValue(SequenceOf(isString,emptyOK=0,lo=12,hi=12), desc='Month names.'),
976
dailyFreq = AttrMapValue(isBoolean, desc='True if we are to assume daily data to be ticked at end of month.'),
979
_valueClass = normalDate.ND
981
def __init__(self, **kw):
982
apply(XValueAxis.__init__, (self,))
984
# some global variables still used...
985
self.bottomAxisLabelSlack = 0.1
987
self.forceEndDate = 0
988
self.forceFirstDate = 0
990
self.xLabelFormat = "{mm}/{yy}"
991
self.dayOfWeekName = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
992
self.monthName = ['January', 'February', 'March', 'April', 'May', 'June', 'July',
993
'August', 'September', 'October', 'November', 'December']
994
self.valueSteps = None
996
def _scalar2ND(self, x):
997
"Convert a scalar to a NormalDate value."
998
d = self._valueClass()
1002
def _dateFormatter(self, v):
1003
"Create a formatted label for some value."
1004
if not isinstance(v,normalDate.NormalDate):
1005
v = self._scalar2ND(v)
1006
d, m = normalDate._dayOfWeekName, normalDate._monthName
1008
normalDate._dayOfWeekName, normalDate._monthName = self.dayOfWeekName, self.monthName
1009
return v.formatMS(self.xLabelFormat)
1011
normalDate._dayOfWeekName, normalDate._monthName = d, m
1013
def _xAxisTicker(self, xVals):
1016
Needs explanation...
1018
axisLength = self._length
1019
formatter = self._dateFormatter
1020
labels = self.labels
1021
fontName, fontSize, leading = labels.fontName, labels.fontSize, labels.leading
1022
textAnchor, boxAnchor, angle = labels.textAnchor, labels.boxAnchor, labels.angle
1023
RBL = _textBoxLimits(string.split(formatter(xVals[0]),'\n'),fontName,
1024
fontSize,leading or 1.2*fontSize,textAnchor,boxAnchor)
1025
RBL = _rotatedBoxLimits(RBL[0],RBL[1],RBL[2],RBL[3], angle)
1026
xLabelW = RBL[1]-RBL[0]
1027
xLabelH = RBL[3]-RBL[2]
1028
w = max(xLabelW,labels.width,self.minimumTickSpacing)
1030
W = w+w*self.bottomAxisLabelSlack
1034
maximumTicks = self.maximumTicks
1036
def addTick(i, xVals=xVals, formatter=formatter, ticks=ticks, labels=labels):
1037
ticks.insert(0,xVals[i])
1038
labels.insert(0,formatter(xVals[i]))
1040
for d in (1,2,3,6,12,24,60,120):
1042
if k<=maximumTicks and k*W <= axisLength:
1045
j = xVals[-1].month() % (d<=12 and d or 12)
1047
if self.forceEndDate: addTick(i)
1050
#weird first date ie not at end of month
1052
wfd = xVals[0].month() == xVals[1].month()
1060
if self.forceFirstDate and ticks[0] != xVals[0]:
1062
if (axisLength/(ticks[-1]-ticks[0]))*(ticks[1]-ticks[0])<=w:
1063
del ticks[1], labels[1]
1064
if self.forceEndDate and self.niceMonth and j:
1065
if (axisLength/(ticks[-1]-ticks[0]))*(ticks[-1]-ticks[-2])<=w:
1066
del ticks[-2], labels[-2]
1068
if labels[0] and labels[0]==labels[1]:
1069
del ticks[1], labels[1]
1073
return ticks, labels
1075
def _convertXV(self,data):
1076
'''Convert all XValues to a standard normalDate type'''
1078
VC = self._valueClass
1080
for i in xrange(len(D)):
1082
if not isinstance(x,VC):
1085
def _getStepsAndLabels(self,xVals):
1093
if pm: xEOM.append(px)
1097
if xEOM[-1]!=x: xEOM.append(px)
1098
steps, labels = self._xAxisTicker(xEOM)
1100
steps, labels = self._xAxisTicker(xVals)
1101
return steps, labels
1103
def configure(self, data):
1104
self._convertXV(data)
1105
from reportlab.lib.set_ops import union
1106
xVals = reduce(union,map(lambda x: map(lambda dv: dv[0],x),data),[])
1108
steps,labels = self._getStepsAndLabels(xVals)
1109
valueMin, valueMax = self.valueMin, self.valueMax
1110
if valueMin is None: valueMin = xVals[0]
1111
if valueMax is None: valueMax = xVals[-1]
1112
self._valueMin, self._valueMax = valueMin, valueMax
1113
self._tickValues = steps
1114
self._labelTextFormat = labels
1116
self._scaleFactor = self._length / float(valueMax - valueMin)
1117
self._tickValues = steps
1118
self._configured = 1
1120
class YValueAxis(ValueAxis):
1123
_attrMap = AttrMap(BASE=ValueAxis,
1124
tickLeft = AttrMapValue(isNumber,
1125
desc='Tick length left of the axis.'),
1126
tickRight = AttrMapValue(isNumber,
1127
desc='Tick length right of the axis.'),
1128
joinAxis = AttrMapValue(None,
1129
desc='Join both axes if true.'),
1130
joinAxisMode = AttrMapValue(OneOf(('left', 'right', 'value', 'points', None)),
1131
desc="Mode used for connecting axis ('left', 'right', 'value', 'points', None)."),
1132
joinAxisPos = AttrMapValue(isNumberOrNone,
1133
desc='Position at which to join with other axis.'),
1136
# Indicate the dimension of the data we're interested in.
1140
ValueAxis.__init__(self)
1142
self.labels.boxAnchor = 'e'
1149
self.joinAxis = None
1150
self.joinAxisMode = None
1151
self.joinAxisPos = None
1155
data = [(10, 20, 30, 42)]
1156
self.setPosition(100, 10, 80)
1157
self.configure(data)
1159
drawing = Drawing(200, 100)
1165
def joinToAxis(self, xAxis, mode='left', pos=None):
1166
"Join with x-axis using some mode."
1169
self._x = xAxis._x * 1.0
1170
self._y = xAxis._y * 1.0
1171
elif mode == 'right':
1172
self._x = (xAxis._x + xAxis._length) * 1.0
1173
self._y = xAxis._y * 1.0
1174
elif mode == 'value':
1175
self._x = xAxis.scale(pos) * 1.0
1176
self._y = xAxis._y * 1.0
1177
elif mode == 'points':
1179
self._y = xAxis._y * 1.0
1181
def scale(self, value):
1182
"""Converts a numeric value to a Y position.
1184
The chart first configures the axis, then asks it to
1185
work out the x value for each point when plotting
1186
lines or bars. You could override this to do
1190
msg = "Axis cannot scale numbers before it is configured"
1191
assert self._configured, msg
1195
return self._y + self._scaleFactor * (value - self._valueMin)
1201
if not self.visibleAxis:
1206
jam = self.joinAxisMode
1207
jap = self.joinAxisPos
1208
jta = self.joinToAxis
1209
if jam in ('left', 'right'):
1211
elif jam in ('value', 'points'):
1212
jta(ja, mode=jam, pos=jap)
1214
axis = Line(self._x, self._y, self._x, self._y + self._length)
1215
axis.strokeColor = self.strokeColor
1216
axis.strokeWidth = self.strokeWidth
1217
axis.strokeDashArray = self.strokeDashArray
1222
def makeTicks(self):
1224
if self.visibleTicks and (self.tickLeft or self.tickRight):
1225
self._makeLines(g,-self.tickLeft,self.tickRight,self.strokeColor,self.strokeWidth,self.strokeDashArray)
1228
class AdjYValueAxis(YValueAxis):
1229
"""A Y-axis applying additional rules.
1231
Depending on the data and some built-in rules, the axis
1232
may choose to adjust its range and origin.
1234
_attrMap = AttrMap(BASE = YValueAxis,
1235
requiredRange = AttrMapValue(isNumberOrNone, desc='Minimum required value range.'),
1236
leftAxisPercent = AttrMapValue(isBoolean, desc='When true add percent sign to label values.'),
1237
leftAxisOrigShiftIPC = AttrMapValue(isNumber, desc='Lowest label shift interval ratio.'),
1238
leftAxisOrigShiftMin = AttrMapValue(isNumber, desc='Minimum amount to shift.'),
1239
leftAxisSkipLL0 = AttrMapValue(EitherOr((isBoolean,isListOfNumbers)), desc='Skip/Keep lowest tick label when true/false.\nOr skiplist')
1243
apply(YValueAxis.__init__, (self,))
1244
self.requiredRange = 30
1245
self.leftAxisPercent = 1
1246
self.leftAxisOrigShiftIPC = 0.15
1247
self.leftAxisOrigShiftMin = 12
1248
self.leftAxisSkipLL0 = 0
1249
self.valueSteps = None
1251
def _rangeAdjust(self):
1252
"Adjusts the value range of the axis."
1254
from reportlab.graphics.charts.utils import find_good_grid, ticks
1255
y_min, y_max = self._valueMin, self._valueMax
1256
m = self.maximumTicks
1257
n = filter(lambda x,m=m: x<=m,[4,5,6,7,8,9])
1260
valueStep, requiredRange = self.valueStep, self.requiredRange
1261
if requiredRange and y_max - y_min < requiredRange:
1262
y1, y2 = find_good_grid(y_min, y_max,n=n,grid=valueStep)[:2]
1263
if y2 - y1 < requiredRange:
1265
y1 = min(ym-requiredRange*0.5,y_min)
1266
y2 = max(ym+requiredRange*0.5,y_max)
1267
if y_min>=100 and y1<100:
1270
elif y_min>=0 and y1<0:
1273
self._valueMin, self._valueMax = y1, y2
1275
T, L = ticks(self._valueMin, self._valueMax, split=1, n=n, percent=self.leftAxisPercent,grid=valueStep)
1276
abf = self.avoidBoundFrac
1279
if type(abf) not in (TupleType,ListType):
1284
_n = getattr(self,'_cValueMin',T[0])
1285
_x = getattr(self,'_cValueMax',T[-1])
1286
if _n - T[0] < i0: self._valueMin = self._valueMin - i0
1287
if T[-1]-_x < i1: self._valueMax = self._valueMax + i1
1288
T, L = ticks(self._valueMin, self._valueMax, split=1, n=n, percent=self.leftAxisPercent,grid=valueStep)
1290
self._valueMin = T[0]
1291
self._valueMax = T[-1]
1292
self._tickValues = self.valueSteps = T
1293
if self.labelTextFormat is None:
1294
self._labelTextFormat = L
1296
self._labelTextFormat = self.labelTextFormat
1298
if abs(self._valueMin-100)<1e-6:
1299
self._calcValueStep()
1300
vMax, vMin = self._valueMax, self._valueMin
1301
m = max(self.leftAxisOrigShiftIPC*self._valueStep,
1302
(vMax-vMin)*self.leftAxisOrigShiftMin/self._length)
1303
self._valueMin = self._valueMin - m
1305
if self.leftAxisSkipLL0:
1306
if type(self.leftAxisSkipLL0) in (ListType,TupleType):
1307
for x in self.leftAxisSkipLL0:
1316
"Sample drawing with one xcat axis and two buckets."
1318
drawing = Drawing(400, 200)
1322
xAxis = XCategoryAxis()
1323
xAxis.setPosition(75, 75, 300)
1324
xAxis.configure(data)
1325
xAxis.categoryNames = ['Ying', 'Yang']
1326
xAxis.labels.boxAnchor = 'n'
1334
"Sample drawing with one xcat axis and one bucket only."
1336
drawing = Drawing(400, 200)
1340
xAxis = XCategoryAxis()
1341
xAxis.setPosition(75, 75, 300)
1342
xAxis.configure(data)
1343
xAxis.categoryNames = ['Ying']
1344
xAxis.labels.boxAnchor = 'n'
1352
"Sample drawing containing two unconnected axes."
1354
drawing = Drawing(400, 200)
1356
data = [(10, 20, 30, 42)]
1358
xAxis = XCategoryAxis()
1359
xAxis.setPosition(75, 75, 300)
1360
xAxis.configure(data)
1361
xAxis.categoryNames = ['Beer','Wine','Meat','Cannelloni']
1362
xAxis.labels.boxAnchor = 'n'
1363
xAxis.labels[3].dy = -15
1364
xAxis.labels[3].angle = 30
1365
xAxis.labels[3].fontName = 'Times-Bold'
1367
yAxis = YValueAxis()
1368
yAxis.setPosition(50, 50, 125)
1369
yAxis.configure(data)
1377
## "Make sample drawing with two axes, x connected at top of y."
1379
## drawing = Drawing(400, 200)
1381
## data = [(10, 20, 30, 42)]
1383
## yAxis = YValueAxis()
1384
## yAxis.setPosition(50, 50, 125)
1385
## yAxis.configure(data)
1387
## xAxis = XCategoryAxis()
1388
## xAxis._length = 300
1389
## xAxis.configure(data)
1390
## xAxis.joinToAxis(yAxis, mode='top')
1391
## xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1392
## xAxis.labels.boxAnchor = 'n'
1394
## drawing.add(xAxis)
1395
## drawing.add(yAxis)
1401
## "Make two axes, x connected at bottom of y."
1403
## drawing = Drawing(400, 200)
1405
## data = [(10, 20, 30, 42)]
1407
## yAxis = YValueAxis()
1408
## yAxis.setPosition(50, 50, 125)
1409
## yAxis.configure(data)
1411
## xAxis = XCategoryAxis()
1412
## xAxis._length = 300
1413
## xAxis.configure(data)
1414
## xAxis.joinToAxis(yAxis, mode='bottom')
1415
## xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1416
## xAxis.labels.boxAnchor = 'n'
1418
## drawing.add(xAxis)
1419
## drawing.add(yAxis)
1425
## "Make two axes, x connected at fixed value (in points) of y."
1427
## drawing = Drawing(400, 200)
1429
## data = [(10, 20, 30, 42)]
1431
## yAxis = YValueAxis()
1432
## yAxis.setPosition(50, 50, 125)
1433
## yAxis.configure(data)
1435
## xAxis = XCategoryAxis()
1436
## xAxis._length = 300
1437
## xAxis.configure(data)
1438
## xAxis.joinToAxis(yAxis, mode='points', pos=100)
1439
## xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1440
## xAxis.labels.boxAnchor = 'n'
1442
## drawing.add(xAxis)
1443
## drawing.add(yAxis)
1449
## "Make two axes, x connected at fixed value (of y-axes) of y."
1451
## drawing = Drawing(400, 200)
1453
## data = [(10, 20, 30, 42)]
1455
## yAxis = YValueAxis()
1456
## yAxis.setPosition(50, 50, 125)
1457
## yAxis.configure(data)
1459
## xAxis = XCategoryAxis()
1460
## xAxis._length = 300
1461
## xAxis.configure(data)
1462
## xAxis.joinToAxis(yAxis, mode='value', pos=20)
1463
## xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1464
## xAxis.labels.boxAnchor = 'n'
1466
## drawing.add(xAxis)
1467
## drawing.add(yAxis)
1473
## "Make sample drawing with two axes, y connected at left of x."
1475
## drawing = Drawing(400, 200)
1477
## data = [(10, 20, 30, 42)]
1479
## xAxis = XCategoryAxis()
1480
## xAxis._length = 300
1481
## xAxis.configure(data)
1482
## xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1483
## xAxis.labels.boxAnchor = 'n'
1485
## yAxis = YValueAxis()
1486
## yAxis.setPosition(50, 50, 125)
1487
## yAxis.configure(data)
1488
## yAxis.joinToAxis(xAxis, mode='left')
1490
## drawing.add(xAxis)
1491
## drawing.add(yAxis)
1497
## "Make sample drawing with two axes, y connected at right of x."
1499
## drawing = Drawing(400, 200)
1501
## data = [(10, 20, 30, 42)]
1503
## xAxis = XCategoryAxis()
1504
## xAxis._length = 300
1505
## xAxis.configure(data)
1506
## xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1507
## xAxis.labels.boxAnchor = 'n'
1509
## yAxis = YValueAxis()
1510
## yAxis.setPosition(50, 50, 125)
1511
## yAxis.configure(data)
1512
## yAxis.joinToAxis(xAxis, mode='right')
1514
## drawing.add(xAxis)
1515
## drawing.add(yAxis)
1521
## "Make two axes, y connected at fixed value (in points) of x."
1523
## drawing = Drawing(400, 200)
1525
## data = [(10, 20, 30, 42)]
1527
## yAxis = YValueAxis()
1528
## yAxis.setPosition(50, 50, 125)
1529
## yAxis.configure(data)
1531
## xAxis = XValueAxis()
1532
## xAxis._length = 300
1533
## xAxis.configure(data)
1534
## xAxis.joinToAxis(yAxis, mode='points', pos=100)
1536
## drawing.add(xAxis)
1537
## drawing.add(yAxis)
1543
"Sample drawing, xvalue/yvalue axes, y connected at 100 pts to x."
1545
drawing = Drawing(400, 200)
1547
data = [(10, 20, 30, 42)]
1549
yAxis = YValueAxis()
1550
yAxis.setPosition(50, 50, 125)
1551
yAxis.configure(data)
1553
xAxis = XValueAxis()
1555
xAxis.joinAxis = yAxis
1556
xAxis.joinAxisMode = 'points'
1557
xAxis.joinAxisPos = 100
1558
xAxis.configure(data)
1567
"Sample drawing, xvalue/yvalue axes, y connected at value 35 of x."
1569
drawing = Drawing(400, 200)
1571
data = [(10, 20, 30, 42)]
1573
yAxis = YValueAxis()
1574
yAxis.setPosition(50, 50, 125)
1575
yAxis.configure(data)
1577
xAxis = XValueAxis()
1579
xAxis.joinAxis = yAxis
1580
xAxis.joinAxisMode = 'value'
1581
xAxis.joinAxisPos = 35
1582
xAxis.configure(data)
1591
"Sample drawing, xvalue/yvalue axes, y connected to bottom of x."
1593
drawing = Drawing(400, 200)
1595
data = [(10, 20, 30, 42)]
1597
yAxis = YValueAxis()
1598
yAxis.setPosition(50, 50, 125)
1599
yAxis.configure(data)
1601
xAxis = XValueAxis()
1603
xAxis.joinAxis = yAxis
1604
xAxis.joinAxisMode = 'bottom'
1605
xAxis.configure(data)
1614
"xvalue/yvalue axes, without drawing axis lines/ticks."
1616
drawing = Drawing(400, 200)
1618
data = [(10, 20, 30, 42)]
1620
yAxis = YValueAxis()
1621
yAxis.setPosition(50, 50, 125)
1622
yAxis.configure(data)
1623
yAxis.visibleAxis = 0
1624
yAxis.visibleTicks = 0
1626
xAxis = XValueAxis()
1628
xAxis.joinAxis = yAxis
1629
xAxis.joinAxisMode = 'bottom'
1630
xAxis.configure(data)
1631
xAxis.visibleAxis = 0
1632
xAxis.visibleTicks = 0
1641
"Sample drawing, xvalue/yvalue axes, y connected to top of x."
1643
drawing = Drawing(400, 200)
1645
data = [(10, 20, 30, 42)]
1647
yAxis = YValueAxis()
1648
yAxis.setPosition(50, 50, 125)
1649
yAxis.configure(data)
1651
xAxis = XValueAxis()
1653
xAxis.joinAxis = yAxis
1654
xAxis.joinAxisMode = 'top'
1655
xAxis.configure(data)
1664
"Sample drawing, xvalue/yvalue axes, y connected at 100 pts to x."
1666
drawing = Drawing(400, 200)
1668
data = [(10, 20, 30, 42)]
1670
xAxis = XValueAxis()
1671
xAxis.setPosition(50, 50, 300)
1672
xAxis.configure(data)
1674
yAxis = YValueAxis()
1675
yAxis.setPosition(50, 50, 125)
1676
yAxis.joinAxis = xAxis
1677
yAxis.joinAxisMode = 'points'
1678
yAxis.joinAxisPos = 100
1679
yAxis.configure(data)
1688
"Sample drawing, xvalue/yvalue axes, y connected at value 35 of x."
1690
drawing = Drawing(400, 200)
1692
data = [(10, 20, 30, 42)]
1694
xAxis = XValueAxis()
1695
xAxis.setPosition(50, 50, 300)
1696
xAxis.configure(data)
1698
yAxis = YValueAxis()
1699
yAxis.setPosition(50, 50, 125)
1700
yAxis.joinAxis = xAxis
1701
yAxis.joinAxisMode = 'value'
1702
yAxis.joinAxisPos = 35
1703
yAxis.configure(data)
1712
"Sample drawing, xvalue/yvalue axes, y connected at right of x."
1714
drawing = Drawing(400, 200)
1716
data = [(10, 20, 30, 42)]
1718
xAxis = XValueAxis()
1719
xAxis.setPosition(50, 50, 300)
1720
xAxis.configure(data)
1722
yAxis = YValueAxis()
1723
yAxis.setPosition(50, 50, 125)
1724
yAxis.joinAxis = xAxis
1725
yAxis.joinAxisMode = 'right'
1726
yAxis.configure(data)
1735
"Sample drawing, xvalue/yvalue axes, y connected at left of x."
1737
drawing = Drawing(400, 200)
1739
data = [(10, 20, 30, 42)]
1741
xAxis = XValueAxis()
1742
xAxis.setPosition(50, 50, 300)
1743
xAxis.configure(data)
1745
yAxis = YValueAxis()
1746
yAxis.setPosition(50, 50, 125)
1747
yAxis.joinAxis = xAxis
1748
yAxis.joinAxisMode = 'left'
1749
yAxis.configure(data)
1758
"Sample drawing, xcat/yvalue axes, x connected at top of y."
1760
drawing = Drawing(400, 200)
1762
data = [(10, 20, 30, 42)]
1764
yAxis = YValueAxis()
1765
yAxis.setPosition(50, 50, 125)
1766
yAxis.configure(data)
1768
xAxis = XCategoryAxis()
1770
xAxis.configure(data)
1771
xAxis.joinAxis = yAxis
1772
xAxis.joinAxisMode = 'top'
1773
xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1774
xAxis.labels.boxAnchor = 'n'
1783
"Sample drawing, xcat/yvalue axes, x connected at bottom of y."
1785
drawing = Drawing(400, 200)
1787
data = [(10, 20, 30, 42)]
1789
yAxis = YValueAxis()
1790
yAxis.setPosition(50, 50, 125)
1791
yAxis.configure(data)
1793
xAxis = XCategoryAxis()
1795
xAxis.configure(data)
1796
xAxis.joinAxis = yAxis
1797
xAxis.joinAxisMode = 'bottom'
1798
xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1799
xAxis.labels.boxAnchor = 'n'
1808
"Sample drawing, xcat/yvalue axes, x connected at 100 pts to y."
1810
drawing = Drawing(400, 200)
1812
data = [(10, 20, 30, 42)]
1814
yAxis = YValueAxis()
1815
yAxis.setPosition(50, 50, 125)
1816
yAxis.configure(data)
1818
xAxis = XCategoryAxis()
1820
xAxis.configure(data)
1821
xAxis.joinAxis = yAxis
1822
xAxis.joinAxisMode = 'points'
1823
xAxis.joinAxisPos = 100
1824
xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1825
xAxis.labels.boxAnchor = 'n'
1834
"Sample drawing, xcat/yvalue axes, x connected at value 20 of y."
1836
drawing = Drawing(400, 200)
1838
data = [(10, 20, 30, 42)]
1840
yAxis = YValueAxis()
1841
yAxis.setPosition(50, 50, 125)
1842
yAxis.configure(data)
1844
xAxis = XCategoryAxis()
1846
xAxis.configure(data)
1847
xAxis.joinAxis = yAxis
1848
xAxis.joinAxisMode = 'value'
1849
xAxis.joinAxisPos = 20
1850
xAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1851
xAxis.labels.boxAnchor = 'n'
1860
"Sample drawing, xvalue/ycat axes, y connected at right of x."
1862
drawing = Drawing(400, 200)
1864
data = [(10, 20, 30, 42)]
1866
xAxis = XValueAxis()
1868
xAxis.configure(data)
1870
yAxis = YCategoryAxis()
1871
yAxis.setPosition(50, 50, 125)
1872
yAxis.joinAxis = xAxis
1873
yAxis.joinAxisMode = 'right'
1874
yAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1875
yAxis.labels.boxAnchor = 'e'
1876
yAxis.configure(data)
1885
"Sample drawing, xvalue/ycat axes, y connected at left of x."
1887
drawing = Drawing(400, 200)
1889
data = [(10, 20, 30, 42)]
1891
xAxis = XValueAxis()
1893
xAxis.configure(data)
1895
yAxis = YCategoryAxis()
1896
yAxis.setPosition(50, 50, 125)
1897
yAxis.joinAxis = xAxis
1898
yAxis.joinAxisMode = 'left'
1899
yAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1900
yAxis.labels.boxAnchor = 'e'
1901
yAxis.configure(data)
1910
"Sample drawing, xvalue/ycat axes, y connected at value 30 of x."
1912
drawing = Drawing(400, 200)
1914
data = [(10, 20, 30, 42)]
1916
xAxis = XValueAxis()
1918
xAxis.configure(data)
1920
yAxis = YCategoryAxis()
1921
yAxis.setPosition(50, 50, 125)
1922
yAxis.joinAxis = xAxis
1923
yAxis.joinAxisMode = 'value'
1924
yAxis.joinAxisPos = 30
1925
yAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1926
yAxis.labels.boxAnchor = 'e'
1927
yAxis.configure(data)
1936
"Sample drawing, xvalue/ycat axes, y connected at 200 pts to x."
1938
drawing = Drawing(400, 200)
1940
data = [(10, 20, 30, 42)]
1942
xAxis = XValueAxis()
1944
xAxis.configure(data)
1946
yAxis = YCategoryAxis()
1947
yAxis.setPosition(50, 50, 125)
1948
yAxis.joinAxis = xAxis
1949
yAxis.joinAxisMode = 'points'
1950
yAxis.joinAxisPos = 200
1951
yAxis.categoryNames = ['Beer', 'Wine', 'Meat', 'Cannelloni']
1952
yAxis.labels.boxAnchor = 'e'
1953
yAxis.configure(data)