root/tags/0.4.1/src/pie.py

Revision 110, 6.8 kB (checked in by lgs, 4 years ago)

Draw the dataset name and not the x value in the axis of a pie chart

Line 
1# Copyright (c) 2007-2008 by Lorenzo Gil Sanchez <lorenzo.gil.sanchez@gmail.com>
2#
3# This file is part of PyCha.
4#
5# PyCha is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# PyCha is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public License
16# along with PyCha.  If not, see <http://www.gnu.org/licenses/>.
17
18import math
19
20import cairo
21
22from pycha.chart import Chart, Option
23from pycha.color import hex2rgb
24
25class PieChart(Chart):
26
27    def __init__(self, surface=None, options={}):
28        super(PieChart, self).__init__(surface, options)
29        self.slices = []
30        self.centerx = 0
31        self.centery = 0
32        self.radius = 0
33
34    def _updateChart(self):
35        """Evaluates measures for pie charts"""
36        self.centerx = self.area.x + self.area.w * 0.5
37        self.centery = self.area.y + self.area.h * 0.5
38        self.radius = min(self.area.w * self.options.pieRadius,
39                          self.area.h * self.options.pieRadius)
40
41        slices = [dict(name=key,
42                       value=(i, value[0][1]))
43                  for i, (key, value) in enumerate(self.datasets)]
44
45        s = float(sum([slice['value'][1] for slice in slices]))
46
47        fraction = angle = 0.0
48
49        self.slices = []
50        for slice in slices:
51            angle += fraction
52            if slice['value'][1] > 0:
53                fraction = slice['value'][1] / s
54                self.slices.append(Slice(slice['name'], fraction,
55                                         slice['value'][0], slice['value'][1],
56                                         angle))
57
58    def _updateTicks(self):
59        """Evaluates pie ticks"""
60        self.xticks = []
61        if self.options.axis.x.ticks:
62            lookup = dict([(slice.name, slice) for slice in self.slices])
63            for tick in self.options.axis.x.ticks:
64                if not isinstance(tick, Option):
65                    tick = Option(tick)
66                slice = lookup[tick.v]
67                label = tick.label or str(tick.v)
68                if slice:
69                    label += ' (%.1f%%)' % (slice.fraction * 100)
70                    self.xticks.append((tick.v, label))
71        else:
72            for slice in self.slices:
73                label = '%s (%.1f%%)' % (slice.name, slice.fraction * 100)
74                self.xticks.append((slice.name, label))
75
76    def _renderBackground(self, cx):
77        """Renders the background of the chart"""
78        if self.options.background.hide:
79            return
80
81        cx.save()
82
83        if self.options.background.baseColor:
84            cx.set_source_rgb(*hex2rgb(self.options.background.baseColor))
85            x, y, w, h = 0, 0, self.area.w, self.area.h
86            w += self.options.padding.left + self.options.padding.right
87            h += self.options.padding.top + self.options.padding.bottom
88            cx.rectangle(x, y, w, h)
89            cx.fill()
90
91        cx.restore()
92
93    def _renderChart(self, cx):
94        """Renders a pie chart"""
95        cx.set_line_join(cairo.LINE_JOIN_ROUND)
96
97        if self.options.stroke.shadow:
98            cx.save()
99            cx.set_source_rgba(0, 0, 0, 0.15)
100
101            cx.new_path()
102            cx.move_to(self.centerx, self.centery)
103            cx.arc(self.centerx + 1, self.centery + 2, self.radius + 1, 0,
104                   math.pi * 2)
105            cx.line_to(self.centerx, self.centery)
106            cx.close_path()
107            cx.fill()
108            cx.restore()
109
110        cx.save()
111        for slice in self.slices:
112            if slice.isBigEnough():
113                cx.set_source_rgb(*self.options.colorScheme[slice.name])
114                if self.options.shouldFill:
115                    slice.draw(cx, self.centerx, self.centery, self.radius)
116                    cx.fill()
117
118                if not self.options.stroke.hide:
119                    slice.draw(cx, self.centerx, self.centery, self.radius)
120                    cx.set_line_width(self.options.stroke.width)
121                    cx.set_source_rgb(*hex2rgb(self.options.stroke.color))
122                    cx.stroke()
123
124        cx.restore()
125
126    def _renderAxis(self, cx):
127        """Renders the axis for pie charts"""
128        if self.options.axis.x.hide or not self.xticks:
129            return
130
131        self.xlabels = []
132        lookup = dict([(slice.name, slice) for slice in self.slices])
133
134        cx.set_source_rgb(*hex2rgb(self.options.axis.labelColor))
135
136        for tick in self.xticks:
137            slice = lookup[tick[0]]
138
139            normalisedAngle = slice.getNormalisedAngle()
140
141            labelx = self.centerx + math.sin(normalisedAngle) * (self.radius + 10)
142            labely = self.centery - math.cos(normalisedAngle) * (self.radius + 10)
143
144            label = tick[1]
145            extents = cx.text_extents(label)
146            labelWidth = extents[2]
147            labelHeight = extents[3]
148            x = y = 0
149
150            if normalisedAngle <= math.pi * 0.5:
151                x = labelx
152                y = labely - labelHeight
153            elif math.pi * 0.5 < normalisedAngle <= math.pi:
154                x = labelx
155                y = labely
156            elif math.pi < normalisedAngle <= math.pi * 1.5:
157                x = labelx - labelWidth
158                y = labely
159            else:
160                x = labelx - labelWidth
161                y = labely - labelHeight
162
163            # draw label with text tick[1]
164            cx.move_to(x, y)
165            cx.show_text(label)
166            self.xlabels.append(label)
167
168
169class Slice(object):
170    def __init__(self, name, fraction, xval, yval, angle):
171        self.name = name
172        self.fraction = fraction
173        self.xval = xval
174        self.yval = yval
175        self.startAngle = 2 * angle * math.pi
176        self.endAngle = 2 * (angle + fraction) * math.pi
177
178    def __str__(self):
179        return ("<pycha.pie.Slice from %.2f to %.2f (%.2f%%)>" %
180                (self.startAngle, self.endAngle, self.fraction))
181
182    def isBigEnough(self):
183        return abs(self.startAngle - self.endAngle) > 0.001
184
185    def draw(self, cx, centerx, centery, radius):
186        cx.new_path()
187        cx.move_to(centerx, centery)
188        cx.arc(centerx, centery, radius,
189               self.startAngle - math.pi/2,
190               self.endAngle - math.pi/2)
191        cx.line_to(centerx, centery)
192        cx.close_path()
193
194    def getNormalisedAngle(self):
195        normalisedAngle = (self.startAngle + self.endAngle) / 2
196
197        if normalisedAngle > math.pi * 2:
198            normalisedAngle -= math.pi * 2
199        elif normalisedAngle < 0:
200            normalisedAngle += math.pi * 2
201
202        return normalisedAngle
Note: See TracBrowser for help on using the browser.