| # Copyright (c) 2005-2006 The Regents of The University of Michigan | |
| # All rights reserved. | |
| # | |
| # Redistribution and use in source and binary forms, with or without | |
| # modification, are permitted provided that the following conditions are | |
| # met: redistributions of source code must retain the above copyright | |
| # notice, this list of conditions and the following disclaimer; | |
| # redistributions in binary form must reproduce the above copyright | |
| # notice, this list of conditions and the following disclaimer in the | |
| # documentation and/or other materials provided with the distribution; | |
| # neither the name of the copyright holders nor the names of its | |
| # contributors may be used to endorse or promote products derived from | |
| # this software without specific prior written permission. | |
| # | |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| # | |
| # Authors: Nathan Binkert | |
| # Lisa Hsu | |
| import matplotlib, pylab | |
| from matplotlib.font_manager import FontProperties | |
| from matplotlib.numerix import array, arange, reshape, shape, transpose, zeros | |
| from matplotlib.numerix import Float | |
| from matplotlib.ticker import NullLocator | |
| matplotlib.interactive(False) | |
| from chart import ChartOptions | |
| class BarChart(ChartOptions): | |
| def __init__(self, default=None, **kwargs): | |
| super(BarChart, self).__init__(default, **kwargs) | |
| self.inputdata = None | |
| self.chartdata = None | |
| self.inputerr = None | |
| self.charterr = None | |
| def gen_colors(self, count): | |
| cmap = matplotlib.cm.get_cmap(self.colormap) | |
| if count == 1: | |
| return cmap([ 0.5 ]) | |
| if count < 5: | |
| return cmap(arange(5) / float(4))[:count] | |
| return cmap(arange(count) / float(count - 1)) | |
| # The input data format does not match the data format that the | |
| # graph function takes because it is intuitive. The conversion | |
| # from input data format to chart data format depends on the | |
| # dimensionality of the input data. Check here for the | |
| # dimensionality and correctness of the input data | |
| def set_data(self, data): | |
| if data is None: | |
| self.inputdata = None | |
| self.chartdata = None | |
| return | |
| data = array(data) | |
| dim = len(shape(data)) | |
| if dim not in (1, 2, 3): | |
| raise AttributeError, "Input data must be a 1, 2, or 3d matrix" | |
| self.inputdata = data | |
| # If the input data is a 1d matrix, then it describes a | |
| # standard bar chart. | |
| if dim == 1: | |
| self.chartdata = array([[data]]) | |
| # If the input data is a 2d matrix, then it describes a bar | |
| # chart with groups. The matrix being an array of groups of | |
| # bars. | |
| if dim == 2: | |
| self.chartdata = transpose([data], axes=(2,0,1)) | |
| # If the input data is a 3d matrix, then it describes an array | |
| # of groups of bars with each bar being an array of stacked | |
| # values. | |
| if dim == 3: | |
| self.chartdata = transpose(data, axes=(1,2,0)) | |
| def get_data(self): | |
| return self.inputdata | |
| data = property(get_data, set_data) | |
| def set_err(self, err): | |
| if err is None: | |
| self.inputerr = None | |
| self.charterr = None | |
| return | |
| err = array(err) | |
| dim = len(shape(err)) | |
| if dim not in (1, 2, 3): | |
| raise AttributeError, "Input err must be a 1, 2, or 3d matrix" | |
| self.inputerr = err | |
| if dim == 1: | |
| self.charterr = array([[err]]) | |
| if dim == 2: | |
| self.charterr = transpose([err], axes=(2,0,1)) | |
| if dim == 3: | |
| self.charterr = transpose(err, axes=(1,2,0)) | |
| def get_err(self): | |
| return self.inputerr | |
| err = property(get_err, set_err) | |
| # Graph the chart data. | |
| # Input is a 3d matrix that describes a plot that has multiple | |
| # groups, multiple bars in each group, and multiple values stacked | |
| # in each bar. The underlying bar() function expects a sequence of | |
| # bars in the same stack location and same group location, so the | |
| # organization of the matrix is that the inner most sequence | |
| # represents one of these bar groups, then those are grouped | |
| # together to make one full stack of bars in each group, and then | |
| # the outer most layer describes the groups. Here is an example | |
| # data set and how it gets plotted as a result. | |
| # | |
| # e.g. data = [[[10,11,12], [13,14,15], [16,17,18], [19,20,21]], | |
| # [[22,23,24], [25,26,27], [28,29,30], [31,32,33]]] | |
| # | |
| # will plot like this: | |
| # | |
| # 19 31 20 32 21 33 | |
| # 16 28 17 29 18 30 | |
| # 13 25 14 26 15 27 | |
| # 10 22 11 23 12 24 | |
| # | |
| # Because this arrangement is rather conterintuitive, the rearrange | |
| # function takes various matricies and arranges them to fit this | |
| # profile. | |
| # | |
| # This code deals with one of the dimensions in the matrix being | |
| # one wide. | |
| # | |
| def graph(self): | |
| if self.chartdata is None: | |
| raise AttributeError, "Data not set for bar chart!" | |
| dim = len(shape(self.inputdata)) | |
| cshape = shape(self.chartdata) | |
| if self.charterr is not None and shape(self.charterr) != cshape: | |
| raise AttributeError, 'Dimensions of error and data do not match' | |
| if dim == 1: | |
| colors = self.gen_colors(cshape[2]) | |
| colors = [ [ colors ] * cshape[1] ] * cshape[0] | |
| if dim == 2: | |
| colors = self.gen_colors(cshape[0]) | |
| colors = [ [ [ c ] * cshape[2] ] * cshape[1] for c in colors ] | |
| if dim == 3: | |
| colors = self.gen_colors(cshape[1]) | |
| colors = [ [ [ c ] * cshape[2] for c in colors ] ] * cshape[0] | |
| colors = array(colors) | |
| self.figure = pylab.figure(figsize=self.chart_size) | |
| outer_axes = None | |
| inner_axes = None | |
| if self.xsubticks is not None: | |
| color = self.figure.get_facecolor() | |
| self.metaaxes = self.figure.add_axes(self.figure_size, | |
| axisbg=color, frameon=False) | |
| for tick in self.metaaxes.xaxis.majorTicks: | |
| tick.tick1On = False | |
| tick.tick2On = False | |
| self.metaaxes.set_yticklabels([]) | |
| self.metaaxes.set_yticks([]) | |
| size = [0] * 4 | |
| size[0] = self.figure_size[0] | |
| size[1] = self.figure_size[1] + .12 | |
| size[2] = self.figure_size[2] | |
| size[3] = self.figure_size[3] - .12 | |
| self.axes = self.figure.add_axes(size) | |
| outer_axes = self.metaaxes | |
| inner_axes = self.axes | |
| else: | |
| self.axes = self.figure.add_axes(self.figure_size) | |
| outer_axes = self.axes | |
| inner_axes = self.axes | |
| bars_in_group = len(self.chartdata) | |
| width = 1.0 / ( bars_in_group + 1) | |
| center = width / 2 | |
| bars = [] | |
| for i,stackdata in enumerate(self.chartdata): | |
| bottom = array([0.0] * len(stackdata[0]), Float) | |
| stack = [] | |
| for j,bardata in enumerate(stackdata): | |
| bardata = array(bardata) | |
| ind = arange(len(bardata)) + i * width + center | |
| yerr = None | |
| if self.charterr is not None: | |
| yerr = self.charterr[i][j] | |
| bar = self.axes.bar(ind, bardata, width, bottom=bottom, | |
| color=colors[i][j], yerr=yerr) | |
| if self.xsubticks is not None: | |
| self.metaaxes.bar(ind, [0] * len(bardata), width) | |
| stack.append(bar) | |
| bottom += bardata | |
| bars.append(stack) | |
| if self.xlabel is not None: | |
| outer_axes.set_xlabel(self.xlabel) | |
| if self.ylabel is not None: | |
| inner_axes.set_ylabel(self.ylabel) | |
| if self.yticks is not None: | |
| ymin, ymax = self.axes.get_ylim() | |
| nticks = float(len(self.yticks)) | |
| ticks = arange(nticks) / (nticks - 1) * (ymax - ymin) + ymin | |
| inner_axes.set_yticks(ticks) | |
| inner_axes.set_yticklabels(self.yticks) | |
| elif self.ylim is not None: | |
| inner_axes.set_ylim(self.ylim) | |
| if self.xticks is not None: | |
| outer_axes.set_xticks(arange(cshape[2]) + .5) | |
| outer_axes.set_xticklabels(self.xticks) | |
| if self.xsubticks is not None: | |
| numticks = (cshape[0] + 1) * cshape[2] | |
| inner_axes.set_xticks(arange(numticks) * width + 2 * center) | |
| xsubticks = list(self.xsubticks) + [ '' ] | |
| inner_axes.set_xticklabels(xsubticks * cshape[2], fontsize=7, | |
| rotation=30) | |
| if self.legend is not None: | |
| if dim == 1: | |
| lbars = bars[0][0] | |
| if dim == 2: | |
| lbars = [ bars[i][0][0] for i in xrange(len(bars))] | |
| if dim == 3: | |
| number = len(bars[0]) | |
| lbars = [ bars[0][number - j - 1][0] for j in xrange(number)] | |
| if self.fig_legend: | |
| self.figure.legend(lbars, self.legend, self.legend_loc, | |
| prop=FontProperties(size=self.legend_size)) | |
| else: | |
| self.axes.legend(lbars, self.legend, self.legend_loc, | |
| prop=FontProperties(size=self.legend_size)) | |
| if self.title is not None: | |
| self.axes.set_title(self.title) | |
| def savefig(self, name): | |
| self.figure.savefig(name) | |
| def savecsv(self, name): | |
| f = file(name, 'w') | |
| data = array(self.inputdata) | |
| dim = len(data.shape) | |
| if dim == 1: | |
| #if self.xlabel: | |
| # f.write(', '.join(list(self.xlabel)) + '\n') | |
| f.write(', '.join([ '%f' % val for val in data]) + '\n') | |
| if dim == 2: | |
| #if self.xlabel: | |
| # f.write(', '.join([''] + list(self.xlabel)) + '\n') | |
| for i,row in enumerate(data): | |
| ylabel = [] | |
| #if self.ylabel: | |
| # ylabel = [ self.ylabel[i] ] | |
| f.write(', '.join(ylabel + [ '%f' % v for v in row]) + '\n') | |
| if dim == 3: | |
| f.write("don't do 3D csv files\n") | |
| pass | |
| f.close() | |
| if __name__ == '__main__': | |
| from random import randrange | |
| import random, sys | |
| dim = 3 | |
| number = 5 | |
| args = sys.argv[1:] | |
| if len(args) > 3: | |
| sys.exit("invalid number of arguments") | |
| elif len(args) > 0: | |
| myshape = [ int(x) for x in args ] | |
| else: | |
| myshape = [ 3, 4, 8 ] | |
| # generate a data matrix of the given shape | |
| size = reduce(lambda x,y: x*y, myshape) | |
| #data = [ random.randrange(size - i) + 10 for i in xrange(size) ] | |
| data = [ float(i)/100.0 for i in xrange(size) ] | |
| data = reshape(data, myshape) | |
| # setup some test bar charts | |
| if True: | |
| chart1 = BarChart() | |
| chart1.data = data | |
| chart1.xlabel = 'Benchmark' | |
| chart1.ylabel = 'Bandwidth (GBps)' | |
| chart1.legend = [ 'x%d' % x for x in xrange(myshape[-1]) ] | |
| chart1.xticks = [ 'xtick%d' % x for x in xrange(myshape[0]) ] | |
| chart1.title = 'this is the title' | |
| if len(myshape) > 2: | |
| chart1.xsubticks = [ '%d' % x for x in xrange(myshape[1]) ] | |
| chart1.graph() | |
| chart1.savefig('/tmp/test1.png') | |
| chart1.savefig('/tmp/test1.ps') | |
| chart1.savefig('/tmp/test1.eps') | |
| chart1.savecsv('/tmp/test1.csv') | |
| if False: | |
| chart2 = BarChart() | |
| chart2.data = data | |
| chart2.colormap = 'gray' | |
| chart2.graph() | |
| chart2.savefig('/tmp/test2.png') | |
| chart2.savefig('/tmp/test2.ps') | |
| # pylab.show() |