PyQt5_QScrollArea内容保存成图片

本文提供两个例子,一个简单,一个复杂。基础没有那么好的同学可以看第一个,功能单一且明确;第二个例子较复杂,是根据一个主题绘制多个曲线,里面还有很多其他功能点(在本人以前博文中提过),基础不扎实的同学看了容易晕。

目录

效果:

例子1

​例子2

 代码:

例子1

 例子2

使用

例子1

例子2 

 数据

效果:

例子1

 例子2

 

 代码:

例子1

import sys
from PyQt5 import QtCore, QtGui, QtWidgets

class ExampleWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.temp_save = 0
        self.temp_mark = 0
        self.init_data()
        self.init_ui()
        self.fill_pic_widget()

    def init_data(self):
        self.pic_file_name_list = ['000', '001', '002', '003', '004', '005', '006', '007', '008', '009', '010', '011',
                                   '012', '013', '014', '015', '016', '017']
        pass

    def init_ui(self):
        self.setWindowTitle('QScrollArea内部生成图片存储')
        one_btn = QtWidgets.QPushButton('换内容')
        one_btn.clicked.connect(self.one_btn_clicked)

        two_btn = QtWidgets.QPushButton('ScrollArea内部生成图片存储')
        two_btn.clicked.connect(self.two_btn_clicked)

        self.pic_layout = QtWidgets.QGridLayout()

        self.scroll_area = QtWidgets.QScrollArea()
        # self.scroll_area.setWidgetResizable(True)
        self.scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(one_btn)
        layout.addWidget(two_btn)
        layout.addWidget(self.scroll_area)
        self.setLayout(layout)
        pass

    def one_btn_clicked(self):
        self.temp_mark = 1
        self.fill_pic_widget()
        pass
    def two_btn_clicked(self):
        self.temp_save += 1
        path,_ = QtWidgets.QFileDialog.getSaveFileName(
            self,
            '选择图片存储路径',
            f"pic_{self.temp_save}",
            'JPG(*.jpg)'
        )
        if not path:
            return

        widget = self.scroll_area.widget()
        pix = widget.grab()
        pix.save(path)
        pass

    def fill_pic_widget(self):
        '''放置图片'''
        # 清空控件
        while self.pic_layout.count():
            item = self.pic_layout.takeAt(0)
            widget = item.widget()
            if widget is not None:
                widget.deleteLater()
                pass
            pass
        sc_child_widget = self.scroll_area.takeWidget()
        if sc_child_widget is not None:
            sc_child_widget.deleteLater()
        pre_dir = r'D:/temp010/'
        row_i = -2
        if self.temp_mark == 1:
            self.pic_file_name_list.reverse()
        for pic_index, file_name in enumerate(self.pic_file_name_list):
            one_check = QtWidgets.QCheckBox(file_name)
            pixmap = QtGui.QPixmap(pre_dir + file_name + '.jpeg')
            one_piclabel = QtWidgets.QLabel()
            one_piclabel.setPixmap(pixmap)
            one_piclabel.setScaledContents(True)

            col_i = pic_index % 4
            if col_i == 0:
                row_i += 2
            print(file_name, row_i, col_i)
            self.pic_layout.addWidget(one_check, row_i, col_i, 1, 1)
            self.pic_layout.addWidget(one_piclabel, row_i + 1, col_i, 1, 1)
            pass
        print('self.pic_layout,', self.pic_layout.count())
        one_sc_child_widget = QtWidgets.QWidget()
        one_sc_child_widget.setLayout(self.pic_layout)
        self.scroll_area.setWidget(one_sc_child_widget)
        pass

 例子2

import sys,math
import pandas as pd
from threading import Thread
from PyQt5 import QtCore,QtWidgets,QtGui
from PyQt5.QtCore import Qt
from typing import Any,Dict,List
import pyqtgraph as pg
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')

class RotateAxisItem(pg.AxisItem):
    def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
        p.setRenderHint(p.Antialiasing,False)
        p.setRenderHint(p.TextAntialiasing,True)

        ## draw long line along axis
        pen,p1,p2 = axisSpec
        p.setPen(pen)
        p.drawLine(p1,p2)
        p.translate(0.5,0)  ## resolves some damn pixel ambiguity

        ## draw ticks
        for pen,p1,p2 in tickSpecs:
            p.setPen(pen)
            p.drawLine(p1,p2)

        ## draw all text
        # if self.tickFont is not None:
        #     p.setFont(self.tickFont)
        p.setPen(self.pen())
        for rect,flags,text in textSpecs:
            # this is the important part
            p.save()
            p.translate(rect.x(),rect.y())
            p.rotate(-30)
            p.drawText(-rect.width(),rect.height(),rect.width(),rect.height(),flags,text)
            # restoring the painter is *required*!!!
            p.restore()

class RotateAxisItemOneYear(pg.AxisItem):
    def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
        p.setRenderHint(p.Antialiasing,False)
        p.setRenderHint(p.TextAntialiasing,True)

        ## draw long line along axis
        pen,p1,p2 = axisSpec
        p.setPen(pen)
        p.drawLine(p1,p2)
        p.translate(0.5,-1)  ## resolves some damn pixel ambiguity
        # p.translate(1,0)  ## resolves some damn pixel ambiguity

        ## draw ticks
        for pen,p1,p2 in tickSpecs:
            p.setPen(pen)
            p.drawLine(p1,p2)

        ## draw all text
        # if self.tickFont is not None:
        #     p.setFont(self.tickFont)
        font = QtGui.QFont()
        font.setPixelSize(9)
        p.setFont(font)
        p.setPen(self.pen())
        for rect,flags,text in textSpecs:
            # this is the important part
            p.save()
            p.translate(rect.x(),rect.y())
            p.rotate(-30)
            p.drawText(-rect.width()*0.6,rect.height(),rect.width(),rect.height(),flags,text)
            # restoring the painter is *required*!!!
            p.restore()

RotateAxisItem: 自定义横坐标

RotateAxisItemOneYear: 自定义横坐标,相比RotateAxisItem相比只是字号小,其实这两个可以合并为一个,大家自己探索哈。

class PyQtGraphLineWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.line_type: int = 1

        self.fomc_p = None
        self.temp_thread = None

        self.init_data()
        self.init_ui()
        # self.register_event()
    def init_data(self):
        # 颜色值 https://www.sioe.cn/yingyong/yanse-rgb-16/
        self.color_one = (30,144,255)
        self.color_two = (255,140,0)
        self.p2 = None
        self.legend2 = None
        self.curve2 = None
        pass
    def init_ui(self):
        self.title_label = QtWidgets.QLabel('折线图')
        self.title_label.setAlignment(Qt.AlignCenter)
        self.title_label.setStyleSheet('QLabel{font-size:18px;font-weight:bold;}')
        xax = RotateAxisItem(orientation='bottom')
        xax.setHeight(h=80)
        self.pw = pg.PlotWidget(axisItems={'bottom': xax})
        self.pw.setMouseEnabled(x=True,y=False)
        # self.pw.enableAutoRange(x=False,y=True)
        self.pw.setAutoVisible(x=False,y=True)
        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.title_label)
        layout.addWidget(self.pw)
        self.setLayout(layout)
        pass

    def set_data(self, data: Dict[str, Any]):
        '''单根y轴'''
        self.line_type = 1
        if data is None:
            self.pw.clear()
            return
        if self.p2 is not None:
            '''
            如果前一次显示的是双y轴,
            【暂且把self.pw叫做主视图,self.p2叫做右侧视图】
            1. 要把主视图右侧坐标轴隐藏
            2. 要把主视图右侧跟随变动的信号断开
            3. 将右侧视图(ViewBox)删除
            4. 将右侧视图(ViewBox)清空
            5. 将右侧视图置空
            '''
            self.pw.hideAxis('right')
            self.vb.sigResized.disconnect(self.updateViews)
            self.pw.scene().removeItem(self.p2)
            self.p2.clear()
            self.legend2.clear()
            self.curve2.clear()
            self.vb.clear()
            self.p2 = None
            self.legend2 = None
            self.curve2 = None
        # 将上一次视图清空
        self.pw.clear()
        self.pw.addLegend()
        title_str = data['title_str']
        self.title_label.setText(title_str)
        xTick = [data['xTick00']]
        x = data['x']
        y = data['y']
        y_name = data['y_name']
        self.pw.setLabel('left', y_name)
        self.y_datas = y
        self.x_data = xTick
        self.x_Tick = data['xTick']
        self.y_name = y_name
        xax = self.pw.getAxis('bottom')
        xax.setTicks(xTick)

        self.pw.plot(x, y, connect='finite', pen=pg.mkPen({'color': self.color_one, 'width': 4}), name=self.y_name)

        self.vLine = pg.InfiniteLine(angle=90, movable=False)
        self.hLine = pg.InfiniteLine(angle=0, movable=False)
        self.label = pg.TextItem()

        self.pw.addItem(self.vLine, ignoreBounds=True)
        self.pw.addItem(self.hLine, ignoreBounds=True)
        self.pw.addItem(self.label, ignoreBounds=True)
        self.vb = self.pw.getViewBox()
        self.proxy = pg.SignalProxy(self.pw.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)
        # 显示整条折线图
        self.pw.enableAutoRange()
        pass

    def set_data_2y(self, data: Dict[str, Any]):
        '''双y轴'''
        self.line_type = 2
        if data is None:
            self.pw.clear()
            return
        if self.p2 is not None:
            self.pw.hideAxis('right')
            self.vb.sigResized.disconnect(self.updateViews)
            self.pw.scene().removeItem(self.p2)
            self.p2.clear()
            self.legend2.clear()
            self.curve2.clear()
            self.vb.clear()
            self.p2 = None
            self.legend2 = None
            self.curve2 = None
        if self.fomc_p:
            self.pw.scene().removeItem(self.fomc_p)
        self.pw.clear()
        self.pw.addLegend()
        title_str = data['title_str']
        self.title_label.setText(title_str)
        xTick = [data['xTick00']]
        x = data['x']
        y = data['y']
        y_name = data['y_name']
        self.pw.setLabel('left', y_name)
        self.y_datas = y
        self.x_data = xTick
        self.x_Tick = data['xTick']
        self.y_name = y_name
        xax = self.pw.getAxis('bottom')
        xax.setTicks(xTick)

        self.pw.plot(x, y, connect='finite', pen=pg.mkPen({'color': self.color_one, 'width': 4}), name=self.y_name)

        self.vLine = pg.InfiniteLine(angle=90, movable=False)
        self.hLine = pg.InfiniteLine(angle=0, movable=False)
        self.label = pg.TextItem()

        self.pw.addItem(self.vLine, ignoreBounds=True)
        self.pw.addItem(self.hLine, ignoreBounds=True)
        self.pw.addItem(self.label, ignoreBounds=True)

        # 第二根y轴 start
        y2 = data['y2']
        y_name2 = data['y_name2']
        self.y_datas2 = y2
        self.pw.setLabel('right', y_name2)
        self.p2 = pg.ViewBox()
        self.p2.setMouseEnabled(x=True, y=False)
        self.p2.setAutoVisible(x=False, y=True)
        self.pw.scene().addItem(self.p2)
        self.pw.getAxis('right').linkToView(self.p2)
        self.p2.setXLink(self.pw)
        self.curve2 = pg.PlotCurveItem(x=x,y=y2, pen=pg.mkPen({'color': self.color_two, 'width': 4}), connect='finite')
        self.p2.addItem(self.curve2)
        self.legend2 = pg.LegendItem(offset=(0., 1.))
        self.legend2.setParentItem(self.p2)
        power = pg.PlotDataItem(antialias=True, pen=pg.mkPen({'color': self.color_two, 'width': 4}))
        self.legend2.addItem(power, y_name2)
        # 第二根y轴 end
        self.vb = self.pw.getViewBox()
        self.updateViews()
        self.vb.sigResized.connect(self.updateViews)
        self.proxy = pg.SignalProxy(self.pw.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)
        self.pw.enableAutoRange()
        pass
    def updateViews(self):
        if self.p2:
            self.p2.setGeometry(self.pw.getViewBox().sceneBoundingRect())
            self.p2.linkedViewChanged(self.pw.getViewBox(), self.p2.XAxis)
        if self.fomc_p:
            self.fomc_p.setGeometry(self.pw.getViewBox().sceneBoundingRect())
            self.fomc_p.linkedViewChanged(self.pw.getViewBox(), self.fomc_p.XAxis)
        pass

    def mouseMoved(self,evt):
        pos = evt[0]
        if self.pw.sceneBoundingRect().contains(pos):
            mousePoint = self.vb.mapSceneToView(pos)
            index = int(mousePoint.x())
            if index >= 0 and index < len(self.y_datas):
                x_str = self.x_Tick[index][1]

                y_str_html = ''
                if self.line_type == 2:
                    y_str2 = str(self.y_datas2[index])
                    y_str_html += '&nbsp;'+y_str2
                y_str = str(self.y_datas[index])
                y_str_html += '&nbsp;' + y_str
                html_str = '<p style="color:black;font-size:18px;font-weight:bold;">&nbsp;' + x_str +'&nbsp;'+y_str_html+ '</p>'
                self.label.setHtml(html_str)
                self.label.setPos(mousePoint.x(), mousePoint.y())
            self.vLine.setPos(mousePoint.x())
            self.hLine.setPos(mousePoint.y())
        pass

class PyQtGraphLineOneYearWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.line_type: int = 1

        self.fomc_p = None
        self.temp_thread = None

        self.init_data()
        self.init_ui()
        # self.register_event()
    def init_data(self):
        # 颜色值 https://www.sioe.cn/yingyong/yanse-rgb-16/
        self.color_one = (30,144,255)
        self.color_two = (255,140,0)
        self.p2 = None
        self.legend2 = None
        self.curve2 = None
        pass
    def init_ui(self):
        self.title_label = QtWidgets.QLabel('折线图')
        self.title_label.setAlignment(Qt.AlignCenter)
        self.title_label.setStyleSheet('QLabel{font-size:18px;font-weight:bold;}')
        xax = RotateAxisItemOneYear(orientation='bottom')
        xax.setHeight(h=50)
        self.pw = pg.PlotWidget(axisItems={'bottom': xax})
        self.pw.setMouseEnabled(x=True,y=False)
        # self.pw.enableAutoRange(x=False,y=True)
        self.pw.setAutoVisible(x=False,y=True)
        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.title_label)
        layout.addWidget(self.pw)
        self.setLayout(layout)
        pass

    def set_data(self, data: Dict[str, Any]):
        '''单根y轴'''
        self.line_type = 1
        if data is None:
            self.pw.clear()
            return
        if self.p2 is not None:
            '''
            如果前一次显示的是双y轴,
            【暂且把self.pw叫做主视图,self.p2叫做右侧视图】
            1. 要把主视图右侧坐标轴隐藏
            2. 要把主视图右侧跟随变动的信号断开
            3. 将右侧视图(ViewBox)删除
            4. 将右侧视图(ViewBox)清空
            5. 将右侧视图置空
            '''
            self.pw.hideAxis('right')
            self.vb.sigResized.disconnect(self.updateViews)
            self.pw.scene().removeItem(self.p2)
            self.p2.clear()
            self.legend2.clear()
            self.curve2.clear()
            self.vb.clear()
            self.p2 = None
            self.legend2 = None
            self.curve2 = None
        # 将上一次视图清空
        self.pw.clear()
        self.pw.addLegend()
        title_str = data['title_str']
        self.title_label.setText(title_str)
        xTick = [data['xTick00']]
        x = data['x']
        y = data['y']
        y_name = data['y_name']
        self.pw.setLabel('left', y_name)
        self.y_datas = y
        self.x_data = xTick
        self.x_Tick = data['xTick']
        self.y_name = y_name
        xax = self.pw.getAxis('bottom')
        xax.setTicks(xTick)

        self.pw.plot(x, y, connect='finite', pen=pg.mkPen({'color': self.color_one, 'width': 4}), name=self.y_name)

        self.vLine = pg.InfiniteLine(angle=90, movable=False)
        self.hLine = pg.InfiniteLine(angle=0, movable=False)
        self.label = pg.TextItem()

        self.pw.addItem(self.vLine, ignoreBounds=True)
        self.pw.addItem(self.hLine, ignoreBounds=True)
        self.pw.addItem(self.label, ignoreBounds=True)
        self.vb = self.pw.getViewBox()
        self.proxy = pg.SignalProxy(self.pw.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)
        # 显示整条折线图
        self.pw.enableAutoRange()
        pass

    def set_data_2y(self, data: Dict[str, Any]):
        '''双y轴'''
        self.line_type = 2
        if data is None:
            self.pw.clear()
            return
        if self.p2 is not None:
            self.pw.hideAxis('right')
            self.vb.sigResized.disconnect(self.updateViews)
            self.pw.scene().removeItem(self.p2)
            self.p2.clear()
            self.legend2.clear()
            self.curve2.clear()
            self.vb.clear()
            self.p2 = None
            self.legend2 = None
            self.curve2 = None
        if self.fomc_p:
            self.pw.scene().removeItem(self.fomc_p)
        self.pw.clear()
        self.pw.addLegend()
        title_str = data['title_str']
        self.title_label.setText(title_str)
        xTick = [data['xTick00']]
        x = data['x']
        y = data['y']
        y_name = data['y_name']
        self.pw.setLabel('left', y_name)
        self.y_datas = y
        self.x_data = xTick
        self.x_Tick = data['xTick']
        self.y_name = y_name
        xax = self.pw.getAxis('bottom')
        xax.setTicks(xTick)

        self.pw.plot(x, y, connect='finite', pen=pg.mkPen({'color': self.color_one, 'width': 4}), name=self.y_name)

        self.vLine = pg.InfiniteLine(angle=90, movable=False)
        self.hLine = pg.InfiniteLine(angle=0, movable=False)
        self.label = pg.TextItem()

        self.pw.addItem(self.vLine, ignoreBounds=True)
        self.pw.addItem(self.hLine, ignoreBounds=True)
        self.pw.addItem(self.label, ignoreBounds=True)

        # 第二根y轴 start
        y2 = data['y2']
        y_name2 = data['y_name2']
        self.y_datas2 = y2
        self.pw.setLabel('right', y_name2)
        self.p2 = pg.ViewBox()
        self.p2.setMouseEnabled(x=True, y=False)
        self.p2.setAutoVisible(x=False, y=True)
        self.pw.scene().addItem(self.p2)
        self.pw.getAxis('right').linkToView(self.p2)
        self.p2.setXLink(self.pw)
        self.curve2 = pg.PlotCurveItem(x=x,y=y2, pen=pg.mkPen({'color': self.color_two, 'width': 4}), connect='finite')
        self.p2.addItem(self.curve2)
        self.legend2 = pg.LegendItem(offset=(0., 1.))
        self.legend2.setParentItem(self.p2)
        power = pg.PlotDataItem(antialias=True, pen=pg.mkPen({'color': self.color_two, 'width': 4}))
        self.legend2.addItem(power, y_name2)
        # 第二根y轴 end
        self.vb = self.pw.getViewBox()
        self.updateViews()
        self.vb.sigResized.connect(self.updateViews)
        self.proxy = pg.SignalProxy(self.pw.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)
        self.pw.enableAutoRange()
        pass
    def updateViews(self):
        if self.p2:
            self.p2.setGeometry(self.pw.getViewBox().sceneBoundingRect())
            self.p2.linkedViewChanged(self.pw.getViewBox(), self.p2.XAxis)
        if self.fomc_p:
            self.fomc_p.setGeometry(self.pw.getViewBox().sceneBoundingRect())
            self.fomc_p.linkedViewChanged(self.pw.getViewBox(), self.fomc_p.XAxis)
        pass

    def mouseMoved(self,evt):
        pos = evt[0]
        if self.pw.sceneBoundingRect().contains(pos):
            mousePoint = self.vb.mapSceneToView(pos)
            index = int(mousePoint.x())
            if index >= 0 and index < len(self.y_datas):
                x_str = self.x_Tick[index][1]

                y_str_html = ''
                if self.line_type == 2:
                    y_str2 = str(self.y_datas2[index])
                    y_str_html += '&nbsp;'+y_str2
                y_str = str(self.y_datas[index])
                y_str_html += '&nbsp;' + y_str
                html_str = '<p style="color:black;font-size:18px;font-weight:bold;">&nbsp;' + x_str +'&nbsp;'+y_str_html+ '</p>'
                self.label.setHtml(html_str)
                self.label.setPos(mousePoint.x(), mousePoint.y())
            self.vLine.setPos(mousePoint.x())
            self.hLine.setPos(mousePoint.y())
        pass

PyQtGraphLineWidget和PyQtGraphLineOneYearWidget也是可以合并为一个的,我只是犯懒,大家有兴趣的可以自己探索哈,这两个是用于显示折线的控件

class ChildLineWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.init_data()
        self.init_ui()
        pass
    def init_data(self):
        pass
    def init_ui(self):
        self.setMinimumHeight(600)
        self.left_line_widget = PyQtGraphLineWidget()
        self.righttop_line_widget = PyQtGraphLineOneYearWidget()
        self.rightdown_table_widget = QtWidgets.QTableWidget()

        layout_right = QtWidgets.QVBoxLayout()
        layout_right.addWidget(self.righttop_line_widget,1)
        layout_right.addWidget(self.rightdown_table_widget,1)

        layout = QtWidgets.QHBoxLayout()
        layout.addWidget(self.left_line_widget,3)
        layout.addLayout(layout_right,1)
        self.setLayout(layout)
        pass
    def set_data(self,data:Dict[str,Any]):
        line_data = data['line_data']
        table_data = data['table_data']

        self.setting_line_data(line_data)
        self.setting_table_data(table_data)
        pass
    def setting_line_data(self,data:Dict[str,Any]):
        line_type = data['line_type']
        whole_line = data['whole_line']
        one_year_line = data['one_year_line']
        if line_type == 2:
            self.left_line_widget.set_data_2y(whole_line)
            self.righttop_line_widget.set_data_2y(one_year_line)
        else:
            self.left_line_widget.set_data(whole_line)
            self.righttop_line_widget.set_data(one_year_line)
        pass
    def setting_table_data(self,data:Dict[str,Any]):
        table_header = data['table_header']
        table_body = data['table_body']
        self.rightdown_table_widget.clearContents()
        self.rightdown_table_widget.setColumnCount(len(table_header))
        self.rightdown_table_widget.setHorizontalHeaderLabels(table_header)
        self.rightdown_table_widget.setRowCount(len(table_body))

        for r_i,r_v in enumerate(table_body):
            for c_i,c_v in enumerate(r_v):
                cell = QtWidgets.QTableWidgetItem(str(c_v))
                self.rightdown_table_widget.setItem(r_i,c_i,cell)
                pass
        self.rightdown_table_widget.resizeColumnsToContents()
        pass
    pass

ChildLineWidget: 在滚动区中一个数据的完整显示控件,显示数据的全数据折线图,近期数据折现图和最新50个数据列表

class MultiChildLineWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.init_data()
        self.init_ui()
        pass
    def init_data(self):
        self.title_str: str = '主题名'
        self.child_line_data_list: List[Any] = []
        pass
    def init_ui(self):
        self.layout_child = QtWidgets.QVBoxLayout()

        self.scroll_area = QtWidgets.QScrollArea()
        self.scroll_area.setWidgetResizable(True)
        self.scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

        layout = QtWidgets.QVBoxLayout()
        layout.addWidget(self.scroll_area)
        self.setLayout(layout)
        pass
    def set_data(self,data:Dict[str,Any]):
        title_str = data['title']
        self.child_line_data_list = data['line_data_list']

        self.title_str = title_str
        self.fill_child_line_widget()
        pass
    def fill_child_line_widget(self):
        # 清空控件
        while self.layout_child.count():
            item = self.layout_child.takeAt(0)
            widget = item.widget()
            if widget is not None:
                widget.deleteLater()
                pass
            pass
        sc_child_widget = self.scroll_area.takeWidget()
        if sc_child_widget is not None:
            sc_child_widget.deleteLater()

        title_label = QtWidgets.QLabel(self.title_str)
        title_label.setAlignment(Qt.AlignCenter)
        title_label.setStyleSheet('QLabel{font-size:26px;font-weight:bold;color:blue}')

        self.layout_child.addWidget(title_label)
        self.layout_child.addSpacing(10)
        for one_item in self.child_line_data_list:
            one_widget = ChildLineWidget()
            one_widget.set_data(one_item)
            self.layout_child.addWidget(one_widget)
            self.layout_child.addSpacing(10)
            pass

        one_sc_child_widget = QtWidgets.QWidget()
        one_sc_child_widget.setLayout(self.layout_child)
        self.scroll_area.setWidget(one_sc_child_widget)
        pass
    def res_scroll_widget(self):
        return self.scroll_area.widget()
    pass

MultiChildLineWidget: 放置多个最小数据单元控件ChildLineWidget的容器

class ExampleScrollLineWidget(QtWidgets.QWidget):
    close_signal = QtCore.pyqtSignal(str)
    signal_themeline = QtCore.pyqtSignal(object)
    def __init__(self):
        super().__init__()
        self.caculate_thread = None

        self.init_data()
        self.init_ui()
        self.register_event()
        pass
    def init_data(self):
        self.pre_catalog_list:List[Any] = []
        self.pre_data_list:List[Any] = []

        self.pic_count:int = 0
        self.catalog_code_list: List[str] = ['economic_usanationdebt10y','economic_goldspotprice','economic_medianpehs300','economic_pigprice','economic_usdebt10sub1','economic_chinadebt10sub1']
        self.period_year_map: Dict[str,int] = {
            '日数据':252,
            '周数据':52,
            '月数据':12,
            '季度数据':8,
            '年数据':5
        }
        self.table_count: int = 50
        self.x_count: int = 20
        pass
    def init_ui(self):
        self.caculate_progress = QtWidgets.QProgressBar()
        self.caculate_status_label = QtWidgets.QLabel()
        layout_progress = QtWidgets.QHBoxLayout()
        layout_progress.addWidget(self.caculate_progress)
        layout_progress.addWidget(self.caculate_status_label)

        choise_btn = QtWidgets.QPushButton('选择数据')
        choise_btn.clicked.connect(self.choise_btn_clicked)
        create_pic_btn = QtWidgets.QPushButton('生成图片')
        create_pic_btn.clicked.connect(self.create_pic_btn_clicked)
        tip_label = QtWidgets.QLabel('主题名')
        self.theme_lineedit = QtWidgets.QLineEdit()

        layout_btn = QtWidgets.QHBoxLayout()
        layout_btn.addWidget(choise_btn)
        layout_btn.addWidget(create_pic_btn)

        layout_input = QtWidgets.QHBoxLayout()
        layout_input.addWidget(tip_label)
        layout_input.addWidget(self.theme_lineedit)

        self.muliti_widget = MultiChildLineWidget()

        layout = QtWidgets.QVBoxLayout()
        layout.addLayout(layout_progress)
        layout.addLayout(layout_btn)
        layout.addLayout(layout_input)
        layout.addWidget(self.muliti_widget)
        self.setLayout(layout)
        pass
    def register_event(self):
        self.signal_themeline.connect(self.process_themeline_event)
        pass
    def process_themeline_event(self,data:Dict[str,Any])->None:
        change_type = data['change_type']
        if change_type == 'caculate_result':
            res_data = data['data']
            title_str = self.theme_lineedit.text()
            print(title_str)
            title_str = title_str.strip()
            if len(title_str)<=0:
                title_str = '主题'
            pre_show_data = {
                'title':title_str,
                'line_data_list':res_data
            }
            self.muliti_widget.set_data(pre_show_data)

            self.progress_finished()
            self.caculate_thread = None
        pass

    def setting_data(self,data:Dict[str,Any]):
        self.pre_catalog_list = data['catalog']
        self.pre_data_list = data['data']
        pass

    def choise_btn_clicked(self):
        '''选择数据按钮点击'''
        self.start_caculate_thread('caculate',None)
        pass
    def create_pic_btn_clicked(self):
        '''生成图片按钮点击'''
        self.pic_count += 1
        path, _ = QtWidgets.QFileDialog.getSaveFileName(
            self,
            '选择图片存储路径',
            f"pic_{self.pic_count}",
            'JPG(*.jpg)'
        )
        if not path:
            return

        widget = self.muliti_widget.res_scroll_widget()
        pix = widget.grab()
        pix.save(path)
        pass

    def init_caculate_thread(self):
        self.progress_init()
        # self.start_caculate_thread('init', None)
        pass

    def start_caculate_thread(self, mark_str: str, data: Dict[str, Any]):
        if self.caculate_thread:
            QtWidgets.QMessageBox.information(
                self,
                '提示',
                '有任务正在执行',
                QtWidgets.QMessageBox.Yes
            )
            return
        self.caculate_thread = Thread(
            target=self.running_caculate_thread,
            args=(
                mark_str,
                data,
            )
        )
        self.caculate_thread.start()
        self.progress_busy()
        pass

    def running_caculate_thread(self, mark_str: str, data: Dict[str, Any]):
        if mark_str == 'caculate':
            catalog_list = self.pre_catalog_list
            data_list = self.pre_data_list
            # 开始处理数据
            catalog_map = {}
            for item in catalog_list:
                catalog_code = item['economic_code']
                level = item['level']
                if level == 2:
                    catalog_name = f"{item['economic_name']}【{item['economic_source']}】"
                else:
                    catalog_name = item['economic_name']
                catalog_lineshow = item['economic_lineshow']
                if catalog_lineshow.get('y2') is not None:
                    line_type = 2
                else:
                    line_type = 1
                catalog_period = item['economic_period']
                if self.period_year_map.get(catalog_period) is None:
                    last_one_year_count = 10
                else:
                    last_one_year_count = self.period_year_map[catalog_period]
                catalog_map[catalog_code] = {
                    'catalog_name':catalog_name,
                    'catalog_lineshow':catalog_lineshow,
                    'line_type':line_type,
                    'catalog_period':catalog_period,
                    'last_one_year_count':last_one_year_count
                }

            res_show_list = []
            for item in data_list:
                catalog_code = item['catalog_code']
                catalog_obj = catalog_map[catalog_code]
                last_one_year_count = catalog_obj['last_one_year_count']
                catalog_name = catalog_obj['catalog_name']
                catalog_lineshow = catalog_obj['catalog_lineshow']
                line_type = catalog_obj['line_type']

                column_list = item['column_list']
                pd_column_list = item['pd_column_list']
                item_data_list = item['data_list']

                df = pd.DataFrame(columns=pd_column_list,data=item_data_list)
                df_one_year = df.iloc[-1*last_one_year_count:, :].copy()
                df_table = df.iloc[-1*self.table_count:,:].copy()

                if line_type == 2:
                    # 双Y轴
                    whole_line_data = self.res_twoY_line_data(df,catalog_lineshow)
                    one_year_line_data = self.res_twoY_line_data(df_one_year,catalog_lineshow)
                    pass
                else:
                    # 一条曲线
                    whole_line_data = self.res_one_line_data(df,catalog_lineshow)
                    one_year_line_data = self.res_one_line_data(df_one_year,catalog_lineshow)
                    pass

                whole_line_data['title_str'] = catalog_name
                one_year_line_data['title_str'] = f"{catalog_name}(近期)"

                one_line = {
                    'line_type':line_type,
                    'whole_line':whole_line_data,
                    'one_year_line':one_year_line_data
                }

                # 表格数据
                one_table = {
                    'table_header':column_list,
                    'table_body':df_table.values.tolist()
                }

                res_show_list.append({
                    'line_data':one_line,
                    'table_data':one_table
                })
                pass

            res_json = {}
            res_json['change_type'] = 'caculate_result'
            res_json['data'] = res_show_list

            self.signal_themeline.emit(res_json)
            pass
        pass
    def res_one_line_data(self,df:pd.DataFrame,catalog_lineshow:Dict[str,Any])->Dict[str,Any]:
        whole_line_data = {}

        df['count'] = range(len(df))
        df[catalog_lineshow['y1']] = df[catalog_lineshow['y1']].astype('float')
        x = df['count'].values.tolist()
        y = df[catalog_lineshow['y1']].values.tolist()
        xnames = df[catalog_lineshow['x']].values.tolist()
        xTicks = [(one_i, two_i) for one_i, two_i in zip(x, xnames)]
        xTicks00 = []
        if len(xTicks) > self.x_count:
            dur_count = math.floor(len(xTicks) / self.x_count)
            for i in range(0, len(xTicks), dur_count):
                xTicks00.append(xTicks[i])
        else:
            xTicks00 = xTicks
        y_name = catalog_lineshow['ylabel']

        whole_line_data['x'] = x
        whole_line_data['y'] = y
        whole_line_data['xTick00'] = xTicks00
        whole_line_data['y_name'] = y_name
        whole_line_data['xTick'] = xTicks
        return whole_line_data

    def res_twoY_line_data(self, df: pd.DataFrame, catalog_lineshow: Dict[str, Any]) -> Dict[str, Any]:
        whole_line_data = {}

        df['count'] = range(len(df))
        df[catalog_lineshow['y1']] = df[catalog_lineshow['y1']].astype('float')
        df[catalog_lineshow['y2']] = df[catalog_lineshow['y2']].astype('float')
        x = df['count'].values.tolist()
        y = df[catalog_lineshow['y1']].values.tolist()
        y2 = df[catalog_lineshow['y2']].values.tolist()
        xnames = df[catalog_lineshow['x']].values.tolist()
        xTicks = [(one_i, two_i) for one_i, two_i in zip(x, xnames)]
        xTicks00 = []
        if len(xTicks) > self.x_count:
            dur_count = math.floor(len(xTicks) / self.x_count)
            for i in range(0, len(xTicks), dur_count):
                xTicks00.append(xTicks[i])
        else:
            xTicks00 = xTicks
        y_name = catalog_lineshow['ylabel']
        y_name2 = catalog_lineshow['ylabel2']

        whole_line_data['x'] = x
        whole_line_data['y'] = y
        whole_line_data['y2'] = y2
        whole_line_data['xTick00'] = xTicks00
        whole_line_data['y_name'] = y_name
        whole_line_data['y_name2'] = y_name2
        whole_line_data['xTick'] = xTicks
        return whole_line_data

    def progress_init(self)->None:
        self.caculate_progress.setValue(0)
        self.caculate_status_label.setText('无任务')
    def progress_busy(self)->None:
        self.caculate_progress.setRange(0,0)
        self.caculate_status_label.setText('正在执行')
    def progress_finished(self)->None:
        self.caculate_progress.setRange(0,100)
        self.caculate_progress.setValue(100)
        self.caculate_status_label.setText('执行完毕')
    def closeEvent(self, QCloseEvent):
        # self.close_signal.emit('ThemeLineWidget')
        self.close()
        pass
    pass

使用

例子1

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    t_win = ExampleWidget()
    t_win.showMaximized()
    sys.exit(app.exec_())
    pass

 点击“ScrollArea内容生成图片存储”按钮

 选择要存储的目录,点击保存,在对应目录下就能看到对应图片

例子2 

if __name__ == '__main__':
    QtCore.QCoreApplication.setAttribute(QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
    app = QtWidgets.QApplication(sys.argv)

    import json
    pre_dir = r'D:/temp006/'
    with open(pre_dir+'catalog_list.json','r',encoding='utf-8') as fr:
        catalog_list = json.load(fr)
    with open(pre_dir+'data_list.json','r',encoding='utf-8') as fr:
        data_list = json.load(fr)
    pre_data = {
        'catalog':catalog_list,
        'data':data_list
    }
    t_win = ExampleScrollLineWidget()
    t_win.showMaximized()
    t_win.setting_data(pre_data)
    sys.exit(app.exec_())
    pass

点击“生成图片”按钮

 选择要保存的目录,单击“保存”,在对应的目录下可以看到图片

 

 数据

文中使用到的数据:

链接:https://pan.baidu.com/s/1NumTfqscqDAXLN0V_pHNRw 
提取码:b5oh

 

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值