In matplotlib, how to draw a bar graphs of multiple datasets to put smallest bars to front?
I want to put multiple datasets on a bar graph and stop the smaller bars being obscured by the larger ones, and I don't want to offset them. For example,
bar(0, 1.)
bar(0, 2.)
only shows the second bar of height of 2.0, the first bar is hidden. Is there a way to get matplotlib to draw the bars with the smallest on top? NB I don't want a stacked bar graph or to offset the bars in x-directions.
I can order all the data, from all datasets, by bar height and plot each bar individually in this order, but I'd prefer to plot each 开发者_高级运维bar individually instead plot each dataset in turn Does anyone know a way of doing this?
Many thanks
I know this is an old question, but I came across it for my own purposes, and since it seemed like something I'd do over and over, I put together a wrapper for the hist function (which is what I'll be using; modification to bar should be trivial):
from matplotlib import pyplot as mpl
from numpy import argsort, linspace
def hist_sorted(*args, **kwargs):
all_ns = []
all_patches = []
labels = kwargs.pop('labels', None)
if not labels:
labels = ['data %d' % (i+1) for i in range(len(args))]
elif len(labels) != len(args):
raise ValueError('length of labels not equal to length of data')
bins = kwargs.pop('bins', linspace(min(min(a) for a in args),
max(max(a) for a in args),
num = 11))
for data, label in zip(args, labels):
ns, bins, patches = mpl.hist(data, bins=bins, label=label, **kwargs)
all_ns.append(ns)
all_patches.append(patches)
z_orders = -argsort(all_ns, axis=0)
for zrow, patchrow in zip(z_orders, all_patches):
assert len(zrow) == len(patchrow)
for z_val, patch in zip(zrow, patchrow):
patch.set_zorder(z_val)
return all_ns, bins, all_patches
This takes the datasets as anonymous arguments, and any labels as keyword arguments (for the legend), as well as any other keyword argument usable with hist.
The bar method will return a matplotlib.patches.Rectangle object. The object has a set_zorder method. Setting the zorder of the first one higher than the second will place it on top.
You could "easily" order the z-order of elements by checking if they are at the same x and zordering based by height.
from matplotlib import pylab
pylab.bar([0, 1], [1.0, 2.0])
pylab.bar([0, 1], [2.0, 1.0])
# loop through all patch objects and collect ones at same x
all_patches = pylab.axes().patches
patch_at_x = {}
for patch in all_patches:
if patch.get_x() not in patch_at_x: patch_at_x[patch.get_x()] = []
patch_at_x[patch.get_x()].append(patch)
# custom sort function, in reverse order of height
def yHeightSort(i,j):
if j.get_height() > i.get_height(): return 1
else: return -1
# loop through sort assign z-order based on sort
for x_pos, patches in patch_at_x.iteritems():
if len(patches) == 1: continue
patches.sort(cmp=yHeightSort)
[patch.set_zorder(patches.index(patch)) for patch in patches]
pylab.show()
alt text http://img697.imageshack.us/img697/8381/tmpp.png
Original:
>>> from matplotlib import pylab
>>> data1 = [0.3, 0.9, 0.1]
>>> data2 = [3.0, 0.2, 0.5]
>>> colors = ['b','magenta','cyan']
>>> data_list = [data1,data2]
>>> num_bars = len(data_list)
>>> for i, d in enumerate(data_list):
... for j,value in enumerate(sorted(d,reverse=True)):
... c = colors[j]
... obj_list = pylab.bar(i*0.4,value,width=0.8/num_bars,color=c)
...
You can draw them in order, like this, or do the zorder
Edit:
I spiffed this up a little. Basically, the key is to sort the data for each bar from largest to smallest before calling bar. But you could go back later and do set_zorder etc. In fact, I save the objects returned from bar () just in case you wanted to inspect them.
import numpy as np
from pylab import *
data = [[6.7, 1.5, 4.5], [2.0, 3.25, 5.7]]
w = 0.5
xlocations = np.array(range(len(data)))+w
colors = ['r','b','cyan']
oL = list()
for x,d in zip(xlocations, data):
for c,value in zip(colors, sorted(d,reverse=True)):
b = bar(x, value, width=w, color=c)
oL.extend(b)
show()
精彩评论