1#!/usr/bin/python2.4 2# 3# Copyright 2008 Google Inc. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Code related to bar charts.""" 18 19import copy 20import warnings 21 22from graphy import common 23from graphy import util 24 25 26class BarsStyle(object): 27 """Style of a series of bars in a BarChart 28 29 Object Attributes: 30 color: Hex string, like '00ff00' for green 31 """ 32 def __init__(self, color): 33 self.color = color 34 35 36class BarChartStyle(object): 37 """Represents the style for bars on a BarChart. 38 39 Any of the object attributes may be set to None, in which case the 40 value will be auto-calculated. 41 42 Object Attributes: 43 bar_thickness: The thickness of a bar, in pixels. 44 bar_gap: The gap between bars, in pixels, or as a fraction of bar thickness 45 if use_fractional_gap_spacing is True. 46 group_gap: The gap between groups of bars, in pixels, or as a fraction of 47 bar thickness if use_fractional_gap_spacing is True. 48 use_fractional_gap_spacing: if True, bar_gap and group_gap specify gap 49 sizes as a fraction of bar width. Default is False. 50 """ 51 52 _DEFAULT_GROUP_GAP = 8 53 _DEFAULT_BAR_GAP = 4 54 55 def __init__(self, bar_thickness=None, 56 bar_gap=_DEFAULT_BAR_GAP, group_gap=_DEFAULT_GROUP_GAP, 57 use_fractional_gap_spacing=False): 58 """Create a new BarChartStyle. 59 60 Args: 61 bar_thickness: The thickness of a bar, in pixels. Set this to None if 62 you want the bar thickness to be auto-calculated (this is the default 63 behaviour). 64 bar_gap: The gap between bars, in pixels. Default is 4. 65 group_gap: The gap between groups of bars, in pixels. Default is 8. 66 """ 67 self.bar_thickness = bar_thickness 68 self.bar_gap = bar_gap 69 self.group_gap = group_gap 70 self.use_fractional_gap_spacing = use_fractional_gap_spacing 71 72 73class BarStyle(BarChartStyle): 74 75 def __init__(self, *args, **kwargs): 76 warnings.warn('BarStyle is deprecated. Use BarChartStyle.', 77 DeprecationWarning, stacklevel=2) 78 super(BarStyle, self).__init__(*args, **kwargs) 79 80 81class BarChart(common.BaseChart): 82 """Represents a bar chart. 83 84 Object attributes: 85 vertical: if True, the bars will be vertical. Default is True. 86 stacked: if True, the bars will be stacked. Default is False. 87 style: The BarChartStyle for all bars on this chart, specifying bar 88 thickness and gaps between bars. 89 """ 90 91 def __init__(self, points=None): 92 """Constructor for BarChart objects.""" 93 super(BarChart, self).__init__() 94 if points is not None: 95 self.AddBars(points) 96 self.vertical = True 97 self.stacked = False 98 self.style = BarChartStyle(None, None, None) # full auto 99 100 def AddBars(self, points, label=None, color=None): 101 """Add a series of bars to the chart. 102 103 points: List of y-values for the bars in this series 104 label: Name of the series (used in the legend) 105 color: Hex string, like '00ff00' for green 106 107 This is a convenience method which constructs & appends the DataSeries for 108 you. 109 """ 110 if label is not None and util._IsColor(label): 111 warnings.warn('Your code may be broken! ' 112 'Label is a hex triplet. Maybe it is a color? The ' 113 'old argument order (color before label) is deprecated.', 114 DeprecationWarning, stacklevel=2) 115 style = BarsStyle(color) 116 series = common.DataSeries(points, label=label, style=style) 117 self.data.append(series) 118 return series 119 120 def GetDependentAxes(self): 121 """Get the dependendant axes, which depend on orientation.""" 122 if self.vertical: 123 return (self._axes[common.AxisPosition.LEFT] + 124 self._axes[common.AxisPosition.RIGHT]) 125 else: 126 return (self._axes[common.AxisPosition.TOP] + 127 self._axes[common.AxisPosition.BOTTOM]) 128 129 def GetIndependentAxes(self): 130 """Get the independendant axes, which depend on orientation.""" 131 if self.vertical: 132 return (self._axes[common.AxisPosition.TOP] + 133 self._axes[common.AxisPosition.BOTTOM]) 134 else: 135 return (self._axes[common.AxisPosition.LEFT] + 136 self._axes[common.AxisPosition.RIGHT]) 137 138 def GetDependentAxis(self): 139 """Get the main dependendant axis, which depends on orientation.""" 140 if self.vertical: 141 return self.left 142 else: 143 return self.bottom 144 145 def GetIndependentAxis(self): 146 """Get the main independendant axis, which depends on orientation.""" 147 if self.vertical: 148 return self.bottom 149 else: 150 return self.left 151 152 def GetMinMaxValues(self): 153 """Get the largest & smallest bar values as (min_value, max_value).""" 154 if not self.stacked: 155 return super(BarChart, self).GetMinMaxValues() 156 157 if not self.data: 158 return None, None # No data, nothing to do. 159 num_bars = max(len(series.data) for series in self.data) 160 positives = [0 for i in xrange(0, num_bars)] 161 negatives = list(positives) 162 for series in self.data: 163 for i, point in enumerate(series.data): 164 if point: 165 if point > 0: 166 positives[i] += point 167 else: 168 negatives[i] += point 169 min_value = min(min(positives), min(negatives)) 170 max_value = max(max(positives), max(negatives)) 171 return min_value, max_value 172