Status: | Needs review |
---|---|
Proposed branch: | lp:~tswast/cairoplot/trunk |
Merge into: | lp:cairoplot |
Diff against target: |
3166 lines (+805/-560) 13 files modified
trunk/cairoplot/__init__.py (+371/-320) trunk/cairoplot/handlers/__init__.py (+10/-0) trunk/cairoplot/handlers/fixedsize.py (+30/-0) trunk/cairoplot/handlers/gtk.py (+39/-0) trunk/cairoplot/handlers/handler.py (+11/-0) trunk/cairoplot/handlers/pdf.py (+12/-0) trunk/cairoplot/handlers/png.py (+19/-0) trunk/cairoplot/handlers/ps.py (+12/-0) trunk/cairoplot/handlers/svg.py (+12/-0) trunk/cairoplot/handlers/vector.py (+17/-0) trunk/cairoplot/series.py (+239/-239) trunk/gtktests.py (+32/-0) trunk/seriestests.py (+1/-1) |
To merge this branch: | bzr merge lp:~tswast/cairoplot/trunk |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Rodrigo Moreira Araújo | Pending | ||
Review via email: mp+20838@code.launchpad.net |
Commit message
Description of the change
This is largely an architectural change. It seems to make some sense to me that the various ways of creating a Cairo canvas should be separate from the Plots which use those canvases. This is inspired by the Lua port of Cairo plot.
Also, this adds the ability to easily embed Cairo Plot inside a GTK application. This opens the door to being able to do interactive plots.
Finally, Bug #426559 (PDF output broken) is fixed by these changes. The PDF handler correctly creates a PDF Cairo surface.
I've run the tests included, and it seems to be 100% compatible.
- 43. By Tim Swast
-
Removed trailing white-space.
- 44. By Tim Swast
-
Improved plot labels, so we don't get a 20 char long representation for what is effectively 0.
- 45. By Tim Swast
-
Removed trailing white-space.
- 46. By Tim Swast
-
Hacked together "fix" for infinite loop caused by xmin==xmax.
- 47. By Tim Swast
-
Fixed bug with plotting multiple equations in FunctionPlot.
- 48. By Tim Swast
-
Added checks to allow plotting functions with axes outside valid bounds of equation.
- 49. By Tim Swast
-
Fixed nan check for python 2.5.
- 50. By Tim Swast
-
Added OverflowError detection to make plotting more robust.
Unmerged revisions
- 50. By Tim Swast
-
Added OverflowError detection to make plotting more robust.
- 49. By Tim Swast
-
Fixed nan check for python 2.5.
- 48. By Tim Swast
-
Added checks to allow plotting functions with axes outside valid bounds of equation.
- 47. By Tim Swast
-
Fixed bug with plotting multiple equations in FunctionPlot.
- 46. By Tim Swast
-
Hacked together "fix" for infinite loop caused by xmin==xmax.
- 45. By Tim Swast
-
Removed trailing white-space.
- 44. By Tim Swast
-
Improved plot labels, so we don't get a 20 char long representation for what is effectively 0.
- 43. By Tim Swast
-
Removed trailing white-space.
- 42. By Tim Swast
-
Added Handler for created plots in as a GTK widget.
- 41. By Tim Swast
-
Created "Handlers" for different file formats to move surface creation code out of Plot objects (inspired by LuaPlot).
Preview Diff
1 | === added directory 'trunk/cairoplot' |
2 | === renamed file 'trunk/cairoplot.py' => 'trunk/cairoplot/__init__.py' |
3 | --- trunk/cairoplot.py 2009-07-09 21:57:24 +0000 |
4 | +++ trunk/cairoplot/__init__.py 2010-04-07 05:06:25 +0000 |
5 | @@ -36,6 +36,8 @@ |
6 | import random |
7 | from series import Series, Group, Data |
8 | |
9 | +import cairoplot.handlers |
10 | + |
11 | HORZ = 0 |
12 | VERT = 1 |
13 | NORM = 2 |
14 | @@ -56,7 +58,7 @@ |
15 | def colors_from_theme( theme, series_length, mode = 'solid' ): |
16 | colors = [] |
17 | if theme not in THEMES.keys() : |
18 | - raise Exception, "Theme not defined" |
19 | + raise Exception, "Theme not defined" |
20 | color_steps = THEMES[theme] |
21 | n_colors = len(color_steps) |
22 | if series_length <= n_colors: |
23 | @@ -79,14 +81,14 @@ |
24 | (next_color[2] - color[2])/(iterations[index] + 1), |
25 | (next_color[3] - color[3])/(iterations[index] + 1)) |
26 | for i in range( iterations[index] ): |
27 | - colors.append((color[0] + color_step[0]*(i+1), |
28 | - color[1] + color_step[1]*(i+1), |
29 | + colors.append((color[0] + color_step[0]*(i+1), |
30 | + color[1] + color_step[1]*(i+1), |
31 | color[2] + color_step[2]*(i+1), |
32 | color[3] + color_step[3]*(i+1), |
33 | mode)) |
34 | colors.append(color_steps[-1] + tuple([mode])) |
35 | return colors |
36 | - |
37 | + |
38 | |
39 | def other_direction(direction): |
40 | "explicit is better than implicit" |
41 | @@ -98,7 +100,7 @@ |
42 | #Class definition |
43 | |
44 | class Plot(object): |
45 | - def __init__(self, |
46 | + def __init__(self, |
47 | surface=None, |
48 | data=None, |
49 | width=640, |
50 | @@ -113,7 +115,7 @@ |
51 | self.dimensions = {} |
52 | self.dimensions[HORZ] = width |
53 | self.dimensions[VERT] = height |
54 | - self.context = cairo.Context(self.surface) |
55 | + self.context = None |
56 | self.labels={} |
57 | self.labels[HORZ] = x_labels |
58 | self.labels[VERT] = y_labels |
59 | @@ -126,50 +128,52 @@ |
60 | self.line_width = 0.5 |
61 | self.label_color = (0.0, 0.0, 0.0) |
62 | self.grid_color = (0.8, 0.8, 0.8) |
63 | - |
64 | + |
65 | def create_surface(self, surface, width=None, height=None): |
66 | self.filename = None |
67 | if isinstance(surface, cairo.Surface): |
68 | - self.surface = surface |
69 | - return |
70 | - if not type(surface) in (str, unicode): |
71 | + self.handler = cairoplot.handlers.VectorHandler(surface, width, |
72 | + height) |
73 | + return |
74 | + if isinstance(surface, cairoplot.handlers.Handler): |
75 | + self.handler = surface |
76 | + return |
77 | + if not type(surface) in (str, unicode): |
78 | raise TypeError("Surface should be either a Cairo surface or a filename, not %s" % surface) |
79 | + |
80 | + # choose handler based on file extension (svg is default) |
81 | sufix = surface.rsplit(".")[-1].lower() |
82 | - self.filename = surface |
83 | + filename = surface |
84 | + handlerclass = cairoplot.handlers.SVGHandler |
85 | if sufix == "png": |
86 | - self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) |
87 | + handlerclass = cairoplot.handlers.PNGHandler |
88 | elif sufix == "ps": |
89 | - self.surface = cairo.PSSurface(surface, width, height) |
90 | + handlerclass = cairoplot.handlers.PSHandler |
91 | elif sufix == "pdf": |
92 | - self.surface = cairo.PSSurface(surface, width, height) |
93 | - else: |
94 | - if sufix != "svg": |
95 | - self.filename += ".svg" |
96 | - self.surface = cairo.SVGSurface(self.filename, width, height) |
97 | + handlerclass = cairoplot.handlers.PDFHandler |
98 | + elif sufix != "svg": |
99 | + filename += ".svg" |
100 | + self.handler = handlerclass(filename, width, height) |
101 | |
102 | def commit(self): |
103 | try: |
104 | - self.context.show_page() |
105 | - if self.filename and self.filename.endswith(".png"): |
106 | - self.surface.write_to_png(self.filename) |
107 | - else: |
108 | - self.surface.finish() |
109 | + self.handler.commit(self) |
110 | except cairo.Error: |
111 | pass |
112 | - |
113 | + |
114 | def load_series (self, data, x_labels=None, y_labels=None, series_colors=None): |
115 | self.series_labels = [] |
116 | self.series = None |
117 | - |
118 | + |
119 | #The pretty way |
120 | #if not isinstance(data, Series): |
121 | # # Not an instance of Series |
122 | # self.series = Series(data) |
123 | #else: |
124 | # self.series = data |
125 | - # |
126 | + # |
127 | #self.series_labels = self.series.get_names() |
128 | - |
129 | + |
130 | #TODO: Remove on next version |
131 | # The ugly way, keeping retrocompatibility... |
132 | if callable(data) or type(data) is list and callable(data[0]): # Lambda or List of lambdas |
133 | @@ -181,18 +185,18 @@ |
134 | else: # Anything else |
135 | self.series = Series(data) |
136 | self.series_labels = self.series.get_names() |
137 | - |
138 | + |
139 | #TODO: allow user passed series_widths |
140 | self.series_widths = [1.0 for group in self.series] |
141 | |
142 | #TODO: Remove on next version |
143 | self.process_colors( series_colors ) |
144 | - |
145 | + |
146 | def process_colors( self, series_colors, length = None, mode = 'solid' ): |
147 | #series_colors might be None, a theme, a string of colors names or a list of color tuples |
148 | if length is None : |
149 | length = len( self.series.to_list() ) |
150 | - |
151 | + |
152 | #no colors passed |
153 | if not series_colors: |
154 | #Randomize colors |
155 | @@ -202,13 +206,13 @@ |
156 | if not hasattr( series_colors, "__iter__" ): |
157 | theme = series_colors |
158 | self.series_colors = colors_from_theme( theme.lower(), length ) |
159 | - |
160 | + |
161 | #Theme pattern and mode |
162 | elif not hasattr(series_colors, '__delitem__') and not hasattr( series_colors[0], "__iter__" ): |
163 | theme = series_colors[0] |
164 | mode = series_colors[1] |
165 | self.series_colors = colors_from_theme( theme.lower(), length, mode ) |
166 | - |
167 | + |
168 | #List |
169 | else: |
170 | self.series_colors = series_colors |
171 | @@ -229,12 +233,6 @@ |
172 | else: |
173 | self.series_colors[index] += tuple([mode]) |
174 | |
175 | - def get_width(self): |
176 | - return self.surface.get_width() |
177 | - |
178 | - def get_height(self): |
179 | - return self.surface.get_height() |
180 | - |
181 | def set_background(self, background): |
182 | if background is None: |
183 | self.background = (0.0,0.0,0.0,0.0) |
184 | @@ -250,7 +248,7 @@ |
185 | self.background.add_color_stop_rgba(float(index)/(len(colors)-1),*COLORS[color]) |
186 | else: |
187 | raise TypeError ("Background should be either cairo.LinearGradient or a 3/4-tuple, not %s" % type(background)) |
188 | - |
189 | + |
190 | def render_background(self): |
191 | if isinstance(self.background, cairo.LinearGradient): |
192 | self.context.set_source(self.background) |
193 | @@ -258,7 +256,7 @@ |
194 | self.context.set_source_rgba(*self.background) |
195 | self.context.rectangle(0,0, self.dimensions[HORZ], self.dimensions[VERT]) |
196 | self.context.fill() |
197 | - |
198 | + |
199 | def render_bounding_box(self): |
200 | self.context.set_source_rgba(*self.line_color) |
201 | self.context.set_line_width(self.line_width) |
202 | @@ -268,10 +266,13 @@ |
203 | self.context.stroke() |
204 | |
205 | def render(self): |
206 | - pass |
207 | + """All plots must prepare their context before rendering.""" |
208 | + self.handler.prepare(self) |
209 | + |
210 | + |
211 | |
212 | class ScatterPlot( Plot ): |
213 | - def __init__(self, |
214 | + def __init__(self, |
215 | surface=None, |
216 | data=None, |
217 | errorx=None, |
218 | @@ -279,7 +280,7 @@ |
219 | width=640, |
220 | height=480, |
221 | background=None, |
222 | - border=0, |
223 | + border=0, |
224 | axis = False, |
225 | dash = False, |
226 | discrete = False, |
227 | @@ -295,7 +296,7 @@ |
228 | y_title = None, |
229 | series_colors = None, |
230 | circle_colors = None ): |
231 | - |
232 | + |
233 | self.bounds = {} |
234 | self.bounds[HORZ] = x_bounds |
235 | self.bounds[VERT] = y_bounds |
236 | @@ -312,9 +313,9 @@ |
237 | self.variable_radius = False |
238 | self.x_label_angle = math.pi / 2.5 |
239 | self.circle_colors = circle_colors |
240 | - |
241 | + |
242 | Plot.__init__(self, surface, data, width, height, background, border, x_labels, y_labels, series_colors) |
243 | - |
244 | + |
245 | self.dash = None |
246 | if dash: |
247 | if hasattr(dash, "keys"): |
248 | @@ -323,9 +324,9 @@ |
249 | self.dash = dash |
250 | else: |
251 | self.dash = [dash] |
252 | - |
253 | + |
254 | self.load_errors(errorx, errory) |
255 | - |
256 | + |
257 | def convert_list_to_tuple(self, data): |
258 | #Data must be converted from lists of coordinates to a single |
259 | # list of tuples |
260 | @@ -333,22 +334,22 @@ |
261 | if len(data) == 3: |
262 | self.variable_radius = True |
263 | return out_data |
264 | - |
265 | + |
266 | def load_series(self, data, x_labels = None, y_labels = None, series_colors=None): |
267 | #TODO: In cairoplot 2.0 keep only the Series instances |
268 | |
269 | # Convert Data and Group to Series |
270 | if isinstance(data, Data) or isinstance(data, Group): |
271 | data = Series(data) |
272 | - |
273 | + |
274 | # Series |
275 | if isinstance(data, Series): |
276 | for group in data: |
277 | for item in group: |
278 | if len(item) is 3: |
279 | self.variable_radius = True |
280 | - |
281 | - #Dictionary with lists |
282 | + |
283 | + #Dictionary with lists |
284 | if hasattr(data, "keys") : |
285 | if hasattr( data.values()[0][0], "__delitem__" ) : |
286 | for key in data.keys() : |
287 | @@ -357,7 +358,7 @@ |
288 | self.variable_radius = True |
289 | #List |
290 | elif hasattr(data[0], "__delitem__") : |
291 | - #List of lists |
292 | + #List of lists |
293 | if hasattr(data[0][0], "__delitem__") : |
294 | for index,value in enumerate(data) : |
295 | data[index] = self.convert_list_to_tuple(value) |
296 | @@ -374,7 +375,7 @@ |
297 | Plot.load_series(self, data, x_labels, y_labels, series_colors) |
298 | self.calc_boundaries() |
299 | self.calc_labels() |
300 | - |
301 | + |
302 | def load_errors(self, errorx, errory): |
303 | self.errors = None |
304 | if errorx == None and errory == None: |
305 | @@ -394,7 +395,7 @@ |
306 | #simetric errors |
307 | elif errory: |
308 | self.errors[VERT] = [errory] |
309 | - |
310 | + |
311 | def calc_labels(self): |
312 | if not self.labels[HORZ]: |
313 | amplitude = self.bounds[HORZ][1] - self.bounds[HORZ][0] |
314 | @@ -418,18 +419,18 @@ |
315 | #HORZ = 0, VERT = 1, NORM = 2 |
316 | min_data_value = [0,0,0] |
317 | max_data_value = [0,0,0] |
318 | - |
319 | + |
320 | for group in self.series: |
321 | if type(group[0].content) in (int, float, long): |
322 | group = [Data((index, item.content)) for index,item in enumerate(group)] |
323 | - |
324 | + |
325 | for point in group: |
326 | for index, item in enumerate(point.content): |
327 | if item > max_data_value[index]: |
328 | max_data_value[index] = item |
329 | elif item < min_data_value[index]: |
330 | min_data_value[index] = item |
331 | - |
332 | + |
333 | if not self.bounds[HORZ]: |
334 | self.bounds[HORZ] = (min_data_value[HORZ], max_data_value[HORZ]) |
335 | if not self.bounds[VERT]: |
336 | @@ -443,9 +444,9 @@ |
337 | |
338 | self.plot_height = self.dimensions[VERT] - 2 * self.borders[VERT] |
339 | self.plot_width = self.dimensions[HORZ] - 2* self.borders[HORZ] |
340 | - |
341 | + |
342 | self.plot_top = self.dimensions[VERT] - self.borders[VERT] |
343 | - |
344 | + |
345 | def calc_steps(self): |
346 | #Calculates all the x, y, z and color steps |
347 | series_amplitude = [self.bounds[index][1] - self.bounds[index][0] for index in range(3)] |
348 | @@ -454,7 +455,7 @@ |
349 | self.horizontal_step = float (self.plot_width) / series_amplitude[HORZ] |
350 | else: |
351 | self.horizontal_step = 0.00 |
352 | - |
353 | + |
354 | if series_amplitude[VERT]: |
355 | self.vertical_step = float (self.plot_height) / series_amplitude[VERT] |
356 | else: |
357 | @@ -468,11 +469,13 @@ |
358 | else: |
359 | self.z_step = 0.00 |
360 | self.circle_color_step = ( 0.0, 0.0, 0.0, 0.0 ) |
361 | - |
362 | + |
363 | def get_circle_color(self, value): |
364 | return tuple( [self.circle_colors[0][i] + value*self.circle_color_step[i] for i in range(4)] ) |
365 | - |
366 | + |
367 | def render(self): |
368 | + Plot.render(self) |
369 | + |
370 | self.calc_all_extents() |
371 | self.calc_steps() |
372 | self.render_background() |
373 | @@ -487,7 +490,7 @@ |
374 | self.render_errors() |
375 | if self.series_legend and self.series_labels: |
376 | self.render_legend() |
377 | - |
378 | + |
379 | def render_axis(self): |
380 | #Draws both the axis lines and their titles |
381 | cr = self.context |
382 | @@ -513,15 +516,15 @@ |
383 | cr.rotate( math.pi/2 ) |
384 | cr.show_text( self.titles[VERT] ) |
385 | cr.rotate( -math.pi/2 ) |
386 | - |
387 | + |
388 | def render_grid(self): |
389 | cr = self.context |
390 | horizontal_step = float( self.plot_height ) / ( len( self.labels[VERT] ) - 1 ) |
391 | vertical_step = float( self.plot_width ) / ( len( self.labels[HORZ] ) - 1 ) |
392 | - |
393 | + |
394 | x = self.borders[HORZ] + vertical_step |
395 | y = self.plot_top - horizontal_step |
396 | - |
397 | + |
398 | for label in self.labels[HORZ][:-1]: |
399 | cr.set_source_rgba(*self.grid_color) |
400 | cr.move_to(x, self.dimensions[VERT] - self.borders[VERT]) |
401 | @@ -534,12 +537,12 @@ |
402 | cr.line_to(self.dimensions[HORZ] - self.borders[HORZ], y) |
403 | cr.stroke() |
404 | y -= horizontal_step |
405 | - |
406 | + |
407 | def render_labels(self): |
408 | self.context.set_font_size(self.font_size * 0.8) |
409 | self.render_horz_labels() |
410 | self.render_vert_labels() |
411 | - |
412 | + |
413 | def render_horz_labels(self): |
414 | cr = self.context |
415 | step = float( self.plot_width ) / ( len( self.labels[HORZ] ) - 1 ) |
416 | @@ -552,7 +555,7 @@ |
417 | cr.show_text(item) |
418 | cr.rotate(-self.x_label_angle) |
419 | x += step |
420 | - |
421 | + |
422 | def render_vert_labels(self): |
423 | cr = self.context |
424 | step = ( self.plot_height ) / ( len( self.labels[VERT] ) - 1 ) |
425 | @@ -573,10 +576,10 @@ |
426 | tallest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[3]) |
427 | max_width = self.context.text_extents(widest_word)[2] |
428 | max_height = self.context.text_extents(tallest_word)[3] * 1.1 |
429 | - |
430 | + |
431 | color_box_height = max_height / 2 |
432 | color_box_width = color_box_height * 2 |
433 | - |
434 | + |
435 | #Draw a bounding box |
436 | bounding_box_width = max_width + color_box_width + 15 |
437 | bounding_box_height = (len(self.series_labels)+0.5) * max_height |
438 | @@ -584,7 +587,7 @@ |
439 | cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - bounding_box_width, self.borders[VERT], |
440 | bounding_box_width, bounding_box_height) |
441 | cr.fill() |
442 | - |
443 | + |
444 | cr.set_source_rgba(*self.line_color) |
445 | cr.set_line_width(self.line_width) |
446 | cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - bounding_box_width, self.borders[VERT], |
447 | @@ -594,17 +597,17 @@ |
448 | for idx,key in enumerate(self.series_labels): |
449 | #Draw color box |
450 | cr.set_source_rgba(*self.series_colors[idx][:4]) |
451 | - cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - max_width - color_box_width - 10, |
452 | + cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - max_width - color_box_width - 10, |
453 | self.borders[VERT] + color_box_height + (idx*max_height) , |
454 | color_box_width, color_box_height) |
455 | cr.fill() |
456 | - |
457 | + |
458 | cr.set_source_rgba(0, 0, 0) |
459 | - cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - max_width - color_box_width - 10, |
460 | + cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - max_width - color_box_width - 10, |
461 | self.borders[VERT] + color_box_height + (idx*max_height), |
462 | color_box_width, color_box_height) |
463 | cr.stroke() |
464 | - |
465 | + |
466 | #Draw series labels |
467 | cr.set_source_rgba(0, 0, 0) |
468 | cr.move_to(self.dimensions[HORZ] - self.borders[HORZ] - max_width - 5, self.borders[VERT] + ((idx+1)*max_height)) |
469 | @@ -650,9 +653,11 @@ |
470 | cr.line_to(x - radius, y1) |
471 | cr.line_to(x + radius, y1) |
472 | cr.stroke() |
473 | - |
474 | - |
475 | + |
476 | + |
477 | def render_plot(self): |
478 | + """Draws the actual plot lines.""" |
479 | + |
480 | cr = self.context |
481 | if self.discrete: |
482 | cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height) |
483 | @@ -664,54 +669,63 @@ |
484 | cr.set_source_rgba(*self.series_colors[number][:4]) |
485 | for data in group : |
486 | if self.variable_radius: |
487 | - radius = data.content[2]*self.z_step |
488 | + radius = data.content[2] * self.z_step |
489 | if self.circle_colors: |
490 | - cr.set_source_rgba( *self.get_circle_color( data.content[2]) ) |
491 | - x = x0 + self.horizontal_step*data.content[0] |
492 | - y = y0 + self.vertical_step*data.content[1] |
493 | + cr.set_source_rgba( *self.get_circle_color( data.content[2])) |
494 | + x = x0 + self.horizontal_step * data.content[0] |
495 | + y = y0 + self.vertical_step * data.content[1] |
496 | cr.arc(x, self.dimensions[VERT] - y, radius, 0, 2*math.pi) |
497 | cr.fill() |
498 | else: |
499 | cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height) |
500 | cr.clip() |
501 | - x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step |
502 | - y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step |
503 | + x0 = self.borders[HORZ] - self.bounds[HORZ][0] * self.horizontal_step |
504 | + y0 = self.borders[VERT] - self.bounds[VERT][0] * self.vertical_step |
505 | + |
506 | radius = self.dots |
507 | for number, group in enumerate (self.series): |
508 | last_data = None |
509 | cr.set_source_rgba(*self.series_colors[number][:4]) |
510 | for data in group : |
511 | - x = x0 + self.horizontal_step*data.content[0] |
512 | - y = y0 + self.vertical_step*data.content[1] |
513 | + x = x0 + self.horizontal_step * data.content[0] |
514 | + y = y0 + self.vertical_step * data.content[1] |
515 | + |
516 | + # only draw a line for valid points |
517 | + if y != y: # math.isnan only in 2.6+ |
518 | + last_data = None |
519 | + continue |
520 | + |
521 | if self.dots: |
522 | if self.variable_radius: |
523 | radius = data.content[2]*self.z_step |
524 | cr.arc(x, self.dimensions[VERT] - y, radius, 0, 2*math.pi) |
525 | cr.fill() |
526 | - if last_data : |
527 | - old_x = x0 + self.horizontal_step*last_data.content[0] |
528 | - old_y = y0 + self.vertical_step*last_data.content[1] |
529 | + if last_data: |
530 | + old_x = x0 + self.horizontal_step * last_data.content[0] |
531 | + old_y = y0 + self.vertical_step * last_data.content[1] |
532 | cr.move_to( old_x, self.dimensions[VERT] - old_y ) |
533 | cr.line_to( x, self.dimensions[VERT] - y) |
534 | cr.set_line_width(self.series_widths[number]) |
535 | |
536 | - # Display line as dash line |
537 | + # Display line as dash line |
538 | if self.dash and self.dash[number]: |
539 | s = self.series_widths[number] |
540 | cr.set_dash([s*3, s*3], 0) |
541 | - |
542 | + |
543 | cr.stroke() |
544 | cr.set_dash([]) |
545 | last_data = data |
546 | |
547 | + |
548 | + |
549 | class DotLinePlot(ScatterPlot): |
550 | - def __init__(self, |
551 | + def __init__(self, |
552 | surface=None, |
553 | data=None, |
554 | width=640, |
555 | height=480, |
556 | background=None, |
557 | - border=0, |
558 | + border=0, |
559 | axis = False, |
560 | dash = False, |
561 | dots = 0, |
562 | @@ -724,8 +738,8 @@ |
563 | x_title = None, |
564 | y_title = None, |
565 | series_colors = None): |
566 | - |
567 | - ScatterPlot.__init__(self, surface, data, None, None, width, height, background, border, |
568 | + |
569 | + ScatterPlot.__init__(self, surface, data, None, None, width, height, background, border, |
570 | axis, dash, False, dots, grid, series_legend, x_labels, y_labels, |
571 | x_bounds, y_bounds, None, x_title, y_title, series_colors, None ) |
572 | |
573 | @@ -740,13 +754,13 @@ |
574 | self.calc_labels() |
575 | |
576 | class FunctionPlot(ScatterPlot): |
577 | - def __init__(self, |
578 | + def __init__(self, |
579 | surface=None, |
580 | data=None, |
581 | width=640, |
582 | height=480, |
583 | background=None, |
584 | - border=0, |
585 | + border=0, |
586 | axis = False, |
587 | discrete = False, |
588 | dots = 0, |
589 | @@ -758,96 +772,126 @@ |
590 | y_bounds = None, |
591 | x_title = None, |
592 | y_title = None, |
593 | - series_colors = None, |
594 | + series_colors = None, |
595 | step = 1): |
596 | |
597 | self.function = data |
598 | + |
599 | + # step should not be zero |
600 | self.step = step |
601 | + if self.step <= 0: |
602 | + self.step = 1 |
603 | + |
604 | self.discrete = discrete |
605 | - |
606 | + |
607 | data, x_bounds = self.load_series_from_function( self.function, x_bounds ) |
608 | |
609 | - ScatterPlot.__init__(self, surface, data, None, None, width, height, background, border, |
610 | + ScatterPlot.__init__(self, surface, data, None, None, width, height, background, border, |
611 | axis, False, discrete, dots, grid, series_legend, x_labels, y_labels, |
612 | x_bounds, y_bounds, None, x_title, y_title, series_colors, None ) |
613 | - |
614 | + |
615 | def load_series(self, data, x_labels = None, y_labels = None, series_colors=None): |
616 | Plot.load_series(self, data, x_labels, y_labels, series_colors) |
617 | - |
618 | - if len(self.series[0][0]) is 1: |
619 | + |
620 | + if len(self.series[0][0]) is 1: |
621 | for group_id, group in enumerate(self.series) : |
622 | for index,data in enumerate(group): |
623 | group[index].content = (self.bounds[HORZ][0] + self.step*index, data.content) |
624 | - |
625 | + |
626 | self.calc_boundaries() |
627 | self.calc_labels() |
628 | - |
629 | - def load_series_from_function( self, function, x_bounds ): |
630 | - #TODO: Add the possibility for the user to define multiple functions with different discretization parameters |
631 | - |
632 | - #This function converts a function, a list of functions or a dictionary |
633 | - #of functions into its corresponding array of data |
634 | + |
635 | + def load_series_from_function(self, function, x_bounds): |
636 | + """Converts a function (or functions) into array of data. |
637 | + |
638 | + Multiple functions can be defined by a list of functions or |
639 | + a dictionary of functions into its corresponding array of data. |
640 | + """ |
641 | + # TODO: Add the possibility for the user to define multiple functions with different discretization parameters |
642 | + |
643 | series = Series() |
644 | - |
645 | + |
646 | if isinstance(function, Group) or isinstance(function, Data): |
647 | function = Series(function) |
648 | - |
649 | - # If is instance of Series |
650 | + |
651 | + # is already a Series |
652 | + # overwrite any bounds passed by the function |
653 | if isinstance(function, Series): |
654 | - # Overwrite any bounds passed by the function |
655 | x_bounds = (function.range[0],function.range[-1]) |
656 | - |
657 | - #if no bounds are provided |
658 | + |
659 | + # no bounds are provided |
660 | if x_bounds == None: |
661 | x_bounds = (0,10) |
662 | - |
663 | - |
664 | - #TODO: Finish the dict translation |
665 | + |
666 | + # convert a single function into a "group" |
667 | + def convert_function(singlefunction, group): |
668 | + """Converts function into usable data. |
669 | + |
670 | + Math bounds errors correspond to nan values.""" |
671 | + |
672 | + def trygetpoint(inx): |
673 | + """Attempt to evaluate point, returns nan on errors""" |
674 | + try: |
675 | + return singlefunction(inx) |
676 | + except (ValueError, ZeroDivisionError, OverflowError): |
677 | + return float("nan") |
678 | + |
679 | + i = x_bounds[0] |
680 | + while i <= x_bounds[1]: |
681 | + group.add_data(trygetpoint(i)) |
682 | + i += self.step |
683 | + |
684 | + # TODO: Finish the dict translation |
685 | if hasattr(function, "keys"): #dictionary: |
686 | for key in function.keys(): |
687 | group = Group(name=key) |
688 | - #data[ key ] = [] |
689 | - i = x_bounds[0] |
690 | - while i <= x_bounds[1] : |
691 | - group.add_data(function[ key ](i)) |
692 | - #data[ key ].append( function[ key ](i) ) |
693 | - i += self.step |
694 | + convert_function(function[key], group) |
695 | series.add_group(group) |
696 | - |
697 | + |
698 | elif hasattr(function, "__delitem__"): #list of functions |
699 | - for index,f in enumerate( function ) : |
700 | + for f in function: |
701 | group = Group() |
702 | - #data.append( [] ) |
703 | - i = x_bounds[0] |
704 | - while i <= x_bounds[1] : |
705 | - group.add_data(f(i)) |
706 | - #data[ index ].append( f(i) ) |
707 | - i += self.step |
708 | + convert_function(f, group) |
709 | series.add_group(group) |
710 | - |
711 | + |
712 | elif isinstance(function, Series): # instance of Series |
713 | series = function |
714 | - |
715 | - else: #function |
716 | + |
717 | + else: # function |
718 | group = Group() |
719 | - i = x_bounds[0] |
720 | - while i <= x_bounds[1] : |
721 | - group.add_data(function(i)) |
722 | - i += self.step |
723 | + convert_function(function, group) |
724 | series.add_group(group) |
725 | - |
726 | - |
727 | + |
728 | return series, x_bounds |
729 | |
730 | + |
731 | def calc_labels(self): |
732 | + """Create labels from bounds""" |
733 | + |
734 | + boundrange = float(self.bounds[HORZ][1] - self.bounds[HORZ][0]) |
735 | + |
736 | + # based on range, change number of decimals displayed |
737 | + digits = 0 |
738 | + if 0 < boundrange < 10: |
739 | + digits = -math.floor(math.log10(boundrange)) |
740 | + digits += 1 |
741 | + labelformat = "%%.%df" % digits |
742 | + |
743 | + # make 10 labels (must be > 0) |
744 | + boundstep = boundrange / 10 |
745 | + if boundstep <= 0: |
746 | + boundstep = 1 |
747 | + |
748 | + # create string for each label |
749 | if not self.labels[HORZ]: |
750 | self.labels[HORZ] = [] |
751 | i = self.bounds[HORZ][0] |
752 | while i<=self.bounds[HORZ][1]: |
753 | - self.labels[HORZ].append(str(i)) |
754 | - i += float(self.bounds[HORZ][1] - self.bounds[HORZ][0])/10 |
755 | + self.labels[HORZ].append(labelformat % i) |
756 | + i += boundstep |
757 | ScatterPlot.calc_labels(self) |
758 | |
759 | + |
760 | def render_plot(self): |
761 | if not self.discrete: |
762 | ScatterPlot.render_plot(self) |
763 | @@ -861,6 +905,7 @@ |
764 | for data in group: |
765 | x = x0 + self.horizontal_step * data.content[0] |
766 | y = y0 + self.vertical_step * data.content[1] |
767 | + |
768 | cr.move_to(x, self.dimensions[VERT] - y) |
769 | cr.line_to(x, self.plot_top) |
770 | cr.set_line_width(self.series_widths[number]) |
771 | @@ -872,7 +917,7 @@ |
772 | cr.fill() |
773 | |
774 | class BarPlot(Plot): |
775 | - def __init__(self, |
776 | + def __init__(self, |
777 | surface = None, |
778 | data = None, |
779 | width = 640, |
780 | @@ -890,7 +935,7 @@ |
781 | y_bounds = None, |
782 | series_colors = None, |
783 | main_dir = None): |
784 | - |
785 | + |
786 | self.bounds = {} |
787 | self.bounds[HORZ] = x_bounds |
788 | self.bounds[VERT] = y_bounds |
789 | @@ -911,22 +956,22 @@ |
790 | def load_series(self, data, x_labels = None, y_labels = None, series_colors = None): |
791 | Plot.load_series(self, data, x_labels, y_labels, series_colors) |
792 | self.calc_boundaries() |
793 | - |
794 | + |
795 | def process_colors(self, series_colors): |
796 | #Data for a BarPlot might be a List or a List of Lists. |
797 | #On the first case, colors must be generated for all bars, |
798 | #On the second, colors must be generated for each of the inner lists. |
799 | - |
800 | + |
801 | #TODO: Didn't get it... |
802 | #if hasattr(self.data[0], '__getitem__'): |
803 | # length = max(len(series) for series in self.data) |
804 | #else: |
805 | # length = len( self.data ) |
806 | - |
807 | + |
808 | length = max(len(group) for group in self.series) |
809 | - |
810 | + |
811 | Plot.process_colors( self, series_colors, length, 'linear') |
812 | - |
813 | + |
814 | def calc_boundaries(self): |
815 | if not self.bounds[self.main_dir]: |
816 | if self.stack: |
817 | @@ -936,7 +981,7 @@ |
818 | self.bounds[self.main_dir] = (0, max_data_value) |
819 | if not self.bounds[other_direction(self.main_dir)]: |
820 | self.bounds[other_direction(self.main_dir)] = (0, len(self.series)) |
821 | - |
822 | + |
823 | def calc_extents(self, direction): |
824 | self.max_value[direction] = 0 |
825 | if self.labels[direction]: |
826 | @@ -979,8 +1024,10 @@ |
827 | series_length = len(self.series) |
828 | self.steps[other_dir] = float(self.plot_dimensions[other_dir])/(series_length + 0.1*(series_length + 1)) |
829 | self.space = 0.1*self.steps[other_dir] |
830 | - |
831 | + |
832 | def render(self): |
833 | + Plot.render(self) |
834 | + |
835 | self.calc_all_extents() |
836 | self.calc_steps() |
837 | self.render_background() |
838 | @@ -995,7 +1042,7 @@ |
839 | self.render_plot() |
840 | if self.series_labels: |
841 | self.render_legend() |
842 | - |
843 | + |
844 | def draw_3d_rectangle_front(self, x0, y0, x1, y1, shift): |
845 | self.context.rectangle(x0-shift, y0+shift, x1-x0, y1-y0) |
846 | |
847 | @@ -1014,7 +1061,7 @@ |
848 | self.context.line_to(x1-shift, y0+shift) |
849 | self.context.line_to(x0-shift, y0+shift) |
850 | self.context.close_path() |
851 | - |
852 | + |
853 | def draw_round_rectangle(self, x0, y0, x1, y1): |
854 | self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2) |
855 | self.context.line_to(x1-5, y0) |
856 | @@ -1027,15 +1074,15 @@ |
857 | self.context.close_path() |
858 | |
859 | def render_ground(self): |
860 | - self.draw_3d_rectangle_front(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
861 | - self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
862 | - self.context.fill() |
863 | - |
864 | - self.draw_3d_rectangle_side (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
865 | - self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
866 | - self.context.fill() |
867 | - |
868 | - self.draw_3d_rectangle_top (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
869 | + self.draw_3d_rectangle_front(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
870 | + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
871 | + self.context.fill() |
872 | + |
873 | + self.draw_3d_rectangle_side (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
874 | + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
875 | + self.context.fill() |
876 | + |
877 | + self.draw_3d_rectangle_top (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
878 | self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
879 | self.context.fill() |
880 | |
881 | @@ -1045,7 +1092,7 @@ |
882 | self.render_horz_labels() |
883 | if self.labels[VERT]: |
884 | self.render_vert_labels() |
885 | - |
886 | + |
887 | def render_legend(self): |
888 | cr = self.context |
889 | cr.set_font_size(self.font_size) |
890 | @@ -1055,10 +1102,10 @@ |
891 | tallest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[3]) |
892 | max_width = self.context.text_extents(widest_word)[2] |
893 | max_height = self.context.text_extents(tallest_word)[3] * 1.1 + 5 |
894 | - |
895 | + |
896 | color_box_height = max_height / 2 |
897 | color_box_width = color_box_height * 2 |
898 | - |
899 | + |
900 | #Draw a bounding box |
901 | bounding_box_width = max_width + color_box_width + 15 |
902 | bounding_box_height = (len(self.series_labels)+0.5) * max_height |
903 | @@ -1066,7 +1113,7 @@ |
904 | cr.rectangle(self.dimensions[HORZ] - self.border - bounding_box_width, self.border, |
905 | bounding_box_width, bounding_box_height) |
906 | cr.fill() |
907 | - |
908 | + |
909 | cr.set_source_rgba(*self.line_color) |
910 | cr.set_line_width(self.line_width) |
911 | cr.rectangle(self.dimensions[HORZ] - self.border - bounding_box_width, self.border, |
912 | @@ -1076,17 +1123,17 @@ |
913 | for idx,key in enumerate(self.series_labels): |
914 | #Draw color box |
915 | cr.set_source_rgba(*self.series_colors[idx][:4]) |
916 | - cr.rectangle(self.dimensions[HORZ] - self.border - max_width - color_box_width - 10, |
917 | + cr.rectangle(self.dimensions[HORZ] - self.border - max_width - color_box_width - 10, |
918 | self.border + color_box_height + (idx*max_height) , |
919 | color_box_width, color_box_height) |
920 | cr.fill() |
921 | - |
922 | + |
923 | cr.set_source_rgba(0, 0, 0) |
924 | - cr.rectangle(self.dimensions[HORZ] - self.border - max_width - color_box_width - 10, |
925 | + cr.rectangle(self.dimensions[HORZ] - self.border - max_width - color_box_width - 10, |
926 | self.border + color_box_height + (idx*max_height), |
927 | color_box_width, color_box_height) |
928 | cr.stroke() |
929 | - |
930 | + |
931 | #Draw series labels |
932 | cr.set_source_rgba(0, 0, 0) |
933 | cr.move_to(self.dimensions[HORZ] - self.border - max_width - 5, self.border + ((idx+1)*max_height)) |
934 | @@ -1094,7 +1141,7 @@ |
935 | |
936 | |
937 | class HorizontalBarPlot(BarPlot): |
938 | - def __init__(self, |
939 | + def __init__(self, |
940 | surface = None, |
941 | data = None, |
942 | width = 640, |
943 | @@ -1113,7 +1160,7 @@ |
944 | y_bounds = None, |
945 | series_colors = None): |
946 | |
947 | - BarPlot.__init__(self, surface, data, width, height, background, border, |
948 | + BarPlot.__init__(self, surface, data, width, height, background, border, |
949 | display_values, grid, rounded_corners, stack, three_dimension, |
950 | x_labels, y_labels, x_bounds, y_bounds, series_colors, HORZ) |
951 | self.series_labels = series_labels |
952 | @@ -1131,7 +1178,7 @@ |
953 | self.context.line_to(x1, y1) |
954 | self.context.line_to(x0+5, y1) |
955 | self.context.close_path() |
956 | - |
957 | + |
958 | def draw_rectangle_top(self, x0, y0, x1, y1): |
959 | self.context.arc(x1-5, y0+5, 5, -math.pi/2, 0) |
960 | self.context.line_to(x1, y1-5) |
961 | @@ -1140,7 +1187,7 @@ |
962 | self.context.line_to(x0, y0) |
963 | self.context.line_to(x1, y0) |
964 | self.context.close_path() |
965 | - |
966 | + |
967 | def draw_rectangle(self, index, length, x0, y0, x1, y1): |
968 | if length == 1: |
969 | BarPlot.draw_rectangle(self, x0, y0, x1, y1) |
970 | @@ -1264,9 +1311,9 @@ |
971 | self.context.rectangle(x0, y0, data.content*self.steps[HORZ], inner_step) |
972 | self.context.fill() |
973 | y0 += inner_step |
974 | - |
975 | + |
976 | class VerticalBarPlot(BarPlot): |
977 | - def __init__(self, |
978 | + def __init__(self, |
979 | surface = None, |
980 | data = None, |
981 | width = 640, |
982 | @@ -1285,7 +1332,7 @@ |
983 | y_bounds = None, |
984 | series_colors = None): |
985 | |
986 | - BarPlot.__init__(self, surface, data, width, height, background, border, |
987 | + BarPlot.__init__(self, surface, data, width, height, background, border, |
988 | display_values, grid, rounded_corners, stack, three_dimension, |
989 | x_labels, y_labels, x_bounds, y_bounds, series_colors, VERT) |
990 | self.series_labels = series_labels |
991 | @@ -1304,7 +1351,7 @@ |
992 | self.context.line_to(x1, y0) |
993 | self.context.line_to(x1, y1) |
994 | self.context.close_path() |
995 | - |
996 | + |
997 | def draw_rectangle_top(self, x0, y0, x1, y1): |
998 | self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2) |
999 | self.context.line_to(x1-5, y0) |
1000 | @@ -1313,7 +1360,7 @@ |
1001 | self.context.line_to(x0, y1) |
1002 | self.context.line_to(x0, y0) |
1003 | self.context.close_path() |
1004 | - |
1005 | + |
1006 | def draw_rectangle(self, index, length, x0, y0, x1, y1): |
1007 | if length == 1: |
1008 | BarPlot.draw_rectangle(self, x0, y0, x1, y1) |
1009 | @@ -1339,17 +1386,17 @@ |
1010 | self.context.line_to(self.dimensions[HORZ] - self.border, y) |
1011 | self.context.stroke() |
1012 | y += vertical_step |
1013 | - |
1014 | + |
1015 | def render_ground(self): |
1016 | - self.draw_3d_rectangle_front(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
1017 | - self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
1018 | - self.context.fill() |
1019 | - |
1020 | - self.draw_3d_rectangle_side (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
1021 | - self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
1022 | - self.context.fill() |
1023 | - |
1024 | - self.draw_3d_rectangle_top (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
1025 | + self.draw_3d_rectangle_front(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
1026 | + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
1027 | + self.context.fill() |
1028 | + |
1029 | + self.draw_3d_rectangle_side (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
1030 | + self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
1031 | + self.context.fill() |
1032 | + |
1033 | + self.draw_3d_rectangle_top (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT], |
1034 | self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10) |
1035 | self.context.fill() |
1036 | |
1037 | @@ -1367,7 +1414,7 @@ |
1038 | self.context.show_text(item) |
1039 | next_x = x + width/2 |
1040 | x += step + self.space |
1041 | - |
1042 | + |
1043 | def render_vert_labels(self): |
1044 | self.context.set_source_rgba(*self.label_color) |
1045 | y = self.borders[VERT] + self.value_label |
1046 | @@ -1449,11 +1496,11 @@ |
1047 | else: |
1048 | self.context.rectangle(x0, self.plot_top - data.content*self.steps[VERT], inner_step, data.content*self.steps[VERT]) |
1049 | self.context.fill() |
1050 | - |
1051 | + |
1052 | x0 += inner_step |
1053 | - |
1054 | + |
1055 | class StreamChart(VerticalBarPlot): |
1056 | - def __init__(self, |
1057 | + def __init__(self, |
1058 | surface = None, |
1059 | data = None, |
1060 | width = 640, |
1061 | @@ -1467,12 +1514,12 @@ |
1062 | y_bounds = None, |
1063 | series_colors = None): |
1064 | |
1065 | - VerticalBarPlot.__init__(self, surface, data, width, height, background, border, |
1066 | + VerticalBarPlot.__init__(self, surface, data, width, height, background, border, |
1067 | False, grid, False, True, False, |
1068 | None, x_labels, None, x_bounds, y_bounds, series_colors) |
1069 | - |
1070 | + |
1071 | def calc_steps(self): |
1072 | - other_dir = other_direction(self.main_dir) |
1073 | + other_dir = other_direction(self.main_dir) |
1074 | self.series_amplitude = self.bounds[self.main_dir][1] - self.bounds[self.main_dir][0] |
1075 | if self.series_amplitude: |
1076 | self.steps[self.main_dir] = float(self.plot_dimensions[self.main_dir])/self.series_amplitude |
1077 | @@ -1480,14 +1527,14 @@ |
1078 | self.steps[self.main_dir] = 0.00 |
1079 | series_length = len(self.data) |
1080 | self.steps[other_dir] = float(self.plot_dimensions[other_dir])/series_length |
1081 | - |
1082 | + |
1083 | def render_legend(self): |
1084 | pass |
1085 | - |
1086 | + |
1087 | def ground(self, index): |
1088 | sum_values = sum(self.data[index]) |
1089 | return -0.5*sum_values |
1090 | - |
1091 | + |
1092 | def calc_angles(self): |
1093 | middle = self.plot_top - self.plot_dimensions[VERT]/2.0 |
1094 | self.angles = [tuple([0.0 for x in range(len(self.data)+1)])] |
1095 | @@ -1503,11 +1550,11 @@ |
1096 | x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] |
1097 | y0 = middle - self.ground(x_index-1)*self.steps[VERT] - self.data[x_index-1][data_index]*self.steps[VERT] |
1098 | y2 = middle - self.ground(x_index+1)*self.steps[VERT] - self.data[x_index+1][data_index]*self.steps[VERT] |
1099 | - |
1100 | + |
1101 | for i in range(0,data_index): |
1102 | y0 -= self.data[x_index-1][i]*self.steps[VERT] |
1103 | y2 -= self.data[x_index+1][i]*self.steps[VERT] |
1104 | - |
1105 | + |
1106 | if data_index == len(self.data[0])-1 and False: |
1107 | self.context.set_source_rgba(0.0,0.0,0.0,0.3) |
1108 | self.context.move_to(x0,y0) |
1109 | @@ -1518,28 +1565,28 @@ |
1110 | t.append(math.atan(float(y0-y2)/(x0-x2))) |
1111 | self.angles.append(tuple(t)) |
1112 | self.angles.append(tuple([0.0 for x in range(len(self.data)+1)])) |
1113 | - |
1114 | + |
1115 | def render_plot(self): |
1116 | self.calc_angles() |
1117 | middle = self.plot_top - self.plot_dimensions[VERT]/2.0 |
1118 | p = 0.4*self.steps[HORZ] |
1119 | for data_index in range(len(self.data[0])-1,-1,-1): |
1120 | self.context.set_source_rgba(*self.series_colors[data_index][:4]) |
1121 | - |
1122 | + |
1123 | #draw the upper line |
1124 | for x_index in range(len(self.data)-1) : |
1125 | x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ] |
1126 | y1 = middle - self.ground(x_index)*self.steps[VERT] - self.data[x_index][data_index]*self.steps[VERT] |
1127 | x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] |
1128 | y2 = middle - self.ground(x_index + 1)*self.steps[VERT] - self.data[x_index + 1][data_index]*self.steps[VERT] |
1129 | - |
1130 | + |
1131 | for i in range(0,data_index): |
1132 | y1 -= self.data[x_index][i]*self.steps[VERT] |
1133 | y2 -= self.data[x_index+1][i]*self.steps[VERT] |
1134 | - |
1135 | + |
1136 | if x_index == 0: |
1137 | self.context.move_to(x1,y1) |
1138 | - |
1139 | + |
1140 | ang1 = self.angles[x_index][data_index+1] |
1141 | ang2 = self.angles[x_index+1][data_index+1] + math.pi |
1142 | self.context.curve_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1), |
1143 | @@ -1551,14 +1598,14 @@ |
1144 | y1 = middle - self.ground(x_index)*self.steps[VERT] |
1145 | x2 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] |
1146 | y2 = middle - self.ground(x_index - 1)*self.steps[VERT] |
1147 | - |
1148 | + |
1149 | for i in range(0,data_index): |
1150 | y1 -= self.data[x_index][i]*self.steps[VERT] |
1151 | y2 -= self.data[x_index-1][i]*self.steps[VERT] |
1152 | - |
1153 | + |
1154 | if x_index == len(self.data)-1: |
1155 | self.context.line_to(x1,y1+2) |
1156 | - |
1157 | + |
1158 | #revert angles by pi degrees to take the turn back |
1159 | ang1 = self.angles[x_index][data_index] + math.pi |
1160 | ang2 = self.angles[x_index-1][data_index] |
1161 | @@ -1568,7 +1615,7 @@ |
1162 | |
1163 | self.context.close_path() |
1164 | self.context.fill() |
1165 | - |
1166 | + |
1167 | if False: |
1168 | self.context.move_to(self.borders[HORZ] + 0.5*self.steps[HORZ], middle) |
1169 | for x_index in range(len(self.data)-1) : |
1170 | @@ -1576,11 +1623,11 @@ |
1171 | y1 = middle - self.ground(x_index)*self.steps[VERT] - self.data[x_index][data_index]*self.steps[VERT] |
1172 | x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ] |
1173 | y2 = middle - self.ground(x_index + 1)*self.steps[VERT] - self.data[x_index + 1][data_index]*self.steps[VERT] |
1174 | - |
1175 | + |
1176 | for i in range(0,data_index): |
1177 | y1 -= self.data[x_index][i]*self.steps[VERT] |
1178 | y2 -= self.data[x_index+1][i]*self.steps[VERT] |
1179 | - |
1180 | + |
1181 | ang1 = self.angles[x_index][data_index+1] |
1182 | ang2 = self.angles[x_index+1][data_index+1] + math.pi |
1183 | self.context.set_source_rgba(1.0,0.0,0.0) |
1184 | @@ -1589,9 +1636,9 @@ |
1185 | self.context.set_source_rgba(0.0,0.0,0.0) |
1186 | self.context.arc(x2+p*math.cos(ang2),y2+p*math.sin(ang2),2,0,2*math.pi) |
1187 | self.context.fill() |
1188 | - '''self.context.set_source_rgba(0.0,0.0,0.0,0.3) |
1189 | + """self.context.set_source_rgba(0.0,0.0,0.0,0.3) |
1190 | self.context.arc(x2,y2,2,0,2*math.pi) |
1191 | - self.context.fill()''' |
1192 | + self.context.fill()""" |
1193 | self.context.move_to(x1,y1) |
1194 | self.context.line_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1)) |
1195 | self.context.stroke() |
1196 | @@ -1604,11 +1651,11 @@ |
1197 | y1 = middle - self.ground(x_index)*self.steps[VERT] |
1198 | x2 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ] |
1199 | y2 = middle - self.ground(x_index - 1)*self.steps[VERT] |
1200 | - |
1201 | + |
1202 | for i in range(0,data_index): |
1203 | y1 -= self.data[x_index][i]*self.steps[VERT] |
1204 | y2 -= self.data[x_index-1][i]*self.steps[VERT] |
1205 | - |
1206 | + |
1207 | #revert angles by pi degrees to take the turn back |
1208 | ang1 = self.angles[x_index][data_index] + math.pi |
1209 | ang2 = self.angles[x_index-1][data_index] |
1210 | @@ -1618,9 +1665,9 @@ |
1211 | self.context.set_source_rgba(0.0,0.0,1.0) |
1212 | self.context.arc(x2+p*math.cos(ang2),y2+p*math.sin(ang2),2,0,2*math.pi) |
1213 | self.context.fill() |
1214 | - '''self.context.set_source_rgba(0.0,0.0,0.0,0.3) |
1215 | + """self.context.set_source_rgba(0.0,0.0,0.0,0.3) |
1216 | self.context.arc(x2,y2,2,0,2*math.pi) |
1217 | - self.context.fill()''' |
1218 | + self.context.fill()""" |
1219 | self.context.move_to(x1,y1) |
1220 | self.context.line_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1)) |
1221 | self.context.stroke() |
1222 | @@ -1628,18 +1675,18 @@ |
1223 | self.context.line_to(x2+p*math.cos(ang2),y2+p*math.sin(ang2)) |
1224 | self.context.stroke() |
1225 | #break |
1226 | - |
1227 | + |
1228 | #self.context.arc(self.dimensions[HORZ]/2, self.dimensions[VERT]/2,50,0,3*math.pi/2) |
1229 | #self.context.fill() |
1230 | - |
1231 | + |
1232 | |
1233 | class PiePlot(Plot): |
1234 | #TODO: Check the old cairoplot, graphs aren't matching |
1235 | def __init__ (self, |
1236 | - surface = None, |
1237 | - data = None, |
1238 | - width = 640, |
1239 | - height = 480, |
1240 | + surface = None, |
1241 | + data = None, |
1242 | + width = 640, |
1243 | + height = 480, |
1244 | background = "white light_gray", |
1245 | gradient = False, |
1246 | shadow = False, |
1247 | @@ -1651,7 +1698,7 @@ |
1248 | self.radius = min(self.dimensions[HORZ]/3,self.dimensions[VERT]/3) |
1249 | self.gradient = gradient |
1250 | self.shadow = shadow |
1251 | - |
1252 | + |
1253 | def sort_function(x,y): |
1254 | return x.content - y.content |
1255 | |
1256 | @@ -1668,6 +1715,8 @@ |
1257 | self.context.close_path() |
1258 | |
1259 | def render(self): |
1260 | + Plot.render(self) |
1261 | + |
1262 | self.render_background() |
1263 | self.render_bounding_box() |
1264 | if self.shadow: |
1265 | @@ -1690,7 +1739,7 @@ |
1266 | for number,key in enumerate(self.series_labels): |
1267 | # self.data[number] should be just a number |
1268 | data = sum(self.series[number].to_list()) |
1269 | - |
1270 | + |
1271 | next_angle = angle + 2.0*math.pi*data/self.total |
1272 | cr.set_source_rgba(*self.series_colors[number][:4]) |
1273 | w = cr.text_extents(key)[2] |
1274 | @@ -1732,9 +1781,9 @@ |
1275 | |
1276 | class DonutPlot(PiePlot): |
1277 | def __init__ (self, |
1278 | - surface = None, |
1279 | - data = None, |
1280 | - width = 640, |
1281 | + surface = None, |
1282 | + data = None, |
1283 | + width = 640, |
1284 | height = 480, |
1285 | background = "white light_gray", |
1286 | gradient = False, |
1287 | @@ -1743,12 +1792,12 @@ |
1288 | inner_radius=-1): |
1289 | |
1290 | Plot.__init__( self, surface, data, width, height, background, series_colors = colors ) |
1291 | - |
1292 | + |
1293 | self.center = ( self.dimensions[HORZ]/2, self.dimensions[VERT]/2 ) |
1294 | self.total = sum( self.series.to_list() ) |
1295 | self.radius = min( self.dimensions[HORZ]/3,self.dimensions[VERT]/3 ) |
1296 | self.inner_radius = inner_radius*self.radius |
1297 | - |
1298 | + |
1299 | if inner_radius == -1: |
1300 | self.inner_radius = self.radius/3 |
1301 | |
1302 | @@ -1762,7 +1811,7 @@ |
1303 | self.context.line_to(self.center[0] + (self.inner_radius)*math.cos(next_angle), self.center[1] + (self.inner_radius)*math.sin(next_angle)) |
1304 | self.context.arc_negative(self.center[0], self.center[1], self.inner_radius, next_angle, angle) |
1305 | self.context.close_path() |
1306 | - |
1307 | + |
1308 | def render_shadow(self): |
1309 | horizontal_shift = 3 |
1310 | vertical_shift = 3 |
1311 | @@ -1791,7 +1840,7 @@ |
1312 | def calc_boundaries(self): |
1313 | self.bounds[HORZ] = (0,len(self.series)) |
1314 | end_pos = max(self.series.to_list()) |
1315 | - |
1316 | + |
1317 | #for group in self.series: |
1318 | # if hasattr(item, "__delitem__"): |
1319 | # for sub_item in item: |
1320 | @@ -1820,6 +1869,8 @@ |
1321 | self.vertical_step = self.borders[VERT] |
1322 | |
1323 | def render(self): |
1324 | + Plot.render(self) |
1325 | + |
1326 | self.calc_horz_extents() |
1327 | self.calc_vert_extents() |
1328 | self.calc_steps() |
1329 | @@ -1835,7 +1886,7 @@ |
1330 | cr.rectangle(0,0,self.dimensions[HORZ], self.dimensions[VERT]) |
1331 | cr.fill() |
1332 | for number,group in enumerate(self.series): |
1333 | - linear = cairo.LinearGradient(self.dimensions[HORZ]/2, self.borders[VERT] + number*self.vertical_step, |
1334 | + linear = cairo.LinearGradient(self.dimensions[HORZ]/2, self.borders[VERT] + number*self.vertical_step, |
1335 | self.dimensions[HORZ]/2, self.borders[VERT] + (number+1)*self.vertical_step) |
1336 | linear.add_color_stop_rgba(0,1.0,1.0,1.0,1.0) |
1337 | linear.add_color_stop_rgba(1.0,0.9,0.9,0.9,1.0) |
1338 | @@ -1871,7 +1922,7 @@ |
1339 | w,h = cr.text_extents(label)[2], cr.text_extents(label)[3] |
1340 | cr.move_to(40,self.borders[VERT] + number*self.vertical_step + self.vertical_step/2 + h/2) |
1341 | cr.show_text(label) |
1342 | - |
1343 | + |
1344 | def render_vert_labels(self): |
1345 | cr = self.context |
1346 | labels = self.labels[VERT] |
1347 | @@ -1890,7 +1941,7 @@ |
1348 | self.context.set_source(gradient) |
1349 | self.context.rectangle(x0,y0,w,h) |
1350 | self.context.fill() |
1351 | - |
1352 | + |
1353 | def draw_circular_shadow(self, x, y, radius, ang_start, ang_end, mult, shadow): |
1354 | gradient = cairo.RadialGradient(x, y, 0, x, y, 2*radius) |
1355 | gradient.add_color_stop_rgba(0, 0, 0, 0, shadow) |
1356 | @@ -1950,10 +2001,10 @@ |
1357 | def render_plot(self): |
1358 | for index,group in enumerate(self.series): |
1359 | for data in group: |
1360 | - self.render_rectangle(self.borders[HORZ] + data.content[0]*self.horizontal_step, |
1361 | + self.render_rectangle(self.borders[HORZ] + data.content[0]*self.horizontal_step, |
1362 | self.borders[VERT] + index*self.vertical_step + self.vertical_step/4.0, |
1363 | - self.borders[HORZ] + data.content[1]*self.horizontal_step, |
1364 | - self.borders[VERT] + index*self.vertical_step + 3.0*self.vertical_step/4.0, |
1365 | + self.borders[HORZ] + data.content[1]*self.horizontal_step, |
1366 | + self.borders[VERT] + index*self.vertical_step + 3.0*self.vertical_step/4.0, |
1367 | self.series_colors[index]) |
1368 | |
1369 | # Function definition |
1370 | @@ -1968,7 +2019,7 @@ |
1371 | border = 0, |
1372 | axis = False, |
1373 | dash = False, |
1374 | - discrete = False, |
1375 | + discrete = False, |
1376 | dots = False, |
1377 | grid = False, |
1378 | series_legend = False, |
1379 | @@ -1981,22 +2032,22 @@ |
1380 | y_title = None, |
1381 | series_colors = None, |
1382 | circle_colors = None): |
1383 | - |
1384 | - ''' |
1385 | + |
1386 | + """ |
1387 | - Function to plot scatter data. |
1388 | - |
1389 | + |
1390 | - Parameters |
1391 | - |
1392 | + |
1393 | data - The values to be ploted might be passed in a two basic: |
1394 | list of points: [(0,0), (0,1), (0,2)] or [(0,0,1), (0,1,4), (0,2,1)] |
1395 | lists of coordinates: [ [0,0,0] , [0,1,2] ] or [ [0,0,0] , [0,1,2] , [1,4,1] ] |
1396 | - Notice that these kinds of that can be grouped in order to form more complex data |
1397 | + Notice that these kinds of that can be grouped in order to form more complex data |
1398 | using lists of lists or dictionaries; |
1399 | series_colors - Define color values for each of the series |
1400 | circle_colors - Define a lower and an upper bound for the circle colors for variable radius |
1401 | (3 dimensions) series |
1402 | - ''' |
1403 | - |
1404 | + """ |
1405 | + |
1406 | plot = ScatterPlot( name, data, errorx, errory, width, height, background, border, |
1407 | axis, dash, discrete, dots, grid, series_legend, x_labels, y_labels, |
1408 | x_bounds, y_bounds, z_bounds, x_title, y_title, series_colors, circle_colors ) |
1409 | @@ -2021,9 +2072,9 @@ |
1410 | x_title = None, |
1411 | y_title = None, |
1412 | series_colors = None): |
1413 | - ''' |
1414 | + """ |
1415 | - Function to plot graphics using dots and lines. |
1416 | - |
1417 | + |
1418 | dot_line_plot (name, data, width, height, background = "white light_gray", border = 0, axis = False, grid = False, x_labels = None, y_labels = None, x_bounds = None, y_bounds = None) |
1419 | |
1420 | - Parameters |
1421 | @@ -2031,7 +2082,7 @@ |
1422 | name - Name of the desired output file, no need to input the .svg as it will be added at runtim; |
1423 | data - The list, list of lists or dictionary holding the data to be plotted; |
1424 | width, height - Dimensions of the output image; |
1425 | - background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
1426 | + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
1427 | If left None, a gray to white gradient will be generated; |
1428 | border - Distance in pixels of a square border into which the graphics will be drawn; |
1429 | axis - Whether or not the axis are to be drawn; |
1430 | @@ -2048,12 +2099,12 @@ |
1431 | |
1432 | data = [0, 1, 3, 8, 9, 0, 10, 10, 2, 1] |
1433 | CairoPlot.dot_line_plot('teste', data, 400, 300) |
1434 | - |
1435 | + |
1436 | data = { "john" : [10, 10, 10, 10, 30], "mary" : [0, 0, 3, 5, 15], "philip" : [13, 32, 11, 25, 2] } |
1437 | x_labels = ["jan/2008", "feb/2008", "mar/2008", "apr/2008", "may/2008" ] |
1438 | - CairoPlot.dot_line_plot( 'test', data, 400, 300, axis = True, grid = True, |
1439 | + CairoPlot.dot_line_plot( 'test', data, 400, 300, axis = True, grid = True, |
1440 | series_legend = True, x_labels = x_labels ) |
1441 | - ''' |
1442 | + """ |
1443 | plot = DotLinePlot( name, data, width, height, background, border, |
1444 | axis, dash, dots, grid, series_legend, x_labels, y_labels, |
1445 | x_bounds, y_bounds, x_title, y_title, series_colors ) |
1446 | @@ -2080,17 +2131,17 @@ |
1447 | series_colors = None, |
1448 | step = 1): |
1449 | |
1450 | - ''' |
1451 | + """ |
1452 | - Function to plot functions. |
1453 | - |
1454 | + |
1455 | function_plot(name, data, width, height, background = "white light_gray", border = 0, axis = True, grid = False, dots = False, x_labels = None, y_labels = None, x_bounds = None, y_bounds = None, step = 1, discrete = False) |
1456 | |
1457 | - Parameters |
1458 | - |
1459 | + |
1460 | name - Name of the desired output file, no need to input the .svg as it will be added at runtim; |
1461 | data - The list, list of lists or dictionary holding the data to be plotted; |
1462 | width, height - Dimensions of the output image; |
1463 | - background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
1464 | + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
1465 | If left None, a gray to white gradient will be generated; |
1466 | border - Distance in pixels of a square border into which the graphics will be drawn; |
1467 | axis - Whether or not the axis are to be drawn; |
1468 | @@ -2100,13 +2151,13 @@ |
1469 | x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted; |
1470 | step - the horizontal distance from one point to the other. The smaller, the smoother the curve will be; |
1471 | discrete - whether or not the function should be plotted in discrete format. |
1472 | - |
1473 | + |
1474 | - Example of use |
1475 | |
1476 | data = lambda x : x**2 |
1477 | CairoPlot.function_plot('function4', data, 400, 300, grid = True, x_bounds=(-10,10), step = 0.1) |
1478 | - ''' |
1479 | - |
1480 | + """ |
1481 | + |
1482 | plot = FunctionPlot( name, data, width, height, background, border, |
1483 | axis, discrete, dots, grid, series_legend, x_labels, y_labels, |
1484 | x_bounds, y_bounds, x_title, y_title, series_colors, step ) |
1485 | @@ -2115,27 +2166,27 @@ |
1486 | |
1487 | def pie_plot( name, data, width, height, background = "white light_gray", gradient = False, shadow = False, colors = None ): |
1488 | |
1489 | - ''' |
1490 | + """ |
1491 | - Function to plot pie graphics. |
1492 | - |
1493 | + |
1494 | pie_plot(name, data, width, height, background = "white light_gray", gradient = False, colors = None) |
1495 | |
1496 | - Parameters |
1497 | - |
1498 | + |
1499 | name - Name of the desired output file, no need to input the .svg as it will be added at runtim; |
1500 | data - The list, list of lists or dictionary holding the data to be plotted; |
1501 | width, height - Dimensions of the output image; |
1502 | - background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
1503 | + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
1504 | If left None, a gray to white gradient will be generated; |
1505 | gradient - Whether or not the pie color will be painted with a gradient; |
1506 | shadow - Whether or not there will be a shadow behind the pie; |
1507 | colors - List of slices colors. |
1508 | |
1509 | - Example of use |
1510 | - |
1511 | + |
1512 | teste_data = {"john" : 123, "mary" : 489, "philip" : 890 , "suzy" : 235} |
1513 | CairoPlot.pie_plot("pie_teste", teste_data, 500, 500) |
1514 | - ''' |
1515 | + """ |
1516 | |
1517 | plot = PiePlot( name, data, width, height, background, gradient, shadow, colors ) |
1518 | plot.render() |
1519 | @@ -2143,17 +2194,17 @@ |
1520 | |
1521 | def donut_plot(name, data, width, height, background = "white light_gray", gradient = False, shadow = False, colors = None, inner_radius = -1): |
1522 | |
1523 | - ''' |
1524 | + """ |
1525 | - Function to plot donut graphics. |
1526 | - |
1527 | + |
1528 | donut_plot(name, data, width, height, background = "white light_gray", gradient = False, inner_radius = -1) |
1529 | |
1530 | - Parameters |
1531 | - |
1532 | + |
1533 | name - Name of the desired output file, no need to input the .svg as it will be added at runtim; |
1534 | data - The list, list of lists or dictionary holding the data to be plotted; |
1535 | width, height - Dimensions of the output image; |
1536 | - background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
1537 | + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
1538 | If left None, a gray to white gradient will be generated; |
1539 | shadow - Whether or not there will be a shadow behind the donut; |
1540 | gradient - Whether or not the donut color will be painted with a gradient; |
1541 | @@ -2161,10 +2212,10 @@ |
1542 | inner_radius - The radius of the donut's inner circle. |
1543 | |
1544 | - Example of use |
1545 | - |
1546 | + |
1547 | teste_data = {"john" : 123, "mary" : 489, "philip" : 890 , "suzy" : 235} |
1548 | CairoPlot.donut_plot("donut_teste", teste_data, 500, 500) |
1549 | - ''' |
1550 | + """ |
1551 | |
1552 | plot = DonutPlot(name, data, width, height, background, gradient, shadow, colors, inner_radius) |
1553 | plot.render() |
1554 | @@ -2172,13 +2223,13 @@ |
1555 | |
1556 | def gantt_chart(name, pieces, width, height, x_labels, y_labels, colors): |
1557 | |
1558 | - ''' |
1559 | + """ |
1560 | - Function to generate Gantt Charts. |
1561 | - |
1562 | + |
1563 | gantt_chart(name, pieces, width, height, x_labels, y_labels, colors): |
1564 | |
1565 | - Parameters |
1566 | - |
1567 | + |
1568 | name - Name of the desired output file, no need to input the .svg as it will be added at runtim; |
1569 | pieces - A list defining the spaces to be drawn. The user must pass, for each line, the index of its start and the index of its end. If a line must have two or more spaces, they must be passed inside a list; |
1570 | width, height - Dimensions of the output image; |
1571 | @@ -2193,42 +2244,42 @@ |
1572 | y_labels = [ '0001', '0002', '0003', '0004', '0005', '0006', '0007', '0008', '0009', '0010' ] |
1573 | colors = [ (1.0, 0.0, 0.0), (1.0, 0.7, 0.0), (1.0, 1.0, 0.0), (0.0, 1.0, 0.0) ] |
1574 | CairoPlot.gantt_chart('gantt_teste', pieces, 600, 300, x_labels, y_labels, colors) |
1575 | - ''' |
1576 | + """ |
1577 | |
1578 | plot = GanttChart(name, pieces, width, height, x_labels, y_labels, colors) |
1579 | plot.render() |
1580 | plot.commit() |
1581 | |
1582 | -def vertical_bar_plot(name, |
1583 | - data, |
1584 | - width, |
1585 | - height, |
1586 | - background = "white light_gray", |
1587 | - border = 0, |
1588 | +def vertical_bar_plot(name, |
1589 | + data, |
1590 | + width, |
1591 | + height, |
1592 | + background = "white light_gray", |
1593 | + border = 0, |
1594 | display_values = False, |
1595 | grid = False, |
1596 | rounded_corners = False, |
1597 | stack = False, |
1598 | three_dimension = False, |
1599 | series_labels = None, |
1600 | - x_labels = None, |
1601 | - y_labels = None, |
1602 | - x_bounds = None, |
1603 | + x_labels = None, |
1604 | + y_labels = None, |
1605 | + x_bounds = None, |
1606 | y_bounds = None, |
1607 | colors = None): |
1608 | #TODO: Fix docstring for vertical_bar_plot |
1609 | - ''' |
1610 | + """ |
1611 | - Function to generate vertical Bar Plot Charts. |
1612 | |
1613 | - bar_plot(name, data, width, height, background, border, grid, rounded_corners, three_dimension, |
1614 | + bar_plot(name, data, width, height, background, border, grid, rounded_corners, three_dimension, |
1615 | x_labels, y_labels, x_bounds, y_bounds, colors): |
1616 | |
1617 | - Parameters |
1618 | - |
1619 | + |
1620 | name - Name of the desired output file, no need to input the .svg as it will be added at runtime; |
1621 | data - The list, list of lists or dictionary holding the data to be plotted; |
1622 | width, height - Dimensions of the output image; |
1623 | - background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
1624 | + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
1625 | If left None, a gray to white gradient will be generated; |
1626 | border - Distance in pixels of a square border into which the graphics will be drawn; |
1627 | grid - Whether or not the gris is to be drawn; |
1628 | @@ -2242,19 +2293,19 @@ |
1629 | |
1630 | data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
1631 | CairoPlot.vertical_bar_plot ('bar2', data, 400, 300, border = 20, grid = True, rounded_corners = False) |
1632 | - ''' |
1633 | - |
1634 | - plot = VerticalBarPlot(name, data, width, height, background, border, |
1635 | - display_values, grid, rounded_corners, stack, three_dimension, |
1636 | + """ |
1637 | + |
1638 | + plot = VerticalBarPlot(name, data, width, height, background, border, |
1639 | + display_values, grid, rounded_corners, stack, three_dimension, |
1640 | series_labels, x_labels, y_labels, x_bounds, y_bounds, colors) |
1641 | plot.render() |
1642 | plot.commit() |
1643 | |
1644 | -def horizontal_bar_plot(name, |
1645 | - data, |
1646 | - width, |
1647 | - height, |
1648 | - background = "white light_gray", |
1649 | +def horizontal_bar_plot(name, |
1650 | + data, |
1651 | + width, |
1652 | + height, |
1653 | + background = "white light_gray", |
1654 | border = 0, |
1655 | display_values = False, |
1656 | grid = False, |
1657 | @@ -2262,25 +2313,25 @@ |
1658 | stack = False, |
1659 | three_dimension = False, |
1660 | series_labels = None, |
1661 | - x_labels = None, |
1662 | - y_labels = None, |
1663 | - x_bounds = None, |
1664 | + x_labels = None, |
1665 | + y_labels = None, |
1666 | + x_bounds = None, |
1667 | y_bounds = None, |
1668 | colors = None): |
1669 | |
1670 | #TODO: Fix docstring for horizontal_bar_plot |
1671 | - ''' |
1672 | + """ |
1673 | - Function to generate Horizontal Bar Plot Charts. |
1674 | |
1675 | - bar_plot(name, data, width, height, background, border, grid, rounded_corners, three_dimension, |
1676 | + bar_plot(name, data, width, height, background, border, grid, rounded_corners, three_dimension, |
1677 | x_labels, y_labels, x_bounds, y_bounds, colors): |
1678 | |
1679 | - Parameters |
1680 | - |
1681 | + |
1682 | name - Name of the desired output file, no need to input the .svg as it will be added at runtime; |
1683 | data - The list, list of lists or dictionary holding the data to be plotted; |
1684 | width, height - Dimensions of the output image; |
1685 | - background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
1686 | + background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient. |
1687 | If left None, a gray to white gradient will be generated; |
1688 | border - Distance in pixels of a square border into which the graphics will be drawn; |
1689 | grid - Whether or not the gris is to be drawn; |
1690 | @@ -2294,29 +2345,29 @@ |
1691 | |
1692 | data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
1693 | CairoPlot.bar_plot ('bar2', data, 400, 300, border = 20, grid = True, rounded_corners = False) |
1694 | - ''' |
1695 | - |
1696 | - plot = HorizontalBarPlot(name, data, width, height, background, border, |
1697 | - display_values, grid, rounded_corners, stack, three_dimension, |
1698 | + """ |
1699 | + |
1700 | + plot = HorizontalBarPlot(name, data, width, height, background, border, |
1701 | + display_values, grid, rounded_corners, stack, three_dimension, |
1702 | series_labels, x_labels, y_labels, x_bounds, y_bounds, colors) |
1703 | plot.render() |
1704 | plot.commit() |
1705 | |
1706 | -def stream_chart(name, |
1707 | - data, |
1708 | - width, |
1709 | - height, |
1710 | - background = "white light_gray", |
1711 | +def stream_chart(name, |
1712 | + data, |
1713 | + width, |
1714 | + height, |
1715 | + background = "white light_gray", |
1716 | border = 0, |
1717 | grid = False, |
1718 | series_legend = None, |
1719 | - x_labels = None, |
1720 | - x_bounds = None, |
1721 | + x_labels = None, |
1722 | + x_bounds = None, |
1723 | y_bounds = None, |
1724 | colors = None): |
1725 | |
1726 | #TODO: Fix docstring for horizontal_bar_plot |
1727 | - plot = StreamChart(name, data, width, height, background, border, |
1728 | + plot = StreamChart(name, data, width, height, background, border, |
1729 | grid, series_legend, x_labels, x_bounds, y_bounds, colors) |
1730 | plot.render() |
1731 | plot.commit() |
1732 | |
1733 | === added directory 'trunk/cairoplot/handlers' |
1734 | === added file 'trunk/cairoplot/handlers/__init__.py' |
1735 | --- trunk/cairoplot/handlers/__init__.py 1970-01-01 00:00:00 +0000 |
1736 | +++ trunk/cairoplot/handlers/__init__.py 2010-04-07 05:06:25 +0000 |
1737 | @@ -0,0 +1,10 @@ |
1738 | +__all__ = ["handler", "png", "pdf", "ps", "svg", "vector"] |
1739 | + |
1740 | +# default handlers |
1741 | +from .handler import Handler |
1742 | +from .png import PNGHandler |
1743 | +from .pdf import PDFHandler |
1744 | +from .ps import PSHandler |
1745 | +from .svg import SVGHandler |
1746 | +from .vector import VectorHandler |
1747 | + |
1748 | |
1749 | === added file 'trunk/cairoplot/handlers/fixedsize.py' |
1750 | --- trunk/cairoplot/handlers/fixedsize.py 1970-01-01 00:00:00 +0000 |
1751 | +++ trunk/cairoplot/handlers/fixedsize.py 2010-04-07 05:06:25 +0000 |
1752 | @@ -0,0 +1,30 @@ |
1753 | + |
1754 | +import cairo |
1755 | +import cairoplot |
1756 | +from .handler import Handler as _Handler |
1757 | + |
1758 | +class FixedSizeHandler(_Handler): |
1759 | + """Base class for handlers with a fixed size.""" |
1760 | + |
1761 | + def __init__(self, width, height): |
1762 | + """Create with fixed width and height.""" |
1763 | + self.dimensions = {} |
1764 | + self.dimensions[cairoplot.HORZ] = width |
1765 | + self.dimensions[cairoplot.VERT] = height |
1766 | + |
1767 | + # sub-classes must create a surface |
1768 | + self.surface = None |
1769 | + |
1770 | + def prepare(self, plot): |
1771 | + """Prepare plot to render by setting its dimensions.""" |
1772 | + _Handler.prepare(self, plot) |
1773 | + plot.dimensions = self.dimensions |
1774 | + plot.context = cairo.Context(self.surface) |
1775 | + |
1776 | + def commit(self, plot): |
1777 | + """Commit the plot (to a file).""" |
1778 | + _Handler.commit(self, plot) |
1779 | + |
1780 | + # since pngs are different from other fixed size handlers, |
1781 | + # sub-classes are in charge of actual file writing |
1782 | + |
1783 | |
1784 | === added file 'trunk/cairoplot/handlers/gtk.py' |
1785 | --- trunk/cairoplot/handlers/gtk.py 1970-01-01 00:00:00 +0000 |
1786 | +++ trunk/cairoplot/handlers/gtk.py 2010-04-07 05:06:25 +0000 |
1787 | @@ -0,0 +1,39 @@ |
1788 | +from __future__ import absolute_import |
1789 | + |
1790 | +import gtk |
1791 | +import cairo |
1792 | +import cairoplot |
1793 | +from .handler import Handler as _Handler |
1794 | + |
1795 | +class GTKHandler(_Handler, gtk.DrawingArea): |
1796 | + """Handler to create plots that output to vector files.""" |
1797 | + |
1798 | + def __init__(self, *args, **kwargs): |
1799 | + """Create Handler for arbitrary surfaces.""" |
1800 | + _Handler.__init__(self) |
1801 | + gtk.DrawingArea.__init__(self) |
1802 | + |
1803 | + # users of this class must set plot manually |
1804 | + self.plot = None |
1805 | + self.context = None |
1806 | + |
1807 | + # connect events for resizing/redrawing |
1808 | + self.connect("expose_event", self.on_expose_event) |
1809 | + |
1810 | + def on_expose_event(self, widget, data): |
1811 | + """Redraws plot if need be.""" |
1812 | + |
1813 | + self.context = widget.window.cairo_create() |
1814 | + if (self.plot is not None): |
1815 | + self.plot.render() |
1816 | + |
1817 | + def prepare(self, plot): |
1818 | + """Update plot's size and context with custom widget.""" |
1819 | + _Handler.prepare(self, plot) |
1820 | + self.plot = plot |
1821 | + plot.context = self.context |
1822 | + |
1823 | + allocation = self.get_allocation() |
1824 | + plot.dimensions[cairoplot.HORZ] = allocation.width |
1825 | + plot.dimensions[cairoplot.VERT] = allocation.height |
1826 | + |
1827 | |
1828 | === added file 'trunk/cairoplot/handlers/handler.py' |
1829 | --- trunk/cairoplot/handlers/handler.py 1970-01-01 00:00:00 +0000 |
1830 | +++ trunk/cairoplot/handlers/handler.py 2010-04-07 05:06:25 +0000 |
1831 | @@ -0,0 +1,11 @@ |
1832 | + |
1833 | +class Handler(object): |
1834 | + """Base class for all handlers.""" |
1835 | + |
1836 | + def prepare(self, plot): |
1837 | + pass |
1838 | + |
1839 | + def commit(self, plot): |
1840 | + """All handlers need to finalize the cairo context.""" |
1841 | + plot.context.show_page() |
1842 | + |
1843 | |
1844 | === added file 'trunk/cairoplot/handlers/pdf.py' |
1845 | --- trunk/cairoplot/handlers/pdf.py 1970-01-01 00:00:00 +0000 |
1846 | +++ trunk/cairoplot/handlers/pdf.py 2010-04-07 05:06:25 +0000 |
1847 | @@ -0,0 +1,12 @@ |
1848 | + |
1849 | +import cairo |
1850 | +from .vector import VectorHandler as _VectorHandler |
1851 | + |
1852 | +class PDFHandler(_VectorHandler): |
1853 | + """Handler to create plots that output to pdf files.""" |
1854 | + |
1855 | + def __init__(self, filename, width, height): |
1856 | + """Creates a surface to be used by Cairo.""" |
1857 | + _VectorHandler.__init__(self, None, width, height) |
1858 | + self.surface = cairo.PDFSurface(filename, width, height) |
1859 | + |
1860 | |
1861 | === added file 'trunk/cairoplot/handlers/png.py' |
1862 | --- trunk/cairoplot/handlers/png.py 1970-01-01 00:00:00 +0000 |
1863 | +++ trunk/cairoplot/handlers/png.py 2010-04-07 05:06:25 +0000 |
1864 | @@ -0,0 +1,19 @@ |
1865 | + |
1866 | +import cairo |
1867 | + |
1868 | +from .fixedsize import FixedSizeHandler as _FixedSizeHandler |
1869 | + |
1870 | +class PNGHandler(_FixedSizeHandler): |
1871 | + """Handler to create plots that output to png files.""" |
1872 | + |
1873 | + def __init__(self, filename, width, height): |
1874 | + """Creates a surface to be used by Cairo.""" |
1875 | + _FixedSizeHandler.__init__(self, width, height) |
1876 | + self.filename = filename |
1877 | + self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) |
1878 | + |
1879 | + def commit(self, plot): |
1880 | + """Writes plot to file.""" |
1881 | + _FixedSizeHandler.commit(self, plot) |
1882 | + self.surface.write_to_png(self.filename) |
1883 | + |
1884 | |
1885 | === added file 'trunk/cairoplot/handlers/ps.py' |
1886 | --- trunk/cairoplot/handlers/ps.py 1970-01-01 00:00:00 +0000 |
1887 | +++ trunk/cairoplot/handlers/ps.py 2010-04-07 05:06:25 +0000 |
1888 | @@ -0,0 +1,12 @@ |
1889 | + |
1890 | +import cairo |
1891 | +from .vector import VectorHandler as _VectorHandler |
1892 | + |
1893 | +class PSHandler(_VectorHandler): |
1894 | + """Handler to create plots that output to PostScript files.""" |
1895 | + |
1896 | + def __init__(self, filename, width, height): |
1897 | + """Creates a surface to be used by Cairo.""" |
1898 | + _VectorHandler.__init__(self, None, width, height) |
1899 | + self.surface = cairo.PSSurface(filename, width, height) |
1900 | + |
1901 | |
1902 | === added file 'trunk/cairoplot/handlers/svg.py' |
1903 | --- trunk/cairoplot/handlers/svg.py 1970-01-01 00:00:00 +0000 |
1904 | +++ trunk/cairoplot/handlers/svg.py 2010-04-07 05:06:25 +0000 |
1905 | @@ -0,0 +1,12 @@ |
1906 | + |
1907 | +import cairo |
1908 | +from .vector import VectorHandler as _VectorHandler |
1909 | + |
1910 | +class SVGHandler(_VectorHandler): |
1911 | + """Handler to create plots that output to svg files.""" |
1912 | + |
1913 | + def __init__(self, filename, width, height): |
1914 | + """Creates a surface to be used by Cairo.""" |
1915 | + _VectorHandler.__init__(self, None, width, height) |
1916 | + self.surface = cairo.SVGSurface(filename, width, height) |
1917 | + |
1918 | |
1919 | === added file 'trunk/cairoplot/handlers/vector.py' |
1920 | --- trunk/cairoplot/handlers/vector.py 1970-01-01 00:00:00 +0000 |
1921 | +++ trunk/cairoplot/handlers/vector.py 2010-04-07 05:06:25 +0000 |
1922 | @@ -0,0 +1,17 @@ |
1923 | + |
1924 | +import cairo |
1925 | +from .fixedsize import FixedSizeHandler as _FixedSizeHandler |
1926 | + |
1927 | +class VectorHandler(_FixedSizeHandler): |
1928 | + """Handler to create plots that output to vector files.""" |
1929 | + |
1930 | + def __init__(self, surface, *args, **kwargs): |
1931 | + """Create Handler for arbitrary surfaces.""" |
1932 | + _FixedSizeHandler.__init__(self, *args, **kwargs) |
1933 | + self.surface = surface |
1934 | + |
1935 | + def commit(self, plot): |
1936 | + """Writes plot to file.""" |
1937 | + _FixedSizeHandler.commit(self, plot) |
1938 | + self.surface.finish() |
1939 | + |
1940 | |
1941 | === renamed file 'trunk/series.py' => 'trunk/cairoplot/series.py' |
1942 | --- trunk/series.py 2009-07-09 22:02:04 +0000 |
1943 | +++ trunk/cairoplot/series.py 2010-04-07 05:06:25 +0000 |
1944 | @@ -36,23 +36,23 @@ |
1945 | DEFAULT_COLOR_LIST = None |
1946 | |
1947 | class Data(object): |
1948 | - ''' |
1949 | + """ |
1950 | Class that models the main data structure. |
1951 | It can hold: |
1952 | - a number type (int, float or long) |
1953 | - a tuple, witch represents a point and can have 2 or 3 items (x,y,z) |
1954 | - if a list is passed it will be converted to a tuple. |
1955 | - |
1956 | + |
1957 | obs: In case a tuple is passed it will convert to tuple |
1958 | - ''' |
1959 | + """ |
1960 | def __init__(self, data=None, name=None, parent=None): |
1961 | - ''' |
1962 | + """ |
1963 | Starts main atributes from the Data class |
1964 | @name - Name for each point; |
1965 | @content - The real data, can be an int, float, long or tuple, which |
1966 | represents a point (x,y) or (x,y,z); |
1967 | @parent - A pointer that give the data access to it's parent. |
1968 | - |
1969 | + |
1970 | Usage: |
1971 | >>> d = Data(name='empty'); print d |
1972 | empty: () |
1973 | @@ -64,23 +64,23 @@ |
1974 | point c: (2, 3) |
1975 | >>> d = Data(12, 'simple value'); print d |
1976 | simple value: 12 |
1977 | - ''' |
1978 | + """ |
1979 | # Initial values |
1980 | self.__content = None |
1981 | self.__name = None |
1982 | - |
1983 | + |
1984 | # Setting passed values |
1985 | self.parent = parent |
1986 | self.name = name |
1987 | self.content = data |
1988 | - |
1989 | + |
1990 | # Name property |
1991 | @apply |
1992 | def name(): |
1993 | - doc = ''' |
1994 | + doc = """ |
1995 | Name is a read/write property that controls the input of name. |
1996 | - If passed an invalid value it cleans the name with None |
1997 | - |
1998 | + |
1999 | Usage: |
2000 | >>> d = Data(13); d.name = 'name_test'; print d |
2001 | name_test: 13 |
2002 | @@ -94,33 +94,33 @@ |
2003 | last_name: 13 |
2004 | >>> d.name = ''; print d |
2005 | 13 |
2006 | - ''' |
2007 | + """ |
2008 | def fget(self): |
2009 | - ''' |
2010 | + """ |
2011 | returns the name as a string |
2012 | - ''' |
2013 | + """ |
2014 | return self.__name |
2015 | - |
2016 | + |
2017 | def fset(self, name): |
2018 | - ''' |
2019 | + """ |
2020 | Sets the name of the Data |
2021 | - ''' |
2022 | + """ |
2023 | if type(name) in STRTYPES and len(name) > 0: |
2024 | self.__name = name |
2025 | else: |
2026 | self.__name = None |
2027 | - |
2028 | - |
2029 | - |
2030 | + |
2031 | + |
2032 | + |
2033 | return property(**locals()) |
2034 | |
2035 | # Content property |
2036 | @apply |
2037 | def content(): |
2038 | - doc = ''' |
2039 | + doc = """ |
2040 | Content is a read/write property that validate the data passed |
2041 | and return it. |
2042 | - |
2043 | + |
2044 | Usage: |
2045 | >>> d = Data(); d.content = 13; d.content |
2046 | 13 |
2047 | @@ -132,90 +132,90 @@ |
2048 | (1, 2, 3) |
2049 | >>> d = Data(); d.content = [1.5,.2,3.3]; d.content |
2050 | (1.5, 0.20000000000000001, 3.2999999999999998) |
2051 | - ''' |
2052 | + """ |
2053 | def fget(self): |
2054 | - ''' |
2055 | + """ |
2056 | Return the content of Data |
2057 | - ''' |
2058 | + """ |
2059 | return self.__content |
2060 | |
2061 | def fset(self, data): |
2062 | - ''' |
2063 | + """ |
2064 | Ensures that data is a valid tuple/list or a number (int, float |
2065 | or long) |
2066 | - ''' |
2067 | + """ |
2068 | # Type: None |
2069 | if data is None: |
2070 | self.__content = None |
2071 | return |
2072 | - |
2073 | + |
2074 | # Type: Int or Float |
2075 | elif type(data) in NUMTYPES: |
2076 | self.__content = data |
2077 | - |
2078 | + |
2079 | # Type: List or Tuple |
2080 | elif type(data) in LISTTYPES: |
2081 | # Ensures the correct size |
2082 | if len(data) not in (2, 3): |
2083 | raise TypeError, "Data (as list/tuple) must have 2 or 3 items" |
2084 | return |
2085 | - |
2086 | + |
2087 | # Ensures that all items in list/tuple is a number |
2088 | isnum = lambda x : type(x) not in NUMTYPES |
2089 | - |
2090 | + |
2091 | if max(map(isnum, data)): |
2092 | # An item in data isn't an int or a float |
2093 | raise TypeError, "All content of data must be a number (int or float)" |
2094 | - |
2095 | + |
2096 | # Convert the tuple to list |
2097 | if type(data) is list: |
2098 | data = tuple(data) |
2099 | - |
2100 | + |
2101 | # Append a copy and sets the type |
2102 | self.__content = data[:] |
2103 | - |
2104 | + |
2105 | # Unknown type! |
2106 | else: |
2107 | self.__content = None |
2108 | raise TypeError, "Data must be an int, float or a tuple with two or three items" |
2109 | return |
2110 | - |
2111 | + |
2112 | return property(**locals()) |
2113 | |
2114 | - |
2115 | + |
2116 | def clear(self): |
2117 | - ''' |
2118 | + """ |
2119 | Clear the all Data (content, name and parent) |
2120 | - ''' |
2121 | + """ |
2122 | self.content = None |
2123 | self.name = None |
2124 | self.parent = None |
2125 | - |
2126 | + |
2127 | def copy(self): |
2128 | - ''' |
2129 | + """ |
2130 | Returns a copy of the Data structure |
2131 | - ''' |
2132 | + """ |
2133 | # The copy |
2134 | new_data = Data() |
2135 | if self.content is not None: |
2136 | # If content is a point |
2137 | if type(self.content) is tuple: |
2138 | new_data.__content = self.content[:] |
2139 | - |
2140 | + |
2141 | # If content is a number |
2142 | else: |
2143 | new_data.__content = self.content |
2144 | - |
2145 | + |
2146 | # If it has a name |
2147 | if self.name is not None: |
2148 | new_data.__name = self.name |
2149 | - |
2150 | + |
2151 | return new_data |
2152 | - |
2153 | + |
2154 | def __str__(self): |
2155 | - ''' |
2156 | + """ |
2157 | Return a string representation of the Data structure |
2158 | - ''' |
2159 | + """ |
2160 | if self.name is None: |
2161 | if self.content is None: |
2162 | return '' |
2163 | @@ -226,23 +226,23 @@ |
2164 | return self.name+": "+str(self.content) |
2165 | |
2166 | def __len__(self): |
2167 | - ''' |
2168 | + """ |
2169 | Return the length of the Data. |
2170 | - If it's a number return 1; |
2171 | - If it's a list return it's length; |
2172 | - If its None return 0. |
2173 | - ''' |
2174 | + """ |
2175 | if self.content is None: |
2176 | return 0 |
2177 | elif type(self.content) in NUMTYPES: |
2178 | return 1 |
2179 | return len(self.content) |
2180 | - |
2181 | - |
2182 | - |
2183 | + |
2184 | + |
2185 | + |
2186 | |
2187 | class Group(object): |
2188 | - ''' |
2189 | + """ |
2190 | Class that models a group of data. Every value (int, float, long, tuple |
2191 | or list) passed is converted to a list of Data. |
2192 | It can receive: |
2193 | @@ -251,20 +251,20 @@ |
2194 | - A tuple of numbers; |
2195 | - An instance of Data; |
2196 | - A list of Data; |
2197 | - |
2198 | + |
2199 | Obs: If a tuple with 2 or 3 items is passed it is converted to a point. |
2200 | If a tuple with only 1 item is passed it's converted to a number; |
2201 | If a tuple with more than 2 items is passed it's converted to a |
2202 | list of numbers |
2203 | - ''' |
2204 | + """ |
2205 | def __init__(self, group=None, name=None, parent=None): |
2206 | - ''' |
2207 | + """ |
2208 | Starts main atributes in Group instance. |
2209 | @data_list - a list of data which forms the group; |
2210 | @range - a range that represent the x axis of possible functions; |
2211 | @name - name of the data group; |
2212 | @parent - the Serie parent of this group. |
2213 | - |
2214 | + |
2215 | Usage: |
2216 | >>> g = Group(13, 'simple number'); print g |
2217 | simple number ['13'] |
2218 | @@ -280,24 +280,24 @@ |
2219 | 2D coordinated lists ['(1, 1)', '(2, 2)', '(3, 3)'] |
2220 | >>> g = Group([[1,2],[1,2],[1,2]], '3D coordinate lists'); print g |
2221 | 3D coordinated lists ['(1, 1, 1)', '(2, 2, 2)'] |
2222 | - ''' |
2223 | + """ |
2224 | # Initial values |
2225 | self.__data_list = [] |
2226 | self.__range = [] |
2227 | self.__name = None |
2228 | - |
2229 | - |
2230 | + |
2231 | + |
2232 | self.parent = parent |
2233 | self.name = name |
2234 | self.data_list = group |
2235 | - |
2236 | + |
2237 | # Name property |
2238 | @apply |
2239 | def name(): |
2240 | - doc = ''' |
2241 | + doc = """ |
2242 | Name is a read/write property that controls the input of name. |
2243 | - If passed an invalid value it cleans the name with None |
2244 | - |
2245 | + |
2246 | Usage: |
2247 | >>> g = Group(13); g.name = 'name_test'; print g |
2248 | name_test ['13'] |
2249 | @@ -311,32 +311,32 @@ |
2250 | last_name ['13'] |
2251 | >>> g.name = ''; print g |
2252 | ['13'] |
2253 | - ''' |
2254 | + """ |
2255 | def fget(self): |
2256 | - ''' |
2257 | + """ |
2258 | Returns the name as a string |
2259 | - ''' |
2260 | + """ |
2261 | return self.__name |
2262 | - |
2263 | + |
2264 | def fset(self, name): |
2265 | - ''' |
2266 | + """ |
2267 | Sets the name of the Group |
2268 | - ''' |
2269 | + """ |
2270 | if type(name) in STRTYPES and len(name) > 0: |
2271 | self.__name = name |
2272 | else: |
2273 | self.__name = None |
2274 | - |
2275 | + |
2276 | return property(**locals()) |
2277 | |
2278 | # data_list property |
2279 | @apply |
2280 | def data_list(): |
2281 | - doc = ''' |
2282 | + doc = """ |
2283 | The data_list is a read/write property that can be a list of |
2284 | numbers, a list of points or a list of 2 or 3 coordinate lists. This |
2285 | property uses mainly the self.add_data method. |
2286 | - |
2287 | + |
2288 | Usage: |
2289 | >>> g = Group(); g.data_list = 13; print g |
2290 | ['13'] |
2291 | @@ -356,32 +356,32 @@ |
2292 | ['(1, 1, 1)', '(2, 2, 2)'] |
2293 | >>> g.range = (10); g.data_list = lambda x:x**2; print g |
2294 | ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 4.0)', '(3.0, 9.0)', '(4.0, 16.0)', '(5.0, 25.0)', '(6.0, 36.0)', '(7.0, 49.0)', '(8.0, 64.0)', '(9.0, 81.0)'] |
2295 | - ''' |
2296 | + """ |
2297 | def fget(self): |
2298 | - ''' |
2299 | + """ |
2300 | Returns the value of data_list |
2301 | - ''' |
2302 | + """ |
2303 | return self.__data_list |
2304 | |
2305 | def fset(self, group): |
2306 | - ''' |
2307 | + """ |
2308 | Ensures that group is valid. |
2309 | - ''' |
2310 | + """ |
2311 | # None |
2312 | if group is None: |
2313 | self.__data_list = [] |
2314 | - |
2315 | + |
2316 | # Int/float/long or Instance of Data |
2317 | elif type(group) in NUMTYPES or isinstance(group, Data): |
2318 | # Clean data_list |
2319 | self.__data_list = [] |
2320 | self.add_data(group) |
2321 | - |
2322 | + |
2323 | # One point |
2324 | elif type(group) is tuple and len(group) in (2,3): |
2325 | self.__data_list = [] |
2326 | self.add_data(group) |
2327 | - |
2328 | + |
2329 | # list of items |
2330 | elif type(group) in LISTTYPES and type(group[0]) is not list: |
2331 | # Clean data_list |
2332 | @@ -389,7 +389,7 @@ |
2333 | for item in group: |
2334 | # try to append and catch an exception |
2335 | self.add_data(item) |
2336 | - |
2337 | + |
2338 | # function lambda |
2339 | elif callable(group): |
2340 | # Explicit is better than implicit |
2341 | @@ -402,7 +402,7 @@ |
2342 | for x in self.range: |
2343 | #self.add_data((x,round(group(x),2))) |
2344 | self.add_data((x,function(x))) |
2345 | - |
2346 | + |
2347 | # Only have range in parent |
2348 | elif self.parent is not None and len(self.parent.range) is not 0: |
2349 | # Copy parent range |
2350 | @@ -413,12 +413,12 @@ |
2351 | for x in self.range: |
2352 | #self.add_data((x,round(group(x),2))) |
2353 | self.add_data((x,function(x))) |
2354 | - |
2355 | + |
2356 | # Don't have range anywhere |
2357 | else: |
2358 | # x_data don't exist |
2359 | raise Exception, "Data argument is valid but to use function type please set x_range first" |
2360 | - |
2361 | + |
2362 | # Coordinate Lists |
2363 | elif type(group) in LISTTYPES and type(group[0]) is list: |
2364 | # Clean data_list |
2365 | @@ -430,10 +430,10 @@ |
2366 | data = zip(group[0], group[1]) |
2367 | else: |
2368 | raise TypeError, "Only one list of coordinates was received." |
2369 | - |
2370 | + |
2371 | for item in data: |
2372 | self.add_data(item) |
2373 | - |
2374 | + |
2375 | else: |
2376 | raise TypeError, "Group type not supported" |
2377 | |
2378 | @@ -441,16 +441,16 @@ |
2379 | |
2380 | @apply |
2381 | def range(): |
2382 | - doc = ''' |
2383 | + doc = """ |
2384 | The range is a read/write property that generates a range of values |
2385 | for the x axis of the functions. When passed a tuple it almost works |
2386 | like the built-in range funtion: |
2387 | - 1 item, represent the end of the range started from 0; |
2388 | - 2 items, represents the start and the end, respectively; |
2389 | - 3 items, the last one represents the step; |
2390 | - |
2391 | + |
2392 | When passed a list the range function understands as a valid range. |
2393 | - |
2394 | + |
2395 | Usage: |
2396 | >>> g = Group(); g.range = 10; print g.range |
2397 | [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] |
2398 | @@ -465,21 +465,21 @@ |
2399 | [0.0] |
2400 | >>> g = Group(); g.range = [0,10,20]; print g.range |
2401 | [0.0, 10.0, 20.0] |
2402 | - ''' |
2403 | + """ |
2404 | def fget(self): |
2405 | - ''' |
2406 | + """ |
2407 | Returns the range |
2408 | - ''' |
2409 | + """ |
2410 | return self.__range |
2411 | - |
2412 | + |
2413 | def fset(self, x_range): |
2414 | - ''' |
2415 | + """ |
2416 | Controls the input of a valid type and generate the range |
2417 | - ''' |
2418 | + """ |
2419 | # if passed a simple number convert to tuple |
2420 | if type(x_range) in NUMTYPES: |
2421 | x_range = (x_range,) |
2422 | - |
2423 | + |
2424 | # A list, just convert to float |
2425 | if type(x_range) is list and len(x_range) > 0: |
2426 | # Convert all to float |
2427 | @@ -488,32 +488,32 @@ |
2428 | self.__range = list(set(x_range[:])) |
2429 | # Sort the list to ascending order |
2430 | self.__range.sort() |
2431 | - |
2432 | + |
2433 | # A tuple, must check the lengths and generate the values |
2434 | elif type(x_range) is tuple and len(x_range) in (1,2,3): |
2435 | # Convert all to float |
2436 | x_range = map(float, x_range) |
2437 | - |
2438 | + |
2439 | # Inital values |
2440 | start = 0.0 |
2441 | step = 1.0 |
2442 | end = 0.0 |
2443 | - |
2444 | + |
2445 | # Only the end and it can't be less or iqual to 0 |
2446 | if len(x_range) is 1 and x_range > 0: |
2447 | end = x_range[0] |
2448 | - |
2449 | + |
2450 | # The start and the end but the start must be less then the end |
2451 | elif len(x_range) is 2 and x_range[0] < x_range[1]: |
2452 | start = x_range[0] |
2453 | end = x_range[1] |
2454 | - |
2455 | + |
2456 | # All 3, but the start must be less then the end |
2457 | elif x_range[0] <= x_range[1]: |
2458 | start = x_range[0] |
2459 | end = x_range[1] |
2460 | step = x_range[2] |
2461 | - |
2462 | + |
2463 | # Starts the range |
2464 | self.__range = [] |
2465 | # Generate the range |
2466 | @@ -521,19 +521,19 @@ |
2467 | while start < end: |
2468 | self.__range.append(start) |
2469 | start += step |
2470 | - |
2471 | + |
2472 | # Incorrect type |
2473 | else: |
2474 | raise Exception, "x_range must be a list with one or more items or a tuple with 2 or 3 items" |
2475 | - |
2476 | + |
2477 | return property(**locals()) |
2478 | |
2479 | def add_data(self, data, name=None): |
2480 | - ''' |
2481 | + """ |
2482 | Append a new data to the data_list. |
2483 | - If data is an instance of Data, append it |
2484 | - If it's an int, float, tuple or list create an instance of Data and append it |
2485 | - |
2486 | + |
2487 | Usage: |
2488 | >>> g = Group() |
2489 | >>> g.add_data(12); print g |
2490 | @@ -546,24 +546,24 @@ |
2491 | ['a: (1, 1)'] |
2492 | >>> g.add_data((2,2),'b'); print g |
2493 | ['a: (1, 1)', 'b: (2, 2)'] |
2494 | - >>> |
2495 | + >>> |
2496 | >>> g.add_data(Data((1,2),'c')); print g |
2497 | ['a: (1, 1)', 'b: (2, 2)', 'c: (1, 2)'] |
2498 | - ''' |
2499 | + """ |
2500 | if not isinstance(data, Data): |
2501 | # Try to convert |
2502 | data = Data(data,name,self) |
2503 | - |
2504 | + |
2505 | if data.content is not None: |
2506 | self.__data_list.append(data.copy()) |
2507 | self.__data_list[-1].parent = self |
2508 | - |
2509 | + |
2510 | |
2511 | def to_list(self): |
2512 | - ''' |
2513 | + """ |
2514 | Returns the group as a list of numbers (int, float or long) or a |
2515 | list of tuples (points 2D or 3D). |
2516 | - |
2517 | + |
2518 | Usage: |
2519 | >>> g = Group([1,2,3,4],'g1'); g.to_list() |
2520 | [1, 2, 3, 4] |
2521 | @@ -571,13 +571,13 @@ |
2522 | [(1, 2), (2, 3), (3, 4)] |
2523 | >>> g = Group([(1,2,3),(3,4,5)],'g2'); g.to_list() |
2524 | [(1, 2, 3), (3, 4, 5)] |
2525 | - ''' |
2526 | + """ |
2527 | return [data.content for data in self] |
2528 | - |
2529 | + |
2530 | def copy(self): |
2531 | - ''' |
2532 | + """ |
2533 | Returns a copy of this group |
2534 | - ''' |
2535 | + """ |
2536 | new_group = Group() |
2537 | new_group.__name = self.__name |
2538 | if self.__range is not None: |
2539 | @@ -585,11 +585,11 @@ |
2540 | for data in self: |
2541 | new_group.add_data(data.copy()) |
2542 | return new_group |
2543 | - |
2544 | + |
2545 | def get_names(self): |
2546 | - ''' |
2547 | + """ |
2548 | Return a list with the names of all data in this group |
2549 | - ''' |
2550 | + """ |
2551 | names = [] |
2552 | for data in self: |
2553 | if data.name is None: |
2554 | @@ -597,12 +597,12 @@ |
2555 | else: |
2556 | names.append(data.name) |
2557 | return names |
2558 | - |
2559 | - |
2560 | + |
2561 | + |
2562 | def __str__ (self): |
2563 | - ''' |
2564 | + """ |
2565 | Returns a string representing the Group |
2566 | - ''' |
2567 | + """ |
2568 | ret = "" |
2569 | if self.name is not None: |
2570 | ret += self.name + " " |
2571 | @@ -612,25 +612,25 @@ |
2572 | else: |
2573 | ret += "[]" |
2574 | return ret |
2575 | - |
2576 | + |
2577 | def __getitem__(self, key): |
2578 | - ''' |
2579 | + """ |
2580 | Makes a Group iterable, based in the data_list property |
2581 | - ''' |
2582 | + """ |
2583 | return self.data_list[key] |
2584 | - |
2585 | + |
2586 | def __len__(self): |
2587 | - ''' |
2588 | + """ |
2589 | Returns the length of the Group, based in the data_list property |
2590 | - ''' |
2591 | + """ |
2592 | return len(self.data_list) |
2593 | |
2594 | |
2595 | class Colors(object): |
2596 | - ''' |
2597 | + """ |
2598 | Class that models the colors its labels (names) and its properties, RGB |
2599 | and filling type. |
2600 | - |
2601 | + |
2602 | It can receive: |
2603 | - A list where each item is a list with 3 or 4 items. The |
2604 | first 3 items represent the RGB values and the last argument |
2605 | @@ -640,19 +640,19 @@ |
2606 | - A dictionary where each key will be the color name and its item |
2607 | can be a list with 3 or 4 items. The first 3 items represent |
2608 | the RGB colors and the last argument defines the filling type. |
2609 | - ''' |
2610 | + """ |
2611 | def __init__(self, color_list=None): |
2612 | - ''' |
2613 | + """ |
2614 | Start the color_list property |
2615 | @ color_list - the list or dict contaning the colors properties. |
2616 | - ''' |
2617 | + """ |
2618 | self.__color_list = None |
2619 | - |
2620 | + |
2621 | self.color_list = color_list |
2622 | - |
2623 | + |
2624 | @apply |
2625 | def color_list(): |
2626 | - doc = ''' |
2627 | + doc = """ |
2628 | >>> c = Colors([[1,1,1],[2,2,2,'linear'],[3,3,3,'gradient']]) |
2629 | >>> print c.color_list |
2630 | {'Color 2': [2, 2, 2, 'linear'], 'Color 3': [3, 3, 3, 'gradient'], 'Color 1': [1, 1, 1, 'solid']} |
2631 | @@ -662,21 +662,21 @@ |
2632 | >>> c.color_list = {'a':[1,1,1],'b':(2,2,2,'solid'),'c':(3,3,3,'linear'), 'd':(4,4,4)} |
2633 | >>> print c.color_list |
2634 | {'a': [1, 1, 1, 'solid'], 'c': [3, 3, 3, 'linear'], 'b': [2, 2, 2, 'solid'], 'd': [4, 4, 4, 'solid']} |
2635 | - ''' |
2636 | + """ |
2637 | def fget(self): |
2638 | - ''' |
2639 | + """ |
2640 | Return the color list |
2641 | - ''' |
2642 | + """ |
2643 | return self.__color_list |
2644 | - |
2645 | + |
2646 | def fset(self, color_list): |
2647 | - ''' |
2648 | + """ |
2649 | Format the color list to a dictionary |
2650 | - ''' |
2651 | + """ |
2652 | if color_list is None: |
2653 | self.__color_list = None |
2654 | return |
2655 | - |
2656 | + |
2657 | if type(color_list) in LISTTYPES and type(color_list[0]) in LISTTYPES: |
2658 | old_color_list = color_list[:] |
2659 | color_list = {} |
2660 | @@ -689,7 +689,7 @@ |
2661 | raise TypeError, "Unsuported color format" |
2662 | elif type(color_list) is not dict: |
2663 | raise TypeError, "Unsuported color format" |
2664 | - |
2665 | + |
2666 | for name, color in color_list.items(): |
2667 | if len(color) is 3: |
2668 | if max(map(type, color)) in NUMTYPES: |
2669 | @@ -702,12 +702,12 @@ |
2670 | else: |
2671 | raise TypeError, "Unsuported color format" |
2672 | self.__color_list = color_list.copy() |
2673 | - |
2674 | + |
2675 | return property(**locals()) |
2676 | - |
2677 | - |
2678 | + |
2679 | + |
2680 | class Series(object): |
2681 | - ''' |
2682 | + """ |
2683 | Class that models a Series (group of groups). Every value (int, float, |
2684 | long, tuple or list) passed is converted to a list of Group or Data. |
2685 | It can receive: |
2686 | @@ -726,15 +726,15 @@ |
2687 | lists); |
2688 | - an instance of Data; |
2689 | - an instance of group. |
2690 | - ''' |
2691 | + """ |
2692 | def __init__(self, series=None, name=None, property=[], colors=None): |
2693 | - ''' |
2694 | + """ |
2695 | Starts main atributes in Group instance. |
2696 | @series - a list, dict of data of which the series is composed; |
2697 | @name - name of the series; |
2698 | @property - a list/dict of properties to be used in the plots of |
2699 | this Series |
2700 | - |
2701 | + |
2702 | Usage: |
2703 | >>> print Series([1,2,3,4]) |
2704 | ["Group 1 ['1', '2', '3', '4']"] |
2705 | @@ -758,26 +758,26 @@ |
2706 | ["Group 1 ['d1: 1']"] |
2707 | >>> print Series(Group([(1,2),(2,3)],'g1')) |
2708 | ["g1 ['(1, 2)', '(2, 3)']"] |
2709 | - ''' |
2710 | + """ |
2711 | # Intial values |
2712 | self.__group_list = [] |
2713 | self.__name = None |
2714 | self.__range = None |
2715 | - |
2716 | + |
2717 | # TODO: Implement colors with filling |
2718 | self.__colors = None |
2719 | - |
2720 | + |
2721 | self.name = name |
2722 | self.group_list = series |
2723 | self.colors = colors |
2724 | - |
2725 | + |
2726 | # Name property |
2727 | @apply |
2728 | def name(): |
2729 | - doc = ''' |
2730 | + doc = """ |
2731 | Name is a read/write property that controls the input of name. |
2732 | - If passed an invalid value it cleans the name with None |
2733 | - |
2734 | + |
2735 | Usage: |
2736 | >>> s = Series(13); s.name = 'name_test'; print s |
2737 | name_test ["Group 1 ['13']"] |
2738 | @@ -791,30 +791,30 @@ |
2739 | last_name ["Group 1 ['13']"] |
2740 | >>> s.name = ''; print s |
2741 | ["Group 1 ['13']"] |
2742 | - ''' |
2743 | + """ |
2744 | def fget(self): |
2745 | - ''' |
2746 | + """ |
2747 | Returns the name as a string |
2748 | - ''' |
2749 | + """ |
2750 | return self.__name |
2751 | - |
2752 | + |
2753 | def fset(self, name): |
2754 | - ''' |
2755 | + """ |
2756 | Sets the name of the Group |
2757 | - ''' |
2758 | + """ |
2759 | if type(name) in STRTYPES and len(name) > 0: |
2760 | self.__name = name |
2761 | else: |
2762 | self.__name = None |
2763 | - |
2764 | + |
2765 | return property(**locals()) |
2766 | - |
2767 | - |
2768 | - |
2769 | + |
2770 | + |
2771 | + |
2772 | # Colors property |
2773 | @apply |
2774 | def colors(): |
2775 | - doc = ''' |
2776 | + doc = """ |
2777 | >>> s = Series() |
2778 | >>> s.colors = [[1,1,1],[2,2,2,'linear'],[3,3,3,'gradient']] |
2779 | >>> print s.colors |
2780 | @@ -825,33 +825,33 @@ |
2781 | >>> s.colors = {'a':[1,1,1],'b':(2,2,2,'solid'),'c':(3,3,3,'linear'), 'd':(4,4,4)} |
2782 | >>> print s.colors |
2783 | {'a': [1, 1, 1, 'solid'], 'c': [3, 3, 3, 'linear'], 'b': [2, 2, 2, 'solid'], 'd': [4, 4, 4, 'solid']} |
2784 | - ''' |
2785 | + """ |
2786 | def fget(self): |
2787 | - ''' |
2788 | + """ |
2789 | Return the color list |
2790 | - ''' |
2791 | + """ |
2792 | return self.__colors.color_list |
2793 | - |
2794 | + |
2795 | def fset(self, colors): |
2796 | - ''' |
2797 | + """ |
2798 | Format the color list to a dictionary |
2799 | - ''' |
2800 | + """ |
2801 | self.__colors = Colors(colors) |
2802 | - |
2803 | + |
2804 | return property(**locals()) |
2805 | - |
2806 | + |
2807 | @apply |
2808 | def range(): |
2809 | - doc = ''' |
2810 | + doc = """ |
2811 | The range is a read/write property that generates a range of values |
2812 | for the x axis of the functions. When passed a tuple it almost works |
2813 | like the built-in range funtion: |
2814 | - 1 item, represent the end of the range started from 0; |
2815 | - 2 items, represents the start and the end, respectively; |
2816 | - 3 items, the last one represents the step; |
2817 | - |
2818 | + |
2819 | When passed a list the range function understands as a valid range. |
2820 | - |
2821 | + |
2822 | Usage: |
2823 | >>> s = Series(); s.range = 10; print s.range |
2824 | [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] |
2825 | @@ -866,21 +866,21 @@ |
2826 | [0.0] |
2827 | >>> s = Series(); s.range = [0,10,20]; print s.range |
2828 | [0.0, 10.0, 20.0] |
2829 | - ''' |
2830 | + """ |
2831 | def fget(self): |
2832 | - ''' |
2833 | + """ |
2834 | Returns the range |
2835 | - ''' |
2836 | + """ |
2837 | return self.__range |
2838 | - |
2839 | + |
2840 | def fset(self, x_range): |
2841 | - ''' |
2842 | + """ |
2843 | Controls the input of a valid type and generate the range |
2844 | - ''' |
2845 | + """ |
2846 | # if passed a simple number convert to tuple |
2847 | if type(x_range) in NUMTYPES: |
2848 | x_range = (x_range,) |
2849 | - |
2850 | + |
2851 | # A list, just convert to float |
2852 | if type(x_range) is list and len(x_range) > 0: |
2853 | # Convert all to float |
2854 | @@ -889,32 +889,32 @@ |
2855 | self.__range = list(set(x_range[:])) |
2856 | # Sort the list to ascending order |
2857 | self.__range.sort() |
2858 | - |
2859 | + |
2860 | # A tuple, must check the lengths and generate the values |
2861 | elif type(x_range) is tuple and len(x_range) in (1,2,3): |
2862 | # Convert all to float |
2863 | x_range = map(float, x_range) |
2864 | - |
2865 | + |
2866 | # Inital values |
2867 | start = 0.0 |
2868 | step = 1.0 |
2869 | end = 0.0 |
2870 | - |
2871 | + |
2872 | # Only the end and it can't be less or iqual to 0 |
2873 | if len(x_range) is 1 and x_range > 0: |
2874 | end = x_range[0] |
2875 | - |
2876 | + |
2877 | # The start and the end but the start must be lesser then the end |
2878 | elif len(x_range) is 2 and x_range[0] < x_range[1]: |
2879 | start = x_range[0] |
2880 | end = x_range[1] |
2881 | - |
2882 | + |
2883 | # All 3, but the start must be lesser then the end |
2884 | elif x_range[0] < x_range[1]: |
2885 | start = x_range[0] |
2886 | end = x_range[1] |
2887 | step = x_range[2] |
2888 | - |
2889 | + |
2890 | # Starts the range |
2891 | self.__range = [] |
2892 | # Generate the range |
2893 | @@ -922,16 +922,16 @@ |
2894 | while start <= end: |
2895 | self.__range.append(start) |
2896 | start += step |
2897 | - |
2898 | + |
2899 | # Incorrect type |
2900 | else: |
2901 | raise Exception, "x_range must be a list with one or more item or a tuple with 2 or 3 items" |
2902 | - |
2903 | + |
2904 | return property(**locals()) |
2905 | - |
2906 | + |
2907 | @apply |
2908 | def group_list(): |
2909 | - doc = ''' |
2910 | + doc = """ |
2911 | The group_list is a read/write property used to pre-process the list |
2912 | of Groups. |
2913 | It can be: |
2914 | @@ -952,7 +952,7 @@ |
2915 | (coordinated lists) or lambdas |
2916 | - an instance of Data; |
2917 | - an instance of group. |
2918 | - |
2919 | + |
2920 | Usage: |
2921 | >>> s = Series() |
2922 | >>> s.group_list = [1,2,3,4]; print s |
2923 | @@ -985,48 +985,48 @@ |
2924 | ["Group 1 ['d1: 1']"] |
2925 | >>> s.group_list = Group([(1,2),(2,3)],'g1'); print s |
2926 | ["g1 ['(1, 2)', '(2, 3)']"] |
2927 | - ''' |
2928 | + """ |
2929 | def fget(self): |
2930 | - ''' |
2931 | + """ |
2932 | Return the group list. |
2933 | - ''' |
2934 | + """ |
2935 | return self.__group_list |
2936 | - |
2937 | + |
2938 | def fset(self, series): |
2939 | - ''' |
2940 | + """ |
2941 | Controls the input of a valid group list. |
2942 | - ''' |
2943 | + """ |
2944 | #TODO: Add support to the following strem of data: [ (0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,9)] |
2945 | - |
2946 | + |
2947 | # Type: None |
2948 | if series is None: |
2949 | self.__group_list = [] |
2950 | - |
2951 | + |
2952 | # List or Tuple |
2953 | elif type(series) in LISTTYPES: |
2954 | self.__group_list = [] |
2955 | - |
2956 | + |
2957 | is_function = lambda x: callable(x) |
2958 | # Groups |
2959 | if list in map(type, series) or max(map(is_function, series)): |
2960 | for group in series: |
2961 | self.add_group(group) |
2962 | - |
2963 | + |
2964 | # single group |
2965 | else: |
2966 | self.add_group(series) |
2967 | - |
2968 | + |
2969 | #old code |
2970 | ## List of numbers |
2971 | #if type(series[0]) in NUMTYPES or type(series[0]) is tuple: |
2972 | # print series |
2973 | # self.add_group(series) |
2974 | - # |
2975 | + # |
2976 | ## List of anything else |
2977 | #else: |
2978 | # for group in series: |
2979 | # self.add_group(group) |
2980 | - |
2981 | + |
2982 | # Dict representing series of groups |
2983 | elif type(series) is dict: |
2984 | self.__group_list = [] |
2985 | @@ -1034,72 +1034,72 @@ |
2986 | names.sort() |
2987 | for name in names: |
2988 | self.add_group(Group(series[name],name,self)) |
2989 | - |
2990 | + |
2991 | # A single lambda |
2992 | elif callable(series): |
2993 | self.__group_list = [] |
2994 | self.add_group(series) |
2995 | - |
2996 | + |
2997 | # Int/float, instance of Group or Data |
2998 | elif type(series) in NUMTYPES or isinstance(series, Group) or isinstance(series, Data): |
2999 | self.__group_list = [] |
3000 | self.add_group(series) |
3001 | - |
3002 | + |
3003 | # Default |
3004 | else: |
3005 | raise TypeError, "Serie type not supported" |
3006 | |
3007 | return property(**locals()) |
3008 | - |
3009 | + |
3010 | def add_group(self, group, name=None): |
3011 | - ''' |
3012 | + """ |
3013 | Append a new group in group_list |
3014 | - ''' |
3015 | + """ |
3016 | if not isinstance(group, Group): |
3017 | #Try to convert |
3018 | group = Group(group, name, self) |
3019 | - |
3020 | + |
3021 | if len(group.data_list) is not 0: |
3022 | # Auto naming groups |
3023 | if group.name is None: |
3024 | group.name = "Group "+str(len(self.__group_list)+1) |
3025 | - |
3026 | + |
3027 | self.__group_list.append(group) |
3028 | self.__group_list[-1].parent = self |
3029 | - |
3030 | + |
3031 | def copy(self): |
3032 | - ''' |
3033 | + """ |
3034 | Returns a copy of the Series |
3035 | - ''' |
3036 | + """ |
3037 | new_series = Series() |
3038 | new_series.__name = self.__name |
3039 | if self.__range is not None: |
3040 | new_series.__range = self.__range[:] |
3041 | #Add color property in the copy method |
3042 | #self.__colors = None |
3043 | - |
3044 | + |
3045 | for group in self: |
3046 | new_series.add_group(group.copy()) |
3047 | - |
3048 | + |
3049 | return new_series |
3050 | - |
3051 | + |
3052 | def get_names(self): |
3053 | - ''' |
3054 | + """ |
3055 | Returns a list of the names of all groups in the Serie |
3056 | - ''' |
3057 | + """ |
3058 | names = [] |
3059 | for group in self: |
3060 | if group.name is None: |
3061 | names.append('Group '+str(group.index()+1)) |
3062 | else: |
3063 | names.append(group.name) |
3064 | - |
3065 | + |
3066 | return names |
3067 | - |
3068 | + |
3069 | def to_list(self): |
3070 | - ''' |
3071 | + """ |
3072 | Returns a list with the content of all groups and data |
3073 | - ''' |
3074 | + """ |
3075 | big_list = [] |
3076 | for group in self: |
3077 | for data in group: |
3078 | @@ -1110,15 +1110,15 @@ |
3079 | return big_list |
3080 | |
3081 | def __getitem__(self, key): |
3082 | - ''' |
3083 | + """ |
3084 | Makes the Series iterable, based in the group_list property |
3085 | - ''' |
3086 | + """ |
3087 | return self.__group_list[key] |
3088 | - |
3089 | + |
3090 | def __str__(self): |
3091 | - ''' |
3092 | + """ |
3093 | Returns a string that represents the Series |
3094 | - ''' |
3095 | + """ |
3096 | ret = "" |
3097 | if self.name is not None: |
3098 | ret += self.name + " " |
3099 | @@ -1128,13 +1128,13 @@ |
3100 | else: |
3101 | ret += "[]" |
3102 | return ret |
3103 | - |
3104 | + |
3105 | def __len__(self): |
3106 | - ''' |
3107 | + """ |
3108 | Returns the length of the Series, based in the group_lsit property |
3109 | - ''' |
3110 | + """ |
3111 | return len(self.group_list) |
3112 | - |
3113 | + |
3114 | |
3115 | if __name__ == '__main__': |
3116 | doctest.testmod() |
3117 | |
3118 | === added file 'trunk/gtktests.py' |
3119 | --- trunk/gtktests.py 1970-01-01 00:00:00 +0000 |
3120 | +++ trunk/gtktests.py 2010-04-07 05:06:25 +0000 |
3121 | @@ -0,0 +1,32 @@ |
3122 | + |
3123 | +import gtk |
3124 | +import cairoplot |
3125 | +from cairoplot.handlers.gtk import GTKHandler |
3126 | + |
3127 | +class CairoPlotWindow(gtk.Window): |
3128 | + """GtkWindow to display a plot.""" |
3129 | + |
3130 | + def __init__(self): |
3131 | + """Make a plot to test.""" |
3132 | + gtk.Window.__init__(self) |
3133 | + self.connect("destroy", gtk.main_quit) |
3134 | + |
3135 | + # make plot handler |
3136 | + handler = GTKHandler() |
3137 | + |
3138 | + # default data |
3139 | + data = [ (-2,10), (0,0), (0,15), (1,5), (2,0), (3,-10), (3,5) ] |
3140 | + plot = cairoplot.ScatterPlot(handler, data=data, |
3141 | + width=500, height=500, background="white", |
3142 | + border=20, axis=True, grid=True) |
3143 | + |
3144 | + handler.plot = plot |
3145 | + self.add(handler) |
3146 | + handler.show() |
3147 | + |
3148 | + |
3149 | +if __name__ == "__main__": |
3150 | + window = CairoPlotWindow() |
3151 | + window.show() |
3152 | + gtk.main() |
3153 | + |
3154 | |
3155 | === modified file 'trunk/seriestests.py' |
3156 | --- trunk/seriestests.py 2009-07-09 21:57:24 +0000 |
3157 | +++ trunk/seriestests.py 2010-04-07 05:06:25 +0000 |
3158 | @@ -1,7 +1,7 @@ |
3159 | import cairo, math, random |
3160 | |
3161 | import cairoplot |
3162 | -from series import Series |
3163 | +from cairoplot.series import Series |
3164 | |
3165 | # Line plotting |
3166 | test_scatter_plot = 1 |