2
# Copyright (C) 2000-2005 by Yasushi Saito (yasushi.saito@gmail.com)
4
# Jockey is free software; you can redistribute it and/or modify it
5
# under the terms of the GNU General Public License as published by the
6
# Free Software Foundation; either version 2, or (at your option) any
9
# Jockey is distributed in the hope that it will be useful, but WITHOUT
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23
from pychart_types import *
28
"direction" : (StringType, "vertical",
29
"""The direction the growth of the bars. The value is either 'horizontal'
31
"data" : (AnyType, None, pychart_util.data_desc),
32
"data_label_offset": (CoordType, (0, 5),
33
"The location of data labels relative to the sample point. See also attribute data_label_format."),
35
"data_label_format": (FormatType, None, """The
36
format string for the label displayed besides each
37
bar. It can be a `printf' style format
38
string, or a two-parameter function that
39
takes (x,y) values and returns a string. """
40
+ pychart_util.string_desc),
42
"label": (StringType, "???", pychart_util.label_desc),
44
"""Specifies the column from which base values (i.e., X values when attribute "direction" is "vertical", Y values otherwise) are extracted.
46
combination of "data", "bcol", and "hcol" attributes defines
47
the set of boxes drawn by this chart. See the
51
d = [[5,10], [7,22], [8,25]]
52
p = bar_plot.T(data = d, bcol = 1, hcol = 2)
55
Here, three bars will be drawn. The X values of the bars
56
will be 5, 7, and 8. The Y values of the bars will be
57
10, 22, and 25, respectively. (In practice, because
58
the values of bcol and hcol defaults to 1 and 2, you can
59
write the above example just as "p = bar_plot.T(data = d)".
62
"""The column from which the height of each bar is extracted.
63
See also the description of the 'bcol' attribute."""),
64
"line_style": (line_style.T, line_style.default,
65
"The style of the outer frame of each box."),
66
"fill_style": (fill_style.T, lambda: fill_styles.next(),
67
"Defines the fill style of each box.",
68
"The style is picked from standard styles round-robin."),
69
"legend_line_style": (line_style.T, None,
70
"""The line style used to draw a legend entry. Usually, the value is None, meaning that the value of "line_style" attribute is used."""),
71
"legend_fill_style": (fill_style.T, None,
72
"""The fill style used to draw a legend entry. Usually, the value is None, meaning that the value of "fill_style" attribute is used."""),
74
"cluster": (TupleType, (0, 1), """This attribute is used to
75
cluster multiple bar plots side by side in a single chart.
76
The value should be a tuple of two integers. The second value should be equal to the total number of bar plots in the chart. The first value should be the relative position of this chart; 0 places this chart the leftmost, and N-1
77
(where N is the 2nd value of this attribute) places this chart the rightmost. Consider the below example:
81
p1 = bar_plot.T(data = [[1,20][2,30]], cluster=(0,2))
82
p2 = bar_plot.T(data = [[1,25],[2,10]], cluster=(1,2))
87
In this example, one group of bars will be drawn side-by-side at
88
position x=1, one with height 20, the other with height 25. The
89
other two bars will be drawn side by side at position x=2, one
90
with height 30 and the other with height 10.
92
"width": (UnitType, 5, """Width of each box.
93
@cindex width, bar chart
94
@cindex size, bar chart
96
"cluster_sep": (UnitType, 0, """The separation between
98
"stack_on": (AnyType, None,
99
"The value must be either None or bar_plot.T. If not None, bars of this plot are stacked on top of another bar plot."),
100
"error_minus_col": (IntType, -1,
101
"""Specifies the column from which the depth of the errorbar is extracted. This attribute is meaningful only when
104
"qerror_minus_col": (IntType, -1,
105
"""The depth of the "quartile" errorbar is extracted from
106
this column in data. This attribute is meaningful only
107
when error_bar != None. """),
108
"error_plus_col": (IntType, -1,
109
"""The depth of the errorbar is extracted from
110
this column in data. This attribute is meaningful only
111
when error_bar != None."""),
112
"qerror_plus_col": (IntType, -1,
113
"""The depth of the "quartile" errorbar is extracted from
114
this column in data. This attribute is meaningful only
115
when error_bar != None."""),
116
"error_bar": (error_bar.T, None,
117
"Specifies the style of the error bar. <<error_bar>>"),
118
"_abs_data" : (ListType, None,
119
"Used only internally."),
122
def find_bar_plot(ar, nth):
123
"Find the NTH barplot of the cluster in area AR."
124
for plot in ar.plots:
125
if isinstance(plot, T) and plot.cluster[0] == nth:
127
raise Exception, "The %dth bar plot in the cluster not found." % nth
129
class T(chart_object.T):
130
__doc__ = bar_plot_doc.doc
132
def check_integrity(self):
134
self.compute_abs_data()
135
def compute_abs_data(self):
136
if self._abs_data != None:
139
if self.stack_on == None:
140
self._abs_data = self.data
143
for pair in self.data:
144
self.stack_on.compute_abs_data()
145
newpair = list(pair[:])
146
newpair[self.hcol] = self.stack_on.get_value(newpair[self.bcol]) + pair[self.hcol]
150
##AUTOMATICALLY GENERATED
152
##END AUTOMATICALLY GENERATED
153
def get_value(self, bval):
154
for pair in self._abs_data:
155
if pair[self.bcol] == bval:
156
return pair[self.hcol]
157
raise ValueError, str(bval) + ": can't find the xval"
159
def get_data_range(self, which):
160
if self.direction == 'vertical':
162
return pychart_util.get_data_range(self._abs_data, self.bcol)
164
return pychart_util.get_data_range(self._abs_data, self.hcol)
166
assert self.direction == 'horizontal'
168
return pychart_util.get_data_range(self._abs_data, self.bcol)
170
return pychart_util.get_data_range(self._abs_data, self.hcol)
172
def get_bar_width(self, ar, nth):
174
for i in range(0, nth):
175
plot = find_bar_plot(ar, i)
176
off += plot.width + plot.cluster_sep
179
def draw_vertical(self, ar, can):
180
for pair in self.data:
181
xval = pair[self.bcol]
182
yval = pychart_util.get_sample_val(pair, self.hcol)
184
if None in (xval, yval): continue
188
ybot = self.stack_on.get_value(xval)
191
totalWidth = self.get_bar_width(ar, self.cluster[1])
192
firstX = ar.x_pos(xval) - totalWidth/2.0
193
thisX = firstX + self.get_bar_width(ar, self.cluster[0])
195
can.rectangle(self.line_style, self.fill_style,
196
thisX, ar.y_pos(ybot), thisX+self.width,
200
plus = pair[self.error_minus_col or self.error_plus_col]
201
minus = pair[self.error_plus_col or self.error_minus_col]
204
if self.qerror_minus_col or self.qerror_plus_col:
205
qplus = pair[self.qerror_minus_col or self.qerror_plus_col]
206
qminus = pair[self.qerror_plus_col or self.qerror_minus_col]
207
if None not in (plus, minus, qplus, qminus):
208
self.error_bar.draw(can, (thisX+self.width/2.0, ar.y_pos(yval)),
209
ar.y_pos(yval - minus),
210
ar.y_pos(yval + plus),
211
ar.y_pos(yval - qminus),
212
ar.y_pos(yval + qplus))
214
if self.data_label_format:
215
can.show(thisX + self.width/2.0 + self.data_label_offset[0],
216
ar.y_pos(yval) + self.data_label_offset[1],
217
"/hC" + pychart_util.apply_format(self.data_label_format, (pair[self.bcol], pair[self.hcol]), 1))
219
def draw_horizontal(self, ar, can):
220
for pair in self.data:
221
yval = pair[self.bcol]
222
xval = pychart_util.get_sample_val(pair, self.hcol)
224
if None in (xval, yval): continue
228
xbot = self.stack_on.get_value(yval)
230
totalWidth = self.get_bar_width(ar, self.cluster[1])
231
firstY = ar.y_pos(yval) - totalWidth/2.0
232
thisY = firstY + self.get_bar_width(ar, self.cluster[0])
234
can.rectangle(self.line_style, self.fill_style,
235
ar.x_pos(xbot), thisY,
236
ar.x_pos(xval), thisY+self.width)
238
if self.data_label_format:
239
can.show(ar.x_pos(xval) + self.data_label_offset[0],
240
thisY + self.width/2.0 + self.data_label_offset[1],
241
"/vM/hL" + pychart_util.apply_format(self.data_label_format, (pair[self.bcol], pair[self.hcol]), 1))
243
def get_legend_entry(self):
245
return legend.Entry(line_style=(self.legend_line_style or self.line_style),
246
fill_style=(self.legend_fill_style or self.fill_style),
250
def draw(self, ar, can):
252
can.clip(ar.loc[0], ar.loc[1],
253
ar.loc[0] + ar.size[0], ar.loc[1] + ar.size[1])
255
if self.direction == "vertical":
256
self.draw_vertical(ar, can)
258
self.draw_horizontal(ar, can)
265
fill_styles = fill_style.standards.iterate()
267
theme.add_reinitialization_hook(init)