2

I am trying to create a horizontal bar graph with a table in matplotlib. I am almost there, but fail to align the table with the graph. What I got so far:

import matplotlib
matplotlib.use('Agg')
import numpy as np
import matplotlib.pyplot as plt


# Example data
appsol = ['llolLl', 'nnM', 'lllld bbbblnl', 'x2x', 'foobar', 'EXZ', 'Flups', 'Flaps', 'Foobar Barfooment', 'ABC', 'FABAS', 'common', 'AQT', 'Faberjak', 'simsalsa', 'LESS', 'Wermut']
y_pos = np.arange(len(appsol)) - .3
y_pos_2 = np.arange(len(appsol)) - .1
y_pos_3 = np.arange(len(appsol)) + .1
y_pos_4 = np.arange(len(appsol)) + .3
num_tickets = [4, 4,3,2,6,7,8,1,4,4,3,2,6,7,8,1,9]
num_tickets_2 = [7,6,5,4,3,4,2,1,2,4,1,0,3,0,2,1,0]
num_tickets_3 = [1,2,1,1,1,2,2,3,1,1,2,1,3,1,1,2,3]
num_tickets_4 = [8,7,6,2,13,6,8,9,7,6,5,4,3,6,8,9,12]

bar_width = .2

fig = plt.figure(figsize=(20,20))
ax = fig.add_subplot(111)

# correct yticks
plt.yticks(y_pos_2, appsol)

plt.barh(y_pos, num_tickets, bar_width,  align='center', alpha=0.4, color='r')
plt.barh(y_pos_2, num_tickets_2, bar_width,  align='center', alpha=0.4, color='b')
plt.barh(y_pos_3, num_tickets_3, bar_width,  align='center', alpha=0.4, color='y')
plt.barh(y_pos_4, num_tickets_4, bar_width,  align='center', alpha=0.4, color='g')
plt.yticks(y_pos, appsol)
plt.xlabel('Numbers')
plt.title('Horizontal Bar Chart with table')

# Table

empty_labels = ['' for a in appsol ]
plt.yticks(y_pos_2, empty_labels)
plt.tick_params(\
    axis='y',          # changes apply to the x-axis
    which='both',      # both major and minor ticks are affected
    left='off',      # ticks along the bottom edge are off
    right='off',         # ticks along the top edge are off
    labelbottom='off')

# Adjust layout to make room for the table:
plt.subplots_adjust(left=0.4, bottom=0.2)

cell_text = []
i = len(num_tickets) - 1
for j in num_tickets:
    cell_text.append([num_tickets[i], num_tickets_2[i], num_tickets_3[i], num_tickets_4[i]])
    i -= 1

row_lables = appsol
column_labels = ['So Huge\nMice', 'Elephants', 'Reptiles', 'Germs']

the_table = ax.table(cellText=cell_text,
        rowLabels=row_lables,
        colLabels=column_labels,
        loc='left')
the_table.set_fontsize(15)
the_table.scale(.5,5.0)
plt.savefig('barh_graph.png')
plt.close('all')

This produces enter image description here almost what I want, except that the table rows are not aligned with the bars of the graph. So I need a way to either move the graph down half a table row, or the table up half a table row. How can I do this?

5
  • Are the items even aligned at all? 'Wermut' seems to be at the top of the barplot but at the bottom of the table. Commented May 9, 2014 at 12:12
  • You are right - that was messed up. Thanks for pointing this out. Hurray for code review ;) It's corrected now. Commented May 9, 2014 at 12:35
  • I am glad you sorted out your problem, however it is best to post the minimal amount of code required to replicate your problem (and most mpl problems can be reproduced in <20 lines), you probably would have found your own problem. Commented May 9, 2014 at 13:23
  • Basic question: Why do you change your default backend to 'Agg', using matplotlib.use('Agg')? Secondary question: Do you have any idea why plt.show() might not work with 'Agg' backend (as in my case)? Commented Jun 30, 2018 at 21:53
  • I think I need the Agg backend to be able to save plots as png files. I need the pngs to include them in a pdf report. I think the plt.show() is not "working" with the Agg backend because its designed to produces files, instead of showing them on the screen. hth Commented Jul 2, 2018 at 6:07

2 Answers 2

5

I think its easier if you create two subplots and add the table to the left subplot, instead of deriving a new subplot from the single subplot you have now. If you add the table to an existing subplot, you can use the bbox to stretch it from 0 to 1 (so fully) in the y-direction. Since the table has a header, setting the ylim of the right plot to (0, n_items), will make both align properly, and adding a slight offset because the bars are also given an offset of 0.4 (offset of the outer bar + half a barwidth). This should work automatically if the number of elements changes.

bar_width = .2

fig, axs = plt.subplots(1,2, figsize=(12,6))
fig.subplots_adjust(wspace=0, top=1, right=1, left=0, bottom=0)

axs[1].barh(y_pos[::-1], num_tickets[::-1], bar_width,  align='center', alpha=0.4, color='r')
axs[1].barh(y_pos_2[::-1], num_tickets_2[::-1], bar_width,  align='center', alpha=0.4, color='b')
axs[1].barh(y_pos_3[::-1], num_tickets_3[::-1], bar_width,  align='center', alpha=0.4, color='y')
axs[1].barh(y_pos_4[::-1], num_tickets_4[::-1], bar_width,  align='center', alpha=0.4, color='g')


axs[1].set_yticks([])
axs[1].set_xlabel('Numbers')
axs[1].set_title('Horizontal Bar Chart with table')
axs[1].set_ylim(0 - .4, (len(appsol)) + .4)

cell_text = list(zip(num_tickets, num_tickets_2, num_tickets_3, num_tickets_4))

row_lables = appsol
column_labels = ['So Huge\nMice', 'Elephants', 'Reptiles', 'Germs']

axs[0].axis('off')

the_table = axs[0].table(cellText=cell_text,
                     rowLabels=row_lables,
                     colLabels=column_labels,
                     bbox=[0.4, 0.0, 0.6, 1.0])

the_table.set_fontsize(15)

plt.savefig('barh_graph.png')
plt.close('all')

enter image description here

If you look closely you might notice that the bars are ranging from the top of the table to the bottom, you could tweak them a bit to make them start at the center of the upper and lower cell of the table.

Sign up to request clarification or add additional context in comments.

3 Comments

Cool, that is what I really wanted. It's not only more flexible, but it also looks nicer.
I actually had to use plt.savefig('barh_graph.png', bbox_inches='tight') to get it right - copy pasting your version gave me a plot that filled the whole figure, so no boundary was shown, no xlegend or title.
You can narrow the area which the subplots cover by setting there edges more inward to the figure, like: top=0.9, right=0.9, left=0.1, bottom=0.1, or even more, depending on what else you want to show in your figure.
1

I found a workaround: I adjust the y-boundaries of the graph:

ax.set_ylim([-.5,17.5])

Of course these numbers only work with these data dimensions. So still, if someone has a better soltution I would like to see it. For completness, here is the enhanced version (also changed font size and added a legend):

import matplotlib
matplotlib.use('Agg')
import numpy as np
import matplotlib.pyplot as plt

# set font and size
font = {'family' : 'Bitstream Vera Sans',
        'size'   : 18}
matplotlib.rc('font', **font)

# Example data
appsol = ['llolLl', 'nnM', 'lllld bbbblnl', 'x2x', 'foobar', 'EXZ', 'Flups', 'Flaps', 'Foobar Barfooment', 'ABC', 'FABAS', 'common', 'AQT', 'Faberjak', 'simsalsa', 'LESS', 'Wermut']
y_pos = np.arange(len(appsol)) - .3
y_pos_2 = np.arange(len(appsol)) - .1
y_pos_3 = np.arange(len(appsol)) + .1
y_pos_4 = np.arange(len(appsol)) + .3
num_tickets = [4, 4,3,2,6,7,8,1,4,4,3,2,6,7,8,1,9]
num_tickets_2 = [7,6,5,4,3,4,2,1,2,4,1,0,3,0,2,1,0]
num_tickets_3 = [1,2,1,1,1,2,2,3,1,1,2,1,3,1,1,2,3]
num_tickets_4 = [8,7,6,2,13,6,8,9,7,6,5,4,3,6,8,9,12]

fig = plt.figure(figsize=(20,20))
ax = fig.add_subplot(111)

# correct yticks
plt.yticks(y_pos_2, appsol)
# this aligns table and graph!!!
ax.set_ylim([-.5,17.5])

labels = ['So Huge Mice', 'Elephants', 'Reptiles', 'Germs']
bar_width = .2

plt.barh(y_pos, num_tickets, bar_width,  align='center', alpha=0.4, color='r', label=labels[0])
plt.barh(y_pos_2, num_tickets_2, bar_width,  align='center', alpha=0.4, color='b', label=labels[1])
plt.barh(y_pos_3, num_tickets_3, bar_width,  align='center', alpha=0.4, color='y', label=labels[2])
plt.barh(y_pos_4, num_tickets_4, bar_width,  align='center', alpha=0.4, color='g', label=labels[3])
plt.yticks(y_pos, appsol)
plt.xlabel('Numbers')
plt.title('Horizontal Bar Chart with table')

# Legend
plt.legend(loc='lower center', shadow=True)
num_plots = 4
x_legend = 0.3
y_legend = -0.1
ax.legend(loc='lower center',
    bbox_to_anchor=(x_legend, y_legend),
    ncol=num_plots, # we want the legend on just one line
    shadow=True)
# Table

empty_labels = ['' for a in appsol ]
plt.yticks(y_pos_2, empty_labels)
plt.tick_params(\
    axis='y',          # changes apply to the x-axis
    which='both',      # both major and minor ticks are affected
    left='off',      # ticks along the bottom edge are off
    right='off',         # ticks along the top edge are off
    labelbottom='off')

# Adjust layout to make room for the table:
plt.subplots_adjust(left=0.4, bottom=0.1)

cell_text = []
i = len(num_tickets) - 1
for j in num_tickets:
    cell_text.append([num_tickets[i], num_tickets_2[i], num_tickets_3[i], num_tickets_4[i]])
    i -= 1

row_lables = appsol
column_labels = ['So Huge\nMice', 'Elephants', 'Reptiles', 'Germs']

the_table = ax.table(cellText=cell_text,
        rowLabels=row_lables,
        colLabels=column_labels,
        loc='left')
the_table.set_fontsize(18)
the_table.scale(.5,5.34)

plt.savefig('barh_graph.png')
plt.close('all')

And this is what it looks like:enter image description here

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.