14a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair#!/usr/bin/python2.4 24a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# 34a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# Copyright 2008 Google Inc. 44a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# 54a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# Licensed under the Apache License, Version 2.0 (the "License"); 64a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# you may not use this file except in compliance with the License. 74a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# You may obtain a copy of the License at 84a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# 94a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# http://www.apache.org/licenses/LICENSE-2.0 104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# 114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# Unless required by applicable law or agreed to in writing, software 124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# distributed under the License is distributed on an "AS IS" BASIS, 134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# See the License for the specific language governing permissions and 154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# limitations under the License. 164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair"""Code common to all chart types.""" 184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport copy 204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairimport warnings 214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom graphy import formatters 234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairfrom graphy import util 244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass Marker(object): 274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Represents an abstract marker, without position. You can attach these to 294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair a DataSeries. 304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Object attributes: 324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair shape: One of the shape codes (Marker.arrow, Marker.diamond, etc.) 334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair color: color (as hex string, f.ex. '0000ff' for blue) 344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair size: size of the marker 354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # TODO: Write an example using markers. 374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # Shapes: 394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair arrow = 'a' 404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair cross = 'c' 414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair diamond = 'd' 424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair circle = 'o' 434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair square = 's' 444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair x = 'x' 454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # Note: The Google Chart API also knows some other markers ('v', 'V', 'r', 474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # 'b') that I think would fit better into a grid API. 484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # TODO: Make such a grid API 494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def __init__(self, shape, color, size): 514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Construct a Marker. See class docstring for details on args.""" 524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # TODO: Shapes 'r' and 'b' would be much easier to use if they had a 534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # special-purpose API (instead of trying to fake it with markers) 544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.shape = shape 554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.color = color 564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.size = size 574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass _BasicStyle(object): 604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Basic style object. Used internally.""" 614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def __init__(self, color): 634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.color = color 644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass DataSeries(object): 674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Represents one data series for a chart (both data & presentation 694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair information). 704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Object attributes: 724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair points: List of numbers representing y-values (x-values are not specified 734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair because the Google Chart API expects even x-value spacing). 744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair label: String with the series' label in the legend. The chart will only 754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair have a legend if at least one series has a label. If some series 764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair do not have a label then they will have an empty description in 774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair the legend. This is currently a limitation in the Google Chart 784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair API. 794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair style: A chart-type-specific style object. (LineStyle for LineChart, 804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair BarsStyle for BarChart, etc.) 814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair markers: List of (x, m) tuples where m is a Marker object and x is the 824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair x-axis value to place it at. 834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair The "fill" markers ('r' & 'b') are a little weird because they 854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair aren't a point on a line. For these, you can fake it by 864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair passing slightly weird data (I'd like a better API for them at 874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair some point): 884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair For 'b', you attach the marker to the starting series, and set x 894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair to the index of the ending line. Size is ignored, I think. 904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair For 'r', you can attach to any line, specify the starting 924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair y-value for x and the ending y-value for size. Y, in this case, 934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair is becase 0.0 (bottom) and 1.0 (top). 944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair color: DEPRECATED 954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # TODO: Should we require the points list to be non-empty ? 984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # TODO: Do markers belong here? They are really only used for LineCharts 994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def __init__(self, points, label=None, style=None, markers=None, color=None): 1004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Construct a DataSeries. See class docstring for details on args.""" 1014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair if label is not None and util._IsColor(label): 1024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair warnings.warn('Your code may be broken! Label is a hex triplet. Maybe ' 1034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'it is a color? The old argument order (color & style ' 1044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'before label) is deprecated.', DeprecationWarning, 1054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair stacklevel=2) 1064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair if color is not None: 1074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair warnings.warn('Passing color is deprecated. Pass a style object ' 1084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'instead.', DeprecationWarning, stacklevel=2) 1094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # Attempt to fix it for them. If they also passed a style, honor it. 1104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair if style is None: 1114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair style = _BasicStyle(color) 1124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair if style is not None and isinstance(style, basestring): 1134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair warnings.warn('Your code is broken! Style is a string, not an object. ' 1144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'Maybe you are passing a color? Passing color is ' 1154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'deprecated; pass a style object instead.', 1164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair DeprecationWarning, stacklevel=2) 1174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair if style is None: 1184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair style = _BasicStyle(None) 1194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.data = points 1204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.style = style 1214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.markers = markers or [] 1224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.label = label 1234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _GetColor(self): 1254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair warnings.warn('DataSeries.color is deprecated, use ' 1264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'DataSeries.style.color instead.', DeprecationWarning, 1274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair stacklevel=2) 1284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return self.style.color 1294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _SetColor(self, color): 1314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair warnings.warn('DataSeries.color is deprecated, use ' 1324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'DataSeries.style.color instead.', DeprecationWarning, 1334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair stacklevel=2) 1344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.style.color = color 1354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair color = property(_GetColor, _SetColor) 1374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _GetStyle(self): 1394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return self._style; 1404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _SetStyle(self, style): 1424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair if style is not None and callable(style): 1434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair warnings.warn('Your code may be broken ! LineStyle.solid and similar ' 1444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'are no longer constants, but class methods that ' 1454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'create LineStyle instances. Change your code to call ' 1464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'LineStyle.solid() instead of passing it as a value.', 1474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair DeprecationWarning, stacklevel=2) 1484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self._style = style() 1494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair else: 1504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self._style = style 1514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair style = property(_GetStyle, _SetStyle) 1534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass AxisPosition(object): 1564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Represents all the available axis positions. 1574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair The available positions are as follows: 1594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair AxisPosition.TOP 1604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair AxisPosition.BOTTOM 1614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair AxisPosition.LEFT 1624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair AxisPosition.RIGHT 1634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 1644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair LEFT = 'y' 1654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair RIGHT = 'r' 1664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair BOTTOM = 'x' 1674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair TOP = 't' 1684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass Axis(object): 1714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Represents one axis. 1734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Object setings: 1754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair min: Minimum value for the bottom or left end of the axis 1764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair max: Max value. 1774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair labels: List of labels to show along the axis. 1784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair label_positions: List of positions to show the labels at. Uses the scale 1794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair set by min & max, so if you set min = 0 and max = 10, then 1804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair label positions [0, 5, 10] would be at the bottom, 1814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair middle, and top of the axis, respectively. 1824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair grid_spacing: Amount of space between gridlines (in min/max scale). 1834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair A value of 0 disables gridlines. 1844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair label_gridlines: If True, draw a line extending from each label 1854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair on the axis all the way across the chart. 1864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 1874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def __init__(self, axis_min=None, axis_max=None): 1894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Construct a new Axis. 1904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 1914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Args: 1924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair axis_min: smallest value on the axis 1934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair axis_max: largest value on the axis 1944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 1954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.min = axis_min 1964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.max = axis_max 1974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.labels = [] 1984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.label_positions = [] 1994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.grid_spacing = 0 2004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.label_gridlines = False 2014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# TODO: Add other chart types. Order of preference: 2034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# - scatter plots 2044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair# - us/world maps 2054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclairclass BaseChart(object): 2074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Base chart object with standard behavior for all other charts. 2084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Object attributes: 2104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair data: List of DataSeries objects. Chart subtypes provide convenience 2114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair functions (like AddLine, AddBars, AddSegment) to add more series 2124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair later. 2134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair left/right/bottom/top: Axis objects for the 4 different axes. 2144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair formatters: A list of callables which will be used to format this chart for 2154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair display. TODO: Need better documentation for how these 2164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair work. 2174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair auto_scale, auto_color, auto_legend: 2184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair These aliases let users access the default formatters without poking 2194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair around in self.formatters. If the user removes them from 2204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.formatters then they will no longer be enabled, even though they'll 2214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair still be accessible through the aliases. Similarly, re-assigning the 2224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair aliases has no effect on the contents of self.formatters. 2234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair display: This variable is reserved for backends to populate with a display 2244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair object. The intention is that the display object would be used to 2254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair render this chart. The details of what gets put here depends on 2264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair the specific backend you are using. 2274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 2284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # Canonical ordering of position keys 2304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair _POSITION_CODES = 'yrxt' 2314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # TODO: Add more inline args to __init__ (esp. labels). 2334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # TODO: Support multiple series in the constructor, if given. 2344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def __init__(self): 2354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Construct a BaseChart object.""" 2364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.data = [] 2374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self._axes = {} 2394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair for code in self._POSITION_CODES: 2404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self._axes[code] = [Axis()] 2414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self._legend_labels = [] # AutoLegend fills this out 2424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self._show_legend = False # AutoLegend fills this out 2434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # Aliases for default formatters 2454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.auto_color = formatters.AutoColor() 2464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.auto_scale = formatters.AutoScale() 2474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.auto_legend = formatters.AutoLegend 2484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.formatters = [self.auto_color, self.auto_scale, self.auto_legend] 2494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # display is used to convert the chart into something displayable (like a 2504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # url or img tag). 2514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.display = None 2524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def AddFormatter(self, formatter): 2544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Add a new formatter to the chart (convenience method).""" 2554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.formatters.append(formatter) 2564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def AddSeries(self, points, color=None, style=None, markers=None, 2584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair label=None): 2594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """DEPRECATED 2604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Add a new series of data to the chart; return the DataSeries object.""" 2624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair warnings.warn('AddSeries is deprecated. Instead, call AddLine for ' 2634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'LineCharts, AddBars for BarCharts, AddSegment for ' 2644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'PieCharts ', DeprecationWarning, stacklevel=2) 2654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair series = DataSeries(points, color=color, style=style, markers=markers, 2664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair label=label) 2674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.data.append(series) 2684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return series 2694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def GetDependentAxes(self): 2714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Return any dependent axes ('left' and 'right' by default for LineCharts, 2724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair although bar charts would use 'bottom' and 'top'). 2734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 2744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return self._axes[AxisPosition.LEFT] + self._axes[AxisPosition.RIGHT] 2754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def GetIndependentAxes(self): 2774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Return any independent axes (normally top & bottom, although horizontal 2784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair bar charts use left & right by default). 2794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 2804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return self._axes[AxisPosition.TOP] + self._axes[AxisPosition.BOTTOM] 2814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def GetDependentAxis(self): 2834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Return this chart's main dependent axis (often 'left', but 2844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair horizontal bar-charts use 'bottom'). 2854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 2864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return self.left 2874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def GetIndependentAxis(self): 2894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Return this chart's main independent axis (often 'bottom', but 2904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair horizontal bar-charts use 'left'). 2914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 2924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return self.bottom 2934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _Clone(self): 2954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Make a deep copy this chart. 2964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 2974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Formatters & display will be missing from the copy, due to limitations in 2984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair deepcopy. 2994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 3004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair orig_values = {} 3014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # Things which deepcopy will likely choke on if it tries to copy. 3024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair uncopyables = ['formatters', 'display', 'auto_color', 'auto_scale', 3034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 'auto_legend'] 3044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair for name in uncopyables: 3054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair orig_values[name] = getattr(self, name) 3064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair setattr(self, name, None) 3074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair clone = copy.deepcopy(self) 3084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair for name, orig_value in orig_values.iteritems(): 3094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair setattr(self, name, orig_value) 3104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return clone 3114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def GetFormattedChart(self): 3134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Get a copy of the chart with formatting applied.""" 3144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # Formatters need to mutate the chart, but we don't want to change it out 3154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # from under the user. So, we work on a copy of the chart. 3164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair scratchpad = self._Clone() 3174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair for formatter in self.formatters: 3184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair formatter(scratchpad) 3194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return scratchpad 3204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def GetMinMaxValues(self): 3224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Get the largest & smallest values in this chart, returned as 3234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair (min_value, max_value). Takes into account complciations like stacked data 3244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair series. 3254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair For example, with non-stacked series, a chart with [1, 2, 3] and [4, 5, 6] 3274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair would return (1, 6). If the same chart was stacking the data series, it 3284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair would return (5, 9). 3294a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 3304a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair MinPoint = lambda data: min(x for x in data if x is not None) 3314a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair MaxPoint = lambda data: max(x for x in data if x is not None) 3324a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair mins = [MinPoint(series.data) for series in self.data if series.data] 3334a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair maxes = [MaxPoint(series.data) for series in self.data if series.data] 3344a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair if not mins or not maxes: 3354a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return None, None # No data, just bail. 3364a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return min(mins), max(maxes) 3374a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3384a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def AddAxis(self, position, axis): 3394a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Add an axis to this chart in the given position. 3404a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3414a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Args: 3424a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair position: an AxisPosition object specifying the axis's position 3434a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair axis: The axis to add, an Axis object 3444a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Returns: 3454a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair the value of the axis parameter 3464a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 3474a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self._axes.setdefault(position, []).append(axis) 3484a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return axis 3494a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3504a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def GetAxis(self, position): 3514a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Get or create the first available axis in the given position. 3524a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3534a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair This is a helper method for the left, right, top, and bottom properties. 3544a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair If the specified axis does not exist, it will be created. 3554a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3564a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Args: 3574a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair position: the position to search for 3584a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Returns: 3594a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair The first axis in the given position 3604a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 3614a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # Not using setdefault here just in case, to avoid calling the Axis() 3624a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair # constructor needlessly 3634a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair if position in self._axes: 3644a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return self._axes[position][0] 3654a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair else: 3664a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair axis = Axis() 3674a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self._axes[position] = [axis] 3684a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return axis 3694a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3704a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def SetAxis(self, position, axis): 3714a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Set the first axis in the given position to the given value. 3724a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3734a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair This is a helper method for the left, right, top, and bottom properties. 3744a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3754a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Args: 3764a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair position: an AxisPosition object specifying the axis's position 3774a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair axis: The axis to set, an Axis object 3784a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair Returns: 3794a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair the value of the axis parameter 3804a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 3814a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self._axes.setdefault(position, [None])[0] = axis 3824a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return axis 3834a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3844a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _GetAxes(self): 3854a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """Return a generator of (position_code, Axis) tuples for this chart's axes. 3864a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3874a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair The axes will be sorted by position using the canonical ordering sequence, 3884a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair _POSITION_CODES. 3894a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair """ 3904a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair for code in self._POSITION_CODES: 3914a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair for axis in self._axes.get(code, []): 3924a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair yield (code, axis) 3934a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3944a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _GetBottom(self): 3954a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return self.GetAxis(AxisPosition.BOTTOM) 3964a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 3974a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _SetBottom(self, value): 3984a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.SetAxis(AxisPosition.BOTTOM, value) 3994a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 4004a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair bottom = property(_GetBottom, _SetBottom, 4014a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair doc="""Get or set the bottom axis""") 4024a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 4034a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _GetLeft(self): 4044a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return self.GetAxis(AxisPosition.LEFT) 4054a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 4064a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _SetLeft(self, value): 4074a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.SetAxis(AxisPosition.LEFT, value) 4084a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 4094a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair left = property(_GetLeft, _SetLeft, 4104a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair doc="""Get or set the left axis""") 4114a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 4124a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _GetRight(self): 4134a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return self.GetAxis(AxisPosition.RIGHT) 4144a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 4154a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _SetRight(self, value): 4164a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.SetAxis(AxisPosition.RIGHT, value) 4174a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 4184a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair right = property(_GetRight, _SetRight, 4194a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair doc="""Get or set the right axis""") 4204a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 4214a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _GetTop(self): 4224a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair return self.GetAxis(AxisPosition.TOP) 4234a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 4244a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair def _SetTop(self, value): 4254a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair self.SetAxis(AxisPosition.TOP, value) 4264a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair 4274a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair top = property(_GetTop, _SetTop, 4284a4f2fe02baf385f6c24fc98c6e17bf6ac5e0724Dan Sinclair doc="""Get or set the top axis""") 429