Bài học này Tui sẽ nâng cấp bài 36, đó là dữ liệu thay vì hardcode thì Tui đọc tự động từ Excel:
- Tự lấy tick trong cột file Excel (Hà Nội, Huế, TP.HCM, Cần Thơ, An Giang)
- Tự lấy Legend cho Bar Item (Quarter 1, Quarter 2, Quarter 3, Quarter 4)
- Màu các Bar Item được tạo ngẫu nhiên theo 3 thông số R, G, B
- Tự động vẽ Chart mà không lệ thuộc vào tiêu đề Cột hay tiều đề dòng của dữ liệu trong File Excel
- Các chức năng còn lại được kế thừa từ bài 36
Ta tiến hành từng bước như sau:
Bước 0: Cài thư viện “xlrd“
pip install xlrd
Bước 1: Tạo dự án tên “LearnPyQtBarGraphPart7” có cấu trúc như dưới đây:
- “dataset-revenue.xlsx” là file Excel mẫu của dự án
- “MainWindow.ui” là giao diện thiết kế tương tác người dùng bằng Qt Designer
- “MainWindow.py” là Generate Python code cho giao diện “MainWindow.ui”
- “MainWindowEx.py” là file mã lệnh kế thừa từ Generate Python Code để xử lý: Nạp giao diện, hiển thị chart, gán sự kiện và không bị lệ thuộc vào giao diện bị thay đổi sau này khi Generate lại code
- “MyApp.py” là file mã lệnh thực thi chương trình.
Bước 2: Thiết kế giao diện “MainWindow.ui” và đặt tên cho Widget/layout như hình dưới đây:
Bạn lần lượt kéo thả các Widget vào giao diện như trên, lưu ý việc lựa chọn các Layout cho phù hợp.
Bạn lưu ý trong bài này Tui bổ sung nhóm QHBoxLayout ở bên trên để chọn Dataset là file Excel.
Bước 3: Generate Python Code cho “MainWindow.ui”, lúc này mã lệnh “MainWindow.py” tự động được tạo ra:
# Form implementation generated from reading ui file 'MainWindow.ui'
#
# Created by: PyQt6 UI code generator 6.4.2
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt6 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(647, 518)
self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.label = QtWidgets.QLabel(parent=self.centralwidget)
self.label.setObjectName("label")
self.horizontalLayout_2.addWidget(self.label)
self.lineEditDataset = QtWidgets.QLineEdit(parent=self.centralwidget)
self.lineEditDataset.setObjectName("lineEditDataset")
self.horizontalLayout_2.addWidget(self.lineEditDataset)
self.pushButtonPickDataset = QtWidgets.QPushButton(parent=self.centralwidget)
self.pushButtonPickDataset.setObjectName("pushButtonPickDataset")
self.horizontalLayout_2.addWidget(self.pushButtonPickDataset)
self.verticalLayout_2.addLayout(self.horizontalLayout_2)
self.layoutGraph = QtWidgets.QVBoxLayout()
self.layoutGraph.setSizeConstraint(QtWidgets.QLayout.SizeConstraint.SetDefaultConstraint)
self.layoutGraph.setObjectName("layoutGraph")
self.verticalLayout_2.addLayout(self.layoutGraph)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.chkBackgroundGrid = QtWidgets.QCheckBox(parent=self.centralwidget)
self.chkBackgroundGrid.setChecked(True)
self.chkBackgroundGrid.setObjectName("chkBackgroundGrid")
self.horizontalLayout.addWidget(self.chkBackgroundGrid)
self.chkLegend = QtWidgets.QCheckBox(parent=self.centralwidget)
self.chkLegend.setChecked(True)
self.chkLegend.setObjectName("chkLegend")
self.horizontalLayout.addWidget(self.chkLegend)
self.pushButtonChangBackground = QtWidgets.QPushButton(parent=self.centralwidget)
self.pushButtonChangBackground.setObjectName("pushButtonChangBackground")
self.horizontalLayout.addWidget(self.pushButtonChangBackground)
self.cboBarItem = QtWidgets.QComboBox(parent=self.centralwidget)
self.cboBarItem.setObjectName("cboBarItem")
self.horizontalLayout.addWidget(self.cboBarItem)
self.pushButtonChangeBarColor = QtWidgets.QPushButton(parent=self.centralwidget)
self.pushButtonChangeBarColor.setObjectName("pushButtonChangeBarColor")
self.horizontalLayout.addWidget(self.pushButtonChangeBarColor)
self.pushButtonClose = QtWidgets.QPushButton(parent=self.centralwidget)
self.pushButtonClose.setObjectName("pushButtonClose")
self.horizontalLayout.addWidget(self.pushButtonClose)
self.verticalLayout_2.addLayout(self.horizontalLayout)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 647, 26))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.pushButtonClose.clicked.connect(MainWindow.close) # type: ignore
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Trần Duy Thanh - BarGraph"))
self.label.setText(_translate("MainWindow", "Choose Dataset:"))
self.pushButtonPickDataset.setText(_translate("MainWindow", "Pick Dataset"))
self.chkBackgroundGrid.setText(_translate("MainWindow", "Background Grid"))
self.chkLegend.setText(_translate("MainWindow", "Legend"))
self.pushButtonChangBackground.setText(_translate("MainWindow", "Chart Background"))
self.pushButtonChangeBarColor.setText(_translate("MainWindow", "Bar Color"))
self.pushButtonClose.setText(_translate("MainWindow", "Close"))
Bước 4: Viết mã lệnh cho “MainWindowEx.py”
Khai báo lớp MainWindowEx kế thừa từ Generate Python code ở bước 3, dưới đây là hàm override setupUi, Tui chỉnh sửa chút xíu so với bài học trước, đó là trong hàm này nó sẽ gọi 2 hạm setupBarGraph() để cấu hình Graph trống ban đầu và setupSignal() để xủ lý sự kiến:
from PyQt6.QtCore import Qt, QPointF
from PyQt6.QtWidgets import QColorDialog, QFileDialog
from MainWindow import Ui_MainWindow
import numpy as np
import pyqtgraph as pg
import pandas as pd
import random as nd
class MainWindowEx(Ui_MainWindow):
def __init__(self):
super().__init__()
def setupUi(self, MainWindow):
super().setupUi(MainWindow)
self.MainWindow=MainWindow
self.setupBarGraph()
self.setupSignal()
Hàm setupBarGraph() được viết theo mã lệnh như dưới đây:
def setupBarGraph(self):
self.graphWidget = pg.PlotWidget()
self.graphWidget.setTitle("Revenue Report", color="r", size="15pt", bold=True, italic=True)
self.graphWidget.setBackground('w')
labelStyle = {"color": "green", "font-size": "18px"}
labelBrandStyle = {"color": "purple", "font-size": "18px"}
self.graphWidget.setLabel("left","Revenue (VNĐ)",**labelStyle)
self.graphWidget.setLabel("bottom","Cities",**labelStyle)
self.graphWidget.setLabel("right", "tranduythanh.com", **labelBrandStyle)
self.graphWidget.showGrid(x=True, y=True)
self.width = 0.2
self.bargraphItems = []
self.legend = self.graphWidget.addLegend()
self.layoutGraph.addWidget(self.graphWidget)
Hàm setupBarGraph() ở trên thiết kế Graph trống như hình dưới đây (các mã lệnh Tui đã giải thích chi tiết ở các bài học trước, nếu chưa hiểu thì các bạn xem lại):
Hàm setupSignal() để gán các Signal để triệu gọi các hàm, slot:
def setupSignal(self):
self.pushButtonPickDataset.clicked.connect(self.processPickDataset)
self.chkBackgroundGrid.stateChanged.connect(self.processChangeGrid)
self.chkLegend.stateChanged.connect(self.processChangeLegend)
self.pushButtonChangBackground.clicked.connect(self.processChangeChartBackground)
self.pushButtonChangeBarColor.clicked.connect(self.processChangeBarColor)
Hàm setupSignal() Tui có bổ sung slot “processPickDataset”, slot này sẽ cung cấp chức năng lựa chọn file Excel cho người dùng và vẽ lên Graph.
def processPickDataset(self):
filters = "Dataset (*.xlsx);;All files(*)"
filename, selected_filter = QFileDialog.getOpenFileName(
self.MainWindow,
filter=filters,
)
self.lineEditDataset.setText(filename)
self.drawChart(filename)
Hàm trên, Tui dùng QFileDialog cho người dùng chọn File được filter .xlsx mặc định.
Chương trình sẽ hiển thị tên file và đường dẫn lên trên QLineEdit
Và gọi hàm drawChart() để vẽ các BarGraph Item.
def drawChart(self,datasetPath):
#read Excel file into DataFrame by Pandas
dataframe = pd.read_excel(datasetPath)
#Read columns, we omit first column (corner)
#Hà Nội,Huế,TP.HCM,Cần Thơ,An Giang:
self.xlab = dataframe.columns.values[1:]
#create an array from 1 to number of column
self.positions = np.arange(1, len(self.xlab) + 1)
#remove existing BarGraph Item
#(user can choose file many times)
for barItem in self.bargraphItems:
self.graphWidget.removeItem(barItem)
self.bargraphItems = []
self.cboBarItem.clear()
i = 0
#loop to read row data
for row in range(len(dataframe.values)):
#Get value in the first column for each row(quarter X)
name = dataframe.values[row][0]
#Get an Array data for each row
revenues = dataframe.values[row][1:].tolist()
#setup the position for each graph bar item
x = self.positions + i * self.width
#random color for each bar item
brush = (nd.randint(0, 255), nd.randint(0, 255), nd.randint(0, 255))
#create BarGraphItem:
bargraphItem = pg.BarGraphItem(x=x, height=revenues, width=self.width, brush=brush, name=name)
i = i + 1
#store bargraphItem into bargraphItems
self.bargraphItems.append(bargraphItem)
#add the Bar Graph Item into the Chart
self.graphWidget.addItem(bargraphItem)
#Draw the name of BarItem into QComBoBox
self.cboBarItem.addItem(bargraphItem.name(), bargraphItem)
#setup tick
self.autoTick(self.graphWidget, self.xlab)
#setup label
self.autolabel(self.bargraphItems)
Hàm ẩn hiển thị lưới processChangeGrid():
def processChangeGrid(self,value):
state=Qt.CheckState(value)
if state==Qt.CheckState.Checked:
self.graphWidget.showGrid(x=True, y=True)
else:
self.graphWidget.showGrid(x=False, y=False)
Hàm ẩn hiển thị legend processChangeLegend():
def processChangeLegend(self,value):
state=Qt.CheckState(value)
if state==Qt.CheckState.Checked:
self.legend.show()
else:
self.legend.hide()
Hàm thay đổi màu nền của Chart processChangeChartBackground():
def processChangeChartBackground(self):
dialog = QColorDialog()
if dialog.exec():
color = dialog.currentColor()
self.graphWidget.setBackground(color.name())
del dialog
Hàm thay đổi màu của từng Baritem, lưu ý là người sử dụng sẽ chọn Bar item trong QComboBox trước, sau đó nhấn vào button “bar color” để đổi màu:
def processChangeBarColor(self):
dialog = QColorDialog()
if dialog.exec():
color = dialog.currentColor()
bargraphItem=self.bargraphItems[self.cboBarItem.currentIndex()]
bargraphItem.opts["brush"]=color.name()
bargraphItem._updateColors(bargraphItem.opts)
del dialog
Hàm autoTick() sẽ thiết lập label cho các thành phố ở bottom Cities:
def autoTick(self,graphWidget, xlab):
ticks = []
for i, item in enumerate(xlab):
ticks.append((i +1+ 2*self.width, item))
ticks = [ticks]
ax = graphWidget.getAxis('bottom')
ax.setTicks(ticks)
Hàm autolabel() sẽ thiết lập label cho các Bar Item:
def autolabel(self,barItems):
# attach some text labels
for barItem in barItems:
xs, heights = barItem.getData()
for i in range(len(heights)):
height = heights[i]
x = xs[i]
clr = barItem.opts["brush"]
text = pg.TextItem(str(height), color=clr)
text.setParentItem(barItem)
text.setX(x)
text.setY(height)
text.setAnchor((QPointF(0.5, 0.75)))
Label cho từng Bar item giúp người sử dụng dễ dàng quan sát và so sánh cũng như đánh giá dữ liệu.
Dưới đây là coding đầy đủ của MainWindowEx.py:
from PyQt6.QtCore import Qt, QPointF
from PyQt6.QtWidgets import QColorDialog, QFileDialog
from MainWindow import Ui_MainWindow
import numpy as np
import pyqtgraph as pg
import pandas as pd
import random as nd
class MainWindowEx(Ui_MainWindow):
def __init__(self):
super().__init__()
def setupUi(self, MainWindow):
super().setupUi(MainWindow)
self.MainWindow=MainWindow
self.setupBarGraph()
self.setupSignal()
def setupBarGraph(self):
self.graphWidget = pg.PlotWidget()
self.graphWidget.setTitle("Revenue Report", color="r", size="15pt", bold=True, italic=True)
self.graphWidget.setBackground('w')
labelStyle = {"color": "green", "font-size": "18px"}
labelBrandStyle = {"color": "purple", "font-size": "18px"}
self.graphWidget.setLabel("left","Revenue (VNĐ)",**labelStyle)
self.graphWidget.setLabel("bottom","Cities",**labelStyle)
self.graphWidget.setLabel("right", "tranduythanh.com", **labelBrandStyle)
self.graphWidget.showGrid(x=True, y=True)
self.width = 0.2
self.bargraphItems = []
self.legend = self.graphWidget.addLegend()
self.layoutGraph.addWidget(self.graphWidget)
def setupSignal(self):
self.pushButtonPickDataset.clicked.connect(self.processPickDataset)
self.chkBackgroundGrid.stateChanged.connect(self.processChangeGrid)
self.chkLegend.stateChanged.connect(self.processChangeLegend)
self.pushButtonChangBackground.clicked.connect(self.processChangeChartBackground)
self.pushButtonChangeBarColor.clicked.connect(self.processChangeBarColor)
def processPickDataset(self):
filters = "Dataset (*.xlsx);;All files(*)"
filename, selected_filter = QFileDialog.getOpenFileName(
self.MainWindow,
filter=filters,
)
self.lineEditDataset.setText(filename)
self.drawChart(filename)
def drawChart(self,datasetPath):
#read Excel file into DataFrame by Pandas
dataframe = pd.read_excel(datasetPath)
#Read columns, we omit first column (corner)
#Hà Nội,Huế,TP.HCM,Cần Thơ,An Giang:
self.xlab = dataframe.columns.values[1:]
#create an array from 1 to number of column
self.positions = np.arange(1, len(self.xlab) + 1)
#remove existing BarGraph Item
#(user can choose file many times)
for barItem in self.bargraphItems:
self.graphWidget.removeItem(barItem)
self.bargraphItems = []
self.cboBarItem.clear()
i = 0
#loop to read row data
for row in range(len(dataframe.values)):
#Get value in the first column for each row(quarter X)
name = dataframe.values[row][0]
#Get an Array data for each row
revenues = dataframe.values[row][1:].tolist()
#setup the position for each graph bar item
x = self.positions + i * self.width
#random color for each bar item
brush = (nd.randint(0, 255), nd.randint(0, 255), nd.randint(0, 255))
#create BarGraphItem:
bargraphItem = pg.BarGraphItem(x=x, height=revenues, width=self.width, brush=brush, name=name)
i = i + 1
#store bargraphItem into bargraphItems
self.bargraphItems.append(bargraphItem)
#add the Bar Graph Item into the Chart
self.graphWidget.addItem(bargraphItem)
#Draw the name of BarItem into QComBoBox
self.cboBarItem.addItem(bargraphItem.name(), bargraphItem)
#setup tick
self.autoTick(self.graphWidget, self.xlab)
#setup label
self.autolabel(self.bargraphItems)
def processChangeGrid(self,value):
state=Qt.CheckState(value)
if state==Qt.CheckState.Checked:
self.graphWidget.showGrid(x=True, y=True)
else:
self.graphWidget.showGrid(x=False, y=False)
def processChangeLegend(self,value):
state=Qt.CheckState(value)
if state==Qt.CheckState.Checked:
self.legend.show()
else:
self.legend.hide()
def processChangeChartBackground(self):
dialog = QColorDialog()
if dialog.exec():
color = dialog.currentColor()
self.graphWidget.setBackground(color.name())
del dialog
def processChangeBarColor(self):
dialog = QColorDialog()
if dialog.exec():
color = dialog.currentColor()
bargraphItem=self.bargraphItems[self.cboBarItem.currentIndex()]
bargraphItem.opts["brush"]=color.name()
bargraphItem._updateColors(bargraphItem.opts)
del dialog
def autoTick(self,graphWidget, xlab):
ticks = []
for i, item in enumerate(xlab):
ticks.append((i +1+ 2*self.width, item))
ticks = [ticks]
ax = graphWidget.getAxis('bottom')
ax.setTicks(ticks)
def autolabel(self,barItems):
# attach some text labels
for barItem in barItems:
xs, heights = barItem.getData()
for i in range(len(heights)):
height = heights[i]
x = xs[i]
clr = barItem.opts["brush"]
text = pg.TextItem(str(height), color=clr)
text.setParentItem(barItem)
text.setX(x)
text.setY(height)
text.setAnchor((QPointF(0.5, 0.75)))
def show(self):
self.MainWindow.show()
Bước 5: Viết mã lệnh “MyApp.py” để thực thi chương trình:
from PyQt6.QtWidgets import QApplication, QMainWindow
from MainWindowEx import MainWindowEx
app=QApplication([])
myWindow=MainWindowEx()
myWindow.setupUi(QMainWindow())
myWindow.show()
app.exec()
Thực thi MyApp.py ta có kết quả, Chạy phần mềm lên:
Ta nhấn vào nút “Pick Dataset” rồi trỏ tới file Excel data mẫu:
Chọn File Excel rồi nhấn Open, Sau khi nhấn Open ta có kết quả:
Chương trình sẽ tự động nạp tên vào QComBoBox như bài trước để ta có thể chọn và đổi màu cho từng bar item:
Ví dụ như ta chọn “Quarter 3” Bar Item rồi chọn Bar Color:
Ta thử chọn màu đỏ rồi bấm OK:
Màu Bar Item cũng đổi như ta mong muốn, tương tự như bài trước.
Như vậy tới đây Tui đã trình bày xong cách lập trình xử lý trực quan hóa dữ liệu với Multiple BarGraph đọc dữ liệu từ EXCEL. Ôn tập lại cách khai báo các mảng dữ liệu tương ứng với các trục, Ôn tập cách gọi các hàm liên quan tới BarGraphItem để hiển thị Chart. Cũng như ôn tập lại được toàn bộ kiến thức liên quan tới PlotWidget.
Ôn tập cách tương tác Chart thông qua các QCheckBox và QPushButton để tương tác lưới của chart, tương tác legend, đổi màu chart và màu bar.
Ôn tập cách lập trình để Auto Tick, và Auto Label cho Bar. Nó có ý nghĩa quan trọng để giúp Chart được rõ nghĩa, đọc dễ hiểu hơn.
Soure code của bài này các bạn tải ở đây:
https://www.mediafire.com/file/89e33vfnoiob6dl/LearnPyQtBarGraphPart7.rar/file
Bài học sau Tui sẽ hướng dẫn các bạn cách kế thừa BarGraphItem để lắng nghe và xử lý sự kiện click chuột ngay trên từng Baritem
Chúc các bạn thành công
One thought on “Bài 37: Trực quan hóa dữ liệu – PyQtGraph-Excel Data-PyQt6 – Part 7”