Merge lp:~tswast/cairoplot/trunk into lp:cairoplot

Proposed by Tim Swast
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
Reviewer Review Type Date Requested Status
Rodrigo Moreira Araújo Pending
Review via email: mp+20838@code.launchpad.net

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.

To post a comment you must log in.
lp:~tswast/cairoplot/trunk updated
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches