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

例子2

- 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
- 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 += ' '+y_str2
- y_str = str(self.y_datas[index])
- y_str_html += ' ' + y_str
- html_str = '<p style="color:black;font-size:18px;font-weight:bold;"> ' + x_str +' '+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 += ' '+y_str2
- y_str = str(self.y_datas[index])
- y_str_html += ' ' + y_str
- html_str = '<p style="color:black;font-size:18px;font-weight:bold;"> ' + x_str +' '+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
- if __name__ == '__main__':
- app = QtWidgets.QApplication(sys.argv)
- t_win = ExampleWidget()
- t_win.showMaximized()
- sys.exit(app.exec_())
- pass

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

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


- 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