1
# Copyright(c) 2007-2009 by Lorenzo Gil Sanchez <lorenzo.gil.sanchez@gmail.com>
3
# This file is part of PyCha.
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.
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.
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/>.
18
from pycha.chart import Chart, uniqueIndices
19
from pycha.color import hex2rgb
22
class BarChart(Chart):
24
def __init__(self, surface=None, options={}):
25
super(BarChart, self).__init__(surface, options)
28
self.barWidthForSet = 0.0
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)
37
def _updateChart(self):
38
"""Evaluates measures for vertical bars"""
39
stores = self._getDatasetsValues()
40
uniqx = uniqueIndices(stores)
45
barWidth = 1.0 * self.options.barWidthFillFraction
46
self.barWidthForSet = barWidth / len(stores)
47
self.barMargin = (1.0 - self.options.barWidthFillFraction) / 2
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)
56
self.minxdelta = xdelta
59
def _renderChart(self, cx):
60
"""Renders a horizontal/vertical bar chart"""
63
stroke_width = self.options.stroke.width
64
ux, uy = cx.device_to_user_distance(stroke_width, stroke_width)
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
76
return # don't draw when the bar is too small
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)
84
if self.options.shouldFill or (not self.options.stroke.hide):
85
cx.rectangle(x, y, w, h)
87
if self.options.shouldFill:
88
cx.set_source_rgb(*self.options.colorScheme[bar.name])
91
if not self.options.stroke.hide:
92
cx.set_source_rgb(*hex2rgb(self.options.stroke.color))
95
# render yvals above/beside bars
96
if self.options.yvals.show:
98
cx.set_font_size(self.options.yvals.fontSize)
99
cx.set_source_rgb(*hex2rgb(self.options.yvals.fontColor))
101
label = unicode(bar.yval)
102
extents = cx.text_extents(label)
106
self._renderYVal(cx, label, labelW, labelH, x, y, w, h)
111
for bar in self.bars:
115
def _renderYVal(self, cx, label, width, height, x, y, w, h):
116
raise NotImplementedError
119
class VerticalBarChart(BarChart):
121
def _updateChart(self):
122
"""Evaluates measures for vertical bars"""
123
super(VerticalBarChart, self)._updateChart()
124
for i, (name, store) in enumerate(self.datasets):
127
x = (((xval - self.minxval) * self.xscale)
128
+ self.barMargin + (i * self.barWidthForSet))
129
w = self.barWidthForSet
130
h = abs(yval) * self.yscale
132
y = (1.0 - h) - self.area.origin
134
y = 1 - self.area.origin
135
rect = Rect(x, y, w, h, xval, yval, name)
137
if (0.0 <= rect.x <= 1.0) and (0.0 <= rect.y <= 1.0):
138
self.bars.append(rect)
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]
146
def _getShadowRectangle(self, x, y, w, h):
147
return (x-2, y-2, w+4, h+2)
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)
154
y = barY - 0.5 * labelH
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
164
class HorizontalBarChart(BarChart):
166
def _updateChart(self):
167
"""Evaluates measures for horizontal bars"""
168
super(HorizontalBarChart, self)._updateChart()
170
for i, (name, store) in enumerate(self.datasets):
173
y = (((xval - self.minxval) * self.xscale)
174
+ self.barMargin + (i * self.barWidthForSet))
175
h = self.barWidthForSet
176
w = abs(yval) * self.yscale
180
x = self.area.origin - w
181
rect = Rect(x, y, w, h, xval, yval, name)
183
if (0.0 <= rect.x <= 1.0) and (0.0 <= rect.y <= 1.0):
184
self.bars.append(rect)
186
def _updateTicks(self):
187
"""Evaluates bar ticks"""
188
super(BarChart, self)._updateTicks()
189
offset = (self.minxdelta * self.xscale) / 2
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]
194
def _renderLines(self, cx):
195
"""Aux function for _renderBackground"""
198
self._renderLine(cx, tick, True)
200
def _getShadowRectangle(self, x, y, w, h):
201
return (x, y-2, w+2, h+4)
203
def _renderXAxis(self, cx):
204
"""Draws the horizontal line representing the X axis"""
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)
211
def _renderYAxis(self, cx):
212
# draws the vertical line representing the Y axis
214
cx.move_to(self.area.x + self.area.origin * self.area.w,
216
cx.line_to(self.area.x + self.area.origin * self.area.w,
217
self.area.y + self.area.h)
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)
226
x = barX + barW + 0.2 * labelW
228
# if the label doesn't fit to the left of the bar, put it to the right
230
x = barX + barW + 0.2 * labelW
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
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,