首页 > 解决方案 > Matplotlib 中的可编辑表格:如何在表格单元格上叠加 TextBox 小部件?

问题描述

我正在朝着在 Matplotlib 中创建交互式表格的方向前进。我希望用户能够单击表格中的数据单元格,以便他们可以编辑其值。根据@ImportanceOfBeingErnest here的建议,我为表中的每个真实数据单元格注册了一个选择事件处理程序。然后我可以检测到用户点击了哪个单元格。但是我不能将TextBox对象完全叠加在拾取的单元格上,以便用户看起来他们正在编辑他们拾取的单元格。

用于说明问题的虚拟代码:

import matplotlib.pyplot as plt

from matplotlib.table import CustomCell
from matplotlib.widgets import TextBox

def on_pick(event):

    if isinstance(event.artist, CustomCell):
        cell = event.artist
        # Doesn't work because cell.get_y() is negative:
        #text_box_axes = plt.axes([cell.get_x(), cell.get_y(), cell.get_width(), cell.get_height()])

        # This doesn't work either but at least you can see the TextBox on the figure!
        text_box_axes = plt.axes([cell.get_x(), -cell.get_y(), cell.get_width(), cell.get_height()])

        cell_text = cell.get_text().get_text()
        TextBox(text_box_axes, '', initial=cell_text)
        plt.draw()

column_labels = ('Length', 'Width', 'Height', 'Sold?')
row_labels = ['Ferrari', 'Porsche']
data = [[2.2, 1.6, 1.2, True],
        [2.1, 1.5, 1.4, False]]
table = plt.table(cellText=data, colLabels=column_labels, rowLabels=row_labels, cellLoc='center', loc='bottom')
text_box = None

celld = table.get_celld()
for key in celld.keys():
    # Each key is a tuple of the form (row, column).
    # Column headings are in row 0. Row headings are in column -1.
    # So the first item of data in the table is actually at (1, 0).
    if key[0] > 0 and key[1] > -1:
        cell = celld[key]
        cell.set_picker(True)

canvas = plt.gcf().canvas
canvas.mpl_connect('pick_event', on_pick)
plt.axis('off')

plt.show()

但是,如果我运行它然后单击其中包含的单元格,1.2我会看到:

在此处输入图像描述

那么如何让 的边界TextBox与用户单击的单元格的边界完全匹配?

似乎文本框的轴是相对于整个图形而不是表本身的。

标签: pythonmatplotlibplot

解决方案


单元格的位置确实在轴坐标中给出,而TextBox' 轴位于图形坐标中。您可以在两个坐标系之间转换为

trans = figure.transFigure.inverted()
trans2 = ax.transAxes
bbox = cell.get_bbox().transformed(trans2 + trans)
text_box_axes.set_position(bbox.bounds)

当然,您还需要确保TextBox每次提交时都根据 的内容更新单元格文本。

以下将是一个功能齐全的可编辑 matplotlib 表。

import matplotlib.pyplot as plt

from matplotlib.table import CustomCell
from matplotlib.widgets import TextBox

class EditableTable():
    def __init__(self, table):
        self.table = table
        self.ax = self.table.axes
        celld = table.get_celld()
        for key in celld.keys():
            if key[0] > 0 and key[1] > -1:
                cell = celld[key]
                cell.set_picker(True)
        self.canvas = self.table.get_figure().canvas
        self.cid = self.canvas.mpl_connect('pick_event', self.on_pick)
        self.tba = self.ax.figure.add_axes([0,0,.01,.01])
        self.tba.set_visible(False)
        self.tb = TextBox(self.tba, '', initial="")
        self.cid2 = self.tb.on_submit(self.on_submit)
        self.currentcell = celld[(1,0)]

    def on_pick(self, event):
        if isinstance(event.artist, CustomCell):
            # clear axes and delete textbox
            self.tba.clear()
            del self.tb
            # make textbox axes visible
            self.tba.set_visible(True)
            self.currentcell = event.artist
            # set position of textbox axes to the position of the current cell
            trans = self.ax.figure.transFigure.inverted()
            trans2 = self.ax.transAxes
            bbox = self.currentcell.get_bbox().transformed(trans2 + trans)
            self.tba.set_position(bbox.bounds)
            # create new Textbox with text of the current cell
            cell_text = self.currentcell.get_text().get_text()
            self.tb = TextBox(self.tba, '', initial=cell_text)
            self.cid2 = self.tb.on_submit(self.on_submit)

            self.canvas.draw()

    def on_submit(self, text):
        # write the text box' text back to the current cell
        self.currentcell.get_text().set_text(text)
        self.tba.set_visible(False)
        self.canvas.draw_idle()

column_labels = ('Length', 'Width', 'Height', 'Sold?')
row_labels = ['Ferrari', 'Porsche']
data = [[2.2, 1.6, 1.2, True],
        [2.1, 1.5, 1.4, False]]

fig, ax = plt.subplots()
table = ax.table(cellText=data, colLabels=column_labels, rowLabels=row_labels, 
                 cellLoc='center', loc='bottom')

et = EditableTable(table)

ax.axis('off')

plt.show()

但是请注意,有时存在一些错误会阻止单元格正确更新。我还没有找到这个的原因。 请注意,在此之前的版本中,使用了单个 TextBox 实例。然而,这导致了无法追踪的错误。相反,每次单击单元格时都需要创建一个新实例,如上述更新版本。


推荐阅读