[MERGE] Remove the embedded pychart library, and use the online version
[odoo/odoo.git] /
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright (C) 2000-2005 by Yasushi Saito (yasushi.saito@gmail.com)
4
5 # Jockey is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2, or (at your option) any
8 # later version.
9 #
10 # Jockey is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13 # for more details.
14 #
15 import canvas
16 import fill_style
17 import line_style
18 import pychart_util
19 import chart_object
20 import legend
21 import font
22 import color
23 from pychart_types import *
24
25 class T(chart_object.T):
26     """Plots sector diagram which can be superimposed on one another.
27     Sector diagrams are also known as wind roses"""
28     keys = {
29         "start_angle" : (NumType, 90, ""), # top of chart (north)
30         "center" : (CoordType, None, ""),
31         "base_radius" : (NumType, None, ""),
32         "line_style" : (line_style.T, line_style.T(color=color.black, width=0.3), ""),
33         "fill_styles" : (list, fill_style.standards.list()[:],
34                          """The fill style of each item. The length of the
35                          list should be equal to the length of the data.
36                          """),
37         "sector_centred":(int, 1,
38                           """Bool indicating whether the sectors should be centred on each sector_width(e.g. on 0)"""),
39         "dir_offset":  (UnitType, None,
40                         """The distance between the directions and the outermost circle. Defaults fine for most cases"""),
41         "data" : (AnyType, None, pychart_util.data_desc),
42         "label_col" : (int, 0,
43                        """The column, within "data", from which the labels of items are retrieved."""),
44         "data_col": (int, 1,
45                      """ The column, within "data", from which the data values are retrieved."""),
46         "dir_line_style": (line_style.T, None, ""),
47         "dir_fill_style": (fill_style.T, fill_style.default, ""),
48         "shadow": (ShadowType, None, pychart_util.shadow_desc),
49         "sector_width": (int, None, ""), # automatically generated
50         }
51
52     def __init__(self, colour=True, **args):
53         chart_object.T.init(self, args)
54         if colour:
55             # the theme.color flag does not seem to affect the fill_style.standards,
56             #besides, I want the first two colors to resemble those of gnuplot's postscript terminal
57             self.fill_styles = [fill_style.Plain(bgcolor=color.red),
58                                 fill_style.Plain(bgcolor=color.green),
59                                 fill_style.Plain(bgcolor=color.blue),
60                                 fill_style.Plain(bgcolor=color.magenta)]
61
62     def check_integrity(self):
63         nSectors = len(self.data[0][self.data_col])
64         if (360%nSectors != 0):
65             raise Exception('Length of dataset ' + str(nSectors) + ' not a divisor of 360 degrees!')
66         for dataset in self.data:
67             length = len(dataset[self.data_col])
68             if length != nSectors:
69                 raise Exception('Lengths of datasets given is different!')
70             for val in dataset[self.data_col]:
71                 if (val < 0) | (val > 1):
72                     raise Exception('Data value ' + str(val) + ' not between 0 and 1!')
73         self.sector_width = 360/nSectors
74         self.type_check()
75
76     def get_data_range(self, which):
77         return (0, 1)
78
79     def get_legend_entry(self):
80         legends = []
81         i = 0
82         for dataset in self.data:
83             fill = self.fill_styles[i]
84             i = (i + 1) % len(self.fill_styles)
85             legends.append(legend.Entry(line_style=self.line_style,
86                                     fill_style=fill,
87                                     label=dataset[self.label_col]))
88         return legends
89
90     def draw(self, ar, can):
91         center = self.center
92         if not center:
93             center = (ar.loc[0] + ar.size[0]/2.0,
94                             ar.loc[1] + ar.size[1]/2.0)
95         base_radius = self.base_radius # the maximum radius of a wedge
96         if not base_radius:
97             base_radius = min(ar.size[0]/2.0, ar.size[1]/2.0) #* 0.8
98
99         sector_decrement = 1./(len(self.data)*2) * self.sector_width # each following sector diagram will have its sector width decremented by half this amount (in degrees)
100         i = 0
101         for dataset in self.data:
102             cur_angle = self.start_angle
103             if self.sector_centred:
104                 cur_angle -= self.sector_width/2.
105             fill = self.fill_styles[i]
106             x_center = center[0]
107             y_center = center[1]
108
109             if not i: # draw directions around sector diagram once off
110                 dir_offset = base_radius + (self.dir_offset or base_radius * 0.04)
111                 directions = ['N', 'E', 'S', 'W']
112                 angle = self.start_angle
113
114                 can.ellipsis(line_style.T(color=color.black, width=0.3, dash=line_style.dash1), None,
115                              x_center, y_center, base_radius, 1,
116                              0, 360) #
117
118                 for d in directions:
119                     x_label, y_label = pychart_util.rotate(dir_offset, 0, angle) # coords for bottom left corner of box
120                     tw = font.text_width(d)
121                     half = 1/3. # normal arithmetic does not seem to apply to these text_box objects...
122                     if (angle == 0): # east
123                         y_label -= font.text_height(d)[0]*half # move down half
124                     elif (angle == -180): # west
125                         y_label -= font.text_height(d)[0]*half # move down half
126                         x_label -= font.text_width(d) # move left full
127                     elif (angle == 90): # north
128                         x_label -= font.text_height(d)[0]*half # move left half
129                     elif (angle == -90): # south
130                         y_label -= font.text_height(d)[0]*.8 # move down (couldn't figure out how to set this dynamically so I fudged...)
131                         x_label -= font.text_height(d)[0]*half # move left half
132                     canvas.show(x_label + x_center, y_label + y_center, d)
133                     angle -= 360/len(directions)
134
135             for val in dataset[self.data_col]: # now draw the sectors
136                 radius = base_radius*val # scale the radius
137                 start = cur_angle-self.sector_width+i*sector_decrement
138                 stop = cur_angle-i*sector_decrement # these may seem confusing, but remember that we need to go counterclockwise
139
140                 can.ellipsis(self.line_style, fill,
141                              x_center, y_center, radius, 1, start, stop, self.shadow) 
142                 cur_angle = (cur_angle - self.sector_width) % 360 # we want to go in anticlockwise direction (North, West, South, etc. as in meteorology)
143             i = (i + 1) % len(self.fill_styles)
144