~openerp-groupes/openobject-server/6.0-fix-setup-windows

« back to all changes in this revision

Viewing changes to bin/pychart/bar_plot.py

  • Committer: pinky
  • Date: 2006-12-07 13:41:40 UTC
  • Revision ID: pinky-3f10ee12cea3c4c75cef44ab04ad33ef47432907
New trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# Copyright (C) 2000-2005 by Yasushi Saito (yasushi.saito@gmail.com)
 
3
 
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
 
7
# later version.
 
8
#
 
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
 
12
# for more details.
 
13
#
 
14
import line_style
 
15
import fill_style
 
16
import pychart_util
 
17
import chart_object
 
18
import legend
 
19
import error_bar
 
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, 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."),
 
34
    
 
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),
 
41
    
 
42
    "label": (StringType, "???", pychart_util.label_desc), 
 
43
    "bcol" : (IntType, 0,
 
44
              """Specifies the column from which base values (i.e., X values when attribute "direction" is "vertical", Y values otherwise) are extracted.
 
45
The
 
46
              combination of "data", "bcol", and "hcol" attributes defines
 
47
              the set of boxes drawn by this chart. See the
 
48
              below example:
 
49
              
 
50
@example
 
51
              d = [[5,10], [7,22], [8,25]]
 
52
              p = bar_plot.T(data = d, bcol = 1, hcol = 2)
 
53
@end example
 
54
 
 
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)".
 
60
              """),
 
61
    "hcol": (IntType, 1,
 
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."""),
 
73
                          
 
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:
 
78
 
 
79
@example
 
80
    a = area.T(...)
 
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))
 
83
    a.add_plot(p1, p2)
 
84
    a.draw()
 
85
@end example
 
86
 
 
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.
 
91
    """),
 
92
    "width": (UnitType, 5, """Width of each box. 
 
93
@cindex width, bar chart
 
94
@cindex size, bar chart
 
95
"""),
 
96
    "cluster_sep": (UnitType, 0, """The separation between
 
97
    clustered boxes."""),
 
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
 
102
                  error_bar != None.
 
103
                  """),
 
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."),
 
120
    }
 
121
 
 
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:
 
126
            return plot
 
127
    raise Exception, "The %dth bar plot in the cluster not found." % nth   
 
128
 
 
129
class T(chart_object.T):
 
130
    __doc__ = bar_plot_doc.doc
 
131
    keys = _keys
 
132
    def check_integrity(self):
 
133
        self.type_check()
 
134
        self.compute_abs_data()
 
135
    def compute_abs_data(self):
 
136
        if self._abs_data != None:
 
137
            return
 
138
        
 
139
        if self.stack_on == None:
 
140
            self._abs_data = self.data
 
141
        else:
 
142
            n = []
 
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]
 
147
                n.append(newpair)
 
148
            self._abs_data = n
 
149
            
 
150
##AUTOMATICALLY GENERATED
 
151
 
 
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"
 
158
 
 
159
    def get_data_range(self, which):
 
160
        if self.direction == 'vertical':
 
161
            if which == 'X':
 
162
                return pychart_util.get_data_range(self._abs_data, self.bcol)
 
163
            else:
 
164
                return pychart_util.get_data_range(self._abs_data, self.hcol)
 
165
        else:
 
166
            assert self.direction == 'horizontal'
 
167
            if which == 'Y':
 
168
                return pychart_util.get_data_range(self._abs_data, self.bcol)
 
169
            else:
 
170
                return pychart_util.get_data_range(self._abs_data, self.hcol)
 
171
 
 
172
    def get_bar_width(self, ar, nth):
 
173
        off = 0
 
174
        for i in range(0, nth):
 
175
            plot = find_bar_plot(ar, i)
 
176
            off += plot.width + plot.cluster_sep
 
177
        return off
 
178
    
 
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)
 
183
            
 
184
            if None in (xval, yval): continue
 
185
 
 
186
            ybot = 0
 
187
            if self.stack_on:
 
188
                ybot = self.stack_on.get_value(xval)
 
189
                yval += ybot
 
190
 
 
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])
 
194
 
 
195
            can.rectangle(self.line_style, self.fill_style,
 
196
                             thisX, ar.y_pos(ybot), thisX+self.width, 
 
197
                             ar.y_pos(yval))
 
198
 
 
199
            if self.error_bar:
 
200
                plus = pair[self.error_minus_col or self.error_plus_col]
 
201
                minus = pair[self.error_plus_col or self.error_minus_col]
 
202
                qplus = 0
 
203
                qminus = 0
 
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))
 
213
                    
 
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))
 
218
            
 
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)
 
223
 
 
224
            if None in (xval, yval): continue
 
225
 
 
226
            xbot = 0
 
227
            if self.stack_on:
 
228
                xbot = self.stack_on.get_value(yval)
 
229
                xval += xbot
 
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])
 
233
            
 
234
            can.rectangle(self.line_style, self.fill_style,
 
235
                          ar.x_pos(xbot), thisY,
 
236
                          ar.x_pos(xval), thisY+self.width)
 
237
 
 
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))
 
242
 
 
243
    def get_legend_entry(self):
 
244
        if self.label:
 
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),
 
247
                                label=self.label)
 
248
        return None
 
249
        
 
250
    def draw(self, ar, can):
 
251
        self.type_check()
 
252
        can.clip(ar.loc[0], ar.loc[1],
 
253
                ar.loc[0] + ar.size[0], ar.loc[1] + ar.size[1])
 
254
            
 
255
        if self.direction == "vertical":
 
256
            self.draw_vertical(ar, can)
 
257
        else:
 
258
            self.draw_horizontal(ar, can)
 
259
 
 
260
        can.endclip()
 
261
 
 
262
 
 
263
def init():
 
264
    global fill_styles
 
265
    fill_styles = fill_style.standards.iterate()
 
266
    
 
267
theme.add_reinitialization_hook(init)
 
268