[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 bar_plot_doc
21 import theme
22 from types import *
23 from pychart_types import *
24
25 fill_styles = None
26
27 _keys = {
28     "direction" : (StringType, "vertical",
29                    """The direction the growth of the bars. The value is either 'horizontal'
30                    or 'vertical'."""),
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
34     "hcol"."""
35     ),
36     "data_label_offset": (CoordType, (0, 5),
37                           "The location of data labels relative to the sample point. See also attribute data_label_format."),
38     
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),
45     
46     "label": (StringType, "???", pychart_util.label_desc), 
47     "bcol" : (IntType, 0,
48               """Specifies the column from which base values (i.e., X values when attribute "direction" is "vertical", Y values otherwise) are extracted.
49 The
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.
53               """),
54     "hcol": (IntType, 1,
55              """The column from which the base and height of
56              bars are extracted. See the below example:
57               
58 @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)
61 @end example
62
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,
66               three bars are drawn,
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
72               "fill_styles".
73              """),
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:
90
91 @example
92     a = area.T(...)
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))
95     a.add_plot(p1, p2)
96     a.draw()
97 @end example
98
99     In this example, one group of bars will be drawn side-by-side at
100     position x=1.
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.
103     """),
104     "width": (UnitType, 5, """Width of each box. The unit is in points.
105 @cindex width, bar chart
106 @cindex size, bar chart
107 """),
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."),
112     }
113
114 class T(chart_object.T):
115     __doc__ = bar_plot_doc.doc
116     keys = _keys
117     def check_integrity(self):
118         self.type_check()
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"
124
125     def __get_data_range(self, col):
126         gmin = 99999999
127         gmax = -99999999
128         for item in self.data:
129             seq = item[col]
130             if seq[0] < gmin: gmin = seq[0]
131             max = 0
132             for v in seq:
133                 max += v
134             if max > gmax: gmax = max
135         return (gmin, gmax)
136     
137     def get_data_range(self, which):
138         if self.direction == 'vertical':
139             if which == 'X':
140                 return pychart_util.get_data_range(self.data, self.bcol)
141             else:
142                 return self.__get_data_range(self.hcol)
143         else:
144             assert self.direction == 'horizontal'
145             if which == 'Y':
146                 return pychart_util.get_data_range(self.data, self.bcol)
147             else:
148                 return self.__get_data_range(self.hcol)
149
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)
154     
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)
159             
160             if None in (xval, yvals): continue
161
162             ybot = 0
163             
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
167
168             cury = yvals[0]
169             n = 0
170             
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))
176                 cury += yval
177                 n += 1
178                 
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))
183             
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)
188
189             if None in (xvals, yval): continue
190
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
194
195             curx = xvals[0]
196             n = 0
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)
202                 curx = xval
203                 n += 1
204                 
205     def get_legend_entry(self):
206         if self.label:
207             return legend.Entry(line_style=self.line_styles[0],
208                                 fill_style=self.fill_styles[0],
209                                 label=self.label)
210         return None
211         
212     def draw(self, ar, can):
213         self.type_check()
214         can.clip(ar.loc[0], ar.loc[1],
215                  ar.loc[0] + ar.size[0], ar.loc[1] + ar.size[1])
216             
217         if self.direction == "vertical":
218             self.draw_vertical(ar, can)
219         else:
220             self.draw_horizontal(ar, can)
221
222         can.endclip()
223
224 def init():
225     global fill_styles
226     fill_styles = fill_style.standards.iterate()
227     
228 theme.add_reinitialization_hook(init)
229