blob: 050e4de316edfc3d7ba38e21d983f0ec045e059e [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Code related to bar charts."""
import copy
import warnings
from mapreduce.lib.graphy import common
from mapreduce.lib.graphy import util
class BarsStyle(object):
"""Style of a series of bars in a BarChart
Object Attributes:
color: Hex string, like '00ff00' for green
"""
def __init__(self, color):
self.color = color
class BarChartStyle(object):
"""Represents the style for bars on a BarChart.
Any of the object attributes may be set to None, in which case the
value will be auto-calculated.
Object Attributes:
bar_thickness: The thickness of a bar, in pixels.
bar_gap: The gap between bars, in pixels, or as a fraction of bar thickness
if use_fractional_gap_spacing is True.
group_gap: The gap between groups of bars, in pixels, or as a fraction of
bar thickness if use_fractional_gap_spacing is True.
use_fractional_gap_spacing: if True, bar_gap and group_gap specify gap
sizes as a fraction of bar width. Default is False.
"""
_DEFAULT_GROUP_GAP = 8
_DEFAULT_BAR_GAP = 4
def __init__(self, bar_thickness=None,
bar_gap=_DEFAULT_BAR_GAP, group_gap=_DEFAULT_GROUP_GAP,
use_fractional_gap_spacing=False):
"""Create a new BarChartStyle.
Args:
bar_thickness: The thickness of a bar, in pixels. Set this to None if
you want the bar thickness to be auto-calculated (this is the default
behaviour).
bar_gap: The gap between bars, in pixels. Default is 4.
group_gap: The gap between groups of bars, in pixels. Default is 8.
"""
self.bar_thickness = bar_thickness
self.bar_gap = bar_gap
self.group_gap = group_gap
self.use_fractional_gap_spacing = use_fractional_gap_spacing
class BarStyle(BarChartStyle):
def __init__(self, *args, **kwargs):
warnings.warn('BarStyle is deprecated. Use BarChartStyle.',
DeprecationWarning, stacklevel=2)
super(BarStyle, self).__init__(*args, **kwargs)
class BarChart(common.BaseChart):
"""Represents a bar chart.
Object attributes:
vertical: if True, the bars will be vertical. Default is True.
stacked: if True, the bars will be stacked. Default is False.
style: The BarChartStyle for all bars on this chart, specifying bar
thickness and gaps between bars.
"""
def __init__(self, points=None):
"""Constructor for BarChart objects."""
super(BarChart, self).__init__()
if points is not None:
self.AddBars(points)
self.vertical = True
self.stacked = False
self.style = BarChartStyle(None, None, None) # full auto
def AddBars(self, points, label=None, color=None):
"""Add a series of bars to the chart.
points: List of y-values for the bars in this series
label: Name of the series (used in the legend)
color: Hex string, like '00ff00' for green
This is a convenience method which constructs & appends the DataSeries for
you.
"""
if label is not None and util._IsColor(label):
warnings.warn('Your code may be broken! '
'Label is a hex triplet. Maybe it is a color? The '
'old argument order (color before label) is deprecated.',
DeprecationWarning, stacklevel=2)
style = BarsStyle(color)
series = common.DataSeries(points, label=label, style=style)
self.data.append(series)
return series
def GetDependentAxes(self):
"""Get the dependendant axes, which depend on orientation."""
if self.vertical:
return (self._axes[common.AxisPosition.LEFT] +
self._axes[common.AxisPosition.RIGHT])
else:
return (self._axes[common.AxisPosition.TOP] +
self._axes[common.AxisPosition.BOTTOM])
def GetIndependentAxes(self):
"""Get the independendant axes, which depend on orientation."""
if self.vertical:
return (self._axes[common.AxisPosition.TOP] +
self._axes[common.AxisPosition.BOTTOM])
else:
return (self._axes[common.AxisPosition.LEFT] +
self._axes[common.AxisPosition.RIGHT])
def GetDependentAxis(self):
"""Get the main dependendant axis, which depends on orientation."""
if self.vertical:
return self.left
else:
return self.bottom
def GetIndependentAxis(self):
"""Get the main independendant axis, which depends on orientation."""
if self.vertical:
return self.bottom
else:
return self.left
def GetMinMaxValues(self):
"""Get the largest & smallest bar values as (min_value, max_value)."""
if not self.stacked:
return super(BarChart, self).GetMinMaxValues()
if not self.data:
return None, None # No data, nothing to do.
num_bars = max(len(series.data) for series in self.data)
positives = [0 for i in xrange(0, num_bars)]
negatives = list(positives)
for series in self.data:
for i, point in enumerate(series.data):
if point:
if point > 0:
positives[i] += point
else:
negatives[i] += point
min_value = min(min(positives), min(negatives))
max_value = max(max(positives), max(negatives))
return min_value, max_value