Matplotlib 中的可编辑表格:如何在表格单元格上叠加 TextBox 小部件? [英] Editable table in Matplotlib: How to superimpose a TextBox widget on a table cell?

查看:103
本文介绍了Matplotlib 中的可编辑表格:如何在表格单元格上叠加 TextBox 小部件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在努力在Matplotlib中创建一个交互式表.我希望用户能够单击表格中的数据单元格,以便他们可以编辑其值.根据@ImportanceOfBeingErnest的建议,

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

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

解决方案

单元格的位置确实以轴坐标给出,而 TextBox 的轴以图形坐标给出.您可以在两个坐标系之间进行转换

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

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

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

 将matplotlib.pyplot导入为plt从matplotlib.table导入CustomCell从matplotlib.widgets导入TextBox类EditableTable():def __init__(self, table):self.table =表self.ax = self.table.axescelld = table.get_celld()对于celld.keys()中的密钥:如果键[0] >0 和键 [1] >-1:cell = celld [key]cell.set_picker(真)self.canvas = self.table.get_figure().canvasself.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):如果isinstance(event.artist,CustomCell):#清除轴并删除文本框self.tba.clear()删除自我.tb# 使文本框轴可见self.tba.set_visible(真)self.currentcell = event.artist#将文本框轴的位置设置为当前单元格的位置trans = self.ax.figure.transFigure.inverted()trans2 = self.ax.transAxesbbox = self.currentcell.get_bbox().transformed(trans2 + trans)self.tba.set_position(bbox.bounds)#使用当前单元格的文本创建新的文本框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.currentcell.get_text().set_text(text)self.tba.set_visible(False)self.canvas.draw_idle()column_labels = ('Length', 'Width', 'Height', 'Sold?')row_labels = ['法拉利','保时捷']数据 = [[2.2, 1.6, 1.2, True],[2.1,1.5,1.4,False]无花果,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 实例.但是,这导致了无法追踪的错误.取而代之的是,每次点击一个单元格时都需要创建一个新实例,就像上面的更新版本一样.

I'm progressing towards creating an interactive table in Matplotlib. I want the user to be able to click on a data cell in the table so they can edit its value. Based on the advice of @ImportanceOfBeingErnest here I've registered a pick event handler for each cell of real data in the table. I can then detect which cell the user has clicked on. But I can't superimpose a TextBox object exactly over the picked cell so that to the user it looks like they're editing the cell they picked.

Dummy code to illustrate the issue:

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()

But if I run this and then click on, say, the cell with 1.2 in it I see this:

So how do I get the bounds of the TextBox to exactly match the bounds of the cell that the user has clicked on?

It seems the axes for the textbox are relative to the whole figure rather than to the table itself.

解决方案

The cell's position is indeed given in axes coordinates, while the TextBox's axes lives in figure coordinates. You may transform in between the two coordinate systems as

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

Of course you then also need to make sure the cell text is updated according to the content of the TextBox, each time it is submitted.

The following would be a fully functional editable matplotlib table.

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()

Note however that there is some bug sometimes preventing the cell to be correctly updated. I haven't found out the reason for this yet. Note that in a previous version of this, a single TextBox instance was used. However this led to untraceable errors. Instead one would need to create a new instance each time a cell is clicked, as in the above updated version.

这篇关于Matplotlib 中的可编辑表格:如何在表格单元格上叠加 TextBox 小部件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆