matplotlib 如何在表格单元格中绘制迷你条形图

7eumitmz  于 2023-10-24  发布在  其他
关注(0)|答案(1)|浏览(78)

我正在使用matplotlib创建一个表格可视化。我想在表格的每一行的最后一个单元格内绘制一个迷你条形图。迷你条形图应该有两种不同的颜色来表示不同的百分比(现在是50%的绿色和50%的红色,稍后会处理)。我还想在带有白色文本的部分内显示红色部分的百分比。
下面是Python代码,它创建了一个可复制的示例:

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

# Create a sample DataFrame
df = pd.DataFrame({
    'A': np.random.randint(1, 10, 10),
    'B': np.random.randint(1, 10, 10),
    'C': np.random.randint(1, 10, 10),
    'D': np.random.randint(1, 10, 10),
    'BAR': np.random.randint(1, 10, 10),
})

# Function to draw mini-bar
def draw_mini_bar(ax, x, y, width, height, percentage):
    ax.add_patch(plt.Rectangle((x, y), width, height, facecolor='#76ed33'))
    ax.add_patch(plt.Rectangle((x, y), width * percentage / 100, height, facecolor='#f55d2f'))
    plt.draw()

# Function to render table
def render_mpl_table(data, col_width=3.0, row_height=0.625, font_size=14,
                     header_color='#40466e', row_colors=['#f1f1f2', '#ffffff'], edge_color='w',
                     bbox=[0, 0, 1, 1], 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)
    
    for k, cell in mpl_table._cells.items():
        cell.set_edgecolor(edge_color)
        
        if k[0] == 0:
            cell.set_text_props(weight='bold', color='w')
            cell.set_facecolor(header_color)
        else:
            cell.set_facecolor(row_colors[k[0]%len(row_colors)])
        
        # DRAW BARS IN LAST COLUMN
        if k[0] > 0 and k[1] == len(data.columns) - 1:
            draw_mini_bar(ax, cell.get_x(), cell.get_y(), cell.get_width(), cell.get_height(), 50)
            
    plt.show()

render_mpl_table(df)

当我运行这段代码时,迷你条形图没有按预期呈现。具体来说,它们要么出现在错误的位置,要么颜色显示不正确。
有没有人遇到过类似的问题,或者知道如何在matplotlib表格单元格中绘制这样的迷你条形图?
我尝试使用add_patch()方法在表格单元格内绘制矩形。我的目标是在每行的最后一个单元格中创建迷你条形图。我希望条形图完全是绿色,部分用红色覆盖,表示不同的百分比(现在我只需要设置50%绿色和50%红色)。
然而,结果并不像预期的那样。这些条要么出现在错误的位置,要么没有正确地呈现颜色。我似乎无法让红色和绿色部分准确地显示在表格单元格中。

lbsnaicq

lbsnaicq1#

注意事项

使用matplotlib创建自定义表可能会非常棘手。在matplotlib.tabledocumentation中甚至有一个警告!
Matplotlib中的表实现维护较少。如果需要更有特色的表实现,您可能希望尝试blume。
在matplotlib文档中的tutorial中,有一个关于用于表的不同索引的注解。
小心表的特殊索引.
尝试matplotlib以外的方法来解决这个问题是明智的。无论如何,我这里的答案是基于您的代码,因此使用matplotlib来解决您的问题。

最终输出

这是下面的解决方案生成的图。注意带有百分比和正确颜色/位置的注解条形图。

解决方案

当每个单元格的xy位置坐标设置正确时,使用ax.add_patch(...)手动创建条形图可以正常工作。ax.table(...)会自动为您设置这些坐标,但尝试访问它们会导致索引问题并弄乱整个表。相反,使用matplotlib.table.table(...)创建表格。然后在访问之前显式设置每个单元格的位置。这可以在单个for循环中完成。

代码

import matplotlib.pyplot as plt
import matplotlib.table as mtab # Don't forget this import!
import pandas as pd
import numpy as np

# Create a sample DataFrame
df = pd.DataFrame({
    'A': np.random.randint(1, 10, 10),
    'B': np.random.randint(1, 10, 10),
    'C': np.random.randint(1, 10, 10),
    'D': np.random.randint(1, 10, 10),
    'BAR': np.random.randint(1, 10, 10),
})

# Function to draw mini-bar
def draw_mini_bar(ax, x, y, width, height, percentage):
    ax.add_patch(plt.Rectangle((x, y), width, height, facecolor='#76ed33', edgecolor='k'))
    ax.add_patch(plt.Rectangle((x, y), width* percentage / 100, height, facecolor='#f55d2f', edgecolor='k'))
    # this labels the red bar on each barchart
    ax.text(x+.005, y+(height/2.8), str(percentage)+"%", c='w')
    plt.draw()

# Function to render table
def render_mpl_table(data, col_width=2.0, row_height=0.625, font_size=14,
                     header_color='#40466e', row_colors=['#f1f1f2', '#ffffff'], edge_color='w',
                     bbox=[0, 0, 1, 1], axs=None, **kwargs):
    
    if axs is None:
        size = (np.array(data.shape[::-1]) + np.array([0, 1])) * np.array([col_width, row_height])
        fig, axs = plt.subplots(figsize=size)
        axs.axis('off')
    
    # THIS IS A BIG CHANGE !!! Using matplotlib.table.table instead of ax.table changes everything
    # The cellText param needs to be a 2D list of strings which is why it looks so funky
    mpl_table = mtab.table(ax=axs, cellText=list([[str(j) for j in l] for l in df.values.tolist()]), bbox=bbox, colLabels=data.columns, **kwargs)
    
    # This creates a dictionary of all cells in the table. We can use it to access and manipulate 
    # individual cells. The keys are the row/column indices of each cell
    dictOfCells = mpl_table.get_celld()
    
    # looping through the keys from the dictionary of cells
    for i in dictOfCells.keys():
        cell = dictOfCells[i]
        cell.set_edgecolor(edge_color)
        
        if i[0] == 0:
            cell.set_text_props(weight='bold', color='w')
            cell.set_facecolor(header_color)
        else:
            cell.set_facecolor(row_colors[i[0]%len(row_colors)])
            # These are the coordinates for the lower left corner of each cell in the rightmost column
            # THEY NEED TO BE SET EXPLICITLY !!! Change the values as needed for different sized tables
            cell.set(xy=(0.8, (i[0]-1)/11))
            
        # just like above, needs to be set explicitly
        cell.set(height=(1/11))
        # DRAW BARS IN LAST COLUMN
        if i[0]>0 and i[1] == len(data.columns)-1:
            # x and y positions that were set above
            xPos = cell.get_x()
            yPos = cell.get_y()
            # notice that the percentage parameter is set to the value in each cell that the barchart is in
            draw_mini_bar(axs, xPos, yPos, cell.get_width(), cell.get_height(), 10*int(cell.get_text().get_text()))
            
    return mpl_table

# call the function here, can set it equal to a variable for more manipulation of the table
render_mpl_table(df)
plt.show()

相关问题