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

« back to all changes in this revision

Viewing changes to bin/reportlab/graphics/charts/spider.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/spider.py
 
4
# spider chart, also known as radar chart
 
5
 
 
6
"""Spider Chart
 
7
 
 
8
Normal use shows variation of 5-10 parameters against some 'norm' or target.
 
9
When there is more than one series, place the series with the largest
 
10
numbers first, as it will be overdrawn by each successive one.
 
11
"""
 
12
__version__=''' $Id$ '''
 
13
 
 
14
import copy
 
15
from math import sin, cos, pi
 
16
 
 
17
from reportlab.lib import colors
 
18
from reportlab.lib.validators import isColor, isNumber, isListOfNumbersOrNone,\
 
19
                                    isListOfNumbers, isColorOrNone, isString,\
 
20
                                    isListOfStringsOrNone, OneOf, SequenceOf,\
 
21
                                    isBoolean, isListOfColors, isNumberOrNone,\
 
22
                                    isNoneOrListOfNoneOrStrings, isTextAnchor,\
 
23
                                    isNoneOrListOfNoneOrNumbers, isBoxAnchor,\
 
24
                                    isStringOrNone
 
25
from reportlab.lib.attrmap import *
 
26
from reportlab.pdfgen.canvas import Canvas
 
27
from reportlab.graphics.shapes import Group, Drawing, Line, Rect, Polygon, Ellipse, \
 
28
    Wedge, String, STATE_DEFAULTS
 
29
from reportlab.graphics.widgetbase import Widget, TypedPropertyCollection, PropHolder
 
30
from reportlab.graphics.charts.areas import PlotArea
 
31
from piecharts import WedgeLabel
 
32
from reportlab.graphics.widgets.markers import makeMarker, uSymbol2Symbol
 
33
 
 
34
class StrandProperties(PropHolder):
 
35
    """This holds descriptive information about concentric 'strands'.
 
36
 
 
37
    Line style, whether filled etc.
 
38
    """
 
39
 
 
40
    _attrMap = AttrMap(
 
41
        strokeWidth = AttrMapValue(isNumber),
 
42
        fillColor = AttrMapValue(isColorOrNone),
 
43
        strokeColor = AttrMapValue(isColorOrNone),
 
44
        strokeDashArray = AttrMapValue(isListOfNumbersOrNone),
 
45
        fontName = AttrMapValue(isString),
 
46
        fontSize = AttrMapValue(isNumber),
 
47
        fontColor = AttrMapValue(isColorOrNone),
 
48
        labelRadius = AttrMapValue(isNumber),
 
49
        markers = AttrMapValue(isBoolean),
 
50
        markerType = AttrMapValue(isAnything),
 
51
        markerSize = AttrMapValue(isNumber),
 
52
        label_dx = AttrMapValue(isNumber),
 
53
        label_dy = AttrMapValue(isNumber),
 
54
        label_angle = AttrMapValue(isNumber),
 
55
        label_boxAnchor = AttrMapValue(isBoxAnchor),
 
56
        label_boxStrokeColor = AttrMapValue(isColorOrNone),
 
57
        label_boxStrokeWidth = AttrMapValue(isNumber),
 
58
        label_boxFillColor = AttrMapValue(isColorOrNone),
 
59
        label_strokeColor = AttrMapValue(isColorOrNone),
 
60
        label_strokeWidth = AttrMapValue(isNumber),
 
61
        label_text = AttrMapValue(isStringOrNone),
 
62
        label_leading = AttrMapValue(isNumberOrNone),
 
63
        label_width = AttrMapValue(isNumberOrNone),
 
64
        label_maxWidth = AttrMapValue(isNumberOrNone),
 
65
        label_height = AttrMapValue(isNumberOrNone),
 
66
        label_textAnchor = AttrMapValue(isTextAnchor),
 
67
        label_visible = AttrMapValue(isBoolean,desc="True if the label is to be drawn"),
 
68
        label_topPadding = AttrMapValue(isNumber,'padding at top of box'),
 
69
        label_leftPadding = AttrMapValue(isNumber,'padding at left of box'),
 
70
        label_rightPadding = AttrMapValue(isNumber,'padding at right of box'),
 
71
        label_bottomPadding = AttrMapValue(isNumber,'padding at bottom of box'),
 
72
        )
 
73
 
 
74
    def __init__(self):
 
75
        self.strokeWidth = 0
 
76
        self.fillColor = None
 
77
        self.strokeColor = STATE_DEFAULTS["strokeColor"]
 
78
        self.strokeDashArray = STATE_DEFAULTS["strokeDashArray"]
 
79
        self.fontName = STATE_DEFAULTS["fontName"]
 
80
        self.fontSize = STATE_DEFAULTS["fontSize"]
 
81
        self.fontColor = STATE_DEFAULTS["fillColor"]
 
82
        self.labelRadius = 1.2
 
83
        self.markers = 0
 
84
        self.markerType = None
 
85
        self.markerSize = 0
 
86
        self.label_dx = self.label_dy = self.label_angle = 0
 
87
        self.label_text = None
 
88
        self.label_topPadding = self.label_leftPadding = self.label_rightPadding = self.label_bottomPadding = 0
 
89
        self.label_boxAnchor = 'c'
 
90
        self.label_boxStrokeColor = None    #boxStroke
 
91
        self.label_boxStrokeWidth = 0.5 #boxStrokeWidth
 
92
        self.label_boxFillColor = None
 
93
        self.label_strokeColor = None
 
94
        self.label_strokeWidth = 0.1
 
95
        self.label_leading =    self.label_width = self.label_maxWidth = self.label_height = None
 
96
        self.label_textAnchor = 'start'
 
97
        self.label_visible = 1
 
98
 
 
99
class SpiderChart(PlotArea):
 
100
    _attrMap = AttrMap(BASE=PlotArea,
 
101
        data = AttrMapValue(None, desc='Data to be plotted, list of (lists of) numbers.'),
 
102
        labels = AttrMapValue(isListOfStringsOrNone, desc="optional list of labels to use for each data point"),
 
103
        startAngle = AttrMapValue(isNumber, desc="angle of first slice; like the compass, 0 is due North"),
 
104
        direction = AttrMapValue( OneOf('clockwise', 'anticlockwise'), desc="'clockwise' or 'anticlockwise'"),
 
105
        strands = AttrMapValue(None, desc="collection of strand descriptor objects"),
 
106
        )
 
107
 
 
108
    def __init__(self):
 
109
        PlotArea.__init__(self)
 
110
 
 
111
        self.data = [[10,12,14,16,14,12], [6,8,10,12,9,11]]
 
112
        self.labels = None  # or list of strings
 
113
        self.startAngle = 90
 
114
        self.direction = "clockwise"
 
115
 
 
116
        self.strands = TypedPropertyCollection(StrandProperties)
 
117
        self.strands[0].fillColor = colors.cornsilk
 
118
        self.strands[1].fillColor = colors.cyan
 
119
 
 
120
 
 
121
    def demo(self):
 
122
        d = Drawing(200, 100)
 
123
 
 
124
        sp = SpiderChart()
 
125
        sp.x = 50
 
126
        sp.y = 10
 
127
        sp.width = 100
 
128
        sp.height = 80
 
129
        sp.data = [[10,12,14,16,18,20],[6,8,4,6,8,10]]
 
130
        sp.labels = ['a','b','c','d','e','f']
 
131
 
 
132
        d.add(sp)
 
133
        return d
 
134
 
 
135
    def normalizeData(self, outer = 0.0):
 
136
        """Turns data into normalized ones where each datum is < 1.0,
 
137
        and 1.0 = maximum radius.  Adds 10% at outside edge by default"""
 
138
        data = self.data
 
139
        theMax = 0.0
 
140
        for row in data:
 
141
            for element in row:
 
142
                assert element >=0, "Cannot do spider plots of negative numbers!"
 
143
                if element > theMax:
 
144
                    theMax = element
 
145
        theMax = theMax * (1.0+outer)
 
146
 
 
147
        scaled = []
 
148
        for row in data:
 
149
            scaledRow = []
 
150
            for element in row:
 
151
                scaledRow.append(element / theMax)
 
152
            scaled.append(scaledRow)
 
153
        return scaled
 
154
 
 
155
 
 
156
    def draw(self):
 
157
        # normalize slice data
 
158
        g = self.makeBackground() or Group()
 
159
 
 
160
        xradius = self.width/2.0
 
161
        yradius = self.height/2.0
 
162
        self._radius = radius = min(xradius, yradius)
 
163
        centerx = self.x + xradius
 
164
        centery = self.y + yradius
 
165
 
 
166
        data = self.normalizeData()
 
167
 
 
168
        n = len(data[0])
 
169
 
 
170
        #labels
 
171
        if self.labels is None:
 
172
            labels = [''] * n
 
173
        else:
 
174
            labels = self.labels
 
175
            #there's no point in raising errors for less than enough errors if
 
176
            #we silently create all for the extreme case of no labels.
 
177
            i = n-len(labels)
 
178
            if i>0:
 
179
                labels = labels + ['']*i
 
180
 
 
181
        spokes = []
 
182
        csa = []
 
183
        angle = self.startAngle*pi/180
 
184
        direction = self.direction == "clockwise" and -1 or 1
 
185
        angleBetween = direction*(2 * pi)/n
 
186
        markers = self.strands.markers
 
187
        for i in xrange(n):
 
188
            car = cos(angle)*radius
 
189
            sar = sin(angle)*radius
 
190
            csa.append((car,sar,angle))
 
191
            spoke = Line(centerx, centery, centerx + car, centery + sar, strokeWidth = 0.5)
 
192
            #print 'added spoke (%0.2f, %0.2f) -> (%0.2f, %0.2f)' % (spoke.x1, spoke.y1, spoke.x2, spoke.y2)
 
193
            spokes.append(spoke)
 
194
            if labels:
 
195
                si = self.strands[i]
 
196
                text = si.label_text
 
197
                if text is None: text = labels[i]
 
198
                if text:
 
199
                    labelRadius = si.labelRadius
 
200
                    L = WedgeLabel()
 
201
                    L.x = centerx + labelRadius*car
 
202
                    L.y = centery + labelRadius*sar
 
203
                    L.boxAnchor = si.label_boxAnchor
 
204
                    L._pmv = angle*180/pi
 
205
                    L.dx = si.label_dx
 
206
                    L.dy = si.label_dy
 
207
                    L.angle = si.label_angle
 
208
                    L.boxAnchor = si.label_boxAnchor
 
209
                    L.boxStrokeColor = si.label_boxStrokeColor
 
210
                    L.boxStrokeWidth = si.label_boxStrokeWidth
 
211
                    L.boxFillColor = si.label_boxFillColor
 
212
                    L.strokeColor = si.label_strokeColor
 
213
                    L.strokeWidth = si.label_strokeWidth
 
214
                    L._text = text
 
215
                    L.leading = si.label_leading
 
216
                    L.width = si.label_width
 
217
                    L.maxWidth = si.label_maxWidth
 
218
                    L.height = si.label_height
 
219
                    L.textAnchor = si.label_textAnchor
 
220
                    L.visible = si.label_visible
 
221
                    L.topPadding = si.label_topPadding
 
222
                    L.leftPadding = si.label_leftPadding
 
223
                    L.rightPadding = si.label_rightPadding
 
224
                    L.bottomPadding = si.label_bottomPadding
 
225
                    L.fontName = si.fontName
 
226
                    L.fontSize = si.fontSize
 
227
                    L.fillColor = si.fontColor
 
228
                    spokes.append(L)
 
229
            angle = angle + angleBetween
 
230
 
 
231
        # now plot the polygons
 
232
 
 
233
        rowIdx = 0
 
234
        for row in data:
 
235
            # series plot
 
236
            points = []
 
237
            car, sar = csa[-1][:2]
 
238
            r = row[-1]
 
239
            points.append(centerx+car*r)
 
240
            points.append(centery+sar*r)
 
241
            for i in xrange(n):
 
242
                car, sar = csa[i][:2]
 
243
                r = row[i]
 
244
                points.append(centerx+car*r)
 
245
                points.append(centery+sar*r)
 
246
 
 
247
                # make up the 'strand'
 
248
                strand = Polygon(points)
 
249
                strand.fillColor = self.strands[rowIdx].fillColor
 
250
                strand.strokeColor = self.strands[rowIdx].strokeColor
 
251
                strand.strokeWidth = self.strands[rowIdx].strokeWidth
 
252
                strand.strokeDashArray = self.strands[rowIdx].strokeDashArray
 
253
 
 
254
                g.add(strand)
 
255
 
 
256
                # put in a marker, if it needs one
 
257
                if markers:
 
258
                    if hasattr(self.strands[rowIdx], 'markerType'):
 
259
                        uSymbol = self.strands[rowIdx].markerType
 
260
                    elif hasattr(self.strands, 'markerType'):
 
261
                        uSymbol = self.strands.markerType
 
262
                    else:
 
263
                        uSymbol = None
 
264
                    m_x =  centerx+car*r
 
265
                    m_y = centery+sar*r
 
266
                    m_size = self.strands[rowIdx].markerSize
 
267
                    m_fillColor = self.strands[rowIdx].fillColor
 
268
                    m_strokeColor = self.strands[rowIdx].strokeColor
 
269
                    m_strokeWidth = self.strands[rowIdx].strokeWidth
 
270
                    m_angle = 0
 
271
                    if type(uSymbol) is type(''):
 
272
                        symbol = makeMarker(uSymbol,
 
273
                                    size = m_size,
 
274
                                    x =  m_x,
 
275
                                    y = m_y,
 
276
                                    fillColor = m_fillColor,
 
277
                                    strokeColor = m_strokeColor,
 
278
                                    strokeWidth = m_strokeWidth,
 
279
                                    angle = m_angle,
 
280
                                    )
 
281
                    else:
 
282
                        symbol = uSymbol2Symbol(uSymbol,m_x,m_y,m_fillColor)
 
283
                        for k,v in (('size', m_size), ('fillColor', m_fillColor),
 
284
                                    ('x', m_x), ('y', m_y),
 
285
                                    ('strokeColor',m_strokeColor), ('strokeWidth',m_strokeWidth),
 
286
                                    ('angle',m_angle),):
 
287
                            try:
 
288
                                setattr(uSymbol,k,v)
 
289
                            except:
 
290
                                pass
 
291
                    g.add(symbol)
 
292
 
 
293
            rowIdx = rowIdx + 1
 
294
 
 
295
        # spokes go over strands
 
296
        for spoke in spokes:
 
297
            g.add(spoke)
 
298
        return g
 
299
 
 
300
def sample1():
 
301
    "Make a simple spider chart"
 
302
 
 
303
    d = Drawing(400, 400)
 
304
 
 
305
    pc = SpiderChart()
 
306
    pc.x = 50
 
307
    pc.y = 50
 
308
    pc.width = 300
 
309
    pc.height = 300
 
310
    pc.data = [[10,12,14,16,14,12], [6,8,10,12,9,15],[7,8,17,4,12,8,3]]
 
311
    pc.labels = ['a','b','c','d','e','f']
 
312
    pc.strands[2].fillColor=colors.palegreen
 
313
 
 
314
    d.add(pc)
 
315
 
 
316
    return d
 
317
 
 
318
 
 
319
def sample2():
 
320
    "Make a spider chart with markers, but no fill"
 
321
 
 
322
    d = Drawing(400, 400)
 
323
 
 
324
    pc = SpiderChart()
 
325
    pc.x = 50
 
326
    pc.y = 50
 
327
    pc.width = 300
 
328
    pc.height = 300
 
329
    pc.data = [[10,12,14,16,14,12], [6,8,10,12,9,15],[7,8,17,4,12,8,3]]
 
330
    pc.labels = ['U','V','W','X','Y','Z']
 
331
    pc.strands.strokeWidth = 2
 
332
    pc.strands[0].fillColor = None
 
333
    pc.strands[1].fillColor = None
 
334
    pc.strands[2].fillColor = None
 
335
    pc.strands[0].strokeColor = colors.red
 
336
    pc.strands[1].strokeColor = colors.blue
 
337
    pc.strands[2].strokeColor = colors.green
 
338
    pc.strands.markers = 1
 
339
    pc.strands.markerType = "FilledDiamond"
 
340
    pc.strands.markerSize = 6
 
341
 
 
342
    d.add(pc)
 
343
 
 
344
    return d
 
345
 
 
346
 
 
347
if __name__=='__main__':
 
348
    d = sample1()
 
349
    from reportlab.graphics.renderPDF import drawToFile
 
350
    drawToFile(d, 'spider.pdf')
 
351
    d = sample2()
 
352
    drawToFile(d, 'spider2.pdf')
 
353
    #print 'saved spider.pdf'