| 1 | #!/usr/bin/env python |
| 2 | # -*- coding: utf-8 -*- |
| 3 | |
| 4 | # Serie.py |
| 5 | # |
| 6 | # Copyright (c) 2008 Magnun Leno da Silva |
| 7 | # |
| 8 | # Author: Magnun Leno da Silva <magnun.leno@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: Rodrigo Moreiro Araujo <alf.rodrigo@gmail.com> |
| 26 | |
| 27 | #import cairoplot |
| 28 | import doctest |
| 29 | |
| 30 | NUMTYPES = (int, float, long) |
| 31 | LISTTYPES = (list, tuple) |
| 32 | STRTYPES = (str, unicode) |
| 33 | FILLING_TYPES = ['linear', 'solid', 'gradient'] |
| 34 | DEFAULT_COLOR_FILLING = 'solid' |
| 35 | #TODO: Define default color list |
| 36 | DEFAULT_COLOR_LIST = None |
| 37 | |
| 38 | class Data(object): |
| 39 | ''' |
| 40 | Class that models the main data structure. |
| 41 | It can hold: |
| 42 | - a number type (int, float or long) |
| 43 | - a tuple, witch represents a point and can have 2 or 3 items (x,y,z) |
| 44 | - if a list is passed it will be converted to a tuple. |
| 45 | |
| 46 | obs: In case a tuple is passed it will convert to tuple |
| 47 | ''' |
| 48 | def __init__(self, data=None, name=None, parent=None): |
| 49 | ''' |
| 50 | Starts main atributes from the Data class |
| 51 | @name - Name for each point; |
| 52 | @content - The real data, can be an int, float, long or tuple, which |
| 53 | represents a point (x,y) or (x,y,z); |
| 54 | @parent - A pointer that give the data access to it's parent. |
| 55 | |
| 56 | Usage: |
| 57 | >>> d = Data(name='empty'); print d |
| 58 | empty: () |
| 59 | >>> d = Data((1,1),'point a'); print d |
| 60 | point a: (1, 1) |
| 61 | >>> d = Data((1,2,3),'point b'); print d |
| 62 | point b: (1, 2, 3) |
| 63 | >>> d = Data([2,3],'point c'); print d |
| 64 | point c: (2, 3) |
| 65 | >>> d = Data(12, 'simple value'); print d |
| 66 | simple value: 12 |
| 67 | ''' |
| 68 | # Initial values |
| 69 | self.__content = None |
| 70 | self.__name = None |
| 71 | |
| 72 | # Setting passed values |
| 73 | self.parent = parent |
| 74 | self.name = name |
| 75 | self.content = data |
| 76 | |
| 77 | # Name property |
| 78 | @apply |
| 79 | def name(): |
| 80 | doc = ''' |
| 81 | Name is a read/write property that controls the input of name. |
| 82 | - If passed an invalid value it cleans the name with None |
| 83 | |
| 84 | Usage: |
| 85 | >>> d = Data(13); d.name = 'name_test'; print d |
| 86 | name_test: 13 |
| 87 | >>> d.name = 11; print d |
| 88 | 13 |
| 89 | >>> d.name = 'other_name'; print d |
| 90 | other_name: 13 |
| 91 | >>> d.name = None; print d |
| 92 | 13 |
| 93 | >>> d.name = 'last_name'; print d |
| 94 | last_name: 13 |
| 95 | >>> d.name = ''; print d |
| 96 | 13 |
| 97 | ''' |
| 98 | def fget(self): |
| 99 | ''' |
| 100 | returns the name as a string |
| 101 | ''' |
| 102 | return self.__name |
| 103 | |
| 104 | def fset(self, name): |
| 105 | ''' |
| 106 | Sets the name of the Data |
| 107 | ''' |
| 108 | if type(name) in STRTYPES and len(name) > 0: |
| 109 | self.__name = name |
| 110 | else: |
| 111 | self.__name = None |
| 112 | |
| 113 | |
| 114 | |
| 115 | return property(**locals()) |
| 116 | |
| 117 | # Content property |
| 118 | @apply |
| 119 | def content(): |
| 120 | doc = ''' |
| 121 | Content is a read/write property that validate the data passed |
| 122 | and return it. |
| 123 | |
| 124 | Usage: |
| 125 | >>> d = Data(); d.content = 13; d.content |
| 126 | 13 |
| 127 | >>> d = Data(); d.content = (1,2); d.content |
| 128 | (1, 2) |
| 129 | >>> d = Data(); d.content = (1,2,3); d.content |
| 130 | (1, 2, 3) |
| 131 | >>> d = Data(); d.content = [1,2,3]; d.content |
| 132 | (1, 2, 3) |
| 133 | >>> d = Data(); d.content = [1.5,.2,3.3]; d.content |
| 134 | (1.5, 0.20000000000000001, 3.2999999999999998) |
| 135 | ''' |
| 136 | def fget(self): |
| 137 | ''' |
| 138 | Return the content of Data |
| 139 | ''' |
| 140 | return self.__content |
| 141 | |
| 142 | def fset(self, data): |
| 143 | ''' |
| 144 | Ensures that data is a valid tuple/list or a number (int, float |
| 145 | or long) |
| 146 | ''' |
| 147 | # Type: None |
| 148 | if data is None: |
| 149 | self.__content = None |
| 150 | return |
| 151 | |
| 152 | # Type: Int or Float |
| 153 | elif type(data) in NUMTYPES: |
| 154 | self.__content = data |
| 155 | |
| 156 | # Type: List or Tuple |
| 157 | elif type(data) in LISTTYPES: |
| 158 | # Ensures the correct size |
| 159 | if len(data) not in (2, 3): |
| 160 | raise TypeError, "Data (as list/tuple) must have 2 or 3 items" |
| 161 | return |
| 162 | |
| 163 | # Ensures that all items in list/tuple is a number |
| 164 | isnum = lambda x : type(x) not in NUMTYPES |
| 165 | |
| 166 | if max(map(isnum, data)): |
| 167 | # An item in data isn't an int or a float |
| 168 | raise TypeError, "All content of data must be a number (int or float)" |
| 169 | |
| 170 | # Convert the tuple to list |
| 171 | if type(data) is list: |
| 172 | data = tuple(data) |
| 173 | |
| 174 | # Append a copy and sets the type |
| 175 | self.__content = data[:] |
| 176 | |
| 177 | # Unknown type! |
| 178 | else: |
| 179 | self.__content = None |
| 180 | raise TypeError, "Data must be an int, float or a tuple with two or three items" |
| 181 | return |
| 182 | |
| 183 | return property(**locals()) |
| 184 | |
| 185 | |
| 186 | def clear(self): |
| 187 | ''' |
| 188 | Clear the all Data (content, name and parent) |
| 189 | ''' |
| 190 | self.content = None |
| 191 | self.name = None |
| 192 | self.parent = None |
| 193 | |
| 194 | def copy(self): |
| 195 | ''' |
| 196 | Returns a copy of the Data structure |
| 197 | ''' |
| 198 | # The copy |
| 199 | new_data = Data() |
| 200 | if self.content is not None: |
| 201 | # If content is a point |
| 202 | if type(self.content) is tuple: |
| 203 | new_data.__content = self.content[:] |
| 204 | |
| 205 | # If content is a number |
| 206 | else: |
| 207 | new_data.__content = self.content |
| 208 | |
| 209 | # If it has a name |
| 210 | if self.name is not None: |
| 211 | new_data.__name = self.name |
| 212 | |
| 213 | return new_data |
| 214 | |
| 215 | def __str__(self): |
| 216 | ''' |
| 217 | Return a string representation of the Data structure |
| 218 | ''' |
| 219 | if self.name is None: |
| 220 | if self.content is None: |
| 221 | return '' |
| 222 | return str(self.content) |
| 223 | else: |
| 224 | if self.content is None: |
| 225 | return self.name+": ()" |
| 226 | return self.name+": "+str(self.content) |
| 227 | |
| 228 | def __len__(self): |
| 229 | ''' |
| 230 | Return the length of the Data. |
| 231 | - If it's a number return 1; |
| 232 | - If it's a list return it's length; |
| 233 | - If its None return 0. |
| 234 | ''' |
| 235 | if self.content is None: |
| 236 | return 0 |
| 237 | elif type(self.content) in NUMTYPES: |
| 238 | return 1 |
| 239 | return len(self.content) |
| 240 | |
| 241 | |
| 242 | |
| 243 | |
| 244 | class Group(object): |
| 245 | ''' |
| 246 | Class that models a group of data. Every value (int, float, long, tuple |
| 247 | or list) passed is converted to a list of Data. |
| 248 | It can receive: |
| 249 | - A single number (int, float, long); |
| 250 | - A list of numbers; |
| 251 | - A tuple of numbers; |
| 252 | - An instance of Data; |
| 253 | - A list of Data; |
| 254 | |
| 255 | Obs: If a tuple with 2 or 3 items is passed it is converted to a point. |
| 256 | If a tuple with only 1 item is passed it's converted to a number; |
| 257 | If a tuple with more than 2 items is passed it's converted to a |
| 258 | list of numbers |
| 259 | ''' |
| 260 | def __init__(self, group=None, name=None, parent=None): |
| 261 | ''' |
| 262 | Starts main atributes in Group instance. |
| 263 | @data_list - a list of data which forms the group; |
| 264 | @range - a range that represent the x axis of possible functions; |
| 265 | @name - name of the data group; |
| 266 | @parent - the Serie parent of this group. |
| 267 | |
| 268 | Usage: |
| 269 | >>> g = Group(13, 'simple number'); print g |
| 270 | simple number ['13'] |
| 271 | >>> g = Group((1,2), 'simple point'); print g |
| 272 | simple point ['(1, 2)'] |
| 273 | >>> g = Group([1,2,3,4], 'list of numbers'); print g |
| 274 | list of numbers ['1', '2', '3', '4'] |
| 275 | >>> g = Group((1,2,3,4),'int in tuple'); print g |
| 276 | int in tuple ['1', '2', '3', '4'] |
| 277 | >>> g = Group([(1,2),(2,3),(3,4)], 'list of points'); print g |
| 278 | list of points ['(1, 2)', '(2, 3)', '(3, 4)'] |
| 279 | >>> g = Group([[1,2,3],[1,2,3]], '2D coordinate lists'); print g |
| 280 | 2D coordinated lists ['(1, 1)', '(2, 2)', '(3, 3)'] |
| 281 | >>> g = Group([[1,2],[1,2],[1,2]], '3D coordinate lists'); print g |
| 282 | 3D coordinated lists ['(1, 1, 1)', '(2, 2, 2)'] |
| 283 | ''' |
| 284 | # Initial values |
| 285 | self.__data_list = [] |
| 286 | self.__range = [] |
| 287 | self.__name = None |
| 288 | |
| 289 | |
| 290 | self.parent = parent |
| 291 | self.name = name |
| 292 | self.data_list = group |
| 293 | |
| 294 | # Name property |
| 295 | @apply |
| 296 | def name(): |
| 297 | doc = ''' |
| 298 | Name is a read/write property that controls the input of name. |
| 299 | - If passed an invalid value it cleans the name with None |
| 300 | |
| 301 | Usage: |
| 302 | >>> g = Group(13); g.name = 'name_test'; print g |
| 303 | name_test ['13'] |
| 304 | >>> g.name = 11; print g |
| 305 | ['13'] |
| 306 | >>> g.name = 'other_name'; print g |
| 307 | other_name ['13'] |
| 308 | >>> g.name = None; print g |
| 309 | ['13'] |
| 310 | >>> g.name = 'last_name'; print g |
| 311 | last_name ['13'] |
| 312 | >>> g.name = ''; print g |
| 313 | ['13'] |
| 314 | ''' |
| 315 | def fget(self): |
| 316 | ''' |
| 317 | Returns the name as a string |
| 318 | ''' |
| 319 | return self.__name |
| 320 | |
| 321 | def fset(self, name): |
| 322 | ''' |
| 323 | Sets the name of the Group |
| 324 | ''' |
| 325 | if type(name) in STRTYPES and len(name) > 0: |
| 326 | self.__name = name |
| 327 | else: |
| 328 | self.__name = None |
| 329 | |
| 330 | return property(**locals()) |
| 331 | |
| 332 | # data_list property |
| 333 | @apply |
| 334 | def data_list(): |
| 335 | doc = ''' |
| 336 | The data_list is a read/write property that can be a list of |
| 337 | numbers, a list of points or a list of 2 or 3 coordinate lists. This |
| 338 | property uses mainly the self.add_data method. |
| 339 | |
| 340 | Usage: |
| 341 | >>> g = Group(); g.data_list = 13; print g |
| 342 | ['13'] |
| 343 | >>> g.data_list = (1,2); print g |
| 344 | ['(1, 2)'] |
| 345 | >>> g.data_list = Data((1,2),'point a'); print g |
| 346 | ['point a: (1, 2)'] |
| 347 | >>> g.data_list = [1,2,3]; print g |
| 348 | ['1', '2', '3'] |
| 349 | >>> g.data_list = (1,2,3,4); print g |
| 350 | ['1', '2', '3', '4'] |
| 351 | >>> g.data_list = [(1,2),(2,3),(3,4)]; print g |
| 352 | ['(1, 2)', '(2, 3)', '(3, 4)'] |
| 353 | >>> g.data_list = [[1,2],[1,2]]; print g |
| 354 | ['(1, 1)', '(2, 2)'] |
| 355 | >>> g.data_list = [[1,2],[1,2],[1,2]]; print g |
| 356 | ['(1, 1, 1)', '(2, 2, 2)'] |
| 357 | >>> g.range = (10); g.data_list = lambda x:x**2; print g |
| 358 | ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 4.0)', '(3.0, 9.0)', '(4.0, 16.0)', '(5.0, 25.0)', '(6.0, 36.0)', '(7.0, 49.0)', '(8.0, 64.0)', '(9.0, 81.0)'] |
| 359 | ''' |
| 360 | def fget(self): |
| 361 | ''' |
| 362 | Returns the value of data_list |
| 363 | ''' |
| 364 | return self.__data_list |
| 365 | |
| 366 | def fset(self, group): |
| 367 | ''' |
| 368 | Ensures that group is valid. |
| 369 | ''' |
| 370 | # None |
| 371 | if group is None: |
| 372 | self.__data_list = [] |
| 373 | |
| 374 | # Int/float/long or Instance of Data |
| 375 | elif type(group) in NUMTYPES or isinstance(group, Data): |
| 376 | # Clean data_list |
| 377 | self.__data_list = [] |
| 378 | self.add_data(group) |
| 379 | |
| 380 | # One point |
| 381 | elif type(group) is tuple and len(group) in (2,3): |
| 382 | self.__data_list = [] |
| 383 | self.add_data(group) |
| 384 | |
| 385 | # list of items |
| 386 | elif type(group) in LISTTYPES and type(group[0]) is not list: |
| 387 | # Clean data_list |
| 388 | self.__data_list = [] |
| 389 | for item in group: |
| 390 | # try to append and catch an exception |
| 391 | self.add_data(item) |
| 392 | |
| 393 | # function lambda |
| 394 | elif callable(group): |
| 395 | # Explicit is better than implicit |
| 396 | function = group |
| 397 | # Has range |
| 398 | if len(self.range) is not 0: |
| 399 | # Clean data_list |
| 400 | self.__data_list = [] |
| 401 | # Generate values for the lambda function |
| 402 | for x in self.range: |
| 403 | #self.add_data((x,round(group(x),2))) |
| 404 | self.add_data((x,function(x))) |
| 405 | |
| 406 | # Only have range in parent |
| 407 | elif self.parent is not None and len(self.parent.range) is not 0: |
| 408 | # Copy parent range |
| 409 | self.__range = self.parent.range[:] |
| 410 | # Clean data_list |
| 411 | self.__data_list = [] |
| 412 | # Generate values for the lambda function |
| 413 | for x in self.range: |
| 414 | #self.add_data((x,round(group(x),2))) |
| 415 | self.add_data((x,function(x))) |
| 416 | |
| 417 | # Don't have range anywhere |
| 418 | else: |
| 419 | # x_data don't exist |
| 420 | raise Exception, "Data argument is valid but to use function type please set x_range first" |
| 421 | |
| 422 | # Coordinate Lists |
| 423 | elif type(group) in LISTTYPES and type(group[0]) is list: |
| 424 | # Clean data_list |
| 425 | self.__data_list = [] |
| 426 | data = [] |
| 427 | if len(group) == 3: |
| 428 | data = zip(group[0], group[1], group[2]) |
| 429 | elif len(group) == 2: |
| 430 | data = zip(group[0], group[1]) |
| 431 | else: |
| 432 | raise TypeError, "Only one list of coordinates was received." |
| 433 | |
| 434 | for item in data: |
| 435 | self.add_data(item) |
| 436 | |
| 437 | else: |
| 438 | raise TypeError, "Group type not supported" |
| 439 | |
| 440 | return property(**locals()) |
| 441 | |
| 442 | @apply |
| 443 | def range(): |
| 444 | doc = ''' |
| 445 | The range is a read/write property that generates a range of values |
| 446 | for the x axis of the functions. When passed a tuple it almost works |
| 447 | like the built-in range funtion: |
| 448 | - 1 item, represent the end of the range started from 0; |
| 449 | - 2 items, represents the start and the end, respectively; |
| 450 | - 3 items, the last one represents the step; |
| 451 | |
| 452 | When passed a list the range function understands as a valid range. |
| 453 | |
| 454 | Usage: |
| 455 | >>> g = Group(); g.range = 10; print g.range |
| 456 | [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0] |
| 457 | >>> g = Group(); g.range = (5); print g.range |
| 458 | [0.0, 1.0, 2.0, 3.0, 4.0] |
| 459 | >>> g = Group(); g.range = (1,7); print g.range |
| 460 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] |
| 461 | >>> g = Group(); g.range = (0,10,2); print g.range |
| 462 | [0.0, 2.0, 4.0, 6.0, 8.0] |
| 463 | >>> |
| 464 | >>> g = Group(); g.range = [0]; print g.range |
| 465 | [0.0] |
| 466 | >>> g = Group(); g.range = [0,10,20]; print g.range |
| 467 | [0.0, 10.0, 20.0] |
| 468 | ''' |
| 469 | def fget(self): |
| 470 | ''' |
| 471 | Returns the range |
| 472 | ''' |
| 473 | return self.__range |
| 474 | |
| 475 | def fset(self, x_range): |
| 476 | ''' |
| 477 | Controls the input of a valid type and generate the range |
| 478 | ''' |
| 479 | # if passed a simple number convert to tuple |
| 480 | if type(x_range) in NUMTYPES: |
| 481 | x_range = (x_range,) |
| 482 | |
| 483 | # A list, just convert to float |
| 484 | if type(x_range) is list and len(x_range) > 0: |
| 485 | # Convert all to float |
| 486 | x_range = map(float, x_range) |
| 487 | # Prevents repeated values and convert back to list |
| 488 | self.__range = list(set(x_range[:])) |
| 489 | # Sort the list to ascending order |
| 490 | self.__range.sort() |
| 491 | |
| 492 | # A tuple, must check the lengths and generate the values |
| 493 | elif type(x_range) is tuple and len(x_range) in (1,2,3): |
| 494 | # Convert all to float |
| 495 | x_range = map(float, x_range) |
| 496 | |
| 497 | # Inital values |
| 498 | start = 0.0 |
| 499 | step = 1.0 |
| 500 | end = 0.0 |
| 501 | |
| 502 | # Only the end and it can't be less or iqual to 0 |
| 503 | if len(x_range) is 1 and x_range > 0: |
| 504 | end = x_range[0] |
| 505 | |
| 506 | # The start and the end but the start must be less then the end |
| 507 | elif len(x_range) is 2 and x_range[0] < x_range[1]: |
| 508 | start = x_range[0] |
| 509 | end = x_range[1] |
| 510 | |
| 511 | # All 3, but the start must be less then the end |
| 512 | elif x_range[0] <= x_range[1]: |
| 513 | start = x_range[0] |
| 514 | end = x_range[1] |
| 515 | step = x_range[2] |
| 516 | |
| 517 | # Starts the range |
| 518 | self.__range = [] |
| 519 | # Generate the range |
| 520 | # Can't use the range function because it doesn't support float values |
| 521 | while start < end: |
| 522 | self.__range.append(start) |
| 523 | start += step |
| 524 | |
| 525 | # Incorrect type |
| 526 | else: |
| 527 | raise Exception, "x_range must be a list with one or more items or a tuple with 2 or 3 items" |
| 528 | |
| 529 | return property(**locals()) |
| 530 | |
| 531 | def add_data(self, data, name=None): |
| 532 | ''' |
| 533 | Append a new data to the data_list. |
| 534 | - If data is an instance of Data, append it |
| 535 | - If it's an int, float, tuple or list create an instance of Data and append it |
| 536 | |
| 537 | Usage: |
| 538 | >>> g = Group() |
| 539 | >>> g.add_data(12); print g |
| 540 | ['12'] |
| 541 | >>> g.add_data(7,'other'); print g |
| 542 | ['12', 'other: 7'] |
| 543 | >>> |
| 544 | >>> g = Group() |
| 545 | >>> g.add_data((1,1),'a'); print g |
| 546 | ['a: (1, 1)'] |
| 547 | >>> g.add_data((2,2),'b'); print g |
| 548 | ['a: (1, 1)', 'b: (2, 2)'] |
| 549 | >>> |
| 550 | >>> g.add_data(Data((1,2),'c')); print g |
| 551 | ['a: (1, 1)', 'b: (2, 2)', 'c: (1, 2)'] |
| 552 | ''' |
| 553 | if not isinstance(data, Data): |
| 554 | # Try to convert |
| 555 | data = Data(data,name,self) |
| 556 | |
| 557 | if data.content is not None: |
| 558 | self.__data_list.append(data.copy()) |
| 559 | self.__data_list[-1].parent = self |
| 560 | |
| 561 | |
| 562 | def to_list(self): |
| 563 | ''' |
| 564 | Returns the group as a list of numbers (int, float or long) or a |
| 565 | list of tuples (points 2D or 3D). |
| 566 | |
| 567 | Usage: |
| 568 | >>> g = Group([1,2,3,4],'g1'); g.to_list() |
| 569 | [1, 2, 3, 4] |
| 570 | >>> g = Group([(1,2),(2,3),(3,4)],'g2'); g.to_list() |
| 571 | [(1, 2), (2, 3), (3, 4)] |
| 572 | >>> g = Group([(1,2,3),(3,4,5)],'g2'); g.to_list() |
| 573 | [(1, 2, 3), (3, 4, 5)] |
| 574 | ''' |
| 575 | return [data.content for data in self] |
| 576 | |
| 577 | def copy(self): |
| 578 | ''' |
| 579 | Returns a copy of this group |
| 580 | ''' |
| 581 | new_group = Group() |
| 582 | new_group.__name = self.__name |
| 583 | if self.__range is not None: |
| 584 | new_group.__range = self.__range[:] |
| 585 | for data in self: |
| 586 | new_group.add_data(data.copy()) |
| 587 | return new_group |
| 588 | |
| 589 | def get_names(self): |
| 590 | ''' |
| 591 | Return a list with the names of all data in this group |
| 592 | ''' |
| 593 | names = [] |
| 594 | for data in self: |
| 595 | if data.name is None: |
| 596 | names.append('Data '+str(data.index()+1)) |
| 597 | else: |
| 598 | names.append(data.name) |
| 599 | return names |
| 600 | |
| 601 | |
| 602 | def __str__ (self): |
| 603 | ''' |
| 604 | Returns a string representing the Group |
| 605 | ''' |
| 606 | ret = "" |
| 607 | if self.name is not None: |
| 608 | ret += self.name + " " |
| 609 | if len(self) > 0: |
| 610 | list_str = [str(item) for item in self] |
| 611 | ret += str(list_str) |
| 612 | else: |
| 613 | ret += "[]" |
| 614 | return ret |
| 615 | |
| 616 | def __getitem__(self, key): |
| 617 | ''' |
| 618 | Makes a Group iterable, based in the data_list property |
| 619 | ''' |
| 620 | return self.data_list[key] |
| 621 | |
| 622 | def __len__(self): |
| 623 | ''' |
| 624 | Returns the length of the Group, based in the data_list property |
| 625 | ''' |
| 626 | return len(self.data_list) |
| 627 | |
| 628 | |
| 629 | class Colors(object): |
| 630 | ''' |
| 631 | Class that models the colors its labels (names) and its properties, RGB |
| 632 | and filling type. |
| 633 | |
| 634 | It can receive: |
| 635 | - A list where each item is a list with 3 or 4 items. The |
| 636 | first 3 items represent the RGB values and the last argument |
| 637 | defines the filling type. The list will be converted to a dict |
| 638 | and each color will receve a name based in its position in the |
| 639 | list. |
| 640 | - A dictionary where each key will be the color name and its item |
| 641 | can be a list with 3 or 4 items. The first 3 items represent |
| 642 | the RGB colors and the last argument defines the filling type. |
| 643 | ''' |
| 644 | def __init__(self, color_list=None): |
| 645 | ''' |
| 646 | Start the color_list property |
| 647 | @ color_list - the list or dict contaning the colors properties. |
| 648 | ''' |
| 649 | self.__color_list = None |
| 650 | |
| 651 | self.color_list = color_list |
| 652 | |
| 653 | @apply |
| 654 | def color_list(): |
| 655 | doc = ''' |
| 656 | >>> c = Colors([[1,1,1],[2,2,2,'linear'],[3,3,3,'gradient']]) |
| 657 | >>> print c.color_list |
| 658 | {'Color 2': [2, 2, 2, 'linear'], 'Color 3': [3, 3, 3, 'gradient'], 'Color 1': [1, 1, 1, 'solid']} |
| 659 | >>> c.color_list = [[1,1,1],(2,2,2,'solid'),(3,3,3,'linear')] |
| 660 | >>> print c.color_list |
| 661 | {'Color 2': [2, 2, 2, 'solid'], 'Color 3': [3, 3, 3, 'linear'], 'Color 1': [1, 1, 1, 'solid']} |
| 662 | >>> c.color_list = {'a':[1,1,1],'b':(2,2,2,'solid'),'c':(3,3,3,'linear'), 'd':(4,4,4)} |
| 663 | >>> print c.color_list |
| 664 | {'a': [1, 1, 1, 'solid'], 'c': [3, 3, 3, 'linear'], 'b': [2, 2, 2, 'solid'], 'd': [4, 4, 4, 'solid']} |
| 665 | ''' |
| 666 | def fget(self): |
| 667 | ''' |
| 668 | Return the color list |
| 669 | ''' |
| 670 | return self.__color_list |
| 671 | |
| 672 | def fset(self, color_list): |
| 673 | ''' |
| 674 | Format the color list to a dictionary |
| 675 | ''' |
| 676 | if color_list is None: |
| 677 | self.__color_list = None |
| 678 | return |
| 679 | |
| 680 | if type(color_list) in LISTTYPES and type(color_list[0]) in LISTTYPES: |
| 681 | old_color_list = color_list[:] |
| 682 | color_list = {} |
| 683 | for index, color in enumerate(old_color_list): |
| 684 | if len(color) is 3 and max(map(type, color)) in NUMTYPES: |
| 685 | color_list['Color '+str(index+1)] = list(color)+[DEFAULT_COLOR_FILLING] |
| 686 | elif len(color) is 4 and max(map(type, color[:-1])) in NUMTYPES and color[-1] in FILLING_TYPES: |
| 687 | color_list['Color '+str(index+1)] = list(color) |
| 688 | else: |
| 689 | raise TypeError, "Unsuported color format" |
| 690 | elif type(color_list) is not dict: |
| 691 | raise TypeError, "Unsuported color format" |
| 692 | |
| 693 | for name, color in color_list.items(): |
| 694 | if len(color) is 3: |
| 695 | if max(map(type, color)) in NUMTYPES: |
| 696 | color_list[name] = list(color)+[DEFAULT_COLOR_FILLING] |
| 697 | else: |
| 698 | raise TypeError, "Unsuported color format" |
| 699 | elif len(color) is 4: |
| 700 | if max(map(type, color[:-1])) in NUMTYPES and color[-1] in FILLING_TYPES: |
| 701 | color_list[name] = list(color) |
| 702 | else: |
| 703 | raise TypeError, "Unsuported color format" |
| 704 | self.__color_list = color_list.copy() |
| 705 | |
| 706 | return property(**locals()) |
| 707 | |
| 708 | |
| 709 | class Series(object): |
| 710 | ''' |
| 711 | Class that models a Series (group of groups). Every value (int, float, |
| 712 | long, tuple or list) passed is converted to a list of Group or Data. |
| 713 | It can receive: |
| 714 | - a single number or point, will be converted to a Group of one Data; |
| 715 | - a list of numbers, will be converted to a group of numbers; |
| 716 | - a list of tuples, will converted to a single Group of points; |
| 717 | - a list of lists of numbers, each 'sublist' will be converted to a |
| 718 | group of numbers; |
| 719 | - a list of lists of tuples, each 'sublist' will be converted to a |
| 720 | group of points; |
| 721 | - a list of lists of lists, the content of the 'sublist' will be |
| 722 | processed as coordinated lists and the result will be converted to |
| 723 | a group of points; |
| 724 | - a Dictionary where each item can be the same of the list: number, |
| 725 | point, list of numbers, list of points or list of lists (coordinated |
| 726 | lists); |
| 727 | - an instance of Data; |
| 728 | - an instance of group. |
| 729 | ''' |
| 730 | def __init__(self, series=None, name=None, property=[], colors=None): |
| 731 | ''' |
| 732 | Starts main atributes in Group instance. |
| 733 | @series - a list, dict of data of which the series is composed; |
| 734 | @name - name of the series; |
| 735 | @property - a list/dict of properties to be used in the plots of |
| 736 | this Series |
| 737 | |
| 738 | Usage: |
| 739 | >>> print Series([1,2,3,4]) |
| 740 | ["Group 1 ['1', '2', '3', '4']"] |
| 741 | >>> print Series([[1,2,3],[4,5,6]]) |
| 742 | ["Group 1 ['1', '2', '3']", "Group 2 ['4', '5', '6']"] |
| 743 | >>> print Series((1,2)) |
| 744 | ["Group 1 ['(1, 2)']"] |
| 745 | >>> print Series([(1,2),(2,3)]) |
| 746 | ["Group 1 ['(1, 2)', '(2, 3)']"] |
| 747 | >>> print Series([[(1,2),(2,3)],[(4,5),(5,6)]]) |
| 748 | ["Group 1 ['(1, 2)', '(2, 3)']", "Group 2 ['(4, 5)', '(5, 6)']"] |
| 749 | >>> print Series([[[1,2,3],[1,2,3],[1,2,3]]]) |
| 750 | ["Group 1 ['(1, 1, 1)', '(2, 2, 2)', '(3, 3, 3)']"] |
| 751 | >>> print Series({'g1':[1,2,3], 'g2':[4,5,6]}) |
| 752 | ["g1 ['1', '2', '3']", "g2 ['4', '5', '6']"] |
| 753 | >>> print Series({'g1':[(1,2),(2,3)], 'g2':[(4,5),(5,6)]}) |
| 754 | ["g1 ['(1, 2)', '(2, 3)']", "g2 ['(4, 5)', '(5, 6)']"] |
| 755 | >>> print Series({'g1':[[1,2],[1,2]], 'g2':[[4,5],[4,5]]}) |
| 756 | ["g1 ['(1, 1)', '(2, 2)']", "g2 ['(4, 4)', '(5, 5)']"] |
| 757 | >>> print Series(Data(1,'d1')) |
| 758 | ["Group 1 ['d1: 1']"] |
| 759 | >>> print Series(Group([(1,2),(2,3)],'g1')) |
| 760 | ["g1 ['(1, 2)', '(2, 3)']"] |
| 761 | ''' |
| 762 | # Intial values |
| 763 | self.__group_list = [] |
| 764 | self.__name = None |
| 765 | self.__range = None |
| 766 | |
| 767 | # TODO: Implement colors with filling |
| 768 | self.__colors = None |
| 769 | |
| 770 | self.name = name |
| 771 | self.group_list = series |
| 772 | self.colors = colors |
| 773 | |
| 774 | # Name property |
| 775 | @apply |
| 776 | def name(): |
| 777 | doc = ''' |
| 778 | Name is a read/write property that controls the input of name. |
| 779 | - If passed an invalid value it cleans the name with None |
| 780 | |
| 781 | Usage: |
| 782 | >>> s = Series(13); s.name = 'name_test'; print s |
| 783 | name_test ["Group 1 ['13']"] |
| 784 | >>> s.name = 11; print s |
| 785 | ["Group 1 ['13']"] |
| 786 | >>> s.name = 'other_name'; print s |
| 787 | other_name ["Group 1 ['13']"] |
| 788 | >>> s.name = None; print s |
| 789 | ["Group 1 ['13']"] |
| 790 | >>> s.name = 'last_name'; print s |
| 791 | last_name ["Group 1 ['13']"] |
| 792 | >>> s.name = ''; print s |
| 793 | ["Group 1 ['13']"] |
| 794 | ''' |
| 795 | def fget(self): |
| 796 | ''' |
| 797 | Returns the name as a string |
| 798 | ''' |
| 799 | return self.__name |
| 800 | |
| 801 | def fset(self, name): |
| 802 | ''' |
| 803 | Sets the name of the Group |
| 804 | ''' |
| 805 | if type(name) in STRTYPES and len(name) > 0: |
| 806 | self.__name = name |
| 807 | else: |
| 808 | self.__name = None |
| 809 | |
| 810 | return property(**locals()) |
| 811 | |
| 812 | |
| 813 | |
| 814 | # Colors property |
| 815 | @apply |
| 816 | def colors(): |
| 817 | doc = ''' |
| 818 | >>> s = Series() |
| 819 | >>> s.colors = [[1,1,1],[2,2,2,'linear'],[3,3,3,'gradient']] |
| 820 | >>> print s.colors |
| 821 | {'Color 2': [2, 2, 2, 'linear'], 'Color 3': [3, 3, 3, 'gradient'], 'Color 1': [1, 1, 1, 'solid']} |
| 822 | >>> s.colors = [[1,1,1],(2,2,2,'solid'),(3,3,3,'linear')] |
| 823 | >>> print s.colors |
| 824 | {'Color 2': [2, 2, 2, 'solid'], 'Color 3': [3, 3, 3, 'linear'], 'Color 1': [1, 1, 1, 'solid']} |
| 825 | >>> s.colors = {'a':[1,1,1],'b':(2,2,2,'solid'),'c':(3,3,3,'linear'), 'd':(4,4,4)} |
| 826 | >>> print s.colors |
| 827 | {'a': [1, 1, 1, 'solid'], 'c': [3, 3, 3, 'linear'], 'b': [2, 2, 2, 'solid'], 'd': [4, 4, 4, 'solid']} |
| 828 | ''' |
| 829 | def fget(self): |
| 830 | ''' |
| 831 | Return the color list |
| 832 | ''' |
| 833 | return self.__colors.color_list |
| 834 | |
| 835 | def fset(self, colors): |
| 836 | ''' |
| 837 | Format the color list to a dictionary |
| 838 | ''' |
| 839 | self.__colors = Colors(colors) |
| 840 | |
| 841 | return property(**locals()) |
| 842 | |
| 843 | @apply |
| 844 | def range(): |
| 845 | doc = ''' |
| 846 | The range is a read/write property that generates a range of values |
| 847 | for the x axis of the functions. When passed a tuple it almost works |
| 848 | like the built-in range funtion: |
| 849 | - 1 item, represent the end of the range started from 0; |
| 850 | - 2 items, represents the start and the end, respectively; |
| 851 | - 3 items, the last one represents the step; |
| 852 | |
| 853 | When passed a list the range function understands as a valid range. |
| 854 | |
| 855 | Usage: |
| 856 | >>> s = Series(); s.range = 10; print s.range |
| 857 | [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] |
| 858 | >>> s = Series(); s.range = (5); print s.range |
| 859 | [0.0, 1.0, 2.0, 3.0, 4.0, 5.0] |
| 860 | >>> s = Series(); s.range = (1,7); print s.range |
| 861 | [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0] |
| 862 | >>> s = Series(); s.range = (0,10,2); print s.range |
| 863 | [0.0, 2.0, 4.0, 6.0, 8.0, 10.0] |
| 864 | >>> |
| 865 | >>> s = Series(); s.range = [0]; print s.range |
| 866 | [0.0] |
| 867 | >>> s = Series(); s.range = [0,10,20]; print s.range |
| 868 | [0.0, 10.0, 20.0] |
| 869 | ''' |
| 870 | def fget(self): |
| 871 | ''' |
| 872 | Returns the range |
| 873 | ''' |
| 874 | return self.__range |
| 875 | |
| 876 | def fset(self, x_range): |
| 877 | ''' |
| 878 | Controls the input of a valid type and generate the range |
| 879 | ''' |
| 880 | # if passed a simple number convert to tuple |
| 881 | if type(x_range) in NUMTYPES: |
| 882 | x_range = (x_range,) |
| 883 | |
| 884 | # A list, just convert to float |
| 885 | if type(x_range) is list and len(x_range) > 0: |
| 886 | # Convert all to float |
| 887 | x_range = map(float, x_range) |
| 888 | # Prevents repeated values and convert back to list |
| 889 | self.__range = list(set(x_range[:])) |
| 890 | # Sort the list to ascending order |
| 891 | self.__range.sort() |
| 892 | |
| 893 | # A tuple, must check the lengths and generate the values |
| 894 | elif type(x_range) is tuple and len(x_range) in (1,2,3): |
| 895 | # Convert all to float |
| 896 | x_range = map(float, x_range) |
| 897 | |
| 898 | # Inital values |
| 899 | start = 0.0 |
| 900 | step = 1.0 |
| 901 | end = 0.0 |
| 902 | |
| 903 | # Only the end and it can't be less or iqual to 0 |
| 904 | if len(x_range) is 1 and x_range > 0: |
| 905 | end = x_range[0] |
| 906 | |
| 907 | # The start and the end but the start must be lesser then the end |
| 908 | elif len(x_range) is 2 and x_range[0] < x_range[1]: |
| 909 | start = x_range[0] |
| 910 | end = x_range[1] |
| 911 | |
| 912 | # All 3, but the start must be lesser then the end |
| 913 | elif x_range[0] < x_range[1]: |
| 914 | start = x_range[0] |
| 915 | end = x_range[1] |
| 916 | step = x_range[2] |
| 917 | |
| 918 | # Starts the range |
| 919 | self.__range = [] |
| 920 | # Generate the range |
| 921 | # Cnat use the range function becouse it don't suport float values |
| 922 | while start <= end: |
| 923 | self.__range.append(start) |
| 924 | start += step |
| 925 | |
| 926 | # Incorrect type |
| 927 | else: |
| 928 | raise Exception, "x_range must be a list with one or more item or a tuple with 2 or 3 items" |
| 929 | |
| 930 | return property(**locals()) |
| 931 | |
| 932 | @apply |
| 933 | def group_list(): |
| 934 | doc = ''' |
| 935 | The group_list is a read/write property used to pre-process the list |
| 936 | of Groups. |
| 937 | It can be: |
| 938 | - a single number, point or lambda, will be converted to a single |
| 939 | Group of one Data; |
| 940 | - a list of numbers, will be converted to a group of numbers; |
| 941 | - a list of tuples, will converted to a single Group of points; |
| 942 | - a list of lists of numbers, each 'sublist' will be converted to |
| 943 | a group of numbers; |
| 944 | - a list of lists of tuples, each 'sublist' will be converted to a |
| 945 | group of points; |
| 946 | - a list of lists of lists, the content of the 'sublist' will be |
| 947 | processed as coordinated lists and the result will be converted |
| 948 | to a group of points; |
| 949 | - a list of lambdas, each lambda represents a Group; |
| 950 | - a Dictionary where each item can be the same of the list: number, |
| 951 | point, list of numbers, list of points, list of lists |
| 952 | (coordinated lists) or lambdas |
| 953 | - an instance of Data; |
| 954 | - an instance of group. |
| 955 | |
| 956 | Usage: |
| 957 | >>> s = Series() |
| 958 | >>> s.group_list = [1,2,3,4]; print s |
| 959 | ["Group 1 ['1', '2', '3', '4']"] |
| 960 | >>> s.group_list = [[1,2,3],[4,5,6]]; print s |
| 961 | ["Group 1 ['1', '2', '3']", "Group 2 ['4', '5', '6']"] |
| 962 | >>> s.group_list = (1,2); print s |
| 963 | ["Group 1 ['(1, 2)']"] |
| 964 | >>> s.group_list = [(1,2),(2,3)]; print s |
| 965 | ["Group 1 ['(1, 2)', '(2, 3)']"] |
| 966 | >>> s.group_list = [[(1,2),(2,3)],[(4,5),(5,6)]]; print s |
| 967 | ["Group 1 ['(1, 2)', '(2, 3)']", "Group 2 ['(4, 5)', '(5, 6)']"] |
| 968 | >>> s.group_list = [[[1,2,3],[1,2,3],[1,2,3]]]; print s |
| 969 | ["Group 1 ['(1, 1, 1)', '(2, 2, 2)', '(3, 3, 3)']"] |
| 970 | >>> s.group_list = [(0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,9)]; print s |
| 971 | ["Group 1 ['(0.5, 5.5)']", "Group 2 ['(0, 4)', '(6, 8)']", "Group 3 ['(5.5, 7)']", "Group 4 ['(7, 9)']"] |
| 972 | >>> s.group_list = {'g1':[1,2,3], 'g2':[4,5,6]}; print s |
| 973 | ["g1 ['1', '2', '3']", "g2 ['4', '5', '6']"] |
| 974 | >>> s.group_list = {'g1':[(1,2),(2,3)], 'g2':[(4,5),(5,6)]}; print s |
| 975 | ["g1 ['(1, 2)', '(2, 3)']", "g2 ['(4, 5)', '(5, 6)']"] |
| 976 | >>> s.group_list = {'g1':[[1,2],[1,2]], 'g2':[[4,5],[4,5]]}; print s |
| 977 | ["g1 ['(1, 1)', '(2, 2)']", "g2 ['(4, 4)', '(5, 5)']"] |
| 978 | >>> s.range = 10 |
| 979 | >>> s.group_list = lambda x:x*2 |
| 980 | >>> s.group_list = [lambda x:x*2, lambda x:x**2, lambda x:x**3]; print s |
| 981 | ["Group 1 ['(0.0, 0.0)', '(1.0, 2.0)', '(2.0, 4.0)', '(3.0, 6.0)', '(4.0, 8.0)', '(5.0, 10.0)', '(6.0, 12.0)', '(7.0, 14.0)', '(8.0, 16.0)', '(9.0, 18.0)', '(10.0, 20.0)']", "Group 2 ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 4.0)', '(3.0, 9.0)', '(4.0, 16.0)', '(5.0, 25.0)', '(6.0, 36.0)', '(7.0, 49.0)', '(8.0, 64.0)', '(9.0, 81.0)', '(10.0, 100.0)']", "Group 3 ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 8.0)', '(3.0, 27.0)', '(4.0, 64.0)', '(5.0, 125.0)', '(6.0, 216.0)', '(7.0, 343.0)', '(8.0, 512.0)', '(9.0, 729.0)', '(10.0, 1000.0)']"] |
| 982 | >>> s.group_list = {'linear':lambda x:x*2, 'square':lambda x:x**2, 'cubic':lambda x:x**3}; print s |
| 983 | ["cubic ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 8.0)', '(3.0, 27.0)', '(4.0, 64.0)', '(5.0, 125.0)', '(6.0, 216.0)', '(7.0, 343.0)', '(8.0, 512.0)', '(9.0, 729.0)', '(10.0, 1000.0)']", "linear ['(0.0, 0.0)', '(1.0, 2.0)', '(2.0, 4.0)', '(3.0, 6.0)', '(4.0, 8.0)', '(5.0, 10.0)', '(6.0, 12.0)', '(7.0, 14.0)', '(8.0, 16.0)', '(9.0, 18.0)', '(10.0, 20.0)']", "square ['(0.0, 0.0)', '(1.0, 1.0)', '(2.0, 4.0)', '(3.0, 9.0)', '(4.0, 16.0)', '(5.0, 25.0)', '(6.0, 36.0)', '(7.0, 49.0)', '(8.0, 64.0)', '(9.0, 81.0)', '(10.0, 100.0)']"] |
| 984 | >>> s.group_list = Data(1,'d1'); print s |
| 985 | ["Group 1 ['d1: 1']"] |
| 986 | >>> s.group_list = Group([(1,2),(2,3)],'g1'); print s |
| 987 | ["g1 ['(1, 2)', '(2, 3)']"] |
| 988 | ''' |
| 989 | def fget(self): |
| 990 | ''' |
| 991 | Return the group list. |
| 992 | ''' |
| 993 | return self.__group_list |
| 994 | |
| 995 | def fset(self, series): |
| 996 | ''' |
| 997 | Controls the input of a valid group list. |
| 998 | ''' |
| 999 | #TODO: Add support to the following strem of data: [ (0.5,5.5) , [(0,4),(6,8)] , (5.5,7) , (7,9)] |
| 1000 | |
| 1001 | # Type: None |
| 1002 | if series is None: |
| 1003 | self.__group_list = [] |
| 1004 | |
| 1005 | # List or Tuple |
| 1006 | elif type(series) in LISTTYPES: |
| 1007 | self.__group_list = [] |
| 1008 | |
| 1009 | is_function = lambda x: callable(x) |
| 1010 | # Groups |
| 1011 | if list in map(type, series) or max(map(is_function, series)): |
| 1012 | for group in series: |
| 1013 | self.add_group(group) |
| 1014 | |
| 1015 | # single group |
| 1016 | else: |
| 1017 | self.add_group(series) |
| 1018 | |
| 1019 | #old code |
| 1020 | ## List of numbers |
| 1021 | #if type(series[0]) in NUMTYPES or type(series[0]) is tuple: |
| 1022 | # print series |
| 1023 | # self.add_group(series) |
| 1024 | # |
| 1025 | ## List of anything else |
| 1026 | #else: |
| 1027 | # for group in series: |
| 1028 | # self.add_group(group) |
| 1029 | |
| 1030 | # Dict representing series of groups |
| 1031 | elif type(series) is dict: |
| 1032 | self.__group_list = [] |
| 1033 | names = series.keys() |
| 1034 | names.sort() |
| 1035 | for name in names: |
| 1036 | self.add_group(Group(series[name],name,self)) |
| 1037 | |
| 1038 | # A single lambda |
| 1039 | elif callable(series): |
| 1040 | self.__group_list = [] |
| 1041 | self.add_group(series) |
| 1042 | |
| 1043 | # Int/float, instance of Group or Data |
| 1044 | elif type(series) in NUMTYPES or isinstance(series, Group) or isinstance(series, Data): |
| 1045 | self.__group_list = [] |
| 1046 | self.add_group(series) |
| 1047 | |
| 1048 | # Default |
| 1049 | else: |
| 1050 | raise TypeError, "Serie type not supported" |
| 1051 | |
| 1052 | return property(**locals()) |
| 1053 | |
| 1054 | def add_group(self, group, name=None): |
| 1055 | ''' |
| 1056 | Append a new group in group_list |
| 1057 | ''' |
| 1058 | if not isinstance(group, Group): |
| 1059 | #Try to convert |
| 1060 | group = Group(group, name, self) |
| 1061 | |
| 1062 | if len(group.data_list) is not 0: |
| 1063 | # Auto naming groups |
| 1064 | if group.name is None: |
| 1065 | group.name = "Group "+str(len(self.__group_list)+1) |
| 1066 | |
| 1067 | self.__group_list.append(group) |
| 1068 | self.__group_list[-1].parent = self |
| 1069 | |
| 1070 | def copy(self): |
| 1071 | ''' |
| 1072 | Returns a copy of the Series |
| 1073 | ''' |
| 1074 | new_series = Series() |
| 1075 | new_series.__name = self.__name |
| 1076 | if self.__range is not None: |
| 1077 | new_series.__range = self.__range[:] |
| 1078 | #Add color property in the copy method |
| 1079 | #self.__colors = None |
| 1080 | |
| 1081 | for group in self: |
| 1082 | new_series.add_group(group.copy()) |
| 1083 | |
| 1084 | return new_series |
| 1085 | |
| 1086 | def get_names(self): |
| 1087 | ''' |
| 1088 | Returns a list of the names of all groups in the Serie |
| 1089 | ''' |
| 1090 | names = [] |
| 1091 | for group in self: |
| 1092 | if group.name is None: |
| 1093 | names.append('Group '+str(group.index()+1)) |
| 1094 | else: |
| 1095 | names.append(group.name) |
| 1096 | |
| 1097 | return names |
| 1098 | |
| 1099 | def to_list(self): |
| 1100 | ''' |
| 1101 | Returns a list with the content of all groups and data |
| 1102 | ''' |
| 1103 | big_list = [] |
| 1104 | for group in self: |
| 1105 | for data in group: |
| 1106 | if type(data.content) in NUMTYPES: |
| 1107 | big_list.append(data.content) |
| 1108 | else: |
| 1109 | big_list = big_list + list(data.content) |
| 1110 | return big_list |
| 1111 | |
| 1112 | def __getitem__(self, key): |
| 1113 | ''' |
| 1114 | Makes the Series iterable, based in the group_list property |
| 1115 | ''' |
| 1116 | return self.__group_list[key] |
| 1117 | |
| 1118 | def __str__(self): |
| 1119 | ''' |
| 1120 | Returns a string that represents the Series |
| 1121 | ''' |
| 1122 | ret = "" |
| 1123 | if self.name is not None: |
| 1124 | ret += self.name + " " |
| 1125 | if len(self) > 0: |
| 1126 | list_str = [str(item) for item in self] |
| 1127 | ret += str(list_str) |
| 1128 | else: |
| 1129 | ret += "[]" |
| 1130 | return ret |
| 1131 | |
| 1132 | def __len__(self): |
| 1133 | ''' |
| 1134 | Returns the length of the Series, based in the group_lsit property |
| 1135 | ''' |
| 1136 | return len(self.group_list) |
| 1137 | |
| 1138 | |
| 1139 | if __name__ == '__main__': |
| 1140 | doctest.testmod() |