-
Notifications
You must be signed in to change notification settings - Fork 4
/
ClusteredBars.py
174 lines (132 loc) · 5.23 KB
/
ClusteredBars.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import pylab
from matplotlib import pyplot
from matplotlib.transforms import Affine2D, IdentityTransform
from PlotInfo import *
from Bar import *
from StackedBars import *
import sys
from boomslang_exceptions import BoomslangPlotConfigurationException
class ClusteredBars(PlotInfo):
"""
A clustered bar chart consisting of multiple series of bars
with the same X axis values.
"""
def __init__(self, **kwargs):
super(ClusteredBars,self).__init__("clustered bar", **kwargs)
self.bars = []
self.spacing = 0
"""
The spacing between each bar in the cluster
"""
self.background_color = "white"
self.barWidth = None
"""
The width of each bar in the cluster
"""
self.deduplicateLegend = True
"""
If multiple legend items have the same key, only show the first one
"""
self.padding = 0
"""
Amount of padding between the edge of the left- and rightmost bars and
the bounding box for the plot
"""
def _getWidth(self):
numBars = len(self.bars)
if self.barWidth is None or numBars == 0:
return None
else:
return self.barWidth * numBars
def _setWidth(self, width):
raise BoomslangPlotConfigurationException(
"It isn't semantically meaningful for a clustered bar graph "
"to have width. Set barWidth instead")
width = property(_getWidth, _setWidth)
"""
The overall width of each cluster of bars
"""
def add(self, bar):
"""
Add `bar` (a :class:`boomslang.Bar.Bar`) to the cluster of bars
"""
if not isinstance(bar, Bar) and not isinstance(bar, StackedBars):
raise BoomslangPlotConfigurationException(
"Can only add Bars to a ClusteredBars")
self.bars.append(bar)
def _labelTransform(self, x):
return (x + 1) * (self.width / 2.0) + \
x * (self.width / 2.0 + self.spacing) - \
(self.barWidth / 2.0)
def _preDraw(self):
if len(self.bars) == 0:
return
for bar in self.bars:
bar._preDraw()
if self.barWidth is None:
self.barWidth = self.bars[0].width
# All bars should have the same width
for bar in self.bars:
if isinstance(bar, Bar):
bar.width = self.barWidth
if self.xTickLabels is not None:
self.xTickLabelPoints = [self._labelTransform(x)
for x in self.bars[0].xValues]
if len(self.xTickLabelPoints) != len(self.xTickLabels):
raise BoomslangPlotConfigurationException(
"Number of clustered bar labels doesn't "
"match number of points. Labels: %s, Points: %s" %
(self.xTickLabels, self.xTickLabelPoints))
def draw(self, fig, axis, transform=None):
super(ClusteredBars,self).draw(fig, axis)
rect = axis.patch
rect.set_facecolor(self.background_color)
return self._draw(fig, axis, transform)
def _draw(self, fig, axis, transform=None):
numBars = len(self.bars)
plotHandles = []
plotLabels = []
clusterWidth = sum([bar.width for bar in self.bars])
for i in xrange(numBars):
bar = self.bars[i]
pre_width = sum([self.bars[j].width for j in xrange(i)])
bar_xform = Affine2D().scale(clusterWidth + self.spacing, 1)\
.translate(pre_width, 0)
if transform:
xform = bar_xform + transform
else:
xform = bar_xform
[handles, labels] = bar._draw(fig, axis, transform=xform)
bar.drawErrorBars(axis, transform=xform)
if isinstance(bar, Bar):
# Only give handle to first rectangle in bar
plotHandles.append(handles[0])
plotLabels.append(labels[0])
else:
# Assuming StackedBar
plotHandles.extend(handles)
plotLabels.extend(labels)
if self.xTickLabels is not None:
xMin = self.xTickLabelPoints[0]
xMax = self.xTickLabelPoints[-1]
else:
xMin = self._labelTransform(self.bars[0].xValues[0])
xMax = self._labelTransform(self.bars[0].xValues[-1])
self.xLimits = ((xMin - clusterWidth / 2.0) - self.padding,
(xMax + clusterWidth / 2.0) + self.padding)
if self.deduplicateLegend:
handlesByLabel = {}
for i in xrange(len(plotHandles)):
if (plotLabels[i] is not None and
plotLabels[i] not in handlesByLabel):
handlesByLabel[plotLabels[i]] = plotHandles[i]
handlesToReturn = set(handlesByLabel.values())
newHandles = []
newLabels = []
for i, handle in enumerate(plotHandles):
if handle in handlesToReturn:
newHandles.append(handle)
newLabels.append(plotLabels[i])
plotHandles = newHandles
plotLabels = newLabels
return [plotHandles, plotLabels]