Bài 38: Trực quan hóa dữ liệu – PyQtGraph-Custom BarGraphItem-PyQt6 – Part 8

Đây là phần cuối cùng về PyQtGraph trong chuỗi bài hướng dẫn, nó vẫn còn nhiều loại chart khác nhau rất đẹp và phong phú, các bạn tự tìm hiểu thêm. Đặc biệt PyQtGraph hỗ trợ cả 3D tương tác rất đẹp và chuyên nghiệp, nhưng vì thời gian có hạn nên Tui không thể giới thiệu tiếp, mà các bài học khác Tui sẽ chuyển qua Lập trình đa tiến trình để cập nhật giao diện thời gian thực được mượt nhất.

Bài học này ta sẽ làm Custom BarGraphItem bằng cách tạo ra một lớp kế thừa từ BarGraphItem. Mục đích của việc này là cung cấp khả năng tương tự sự kiện trực tiếp trên từng BarItem để hỗ trợ cho việc thống kê cũng như xem chi tiết được tốt nhất.

Bài này xử lý y chang như bài 37, nó chỉ khác ở chỗ ta làm CustomBarGraphItem và xử lý sự kiện cho các Bar item này, khi nhấn vào Bar Item nó sẽ tô nền vàng, khi nhấn qua bar item khác thì nó được phục hồi lại màu cũ.

Ta làm theo các bước dưới đây.

Bước 1: Tạo dự án tên “LearnPyQtBarGraphPart8” có cấu trúc như dưới đây:

  • dataset-revenue.xlsx” là file Excel mẫu của dự án
  • “CustomBarGraphItem.py” là lớp kế thừa từ BarGraphItem để làm Custom Bar Item, xử lý người dùng tương tác trực tiếp trên Bar Item
  • “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 - Custom 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 “CustomBarGraphItem.py”

import pyqtgraph as pg

previousBarGraph=None
previousBarGraphBrush=None
class CustomBarGraphItem(pg.BarGraphItem):
    def mouseClickEvent(self, event):
        global previousBarGraph
        global  previousBarGraphBrush
        if previousBarGraph!=None:
            previousBarGraph.opts["brush"]=previousBarGraphBrush
            previousBarGraph._updateColors(previousBarGraph.opts)
            previousBarGraph.update()
        previousBarGraph=self
        previousBarGraphBrush=self.opts["brush"]
        self.opts["brush"]='y'
        self._updateColors(self.opts)
        self.update()

Lớp CustomBarGraphItem kế thừa từ BarGraphItem.

Lớp này khai báo 2 global veriable:

  • previousBarGraph để lưu lại BarItem nào được click trước đó
  • previousBarGraphBrush để lưu lại màu của BarItem được click trước đó

2 biến này dùng để phục hồi lại trạng thái ban đầu khi người dùng chọn qua Bar item khác.

Và ta override phương thức mouseClickEvent, sự kiện này sẽ kiểm tra BarItem nào đang được click để đổi màu, ta dùng hàm _updateColors() để đổi màu

Bước 5: Viết mã lệnh cho “MainWindowEx.py”

Lưu ý lớp này mã lệnh y chang như bài 37, Tui chỉ thay thế chỗ sử dụng CustomBarrGraphItem mà thôi, vì vậy Tui không giải thích chi tiết từng thành phần nữa, mà Tui gửi đầy đủ coding ở đây, cuối coding Tui giải thích sơ lược cách dùng CustomBarGraphItem:

from PyQt6.QtCore import Qt, QPointF
from PyQt6.QtWidgets import QColorDialog, QFileDialog

from CustomBarGraphItem import CustomBarGraphItem
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 = CustomBarGraphItem(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()

Chúng ta quan sát dòng lệnh 83 thôi, đây là chỗ thay thế pg.BarGraphItem bằng:

bargraphItem = CustomBarGraphItem(x=x, height=revenues, width=self.width, brush=brush, name=name).

Bước 6: 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:

Khi nhấn BarItem nào thì nó tự động đổi qua nền vàng, bấm qua BarItem khác thì nó phục hồi lại màu như cũ.

Như vậy chúng ta đã làm xong CustomGraphBarItem, các bạn đã biết cách xử lý sự kiện riêng cho từng bar item, có thể áp dụng vào nhiều mục đích khác nhau.

Soure code của bài này các bạn tải ở đây:

https://www.mediafire.com/file/kdvzf1melwq37ty/LearnPyQtBarGraphPart8.rar/file

Bài học sau Tui sẽ hướng dẫn các bạn cách lập trình Đa tiến trình để giúp cập nhật giao diện thời gian thực được mượt mà.

Chúc các bạn thành công

Bài 37: Trực quan hóa dữ liệu – PyQtGraph-Excel Data-PyQt6 – Part 7

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

Bài 36: Trực quan hóa dữ liệu – PyQtGraph-Multiple BarGraph-PyQt6 – Part 6

Bài này chúng ta sẽ trực quan hóa dạng Multiple Bar Graph và nó được mở rộng từ bài trước.

Giả sử chúng ta có doanh số trung bình của công ty Lucy ở các chi nhánh Hà Nội, Huế, TP.HCM, Cần Thơ và An Giang theo 4 quý như sau:

Hà NộiHuếTP.HCMCần ThơAn Giang
Quarter 1100200250190220
Quarter 21207015080160
Quarter 380270180200250
Quarter 472230106210180

Sau khi viết các mã lệnh trực quan hóa thì ta có giao diện như dưới đây:

Sự khác biệt lớn của bài này là ở 2 chỗ:

  • Có nhiều Bar Item ở mỗi nhóm trực quan
  • Khi đổi Bar Color thì người dùng có thể lực chọn từng Bar Item để đổi màu, các Bar Item này được hiển thị trong QComboBox.

Chúng ta thực hiện các bước như sau:

Bước 1: Tạo dự án tên “LearnPyQtBarGraphPart6” có cấu trúc như dưới đây:

  • “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ướ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.label = QtWidgets.QLabel(parent=self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
        self.label.setSizePolicy(sizePolicy)
        self.label.setMinimumSize(QtCore.QSize(0, 25))
        self.label.setMaximumSize(QtCore.QSize(16777215, 20))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.label.setFont(font)
        self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.label.setObjectName("label")
        self.verticalLayout_2.addWidget(self.label)
        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", "Lucy Company"))
        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ết thừa từ Generate Python code ở bước 3, dưới đây là hàm override setupUi để định nghĩa các thành phần cho Bar Chart:

from PyQt6.QtCore import Qt, QPointF
from PyQt6.QtWidgets import QColorDialog

from MainWindow import Ui_MainWindow
import numpy as np
import pyqtgraph as pg

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow

        self.width = 0.2
        self.xlab = ["Hà Nội", "Huế", "TP.HCM", "Cần Thơ", "An Giang"]
        self.titleLegends=["Quarter 1","Quarter 2","Quarter 3","Quarter 4"]
        self.positions = np.array([1, 2, 3, 4, 5])
        self.revenues = [[100, 200, 250, 190, 220],
                         [120, 70, 150, 80, 160],
                         [80, 270, 180, 200, 250],
                         [72, 230, 106, 210, 180]
                         ]
        self.brushes=["r","g","b","c"]
        self.bargraphItems=[]

        self.setupBarGraph()

        self.autoTick(self.graphWidget,self.xlab)

        self.autolabel(self.bargraphItems)

Mã lệnh ở trên Tui định nghĩa các tiêu đề cho 5 thành phố (Hà Nội, Huế, TP.HCM, Cần Thơ, An Giang) và các doanh thu tương ứng theo các quý.

Đồng thời khai báo mảng các màu brushes (r, g, b, c) cho các Bar Item.

Trong setupUi() có gọi các hàm setupBarGraph(), autoTick(), và autoLabel()

Dưới đây là hàm setupBarGraph để vẽ Multiple Bar Graph:

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.legend = self.graphWidget.addLegend()
    self.bargraphItems.clear()
    self.cboBarItem.clear()
    for i in range(len(self.revenues)):
        bargraphItem = pg.BarGraphItem(
            x=self.positions+i*self.width,
            height=self.revenues[i],
            width=self.width,
            brush=self.brushes[i],
            name=self.titleLegends[i])
        self.bargraphItems.append(bargraphItem)
        self.graphWidget.addItem(bargraphItem)
        self.cboBarItem.addItem(bargraphItem.name(),bargraphItem)

    self.layoutGraph.addWidget( self.graphWidget)

    self.chkBackgroundGrid.stateChanged.connect(self.processChangeGrid)
    self.chkLegend.stateChanged.connect(self.processChangeLegend)
    self.pushButtonChangBackground.clicked.connect(self.processChangeChartBackground)
    self.pushButtonChangeBarColor.clicked.connect(self.processChangeBarColor)

Các mã lệnh ở trên đã được giải thích chi tiết ở những bài học trước do đó các bạn cần xem lại. Chỉ có chỗ vòng lặp là Tui dùng để duyệt qua từng phần tử trong ma trận revenues, mỗi lần duyệt sẽ tạo ra một BarGraphItem và tiến hành định dạng cho nó.

Trong vòng lặp ngoài việc vẽ các Bar Item thì nó cũng đưa tên và bar item object vào bên trong QCombox để cho người sử dụng có thể lựa chọn từng Bar item trên giao diện để đổi màu theo mong muốn của họ.

Ở cuối hàm setupUi() cũng gọi các signal như hiển thị/ẩn lưới của chart, ẩn hiện Legend, đổi màu nền chart, đổi bar item color.

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

Ví dụ chọn Quarter 2 trên giao diện:

Sau đó chọn Bar Color button:

Sau khi nhấn OK ta có màu vàng được thiết lập cho Bar Item Quarter 2:

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

from MainWindow import Ui_MainWindow
import numpy as np
import pyqtgraph as pg

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow

        self.width = 0.2
        self.xlab = ["Hà Nội", "Huế", "TP.HCM", "Cần Thơ", "An Giang"]
        self.titleLegends=["Quarter 1","Quarter 2","Quarter 3","Quarter 4"]
        self.positions = np.array([1, 2, 3, 4, 5])
        self.revenues = [[100, 200, 250, 190, 220],
                         [120, 70, 150, 80, 160],
                         [80, 270, 180, 200, 250],
                         [72, 230, 106, 210, 180]
                         ]
        self.brushes=["r","g","b","c"]
        self.bargraphItems=[]

        self.setupBarGraph()

        self.autoTick(self.graphWidget,self.xlab)

        self.autolabel(self.bargraphItems)
    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.legend = self.graphWidget.addLegend()
        self.bargraphItems.clear()
        self.cboBarItem.clear()
        for i in range(len(self.revenues)):
            bargraphItem = pg.BarGraphItem(
                x=self.positions+i*self.width,
                height=self.revenues[i],
                width=self.width,
                brush=self.brushes[i],
                name=self.titleLegends[i])
            self.bargraphItems.append(bargraphItem)
            self.graphWidget.addItem(bargraphItem)
            self.cboBarItem.addItem(bargraphItem.name(),bargraphItem)

        self.layoutGraph.addWidget( self.graphWidget)

        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 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ả:

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. Ô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.

Đặc biệt trong bài này chúng ta biết cách vẽ Multiple Bar Item lên 1 Chart, nạp được bar item vào QComboBox để người dùng có thể tùy ý chọn màu cho từng Bar item. Đây là bài học ứng dụng thực tế rất cao. Các bạn chú ý làm lại nhiều lần để thuần thục về kỹ thuật, cũng như biết cách áp dụng vào các bài multiple chart tương tự

Soure code của bài này các bạn tải ở đây:

https://www.mediafire.com/file/gyv9hgo9ezgro0e/LearnPyQtBarGraphPart6.rar/file

Bài học sau Tui sẽ hướng dẫn các bạn cách nạp dữ liệu từ Excel vào phần mềm Python và vẽ lên Bar chart. Nó cũng là một bài áp dụng trong thức tế, Các bạn chú ý theo dõi

Chúc các bạn thành công

Bài 35: Trực quan hóa dữ liệu – PyQtGraph-BarGraph-PyQt6 – Part 5

Chúng ta đã biết cách sử dụng BarGraph để trực quan hóa dữ liệu ở các bài học trước, Lập trình xử lý sự kiện được trên BarGraph như ẩn hiện Background Grid, ẩn hiện Legend, thay đổi màu nền của Chart, thay đổi màu nền của Bar bằng QColorDialog. Với các bài minh họa trước thì các bạn đã có thể ứng dụng vào việc trực quan hóa dữ liệu dạng Bar ở một số trường hợp trong thực tế.

Trong bài học này Tui bổ sung thêm 2 kỹ thuật: Viết mã lệnh để tự động gán label cho các Bar Item, và viết mã lệnh để tự động tạo các Tick cho trục X của Chart nhằm làm rõ nghĩa cho các cột này thay vì chỉ các con số.

Đặc biệt Tui sẽ lập trình để 2 chức năng này có thể tự động hiểu được các Bar Chart theo dữ liệu khác nhau, có thể hiển thị chính xác theo Bar Item đơn hay nhóm các Bar Item, và các label có khả năng khớp màu với màu của mỗi Bar Item tương ứng.

  • Ở trên ta thấy Trục X thay vì chỉ hiển thị các con số 1, 2, 3, 4 mà nó được hiển thị bằng chuỗi “Quarter 1”, “Quarter 2”, “Quarter 3”, và “Quarter 4”
  • Đồng thời trên mỗi Bar item ta có các giá trị được hiển thị, ta gọi nó là label hoặc notation. Ví dụ ta thấy cột đầu tiên có giá trị 100, cột thứ 2 có giá trị 200, vân vân.

Rõ ràng việc hiển thị các Tick và Label sẽ giúp Chart được rõ nghĩa và dễ quan sát hơn.

Như vậy bài này sẽ có giao diện giống như bài trước, và các mã lệnh của một số chức năng cũng y chang nên Tui sẽ không giải thích lại các hàm này. Mà Tui chỉ nói tới 2 giải thuật về tự động tick và tự động label thôi.

Dưới đây là chi tiết từng bước thực hiện dự án:

Bước 1: Tạo dự án “LearnPyQtBarGraphPart5” trong Pycharm có cấu trúc như dưới đây:

  • “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ướ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.5.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(566, 436)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.label = QtWidgets.QLabel(parent=self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
        self.label.setSizePolicy(sizePolicy)
        self.label.setMinimumSize(QtCore.QSize(0, 25))
        self.label.setMaximumSize(QtCore.QSize(16777215, 20))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.label.setFont(font)
        self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.label.setObjectName("label")
        self.verticalLayout_2.addWidget(self.label)
        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.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, 566, 22))
        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", "Lucy Company"))
        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”

Chú ý rằng các mã lệnh nó y chang như bài 34, còn bài này Ta chỉ bổ sung thêm 2 giải thuật cho Auto Tick và Auto Label. Do đó Tui sẽ gửi code đầy đủ ở đây, và ở bên dưới Tui sẽ giải thích 2 giải thuật này:

from PyQt6.QtCore import Qt, QPointF
from PyQt6.QtWidgets import QColorDialog

from MainWindow import Ui_MainWindow
import numpy as np
import pyqtgraph as pg

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.setupBarGraph()

        self.xlab = ["Quarter 1", "Quarter 2", "Quarter 3", "Quarter 4"]

        self.autoTick(self.graphWidget,self.xlab)

        self.autolabel([self.bargraphItem])

Trong hàm setupUi() ta khai báo và gọi 3 lệnh:

self.xlab = ["Quarter 1", "Quarter 2", "Quarter 3", "Quarter 4"]

Lệnh trên khai báo mảng chuỗi để hiển thị Tick ở bottom của Chart, nó sẽ thay thế cho các giá trị số, việc hiển thị chuỗi như này sẽ giúp cho Chart được rõ nghĩa hơn.

Lệnh:

self.autoTick(self.graphWidget,self.xlab)

Để gán các Tick

def autoTick(self,graphWidget, xlab):
    ticks = []
    for i, item in enumerate(xlab):
        ticks.append((i + 1, item))
    ticks = [ticks]
    ax = graphWidget.getAxis('bottom')
    ax.setTicks(ticks)

Hàm autoTick nhận vào 2 đối số:

-Đối số 1 là graphWidget (đối tượng PlotWidget)

– Đối số 2 là xlab là mảng chuỗi để hiển thị Tick

Vòng lặp mã lệnh bên trong autoTick ta sẽ đưa các chuỗi tick vào mảng ticks.

Lệnh graphWidget.getAxis(‘bottom’) để truy suất trục bottom,

Sau đó ta gọi lệnh setTicks(ticks) lúc này chương trình sẽ gán các Tick lên giao diện

Lệnh self.autolabel([self.bargraphItem]) để gán label cho mỗi 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)))

Hàm autolabel sẽ nhận vào 1 mảng BarGraphItem, Tui thiết kế đối số là mảng để chương trình có thể tự động hiểu được tùy ý số nhóm và số bar item trong mỗi nhóm.

Hàm barItem.getData() sẽ trả về 2 dữ liệu: xs mảng trục bottom, heights mảng độ cao của Bar Item (mảng giá trị mà ta trực quan hóa). Dựa vào 2 dữ liệu này ta dễ dàng phân bổ các vị trí label phù hợp

Ta dùng lệnh: pg.TextItem() để tạo các TextItem tương ứng với từng nhóm Bar Item, và nó được vẽ chính xác vị trí thông qua hàm setX(), setY().

Lệnh setAnchor() giúp neo label theo tọa độ cho từng Bar Item.

Dưới đây là mã lệnh đầy đủ của “MainWindowEx.py“:

from PyQt6.QtCore import Qt, QPointF
from PyQt6.QtWidgets import QColorDialog

from MainWindow import Ui_MainWindow
import numpy as np
import pyqtgraph as pg

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.setupBarGraph()

        self.xlab = ["Quarter 1", "Quarter 2", "Quarter 3", "Quarter 4"]

        self.autoTick(self.graphWidget,self.xlab)

        self.autolabel([self.bargraphItem])

    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","Quarter (time)",**labelStyle)
        self.graphWidget.setLabel("right", "tranduythanh.com", **labelBrandStyle)
        self.graphWidget.showGrid(x=True, y=True)

        width = 0.3

        quarter = np.array([1, 2, 3, 4])
        revenue = [100, 200, 250, 190]

        self.bargraphItem = pg.BarGraphItem(x=quarter, height=revenue, width=width, brush='b', name="ABC Revenue")

        self.legend=self.graphWidget.addLegend()

        self.graphWidget.addItem(self.bargraphItem)
        self.layoutGraph.addWidget( self.graphWidget)

        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 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()
            self.bargraphItem.opts["brush"]=color.name()
            self.bargraphItem._updateColors(self.bargraphItem.opts)
            del dialog
    def autoTick(self,graphWidget, xlab):
        ticks = []
        for i, item in enumerate(xlab):
            ticks.append((i + 1, 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()

Chạy “MyApp.py” ta có kết quả như mong muốn:

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 BarGraph. Ô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. Đặc biệt các bạn biết 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.

Đồng thời bài này Tui hướng dẫn các bạn 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/gypdzk2ajb61fni/LearnPyQtBarGraphPart5.rar/file

Bài học sau Tui sẽ hướng cách vẽ Multiple Bar Item trên cùng một Chart. Đây là tính năng rất quan trọng và phổ biến để trực quan hóa dữ liệu trong việc so sánh kết quả.

Các bạn chú ý theo dõi

Chúc các bạn thành công

Bài 34: Trực quan hóa dữ liệu – PyQtGraph-BarGraph-PyQt6 – Part 4

Bài học này chúng ta tiếp tục làm việc với BarGraph, Tui hướng dẫn các bạn cách bổ sung thiết kế giao diện trong Qt Designer để cung cấp các chức năng tương tác trong quá trình trực quan hóa dữ liệu với BarGraph:

  • Chức năng ẩn và hiện Background Grid
  • Chức năng ẩn và hiện Legend
  • Chức năng đổi màu nền của Chart dùng QColorDialog
  • Chức năng đổi màu nền của Bar dùng QColorDialog

Các chức năng này đều được thiết kế giao diện và cung cấp sự kiện tương tác người dùng. Dựa vào bài này mà các bạn có thể tùy chỉnh tương tác biểu đồ trong các trường hợp thực tế khác nhau.

Ngoài ra các bạn cũng được ôn tập lại các kiến thức liên quan tới QCheckBox, xử lý các sự kiện trên QCheckBox. Cũng như xử lý sự kiện trên QPushButton.

Bạn quan sát giao diện ở trên có 2 QCheckBox, 3 QPushButton được bổ sung vào giao diện để cho người dùng tương tác. Bài này mở rộng từ bài trước

Dưới đây là chi tiết từng bước thực hiện dự án:

Bước 1: Tạo dự án “LearnPyQtBarGraphPart4” trong Pycharm có cấu trúc như dưới đây:

  • “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ướ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.5.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(566, 436)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.label = QtWidgets.QLabel(parent=self.centralwidget)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
        self.label.setSizePolicy(sizePolicy)
        self.label.setMinimumSize(QtCore.QSize(0, 25))
        self.label.setMaximumSize(QtCore.QSize(16777215, 20))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.label.setFont(font)
        self.label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.label.setObjectName("label")
        self.verticalLayout_2.addWidget(self.label)
        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.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, 566, 22))
        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", "Lucy Company"))
        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”

Lớp MainWindowEx kế thừa lớp Ui_MainWindow (lớp được Generate Python code ở bước trước)

Tui định nghĩa 1 constructor __init__() gọi lại constructor ở lớp cha, tạm thời trong bài tập này chưa xử lý gì khác.

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QColorDialog

from MainWindow import Ui_MainWindow
import numpy as np
import pyqtgraph as pg

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.setupBarGraph()

Hàm setupUi() được override để nạp giao diện, lưu lại biến MainWindow để sử dụng cho quá trình xử lý trong tương lai. Đồng thời nó cũng gọi hàm setupBarGraph() để hiển thị Chart, Hàm này ta viết như sau:

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","Quarter (time)",**labelStyle)
    self.graphWidget.setLabel("right", "tranduythanh.com", **labelBrandStyle)
    self.graphWidget.showGrid(x=True, y=True)

    width = 0.3

    quarter = np.array([1, 2, 3, 4])
    revenue = [100, 200, 250, 190]

    self.bargraphItem = pg.BarGraphItem(x=quarter, height=revenue, width=width, brush='b', name="ABC Revenue")

    self.legend=self.graphWidget.addLegend()

    self.graphWidget.addItem(self.bargraphItem)
    self.layoutGraph.addWidget( self.graphWidget)

    self.chkBackgroundGrid.stateChanged.connect(self.processChangeGrid)
    self.chkLegend.stateChanged.connect(self.processChangeLegend)
    self.pushButtonChangBackground.clicked.connect(self.processChangeChartBackground)
    self.pushButtonChangeBarColor.clicked.connect(self.processChangeBarColor)
  • Tất cả các kỹ thuật liên quan tới PlotWidget và cách xử lý BarGraphItem Tui đã trình bày chi tiết và cụ thể ở các bài trước rồi, nên bài này Tui không nhắc lại. Mà chúng ta chỉ quan tâm xử lý Signal liên quan tới các QCheckBox và QPushButton để cung cấp chức năng tương tác Chart cho người dùng.
  • Ta xem Signal statechanged của QCheckBox background grid:
self.chkBackgroundGrid.stateChanged.connect(self.processChangeGrid)

Slot(hàm) chi tiết xử lý khi người dùng Checked vào QCheckBox background grid được khai báo trong hàm 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)

Khi chạy chức năng này thì ta thấy như sau: Nếu người sử dụng Unchecked QCheckBox background Grid này thì các lưới sẽ bị biến mất, còn Checked thì các lưới sẽ được hiển thị trở lại:

  • Tiếp theo Ta xem Signal statechanged của QCheckBox Legend:
self.chkLegend.stateChanged.connect(self.processChangeLegend)

Slot(hàm) chi tiết xử lý khi người dùng Checked vào QCheckBox legend được khai báo trong hàm processChangeLegend():

def processChangeLegend(self,value):
    state=Qt.CheckState(value)
    if state==Qt.CheckState.Checked:
        self.legend.show()
    else:
        self.legend.hide()

Khi chạy chức năng này thì ta thấy như sau: Nếu người sử dụng Unchecked QCheckBox Legend này thì Legend bị biến mất, còn Checked thì Legend sẽ được hiển thị trở lại:

  • Tiếp theo ta qua Signal clicked cho QPushButton thay đổi màu nền của Chart:
self.pushButtonChangBackground.clicked.connect(self.processChangeChartBackground)

Slot(hàm) chi tiết xử lý khi người dùng nhấn vào QPushButton Chart Background được khai báo trong hàm processChangeChartBackground():

def processChangeChartBackground(self):
    dialog = QColorDialog()
    if dialog.exec():
        color = dialog.currentColor()
        self.graphWidget.setBackground(color.name())
        del dialog

Ta thấy, hàm trên Tui dùng QColorDialog() để hiển thị màn hình chọn màu, khi người dùng lựa chọn màu và nhần OK thì màu sẽ được lấy từ hàm currentColor(), sau đó ta truy suất tên của màu này để đổi màu cho Chart thông qua hàm: setBackground(color.name()).

Ví dụ người dùng chọn màu hồng rồi nhấn OK. Lúc này nền màu của Chart sẽ đổi thành màu hồng này:

  • Tiếp theo ta qua Signal clicked cho QPushButton thay đổi màu của Bar:
self.pushButtonChangeBarColor.clicked.connect(self.processChangeBarColor)

Slot(hàm) chi tiết xử lý khi người dùng nhấn vào QPushButton Chart Background được khai báo trong hàm processChangeBarColor():

def processChangeBarColor(self):
    dialog = QColorDialog()
    if dialog.exec():
        color = dialog.currentColor()
        self.bargraphItem.opts["brush"]=color.name()
        self.bargraphItem._updateColors(self.bargraphItem.opts)
        del dialog

Mã lệnh ở trên Tui cũng sử dụng QColorDialog để cho người dùng lựa chọn màu, sau khi lựa chọn màu thì tên màu sẽ được lưu với mảng opts[“brush”] của BarGraphItem.

sau đó nó sẽ được cập nhật màu thông qua hàm:

_updateColors(self.bargraphItem.opts) của BarGraphItem

Ví dụ người dùng chọn màu vàng rồi nhấn OK:

Dưới đây là mã lệnh đầy đủ của “MainWindowEx.py“:

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QColorDialog

from MainWindow import Ui_MainWindow
import numpy as np
import pyqtgraph as pg

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.setupBarGraph()
    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","Quarter (time)",**labelStyle)
        self.graphWidget.setLabel("right", "tranduythanh.com", **labelBrandStyle)
        self.graphWidget.showGrid(x=True, y=True)

        width = 0.3

        quarter = np.array([1, 2, 3, 4])
        revenue = [100, 200, 250, 190]

        self.bargraphItem = pg.BarGraphItem(x=quarter, height=revenue, width=width, brush='b', name="ABC Revenue")

        self.legend=self.graphWidget.addLegend()

        self.graphWidget.addItem(self.bargraphItem)
        self.layoutGraph.addWidget( self.graphWidget)

        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 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()
            self.bargraphItem.opts["brush"]=color.name()
            self.bargraphItem._updateColors(self.bargraphItem.opts)
            del dialog
    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()

Chạy “MyApp.py” ta có kết quả như mong muốn, các biểu đồ cột, các nhãn của các cột, tiêu đề chart, các QCheckBox và QPushButton được hiển thị để người dùng tương tá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 BarGraph. Ô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. Đặc biệt các bạn biết 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.

Soure code của bài này các bạn tải ở đây:

https://www.mediafire.com/file/mrb3yyoajnirgfr/LearnPyQtBarGraphPart4.rar/file

Bài học sau Tui sẽ hướng dẫn các bạn cách lập trình để hiển thị ticks và cách tự động gán notation cho từng Bar, các bạn chú ý theo dõi

Chúc các bạn thành công

Bài 33: Trực quan hóa dữ liệu – PyQtGraph-BarGraph-PyQt6 – Part 3

Trong chuỗi các bài học về BarGraph để trực quan hóa dữ liệu Tui sẽ trình bày 6 bài về các kỹ thuật liên quan tới BarGraph, để tùy từng tình huống hay nhu cầu sử dụng khác nhau mà các bạn có thể áp dụng.

Đối tượng PlotWidget cũng như các kỹ thuật liên quan chúng ta đã học rất chi tiết và đầy đủ ở phần 2, ở phần này Tui không nói lại PlotWidget mà Tui chỉ sử dụng lại PlotWidget để vẽ các BarGraph biểu độ dạng cột, một trong những loại trực quan quá phổ biến.

Mô tả tập dữ liệu cho bài này:

Công ty Lucy có dữ liệu doanh thu trung bình theo quý của năm 2023 như sau:

QuýTrung bình doanh thu
1100
2200
3250
4190

Hãy trực quan hóa dữ liệu bằng biểu đồ cột. Hình dưới đây minh họa kỹ thuật đầu tiên trong chuỗi 6 bài về BarGraphItem này:

Chúng ta lưu ý là toàn bộ các bài liên quan tới trực quan hóa dữ liệu, Chúng ta sẽ sử dụng PlotWidget, các hàm phổ biến liên quan đã được học ở các bài trước đều được tái sử dụng. Còn loại Chart hiển thị như thế nào thì tùy từng trường hợp mà ta sẽ gọi các hàm hiển thị khác nhau.

Ta từng bước thực hiện bài này như sau:

Bước 1: Tạo dự án “LearnPyQtBarGraphPart3” có cấu trúc như hình dưới đây:

  • “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ướ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.5.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(434, 352)
        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout_2.setObjectName("verticalLayout_2")
        self.myLayout = QtWidgets.QVBoxLayout()
        self.myLayout.setObjectName("myLayout")
        self.verticalLayout_2.addLayout(self.myLayout)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 434, 22))
        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)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "Trần Duy Thanh - BarGraphItem"))

Bước 4: Viết mã lệnh cho “MainWindowEx.py”

Lớp MainWindowEx kế thừa lớp Ui_MainWindow (lớp được Generate Python code ở bước trước)

Tui định nghĩa 1 constructor __init__() gọi lại constructor ở lớp cha, tạm thời trong bài tập này chưa xử lý gì khác.

from MainWindow import Ui_MainWindow
import pyqtgraph as pg

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.setupBarGraph()

Hàm setupUi() được override để nạp giao diện, lưu lại biến MainWindow để sử dụng cho quá trình xử lý trong tương lai. Đồng thời nó cũng gọi hàm setupBarGraph() để hiển thị Chart, Hàm này ta viết như sau:

def setupBarGraph(self):
    self.graphWidget = pg.PlotWidget()
    self.graphWidget.setTitle("Lucy Company",
                              color="r",
                              size="15pt",
                              bold=True,
                              italic=True)
    self.graphWidget.setBackground('w')

    labelStyle = {"color": "green", "font-size": "18px"}
    labelBrandStyle = {"color": "pink", "font-size": "18px"}
    self.graphWidget.setLabel("left", "Revenue (VNĐ)", **labelStyle)
    self.graphWidget.setLabel("bottom", "Quarter (time)", **labelStyle)
    self.graphWidget.setLabel("top", "Revenue Report", **labelStyle)
    self.graphWidget.setLabel("right", "tranduythanh.com", **labelBrandStyle)
    self.graphWidget.showGrid(x=True, y=True)

    width = 0.3

    quarter = [1, 2, 3, 4]
    revenue = [100, 200, 250, 190]

    self.bargraphItem = pg.BarGraphItem(x=quarter, height=revenue, width=width, brush='b', name="ABC Revenue")

    self.legend = self.graphWidget.addLegend()

    self.graphWidget.addItem(self.bargraphItem)
    self.myLayout.addWidget(self.graphWidget)

Ta thấy rằng tất cả các phương thức thường sử dụng của PlotWidget trình bày ở bài học trước đều được dùng lại ở đây, nên Tui không có trình bày lại các kiến thức cũ.

Mà các bạn hãy để ý các dòng lệnh mới liên quan tới BarGraphItem thôi:

  • Khai bao và khởi tạo giá trị cho biến width, biến này là độ rộng của cột Bar :
width = 0.3
  • Khai báo mảng lưu 4 quý vào biến quarter:
quarter = [1, 2, 3, 4]
  • Khai báo mảng doanh thu tương ứng với mảng quarter:
revenue = [100, 200, 250, 190]
  • Cuối cùng là hàm vẽ biểu đồ BarGraph bằng cách khai báo đối tượng BarGraphItem:
self.bargraphItem = pg.BarGraphItem(x=quarter, height=revenue, width=width, brush='b', name="ABC Revenue")

Ý nghĩa của BarGraphItem là vẽ các biểu đồ cột, dưới đây là một số thuộc tính/parameter thường dùng của BarGraphItem:

Thuộc tínhÝ nghĩa, Chức năng
xlà đối số lưu mảng các vị trí mà mỗi Cột biểu đồ sẽ được vẽ
heightlà đối số lưu mảng các giá trị (độ cao) của mỗi cột biểu đồ ở vị trí tương ứng trong mảng x
widthLà độ rộng của cột biểu đồ, mà ở trên ta khai báo mặc định là 0.3, ta có thể lựa chọn giá trị tùy thích
brushlà màu của các cột trong biểu đồ
nameLà tên của biểu đồ, nó có ý nghĩa cho xử lý tương tác biểu đồ, legend trên biểu đồ….
optsMảng lưu đầy đủ các thông số cấu hình của Chart, dựa vào đây ta có thể hiệu chỉnh trực tiếp biểu đồ.

Dưới đây là mã lệnh đầy đủ của “MainWindowEx.py“:

from MainWindow import Ui_MainWindow
import pyqtgraph as pg

class MainWindowEx(Ui_MainWindow):
    def __init__(self):
        super().__init__()
    def setupUi(self, MainWindow):
        super().setupUi(MainWindow)
        self.MainWindow=MainWindow
        self.setupBarGraph()
    def setupBarGraph(self):
        self.graphWidget = pg.PlotWidget()
        self.graphWidget.setTitle("Lucy Company",
                                  color="r",
                                  size="15pt",
                                  bold=True,
                                  italic=True)
        self.graphWidget.setBackground('w')

        labelStyle = {"color": "green", "font-size": "18px"}
        labelBrandStyle = {"color": "pink", "font-size": "18px"}
        self.graphWidget.setLabel("left", "Revenue (VNĐ)", **labelStyle)
        self.graphWidget.setLabel("bottom", "Quarter (time)", **labelStyle)
        self.graphWidget.setLabel("top", "Revenue Report", **labelStyle)
        self.graphWidget.setLabel("right", "tranduythanh.com", **labelBrandStyle)
        self.graphWidget.showGrid(x=True, y=True)

        width = 0.3

        quarter = [1, 2, 3, 4]
        revenue = [100, 200, 250, 190]

        self.bargraphItem = pg.BarGraphItem(x=quarter, height=revenue, width=width, brush='b', name="ABC Revenue")

        self.legend = self.graphWidget.addLegend()

        self.graphWidget.addItem(self.bargraphItem)
        self.myLayout.addWidget(self.graphWidget)

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

Chạy “MyApp.py” ta có kết quả như mong muốn, các biểu đồ cột, các nhãn của các cột, tiêu đề chart… được hiển thị:

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 BarGraph. Các bạn đã biết cách khai báo các mảng tương ứng với các trục, biết 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, một trong các đối tượng quan trọng và thường sử dụng trong trực quan hóa dữ liệu.

Soure code của bài này các bạn tải ở đây:

https://www.mediafire.com/file/tpic3u6bkckjilt/LearnPyQtBarGraphPart3.rar/file

Bài học sau Tui sẽ nâng cấp bài học này bằng cách bổ sung thêm các Widget cho người dùng tương tác, chẳng hạn như:

  • Cung cấp chức năng ẩn hiện Background Grid
  • Cung cấp chức năng ẩn hiện Legend
  • Cung cấp chức năng đổi màu nền của Chart
  • Cung cấp chức năng đổi màu nền của Bar

Các bạn chú ý theo dõi

Chúc các bạn thành công