~alf-rodrigo/cairoplot/trunk

« back to all changes in this revision

Viewing changes to trunk/CairoPlot.py

  • Committer: Rodrigo Araujo
  • Date: 2008-07-04 14:42:00 UTC
  • Revision ID: alf.rodrigo@gmail.com-20080704144200-9q1e04xtcdg34dhw
Initial Commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: utf-8 -*-
 
3
 
 
4
# CairoPlot.py
 
5
#
 
6
# Copyright (c) 2008 Rodrigo Moreira Araújo
 
7
#
 
8
# Author: Rodrigo Moreiro Araujo <alf.rodrigo@gmail.com>
 
9
#
 
10
# This program is free software; you can redistribute it and/or
 
11
# modify it under the terms of the GNU Lesser General Public License
 
12
# as published by the Free Software Foundation; either version 2 of
 
13
# the License, or (at your option) any later version.
 
14
#
 
15
# This program is distributed in the hope that it will be useful,
 
16
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
18
# GNU General Public License for more details.
 
19
#
 
20
# You should have received a copy of the GNU Lesser General Public
 
21
# License along with this program; if not, write to the Free Software
 
22
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 
23
# USA
 
24
 
 
25
#Contributor: João S. O. Bueno
 
26
 
 
27
__version__ = 1.1
 
28
 
 
29
import cairo
 
30
import math
 
31
import random
 
32
 
 
33
HORZ = 0
 
34
VERT = 1
 
35
 
 
36
def other_direction(direction):
 
37
    "explicit is better than implicit"
 
38
    if direction == HORZ:
 
39
        return VERT
 
40
    else:
 
41
        return HORZ
 
42
 
 
43
class Plot(object):
 
44
    def __init__(self, 
 
45
                 surface=None,
 
46
                 data=None,
 
47
                 width=640,
 
48
                 height=480,
 
49
                 background=None,
 
50
                 border = 0,
 
51
                 h_labels = None,
 
52
                 v_labels = None):
 
53
        self.create_surface(surface, width, height)
 
54
        self.width = width
 
55
        self.height = height
 
56
        self.context = cairo.Context(self.surface)
 
57
        self.load_series(data, h_labels, v_labels)
 
58
 
 
59
        self.labels={}
 
60
        self.labels[HORZ] = h_labels
 
61
        self.labels[VERT] = v_labels
 
62
 
 
63
        self.font_size = 10
 
64
        
 
65
        self.set_background (background)
 
66
        self.border = 0
 
67
        self.borders = {}
 
68
        
 
69
        self.line_color = (0.5, 0.5, 0.5)
 
70
        self.line_width = 0.5
 
71
        self.label_color = (0.0, 0.0, 0.0)
 
72
        self.grid_color = (0.8, 0.8, 0.8)
 
73
        
 
74
    
 
75
    def create_surface(self, surface, width=None, height=None):
 
76
        self.filename = None
 
77
        if isinstance(surface, cairo.Surface):
 
78
            self.surface = surface
 
79
            return
 
80
        if not type(surface) in (str, unicode): 
 
81
            raise TypeError("Surface should be either a Cairo surface or a filename, not %s" % surface)
 
82
        sufix = surface.rsplit(".")[-1].lower()
 
83
        self.filename = surface
 
84
        if sufix == "png":
 
85
            self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
 
86
        elif sufix == "ps":
 
87
            self.surface = cairo.PSSurface(surface, width, height)
 
88
        elif sufix == "pdf":
 
89
            self.surface = cairo.PSSurface(surface, width, height)
 
90
        else:
 
91
            if sufix != "svg":
 
92
                self.filename += ".svg"
 
93
            self.surface = cairo.SVGSurface(self.filename, width, height)
 
94
    
 
95
    #def __del__(self):
 
96
    #    self.commit()
 
97
 
 
98
    def commit(self):
 
99
        try:
 
100
            self.context.show_page()
 
101
            if self.filename.endswith(".png"):
 
102
                self.surface.write_to_png(self.filename)
 
103
            else:
 
104
                self.surface.finish()
 
105
        except cairo.Error:
 
106
            pass
 
107
        
 
108
    def load_series (self, data, h_labels=None, v_labels=None):
 
109
        #FIXME: implement Series class for holding series data,
 
110
        # labels and presentation properties
 
111
        
 
112
        #data can be a list, a list of lists or a dictionary with 
 
113
        #each item as a labeled data series.
 
114
        #we should (for teh time being) create a list of lists
 
115
        #and set labels for teh series rom  teh values provided.
 
116
        
 
117
        self.series_labels = []
 
118
        self.data = []
 
119
        #if we have labeled series:
 
120
        if hasattr(data, "keys"):
 
121
            #dictionary:
 
122
            self.series_labels = data.keys()
 
123
            for key in self.series_labels:
 
124
                self.data.append(data[key])
 
125
        #if we have a series of series:
 
126
        elif hasattr(data[0], "__getitem__"):
 
127
            self.data = data
 
128
            self.series_labels = range(len(data))
 
129
        else:
 
130
            self.data = [data]
 
131
            self.series_labels = None
 
132
        #FIXME: select some pre-sets and allow these to be parametrized:
 
133
        random.seed(1)
 
134
        self.series_colors = [[random.random() for i in range(3)]  for series in self.data]
 
135
        self.series_widths = [1.0 for series in self.data]
 
136
 
 
137
    def get_width(self):
 
138
        return self.surface.get_width()
 
139
    def get_height(self):
 
140
        return self.surface.get_height()
 
141
 
 
142
    def set_background(self, background):
 
143
        if background is None:
 
144
            self.background = cairo.LinearGradient(self.width / 2, 0, self.width / 2, self.height)
 
145
            self.background.add_color_stop_rgb(0,1.0,1.0,1.0)
 
146
            self.background.add_color_stop_rgb(1.0,0.9,0.9,0.9)
 
147
        else:
 
148
            if type(background) in (cairo.LinearGradient, tuple):
 
149
                self.background = background
 
150
            else:
 
151
                raise TypeError ("Background should be either cairo.LinearGradient or a 3-tuple, not %s" % type(background))
 
152
        
 
153
    def render_background(self):
 
154
        if isinstance (self.background, cairo.LinearGradient):
 
155
            self.context.set_source(self.background)
 
156
        else:
 
157
            self.context.set_source_rgb(*self.background)
 
158
        self.context.rectangle(0,0, self.width, self.height)
 
159
        self.context.fill()
 
160
        
 
161
    def render_bounding_box(self):
 
162
        self.context.set_source_rgb(*self.line_color)
 
163
        self.context.set_line_width(self.line_width)
 
164
        self.context.rectangle(self.border, self.border,
 
165
                               self.width - 2 * self.border,
 
166
                               self.height - 2 * self.border)
 
167
 
 
168
    def render(self):
 
169
        pass
 
170
 
 
171
class DotLinePlot(Plot):
 
172
    def __init__(self, 
 
173
                 surface=None,
 
174
                 data=None,
 
175
                 width=640,
 
176
                 height=480,
 
177
                 background=None,
 
178
                 border=0, 
 
179
                 axis = False,
 
180
                 grid = False,
 
181
                 h_labels = None,
 
182
                 v_labels = None,
 
183
                 h_bounds = None,
 
184
                 v_bounds = None):
 
185
        
 
186
        self.bounds = {}
 
187
        self.bounds[HORZ] = h_bounds
 
188
        self.bounds[VERT] = v_bounds
 
189
        
 
190
        Plot.__init__(self, surface, data, width, height, background, border, h_labels, v_labels)
 
191
        self.axis = axis
 
192
        self.grid = grid
 
193
 
 
194
        self.max_value = {}
 
195
        
 
196
        self.h_label_angle = math.pi / 2.5
 
197
 
 
198
    def load_series(self, data, h_labels = None, v_labels = None):
 
199
        Plot.load_series(self, data, h_labels, v_labels)
 
200
        self.calc_boundaries()
 
201
    
 
202
    def calc_boundaries(self):
 
203
        
 
204
        if not self.bounds[HORZ]:
 
205
            self.bounds[HORZ] = (0, max([len(series) for series in (self.data)]))
 
206
            
 
207
        if not self.bounds[VERT]:
 
208
            max_data_value = min_data_value = 0
 
209
            for series in self.data:
 
210
                if max(series) > max_data_value:
 
211
                    max_data_value = max(series)
 
212
                if min(series) < min_data_value:
 
213
                    min_data_value = min(series)
 
214
            self.bounds[VERT] = (min_data_value, max_data_value)
 
215
 
 
216
 
 
217
    def calc_extents(self, direction):
 
218
 
 
219
        self.max_value[direction] = 0
 
220
        if self.labels[direction]:
 
221
            widest_word = max(self.labels[direction], lambda item: self.context.text_extents(item)[2])
 
222
            self.max_value[direction] = self.context.text_extents(widest_word)[2]
 
223
            self.borders[other_direction(direction)] = self.max_value[direction] + self.border
 
224
        else:
 
225
            self.max_value[direction] = self.context.text_extents(str(self.bounds[direction][1]))[2]
 
226
            self.borders[other_direction(direction)] = self.max_value[direction] + self.border + 20
 
227
            
 
228
    def calc_horz_extents(self):
 
229
        self.calc_extents(HORZ)
 
230
        
 
231
    def calc_vert_extents(self):
 
232
        self.calc_extents(VERT)
 
233
        
 
234
    
 
235
    def render_axis(self):
 
236
        cr = self.context
 
237
        h_border = self.borders[HORZ]
 
238
        v_border = self.borders[VERT]
 
239
        cr.set_source_rgb(*self.line_color)
 
240
 
 
241
        cr.move_to(h_border, self.height - v_border)
 
242
        cr.line_to(h_border, v_border)
 
243
        cr.stroke()
 
244
 
 
245
        cr.move_to(h_border, self.height - v_border)
 
246
        cr.line_to(self.width - h_border, self.height - v_border)
 
247
        cr.stroke()
 
248
    
 
249
    def render_labels(self):
 
250
 
 
251
        self.context.set_font_size(self.font_size * 0.8)
 
252
        
 
253
        self.render_horz_labels()
 
254
        self.render_vert_labels()
 
255
    
 
256
    def render_horz_labels(self):
 
257
        cr = self.context
 
258
        labels = self.labels[HORZ]
 
259
        if not labels:
 
260
            labels = [str(i) for i in range(self.bounds[HORZ][0], self.bounds[HORZ][1])]
 
261
        border = self.borders[HORZ]
 
262
        
 
263
        step = (self.width - 2 * border) / len(labels)
 
264
        x = border
 
265
        for item in labels:
 
266
            cr.set_source_rgb(*self.label_color)
 
267
            width = cr.text_extents(item)[2]
 
268
            cr.move_to(x, self.height - self.borders[VERT] + 10)
 
269
            cr.rotate(self.h_label_angle)
 
270
            cr.show_text(item)
 
271
            cr.rotate(-self.h_label_angle)
 
272
            #FIXME: render grid in a separate method
 
273
            if self.grid and x != border:
 
274
                cr.set_source_rgb(*self.grid_color)
 
275
                cr.move_to(x, self.height - self.borders[VERT])
 
276
                cr.line_to(x, self.borders[VERT])
 
277
                cr.stroke()
 
278
            x += step
 
279
    
 
280
    def render_vert_labels(self):
 
281
        cr = self.context
 
282
        labels = self.labels[VERT]
 
283
        if not labels:
 
284
            amplitude = self.bounds[VERT][1] - self.bounds[VERT][0]
 
285
            #if vertical labels need decimal points
 
286
            if amplitude % 10:
 
287
                label_type = lambda x : int(x)
 
288
            else:
 
289
                label_type = lambda x: float(x)
 
290
            labels = [str(
 
291
                        label_type(
 
292
                            self.bounds[VERT][0] +
 
293
                            (amplitude * i / 10.0)
 
294
                                  )                
 
295
                         ) for i in range(10) ]
 
296
        border = self.borders[VERT]
 
297
        
 
298
        step = (self.height - 2 * border)/ len(labels)
 
299
        y = self.height - border
 
300
        for item in labels:
 
301
            cr.set_source_rgb(*self.label_color)
 
302
            width = cr.text_extents(item)[2]
 
303
            cr.move_to(self.borders[HORZ] - width - 5,y)
 
304
            cr.show_text(item)
 
305
            #FIXME: render grid in a separate method
 
306
            if self.grid and y != self.height - border:
 
307
                cr.set_source_rgb(*self.grid_color)
 
308
                cr.move_to(self.borders[HORZ], y)
 
309
                cr.line_to(self.width - self.borders[HORZ], y)
 
310
                cr.stroke()
 
311
            y -=step
 
312
    
 
313
    
 
314
    def render(self):
 
315
        self.calc_horz_extents()
 
316
        self.calc_vert_extents()
 
317
            
 
318
        self.render_background()
 
319
        self.render_bounding_box()
 
320
        
 
321
        if self.axis:
 
322
            self.render_axis()
 
323
 
 
324
        self.render_labels()
 
325
        
 
326
        self.render_plot()
 
327
        
 
328
    def render_series_labels(self):
 
329
        #FIXME: implement this
 
330
        for key in self.series_labels:
 
331
            pass
 
332
            #This was not working in Rodrigo's original code anyway 
 
333
 
 
334
    def render_plot(self):
 
335
        #render_series_labels
 
336
        largest_series_length = len(max(self.data, key=len))
 
337
        #FIXME: plot_width and plot_height should be object properties and be re-used.
 
338
        plot_width = self.width - 2* self.borders[HORZ]
 
339
        plot_height = self.height - 2 * self.borders[VERT]
 
340
        plot_top = self.height - self.borders[VERT]
 
341
        
 
342
        series_amplitude = self.bounds[VERT][1] - self.bounds[VERT][0]
 
343
        
 
344
        horizontal_step = float (plot_width) / largest_series_length
 
345
        vertical_step = float (plot_height) / series_amplitude
 
346
        last = None
 
347
        cr = self.context
 
348
        for number, series in  enumerate (self.data):
 
349
            cr.set_source_rgb(*self.series_colors[number])
 
350
            x = self.borders[HORZ]
 
351
            last = None
 
352
            #FIXME: separate plotting of lines, dots and area
 
353
 
 
354
            for value in series:
 
355
                if last != None:
 
356
                    cr.move_to(x - horizontal_step, plot_top - int(last * vertical_step))
 
357
                    cr.line_to(x, plot_top - int(value * vertical_step))
 
358
                    cr.set_line_width(self.series_widths[number])
 
359
                    cr.stroke()
 
360
                cr.new_path()
 
361
                cr.arc(x, plot_top - int(value * vertical_step), 3, 0, 2.1 * math.pi)
 
362
                cr.close_path()
 
363
                cr.fill()
 
364
                x += horizontal_step
 
365
                last = value
 
366
 
 
367
 
 
368
        
 
369
 
 
370
def dot_line_plot(name,
 
371
                  data,
 
372
                  width,
 
373
                  height,
 
374
                  background = None,
 
375
                  border = 0,
 
376
                  axis = False,
 
377
                  grid = False,
 
378
                  h_legend = None,
 
379
                  v_legend = None,
 
380
                  h_bounds = None,
 
381
                  v_bounds = None):
 
382
    '''
 
383
        Function to plot graphics using dots and lines.
 
384
        dot_line_plot (name, data, width, height, background = None, border = 0, axis = False, grid = False, h_legend = None, v_legend = None, h_bounds = None, v_bounds = None)
 
385
 
 
386
        Parameters
 
387
 
 
388
        name - Name of the desired output file, no need to input the .svg as it will be added at runtim;
 
389
        data - The list, list of lists or dictionary holding the data to be plotted;
 
390
        width, height - Dimensions of the output image;
 
391
        background - A 3 element tuple representing the rgb color expected for the background. If left None, a gray to white gradient will be generated;
 
392
        border - Distance in pixels of a square border into which the graphics will be drawn;
 
393
        axis - Whether or not the axis are to be drawn;
 
394
        grid - Whether or not the gris is to be drawn;
 
395
        h_legend, v_legend - lists of strings containing the horizontal and vertical legends for the axis;
 
396
        h_bounds, v_bounds - tuples containing the lower and upper value bounds for the data to be plotted.
 
397
 
 
398
        Examples of use
 
399
 
 
400
        teste_data = [0, 1, 3, 8, 9, 0, 10, 10, 2, 1]
 
401
        CairoPlot.dot_line_plot('teste', teste_data, 400, 300)
 
402
        
 
403
        teste_data_2 = {"john" : [10, 10, 10, 10, 30], "mary" : [0, 0, 3, 5, 15], "philip" : [13, 32, 11, 25, 2]}
 
404
        teste_h_legend = ["jan/2008", "feb/2008", "mar/2008", "apr/2008", "may/2008"]
 
405
        CairoPlot.dot_line_plot('teste2', teste_data_2, 400, 300, axis = True, grid = True, h_legend = teste_h_legend)
 
406
    '''
 
407
    plot = DotLinePlot(name, data, width, height, background, border,
 
408
                       axis, grid, h_legend, v_legend, h_bounds, v_bounds)
 
409
    plot.render()
 
410
    plot.commit()
 
411
 
 
412
 
 
413
 
 
414
def pizza_plot(name, data, width, height, background = None):
 
415
 
 
416
    '''
 
417
        Function to plot pizza graphics.
 
418
        pizza_plot(name, data, width, height, background = None)
 
419
 
 
420
        Parameters
 
421
        
 
422
        name - Name of the desired output file, no need to input the .svg as it will be added at runtim;
 
423
        data - The list, list of lists or dictionary holding the data to be plotted;
 
424
        width, height - Dimensions of the output image;
 
425
        background - A 3 element tuple representing the rgb color expected for the background. If left None, a gray to white gradient will be generated;
 
426
 
 
427
        Examples of use
 
428
        
 
429
        teste_data = {"john" : 123, "mary" : 489, "philip" : 890 , "suzy" : 235}
 
430
        CairoPlot.pizza_plot("pizza_teste", teste_data, 500, 500)
 
431
        
 
432
    '''
 
433
 
 
434
    surface = cairo.SVGSurface(name + '.svg', width, height)
 
435
    print width, height
 
436
    cr = cairo.Context(surface)
 
437
 
 
438
    if background != None:
 
439
        cr.set_source_rgb(background[0], background[1], background[2])
 
440
        cr.rectangle(0,0,width,height)
 
441
        cr.fill()
 
442
    else:
 
443
        linear = cairo.LinearGradient(width/2, 0, width/2, height)
 
444
        linear.add_color_stop_rgb(0,1.0,1.0,1.0)
 
445
        linear.add_color_stop_rgb(1.0,0.9,0.9,0.9)
 
446
        cr.set_source(linear)
 
447
        cr.rectangle(0,0, width, height)
 
448
        cr.fill()
 
449
 
 
450
    angle = 0
 
451
    next_angle = 0
 
452
    cr.set_line_width(2.0)
 
453
    x0 = width/2
 
454
    y0 = height/2
 
455
    n = 0
 
456
    for key in data.keys():
 
457
        n += data[key]
 
458
 
 
459
    for key in data.keys():
 
460
        next_angle = angle + 2.0*math.pi*data[key]/n
 
461
    
 
462
        radius = width/3 if width < height else height/3
 
463
        print radius
 
464
        cr.set_source_rgb(random.random(), random.random(), random.random())
 
465
    
 
466
        w = cr.text_extents(key)[2]
 
467
        if (angle + next_angle)/2 < math.pi/2 or (angle + next_angle)/2 > 3*math.pi/2:
 
468
            cr.move_to(x0 + (radius+10)*math.cos((angle+next_angle)/2), y0 + (radius+10)*math.sin((angle+next_angle)/2) )
 
469
        else:
 
470
            cr.move_to(x0 + (radius+10)*math.cos((angle+next_angle)/2) - w, y0 + (radius+10)*math.sin((angle+next_angle)/2) )
 
471
        cr.show_text(key)
 
472
 
 
473
        cr.move_to(x0,y0)
 
474
        cr.line_to(x0+radius*math.cos(angle), y0+radius*math.sin(angle))
 
475
        cr.arc(x0, y0, radius, angle, angle + 2.0*math.pi*data[key]/n)
 
476
        cr.line_to(x0,y0)
 
477
        cr.close_path()
 
478
        cr.fill()
 
479
        cr.set_source_rgb(1.0, 1.0, 1.0)
 
480
        cr.move_to(x0,y0)
 
481
        cr.line_to(x0+radius*math.cos(angle), y0+radius*math.sin(angle))
 
482
        cr.arc(x0, y0, radius, angle, angle + 2.0*math.pi*data[key]/n)
 
483
        cr.line_to(x0,y0)
 
484
        cr.close_path()
 
485
        cr.stroke()
 
486
        angle = next_angle
 
487
 
 
488
 
 
489
def drawRectangle(cr, x0, y0, x1, y1, color):
 
490
    mid = (x0+x1)/2
 
491
    linear = cairo.LinearGradient(mid,y0,mid,y1)
 
492
    linear.add_color_stop_rgb(0,3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0)
 
493
    linear.add_color_stop_rgb(1,color[0],color[1],color[2])
 
494
    cr.set_source(linear)
 
495
 
 
496
    cr.arc(x0+5, y0+5, 5, 0, 2*math.pi)
 
497
    cr.arc(x1-5, y0+5, 5, 0, 2*math.pi)
 
498
    cr.arc(x0+5, y1-5, 5, 0, 2*math.pi)
 
499
    cr.arc(x1-5, y1-5, 5, 0, 2*math.pi)
 
500
    cr.rectangle(x0+5,y0,x1-x0-10,y1-y0)
 
501
    cr.rectangle(x0,y0+5,x1-x0,y1-y0-10)
 
502
    cr.fill()
 
503
 
 
504
def drawShadow(cr, x0, y0, x1, y1):
 
505
    shadow = 0.4
 
506
    h_mid = (x0+x1)/2
 
507
    v_mid = (y0+y1)/2
 
508
    h_linear_1 = cairo.LinearGradient(h_mid,y0-4,h_mid,y0+4)
 
509
    h_linear_2 = cairo.LinearGradient(h_mid,y1-4,h_mid,y1+4)
 
510
    v_linear_1 = cairo.LinearGradient(x0-4,v_mid,x0+4,v_mid)
 
511
    v_linear_2 = cairo.LinearGradient(x1-4,v_mid,x1+4,v_mid)
 
512
    radial_00 = cairo.RadialGradient(x0+4, y0+4, 0, x0+4, y0+4, 8)
 
513
    radial_01 = cairo.RadialGradient(x1-4, y0+4, 0, x1-4, y0+4, 8)
 
514
    radial_10 = cairo.RadialGradient(x0+4, y1-4, 0, x0+4, y1-4, 8)
 
515
    radial_11 = cairo.RadialGradient(x1-4, y1-4, 0, x1-4, y1-4, 8)
 
516
 
 
517
 
 
518
    h_linear_1.add_color_stop_rgba( 0, 0, 0, 0, 0)
 
519
    h_linear_1.add_color_stop_rgba( 1, 0, 0, 0, shadow)
 
520
    h_linear_2.add_color_stop_rgba( 0, 0, 0, 0, shadow)
 
521
    h_linear_2.add_color_stop_rgba( 1, 0, 0, 0, 0)
 
522
    v_linear_1.add_color_stop_rgba( 0, 0, 0, 0, 0)
 
523
    v_linear_1.add_color_stop_rgba( 1, 0, 0, 0, shadow)
 
524
    v_linear_2.add_color_stop_rgba( 0, 0, 0, 0, shadow)
 
525
    v_linear_2.add_color_stop_rgba( 1, 0, 0, 0, 0)
 
526
    cr.set_source(h_linear_1)
 
527
    #cr.set_source_rgb(0,0,1)
 
528
    cr.rectangle(x0+4,y0-4,x1-x0-8,8)
 
529
    cr.fill()
 
530
    cr.set_source(h_linear_2)
 
531
    #cr.set_source_rgb(0,0,1)
 
532
    cr.rectangle(x0+4,y1-4,x1-x0-8,8)
 
533
    cr.fill()
 
534
    cr.set_source(v_linear_1)
 
535
    #cr.set_source_rgb(0,0,1)
 
536
    cr.rectangle(x0-4,y0+4,8,y1-y0-8)
 
537
    cr.fill()
 
538
    cr.set_source(v_linear_2)
 
539
    #cr.set_source_rgb(0,0,1)
 
540
    cr.rectangle(x1-4,y0+4,8,y1-y0-8)
 
541
    cr.fill()
 
542
 
 
543
    radial_00.add_color_stop_rgba(0, 0, 0, 0, shadow)
 
544
    radial_00.add_color_stop_rgba(1, 0, 0, 0, 0)
 
545
    radial_01.add_color_stop_rgba(0, 0, 0, 0, shadow)
 
546
    radial_01.add_color_stop_rgba(1, 0, 0, 0, 0)
 
547
    radial_10.add_color_stop_rgba(0, 0, 0, 0, shadow)
 
548
    radial_10.add_color_stop_rgba(1, 0, 0, 0, 0)
 
549
    radial_11.add_color_stop_rgba(0, 0, 0, 0, shadow)
 
550
    radial_11.add_color_stop_rgba(1, 0, 0, 0, 0)
 
551
    #cr.set_source_rgb(0,0,1)
 
552
    cr.set_source(radial_00)
 
553
    cr.move_to(x0+4,y0+4)
 
554
    cr.line_to(x0,y0+4)
 
555
    cr.arc(x0+4, y0+4, 8, math.pi, 3*math.pi/2)
 
556
    cr.line_to(x0+4,y0+4)
 
557
    cr.close_path()
 
558
    cr.fill()
 
559
    #cr.set_source_rgb(0,0,1)
 
560
    cr.set_source(radial_01)
 
561
    cr.move_to(x1-4,y0+4)
 
562
    cr.line_to(x1-4,y0)
 
563
    cr.arc(x1-4, y0+4, 8, 3*math.pi/2, 2*math.pi)
 
564
    cr.line_to(x1-4,y0+4)
 
565
    cr.close_path()
 
566
    cr.fill()
 
567
    #cr.set_source_rgb(0,0,0)
 
568
    cr.set_source(radial_10)
 
569
    cr.move_to(x0+4,y1-4)
 
570
    cr.line_to(x0+4,y1)
 
571
    cr.arc(x0+4, y1-4, 8, math.pi/2, math.pi)
 
572
    cr.line_to(x0+4,y1-4)
 
573
    cr.close_path()
 
574
    cr.fill()
 
575
    #cr.set_source_rgb(0,0,0)
 
576
    cr.set_source(radial_11)
 
577
    cr.move_to(x1-4,y1-4)
 
578
    cr.line_to(x1,y1-4)
 
579
    cr.arc(x1-4, y1-4, 8, 0, math.pi/2)
 
580
    cr.line_to(x1-4,y1-4)
 
581
    cr.close_path()
 
582
    cr.fill()
 
583
 
 
584
def gantt_chart(name, pieces, width, height, h_legend, v_legend, colors):
 
585
 
 
586
    '''
 
587
        Function to generate Gantt Diagrams.
 
588
        gantt_chart(name, pieces, width, height, h_legend, v_legend, colors):
 
589
 
 
590
        Parameters
 
591
        
 
592
        name - Name of the desired output file, no need to input the .svg as it will be added at runtim;
 
593
        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;
 
594
        width, height - Dimensions of the output image;
 
595
        h_legend - A list of names for each of the vertical lines;
 
596
        v_legend - A list of names for each of the horizontal spaces;
 
597
        colors - List containing the colors expected for each of the horizontal spaces
 
598
 
 
599
        Example of use
 
600
 
 
601
        pieces = [ (0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,8)]
 
602
        h_legend = [ 'teste01', 'teste02', 'teste03', 'teste04']
 
603
        v_legend = [ '0001', '0002', '0003', '0004', '0005', '0006', '0007', '0008', '0009', '0010' ]
 
604
        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) ]
 
605
        CairoPlot.gantt_chart('gantt_teste', pieces, 600, 300, h_legend, v_legend, colors)
 
606
        
 
607
    '''
 
608
 
 
609
    surface = cairo.SVGSurface(name + '.svg', width, height)
 
610
    cr = cairo.Context(surface)
 
611
    cr.set_source_rgb(1.0, 1.0, 1.0)
 
612
    cr.rectangle(0,0,width,height)
 
613
    cr.fill()
 
614
    cr.set_font_size(0.02*width)
 
615
    max_word = ''
 
616
    for word in h_legend:
 
617
        max_word = word if word != None and len(word) > len(max_word) else max_word
 
618
    h_border = 100 + cr.text_extents(max_word)[2]
 
619
    horizontal_step = (width-h_border)/len(v_legend)
 
620
    vertical_step = height/(len(h_legend) + 1)
 
621
    v_border = vertical_step
 
622
 
 
623
    for line in pieces:
 
624
        linear = cairo.LinearGradient(width/2, v_border + pieces.index(line)*vertical_step, width/2, v_border + (pieces.index(line) + 1)*vertical_step)
 
625
        linear.add_color_stop_rgb(0,1.0,1.0,1.0)
 
626
        linear.add_color_stop_rgb(1.0,0.9,0.9,0.9)
 
627
        cr.set_source(linear)
 
628
        cr.rectangle(0,v_border + pieces.index(line)*vertical_step,width,vertical_step)
 
629
        cr.fill()
 
630
 
 
631
    cr.set_font_size(0.015*width)
 
632
    cr.set_source_rgb(0.7, 0.7, 0.7)
 
633
    cr.set_dash((1,0,0,0,0,0,1))
 
634
    cr.set_line_width(0.5)
 
635
    for word in v_legend:
 
636
        w,h = cr.text_extents(word)[2], cr.text_extents(word)[3]
 
637
        cr.move_to(h_border + v_legend.index(word)*horizontal_step-w/2, vertical_step/2)
 
638
        cr.show_text(word)
 
639
        cr.move_to(h_border + v_legend.index(word)*horizontal_step, vertical_step/2 + h)
 
640
        cr.line_to(h_border + v_legend.index(word)*horizontal_step, height)
 
641
    cr.stroke()
 
642
 
 
643
    cr.set_font_size(0.02*width)
 
644
    for line in pieces:
 
645
        word = h_legend[pieces.index(line)]
 
646
        if word != None:
 
647
            cr.set_source_rgb(0.5, 0.5, 0.5)
 
648
            w,h = cr.text_extents(word)[2], cr.text_extents(word)[3]
 
649
            cr.move_to(40,v_border + pieces.index(line)*vertical_step + vertical_step/2 + h/2)
 
650
            cr.show_text(word)
 
651
 
 
652
        if type(line) == type([]):
 
653
            for space in line:
 
654
                drawShadow(cr, h_border + space[0]*horizontal_step, v_border + pieces.index(line)*vertical_step + vertical_step/4.0, 
 
655
                               h_border + space[1]*horizontal_step, v_border + pieces.index(line)*vertical_step + 3.0*vertical_step/4.0)
 
656
                drawRectangle(cr, h_border + space[0]*horizontal_step, v_border + pieces.index(line)*vertical_step + vertical_step/4.0, 
 
657
                                  h_border + space[1]*horizontal_step, v_border + pieces.index(line)*vertical_step + 3.0*vertical_step/4.0, colors[pieces.index(line)])
 
658
        else:
 
659
            space = line
 
660
            drawShadow(cr, h_border + space[0]*horizontal_step, v_border + pieces.index(line)*vertical_step + vertical_step/4.0, 
 
661
                           h_border + space[1]*horizontal_step, v_border + pieces.index(line)*vertical_step + 3.0*vertical_step/4.0)
 
662
            drawRectangle(cr, h_border + space[0]*horizontal_step, v_border + pieces.index(line)*vertical_step + vertical_step/4.0, 
 
663
                              h_border + space[1]*horizontal_step, v_border + pieces.index(line)*vertical_step + 3.0*vertical_step/4.0, colors[pieces.index(line)])
 
664