| # 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() |