2
# -*- coding: utf-8 -*-
6
# Copyright (c) 2008 Rodrigo Moreira Araújo
8
# Author: Rodrigo Moreiro Araujo <alf.rodrigo@gmail.com>
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.
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.
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
25
#Contributor: João S. O. Bueno
27
#TODO: review BarPlot Code
28
#TODO: x_label colision problem on Horizontal Bar Plot
29
#TODO: y_label's eat too much space on HBP
37
from Series import Series, Group, Data
43
COLORS = {"red" : (1.0,0.0,0.0,1.0), "lime" : (0.0,1.0,0.0,1.0), "blue" : (0.0,0.0,1.0,1.0),
44
"maroon" : (0.5,0.0,0.0,1.0), "green" : (0.0,0.5,0.0,1.0), "navy" : (0.0,0.0,0.5,1.0),
45
"yellow" : (1.0,1.0,0.0,1.0), "magenta" : (1.0,0.0,1.0,1.0), "cyan" : (0.0,1.0,1.0,1.0),
46
"orange" : (1.0,0.5,0.0,1.0), "white" : (1.0,1.0,1.0,1.0), "black" : (0.0,0.0,0.0,1.0),
47
"gray" : (0.5,0.5,0.5,1.0), "light_gray" : (0.9,0.9,0.9,1.0),
48
"transparent" : (0.0,0.0,0.0,0.0)}
50
THEMES = {"black_red" : [(0.0,0.0,0.0,1.0), (1.0,0.0,0.0,1.0)],
51
"red_green_blue" : [(1.0,0.0,0.0,1.0), (0.0,1.0,0.0,1.0), (0.0,0.0,1.0,1.0)],
52
"red_orange_yellow" : [(1.0,0.2,0.0,1.0), (1.0,0.7,0.0,1.0), (1.0,1.0,0.0,1.0)],
53
"yellow_orange_red" : [(1.0,1.0,0.0,1.0), (1.0,0.7,0.0,1.0), (1.0,0.2,0.0,1.0)],
54
"rainbow" : [(1.0,0.0,0.0,1.0), (1.0,0.5,0.0,1.0), (1.0,1.0,0.0,1.0), (0.0,1.0,0.0,1.0), (0.0,0.0,1.0,1.0), (0.3, 0.0, 0.5,1.0), (0.5, 0.0, 1.0, 1.0)]}
56
def colors_from_theme( theme, series_length, mode = 'solid' ):
58
if theme not in THEMES.keys() :
59
raise Exception, "Theme not defined"
60
color_steps = THEMES[theme]
61
n_colors = len(color_steps)
62
if series_length <= n_colors:
63
colors = [color + tuple([mode]) for color in color_steps[0:n_colors]]
65
iterations = [(series_length - n_colors)/(n_colors - 1) for i in color_steps[:-1]]
66
over_iterations = (series_length - n_colors) % (n_colors - 1)
67
for i in range(n_colors - 1):
68
if over_iterations <= 0:
72
for index,color in enumerate(color_steps[:-1]):
73
colors.append(color + tuple([mode]))
74
if iterations[index] == 0:
76
next_color = color_steps[index+1]
77
color_step = ((next_color[0] - color[0])/(iterations[index] + 1),
78
(next_color[1] - color[1])/(iterations[index] + 1),
79
(next_color[2] - color[2])/(iterations[index] + 1),
80
(next_color[3] - color[3])/(iterations[index] + 1))
81
for i in range( iterations[index] ):
82
colors.append((color[0] + color_step[0]*(i+1),
83
color[1] + color_step[1]*(i+1),
84
color[2] + color_step[2]*(i+1),
85
color[3] + color_step[3]*(i+1),
87
colors.append(color_steps[-1] + tuple([mode]))
91
def other_direction(direction):
92
"explicit is better than implicit"
110
series_colors = None):
112
self.create_surface(surface, width, height)
114
self.dimensions[HORZ] = width
115
self.dimensions[VERT] = height
116
self.context = cairo.Context(self.surface)
118
self.labels[HORZ] = x_labels
119
self.labels[VERT] = y_labels
120
self.load_series(data, x_labels, y_labels, series_colors)
122
self.set_background (background)
125
self.line_color = (0.5, 0.5, 0.5)
126
self.line_width = 0.5
127
self.label_color = (0.0, 0.0, 0.0)
128
self.grid_color = (0.8, 0.8, 0.8)
130
def create_surface(self, surface, width=None, height=None):
132
if isinstance(surface, cairo.Surface):
133
self.surface = surface
135
if not type(surface) in (str, unicode):
136
raise TypeError("Surface should be either a Cairo surface or a filename, not %s" % surface)
137
sufix = surface.rsplit(".")[-1].lower()
138
self.filename = surface
140
self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
142
self.surface = cairo.PSSurface(surface, width, height)
144
self.surface = cairo.PSSurface(surface, width, height)
147
self.filename += ".svg"
148
self.surface = cairo.SVGSurface(self.filename, width, height)
152
self.context.show_page()
153
if self.filename and self.filename.endswith(".png"):
154
self.surface.write_to_png(self.filename)
156
self.surface.finish()
160
def load_series (self, data, x_labels=None, y_labels=None, series_colors=None):
161
self.series_labels = []
165
#if not isinstance(data, Series):
166
# # Not an instance of Series
167
# self.series = Series(data)
171
#self.series_labels = self.series.get_names()
173
#TODO: Remove on next version
174
# The ugly way, keeping retrocompatibility...
175
if callable(data) or type(data) is list and callable(data[0]): # Lambda or List of lambdas
177
self.series_labels = None
178
elif isinstance(data, Series): # Instance of Series
180
self.series_labels = data.get_names()
181
else: # Anything else
182
self.series = Series(data)
183
self.series_labels = self.series.get_names()
185
#TODO: allow user passed series_widths
186
self.series_widths = [1.0 for group in self.series]
188
#TODO: Remove on next version
189
self.process_colors( series_colors )
191
def process_colors( self, series_colors, length = None, mode = 'solid' ):
192
#series_colors might be None, a theme, a string of colors names or a list of color tuples
194
length = len( self.series.to_list() )
197
if not series_colors:
199
self.series_colors = [ [random.random() for i in range(3)] + [1.0, mode] for series in range( length ) ]
202
if not hasattr( series_colors, "__iter__" ):
203
theme = series_colors
204
self.series_colors = colors_from_theme( theme.lower(), length )
206
#Theme pattern and mode
207
elif not hasattr(series_colors, '__delitem__') and not hasattr( series_colors[0], "__iter__" ):
208
theme = series_colors[0]
209
mode = series_colors[1]
210
self.series_colors = colors_from_theme( theme.lower(), length, mode )
214
self.series_colors = series_colors
215
for index, color in enumerate( self.series_colors ):
216
#element is a color name
217
if not hasattr(color, "__iter__"):
218
self.series_colors[index] = COLORS[color.lower()] + tuple([mode])
219
#element is rgb tuple instead of rgba
220
elif len( color ) == 3 :
221
self.series_colors[index] += (1.0,mode)
222
#element has 4 elements, might be rgba tuple or rgb tuple with mode
223
elif len( color ) == 4 :
224
#last element is mode
225
if not hasattr(color[3], "__iter__"):
226
self.series_colors[index] += tuple([color[3]])
227
self.series_colors[index][3] = 1.0
228
#last element is alpha
230
self.series_colors[index] += tuple([mode])
233
return self.surface.get_width()
235
def get_height(self):
236
return self.surface.get_height()
238
def set_background(self, background):
239
if background is None:
240
self.background = (0.0,0.0,0.0,0.0)
241
elif type(background) in (cairo.LinearGradient, tuple):
242
self.background = background
243
elif not hasattr(background,"__iter__"):
244
colors = background.split(" ")
245
if len(colors) == 1 and colors[0] in COLORS:
246
self.background = COLORS[background]
247
elif len(colors) > 1:
248
self.background = cairo.LinearGradient(self.dimensions[HORZ] / 2, 0, self.dimensions[HORZ] / 2, self.dimensions[VERT])
249
for index,color in enumerate(colors):
250
self.background.add_color_stop_rgba(float(index)/(len(colors)-1),*COLORS[color])
252
raise TypeError ("Background should be either cairo.LinearGradient or a 3/4-tuple, not %s" % type(background))
254
def render_background(self):
255
if isinstance(self.background, cairo.LinearGradient):
256
self.context.set_source(self.background)
258
self.context.set_source_rgba(*self.background)
259
self.context.rectangle(0,0, self.dimensions[HORZ], self.dimensions[VERT])
262
def render_bounding_box(self):
263
self.context.set_source_rgba(*self.line_color)
264
self.context.set_line_width(self.line_width)
265
self.context.rectangle(self.border, self.border,
266
self.dimensions[HORZ] - 2 * self.border,
267
self.dimensions[VERT] - 2 * self.border)
268
self.context.stroke()
273
class ScatterPlot( Plot ):
288
series_legend = False,
296
series_colors = None,
297
circle_colors = None ):
300
self.bounds[HORZ] = x_bounds
301
self.bounds[VERT] = y_bounds
302
self.bounds[NORM] = z_bounds
304
self.titles[HORZ] = x_title
305
self.titles[VERT] = y_title
308
self.discrete = discrete
311
self.series_legend = series_legend
312
self.variable_radius = False
313
self.x_label_angle = math.pi / 2.5
314
self.circle_colors = circle_colors
316
Plot.__init__(self, surface, data, width, height, background, border, x_labels, y_labels, series_colors)
320
if hasattr(dash, "keys"):
321
self.dash = [dash[key] for key in self.series_labels]
322
elif max([hasattr(item,'__delitem__') for item in data]) :
327
self.load_errors(errorx, errory)
329
def convert_list_to_tuple(self, data):
330
#Data must be converted from lists of coordinates to a single
332
out_data = zip(*data)
334
self.variable_radius = True
337
def load_series(self, data, x_labels = None, y_labels = None, series_colors=None):
338
#TODO: In cairoplot 2.0 keep only the Series instances
340
# Convert Data and Group to Series
341
if isinstance(data, Data) or isinstance(data, Group):
345
if isinstance(data, Series):
349
self.variable_radius = True
351
#Dictionary with lists
352
if hasattr(data, "keys") :
353
if hasattr( data.values()[0][0], "__delitem__" ) :
354
for key in data.keys() :
355
data[key] = self.convert_list_to_tuple(data[key])
356
elif len(data.values()[0][0]) == 3:
357
self.variable_radius = True
359
elif hasattr(data[0], "__delitem__") :
361
if hasattr(data[0][0], "__delitem__") :
362
for index,value in enumerate(data) :
363
data[index] = self.convert_list_to_tuple(value)
365
elif type(data[0][0]) != type((0,0)):
366
data = self.convert_list_to_tuple(data)
367
#Three dimensional data
368
elif len(data[0][0]) == 3:
369
self.variable_radius = True
371
#List with three dimensional tuples
372
elif len(data[0]) == 3:
373
self.variable_radius = True
374
Plot.load_series(self, data, x_labels, y_labels, series_colors)
375
self.calc_boundaries()
378
def load_errors(self, errorx, errory):
380
if errorx == None and errory == None:
383
self.errors[HORZ] = None
384
self.errors[VERT] = None
386
if errorx and hasattr(errorx[0], "__delitem__"):
387
self.errors[HORZ] = errorx
390
self.errors[HORZ] = [errorx]
392
if errory and hasattr(errory[0], "__delitem__"):
393
self.errors[VERT] = errory
396
self.errors[VERT] = [errory]
398
def calc_labels(self):
399
if not self.labels[HORZ]:
400
amplitude = self.bounds[HORZ][1] - self.bounds[HORZ][0]
401
if amplitude % 10: #if horizontal labels need floating points
402
self.labels[HORZ] = ["%.2lf" % (float(self.bounds[HORZ][0] + (amplitude * i / 10.0))) for i in range(11) ]
404
self.labels[HORZ] = ["%d" % (int(self.bounds[HORZ][0] + (amplitude * i / 10.0))) for i in range(11) ]
405
if not self.labels[VERT]:
406
amplitude = self.bounds[VERT][1] - self.bounds[VERT][0]
407
if amplitude % 10: #if vertical labels need floating points
408
self.labels[VERT] = ["%.2lf" % (float(self.bounds[VERT][0] + (amplitude * i / 10.0))) for i in range(11) ]
410
self.labels[VERT] = ["%d" % (int(self.bounds[VERT][0] + (amplitude * i / 10.0))) for i in range(11) ]
412
def calc_extents(self, direction):
413
self.context.set_font_size(self.font_size * 0.8)
414
self.max_value[direction] = max(self.context.text_extents(item)[2] for item in self.labels[direction])
415
self.borders[other_direction(direction)] = self.max_value[direction] + self.border + 20
417
def calc_boundaries(self):
418
#HORZ = 0, VERT = 1, NORM = 2
419
min_data_value = [0,0,0]
420
max_data_value = [0,0,0]
422
for group in self.series:
423
if type(group[0].content) in (int, float, long):
424
group = [Data((index, item.content)) for index,item in enumerate(group)]
427
for index, item in enumerate(point.content):
428
if item > max_data_value[index]:
429
max_data_value[index] = item
430
elif item < min_data_value[index]:
431
min_data_value[index] = item
433
if not self.bounds[HORZ]:
434
self.bounds[HORZ] = (min_data_value[HORZ], max_data_value[HORZ])
435
if not self.bounds[VERT]:
436
self.bounds[VERT] = (min_data_value[VERT], max_data_value[VERT])
437
if not self.bounds[NORM]:
438
self.bounds[NORM] = (min_data_value[NORM], max_data_value[NORM])
440
def calc_all_extents(self):
441
self.calc_extents(HORZ)
442
self.calc_extents(VERT)
444
self.plot_height = self.dimensions[VERT] - 2 * self.borders[VERT]
445
self.plot_width = self.dimensions[HORZ] - 2* self.borders[HORZ]
447
self.plot_top = self.dimensions[VERT] - self.borders[VERT]
449
def calc_steps(self):
450
#Calculates all the x, y, z and color steps
451
series_amplitude = [self.bounds[index][1] - self.bounds[index][0] for index in range(3)]
453
if series_amplitude[HORZ]:
454
self.horizontal_step = float (self.plot_width) / series_amplitude[HORZ]
456
self.horizontal_step = 0.00
458
if series_amplitude[VERT]:
459
self.vertical_step = float (self.plot_height) / series_amplitude[VERT]
461
self.vertical_step = 0.00
463
if series_amplitude[NORM]:
464
if self.variable_radius:
465
self.z_step = float (self.bounds[NORM][1]) / series_amplitude[NORM]
466
if self.circle_colors:
467
self.circle_color_step = tuple([float(self.circle_colors[1][i]-self.circle_colors[0][i])/series_amplitude[NORM] for i in range(4)])
470
self.circle_color_step = ( 0.0, 0.0, 0.0, 0.0 )
472
def get_circle_color(self, value):
473
return tuple( [self.circle_colors[0][i] + value*self.circle_color_step[i] for i in range(4)] )
476
self.calc_all_extents()
478
self.render_background()
479
self.render_bounding_box()
488
if self.series_legend and self.series_labels:
491
def render_axis(self):
492
#Draws both the axis lines and their titles
494
cr.set_source_rgba(*self.line_color)
495
cr.move_to(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT])
496
cr.line_to(self.borders[HORZ], self.borders[VERT])
499
cr.move_to(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT])
500
cr.line_to(self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT])
503
cr.set_source_rgba(*self.label_color)
504
self.context.set_font_size( 1.2 * self.font_size )
505
if self.titles[HORZ]:
506
title_width,title_height = cr.text_extents(self.titles[HORZ])[2:4]
507
cr.move_to( self.dimensions[HORZ]/2 - title_width/2, self.borders[VERT] - title_height/2 )
508
cr.show_text( self.titles[HORZ] )
510
if self.titles[VERT]:
511
title_width,title_height = cr.text_extents(self.titles[VERT])[2:4]
512
cr.move_to( self.dimensions[HORZ] - self.borders[HORZ] + title_height/2, self.dimensions[VERT]/2 - title_width/2)
513
cr.rotate( math.pi/2 )
514
cr.show_text( self.titles[VERT] )
515
cr.rotate( -math.pi/2 )
517
def render_grid(self):
519
horizontal_step = float( self.plot_height ) / ( len( self.labels[VERT] ) - 1 )
520
vertical_step = float( self.plot_width ) / ( len( self.labels[HORZ] ) - 1 )
522
x = self.borders[HORZ] + vertical_step
523
y = self.plot_top - horizontal_step
525
for label in self.labels[HORZ][:-1]:
526
cr.set_source_rgba(*self.grid_color)
527
cr.move_to(x, self.dimensions[VERT] - self.borders[VERT])
528
cr.line_to(x, self.borders[VERT])
531
for label in self.labels[VERT][:-1]:
532
cr.set_source_rgba(*self.grid_color)
533
cr.move_to(self.borders[HORZ], y)
534
cr.line_to(self.dimensions[HORZ] - self.borders[HORZ], y)
538
def render_labels(self):
539
self.context.set_font_size(self.font_size * 0.8)
540
self.render_horz_labels()
541
self.render_vert_labels()
543
def render_horz_labels(self):
545
step = float( self.plot_width ) / ( len( self.labels[HORZ] ) - 1 )
546
x = self.borders[HORZ]
547
for item in self.labels[HORZ]:
548
cr.set_source_rgba(*self.label_color)
549
width = cr.text_extents(item)[2]
550
cr.move_to(x, self.dimensions[VERT] - self.borders[VERT] + 5)
551
cr.rotate(self.x_label_angle)
553
cr.rotate(-self.x_label_angle)
556
def render_vert_labels(self):
558
step = ( self.plot_height ) / ( len( self.labels[VERT] ) - 1 )
560
for item in self.labels[VERT]:
561
cr.set_source_rgba(*self.label_color)
562
width = cr.text_extents(item)[2]
563
cr.move_to(self.borders[HORZ] - width - 5,y)
567
def render_legend(self):
569
cr.set_font_size(self.font_size)
570
cr.set_line_width(self.line_width)
572
widest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[2])
573
tallest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[3])
574
max_width = self.context.text_extents(widest_word)[2]
575
max_height = self.context.text_extents(tallest_word)[3] * 1.1
577
color_box_height = max_height / 2
578
color_box_width = color_box_height * 2
581
bounding_box_width = max_width + color_box_width + 15
582
bounding_box_height = (len(self.series_labels)+0.5) * max_height
583
cr.set_source_rgba(1,1,1)
584
cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - bounding_box_width, self.borders[VERT],
585
bounding_box_width, bounding_box_height)
588
cr.set_source_rgba(*self.line_color)
589
cr.set_line_width(self.line_width)
590
cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - bounding_box_width, self.borders[VERT],
591
bounding_box_width, bounding_box_height)
594
for idx,key in enumerate(self.series_labels):
596
cr.set_source_rgba(*self.series_colors[idx][:4])
597
cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - max_width - color_box_width - 10,
598
self.borders[VERT] + color_box_height + (idx*max_height) ,
599
color_box_width, color_box_height)
602
cr.set_source_rgba(0, 0, 0)
603
cr.rectangle(self.dimensions[HORZ] - self.borders[HORZ] - max_width - color_box_width - 10,
604
self.borders[VERT] + color_box_height + (idx*max_height),
605
color_box_width, color_box_height)
609
cr.set_source_rgba(0, 0, 0)
610
cr.move_to(self.dimensions[HORZ] - self.borders[HORZ] - max_width - 5, self.borders[VERT] + ((idx+1)*max_height))
613
def render_errors(self):
615
cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height)
618
x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step
619
y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step
620
for index, group in enumerate(self.series):
621
cr.set_source_rgba(*self.series_colors[index][:4])
622
for number, data in enumerate(group):
623
x = x0 + self.horizontal_step * data.content[0]
624
y = self.dimensions[VERT] - y0 - self.vertical_step * data.content[1]
625
if self.errors[HORZ]:
627
x1 = x - self.horizontal_step * self.errors[HORZ][0][number]
629
cr.line_to(x1, y - radius)
630
cr.line_to(x1, y + radius)
632
if self.errors[HORZ] and len(self.errors[HORZ]) == 2:
634
x1 = x + self.horizontal_step * self.errors[HORZ][1][number]
636
cr.line_to(x1, y - radius)
637
cr.line_to(x1, y + radius)
639
if self.errors[VERT]:
641
y1 = y + self.vertical_step * self.errors[VERT][0][number]
643
cr.line_to(x - radius, y1)
644
cr.line_to(x + radius, y1)
646
if self.errors[VERT] and len(self.errors[VERT]) == 2:
648
y1 = y - self.vertical_step * self.errors[VERT][1][number]
650
cr.line_to(x - radius, y1)
651
cr.line_to(x + radius, y1)
655
def render_plot(self):
658
cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height)
660
x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step
661
y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step
663
for number, group in enumerate (self.series):
664
cr.set_source_rgba(*self.series_colors[number][:4])
666
if self.variable_radius:
667
radius = data.content[2]*self.z_step
668
if self.circle_colors:
669
cr.set_source_rgba( *self.get_circle_color( data.content[2]) )
670
x = x0 + self.horizontal_step*data.content[0]
671
y = y0 + self.vertical_step*data.content[1]
672
cr.arc(x, self.dimensions[VERT] - y, radius, 0, 2*math.pi)
675
cr.rectangle(self.borders[HORZ], self.borders[VERT], self.plot_width, self.plot_height)
677
x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step
678
y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step
680
for number, group in enumerate (self.series):
682
cr.set_source_rgba(*self.series_colors[number][:4])
684
x = x0 + self.horizontal_step*data.content[0]
685
y = y0 + self.vertical_step*data.content[1]
687
if self.variable_radius:
688
radius = data.content[2]*self.z_step
689
cr.arc(x, self.dimensions[VERT] - y, radius, 0, 2*math.pi)
692
old_x = x0 + self.horizontal_step*last_data.content[0]
693
old_y = y0 + self.vertical_step*last_data.content[1]
694
cr.move_to( old_x, self.dimensions[VERT] - old_y )
695
cr.line_to( x, self.dimensions[VERT] - y)
696
cr.set_line_width(self.series_widths[number])
698
# Display line as dash line
699
if self.dash and self.dash[number]:
700
s = self.series_widths[number]
701
cr.set_dash([s*3, s*3], 0)
707
class DotLinePlot(ScatterPlot):
719
series_legend = False,
726
series_colors = None):
728
ScatterPlot.__init__(self, surface, data, None, None, width, height, background, border,
729
axis, dash, False, dots, grid, series_legend, x_labels, y_labels,
730
x_bounds, y_bounds, None, x_title, y_title, series_colors, None )
733
def load_series(self, data, x_labels = None, y_labels = None, series_colors=None):
734
Plot.load_series(self, data, x_labels, y_labels, series_colors)
735
for group in self.series :
736
for index,data in enumerate(group):
737
group[index].content = (index, data.content)
739
self.calc_boundaries()
742
class FunctionPlot(ScatterPlot):
754
series_legend = False,
761
series_colors = None,
766
self.discrete = discrete
768
data, x_bounds = self.load_series_from_function( self.function, x_bounds )
770
ScatterPlot.__init__(self, surface, data, None, None, width, height, background, border,
771
axis, False, discrete, dots, grid, series_legend, x_labels, y_labels,
772
x_bounds, y_bounds, None, x_title, y_title, series_colors, None )
774
def load_series(self, data, x_labels = None, y_labels = None, series_colors=None):
775
Plot.load_series(self, data, x_labels, y_labels, series_colors)
777
if len(self.series[0][0]) is 1:
778
for group_id, group in enumerate(self.series) :
779
for index,data in enumerate(group):
780
group[index].content = (self.bounds[HORZ][0] + self.step*index, data.content)
782
self.calc_boundaries()
785
def load_series_from_function( self, function, x_bounds ):
786
#TODO: Add the possibility for the user to define multiple functions with different discretization parameters
788
#This function converts a function, a list of functions or a dictionary
789
#of functions into its corresponding array of data
792
if isinstance(function, Group) or isinstance(function, Data):
793
function = Series(function)
795
# If is instance of Series
796
if isinstance(function, Series):
797
# Overwrite any bounds passed by the function
798
x_bounds = (function.range[0],function.range[-1])
800
#if no bounds are provided
805
#TODO: Finish the dict translation
806
if hasattr(function, "keys"): #dictionary:
807
for key in function.keys():
808
group = Group(name=key)
811
while i <= x_bounds[1] :
812
group.add_data(function[ key ](i))
813
#data[ key ].append( function[ key ](i) )
815
series.add_group(group)
817
elif hasattr(function, "__delitem__"): #list of functions
818
for index,f in enumerate( function ) :
822
while i <= x_bounds[1] :
824
#data[ index ].append( f(i) )
826
series.add_group(group)
828
elif isinstance(function, Series): # instance of Series
834
while i <= x_bounds[1] :
835
group.add_data(function(i))
837
series.add_group(group)
840
return series, x_bounds
842
def calc_labels(self):
843
if not self.labels[HORZ]:
844
self.labels[HORZ] = []
845
i = self.bounds[HORZ][0]
846
while i<=self.bounds[HORZ][1]:
847
self.labels[HORZ].append(str(i))
848
i += float(self.bounds[HORZ][1] - self.bounds[HORZ][0])/10
849
ScatterPlot.calc_labels(self)
851
def render_plot(self):
852
if not self.discrete:
853
ScatterPlot.render_plot(self)
857
for number, group in enumerate (self.series):
858
cr.set_source_rgba(*self.series_colors[number][:4])
859
x0 = self.borders[HORZ] - self.bounds[HORZ][0]*self.horizontal_step
860
y0 = self.borders[VERT] - self.bounds[VERT][0]*self.vertical_step
862
x = x0 + self.horizontal_step * data.content[0]
863
y = y0 + self.vertical_step * data.content[1]
864
cr.move_to(x, self.dimensions[VERT] - y)
865
cr.line_to(x, self.plot_top)
866
cr.set_line_width(self.series_widths[number])
870
cr.arc(x, self.dimensions[VERT] - y, 3, 0, 2.1 * math.pi)
880
background = "white light_gray",
882
display_values = False,
884
rounded_corners = False,
886
three_dimension = False,
891
series_colors = None,
895
self.bounds[HORZ] = x_bounds
896
self.bounds[VERT] = y_bounds
897
self.display_values = display_values
899
self.rounded_corners = rounded_corners
901
self.three_dimension = three_dimension
902
self.x_label_angle = math.pi / 2.5
903
self.main_dir = main_dir
905
self.plot_dimensions = {}
907
self.value_label_color = (0.5,0.5,0.5,1.0)
909
Plot.__init__(self, surface, data, width, height, background, border, x_labels, y_labels, series_colors)
911
def load_series(self, data, x_labels = None, y_labels = None, series_colors = None):
912
Plot.load_series(self, data, x_labels, y_labels, series_colors)
913
self.calc_boundaries()
915
def process_colors(self, series_colors):
916
#Data for a BarPlot might be a List or a List of Lists.
917
#On the first case, colors must be generated for all bars,
918
#On the second, colors must be generated for each of the inner lists.
920
#TODO: Didn't get it...
921
#if hasattr(self.data[0], '__getitem__'):
922
# length = max(len(series) for series in self.data)
924
# length = len( self.data )
926
length = max(len(group) for group in self.series)
928
Plot.process_colors( self, series_colors, length, 'linear')
930
def calc_boundaries(self):
931
if not self.bounds[self.main_dir]:
933
max_data_value = max(sum(group.to_list()) for group in self.series)
935
max_data_value = max(max(group.to_list()) for group in self.series)
936
self.bounds[self.main_dir] = (0, max_data_value)
937
if not self.bounds[other_direction(self.main_dir)]:
938
self.bounds[other_direction(self.main_dir)] = (0, len(self.series))
940
def calc_extents(self, direction):
941
self.max_value[direction] = 0
942
if self.labels[direction]:
943
widest_word = max(self.labels[direction], key = lambda item: self.context.text_extents(item)[2])
944
self.max_value[direction] = self.context.text_extents(widest_word)[3 - direction]
945
self.borders[other_direction(direction)] = (2-direction)*self.max_value[direction] + self.border + direction*(5)
947
self.borders[other_direction(direction)] = self.border
949
def calc_horz_extents(self):
950
self.calc_extents(HORZ)
952
def calc_vert_extents(self):
953
self.calc_extents(VERT)
955
def calc_all_extents(self):
956
self.calc_horz_extents()
957
self.calc_vert_extents()
958
other_dir = other_direction(self.main_dir)
960
if self.display_values:
962
self.value_label = self.context.text_extents(str(max(sum(group.to_list()) for group in self.series)))[2 + self.main_dir]
964
self.value_label = self.context.text_extents(str(max(max(group.to_list()) for group in self.series)))[2 + self.main_dir]
965
if self.labels[self.main_dir]:
966
self.plot_dimensions[self.main_dir] = self.dimensions[self.main_dir] - 2*self.borders[self.main_dir] - self.value_label
968
self.plot_dimensions[self.main_dir] = self.dimensions[self.main_dir] - self.borders[self.main_dir] - 1.2*self.border - self.value_label
969
self.plot_dimensions[other_dir] = self.dimensions[other_dir] - self.borders[other_dir] - self.border
970
self.plot_top = self.dimensions[VERT] - self.borders[VERT]
972
def calc_steps(self):
973
other_dir = other_direction(self.main_dir)
974
self.series_amplitude = self.bounds[self.main_dir][1] - self.bounds[self.main_dir][0]
975
if self.series_amplitude:
976
self.steps[self.main_dir] = float(self.plot_dimensions[self.main_dir])/self.series_amplitude
978
self.steps[self.main_dir] = 0.00
979
series_length = len(self.series)
980
self.steps[other_dir] = float(self.plot_dimensions[other_dir])/(series_length + 0.1*(series_length + 1))
981
self.space = 0.1*self.steps[other_dir]
984
self.calc_all_extents()
986
self.render_background()
987
self.render_bounding_box()
990
if self.three_dimension:
992
if self.display_values:
996
if self.series_labels:
999
def draw_3d_rectangle_front(self, x0, y0, x1, y1, shift):
1000
self.context.rectangle(x0-shift, y0+shift, x1-x0, y1-y0)
1002
def draw_3d_rectangle_side(self, x0, y0, x1, y1, shift):
1003
self.context.move_to(x1-shift,y0+shift)
1004
self.context.line_to(x1, y0)
1005
self.context.line_to(x1, y1)
1006
self.context.line_to(x1-shift, y1+shift)
1007
self.context.line_to(x1-shift, y0+shift)
1008
self.context.close_path()
1010
def draw_3d_rectangle_top(self, x0, y0, x1, y1, shift):
1011
self.context.move_to(x0-shift,y0+shift)
1012
self.context.line_to(x0, y0)
1013
self.context.line_to(x1, y0)
1014
self.context.line_to(x1-shift, y0+shift)
1015
self.context.line_to(x0-shift, y0+shift)
1016
self.context.close_path()
1018
def draw_round_rectangle(self, x0, y0, x1, y1):
1019
self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2)
1020
self.context.line_to(x1-5, y0)
1021
self.context.arc(x1-5, y0+5, 5, -math.pi/2, 0)
1022
self.context.line_to(x1, y1-5)
1023
self.context.arc(x1-5, y1-5, 5, 0, math.pi/2)
1024
self.context.line_to(x0+5, y1)
1025
self.context.arc(x0+5, y1-5, 5, math.pi/2, math.pi)
1026
self.context.line_to(x0, y0+5)
1027
self.context.close_path()
1029
def render_ground(self):
1030
self.draw_3d_rectangle_front(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT],
1031
self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10)
1034
self.draw_3d_rectangle_side (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT],
1035
self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10)
1038
self.draw_3d_rectangle_top (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT],
1039
self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10)
1042
def render_labels(self):
1043
self.context.set_font_size(self.font_size * 0.8)
1044
if self.labels[HORZ]:
1045
self.render_horz_labels()
1046
if self.labels[VERT]:
1047
self.render_vert_labels()
1049
def render_legend(self):
1051
cr.set_font_size(self.font_size)
1052
cr.set_line_width(self.line_width)
1054
widest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[2])
1055
tallest_word = max(self.series_labels, key = lambda item: self.context.text_extents(item)[3])
1056
max_width = self.context.text_extents(widest_word)[2]
1057
max_height = self.context.text_extents(tallest_word)[3] * 1.1 + 5
1059
color_box_height = max_height / 2
1060
color_box_width = color_box_height * 2
1062
#Draw a bounding box
1063
bounding_box_width = max_width + color_box_width + 15
1064
bounding_box_height = (len(self.series_labels)+0.5) * max_height
1065
cr.set_source_rgba(1,1,1)
1066
cr.rectangle(self.dimensions[HORZ] - self.border - bounding_box_width, self.border,
1067
bounding_box_width, bounding_box_height)
1070
cr.set_source_rgba(*self.line_color)
1071
cr.set_line_width(self.line_width)
1072
cr.rectangle(self.dimensions[HORZ] - self.border - bounding_box_width, self.border,
1073
bounding_box_width, bounding_box_height)
1076
for idx,key in enumerate(self.series_labels):
1078
cr.set_source_rgba(*self.series_colors[idx][:4])
1079
cr.rectangle(self.dimensions[HORZ] - self.border - max_width - color_box_width - 10,
1080
self.border + color_box_height + (idx*max_height) ,
1081
color_box_width, color_box_height)
1084
cr.set_source_rgba(0, 0, 0)
1085
cr.rectangle(self.dimensions[HORZ] - self.border - max_width - color_box_width - 10,
1086
self.border + color_box_height + (idx*max_height),
1087
color_box_width, color_box_height)
1091
cr.set_source_rgba(0, 0, 0)
1092
cr.move_to(self.dimensions[HORZ] - self.border - max_width - 5, self.border + ((idx+1)*max_height))
1096
class HorizontalBarPlot(BarPlot):
1102
background = "white light_gray",
1104
display_values = False,
1106
rounded_corners = False,
1108
three_dimension = False,
1109
series_labels = None,
1114
series_colors = None):
1116
BarPlot.__init__(self, surface, data, width, height, background, border,
1117
display_values, grid, rounded_corners, stack, three_dimension,
1118
x_labels, y_labels, x_bounds, y_bounds, series_colors, HORZ)
1119
self.series_labels = series_labels
1121
def calc_vert_extents(self):
1122
self.calc_extents(VERT)
1123
if self.labels[HORZ] and not self.labels[VERT]:
1124
self.borders[HORZ] += 10
1126
def draw_rectangle_bottom(self, x0, y0, x1, y1):
1127
self.context.arc(x0+5, y1-5, 5, math.pi/2, math.pi)
1128
self.context.line_to(x0, y0+5)
1129
self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2)
1130
self.context.line_to(x1, y0)
1131
self.context.line_to(x1, y1)
1132
self.context.line_to(x0+5, y1)
1133
self.context.close_path()
1135
def draw_rectangle_top(self, x0, y0, x1, y1):
1136
self.context.arc(x1-5, y0+5, 5, -math.pi/2, 0)
1137
self.context.line_to(x1, y1-5)
1138
self.context.arc(x1-5, y1-5, 5, 0, math.pi/2)
1139
self.context.line_to(x0, y1)
1140
self.context.line_to(x0, y0)
1141
self.context.line_to(x1, y0)
1142
self.context.close_path()
1144
def draw_rectangle(self, index, length, x0, y0, x1, y1):
1146
BarPlot.draw_rectangle(self, x0, y0, x1, y1)
1148
self.draw_rectangle_bottom(x0, y0, x1, y1)
1149
elif index == length-1:
1150
self.draw_rectangle_top(x0, y0, x1, y1)
1152
self.context.rectangle(x0, y0, x1-x0, y1-y0)
1154
#TODO: Review BarPlot.render_grid code
1155
def render_grid(self):
1156
self.context.set_source_rgba(0.8, 0.8, 0.8)
1157
if self.labels[HORZ]:
1158
self.context.set_font_size(self.font_size * 0.8)
1159
step = (self.dimensions[HORZ] - 2*self.borders[HORZ] - self.value_label)/(len(self.labels[HORZ])-1)
1160
x = self.borders[HORZ]
1162
for item in self.labels[HORZ]:
1163
width = self.context.text_extents(item)[2]
1164
if x - width/2 > next_x and x - width/2 > self.border:
1165
self.context.move_to(x, self.border)
1166
self.context.line_to(x, self.dimensions[VERT] - self.borders[VERT])
1167
self.context.stroke()
1168
next_x = x + width/2
1172
horizontal_step = float(self.plot_dimensions[HORZ])/(lines-1)
1173
x = self.borders[HORZ]
1174
for y in xrange(0, lines):
1175
self.context.move_to(x, self.border)
1176
self.context.line_to(x, self.dimensions[VERT] - self.borders[VERT])
1177
self.context.stroke()
1178
x += horizontal_step
1180
def render_horz_labels(self):
1181
step = (self.dimensions[HORZ] - 2*self.borders[HORZ])/(len(self.labels[HORZ])-1)
1182
x = self.borders[HORZ]
1185
for item in self.labels[HORZ]:
1186
self.context.set_source_rgba(*self.label_color)
1187
width = self.context.text_extents(item)[2]
1188
if x - width/2 > next_x and x - width/2 > self.border:
1189
self.context.move_to(x - width/2, self.dimensions[VERT] - self.borders[VERT] + self.max_value[HORZ] + 3)
1190
self.context.show_text(item)
1191
next_x = x + width/2
1194
def render_vert_labels(self):
1195
series_length = len(self.labels[VERT])
1196
step = (self.plot_dimensions[VERT] - (series_length + 1)*self.space)/(len(self.labels[VERT]))
1197
y = self.border + step/2 + self.space
1199
for item in self.labels[VERT]:
1200
self.context.set_source_rgba(*self.label_color)
1201
width, height = self.context.text_extents(item)[2:4]
1202
self.context.move_to(self.borders[HORZ] - width - 5, y + height/2)
1203
self.context.show_text(item)
1204
y += step + self.space
1205
self.labels[VERT].reverse()
1207
def render_values(self):
1208
self.context.set_source_rgba(*self.value_label_color)
1209
self.context.set_font_size(self.font_size * 0.8)
1211
for i,group in enumerate(self.series):
1212
value = sum(group.to_list())
1213
height = self.context.text_extents(str(value))[3]
1214
x = self.borders[HORZ] + value*self.steps[HORZ] + 2
1215
y = self.borders[VERT] + (i+0.5)*self.steps[VERT] + (i+1)*self.space + height/2
1216
self.context.move_to(x, y)
1217
self.context.show_text(str(value))
1219
for i,group in enumerate(self.series):
1220
inner_step = self.steps[VERT]/len(group)
1221
y0 = self.border + i*self.steps[VERT] + (i+1)*self.space
1222
for number,data in enumerate(group):
1223
height = self.context.text_extents(str(data.content))[3]
1224
self.context.move_to(self.borders[HORZ] + data.content*self.steps[HORZ] + 2, y0 + 0.5*inner_step + height/2, )
1225
self.context.show_text(str(data.content))
1228
def render_plot(self):
1230
for i,group in enumerate(self.series):
1231
x0 = self.borders[HORZ]
1232
y0 = self.borders[VERT] + i*self.steps[VERT] + (i+1)*self.space
1233
for number,data in enumerate(group):
1234
if self.series_colors[number][4] in ('radial','linear') :
1235
linear = cairo.LinearGradient( data.content*self.steps[HORZ]/2, y0, data.content*self.steps[HORZ]/2, y0 + self.steps[VERT] )
1236
color = self.series_colors[number]
1237
linear.add_color_stop_rgba(0.0, 3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0)
1238
linear.add_color_stop_rgba(1.0, *color[:4])
1239
self.context.set_source(linear)
1240
elif self.series_colors[number][4] == 'solid':
1241
self.context.set_source_rgba(*self.series_colors[number][:4])
1242
if self.rounded_corners:
1243
self.draw_rectangle(number, len(group), x0, y0, x0+data.content*self.steps[HORZ], y0+self.steps[VERT])
1246
self.context.rectangle(x0, y0, data.content*self.steps[HORZ], self.steps[VERT])
1248
x0 += data.content*self.steps[HORZ]
1250
for i,group in enumerate(self.series):
1251
inner_step = self.steps[VERT]/len(group)
1252
x0 = self.borders[HORZ]
1253
y0 = self.border + i*self.steps[VERT] + (i+1)*self.space
1254
for number,data in enumerate(group):
1255
linear = cairo.LinearGradient(data.content*self.steps[HORZ]/2, y0, data.content*self.steps[HORZ]/2, y0 + inner_step)
1256
color = self.series_colors[number]
1257
linear.add_color_stop_rgba(0.0, 3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0)
1258
linear.add_color_stop_rgba(1.0, *color[:4])
1259
self.context.set_source(linear)
1260
if self.rounded_corners and data.content != 0:
1261
BarPlot.draw_round_rectangle(self,x0, y0, x0 + data.content*self.steps[HORZ], y0 + inner_step)
1264
self.context.rectangle(x0, y0, data.content*self.steps[HORZ], inner_step)
1268
class VerticalBarPlot(BarPlot):
1274
background = "white light_gray",
1276
display_values = False,
1278
rounded_corners = False,
1280
three_dimension = False,
1281
series_labels = None,
1286
series_colors = None):
1288
BarPlot.__init__(self, surface, data, width, height, background, border,
1289
display_values, grid, rounded_corners, stack, three_dimension,
1290
x_labels, y_labels, x_bounds, y_bounds, series_colors, VERT)
1291
self.series_labels = series_labels
1293
def calc_vert_extents(self):
1294
self.calc_extents(VERT)
1295
if self.labels[VERT] and not self.labels[HORZ]:
1296
self.borders[VERT] += 10
1298
def draw_rectangle_bottom(self, x0, y0, x1, y1):
1299
self.context.move_to(x1,y1)
1300
self.context.arc(x1-5, y1-5, 5, 0, math.pi/2)
1301
self.context.line_to(x0+5, y1)
1302
self.context.arc(x0+5, y1-5, 5, math.pi/2, math.pi)
1303
self.context.line_to(x0, y0)
1304
self.context.line_to(x1, y0)
1305
self.context.line_to(x1, y1)
1306
self.context.close_path()
1308
def draw_rectangle_top(self, x0, y0, x1, y1):
1309
self.context.arc(x0+5, y0+5, 5, -math.pi, -math.pi/2)
1310
self.context.line_to(x1-5, y0)
1311
self.context.arc(x1-5, y0+5, 5, -math.pi/2, 0)
1312
self.context.line_to(x1, y1)
1313
self.context.line_to(x0, y1)
1314
self.context.line_to(x0, y0)
1315
self.context.close_path()
1317
def draw_rectangle(self, index, length, x0, y0, x1, y1):
1319
BarPlot.draw_rectangle(self, x0, y0, x1, y1)
1321
self.draw_rectangle_bottom(x0, y0, x1, y1)
1322
elif index == length-1:
1323
self.draw_rectangle_top(x0, y0, x1, y1)
1325
self.context.rectangle(x0, y0, x1-x0, y1-y0)
1327
def render_grid(self):
1328
self.context.set_source_rgba(0.8, 0.8, 0.8)
1329
if self.labels[VERT]:
1330
lines = len(self.labels[VERT])
1331
vertical_step = float(self.plot_dimensions[self.main_dir])/(lines-1)
1332
y = self.borders[VERT] + self.value_label
1335
vertical_step = float(self.plot_dimensions[self.main_dir])/(lines-1)
1336
y = 1.2*self.border + self.value_label
1337
for x in xrange(0, lines):
1338
self.context.move_to(self.borders[HORZ], y)
1339
self.context.line_to(self.dimensions[HORZ] - self.border, y)
1340
self.context.stroke()
1343
def render_ground(self):
1344
self.draw_3d_rectangle_front(self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT],
1345
self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10)
1348
self.draw_3d_rectangle_side (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT],
1349
self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10)
1352
self.draw_3d_rectangle_top (self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT],
1353
self.dimensions[HORZ] - self.borders[HORZ], self.dimensions[VERT] - self.borders[VERT] + 5, 10)
1356
def render_horz_labels(self):
1357
series_length = len(self.labels[HORZ])
1358
step = float (self.plot_dimensions[HORZ] - (series_length + 1)*self.space)/len(self.labels[HORZ])
1359
x = self.borders[HORZ] + step/2 + self.space
1362
for item in self.labels[HORZ]:
1363
self.context.set_source_rgba(*self.label_color)
1364
width = self.context.text_extents(item)[2]
1365
if x - width/2 > next_x and x - width/2 > self.borders[HORZ]:
1366
self.context.move_to(x - width/2, self.dimensions[VERT] - self.borders[VERT] + self.max_value[HORZ] + 3)
1367
self.context.show_text(item)
1368
next_x = x + width/2
1369
x += step + self.space
1371
def render_vert_labels(self):
1372
self.context.set_source_rgba(*self.label_color)
1373
y = self.borders[VERT] + self.value_label
1374
step = (self.dimensions[VERT] - 2*self.borders[VERT] - self.value_label)/(len(self.labels[VERT]) - 1)
1375
self.labels[VERT].reverse()
1376
for item in self.labels[VERT]:
1377
width, height = self.context.text_extents(item)[2:4]
1378
self.context.move_to(self.borders[HORZ] - width - 5, y + height/2)
1379
self.context.show_text(item)
1381
self.labels[VERT].reverse()
1383
def render_values(self):
1384
self.context.set_source_rgba(*self.value_label_color)
1385
self.context.set_font_size(self.font_size * 0.8)
1387
for i,group in enumerate(self.series):
1388
value = sum(group.to_list())
1389
width = self.context.text_extents(str(value))[2]
1390
x = self.borders[HORZ] + (i+0.5)*self.steps[HORZ] + (i+1)*self.space - width/2
1391
y = value*self.steps[VERT] + 2
1392
self.context.move_to(x, self.plot_top-y)
1393
self.context.show_text(str(value))
1395
for i,group in enumerate(self.series):
1396
inner_step = self.steps[HORZ]/len(group)
1397
x0 = self.borders[HORZ] + i*self.steps[HORZ] + (i+1)*self.space
1398
for number,data in enumerate(group):
1399
width = self.context.text_extents(str(data.content))[2]
1400
self.context.move_to(x0 + 0.5*inner_step - width/2, self.plot_top - data.content*self.steps[VERT] - 2)
1401
self.context.show_text(str(data.content))
1404
def render_plot(self):
1406
for i,group in enumerate(self.series):
1407
x0 = self.borders[HORZ] + i*self.steps[HORZ] + (i+1)*self.space
1409
for number,data in enumerate(group):
1410
if self.series_colors[number][4] in ('linear','radial'):
1411
linear = cairo.LinearGradient( x0, data.content*self.steps[VERT]/2, x0 + self.steps[HORZ], data.content*self.steps[VERT]/2 )
1412
color = self.series_colors[number]
1413
linear.add_color_stop_rgba(0.0, 3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0)
1414
linear.add_color_stop_rgba(1.0, *color[:4])
1415
self.context.set_source(linear)
1416
elif self.series_colors[number][4] == 'solid':
1417
self.context.set_source_rgba(*self.series_colors[number][:4])
1418
if self.rounded_corners:
1419
self.draw_rectangle(number, len(group), x0, self.plot_top - y0 - data.content*self.steps[VERT], x0 + self.steps[HORZ], self.plot_top - y0)
1422
self.context.rectangle(x0, self.plot_top - y0 - data.content*self.steps[VERT], self.steps[HORZ], data.content*self.steps[VERT])
1424
y0 += data.content*self.steps[VERT]
1426
for i,group in enumerate(self.series):
1427
inner_step = self.steps[HORZ]/len(group)
1428
y0 = self.borders[VERT]
1429
x0 = self.borders[HORZ] + i*self.steps[HORZ] + (i+1)*self.space
1430
for number,data in enumerate(group):
1431
if self.series_colors[number][4] == 'linear':
1432
linear = cairo.LinearGradient( x0, data.content*self.steps[VERT]/2, x0 + inner_step, data.content*self.steps[VERT]/2 )
1433
color = self.series_colors[number]
1434
linear.add_color_stop_rgba(0.0, 3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0)
1435
linear.add_color_stop_rgba(1.0, *color[:4])
1436
self.context.set_source(linear)
1437
elif self.series_colors[number][4] == 'solid':
1438
self.context.set_source_rgba(*self.series_colors[number][:4])
1439
if self.rounded_corners and data.content != 0:
1440
BarPlot.draw_round_rectangle(self, x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top)
1442
elif self.three_dimension:
1443
self.draw_3d_rectangle_front(x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top, 5)
1445
self.draw_3d_rectangle_side(x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top, 5)
1447
self.draw_3d_rectangle_top(x0, self.plot_top - data.content*self.steps[VERT], x0+inner_step, self.plot_top, 5)
1450
self.context.rectangle(x0, self.plot_top - data.content*self.steps[VERT], inner_step, data.content*self.steps[VERT])
1455
class StreamChart(VerticalBarPlot):
1461
background = "white light_gray",
1464
series_legend = None,
1468
series_colors = None):
1470
VerticalBarPlot.__init__(self, surface, data, width, height, background, border,
1471
False, grid, False, True, False,
1472
None, x_labels, None, x_bounds, y_bounds, series_colors)
1474
def calc_steps(self):
1475
other_dir = other_direction(self.main_dir)
1476
self.series_amplitude = self.bounds[self.main_dir][1] - self.bounds[self.main_dir][0]
1477
if self.series_amplitude:
1478
self.steps[self.main_dir] = float(self.plot_dimensions[self.main_dir])/self.series_amplitude
1480
self.steps[self.main_dir] = 0.00
1481
series_length = len(self.data)
1482
self.steps[other_dir] = float(self.plot_dimensions[other_dir])/series_length
1484
def render_legend(self):
1487
def ground(self, index):
1488
sum_values = sum(self.data[index])
1489
return -0.5*sum_values
1491
def calc_angles(self):
1492
middle = self.plot_top - self.plot_dimensions[VERT]/2.0
1493
self.angles = [tuple([0.0 for x in range(len(self.data)+1)])]
1494
for x_index in range(1, len(self.data)-1):
1496
x0 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ]
1497
x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ]
1498
y0 = middle - self.ground(x_index-1)*self.steps[VERT]
1499
y2 = middle - self.ground(x_index+1)*self.steps[VERT]
1500
t.append(math.atan(float(y0-y2)/(x0-x2)))
1501
for data_index in range(len(self.data[x_index])):
1502
x0 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ]
1503
x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ]
1504
y0 = middle - self.ground(x_index-1)*self.steps[VERT] - self.data[x_index-1][data_index]*self.steps[VERT]
1505
y2 = middle - self.ground(x_index+1)*self.steps[VERT] - self.data[x_index+1][data_index]*self.steps[VERT]
1507
for i in range(0,data_index):
1508
y0 -= self.data[x_index-1][i]*self.steps[VERT]
1509
y2 -= self.data[x_index+1][i]*self.steps[VERT]
1511
if data_index == len(self.data[0])-1 and False:
1512
self.context.set_source_rgba(0.0,0.0,0.0,0.3)
1513
self.context.move_to(x0,y0)
1514
self.context.line_to(x2,y2)
1515
self.context.stroke()
1516
self.context.arc(x0,y0,2,0,2*math.pi)
1518
t.append(math.atan(float(y0-y2)/(x0-x2)))
1519
self.angles.append(tuple(t))
1520
self.angles.append(tuple([0.0 for x in range(len(self.data)+1)]))
1522
def render_plot(self):
1524
middle = self.plot_top - self.plot_dimensions[VERT]/2.0
1525
p = 0.4*self.steps[HORZ]
1526
for data_index in range(len(self.data[0])-1,-1,-1):
1527
self.context.set_source_rgba(*self.series_colors[data_index][:4])
1529
#draw the upper line
1530
for x_index in range(len(self.data)-1) :
1531
x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ]
1532
y1 = middle - self.ground(x_index)*self.steps[VERT] - self.data[x_index][data_index]*self.steps[VERT]
1533
x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ]
1534
y2 = middle - self.ground(x_index + 1)*self.steps[VERT] - self.data[x_index + 1][data_index]*self.steps[VERT]
1536
for i in range(0,data_index):
1537
y1 -= self.data[x_index][i]*self.steps[VERT]
1538
y2 -= self.data[x_index+1][i]*self.steps[VERT]
1541
self.context.move_to(x1,y1)
1543
ang1 = self.angles[x_index][data_index+1]
1544
ang2 = self.angles[x_index+1][data_index+1] + math.pi
1545
self.context.curve_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1),
1546
x2+p*math.cos(ang2),y2+p*math.sin(ang2),
1549
for x_index in range(len(self.data)-1,0,-1) :
1550
x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ]
1551
y1 = middle - self.ground(x_index)*self.steps[VERT]
1552
x2 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ]
1553
y2 = middle - self.ground(x_index - 1)*self.steps[VERT]
1555
for i in range(0,data_index):
1556
y1 -= self.data[x_index][i]*self.steps[VERT]
1557
y2 -= self.data[x_index-1][i]*self.steps[VERT]
1559
if x_index == len(self.data)-1:
1560
self.context.line_to(x1,y1+2)
1562
#revert angles by pi degrees to take the turn back
1563
ang1 = self.angles[x_index][data_index] + math.pi
1564
ang2 = self.angles[x_index-1][data_index]
1565
self.context.curve_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1),
1566
x2+p*math.cos(ang2),y2+p*math.sin(ang2),
1569
self.context.close_path()
1573
self.context.move_to(self.borders[HORZ] + 0.5*self.steps[HORZ], middle)
1574
for x_index in range(len(self.data)-1) :
1575
x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ]
1576
y1 = middle - self.ground(x_index)*self.steps[VERT] - self.data[x_index][data_index]*self.steps[VERT]
1577
x2 = self.borders[HORZ] + (0.5 + x_index + 1)*self.steps[HORZ]
1578
y2 = middle - self.ground(x_index + 1)*self.steps[VERT] - self.data[x_index + 1][data_index]*self.steps[VERT]
1580
for i in range(0,data_index):
1581
y1 -= self.data[x_index][i]*self.steps[VERT]
1582
y2 -= self.data[x_index+1][i]*self.steps[VERT]
1584
ang1 = self.angles[x_index][data_index+1]
1585
ang2 = self.angles[x_index+1][data_index+1] + math.pi
1586
self.context.set_source_rgba(1.0,0.0,0.0)
1587
self.context.arc(x1+p*math.cos(ang1),y1+p*math.sin(ang1),2,0,2*math.pi)
1589
self.context.set_source_rgba(0.0,0.0,0.0)
1590
self.context.arc(x2+p*math.cos(ang2),y2+p*math.sin(ang2),2,0,2*math.pi)
1592
'''self.context.set_source_rgba(0.0,0.0,0.0,0.3)
1593
self.context.arc(x2,y2,2,0,2*math.pi)
1594
self.context.fill()'''
1595
self.context.move_to(x1,y1)
1596
self.context.line_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1))
1597
self.context.stroke()
1598
self.context.move_to(x2,y2)
1599
self.context.line_to(x2+p*math.cos(ang2),y2+p*math.sin(ang2))
1600
self.context.stroke()
1602
for x_index in range(len(self.data)-1,0,-1) :
1603
x1 = self.borders[HORZ] + (0.5 + x_index)*self.steps[HORZ]
1604
y1 = middle - self.ground(x_index)*self.steps[VERT]
1605
x2 = self.borders[HORZ] + (0.5 + x_index - 1)*self.steps[HORZ]
1606
y2 = middle - self.ground(x_index - 1)*self.steps[VERT]
1608
for i in range(0,data_index):
1609
y1 -= self.data[x_index][i]*self.steps[VERT]
1610
y2 -= self.data[x_index-1][i]*self.steps[VERT]
1612
#revert angles by pi degrees to take the turn back
1613
ang1 = self.angles[x_index][data_index] + math.pi
1614
ang2 = self.angles[x_index-1][data_index]
1615
self.context.set_source_rgba(0.0,1.0,0.0)
1616
self.context.arc(x1+p*math.cos(ang1),y1+p*math.sin(ang1),2,0,2*math.pi)
1618
self.context.set_source_rgba(0.0,0.0,1.0)
1619
self.context.arc(x2+p*math.cos(ang2),y2+p*math.sin(ang2),2,0,2*math.pi)
1621
'''self.context.set_source_rgba(0.0,0.0,0.0,0.3)
1622
self.context.arc(x2,y2,2,0,2*math.pi)
1623
self.context.fill()'''
1624
self.context.move_to(x1,y1)
1625
self.context.line_to(x1+p*math.cos(ang1),y1+p*math.sin(ang1))
1626
self.context.stroke()
1627
self.context.move_to(x2,y2)
1628
self.context.line_to(x2+p*math.cos(ang2),y2+p*math.sin(ang2))
1629
self.context.stroke()
1632
#self.context.arc(self.dimensions[HORZ]/2, self.dimensions[VERT]/2,50,0,3*math.pi/2)
1633
#self.context.fill()
1636
class PiePlot(Plot):
1637
#TODO: Check the old cairoplot, graphs aren't matching
1643
background = "white light_gray",
1648
Plot.__init__( self, surface, data, width, height, background, series_colors = colors )
1649
self.center = (self.dimensions[HORZ]/2, self.dimensions[VERT]/2)
1650
self.total = sum( self.series.to_list() )
1651
self.radius = min(self.dimensions[HORZ]/3,self.dimensions[VERT]/3)
1652
self.gradient = gradient
1653
self.shadow = shadow
1655
def sort_function(x,y):
1656
return x.content - y.content
1658
def load_series(self, data, x_labels=None, y_labels=None, series_colors=None):
1659
Plot.load_series(self, data, x_labels, y_labels, series_colors)
1660
# Already done inside series
1661
#self.data = sorted(self.data)
1663
def draw_piece(self, angle, next_angle):
1664
self.context.move_to(self.center[0],self.center[1])
1665
self.context.line_to(self.center[0] + self.radius*math.cos(angle), self.center[1] + self.radius*math.sin(angle))
1666
self.context.arc(self.center[0], self.center[1], self.radius, angle, next_angle)
1667
self.context.line_to(self.center[0], self.center[1])
1668
self.context.close_path()
1671
self.render_background()
1672
self.render_bounding_box()
1674
self.render_shadow()
1676
self.render_series_labels()
1678
def render_shadow(self):
1679
horizontal_shift = 3
1681
self.context.set_source_rgba(0, 0, 0, 0.5)
1682
self.context.arc(self.center[0] + horizontal_shift, self.center[1] + vertical_shift, self.radius, 0, 2*math.pi)
1685
def render_series_labels(self):
1690
for number,key in enumerate(self.series_labels):
1691
# self.data[number] should be just a number
1692
data = sum(self.series[number].to_list())
1694
next_angle = angle + 2.0*math.pi*data/self.total
1695
cr.set_source_rgba(*self.series_colors[number][:4])
1696
w = cr.text_extents(key)[2]
1697
if (angle + next_angle)/2 < math.pi/2 or (angle + next_angle)/2 > 3*math.pi/2:
1698
cr.move_to(x0 + (self.radius+10)*math.cos((angle+next_angle)/2), y0 + (self.radius+10)*math.sin((angle+next_angle)/2) )
1700
cr.move_to(x0 + (self.radius+10)*math.cos((angle+next_angle)/2) - w, y0 + (self.radius+10)*math.sin((angle+next_angle)/2) )
1704
def render_plot(self):
1709
for number,group in enumerate(self.series):
1710
# Group should be just a number
1711
data = sum(group.to_list())
1712
next_angle = angle + 2.0*math.pi*data/self.total
1713
if self.gradient or self.series_colors[number][4] in ('linear','radial'):
1714
gradient_color = cairo.RadialGradient(self.center[0], self.center[1], 0, self.center[0], self.center[1], self.radius)
1715
gradient_color.add_color_stop_rgba(0.3, *self.series_colors[number][:4])
1716
gradient_color.add_color_stop_rgba(1, self.series_colors[number][0]*0.7,
1717
self.series_colors[number][1]*0.7,
1718
self.series_colors[number][2]*0.7,
1719
self.series_colors[number][3])
1720
cr.set_source(gradient_color)
1722
cr.set_source_rgba(*self.series_colors[number][:4])
1724
self.draw_piece(angle, next_angle)
1727
cr.set_source_rgba(1.0, 1.0, 1.0)
1728
self.draw_piece(angle, next_angle)
1733
class DonutPlot(PiePlot):
1739
background = "white light_gray",
1745
Plot.__init__( self, surface, data, width, height, background, series_colors = colors )
1747
self.center = ( self.dimensions[HORZ]/2, self.dimensions[VERT]/2 )
1748
self.total = sum( self.series.to_list() )
1749
self.radius = min( self.dimensions[HORZ]/3,self.dimensions[VERT]/3 )
1750
self.inner_radius = inner_radius*self.radius
1752
if inner_radius == -1:
1753
self.inner_radius = self.radius/3
1755
self.gradient = gradient
1756
self.shadow = shadow
1758
def draw_piece(self, angle, next_angle):
1759
self.context.move_to(self.center[0] + (self.inner_radius)*math.cos(angle), self.center[1] + (self.inner_radius)*math.sin(angle))
1760
self.context.line_to(self.center[0] + self.radius*math.cos(angle), self.center[1] + self.radius*math.sin(angle))
1761
self.context.arc(self.center[0], self.center[1], self.radius, angle, next_angle)
1762
self.context.line_to(self.center[0] + (self.inner_radius)*math.cos(next_angle), self.center[1] + (self.inner_radius)*math.sin(next_angle))
1763
self.context.arc_negative(self.center[0], self.center[1], self.inner_radius, next_angle, angle)
1764
self.context.close_path()
1766
def render_shadow(self):
1767
horizontal_shift = 3
1769
self.context.set_source_rgba(0, 0, 0, 0.5)
1770
self.context.arc(self.center[0] + horizontal_shift, self.center[1] + vertical_shift, self.inner_radius, 0, 2*math.pi)
1771
self.context.arc_negative(self.center[0] + horizontal_shift, self.center[1] + vertical_shift, self.radius, 0, -2*math.pi)
1774
class GanttChart (Plot) :
1785
Plot.__init__(self, surface, data, width, height, x_labels = x_labels, y_labels = y_labels, series_colors = colors)
1787
def load_series(self, data, x_labels=None, y_labels=None, series_colors=None):
1788
Plot.load_series(self, data, x_labels, y_labels, series_colors)
1789
self.calc_boundaries()
1791
def calc_boundaries(self):
1792
self.bounds[HORZ] = (0,len(self.series))
1793
end_pos = max(self.series.to_list())
1795
#for group in self.series:
1796
# if hasattr(item, "__delitem__"):
1797
# for sub_item in item:
1798
# end_pos = max(sub_item)
1800
# end_pos = max(item)
1801
self.bounds[VERT] = (0,end_pos)
1803
def calc_extents(self, direction):
1804
self.max_value[direction] = 0
1805
if self.labels[direction]:
1806
self.max_value[direction] = max(self.context.text_extents(item)[2] for item in self.labels[direction])
1808
self.max_value[direction] = self.context.text_extents( str(self.bounds[direction][1] + 1) )[2]
1810
def calc_horz_extents(self):
1811
self.calc_extents(HORZ)
1812
self.borders[HORZ] = 100 + self.max_value[HORZ]
1814
def calc_vert_extents(self):
1815
self.calc_extents(VERT)
1816
self.borders[VERT] = self.dimensions[VERT]/(self.bounds[HORZ][1] + 1)
1818
def calc_steps(self):
1819
self.horizontal_step = (self.dimensions[HORZ] - self.borders[HORZ])/(len(self.labels[VERT]))
1820
self.vertical_step = self.borders[VERT]
1823
self.calc_horz_extents()
1824
self.calc_vert_extents()
1826
self.render_background()
1828
self.render_labels()
1832
def render_background(self):
1834
cr.set_source_rgba(255,255,255)
1835
cr.rectangle(0,0,self.dimensions[HORZ], self.dimensions[VERT])
1837
for number,group in enumerate(self.series):
1838
linear = cairo.LinearGradient(self.dimensions[HORZ]/2, self.borders[VERT] + number*self.vertical_step,
1839
self.dimensions[HORZ]/2, self.borders[VERT] + (number+1)*self.vertical_step)
1840
linear.add_color_stop_rgba(0,1.0,1.0,1.0,1.0)
1841
linear.add_color_stop_rgba(1.0,0.9,0.9,0.9,1.0)
1842
cr.set_source(linear)
1843
cr.rectangle(0,self.borders[VERT] + number*self.vertical_step,self.dimensions[HORZ],self.vertical_step)
1846
def render_grid(self):
1848
cr.set_source_rgba(0.7, 0.7, 0.7)
1849
cr.set_dash((1,0,0,0,0,0,1))
1850
cr.set_line_width(0.5)
1851
for number,label in enumerate(self.labels[VERT]):
1852
h = cr.text_extents(label)[3]
1853
cr.move_to(self.borders[HORZ] + number*self.horizontal_step, self.vertical_step/2 + h)
1854
cr.line_to(self.borders[HORZ] + number*self.horizontal_step, self.dimensions[VERT])
1857
def render_labels(self):
1858
self.context.set_font_size(0.02 * self.dimensions[HORZ])
1860
self.render_horz_labels()
1861
self.render_vert_labels()
1863
def render_horz_labels(self):
1865
labels = self.labels[HORZ]
1867
labels = [str(i) for i in range(1, self.bounds[HORZ][1] + 1) ]
1868
for number,label in enumerate(labels):
1870
cr.set_source_rgba(0.5, 0.5, 0.5)
1871
w,h = cr.text_extents(label)[2], cr.text_extents(label)[3]
1872
cr.move_to(40,self.borders[VERT] + number*self.vertical_step + self.vertical_step/2 + h/2)
1875
def render_vert_labels(self):
1877
labels = self.labels[VERT]
1879
labels = [str(i) for i in range(1, self.bounds[VERT][1] + 1) ]
1880
for number,label in enumerate(labels):
1881
w,h = cr.text_extents(label)[2], cr.text_extents(label)[3]
1882
cr.move_to(self.borders[HORZ] + number*self.horizontal_step - w/2, self.vertical_step/2)
1885
def render_rectangle(self, x0, y0, x1, y1, color):
1886
self.draw_shadow(x0, y0, x1, y1)
1887
self.draw_rectangle(x0, y0, x1, y1, color)
1889
def draw_rectangular_shadow(self, gradient, x0, y0, w, h):
1890
self.context.set_source(gradient)
1891
self.context.rectangle(x0,y0,w,h)
1894
def draw_circular_shadow(self, x, y, radius, ang_start, ang_end, mult, shadow):
1895
gradient = cairo.RadialGradient(x, y, 0, x, y, 2*radius)
1896
gradient.add_color_stop_rgba(0, 0, 0, 0, shadow)
1897
gradient.add_color_stop_rgba(1, 0, 0, 0, 0)
1898
self.context.set_source(gradient)
1899
self.context.move_to(x,y)
1900
self.context.line_to(x + mult[0]*radius,y + mult[1]*radius)
1901
self.context.arc(x, y, 8, ang_start, ang_end)
1902
self.context.line_to(x,y)
1903
self.context.close_path()
1906
def draw_rectangle(self, x0, y0, x1, y1, color):
1909
linear = cairo.LinearGradient(middle,y0,middle,y1)
1910
linear.add_color_stop_rgba(0,3.5*color[0]/5.0, 3.5*color[1]/5.0, 3.5*color[2]/5.0,1.0)
1911
linear.add_color_stop_rgba(1,*color[:4])
1912
cr.set_source(linear)
1914
cr.arc(x0+5, y0+5, 5, 0, 2*math.pi)
1915
cr.arc(x1-5, y0+5, 5, 0, 2*math.pi)
1916
cr.arc(x0+5, y1-5, 5, 0, 2*math.pi)
1917
cr.arc(x1-5, y1-5, 5, 0, 2*math.pi)
1918
cr.rectangle(x0+5,y0,x1-x0-10,y1-y0)
1919
cr.rectangle(x0,y0+5,x1-x0,y1-y0-10)
1922
def draw_shadow(self, x0, y0, x1, y1):
1926
h_linear_1 = cairo.LinearGradient(h_mid,y0-4,h_mid,y0+4)
1927
h_linear_2 = cairo.LinearGradient(h_mid,y1-4,h_mid,y1+4)
1928
v_linear_1 = cairo.LinearGradient(x0-4,v_mid,x0+4,v_mid)
1929
v_linear_2 = cairo.LinearGradient(x1-4,v_mid,x1+4,v_mid)
1931
h_linear_1.add_color_stop_rgba( 0, 0, 0, 0, 0)
1932
h_linear_1.add_color_stop_rgba( 1, 0, 0, 0, shadow)
1933
h_linear_2.add_color_stop_rgba( 0, 0, 0, 0, shadow)
1934
h_linear_2.add_color_stop_rgba( 1, 0, 0, 0, 0)
1935
v_linear_1.add_color_stop_rgba( 0, 0, 0, 0, 0)
1936
v_linear_1.add_color_stop_rgba( 1, 0, 0, 0, shadow)
1937
v_linear_2.add_color_stop_rgba( 0, 0, 0, 0, shadow)
1938
v_linear_2.add_color_stop_rgba( 1, 0, 0, 0, 0)
1940
self.draw_rectangular_shadow(h_linear_1,x0+4,y0-4,x1-x0-8,8)
1941
self.draw_rectangular_shadow(h_linear_2,x0+4,y1-4,x1-x0-8,8)
1942
self.draw_rectangular_shadow(v_linear_1,x0-4,y0+4,8,y1-y0-8)
1943
self.draw_rectangular_shadow(v_linear_2,x1-4,y0+4,8,y1-y0-8)
1945
self.draw_circular_shadow(x0+4, y0+4, 4, math.pi, 3*math.pi/2, (-1,0), shadow)
1946
self.draw_circular_shadow(x1-4, y0+4, 4, 3*math.pi/2, 2*math.pi, (0,-1), shadow)
1947
self.draw_circular_shadow(x0+4, y1-4, 4, math.pi/2, math.pi, (0,1), shadow)
1948
self.draw_circular_shadow(x1-4, y1-4, 4, 0, math.pi/2, (1,0), shadow)
1950
def render_plot(self):
1951
for index,group in enumerate(self.series):
1953
self.render_rectangle(self.borders[HORZ] + data.content[0]*self.horizontal_step,
1954
self.borders[VERT] + index*self.vertical_step + self.vertical_step/4.0,
1955
self.borders[HORZ] + data.content[1]*self.horizontal_step,
1956
self.borders[VERT] + index*self.vertical_step + 3.0*self.vertical_step/4.0,
1957
self.series_colors[index])
1959
# Function definition
1961
def scatter_plot(name,
1967
background = "white light_gray",
1974
series_legend = False,
1982
series_colors = None,
1983
circle_colors = None):
1986
- Function to plot scatter data.
1990
data - The values to be ploted might be passed in a two basic:
1991
list of points: [(0,0), (0,1), (0,2)] or [(0,0,1), (0,1,4), (0,2,1)]
1992
lists of coordinates: [ [0,0,0] , [0,1,2] ] or [ [0,0,0] , [0,1,2] , [1,4,1] ]
1993
Notice that these kinds of that can be grouped in order to form more complex data
1994
using lists of lists or dictionaries;
1995
series_colors - Define color values for each of the series
1996
circle_colors - Define a lower and an upper bound for the circle colors for variable radius
1997
(3 dimensions) series
2000
plot = ScatterPlot( name, data, errorx, errory, width, height, background, border,
2001
axis, dash, discrete, dots, grid, series_legend, x_labels, y_labels,
2002
x_bounds, y_bounds, z_bounds, x_title, y_title, series_colors, circle_colors )
2006
def dot_line_plot(name,
2010
background = "white light_gray",
2016
series_legend = False,
2023
series_colors = None):
2025
- Function to plot graphics using dots and lines.
2027
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)
2031
name - Name of the desired output file, no need to input the .svg as it will be added at runtim;
2032
data - The list, list of lists or dictionary holding the data to be plotted;
2033
width, height - Dimensions of the output image;
2034
background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient.
2035
If left None, a gray to white gradient will be generated;
2036
border - Distance in pixels of a square border into which the graphics will be drawn;
2037
axis - Whether or not the axis are to be drawn;
2038
dash - Boolean or a list or a dictionary of booleans indicating whether or not the associated series should be drawn in dashed mode;
2039
dots - Whether or not dots should be drawn on each point;
2040
grid - Whether or not the gris is to be drawn;
2041
series_legend - Whether or not the legend is to be drawn;
2042
x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis;
2043
x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted;
2044
x_title - Whether or not to plot a title over the x axis.
2045
y_title - Whether or not to plot a title over the y axis.
2049
data = [0, 1, 3, 8, 9, 0, 10, 10, 2, 1]
2050
CairoPlot.dot_line_plot('teste', data, 400, 300)
2052
data = { "john" : [10, 10, 10, 10, 30], "mary" : [0, 0, 3, 5, 15], "philip" : [13, 32, 11, 25, 2] }
2053
x_labels = ["jan/2008", "feb/2008", "mar/2008", "apr/2008", "may/2008" ]
2054
CairoPlot.dot_line_plot( 'test', data, 400, 300, axis = True, grid = True,
2055
series_legend = True, x_labels = x_labels )
2057
plot = DotLinePlot( name, data, width, height, background, border,
2058
axis, dash, dots, grid, series_legend, x_labels, y_labels,
2059
x_bounds, y_bounds, x_title, y_title, series_colors )
2063
def function_plot(name,
2067
background = "white light_gray",
2073
series_legend = False,
2080
series_colors = None,
2084
- Function to plot functions.
2086
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)
2090
name - Name of the desired output file, no need to input the .svg as it will be added at runtim;
2091
data - The list, list of lists or dictionary holding the data to be plotted;
2092
width, height - Dimensions of the output image;
2093
background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient.
2094
If left None, a gray to white gradient will be generated;
2095
border - Distance in pixels of a square border into which the graphics will be drawn;
2096
axis - Whether or not the axis are to be drawn;
2097
grid - Whether or not the gris is to be drawn;
2098
dots - Whether or not dots should be shown at each point;
2099
x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis;
2100
x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted;
2101
step - the horizontal distance from one point to the other. The smaller, the smoother the curve will be;
2102
discrete - whether or not the function should be plotted in discrete format.
2106
data = lambda x : x**2
2107
CairoPlot.function_plot('function4', data, 400, 300, grid = True, x_bounds=(-10,10), step = 0.1)
2110
plot = FunctionPlot( name, data, width, height, background, border,
2111
axis, discrete, dots, grid, series_legend, x_labels, y_labels,
2112
x_bounds, y_bounds, x_title, y_title, series_colors, step )
2116
def pie_plot( name, data, width, height, background = "white light_gray", gradient = False, shadow = False, colors = None ):
2119
- Function to plot pie graphics.
2121
pie_plot(name, data, width, height, background = "white light_gray", gradient = False, colors = None)
2125
name - Name of the desired output file, no need to input the .svg as it will be added at runtim;
2126
data - The list, list of lists or dictionary holding the data to be plotted;
2127
width, height - Dimensions of the output image;
2128
background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient.
2129
If left None, a gray to white gradient will be generated;
2130
gradient - Whether or not the pie color will be painted with a gradient;
2131
shadow - Whether or not there will be a shadow behind the pie;
2132
colors - List of slices colors.
2136
teste_data = {"john" : 123, "mary" : 489, "philip" : 890 , "suzy" : 235}
2137
CairoPlot.pie_plot("pie_teste", teste_data, 500, 500)
2140
plot = PiePlot( name, data, width, height, background, gradient, shadow, colors )
2144
def donut_plot(name, data, width, height, background = "white light_gray", gradient = False, shadow = False, colors = None, inner_radius = -1):
2147
- Function to plot donut graphics.
2149
donut_plot(name, data, width, height, background = "white light_gray", gradient = False, inner_radius = -1)
2153
name - Name of the desired output file, no need to input the .svg as it will be added at runtim;
2154
data - The list, list of lists or dictionary holding the data to be plotted;
2155
width, height - Dimensions of the output image;
2156
background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient.
2157
If left None, a gray to white gradient will be generated;
2158
shadow - Whether or not there will be a shadow behind the donut;
2159
gradient - Whether or not the donut color will be painted with a gradient;
2160
colors - List of slices colors;
2161
inner_radius - The radius of the donut's inner circle.
2165
teste_data = {"john" : 123, "mary" : 489, "philip" : 890 , "suzy" : 235}
2166
CairoPlot.donut_plot("donut_teste", teste_data, 500, 500)
2169
plot = DonutPlot(name, data, width, height, background, gradient, shadow, colors, inner_radius)
2173
def gantt_chart(name, pieces, width, height, x_labels, y_labels, colors):
2176
- Function to generate Gantt Charts.
2178
gantt_chart(name, pieces, width, height, x_labels, y_labels, colors):
2182
name - Name of the desired output file, no need to input the .svg as it will be added at runtim;
2183
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;
2184
width, height - Dimensions of the output image;
2185
x_labels - A list of names for each of the vertical lines;
2186
y_labels - A list of names for each of the horizontal spaces;
2187
colors - List containing the colors expected for each of the horizontal spaces
2191
pieces = [ (0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,8)]
2192
x_labels = [ 'teste01', 'teste02', 'teste03', 'teste04']
2193
y_labels = [ '0001', '0002', '0003', '0004', '0005', '0006', '0007', '0008', '0009', '0010' ]
2194
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) ]
2195
CairoPlot.gantt_chart('gantt_teste', pieces, 600, 300, x_labels, y_labels, colors)
2198
plot = GanttChart(name, pieces, width, height, x_labels, y_labels, colors)
2202
def vertical_bar_plot(name,
2206
background = "white light_gray",
2208
display_values = False,
2210
rounded_corners = False,
2212
three_dimension = False,
2213
series_labels = None,
2219
#TODO: Fix docstring for vertical_bar_plot
2221
- Function to generate vertical Bar Plot Charts.
2223
bar_plot(name, data, width, height, background, border, grid, rounded_corners, three_dimension,
2224
x_labels, y_labels, x_bounds, y_bounds, colors):
2228
name - Name of the desired output file, no need to input the .svg as it will be added at runtime;
2229
data - The list, list of lists or dictionary holding the data to be plotted;
2230
width, height - Dimensions of the output image;
2231
background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient.
2232
If left None, a gray to white gradient will be generated;
2233
border - Distance in pixels of a square border into which the graphics will be drawn;
2234
grid - Whether or not the gris is to be drawn;
2235
rounded_corners - Whether or not the bars should have rounded corners;
2236
three_dimension - Whether or not the bars should be drawn in pseudo 3D;
2237
x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis;
2238
x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted;
2239
colors - List containing the colors expected for each of the bars.
2243
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2244
CairoPlot.vertical_bar_plot ('bar2', data, 400, 300, border = 20, grid = True, rounded_corners = False)
2247
plot = VerticalBarPlot(name, data, width, height, background, border,
2248
display_values, grid, rounded_corners, stack, three_dimension,
2249
series_labels, x_labels, y_labels, x_bounds, y_bounds, colors)
2253
def horizontal_bar_plot(name,
2257
background = "white light_gray",
2259
display_values = False,
2261
rounded_corners = False,
2263
three_dimension = False,
2264
series_labels = None,
2271
#TODO: Fix docstring for horizontal_bar_plot
2273
- Function to generate Horizontal Bar Plot Charts.
2275
bar_plot(name, data, width, height, background, border, grid, rounded_corners, three_dimension,
2276
x_labels, y_labels, x_bounds, y_bounds, colors):
2280
name - Name of the desired output file, no need to input the .svg as it will be added at runtime;
2281
data - The list, list of lists or dictionary holding the data to be plotted;
2282
width, height - Dimensions of the output image;
2283
background - A 3 element tuple representing the rgb color expected for the background or a new cairo linear gradient.
2284
If left None, a gray to white gradient will be generated;
2285
border - Distance in pixels of a square border into which the graphics will be drawn;
2286
grid - Whether or not the gris is to be drawn;
2287
rounded_corners - Whether or not the bars should have rounded corners;
2288
three_dimension - Whether or not the bars should be drawn in pseudo 3D;
2289
x_labels, y_labels - lists of strings containing the horizontal and vertical labels for the axis;
2290
x_bounds, y_bounds - tuples containing the lower and upper value bounds for the data to be plotted;
2291
colors - List containing the colors expected for each of the bars.
2295
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
2296
CairoPlot.bar_plot ('bar2', data, 400, 300, border = 20, grid = True, rounded_corners = False)
2299
plot = HorizontalBarPlot(name, data, width, height, background, border,
2300
display_values, grid, rounded_corners, stack, three_dimension,
2301
series_labels, x_labels, y_labels, x_bounds, y_bounds, colors)
2305
def stream_chart(name,
2309
background = "white light_gray",
2312
series_legend = None,
2318
#TODO: Fix docstring for horizontal_bar_plot
2319
plot = StreamChart(name, data, width, height, background, border,
2320
grid, series_legend, x_labels, x_bounds, y_bounds, colors)
2325
if __name__ == "__main__":