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