[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 coord
16 import line_style
17 import legend
18 import axis
19 import pychart_util
20 import chart_object
21 import fill_style
22 import canvas
23 import area_doc
24 import linear_coord
25 import category_coord
26 import theme
27 from pychart_types import *
28 from types import *
29
30 _dummy_legend = legend.T()
31
32 def range_doc(t):
33     u = t.upper()
34     
35     return """Specifies the range of %s values that are displayed in the
36     chart. IF the value is None, both the values are computed
37     automatically from the samples.  Otherwise, the value must be a
38     tuple of format (MIN, MAX). MIN and MAX must be either None or a
39     number. If None, the value is computed automatically from the
40     samples. For example, if %s_range = (None,5), then the minimum %s
41     value is computed automatically, but the maximum %s value is fixed
42     at 5.""" % (u, t, u, u)
43     
44 _keys = {
45     "loc" : (CoordType, (0,0),
46              """The location of the bottom-left corner of the chart.
47 @cindex chart location
48 @cindex location, chart
49 """),
50     "size" : (CoordType, (120,110),
51               """The size of the chart-drawing area, excluding axis labels,
52               legends, tick marks, etc.
53 @cindex chart size
54 @cindex size, chart              
55               """),
56     "bg_style": (fill_style.T, None, "Background fill-pattern."),
57     "border_line_style": (line_style.T, None, "Line style of the outer frame of the chart."),
58     "x_coord":
59     (coord.T, linear_coord.T(),
60      """Set the X coordinate system.""",
61      """A linear coordinate system."""),
62     "y_coord": (coord.T, linear_coord.T(),
63                 "Set the Y coordinate system.",
64                 """A linear coordinate system."""),
65     "x_range": (CoordType, None, range_doc("x")),
66     "y_range": (CoordType, None, range_doc("y")),
67     "x_axis": (axis.X, None, "The X axis. <<axis>>."),
68     "x_axis2": (axis.X, None, """The second X axis. This axis should be non-None either when you want to display plots with two distinct domains or when
69     you just want to display two axes at the top and bottom of the chart.
70     <<axis>>"""),
71     "y_axis": (axis.Y, None, "The Y axis. <<axis>>."),
72     "y_axis2": (axis.Y, None,
73                 """The second Y axis. This axis should be non-None either when you want to display plots with two distinct ranges or when
74                 you just want to display two axes at the left and right of the chart. <<axis>>"""),
75     "x_grid_style" : (line_style.T, None,
76                       """The style of horizontal grid lines.
77 @cindex grid lines"""),
78     "y_grid_style" : (line_style.T, line_style.gray70_dash3,
79                       "The style of vertical grid lines."),
80     "x_grid_interval": (IntervalType, None,
81                         """The horizontal grid-line interval.
82                         A numeric value
83                         specifies the interval at which
84                         lines are drawn. If value is a function, it
85                         takes two arguments, (MIN, MAX), that tells
86                         the minimum and maximum values found in the
87                         sample data. The function should return a list
88                         of values at which lines are drawn."""),
89     "y_grid_interval": (IntervalType, None,
90                         "The vertical grid-line interval. See also x_grid_interval"),
91     "x_grid_over_plot": (IntType, False,
92                       "If True, grid lines are drawn over plots. Otherwise, plots are drawn over grid lines."),
93     "y_grid_over_plot": (IntType, False, "See x_grid_over_plot."),
94     "plots": (ListType, pychart_util.new_list,
95                """Used only internally by pychart."""),
96     "legend": (legend.T, _dummy_legend, "The legend of the chart.",
97                """a legend is by default displayed
98                in the right-center of the chart."""),
99     }
100
101
102 class T(chart_object.T):
103     keys = _keys
104     __doc__ = area_doc.doc
105 ##AUTOMATICALLY GENERATED
106
107 ##END AUTOMATICALLY GENERATED
108     def x_pos(self, xval):
109         "Return the x position (on the canvas) corresponding to XVAL."
110         off = self.x_coord.get_canvas_pos(self.size[0], xval,
111                                           self.x_range[0], self.x_range[1])
112         return self.loc[0] + off
113     
114     def y_pos(self, yval):
115         "Return the y position (on the canvas) corresponding to YVAL."
116         off = self.y_coord.get_canvas_pos(self.size[1], yval,
117                                           self.y_range[0], self.y_range[1])
118         return self.loc[1] + off
119
120     def x_tic_points(self, interval):
121         "Return the list of X values for which tick marks and grid lines are drawn."
122         if type(interval) == FunctionType:
123             return apply(interval, self.x_range)
124
125         return self.x_coord.get_tics(self.x_range[0], self.x_range[1], interval)
126     def y_tic_points(self, interval):
127         "Return the list of Y values for which tick marks and grid lines are drawn."
128         if type(interval) == FunctionType:
129             return apply(interval, self.y_range)
130
131         return self.y_coord.get_tics(self.y_range[0], self.y_range[1], interval)
132     def __draw_x_grid_and_axis(self, can):
133         if self.x_grid_style:
134             for i in self.x_tic_points(self.x_grid_interval):
135                 x = self.x_pos(i)
136                 if x > self.loc[0]:
137                     can.line(self.x_grid_style,
138                              x, self.loc[1], x, self.loc[1]+self.size[1])
139         if self.x_axis:
140             self.x_axis.draw(self, can)
141         if self.x_axis2:
142             self.x_axis2.draw(self, can)
143     def __draw_y_grid_and_axis(self, can):
144         if self.y_grid_style:
145             for i in self.y_tic_points(self.y_grid_interval):
146                 y = self.y_pos(i)
147                 if y > self.loc[1]:
148                     can.line(self.y_grid_style,
149                              self.loc[0], y,
150                              self.loc[0]+self.size[0], y)
151         if self.y_axis:
152             self.y_axis.draw(self, can)
153         if self.y_axis2:
154             self.y_axis2.draw(self, can)
155
156     def __get_data_range(self, r, which, coord, interval):
157         if isinstance(coord, category_coord.T):
158             # This info is unused for the category coord type.
159             # So I just return a random value.
160             return ((0,0), 1)
161
162         r = r or (None, None)
163         
164         if len(self.plots) == 0:
165             raise ValueError, "No chart to draw, and no data range specified.\n";
166         dmin, dmax = 999999, -999999
167  
168         for plot in self.plots:
169             this_min, this_max = plot.get_data_range(which)
170             dmin = min(this_min, dmin)
171             dmax = max(this_max, dmax)
172
173         if interval and type(interval) == FunctionType:
174             tics = apply(interval, (dmin, dmax))
175             dmin = tics[0]
176             dmax = tics[len(tics)-1]
177         else:
178             dmin, dmax, interval = coord.get_min_max(dmin, dmax, interval)
179
180         if r[0] != None:
181             dmin = r[0]
182         if r[1] != None:
183             dmax = r[1]
184         return ((dmin, dmax), interval)
185     def draw(self, can = None):
186         "Draw the charts."
187
188         if can == None:
189             can = canvas.default_canvas()
190             
191         self.type_check()
192         for plot in self.plots:
193             plot.check_integrity()
194             
195         self.x_range, self.x_grid_interval = \
196                       self.__get_data_range(self.x_range, 'X',
197                                             self.x_coord,
198                                             self.x_grid_interval)
199             
200         self.y_range, self.y_grid_interval = \
201                       self.__get_data_range(self.y_range, 'Y',
202                                             self.y_coord,
203                                             self.y_grid_interval)
204         
205         can.rectangle(self.border_line_style, self.bg_style,
206                       self.loc[0], self.loc[1],
207                       self.loc[0] + self.size[0], self.loc[1] + self.size[1])
208
209         if not self.x_grid_over_plot:
210             self.__draw_x_grid_and_axis(can)
211
212         if not self.y_grid_over_plot:
213             self.__draw_y_grid_and_axis(can)
214
215         clipbox = theme.adjust_bounding_box([self.loc[0], self.loc[1],
216                                              self.loc[0] + self.size[0],
217                                              self.loc[1] + self.size[1]])
218
219         can.clip(clipbox[0], clipbox[1],
220                  clipbox[2], clipbox[3])
221
222         for plot in self.plots:
223             plot.draw(self, can)
224             
225         can.endclip()
226             
227         if self.x_grid_over_plot:
228             self.__draw_x_grid_and_axis(can)
229         if self.y_grid_over_plot:
230             self.__draw_y_grid_and_axis(can)
231
232         if self.legend == _dummy_legend:
233             self.legend = legend.T()
234             
235         if self.legend:
236             legends = []
237             for plot in self.plots:
238                 entry = plot.get_legend_entry()
239                 if entry == None:
240                     pass
241                 elif type(entry) != ListType:
242                     legends.append(entry)
243                 else:
244                     for e in entry:
245                         legends.append(e)
246             self.legend.draw(self, legends, can)
247
248     def add_plot(self, *plots):
249         "Add PLOTS... to the area."
250         self.plots.extend(plots)