~ubuntu-branches/ubuntu/lucid/pycha/lucid

« back to all changes in this revision

Viewing changes to pycha/bar.py

  • Committer: Bazaar Package Importer
  • Author(s): Vincent Bernat
  • Date: 2009-03-18 20:27:10 UTC
  • mfrom: (2.1.1 sid)
  • Revision ID: james.westby@ubuntu.com-20090318202710-dm6eicm7lxnjg3jp
Tags: 0.4.2-2
* Recompile with python-support from unstable. Closes: #520224.
* Bump Standards-Version to 3.8.1. No changes required.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright(c) 2007-2009 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
 
 
18
from pycha.chart import Chart, uniqueIndices
 
19
from pycha.color import hex2rgb
 
20
 
 
21
 
 
22
class BarChart(Chart):
 
23
 
 
24
    def __init__(self, surface=None, options={}):
 
25
        super(BarChart, self).__init__(surface, options)
 
26
        self.bars = []
 
27
        self.minxdelta = 0.0
 
28
        self.barWidthForSet = 0.0
 
29
        self.barMargin = 0.0
 
30
 
 
31
    def _updateXY(self):
 
32
        super(BarChart, self)._updateXY()
 
33
        # each dataset is centered around a line segment. that's why we
 
34
        # need n + 1 divisions on the x axis
 
35
        self.xscale = 1 / (self.xrange + 1.0)
 
36
 
 
37
    def _updateChart(self):
 
38
        """Evaluates measures for vertical bars"""
 
39
        stores = self._getDatasetsValues()
 
40
        uniqx = uniqueIndices(stores)
 
41
 
 
42
        barWidth = 0
 
43
        if len(uniqx) == 1:
 
44
            xdelta = 1.0
 
45
            barWidth = 1.0 * self.options.barWidthFillFraction
 
46
            self.barWidthForSet = barWidth / len(stores)
 
47
            self.barMargin = (1.0 - self.options.barWidthFillFraction) / 2
 
48
        else:
 
49
            xdelta = min([abs(uniqx[j] - uniqx[j-1])
 
50
                          for j in range(1, len(uniqx))])
 
51
            barWidth = xdelta * self.xscale * self.options.barWidthFillFraction
 
52
            self.barWidthForSet = barWidth / len(stores)
 
53
            self.barMargin = (xdelta * self.xscale
 
54
                              * (1.0 - self.options.barWidthFillFraction) / 2)
 
55
 
 
56
        self.minxdelta = xdelta
 
57
        self.bars = []
 
58
 
 
59
    def _renderChart(self, cx):
 
60
        """Renders a horizontal/vertical bar chart"""
 
61
 
 
62
        def drawBar(bar):
 
63
            stroke_width = self.options.stroke.width
 
64
            ux, uy = cx.device_to_user_distance(stroke_width, stroke_width)
 
65
            if ux < uy:
 
66
                ux = uy
 
67
            cx.set_line_width(ux)
 
68
 
 
69
            # gather bar proportions
 
70
            x = self.area.x + self.area.w * bar.x
 
71
            y = self.area.y + self.area.h * bar.y
 
72
            w = self.area.w * bar.w
 
73
            h = self.area.h * bar.h
 
74
 
 
75
            if w < 1 or h < 1:
 
76
                return # don't draw when the bar is too small
 
77
 
 
78
            if self.options.stroke.shadow:
 
79
                cx.set_source_rgba(0, 0, 0, 0.15)
 
80
                rectangle = self._getShadowRectangle(x, y, w, h)
 
81
                cx.rectangle(*rectangle)
 
82
                cx.fill()
 
83
 
 
84
            if self.options.shouldFill or (not self.options.stroke.hide):
 
85
                cx.rectangle(x, y, w, h)
 
86
 
 
87
                if self.options.shouldFill:
 
88
                    cx.set_source_rgb(*self.options.colorScheme[bar.name])
 
89
                    cx.fill_preserve()
 
90
 
 
91
                if not self.options.stroke.hide:
 
92
                    cx.set_source_rgb(*hex2rgb(self.options.stroke.color))
 
93
                    cx.stroke()
 
94
 
 
95
            # render yvals above/beside bars
 
96
            if self.options.yvals.show:
 
97
                cx.save()
 
98
                cx.set_font_size(self.options.yvals.fontSize)
 
99
                cx.set_source_rgb(*hex2rgb(self.options.yvals.fontColor))
 
100
 
 
101
                label = unicode(bar.yval)
 
102
                extents = cx.text_extents(label)
 
103
                labelW = extents[2]
 
104
                labelH = extents[3]
 
105
 
 
106
                self._renderYVal(cx, label, labelW, labelH, x, y, w, h)
 
107
 
 
108
                cx.restore()
 
109
 
 
110
        cx.save()
 
111
        for bar in self.bars:
 
112
            drawBar(bar)
 
113
        cx.restore()
 
114
 
 
115
    def _renderYVal(self, cx, label, width, height, x, y, w, h):
 
116
        raise NotImplementedError
 
117
 
 
118
 
 
119
class VerticalBarChart(BarChart):
 
120
 
 
121
    def _updateChart(self):
 
122
        """Evaluates measures for vertical bars"""
 
123
        super(VerticalBarChart, self)._updateChart()
 
124
        for i, (name, store) in enumerate(self.datasets):
 
125
            for item in store:
 
126
                xval, yval = item
 
127
                x = (((xval - self.minxval) * self.xscale)
 
128
                    + self.barMargin + (i * self.barWidthForSet))
 
129
                w = self.barWidthForSet
 
130
                h = abs(yval) * self.yscale
 
131
                if yval > 0:
 
132
                    y = (1.0 - h) - self.area.origin
 
133
                else:
 
134
                    y = 1 - self.area.origin
 
135
                rect = Rect(x, y, w, h, xval, yval, name)
 
136
 
 
137
                if (0.0 <= rect.x <= 1.0) and (0.0 <= rect.y <= 1.0):
 
138
                    self.bars.append(rect)
 
139
 
 
140
    def _updateTicks(self):
 
141
        """Evaluates bar ticks"""
 
142
        super(BarChart, self)._updateTicks()
 
143
        offset = (self.minxdelta * self.xscale) / 2
 
144
        self.xticks = [(tick[0] + offset, tick[1]) for tick in self.xticks]
 
145
 
 
146
    def _getShadowRectangle(self, x, y, w, h):
 
147
        return (x-2, y-2, w+4, h+2)
 
148
 
 
149
    def _renderYVal(self, cx, label, labelW, labelH, barX, barY, barW, barH):
 
150
        x = barX + (barW / 2.0) - (labelW / 2.0)
 
151
        if self.options.yvals.inside:
 
152
            y = barY + (1.5 * labelH)
 
153
        else:
 
154
            y = barY - 0.5 * labelH
 
155
 
 
156
        # if the label doesn't fit below the bar, put it above the bar
 
157
        if y > (barY + barH):
 
158
            y = barY - 0.5 * labelH
 
159
 
 
160
        cx.move_to(x, y)
 
161
        cx.show_text(label)
 
162
 
 
163
 
 
164
class HorizontalBarChart(BarChart):
 
165
 
 
166
    def _updateChart(self):
 
167
        """Evaluates measures for horizontal bars"""
 
168
        super(HorizontalBarChart, self)._updateChart()
 
169
 
 
170
        for i, (name, store) in enumerate(self.datasets):
 
171
            for item in store:
 
172
                xval, yval = item
 
173
                y = (((xval - self.minxval) * self.xscale)
 
174
                     + self.barMargin + (i * self.barWidthForSet))
 
175
                h = self.barWidthForSet
 
176
                w = abs(yval) * self.yscale
 
177
                if yval > 0:
 
178
                    x = self.area.origin
 
179
                else:
 
180
                    x = self.area.origin - w
 
181
                rect = Rect(x, y, w, h, xval, yval, name)
 
182
 
 
183
                if (0.0 <= rect.x <= 1.0) and (0.0 <= rect.y <= 1.0):
 
184
                    self.bars.append(rect)
 
185
 
 
186
    def _updateTicks(self):
 
187
        """Evaluates bar ticks"""
 
188
        super(BarChart, self)._updateTicks()
 
189
        offset = (self.minxdelta * self.xscale) / 2
 
190
        tmp = self.xticks
 
191
        self.xticks = [(1.0 - tick[0], tick[1]) for tick in self.yticks]
 
192
        self.yticks = [(tick[0] + offset, tick[1]) for tick in tmp]
 
193
 
 
194
    def _renderLines(self, cx):
 
195
        """Aux function for _renderBackground"""
 
196
        ticks = self.xticks
 
197
        for tick in ticks:
 
198
            self._renderLine(cx, tick, True)
 
199
 
 
200
    def _getShadowRectangle(self, x, y, w, h):
 
201
        return (x, y-2, w+2, h+4)
 
202
 
 
203
    def _renderXAxis(self, cx):
 
204
        """Draws the horizontal line representing the X axis"""
 
205
        cx.new_path()
 
206
        cx.move_to(self.area.x, self.area.y + self.area.h)
 
207
        cx.line_to(self.area.x + self.area.w, self.area.y + self.area.h)
 
208
        cx.close_path()
 
209
        cx.stroke()
 
210
 
 
211
    def _renderYAxis(self, cx):
 
212
        # draws the vertical line representing the Y axis
 
213
        cx.new_path()
 
214
        cx.move_to(self.area.x + self.area.origin * self.area.w,
 
215
                   self.area.y)
 
216
        cx.line_to(self.area.x + self.area.origin * self.area.w,
 
217
                   self.area.y + self.area.h)
 
218
        cx.close_path()
 
219
        cx.stroke()
 
220
 
 
221
    def _renderYVal(self, cx, label, labelW, labelH, barX, barY, barW, barH):
 
222
        y = barY + (barH / 2.0) + (labelH / 2.0)
 
223
        if self.options.yvals.inside:
 
224
            x = barX + barW - (1.2 * labelW)
 
225
        else:
 
226
            x = barX + barW + 0.2 * labelW
 
227
 
 
228
        # if the label doesn't fit to the left of the bar, put it to the right
 
229
        if x < barX:
 
230
            x = barX + barW + 0.2 * labelW
 
231
 
 
232
        cx.move_to(x, y)
 
233
        cx.show_text(label)
 
234
 
 
235
 
 
236
class Rect(object):
 
237
 
 
238
    def __init__(self, x, y, w, h, xval, yval, name):
 
239
        self.x, self.y, self.w, self.h = x, y, w, h
 
240
        self.xval, self.yval = xval, yval
 
241
        self.name = name
 
242
 
 
243
    def __str__(self):
 
244
        return ("<pycha.bar.Rect@(%.2f, %.2f) %.2fx%.2f (%.2f, %.2f) %s>"
 
245
                % (self.x, self.y, self.w, self.h, self.xval, self.yval,
 
246
                   self.name))