Search code examples
pythonmatplotlibbordercellmatplotlib-table

Setting row edge color of matplotlib table


I've a pandas DataFrame plotted as a table using matplotlib (from this answer).

Now I want to set the bottom edge color of a given row and I've this code:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
import six

df = pd.DataFrame()
df['date'] = ['2016-04-01', '2016-04-02', '2016-04-03', '2016-04-04']
df['calories'] = [2200, 2100, 1500, 1800]
df['sleep hours'] = [2200, 2100, 1500, 1500]
df['gym'] = [True, False, False, True]

def render_mpl_table(data, col_width=3.0, row_height=0.625, font_size=14,
                     header_color='#40466e', row_colors=['#f1f1f2', 'w'], edge_color='w',
                     bbox=[0, 0, 1, 1], header_columns=0,
                     ax=None, **kwargs):
    if ax is None:
        size = (np.array(data.shape[::-1]) + np.array([0, 1])) * np.array([col_width, row_height])
        fig, ax = plt.subplots(figsize=size)
        ax.axis('off')

    mpl_table = ax.table(cellText=data.values, bbox=bbox, colLabels=data.columns, **kwargs)

    mpl_table.auto_set_font_size(False)
    mpl_table.set_fontsize(font_size)

    for k, cell in six.iteritems(mpl_table._cells):
        cell.set_edgecolor(edge_color)
        if k[0] == 0 or k[1] < header_columns:
            cell.set_text_props(weight='bold', color='w')
            cell.set_facecolor(header_color)
        else:
            cell.set_facecolor(row_colors[k[0]%len(row_colors) ])
    return ax

def get_table(ax):
    table = None
    for child in ax.get_children():
        if isinstance(child, matplotlib.table.Table):
            table = child
            return table
    return table

def set_row_edge_color(ax, row, color):
    table = get_table(ax)
    for k, cell in  six.iteritems(table._cells):
        if (k[0] == row):
            cell.set_edgecolor(color)

ax = render_mpl_table(df, header_columns=0, col_width=2.0)
set_row_edge_color(ax, 2, 'k')
plt.show()

I'm unable to set only the color of row bottom and it gets set like this: enter image description here

Is there a way to set only the row bottom color like this?

enter image description here

Or is there a way to locate row in the figure/plot and draw a horizontal line?


Solution

  • There is no general way to make lines of different thickness or color on individual sides of cells (Rectangles) in matplotlib. In the case of the question, the solution is however quite easily obtained via ax.axhline() (as commented by @GAnderson) due to the table filling the entire bounding box of the axes.

    You would first need to set the data range of the axes to range between -1 and the number of rows in the table. Then you can just plot an axhline at the position of choice.

    There are only two line changed (which I marked with a comment) and it seems you can completely get rid of the get_table function.

    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib
    import six
    
    df = pd.DataFrame()
    df['date'] = ['2016-04-01', '2016-04-02', '2016-04-03', '2016-04-04']
    df['calories'] = [2200, 2100, 1500, 1800]
    df['sleep hours'] = [2200, 2100, 1500, 1500]
    df['gym'] = [True, False, False, True]
    
    def render_mpl_table(data, col_width=3.0, row_height=0.625, font_size=14,
                         header_color='#40466e', row_colors=['#f1f1f2', 'w'], edge_color='w',
                         bbox=[0, 0, 1, 1], header_columns=0,
                         ax=None, **kwargs):
        if ax is None:
            size = (np.array(data.shape[::-1]) + np.array([0, 1])) * np.array([col_width, row_height])
            fig, ax = plt.subplots(figsize=size)
            ax.axis('off')
        ax.axis([0,1,data.shape[0],-1])                ## <---------- Change here
        mpl_table = ax.table(cellText=data.values, bbox=bbox, colLabels=data.columns, **kwargs)
    
        mpl_table.auto_set_font_size(False)
        mpl_table.set_fontsize(font_size)
    
        for k, cell in six.iteritems(mpl_table._cells):
            cell.set_edgecolor(edge_color)
            if k[0] == 0 or k[1] < header_columns:
                cell.set_text_props(weight='bold', color='w')
                cell.set_facecolor(header_color)
            else:
                cell.set_facecolor(row_colors[k[0]%len(row_colors) ])
        return ax
    
    
    def set_row_edge_color(ax, row, color):
        ax.axhline(y=row, color=color)                  ## <---------- Change here
    
    ax = render_mpl_table(df, header_columns=0, col_width=2.0)
    set_row_edge_color(ax, 2, 'k')
    plt.show()
    

    enter image description here