1 # -*- coding: utf-8 -*-
3 # Copyright (C) 2000-2005 by Yasushi Saito (yasushi.saito@gmail.com)
5 # Jockey is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2, or (at your option) any
10 # Jockey is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # 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, """Specifes data points. Unlike other types
32 of charts, the "hcol"th column of the data must be a sequence of
33 numbers, not just a single number. See also the description of
36 "data_label_offset": (CoordType, (0, 5),
37 "The location of data labels relative to the sample point. See also attribute data_label_format."),
39 "data_label_format": (FormatType, None, """The
40 format string for the label displayed besides each
41 bar. It can be a `printf' style format
42 string, or a two-parameter function that
43 takes (x,y) values and returns a string. """
44 + pychart_util.string_desc),
46 "label": (StringType, "???", pychart_util.label_desc),
48 """Specifies the column from which base values (i.e., X values when attribute "direction" is "vertical", Y values otherwise) are extracted.
50 combination of "data", "bcol", and "hcol" attributes defines
51 the set of boxes drawn by this chart.
52 See also the descriptions of the 'bcol' and 'data' attributes.
55 """The column from which the base and height of
56 bars are extracted. See the below example:
59 d = [[5,[10,15,22]], [7,[22,23,5,10]], [8,[25,3]]]
60 p = interval_bar_plot.T(data = d, bcol = 0, hcol = 1)
63 Here, three sequence of bars will be drawn.
64 The X locations of the bars
65 will be 5, 7, and 8. For example, at location X=7,
67 one corresponding to Y values of 22 to 45 (=22+23),
68 and the second one for values 45 to 50, and the third one
69 for values 50 to 60. The line and fill styles of the bars
70 are picked in a round-robin fashion
71 from attributes "line_styles" and
74 "line_styles": (ListType, [line_style.default, None],
75 """The list of line styles for bars.
76 The style of each bar is chosen in a round-robin fashion, if the
77 number of elements in "line_styles" is smaller than
78 actual number of boxes."""),
79 "fill_styles": (ListType, [lambda: fill_styles.next(), None],
80 """List of fill styles for bars.
81 The style of each bar is chosen in a round-robin fashion, if the
82 number of elements in "line_styles" is smaller than
83 actual number of boxes.
84 If this attribute is omitted,
85 a style is picked from standard styles round-robin. <<fill_style>>."""),
86 "cluster": (TupleType, (0, 1), """This attribute is used to
87 cluster multiple bar plots side by side in a single chart.
88 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
89 (where N is the 2nd value of this attribute) places this chart the rightmost. Consider the below example:
93 p1 = interval_bar_plot.T(data = [[1, [20,10]][2,[30,5]]], cluster=(0,2))
94 p2 = interval_bar_plot.T(data = [[1,[25,11,2]],[2,[10,5,3]]], cluster=(1,2))
99 In this example, one group of bars will be drawn side-by-side at
101 Other two bars will be drawn side by side at position x=2.
102 See also the description of attribute "cluster" for bar_plot.T.
104 "width": (UnitType, 5, """Width of each box. The unit is in points.
105 @cindex width, bar chart
106 @cindex size, bar chart
108 "cluster_sep": (UnitType, 0, """The separation between
109 clustered boxes. The unit is points."""),
110 "stack_on": (AnyType, None,
111 "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."),
114 class T(chart_object.T):
115 __doc__ = bar_plot_doc.doc
117 def check_integrity(self):
119 def get_value(self, bval):
120 for pair in self.data:
121 if pair[self.bcol] == bval:
122 return pair[self.hcol]
123 raise ValueError, str(bval) + ": can't find the xval"
125 def __get_data_range(self, col):
128 for item in self.data:
130 if seq[0] < gmin: gmin = seq[0]
134 if max > gmax: gmax = max
137 def get_data_range(self, which):
138 if self.direction == 'vertical':
140 return pychart_util.get_data_range(self.data, self.bcol)
142 return self.__get_data_range(self.hcol)
144 assert self.direction == 'horizontal'
146 return pychart_util.get_data_range(self.data, self.bcol)
148 return self.__get_data_range(self.hcol)
150 def get_style(self, nth):
151 line_style = self.line_styles[nth % len(self.line_styles)]
152 fill_style = self.fill_styles[nth % len(self.fill_styles)]
153 return (line_style, fill_style)
155 def draw_vertical(self, ar, can):
156 for pair in self.data:
157 xval = pair[self.bcol]
158 yvals = pychart_util.get_sample_val(pair, self.hcol)
160 if None in (xval, yvals): continue
164 totalWidth = (self.width+self.cluster_sep) * self.cluster[1] - self.cluster_sep
165 firstX = ar.x_pos(xval) - totalWidth/2.0
166 thisX = firstX + (self.width+self.cluster_sep) * self.cluster[0] - self.cluster_sep
171 for yval in yvals[1:]:
172 (line_style, fill_style) = self.get_style(n)
173 can.rectangle(line_style, fill_style,
174 thisX, ar.y_pos(cury), thisX+self.width,
175 ar.y_pos(cury + yval))
179 if self.data_label_format:
180 can.show(thisX + self.width/2.0 + self.data_label_offset[0],
181 ar.y_pos(cury) + self.data_label_offset[1],
182 "/hC" + pychart_util.apply_format(self.data_label_format, (pair[self.bcol], pair[self.hcol]), 1))
184 def draw_horizontal(self, ar, can):
185 for pair in self.data:
186 yval = pair[self.bcol]
187 xvals = pychart_util.get_sample_val(pair, self.hcol)
189 if None in (xvals, yval): continue
191 totalWidth = (self.width+self.cluster_sep) * self.cluster[1] - self.cluster_sep
192 firstY = ar.y_pos(yval) - totalWidth/2.0
193 thisY = firstY + (self.width+self.cluster_sep) * self.cluster[0] - self.cluster_sep
197 for xval in xvals[1:]:
198 line_style, fill_style = self.get_style(n)
199 can.rectangle(line_style, fill_style,
200 ar.x_pos(curx), thisY,
201 ar.x_pos(xval), thisY+self.width)
205 def get_legend_entry(self):
207 return legend.Entry(line_style=self.line_styles[0],
208 fill_style=self.fill_styles[0],
212 def draw(self, ar, can):
214 can.clip(ar.loc[0], ar.loc[1],
215 ar.loc[0] + ar.size[0], ar.loc[1] + ar.size[1])
217 if self.direction == "vertical":
218 self.draw_vertical(ar, can)
220 self.draw_horizontal(ar, can)
226 fill_styles = fill_style.standards.iterate()
228 theme.add_reinitialization_hook(init)