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)