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
27 from pychart_types import *
30 _dummy_legend = legend.T()
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)
45 "loc" : (CoordType, (0,0),
46 """The location of the bottom-left corner of the chart.
47 @cindex chart location
48 @cindex location, chart
50 "size" : (CoordType, (120,110),
51 """The size of the chart-drawing area, excluding axis labels,
52 legends, tick marks, etc.
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."),
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.
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.
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."""),
102 class T(chart_object.T):
104 __doc__ = area_doc.doc
105 ##AUTOMATICALLY GENERATED
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
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
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)
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)
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):
137 can.line(self.x_grid_style,
138 x, self.loc[1], x, self.loc[1]+self.size[1])
140 self.x_axis.draw(self, can)
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):
148 can.line(self.y_grid_style,
150 self.loc[0]+self.size[0], y)
152 self.y_axis.draw(self, can)
154 self.y_axis2.draw(self, can)
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.
162 r = r or (None, None)
164 if len(self.plots) == 0:
165 raise ValueError, "No chart to draw, and no data range specified.\n";
166 dmin, dmax = 999999, -999999
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)
173 if interval and type(interval) == FunctionType:
174 tics = apply(interval, (dmin, dmax))
176 dmax = tics[len(tics)-1]
178 dmin, dmax, interval = coord.get_min_max(dmin, dmax, interval)
184 return ((dmin, dmax), interval)
185 def draw(self, can = None):
189 can = canvas.default_canvas()
192 for plot in self.plots:
193 plot.check_integrity()
195 self.x_range, self.x_grid_interval = \
196 self.__get_data_range(self.x_range, 'X',
198 self.x_grid_interval)
200 self.y_range, self.y_grid_interval = \
201 self.__get_data_range(self.y_range, 'Y',
203 self.y_grid_interval)
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])
209 if not self.x_grid_over_plot:
210 self.__draw_x_grid_and_axis(can)
212 if not self.y_grid_over_plot:
213 self.__draw_y_grid_and_axis(can)
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]])
219 can.clip(clipbox[0], clipbox[1],
220 clipbox[2], clipbox[3])
222 for plot in self.plots:
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)
232 if self.legend == _dummy_legend:
233 self.legend = legend.T()
237 for plot in self.plots:
238 entry = plot.get_legend_entry()
241 elif type(entry) != ListType:
242 legends.append(entry)
246 self.legend.draw(self, legends, can)
248 def add_plot(self, *plots):
249 "Add PLOTS... to the area."
250 self.plots.extend(plots)