Displaying LaTeX in pyQt/pySide QTableWidget

后端 未结 1 1614
一生所求
一生所求 2020-12-14 12:49

I would like to add mathematical expressions to the table labels (e.g.: 2^3 should be properly formatted)

Here is a simple example of a table: http://thomas-cokelaer

相关标签:
1条回答
  • 2020-12-14 13:36

    I've also been trying for some time to display complex labels in the header of a QTableWidget. I was able to do it by reimplementing the paintSection method of a QHeaderView and painting manually the label with a QTextDocument as described in a thread on Qt Centre.

    However, this solution was somewhat limited compared to what could be done with LaTex. I thought this could be a good idea to try the approach you suggested in your OP, i.e. using the capability of matplotlib to render LaTex in PySide.

    1. Convert matplotlib Figure to QPixmap

    First thing that is required in this approach is to be able to convert matplotlib figure in a format that can be easily painted on any QWidget. Below is a function that take a mathTex expression as input and convert it through matplotlib to a QPixmap.

    import sys
    import matplotlib as mpl
    from matplotlib.backends.backend_agg import FigureCanvasAgg
    from PySide import QtGui, QtCore
    
    def mathTex_to_QPixmap(mathTex, fs):
    
        #---- set up a mpl figure instance ----
    
        fig = mpl.figure.Figure()
        fig.patch.set_facecolor('none')
        fig.set_canvas(FigureCanvasAgg(fig))
        renderer = fig.canvas.get_renderer()
    
        #---- plot the mathTex expression ----
    
        ax = fig.add_axes([0, 0, 1, 1])
        ax.axis('off')
        ax.patch.set_facecolor('none')
        t = ax.text(0, 0, mathTex, ha='left', va='bottom', fontsize=fs)
    
        #---- fit figure size to text artist ----
    
        fwidth, fheight = fig.get_size_inches()
        fig_bbox = fig.get_window_extent(renderer)
    
        text_bbox = t.get_window_extent(renderer)
    
        tight_fwidth = text_bbox.width * fwidth / fig_bbox.width
        tight_fheight = text_bbox.height * fheight / fig_bbox.height
    
        fig.set_size_inches(tight_fwidth, tight_fheight)
    
        #---- convert mpl figure to QPixmap ----
    
        buf, size = fig.canvas.print_to_buffer()
        qimage = QtGui.QImage.rgbSwapped(QtGui.QImage(buf, size[0], size[1],
                                                      QtGui.QImage.Format_ARGB32))
        qpixmap = QtGui.QPixmap(qimage)
    
        return qpixmap
    

    2. Paint the QPixmaps to the header of a QTableWidget

    The next step is to paint the QPixmap in the header of a QTableWidget. As shown below, I've done it by sub-classing QTableWidget and reimplementing the setHorizontalHeaderLabels method, which is used to convert the mathTex expressions for the labels into QPixmap and to pass it as a list to a subclass of QHeaderView. The QPixmap are then painted within a reimplementation of the paintSection method of QHeaderView and the height of the header is set up to fit the height of the mathTex expression in the reimplementation of the sizeHint methods.

    class MyQTableWidget(QtGui.QTableWidget):   
        def __init__(self, parent=None):
            super(MyQTableWidget, self).__init__(parent)
    
            self.setHorizontalHeader(MyHorizHeader(self))
    
        def setHorizontalHeaderLabels(self, headerLabels, fontsize):
    
            qpixmaps = []
            indx = 0
            for labels in headerLabels:
                qpixmaps.append(mathTex_to_QPixmap(labels, fontsize))            
                self.setColumnWidth(indx, qpixmaps[indx].size().width() + 16)
                indx += 1
    
            self.horizontalHeader().qpixmaps = qpixmaps
    
            super(MyQTableWidget, self).setHorizontalHeaderLabels(headerLabels)
    
    
    class MyHorizHeader(QtGui.QHeaderView):
        def __init__(self, parent):
            super(MyHorizHeader, self).__init__(QtCore.Qt.Horizontal, parent)
    
            self.setClickable(True)
            self.setStretchLastSection(True)
    
            self.qpixmaps = []
    
        def paintSection(self, painter, rect, logicalIndex):
    
            if not rect.isValid():
                return
    
            #------------------------------ paint section (without the label) ----
    
            opt = QtGui.QStyleOptionHeader()        
            self.initStyleOption(opt)
    
            opt.rect = rect
            opt.section = logicalIndex
            opt.text = ""
    
            #---- mouse over highlight ----
    
            mouse_pos = self.mapFromGlobal(QtGui.QCursor.pos())               
            if rect.contains(mouse_pos):
                opt.state |= QtGui.QStyle.State_MouseOver
    
            #---- paint ----
    
            painter.save()        
            self.style().drawControl(QtGui.QStyle.CE_Header, opt, painter, self)
            painter.restore()
    
            #------------------------------------------- paint mathText label ----
    
            qpixmap = self.qpixmaps[logicalIndex]
    
            #---- centering ----
    
            xpix = (rect.width() - qpixmap.size().width()) / 2. + rect.x()
            ypix = (rect.height() - qpixmap.size().height()) / 2.
    
            #---- paint ----
    
            rect = QtCore.QRect(xpix, ypix, qpixmap.size().width(),
                                qpixmap.size().height())
            painter.drawPixmap(rect, qpixmap)        
    
        def sizeHint(self):
    
            baseSize = QtGui.QHeaderView.sizeHint(self)
    
            baseHeight = baseSize.height()
            if len(self.qpixmaps):
                for pixmap in self.qpixmaps:
                   baseHeight = max(pixmap.height() + 8, baseHeight)
            baseSize.setHeight(baseHeight)
    
            self.parentWidget().repaint()
    
            return baseSize
    

    3. Application

    Below is an example of a simple application of the above.

    if __name__ == '__main__':
    
        app = QtGui.QApplication(sys.argv)
    
        w = MyQTableWidget()
        w.verticalHeader().hide()
    
        headerLabels = [
            '$C_{soil}=(1 - n) C_m + \\theta_w C_w$',
            '$k_{soil}=\\frac{\\sum f_j k_j \\theta_j}{\\sum f_j \\theta_j}$',
            '$\\lambda_{soil}=k_{soil} / C_{soil}$']
    
        w.setColumnCount(len(headerLabels))
        w.setHorizontalHeaderLabels(headerLabels, 25)        
        w.setRowCount(3)
        w.setAlternatingRowColors(True)
    
        k = 1
        for j in range(3):
            for i in range(3):
                w.setItem(i, j, QtGui.QTableWidgetItem('Value %i' % (k)))
                k += 1
    
        w.show() 
        w.resize(700, 200)
    
        sys.exit(app.exec_())
    

    which results in:

    The solution is not perfect, but it is a good starting point. I'll update it when I will improve it for my own application.

    0 讨论(0)
提交回复
热议问题