[MERGE] Remove the embedded pychart library, and use the online version
[odoo/odoo.git] /
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2000-2005 by Yasushi Saito (yasushi.saito@gmail.com)
4
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
8 # later version.
9 #
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
13 # for more details.
14 #
15 import line_style
16 import fill_style
17 import pychart_util
18 import chart_object
19 import legend
20 import error_bar
21 import bar_plot_doc
22 import theme
23 from types import *
24 from pychart_types import *
25
26 fill_styles = None
27
28 _keys = {
29     "direction" : (StringType, "vertical",
30                    """The direction the growth of the bars. The value is either 'horizontal'
31                    or 'vertical'."""),
32     "data" : (AnyType, None, pychart_util.data_desc),
33     "data_label_offset": (CoordType, (0, 5),
34                           "The location of data labels relative to the sample point. See also attribute data_label_format."),
35     
36     "data_label_format": (FormatType, None, """The
37                           format string for the label displayed besides each
38                           bar.  It can be a `printf' style format
39                           string, or a two-parameter function that
40                           takes (x,y) values and returns a string. """
41                           + pychart_util.string_desc),
42     
43     "label": (StringType, "???", pychart_util.label_desc), 
44     "bcol" : (IntType, 0,
45               """Specifies the column from which base values (i.e., X values when attribute "direction" is "vertical", Y values otherwise) are extracted.
46 The
47               combination of "data", "bcol", and "hcol" attributes defines
48               the set of boxes drawn by this chart. See the
49               below example:
50               
51 @example
52               d = [[5,10], [7,22], [8,25]]
53               p = bar_plot.T(data = d, bcol = 1, hcol = 2)
54 @end example
55
56               Here, three bars will be drawn. The X values of the bars
57               will be 5, 7, and 8. The Y values of the bars will be
58               10, 22, and 25, respectively. (In practice, because
59               the values of bcol and hcol defaults to 1 and 2, you can
60               write the above example just as "p = bar_plot.T(data = d)".
61               """),
62     "hcol": (IntType, 1,
63              """The column from which the height of each bar is extracted.
64              See also the description of the 'bcol' attribute."""),
65     "line_style": (line_style.T, line_style.default,
66                    "The style of the outer frame of each box."),
67     "fill_style": (fill_style.T, lambda: fill_styles.next(),
68                    "Defines the fill style of each box.",
69                    "The style is picked from standard styles round-robin."),
70     "legend_line_style": (line_style.T, None,
71                           """The line style used to draw a legend entry. Usually, the value is None, meaning that the value of "line_style" attribute is used."""),
72     "legend_fill_style": (fill_style.T, None,
73                    """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                           
75     "cluster": (TupleType, (0, 1), """This attribute is used to
76     cluster multiple bar plots side by side in a single chart.
77     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
78     (where N is the 2nd value of this attribute) places this chart the rightmost. Consider the below example:
79
80 @example
81     a = area.T(...)
82     p1 = bar_plot.T(data = [[1,20][2,30]], cluster=(0,2))
83     p2 = bar_plot.T(data = [[1,25],[2,10]], cluster=(1,2))
84     a.add_plot(p1, p2)
85     a.draw()
86 @end example
87
88     In this example, one group of bars will be drawn side-by-side at
89     position x=1, one with height 20, the other with height 25. The
90     other two bars will be drawn side by side at position x=2, one
91     with height 30 and the other with height 10.
92     """),
93     "width": (UnitType, 5, """Width of each box. 
94 @cindex width, bar chart
95 @cindex size, bar chart
96 """),
97     "cluster_sep": (UnitType, 0, """The separation between
98     clustered boxes."""),
99     "stack_on": (AnyType, None,
100                  "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."),
101     "error_minus_col": (IntType, -1,
102                   """Specifies the column from which the depth of the errorbar is extracted.  This attribute is meaningful only when
103                   error_bar != None.
104                   """),
105     "qerror_minus_col":  (IntType, -1,
106                   """The depth of the "quartile" errorbar is extracted from 
107                   this column in data. This attribute is meaningful only
108                   when error_bar != None. """),
109     "error_plus_col": (IntType, -1,
110                   """The depth of the errorbar is extracted from 
111                   this column in data. This attribute is meaningful only
112                   when error_bar != None."""),
113     "qerror_plus_col":  (IntType, -1, 
114                   """The depth of the "quartile" errorbar is extracted from 
115                   this column in data. This attribute is meaningful only
116                   when error_bar != None."""),
117     "error_bar": (error_bar.T, None,
118                   "Specifies the style of the error bar. <<error_bar>>"),
119     "_abs_data" : (ListType, None,
120                    "Used only internally."),
121     }
122
123 def find_bar_plot(ar, nth):
124     "Find the NTH barplot of the cluster in area AR."
125     for plot in ar.plots:
126         if isinstance(plot, T) and plot.cluster[0] == nth:
127             return plot
128     raise Exception, "The %dth bar plot in the cluster not found." % nth   
129
130 class T(chart_object.T):
131     __doc__ = bar_plot_doc.doc
132     keys = _keys
133     def check_integrity(self):
134         self.type_check()
135         self.compute_abs_data()
136     def compute_abs_data(self):
137         if self._abs_data != None:
138             return
139         
140         if self.stack_on == None:
141             self._abs_data = self.data
142         else:
143             n = []
144             for pair in self.data:
145                 self.stack_on.compute_abs_data()
146                 newpair = list(pair[:])
147                 newpair[self.hcol] = self.stack_on.get_value(newpair[self.bcol]) + pair[self.hcol]
148                 n.append(newpair)
149             self._abs_data = n
150             
151 ##AUTOMATICALLY GENERATED
152
153 ##END AUTOMATICALLY GENERATED
154     def get_value(self, bval):
155         for pair in self._abs_data:
156             if pair[self.bcol] == bval:
157                 return pair[self.hcol]
158         raise ValueError, str(bval) + ": can't find the xval"
159
160     def get_data_range(self, which):
161         if self.direction == 'vertical':
162             if which == 'X':
163                 return pychart_util.get_data_range(self._abs_data, self.bcol)
164             else:
165                 return pychart_util.get_data_range(self._abs_data, self.hcol)
166         else:
167             assert self.direction == 'horizontal'
168             if which == 'Y':
169                 return pychart_util.get_data_range(self._abs_data, self.bcol)
170             else:
171                 return pychart_util.get_data_range(self._abs_data, self.hcol)
172
173     def get_bar_width(self, ar, nth):
174         off = 0
175         for i in range(0, nth):
176             plot = find_bar_plot(ar, i)
177             off += plot.width + plot.cluster_sep
178         return off
179     
180     def draw_vertical(self, ar, can):
181         for pair in self.data:
182             xval = pair[self.bcol]
183             yval = pychart_util.get_sample_val(pair, self.hcol)
184             
185             if None in (xval, yval): continue
186
187             ybot = 0
188             if self.stack_on:
189                 ybot = self.stack_on.get_value(xval)
190                 yval += ybot
191
192             totalWidth = self.get_bar_width(ar, self.cluster[1])
193             firstX = ar.x_pos(xval) - totalWidth/2.0
194             thisX = firstX + self.get_bar_width(ar, self.cluster[0])
195
196             can.rectangle(self.line_style, self.fill_style,
197                              thisX, ar.y_pos(ybot), thisX+self.width, 
198                              ar.y_pos(yval))
199
200             if self.error_bar:
201                 plus = pair[self.error_minus_col or self.error_plus_col]
202                 minus = pair[self.error_plus_col or self.error_minus_col]
203                 qplus = 0
204                 qminus = 0
205                 if self.qerror_minus_col or self.qerror_plus_col:
206                     qplus = pair[self.qerror_minus_col or self.qerror_plus_col]
207                     qminus = pair[self.qerror_plus_col or self.qerror_minus_col]
208                 if None not in (plus, minus, qplus, qminus): 
209                     self.error_bar.draw(can, (thisX+self.width/2.0, ar.y_pos(yval)),
210                                         ar.y_pos(yval - minus),
211                                         ar.y_pos(yval + plus),
212                                         ar.y_pos(yval - qminus),
213                                         ar.y_pos(yval + qplus))
214                     
215             if self.data_label_format:
216                 can.show(thisX + self.width/2.0 + self.data_label_offset[0],
217                             ar.y_pos(yval) + self.data_label_offset[1],
218                             "/hC" + pychart_util.apply_format(self.data_label_format, (pair[self.bcol], pair[self.hcol]), 1))
219             
220     def draw_horizontal(self, ar, can):
221         for pair in self.data:
222             yval = pair[self.bcol]
223             xval = pychart_util.get_sample_val(pair, self.hcol)
224
225             if None in (xval, yval): continue
226
227             xbot = 0
228             if self.stack_on:
229                 xbot = self.stack_on.get_value(yval)
230                 xval += xbot
231             totalWidth = self.get_bar_width(ar, self.cluster[1])
232             firstY = ar.y_pos(yval) - totalWidth/2.0
233             thisY = firstY + self.get_bar_width(ar, self.cluster[0])
234             
235             can.rectangle(self.line_style, self.fill_style,
236                           ar.x_pos(xbot), thisY,
237                           ar.x_pos(xval), thisY+self.width)
238
239             if self.data_label_format:
240                 can.show(ar.x_pos(xval) + self.data_label_offset[0],
241                             thisY + self.width/2.0 + self.data_label_offset[1],
242                             "/vM/hL" + pychart_util.apply_format(self.data_label_format, (pair[self.bcol], pair[self.hcol]), 1))
243
244     def get_legend_entry(self):
245         if self.label:
246             return legend.Entry(line_style=(self.legend_line_style or self.line_style),
247                                 fill_style=(self.legend_fill_style or self.fill_style),
248                                 label=self.label)
249         return None
250         
251     def draw(self, ar, can):
252         self.type_check()
253         can.clip(ar.loc[0], ar.loc[1],
254                 ar.loc[0] + ar.size[0], ar.loc[1] + ar.size[1])
255             
256         if self.direction == "vertical":
257             self.draw_vertical(ar, can)
258         else:
259             self.draw_horizontal(ar, can)
260
261         can.endclip()
262
263
264 def init():
265     global fill_styles
266     fill_styles = fill_style.standards.iterate()
267     
268 theme.add_reinitialization_hook(init)
269