Matplotlib table formatting - change width of row label cells
Asked Answered
A

3

9

enter image description here

I have an obvious problem with this table, because the row labels are outside of the figure and I don't know how to fix it. I know I can go into child artists and change things like height and width there, but I already tried it and it didn't work, so maybe you can help me now.

Here is the code I'm using for this, hope it isn't too hard to read.... :

ind1=np.arange(5)


figure()
axes([0.2, 0.45, 0.7, 0.45])

## define different bars
l1=bar((ind1-0.45),mean_morphing_cc[0:5],width=0.2,bottom=0,color='darkblue',yerr=[min_dif_morphing_cc[0:5],max_dif_morphing_cc[0:5]],error_kw=dict(elinewidth=2, ecolor='darkkhaki'))

l2=bar((ind1-0.25),mean_persistence_cc[0:5],width=0.2,bottom=0,color='darkred',yerr=[min_dif_persistence_cc[0:5],max_dif_persistence_cc[0:5]],error_kw=dict(elinewidth=2, ecolor='darkkhaki'))

l3=bar((ind1+0.05),mean_m_vs_p_cc[0:5],width=0.2,bottom=0,color='purple',yerr=[min_dif_m_vs_p_cc[0:5],max_dif_m_vs_p_cc[0:5]],error_kw=dict(elinewidth=2, ecolor='darkkhaki'))

## print grid and a horizontal line at "0"
grid(True, linestyle='-', which='major', color='lightgrey',alpha=0.5)

hlines(0, -0.5,(max(ind1)+0.5), colors='k', linestyles='solid')


ylabel('mean((cloud cover_forecast/cloud cover_observation)-1),\n mean("morphing" - "persistence")',horizontalalignment='right',multialignment='center',size='xx-small')

xlim(-0.5,(max(ind1)+0.5))

xticks(ind1,[])

## print a legend
legend((l1[0],l2[0],l3[0]),('mean morphing cloud cover','mean persistence cloud cover','mean morphing vs persistence error'),'lower center',ncol=2,bbox_to_anchor=(0.5,-0.92),borderpad=0.2,labelspacing=0.2,handlelength=1,handletextpad=0.2)

leg = plt.gca().get_legend()

ltext  = leg.get_texts()  # all the text.Text instance in the legend

llines = leg.get_lines()  # all the lines.Line2D instance in the legend

frame  = leg.get_frame()  # the patch.Rectangle instance surrounding the legend

frame.set_facecolor('0.90')      # set the frame face color to light gray

plt.setp(ltext, fontsize='x-small')    # the legend text fontsize

## print the title
title('cloud cover over- or underestimation\n morphing forecast compared to persistence',size='small')

## print the table
the_table=plt.table(cellText=[[str(i)[:4] for i in mean_morphing_cc[0:5]],max_morphing_cc[0:5],min_morphing_cc[0:5],mean_persistence_cc[0:5],max_persistence_cc[0:5],min_persistence_cc[0:5],mean_m_vs_p_cc[0:5],max_m_vs_p_cc[0:5],min_m_vs_p_cc[0:5]],
                    rowLabels=['morphing: mean','morphing: max','morphing: min','persistence: mean','persistence: max','persistence: min','morph vs per: mean','morph vs per: max','morph vs per: min'],
                    rowColours=['darkblue','darkblue','darkblue','darkred','darkred','darkred','purple','purple','purple'],colLabels=['t+1','t+2','t+3','t+4','t+5'],loc='bottom')

## change cell properties
table_props=the_table.properties()
table_cells=table_props['child_artists']
for cell in table_cells:
    cell.set_width(0.2)
    cell.set_height(0.065)
    cell.set_fontsize(12)

show()
Angelika answered 17/12, 2012 at 20:4 Comment(0)
F
2

I haven't figured out the perfect answer to this, but I've found a solution that has been useful for my own purposes.

Adjusting both the colWidth and the table width can shrink the rowLabel-column width. In the source code, there's actually a variable they use, rowLabelWidth, but they do not let the user set it. Anyways, first I'm just going to rewrite your existing code so you can see where changes are made. This is the original in a variable format:

## setting properties to variables to make table function easier to read
data = [[str(i)[:4] for i in mean_morphing_cc[0:5]],max_morphing_cc[0:5],min_morphing_cc[0:5],mean_persistence_cc[0:5],max_persistence_cc[0:5],min_persistence_cc[0:5],mean_m_vs_p_cc[0:5],max_m_vs_p_cc[0:5],min_m_vs_p_cc[0:5]]
rowLabels = ['morphing: mean','morphing: max','morphing: min','persistence: mean','persistence: max','persistence: min','morph vs per: mean','morph vs per: max','morph vs per: min']
rowColours = ['darkblue','darkblue','darkblue','darkred','darkred','darkred','purple','purple','purple']
colLabels = ['t+1','t+2','t+3','t+4','t+5']
loc = 'bottom'

## without changing anything, this is what your table function would look like
the_table=plt.table(cellText = data,
                rowLabels = rowLabels, rowColours = rowColours,
                colLabels = colLabels, loc = loc)

Here's what's in the source code for rowLabelWidth, which we'll use to help figure out what to set width and colWidth to.

# Do row labels
if rowLabels is not None:
    for row in xrange(rows):
        table.add_cell(row + offset, -1,
                       width=rowLabelWidth or 1e-15, height=height,
                       text=rowLabels[row], facecolor=rowColours[row],
                       loc=rowLoc)
    if rowLabelWidth == 0:
        table.auto_set_column_width(-1)

It appears you have set the chart to a width in axes([0.2, 0.45, 0.7, 0.45]), to 0.7, so we will set that to a variable, tb_width

tb_width = 0.7

The rowLabelWidth gets auto-sized, which doesn't really help us. But if you play around with the following three options for colWidths, you can start to figure out how to make it work the way you want. Add in bbox properties to state specifically where the table should be. An important thing to note is that it appears the rowLabelWidth is not included in the overall table width.

## standard - essentially what happens when you don't specify colWidths. Takes the table width, divides it by the number of columns, giving each column an equal width.
colWidths = [tb_width/n_cols] * n_cols
## rowLabels stick out on the left

## similar to the above, but the '+1' attempts to account for the fact that another column's width, rowLabels, should fit inside the overall table width
colWidths=[( tb_width / (n_cols + 1) )] * n_cols  

## set your own width. this will start messing with the width of the rowLabelsWidth as now the colWidths aren't perfectly proportioned within the table width
tb_colWidth = 0.08
colWidths = [tb_colWidth] * n_cols

To ensure that the rowLabels fit right beneath the chart without sticking out on the left, use bbox coordinates. Using the bottom left hand corner of the table as your reference point, position the table: bbox=[x_coordinate, y_coordinate, width, height]. If the x_coordinate is set to account for your tb_colWidth, then it will shift the table over the exact amount necessary to have the rowLabels-column's leftmost side directly under the left corner of the chart above.

bbox = [tb_colWidth, y_coordinate, tb_width, tb_height]

If this now causes the rightmost column to stick out from under the chart, then shrink the width by the size of one column:

bbox = [tb_colWidth, y_coordinate, tb_width - tb_colWidth, tb_height]

Now put it all together:

the_table=plt.table(cellText = data,
            rowLabels = rowLabels, rowColours = rowColours,
            colLabels = colLabels, loc = 'bottom',
            colWidths = colWidths, bbox = bbox)

You shouldn't have to do your final table cell loop of adjusting the cell widths, but instead, can control that using the above tools.

Fonz answered 14/11, 2017 at 18:54 Comment(0)
M
1

Even though the question had been asked a long time ago, I found a solution that worked for me and could help others.

NOTE: Currently I'm using matplotlib's version 3.5.3

You are close of the solution in this part of your code. You just have to modify the right cells.

## print the table
the_table=plt.table(cellText=[[str(i)[:4] for i in mean_morphing_cc[0:5]],max_morphing_cc[0:5],min_morphing_cc[0:5],mean_persistence_cc[0:5],max_persistence_cc[0:5],min_persistence_cc[0:5],mean_m_vs_p_cc[0:5],max_m_vs_p_cc[0:5],min_m_vs_p_cc[0:5]],
                    rowLabels=['morphing: mean','morphing: max','morphing: min','persistence: mean','persistence: max','persistence: min','morph vs per: mean','morph vs per: max','morph vs per: min'],
                    rowColours=['darkblue','darkblue','darkblue','darkred','darkred','darkred','purple','purple','purple'],colLabels=['t+1','t+2','t+3','t+4','t+5'],loc='bottom')

## change cell properties
table_props=the_table.properties()table_cells=table_props['child_artists']
for cell in table_cells:
    cell.set_width(0.2)
    cell.set_height(0.065)
    cell.set_fontsize(12)

If we print the keys of the properties we'll find the next list:

for property in table.properties():
    print(property)

agg_filter
alpha
animated
celld
children
clip_box
clip_on
clip_path
figure
gid
in_layout
label
path_effects
picker
rasterized
sketch_params
snap
transform
transformed_clip_path_and_affine
url
visible
zorder

We have to focus in celld and children. celld it's a coords list of each cell including row labels.

for cell in table.properties()["celld"]:
     print(cell)
(coord_x, coord_y)
(1, 0)
(1, 1)
(2, 0)
(2, 1)
(3, 0)
(3, 1)
(4, 0)
(4, 1)
(0, 0)
(0, 1)
(1, -1)
(2, -1)
(3, -1)
(4, -1)

Note: This amount of items in the list will vary in each case.

the row label cells are those that the y_coord is -1 and the x_coord start in 1. We have to modify those cells.

The property Children it's the same list but fill with Rectangle objects.

for cell in table.properties()["children"]:
     print(cell)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)
Rectangle(xy=(0, 0), width=0.7, height=0.04329, angle=0)

As we can see, each rectangle doesn't specify the coords. But I founded that they are in the same position inside the list just like celld list.

--- SOLUTION ---

Although I built a pie chart, the table code works with others charts.

import matplotlib.pyplot as plt

columns_name = ["Column 1", "Column2"]
data = [data1, data2, data3, data4]
rows = [[data1, "value1"], [data2, value2], [data3, "value3"], 
["data4", "value4"]]

fig, ax = plt.subplots(figsize=(13.5, 7), subplot_kw=dict(aspect="equal"))
ax.pie(data, radius=1)
        
# get default colors
colors = plt.rcParams["axes.prop_cycle"].by_key()["color"]

table = plt.table(cellText=rows, rowColours=colors, colLabels=columns_name,
colLoc='center', cellLoc='left')
table.scale(1.4, 1.4)


for cell in table.properties()["children"]:
     print(cell)

# RowLabelCell width modification
for i in range(len(data)):
     table.properties()["children"][(i*-1)-1].set_width(0.10)

plt.tight_layout()
plt.show()

In the range of the for loop you have to put the same list that you used in the parameter cellText of the table or the data list to built the chart.

I recommend to use very low values on the function set_width().

--- RESULT ---

I'll let you an image of my result.

Chart and Table Result

Morten answered 14/9, 2022 at 12:48 Comment(0)
E
0

I had this problem and I partially fixed it by changing the alignment of the row labels from 'left' to 'right'.

cellDict = the_table.get_celld()
for x in range(1, len(row_labels)+1):
    cellDict[(x,-1)]._loc = 'right'

The get_celld() returns a dictionary of the cells, and cellDict[(x,-1)] picks the cell in the x row and -1 column, which is the row label column.

This does not change the width of the row label column, so you may still have a space off to the left of the label. Mine goes off the screen to the left, but at least I can see all of the text in the row label now.

Eagle answered 23/7, 2018 at 17:53 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.