barchart.py revision 2283
12115Shsul@eecs.umich.edu# Copyright (c) 2005-2006 The Regents of The University of Michigan 21881Sbinkertn@umich.edu# All rights reserved. 31881Sbinkertn@umich.edu# 41881Sbinkertn@umich.edu# Redistribution and use in source and binary forms, with or without 51881Sbinkertn@umich.edu# modification, are permitted provided that the following conditions are 61881Sbinkertn@umich.edu# met: redistributions of source code must retain the above copyright 71881Sbinkertn@umich.edu# notice, this list of conditions and the following disclaimer; 81881Sbinkertn@umich.edu# redistributions in binary form must reproduce the above copyright 91881Sbinkertn@umich.edu# notice, this list of conditions and the following disclaimer in the 101881Sbinkertn@umich.edu# documentation and/or other materials provided with the distribution; 111881Sbinkertn@umich.edu# neither the name of the copyright holders nor the names of its 121881Sbinkertn@umich.edu# contributors may be used to endorse or promote products derived from 131881Sbinkertn@umich.edu# this software without specific prior written permission. 141881Sbinkertn@umich.edu# 151881Sbinkertn@umich.edu# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 161881Sbinkertn@umich.edu# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 171881Sbinkertn@umich.edu# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 181881Sbinkertn@umich.edu# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 191881Sbinkertn@umich.edu# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 201881Sbinkertn@umich.edu# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 211881Sbinkertn@umich.edu# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 221881Sbinkertn@umich.edu# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 231881Sbinkertn@umich.edu# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 241881Sbinkertn@umich.edu# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 251881Sbinkertn@umich.edu# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 261881Sbinkertn@umich.edu# 271881Sbinkertn@umich.edu# Authors: Nathan Binkert 281881Sbinkertn@umich.edu# Lisa Hsu 291881Sbinkertn@umich.edu 301881Sbinkertn@umich.eduimport matplotlib, pylab 312006Sbinkertn@umich.edufrom matplotlib.font_manager import FontProperties 321881Sbinkertn@umich.edufrom matplotlib.numerix import array, arange, reshape, shape, transpose, zeros 331881Sbinkertn@umich.edufrom matplotlib.numerix import Float 342119Shsul@eecs.umich.edufrom matplotlib.ticker import NullLocator 351881Sbinkertn@umich.edu 361881Sbinkertn@umich.edumatplotlib.interactive(False) 371881Sbinkertn@umich.edu 382006Sbinkertn@umich.edufrom chart import ChartOptions 391881Sbinkertn@umich.edu 402006Sbinkertn@umich.educlass BarChart(ChartOptions): 412006Sbinkertn@umich.edu def __init__(self, default=None, **kwargs): 422006Sbinkertn@umich.edu super(BarChart, self).__init__(default, **kwargs) 431881Sbinkertn@umich.edu self.inputdata = None 441881Sbinkertn@umich.edu self.chartdata = None 452180Sbinkertn@umich.edu self.inputerr = None 462180Sbinkertn@umich.edu self.charterr = None 471881Sbinkertn@umich.edu 481881Sbinkertn@umich.edu def gen_colors(self, count): 491881Sbinkertn@umich.edu cmap = matplotlib.cm.get_cmap(self.colormap) 501881Sbinkertn@umich.edu if count == 1: 511881Sbinkertn@umich.edu return cmap([ 0.5 ]) 522179Sbinkertn@umich.edu 532179Sbinkertn@umich.edu if count < 5: 542179Sbinkertn@umich.edu return cmap(arange(5) / float(4))[:count] 552179Sbinkertn@umich.edu 562179Sbinkertn@umich.edu return cmap(arange(count) / float(count - 1)) 571881Sbinkertn@umich.edu 581881Sbinkertn@umich.edu # The input data format does not match the data format that the 591881Sbinkertn@umich.edu # graph function takes because it is intuitive. The conversion 601881Sbinkertn@umich.edu # from input data format to chart data format depends on the 611881Sbinkertn@umich.edu # dimensionality of the input data. Check here for the 621881Sbinkertn@umich.edu # dimensionality and correctness of the input data 631881Sbinkertn@umich.edu def set_data(self, data): 641881Sbinkertn@umich.edu if data is None: 651881Sbinkertn@umich.edu self.inputdata = None 661881Sbinkertn@umich.edu self.chartdata = None 671881Sbinkertn@umich.edu return 681881Sbinkertn@umich.edu 691881Sbinkertn@umich.edu data = array(data) 701881Sbinkertn@umich.edu dim = len(shape(data)) 711881Sbinkertn@umich.edu if dim not in (1, 2, 3): 721881Sbinkertn@umich.edu raise AttributeError, "Input data must be a 1, 2, or 3d matrix" 731881Sbinkertn@umich.edu self.inputdata = data 741881Sbinkertn@umich.edu 751881Sbinkertn@umich.edu # If the input data is a 1d matrix, then it describes a 761881Sbinkertn@umich.edu # standard bar chart. 771881Sbinkertn@umich.edu if dim == 1: 781881Sbinkertn@umich.edu self.chartdata = array([[data]]) 791881Sbinkertn@umich.edu 801881Sbinkertn@umich.edu # If the input data is a 2d matrix, then it describes a bar 811881Sbinkertn@umich.edu # chart with groups. The matrix being an array of groups of 821881Sbinkertn@umich.edu # bars. 831881Sbinkertn@umich.edu if dim == 2: 841881Sbinkertn@umich.edu self.chartdata = transpose([data], axes=(2,0,1)) 851881Sbinkertn@umich.edu 861881Sbinkertn@umich.edu # If the input data is a 3d matrix, then it describes an array 871881Sbinkertn@umich.edu # of groups of bars with each bar being an array of stacked 881881Sbinkertn@umich.edu # values. 891881Sbinkertn@umich.edu if dim == 3: 901881Sbinkertn@umich.edu self.chartdata = transpose(data, axes=(1,2,0)) 911881Sbinkertn@umich.edu 921881Sbinkertn@umich.edu def get_data(self): 931881Sbinkertn@umich.edu return self.inputdata 941881Sbinkertn@umich.edu 951881Sbinkertn@umich.edu data = property(get_data, set_data) 961881Sbinkertn@umich.edu 972180Sbinkertn@umich.edu def set_err(self, err): 982180Sbinkertn@umich.edu if err is None: 992180Sbinkertn@umich.edu self.inputerr = None 1002180Sbinkertn@umich.edu self.charterr = None 1012180Sbinkertn@umich.edu return 1022180Sbinkertn@umich.edu 1032180Sbinkertn@umich.edu err = array(err) 1042180Sbinkertn@umich.edu dim = len(shape(err)) 1052180Sbinkertn@umich.edu if dim not in (1, 2, 3): 1062180Sbinkertn@umich.edu raise AttributeError, "Input err must be a 1, 2, or 3d matrix" 1072180Sbinkertn@umich.edu self.inputerr = err 1082180Sbinkertn@umich.edu 1092180Sbinkertn@umich.edu if dim == 1: 1102180Sbinkertn@umich.edu self.charterr = array([[err]]) 1112180Sbinkertn@umich.edu 1122180Sbinkertn@umich.edu if dim == 2: 1132180Sbinkertn@umich.edu self.charterr = transpose([err], axes=(2,0,1)) 1142180Sbinkertn@umich.edu 1152180Sbinkertn@umich.edu if dim == 3: 1162180Sbinkertn@umich.edu self.charterr = transpose(err, axes=(1,2,0)) 1172180Sbinkertn@umich.edu 1182180Sbinkertn@umich.edu def get_err(self): 1192180Sbinkertn@umich.edu return self.inputerr 1202180Sbinkertn@umich.edu 1212180Sbinkertn@umich.edu err = property(get_err, set_err) 1222180Sbinkertn@umich.edu 1231881Sbinkertn@umich.edu # Graph the chart data. 1241881Sbinkertn@umich.edu # Input is a 3d matrix that describes a plot that has multiple 1251881Sbinkertn@umich.edu # groups, multiple bars in each group, and multiple values stacked 1261881Sbinkertn@umich.edu # in each bar. The underlying bar() function expects a sequence of 1271881Sbinkertn@umich.edu # bars in the same stack location and same group location, so the 1281881Sbinkertn@umich.edu # organization of the matrix is that the inner most sequence 1291881Sbinkertn@umich.edu # represents one of these bar groups, then those are grouped 1301881Sbinkertn@umich.edu # together to make one full stack of bars in each group, and then 1311881Sbinkertn@umich.edu # the outer most layer describes the groups. Here is an example 1321881Sbinkertn@umich.edu # data set and how it gets plotted as a result. 1331881Sbinkertn@umich.edu # 1341881Sbinkertn@umich.edu # e.g. data = [[[10,11,12], [13,14,15], [16,17,18], [19,20,21]], 1351881Sbinkertn@umich.edu # [[22,23,24], [25,26,27], [28,29,30], [31,32,33]]] 1361881Sbinkertn@umich.edu # 1371881Sbinkertn@umich.edu # will plot like this: 1381881Sbinkertn@umich.edu # 1391881Sbinkertn@umich.edu # 19 31 20 32 21 33 1401881Sbinkertn@umich.edu # 16 28 17 29 18 30 1411881Sbinkertn@umich.edu # 13 25 14 26 15 27 1421881Sbinkertn@umich.edu # 10 22 11 23 12 24 1431881Sbinkertn@umich.edu # 1441881Sbinkertn@umich.edu # Because this arrangement is rather conterintuitive, the rearrange 1451881Sbinkertn@umich.edu # function takes various matricies and arranges them to fit this 1461881Sbinkertn@umich.edu # profile. 1471881Sbinkertn@umich.edu # 1481881Sbinkertn@umich.edu # This code deals with one of the dimensions in the matrix being 1491881Sbinkertn@umich.edu # one wide. 1501881Sbinkertn@umich.edu # 1511881Sbinkertn@umich.edu def graph(self): 1521881Sbinkertn@umich.edu if self.chartdata is None: 1531881Sbinkertn@umich.edu raise AttributeError, "Data not set for bar chart!" 1541881Sbinkertn@umich.edu 1551881Sbinkertn@umich.edu dim = len(shape(self.inputdata)) 1561881Sbinkertn@umich.edu cshape = shape(self.chartdata) 1572180Sbinkertn@umich.edu if self.charterr is not None and shape(self.charterr) != cshape: 1582180Sbinkertn@umich.edu raise AttributeError, 'Dimensions of error and data do not match' 1592180Sbinkertn@umich.edu 1601881Sbinkertn@umich.edu if dim == 1: 1611881Sbinkertn@umich.edu colors = self.gen_colors(cshape[2]) 1621881Sbinkertn@umich.edu colors = [ [ colors ] * cshape[1] ] * cshape[0] 1631881Sbinkertn@umich.edu 1641881Sbinkertn@umich.edu if dim == 2: 1651881Sbinkertn@umich.edu colors = self.gen_colors(cshape[0]) 1661881Sbinkertn@umich.edu colors = [ [ [ c ] * cshape[2] ] * cshape[1] for c in colors ] 1671881Sbinkertn@umich.edu 1681881Sbinkertn@umich.edu if dim == 3: 1691881Sbinkertn@umich.edu colors = self.gen_colors(cshape[1]) 1701881Sbinkertn@umich.edu colors = [ [ [ c ] * cshape[2] for c in colors ] ] * cshape[0] 1711881Sbinkertn@umich.edu 1721881Sbinkertn@umich.edu colors = array(colors) 1731881Sbinkertn@umich.edu 1742115Shsul@eecs.umich.edu self.figure = pylab.figure(figsize=self.chart_size) 1752115Shsul@eecs.umich.edu 1762115Shsul@eecs.umich.edu outer_axes = None 1772115Shsul@eecs.umich.edu inner_axes = None 1782119Shsul@eecs.umich.edu if self.xsubticks is not None: 1792119Shsul@eecs.umich.edu color = self.figure.get_facecolor() 1802182Sbinkertn@umich.edu self.metaaxes = self.figure.add_axes(self.figure_size, 1812182Sbinkertn@umich.edu axisbg=color, frameon=False) 1822119Shsul@eecs.umich.edu for tick in self.metaaxes.xaxis.majorTicks: 1832119Shsul@eecs.umich.edu tick.tick1On = False 1842119Shsul@eecs.umich.edu tick.tick2On = False 1852115Shsul@eecs.umich.edu self.metaaxes.set_yticklabels([]) 1862115Shsul@eecs.umich.edu self.metaaxes.set_yticks([]) 1872115Shsul@eecs.umich.edu size = [0] * 4 1882115Shsul@eecs.umich.edu size[0] = self.figure_size[0] 1892160Shsul@eecs.umich.edu size[1] = self.figure_size[1] + .12 1902115Shsul@eecs.umich.edu size[2] = self.figure_size[2] 1912160Shsul@eecs.umich.edu size[3] = self.figure_size[3] - .12 1922115Shsul@eecs.umich.edu self.axes = self.figure.add_axes(size) 1932115Shsul@eecs.umich.edu outer_axes = self.metaaxes 1942115Shsul@eecs.umich.edu inner_axes = self.axes 1952115Shsul@eecs.umich.edu else: 1962115Shsul@eecs.umich.edu self.axes = self.figure.add_axes(self.figure_size) 1972115Shsul@eecs.umich.edu outer_axes = self.axes 1982115Shsul@eecs.umich.edu inner_axes = self.axes 1992115Shsul@eecs.umich.edu 2001881Sbinkertn@umich.edu bars_in_group = len(self.chartdata) 2012160Shsul@eecs.umich.edu 2022160Shsul@eecs.umich.edu width = 1.0 / ( bars_in_group + 1) 2032160Shsul@eecs.umich.edu center = width / 2 2041881Sbinkertn@umich.edu 2051881Sbinkertn@umich.edu bars = [] 2061881Sbinkertn@umich.edu for i,stackdata in enumerate(self.chartdata): 2072006Sbinkertn@umich.edu bottom = array([0.0] * len(stackdata[0]), Float) 2081881Sbinkertn@umich.edu stack = [] 2091881Sbinkertn@umich.edu for j,bardata in enumerate(stackdata): 2101881Sbinkertn@umich.edu bardata = array(bardata) 2111881Sbinkertn@umich.edu ind = arange(len(bardata)) + i * width + center 2122180Sbinkertn@umich.edu yerr = None 2132180Sbinkertn@umich.edu if self.charterr is not None: 2142180Sbinkertn@umich.edu yerr = self.charterr[i][j] 2151881Sbinkertn@umich.edu bar = self.axes.bar(ind, bardata, width, bottom=bottom, 2162180Sbinkertn@umich.edu color=colors[i][j], yerr=yerr) 2172160Shsul@eecs.umich.edu if self.xsubticks is not None: 2182115Shsul@eecs.umich.edu self.metaaxes.bar(ind, [0] * len(bardata), width) 2191881Sbinkertn@umich.edu stack.append(bar) 2201881Sbinkertn@umich.edu bottom += bardata 2211881Sbinkertn@umich.edu bars.append(stack) 2221881Sbinkertn@umich.edu 2231881Sbinkertn@umich.edu if self.xlabel is not None: 2242115Shsul@eecs.umich.edu outer_axes.set_xlabel(self.xlabel) 2251881Sbinkertn@umich.edu 2261881Sbinkertn@umich.edu if self.ylabel is not None: 2272115Shsul@eecs.umich.edu inner_axes.set_ylabel(self.ylabel) 2281881Sbinkertn@umich.edu 2291881Sbinkertn@umich.edu if self.yticks is not None: 2301881Sbinkertn@umich.edu ymin, ymax = self.axes.get_ylim() 2311881Sbinkertn@umich.edu nticks = float(len(self.yticks)) 2321881Sbinkertn@umich.edu ticks = arange(nticks) / (nticks - 1) * (ymax - ymin) + ymin 2332115Shsul@eecs.umich.edu inner_axes.set_yticks(ticks) 2342115Shsul@eecs.umich.edu inner_axes.set_yticklabels(self.yticks) 2352006Sbinkertn@umich.edu elif self.ylim is not None: 2362283Sbinkertn@umich.edu inner_axes.set_ylim(self.ylim) 2371881Sbinkertn@umich.edu 2381881Sbinkertn@umich.edu if self.xticks is not None: 2392115Shsul@eecs.umich.edu outer_axes.set_xticks(arange(cshape[2]) + .5) 2402115Shsul@eecs.umich.edu outer_axes.set_xticklabels(self.xticks) 2412119Shsul@eecs.umich.edu 2422115Shsul@eecs.umich.edu if self.xsubticks is not None: 2432182Sbinkertn@umich.edu numticks = (cshape[0] + 1) * cshape[2] 2442182Sbinkertn@umich.edu inner_axes.set_xticks(arange(numticks) * width + 2 * center) 2452283Sbinkertn@umich.edu xsubticks = list(self.xsubticks) + [ '' ] 2462283Sbinkertn@umich.edu inner_axes.set_xticklabels(xsubticks * cshape[2], fontsize=7, 2472283Sbinkertn@umich.edu rotation=30) 2482119Shsul@eecs.umich.edu 2491881Sbinkertn@umich.edu if self.legend is not None: 2501881Sbinkertn@umich.edu if dim == 1: 2511881Sbinkertn@umich.edu lbars = bars[0][0] 2521881Sbinkertn@umich.edu if dim == 2: 2531881Sbinkertn@umich.edu lbars = [ bars[i][0][0] for i in xrange(len(bars))] 2541881Sbinkertn@umich.edu if dim == 3: 2551881Sbinkertn@umich.edu number = len(bars[0]) 2561881Sbinkertn@umich.edu lbars = [ bars[0][number - j - 1][0] for j in xrange(number)] 2571881Sbinkertn@umich.edu 2582180Sbinkertn@umich.edu if self.fig_legend: 2592180Sbinkertn@umich.edu self.figure.legend(lbars, self.legend, self.legend_loc, 2602180Sbinkertn@umich.edu prop=FontProperties(size=self.legend_size)) 2612180Sbinkertn@umich.edu else: 2622180Sbinkertn@umich.edu self.axes.legend(lbars, self.legend, self.legend_loc, 2632180Sbinkertn@umich.edu prop=FontProperties(size=self.legend_size)) 2641881Sbinkertn@umich.edu 2651881Sbinkertn@umich.edu if self.title is not None: 2661881Sbinkertn@umich.edu self.axes.set_title(self.title) 2671881Sbinkertn@umich.edu 2681881Sbinkertn@umich.edu def savefig(self, name): 2691881Sbinkertn@umich.edu self.figure.savefig(name) 2701881Sbinkertn@umich.edu 2712006Sbinkertn@umich.edu def savecsv(self, name): 2722006Sbinkertn@umich.edu f = file(name, 'w') 2732006Sbinkertn@umich.edu data = array(self.inputdata) 2742006Sbinkertn@umich.edu dim = len(data.shape) 2752006Sbinkertn@umich.edu 2762006Sbinkertn@umich.edu if dim == 1: 2772006Sbinkertn@umich.edu #if self.xlabel: 2782006Sbinkertn@umich.edu # f.write(', '.join(list(self.xlabel)) + '\n') 2792006Sbinkertn@umich.edu f.write(', '.join([ '%f' % val for val in data]) + '\n') 2802006Sbinkertn@umich.edu if dim == 2: 2812006Sbinkertn@umich.edu #if self.xlabel: 2822006Sbinkertn@umich.edu # f.write(', '.join([''] + list(self.xlabel)) + '\n') 2832006Sbinkertn@umich.edu for i,row in enumerate(data): 2842006Sbinkertn@umich.edu ylabel = [] 2852006Sbinkertn@umich.edu #if self.ylabel: 2862006Sbinkertn@umich.edu # ylabel = [ self.ylabel[i] ] 2872182Sbinkertn@umich.edu f.write(', '.join(ylabel + [ '%f' % v for v in row]) + '\n') 2882006Sbinkertn@umich.edu if dim == 3: 2892006Sbinkertn@umich.edu f.write("don't do 3D csv files\n") 2902006Sbinkertn@umich.edu pass 2912006Sbinkertn@umich.edu 2922006Sbinkertn@umich.edu f.close() 2932006Sbinkertn@umich.edu 2941881Sbinkertn@umich.eduif __name__ == '__main__': 2952006Sbinkertn@umich.edu from random import randrange 2961881Sbinkertn@umich.edu import random, sys 2971881Sbinkertn@umich.edu 2981881Sbinkertn@umich.edu dim = 3 2991881Sbinkertn@umich.edu number = 5 3001881Sbinkertn@umich.edu 3011881Sbinkertn@umich.edu args = sys.argv[1:] 3021881Sbinkertn@umich.edu if len(args) > 3: 3031881Sbinkertn@umich.edu sys.exit("invalid number of arguments") 3041881Sbinkertn@umich.edu elif len(args) > 0: 3051881Sbinkertn@umich.edu myshape = [ int(x) for x in args ] 3061881Sbinkertn@umich.edu else: 3071881Sbinkertn@umich.edu myshape = [ 3, 4, 8 ] 3081881Sbinkertn@umich.edu 3091881Sbinkertn@umich.edu # generate a data matrix of the given shape 3101881Sbinkertn@umich.edu size = reduce(lambda x,y: x*y, myshape) 3111881Sbinkertn@umich.edu #data = [ random.randrange(size - i) + 10 for i in xrange(size) ] 3121881Sbinkertn@umich.edu data = [ float(i)/100.0 for i in xrange(size) ] 3131881Sbinkertn@umich.edu data = reshape(data, myshape) 3141881Sbinkertn@umich.edu 3151881Sbinkertn@umich.edu # setup some test bar charts 3161881Sbinkertn@umich.edu if True: 3171881Sbinkertn@umich.edu chart1 = BarChart() 3181881Sbinkertn@umich.edu chart1.data = data 3191881Sbinkertn@umich.edu 3201881Sbinkertn@umich.edu chart1.xlabel = 'Benchmark' 3211881Sbinkertn@umich.edu chart1.ylabel = 'Bandwidth (GBps)' 3221881Sbinkertn@umich.edu chart1.legend = [ 'x%d' % x for x in xrange(myshape[-1]) ] 3231881Sbinkertn@umich.edu chart1.xticks = [ 'xtick%d' % x for x in xrange(myshape[0]) ] 3241881Sbinkertn@umich.edu chart1.title = 'this is the title' 3252119Shsul@eecs.umich.edu if len(myshape) > 2: 3262117Shsul@eecs.umich.edu chart1.xsubticks = [ '%d' % x for x in xrange(myshape[1]) ] 3271881Sbinkertn@umich.edu chart1.graph() 3282006Sbinkertn@umich.edu chart1.savefig('/tmp/test1.png') 3292006Sbinkertn@umich.edu chart1.savefig('/tmp/test1.ps') 3302006Sbinkertn@umich.edu chart1.savefig('/tmp/test1.eps') 3312006Sbinkertn@umich.edu chart1.savecsv('/tmp/test1.csv') 3321881Sbinkertn@umich.edu 3331881Sbinkertn@umich.edu if False: 3341881Sbinkertn@umich.edu chart2 = BarChart() 3351881Sbinkertn@umich.edu chart2.data = data 3361881Sbinkertn@umich.edu chart2.colormap = 'gray' 3371881Sbinkertn@umich.edu chart2.graph() 3382006Sbinkertn@umich.edu chart2.savefig('/tmp/test2.png') 3392006Sbinkertn@umich.edu chart2.savefig('/tmp/test2.ps') 3401881Sbinkertn@umich.edu 3412160Shsul@eecs.umich.edu# pylab.show() 342