~openerp-groupes/openobject-server/6.0-fix-setup-windows

« back to all changes in this revision

Viewing changes to bin/reportlab/graphics/charts/textlabels.py

  • Committer: pinky
  • Date: 2006-12-07 13:41:40 UTC
  • Revision ID: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
New trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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/textlabels.py
 
4
__version__=''' $Id$ '''
 
5
import string
 
6
 
 
7
from reportlab.lib import colors
 
8
from reportlab.lib.validators import isNumber, isNumberOrNone, OneOf, isColorOrNone, isString, \
 
9
        isTextAnchor, isBoxAnchor, isBoolean, NoneOr, isInstanceOf, isNoneOrString
 
10
from reportlab.lib.attrmap import *
 
11
from reportlab.pdfbase.pdfmetrics import stringWidth
 
12
from reportlab.graphics.shapes import Drawing, Group, Circle, Rect, String, STATE_DEFAULTS
 
13
from reportlab.graphics.shapes import _PATH_OP_ARG_COUNT, _PATH_OP_NAMES, definePath
 
14
from reportlab.graphics.widgetbase import Widget, PropHolder
 
15
 
 
16
_gs = None
 
17
_A2BA=  {
 
18
        'x': {0:'n', 45:'ne', 90:'e', 135:'se', 180:'s', 225:'sw', 270:'w', 315: 'nw', -45: 'nw'},
 
19
        'y': {0:'e', 45:'se', 90:'s', 135:'sw', 180:'w', 225:'nw', 270:'n', 315: 'ne', -45: 'ne'},
 
20
        }
 
21
def _simpleSplit(txt,mW,SW):
 
22
    L = []
 
23
    ws = SW(' ')
 
24
    O = []
 
25
    w = -ws
 
26
    for t in string.split(txt):
 
27
        lt = SW(t)
 
28
        if w+ws+lt<=mW or O==[]:
 
29
            O.append(t)
 
30
            w = w + ws + lt
 
31
        else:
 
32
            L.append(string.join(O,' '))
 
33
            O = [t]
 
34
            w = lt
 
35
    if O!=[]: L.append(string.join(O,' '))
 
36
    return L
 
37
 
 
38
def _pathNumTrunc(n):
 
39
    if int(n)==n: return int(n)
 
40
    return round(n,5)
 
41
 
 
42
def _processGlyph(G, truncate=1, pathReverse=0):
 
43
    O = []
 
44
    P = []
 
45
    R = []
 
46
    for g in G+(('end',),):
 
47
        op = g[0]
 
48
        if O and op in ['moveTo', 'moveToClosed','end']:
 
49
            if O[0]=='moveToClosed':
 
50
                O = O[1:]
 
51
                if pathReverse:
 
52
                    for i in xrange(0,len(P),2):
 
53
                        P[i+1], P[i] = P[i:i+2]
 
54
                    P.reverse()
 
55
                    O.reverse()
 
56
                O.insert(0,'moveTo')
 
57
                O.append('closePath')
 
58
            i = 0
 
59
            if truncate: P = map(_pathNumTrunc,P)
 
60
            for o in O:
 
61
                j = i + _PATH_OP_ARG_COUNT[_PATH_OP_NAMES.index(o)]
 
62
                if o=='closePath':
 
63
                    R.append(o)
 
64
                else:
 
65
                    R.append((o,)+ tuple(P[i:j]))
 
66
                i = j
 
67
            O = []
 
68
            P = []
 
69
        O.append(op)
 
70
        P.extend(g[1:])
 
71
    return R
 
72
 
 
73
def _text2PathDescription(text, x=0, y=0, fontName='Times-Roman', fontSize=1000,
 
74
                            anchor='start', truncate=1, pathReverse=0):
 
75
    global _gs
 
76
    if not _gs:
 
77
        import _renderPM
 
78
        _gs = _renderPM.gstate(1,1)
 
79
    from reportlab.graphics import renderPM
 
80
    renderPM._setFont(_gs,fontName,fontSize)
 
81
    P = []
 
82
    if not anchor =='start':
 
83
        textLen = stringWidth(text, fontName,fontSize)
 
84
        if text_anchor=='end':
 
85
            x = x-textLen
 
86
        elif text_anchor=='middle':
 
87
            x = x - textLen/2.
 
88
    for g in _gs._stringPath(text,x,y):
 
89
        P.extend(_processGlyph(g,truncate=truncate,pathReverse=pathReverse))
 
90
    return P
 
91
 
 
92
def _text2Path(text, x=0, y=0, fontName='Times-Roman', fontSize=1000,
 
93
                anchor='start', truncate=1, pathReverse=0):
 
94
    return definePath(_text2PathDescription(text,x=x,y=y,fontName=fontName,
 
95
                    fontSize=fontSize,anchor=anchor,truncate=truncate,pathReverse=pathReverse))
 
96
 
 
97
_BA2TA={'w':'start','nw':'start','sw':'start','e':'end', 'ne': 'end', 'se':'end', 'n':'middle','s':'middle','c':'middle'}
 
98
class Label(Widget):
 
99
    """A text label to attach to something else, such as a chart axis.
 
100
 
 
101
    This allows you to specify an offset, angle and many anchor
 
102
    properties relative to the label's origin.  It allows, for example,
 
103
    angled multiline axis labels.
 
104
    """
 
105
    # fairly straight port of Robin Becker's textbox.py to new widgets
 
106
    # framework.
 
107
 
 
108
    _attrMap = AttrMap(
 
109
        x = AttrMapValue(isNumber),
 
110
        y = AttrMapValue(isNumber),
 
111
        dx = AttrMapValue(isNumber),
 
112
        dy = AttrMapValue(isNumber),
 
113
        angle = AttrMapValue(isNumber),
 
114
        boxAnchor = AttrMapValue(isBoxAnchor),
 
115
        boxStrokeColor = AttrMapValue(isColorOrNone),
 
116
        boxStrokeWidth = AttrMapValue(isNumber),
 
117
        boxFillColor = AttrMapValue(isColorOrNone),
 
118
        fillColor = AttrMapValue(isColorOrNone),
 
119
        strokeColor = AttrMapValue(isColorOrNone),
 
120
        strokeWidth = AttrMapValue(isNumber),
 
121
        text = AttrMapValue(isString),
 
122
        fontName = AttrMapValue(isString),
 
123
        fontSize = AttrMapValue(isNumber),
 
124
        leading = AttrMapValue(isNumberOrNone),
 
125
        width = AttrMapValue(isNumberOrNone),
 
126
        maxWidth = AttrMapValue(isNumberOrNone),
 
127
        height = AttrMapValue(isNumberOrNone),
 
128
        textAnchor = AttrMapValue(isTextAnchor),
 
129
        visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
 
130
        topPadding = AttrMapValue(isNumber,'padding at top of box'),
 
131
        leftPadding = AttrMapValue(isNumber,'padding at left of box'),
 
132
        rightPadding = AttrMapValue(isNumber,'padding at right of box'),
 
133
        bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'),
 
134
        )
 
135
 
 
136
    def __init__(self,**kw):
 
137
        self._setKeywords(**kw)
 
138
        self._setKeywords(
 
139
                _text = 'Multi-Line\nString',
 
140
                boxAnchor = 'c',
 
141
                angle = 0,
 
142
                x = 0,
 
143
                y = 0,
 
144
                dx = 0,
 
145
                dy = 0,
 
146
                topPadding = 0,
 
147
                leftPadding = 0,
 
148
                rightPadding = 0,
 
149
                bottomPadding = 0,
 
150
                boxStrokeWidth = 0.5,
 
151
                boxStrokeColor = None,
 
152
                strokeColor = None,
 
153
                boxFillColor = None,
 
154
                leading = None,
 
155
                width = None,
 
156
                maxWidth = None,
 
157
                height = None,
 
158
                fillColor = STATE_DEFAULTS['fillColor'],
 
159
                fontName = STATE_DEFAULTS['fontName'],
 
160
                fontSize = STATE_DEFAULTS['fontSize'],
 
161
                strokeWidth = 0.1,
 
162
                textAnchor = 'start',
 
163
                visible = 1,
 
164
                )
 
165
 
 
166
    def setText(self, text):
 
167
        """Set the text property.  May contain embedded newline characters.
 
168
        Called by the containing chart or axis."""
 
169
        self._text = text
 
170
 
 
171
 
 
172
    def setOrigin(self, x, y):
 
173
        """Set the origin.  This would be the tick mark or bar top relative to
 
174
        which it is defined.  Called by the containing chart or axis."""
 
175
        self.x = x
 
176
        self.y = y
 
177
 
 
178
 
 
179
    def demo(self):
 
180
        """This shows a label positioned with its top right corner
 
181
        at the top centre of the drawing, and rotated 45 degrees."""
 
182
 
 
183
        d = Drawing(200, 100)
 
184
 
 
185
        # mark the origin of the label
 
186
        d.add(Circle(100,90, 5, fillColor=colors.green))
 
187
 
 
188
        lab = Label()
 
189
        lab.setOrigin(100,90)
 
190
        lab.boxAnchor = 'ne'
 
191
        lab.angle = 45
 
192
        lab.dx = 0
 
193
        lab.dy = -20
 
194
        lab.boxStrokeColor = colors.green
 
195
        lab.setText('Another\nMulti-Line\nString')
 
196
        d.add(lab)
 
197
 
 
198
        return d
 
199
 
 
200
    def _getBoxAnchor(self):
 
201
        '''hook for allowing special box anchor effects'''
 
202
        ba = self.boxAnchor
 
203
        if ba in ('autox', 'autoy'):
 
204
            angle = self.angle
 
205
            na = (int((angle%360)/45.)*45)%360
 
206
            if not (na % 90): # we have a right angle case
 
207
                da = (angle - na) % 360
 
208
                if abs(da)>5:
 
209
                    na = na + (da>0 and 45 or -45)
 
210
            ba = _A2BA[ba[-1]][na]
 
211
        return ba
 
212
 
 
213
    def computeSize(self):
 
214
        # the thing will draw in its own coordinate system
 
215
        self._lines = string.split(self._text, '\n')
 
216
        self._lineWidths = []
 
217
        topPadding = self.topPadding
 
218
        leftPadding = self.leftPadding
 
219
        rightPadding = self.rightPadding
 
220
        bottomPadding = self.bottomPadding
 
221
        SW = lambda text, fN=self.fontName, fS=self.fontSize: stringWidth(text, fN, fS)
 
222
        if self.maxWidth:
 
223
            L = []
 
224
            for l in self._lines:
 
225
                L[-1:-1] = _simpleSplit(l,self.maxWidth,SW)
 
226
            self._lines = L
 
227
        if not self.width:
 
228
            w = 0
 
229
            for line in self._lines:
 
230
                thisWidth = SW(line)
 
231
                self._lineWidths.append(thisWidth)
 
232
                w = max(w,thisWidth)
 
233
            self._width = w+leftPadding+rightPadding
 
234
        else:
 
235
            self._width = self.width
 
236
        self._height = self.height or ((self.leading or 1.2*self.fontSize) * len(self._lines)+topPadding+bottomPadding)
 
237
        self._ewidth = (self._width-leftPadding-rightPadding)
 
238
        self._eheight = (self._height-topPadding-bottomPadding)
 
239
        boxAnchor = self._getBoxAnchor()
 
240
        if boxAnchor in ['n','ne','nw']:
 
241
            self._top = -topPadding
 
242
        elif boxAnchor in ['s','sw','se']:
 
243
            self._top = self._height-topPadding
 
244
        else:
 
245
            self._top = 0.5*self._eheight
 
246
        self._bottom = self._top - self._eheight
 
247
 
 
248
        if boxAnchor in ['ne','e','se']:
 
249
            self._left = leftPadding - self._width
 
250
        elif boxAnchor in ['nw','w','sw']:
 
251
            self._left = leftPadding
 
252
        else:
 
253
            self._left = -self._ewidth*0.5
 
254
        self._right = self._left+self._ewidth
 
255
 
 
256
    def _getTextAnchor(self):
 
257
        '''This can be overridden to allow special effects'''
 
258
        ta = self.textAnchor
 
259
        if ta=='boxauto': ta = _BA2TA[self._getBoxAnchor()]
 
260
        return ta
 
261
 
 
262
    def draw(self):
 
263
        _text = self._text
 
264
        self._text = _text or ''
 
265
        self.computeSize()
 
266
        self._text = _text
 
267
        g = Group()
 
268
        g.translate(self.x + self.dx, self.y + self.dy)
 
269
        g.rotate(self.angle)
 
270
 
 
271
        y = self._top - self.fontSize
 
272
        textAnchor = self._getTextAnchor()
 
273
        if textAnchor == 'start':
 
274
            x = self._left
 
275
        elif textAnchor == 'middle':
 
276
            x = self._left + self._ewidth*0.5
 
277
        else:
 
278
            x = self._right
 
279
 
 
280
        # paint box behind text just in case they
 
281
        # fill it
 
282
        if self.boxFillColor or (self.boxStrokeColor and self.boxStrokeWidth):
 
283
            g.add(Rect( self._left-self.leftPadding,
 
284
                        self._bottom-self.bottomPadding,
 
285
                        self._width,
 
286
                        self._height,
 
287
                        strokeColor=self.boxStrokeColor,
 
288
                        strokeWidth=self.boxStrokeWidth,
 
289
                        fillColor=self.boxFillColor)
 
290
                        )
 
291
 
 
292
        fillColor, fontName, fontSize = self.fillColor, self.fontName, self.fontSize
 
293
        strokeColor, strokeWidth, leading = self.strokeColor, self.strokeWidth, (self.leading or 1.2*fontSize)
 
294
        if strokeColor:
 
295
            for line in self._lines:
 
296
                s = _text2Path(line, x, y, fontName, fontSize, textAnchor)
 
297
                s.fillColor = fillColor
 
298
                s.strokeColor = strokeColor
 
299
                s.strokeWidth = strokeWidth
 
300
                g.add(s)
 
301
                y = y - leading
 
302
        else:
 
303
            for line in self._lines:
 
304
                s = String(x, y, line)
 
305
                s.textAnchor = textAnchor
 
306
                s.fontName = fontName
 
307
                s.fontSize = fontSize
 
308
                s.fillColor = fillColor
 
309
                g.add(s)
 
310
                y = y - leading
 
311
 
 
312
        return g
 
313
 
 
314
class LabelDecorator:
 
315
    _attrMap = AttrMap(
 
316
        x = AttrMapValue(isNumberOrNone),
 
317
        y = AttrMapValue(isNumberOrNone),
 
318
        dx = AttrMapValue(isNumberOrNone),
 
319
        dy = AttrMapValue(isNumberOrNone),
 
320
        angle = AttrMapValue(isNumberOrNone),
 
321
        boxAnchor = AttrMapValue(isBoxAnchor),
 
322
        boxStrokeColor = AttrMapValue(isColorOrNone),
 
323
        boxStrokeWidth = AttrMapValue(isNumberOrNone),
 
324
        boxFillColor = AttrMapValue(isColorOrNone),
 
325
        fillColor = AttrMapValue(isColorOrNone),
 
326
        strokeColor = AttrMapValue(isColorOrNone),
 
327
        strokeWidth = AttrMapValue(isNumberOrNone),
 
328
        fontName = AttrMapValue(isNoneOrString),
 
329
        fontSize = AttrMapValue(isNumberOrNone),
 
330
        leading = AttrMapValue(isNumberOrNone),
 
331
        width = AttrMapValue(isNumberOrNone),
 
332
        maxWidth = AttrMapValue(isNumberOrNone),
 
333
        height = AttrMapValue(isNumberOrNone),
 
334
        textAnchor = AttrMapValue(isTextAnchor),
 
335
        visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
 
336
        )
 
337
 
 
338
    def __init__(self):
 
339
        self.textAnchor = 'start'
 
340
        self.boxAnchor = 'w'
 
341
        for a in self._attrMap.keys():
 
342
            if not hasattr(self,a): setattr(self,a,None)
 
343
 
 
344
    def decorate(self,l,L):
 
345
        chart,g,rowNo,colNo,x,y,width,height,x00,y00,x0,y0 = l._callOutInfo
 
346
        L.setText(chart.categoryAxis.categoryNames[colNo])
 
347
        g.add(L)
 
348
 
 
349
    def __call__(self,l):
 
350
        from copy import deepcopy
 
351
        L = Label()
 
352
        for a,v in self.__dict__.items():
 
353
            if v is None: v = getattr(l,a,None)
 
354
            setattr(L,a,v)
 
355
        self.decorate(l,L)
 
356
 
 
357
isOffsetMode=OneOf('high','low','bar','axis')
 
358
class LabelOffset(PropHolder):
 
359
    _attrMap = AttrMap(
 
360
                posMode = AttrMapValue(isOffsetMode,desc="Where to base +ve offset"),
 
361
                pos = AttrMapValue(isNumber,desc='Value for positive elements'),
 
362
                negMode = AttrMapValue(isOffsetMode,desc="Where to base -ve offset"),
 
363
                neg = AttrMapValue(isNumber,desc='Value for negative elements'),
 
364
                )
 
365
    def __init__(self):
 
366
        self.posMode=self.negMode='axis'
 
367
        self.pos = self.neg = 0
 
368
 
 
369
    def _getValue(self, chart, val):
 
370
        flipXY = chart._flipXY
 
371
        A = chart.categoryAxis
 
372
        jA = A.joinAxis
 
373
        if val>=0:
 
374
            mode = self.posMode
 
375
            delta = self.pos
 
376
        else:
 
377
            mode = self.negMode
 
378
            delta = self.neg
 
379
        if flipXY:
 
380
            v = A._x
 
381
        else:
 
382
            v = A._y
 
383
        if jA:
 
384
            if flipXY:
 
385
                _v = jA._x
 
386
            else:
 
387
                _v = jA._y
 
388
            if mode=='high':
 
389
                v = _v + jA._length
 
390
            elif mode=='low':
 
391
                v = _v
 
392
            elif mode=='bar':
 
393
                v = _v+val
 
394
        return v+delta
 
395
 
 
396
NoneOrInstanceOfLabelOffset=NoneOr(isInstanceOf(LabelOffset))
 
397
 
 
398
class BarChartLabel(Label):
 
399
    """
 
400
    An extended Label allowing for nudging, lines visibility etc
 
401
    """
 
402
    _attrMap = AttrMap(
 
403
        BASE=Label,
 
404
        lineStrokeWidth = AttrMapValue(isNumberOrNone, desc="Non-zero for a drawn line"),
 
405
        lineStrokeColor = AttrMapValue(isColorOrNone, desc="Color for a drawn line"),
 
406
        fixedEnd = AttrMapValue(NoneOrInstanceOfLabelOffset, desc="None or fixed draw ends +/-"),
 
407
        fixedStart = AttrMapValue(NoneOrInstanceOfLabelOffset, desc="None or fixed draw starts +/-"),
 
408
        nudge = AttrMapValue(isNumber, desc="Non-zero sign dependent nudge"),
 
409
        )
 
410
 
 
411
    def __init__(self):
 
412
        Label.__init__(self)
 
413
        self.lineStrokeWidth = 0
 
414
        self.lineStrokeColor = None
 
415
        self.nudge = 0
 
416
        self.fixedStart = self.fixedEnd = None
 
417
        self._pmv = 0
 
418
 
 
419
    def _getBoxAnchor(self):
 
420
        a = self.boxAnchor
 
421
        if self._pmv<0: a = {'nw':'se','n':'s','ne':'sw','w':'e','c':'c','e':'w','sw':'ne','s':'n','se':'nw'}[a]
 
422
        return a
 
423
 
 
424
    def _getTextAnchor(self):
 
425
        a = self.textAnchor
 
426
        if self._pmv<0: a = {'start':'end', 'middle':'middle', 'end':'start'}[a]
 
427
        return a
 
428
 
 
429
class NA_Label(BarChartLabel):
 
430
    """
 
431
    An extended Label allowing for nudging, lines visibility etc
 
432
    """
 
433
    _attrMap = AttrMap(
 
434
        BASE=BarChartLabel,
 
435
        text = AttrMapValue(isNoneOrString, desc="Text to be used for N/A values"),
 
436
        )
 
437
    def __init__(self):
 
438
        BarChartLabel.__init__(self)
 
439
        self.text = 'n/a'
 
440
NoneOrInstanceOfNA_Label=NoneOr(isInstanceOf(NA_Label))