PyQt : 图表也能这么秀 ,无缝集成 ECharts

1,457 阅读5分钟

一. 前言

在这一篇里面会引入两个知识点 :

  • Apache ECharts : 一款 JavaScript 的可视化图表图 ,功能齐全 ,用法简单
  • PyQt6-WebEngine : PyQt6 的扩展模块,它基于 Qt WebEngine,允许在 PyQt6 应用程序中嵌入和显示现代网页内容

先来看一下 ECharts 支持的图表有多少

image.png

再来看看 WebEngine 的简单原理

WebEngine 是基于 Chromium 开发 ,通过集成 Chromium 到 QT 框架中 ,使 WebEngine 具有了浏览器的功能。

但是要注意 ,由于打入了 Chromium 的相关资源和文件 ,会导致 PyQt 导打包的文件明显变大。

所以在打包过程中 ,需要优化文件大小等工作(这个后续再看)。

二. PyQt 集成 Web-Engine

2.1 基础入门使用

// 安装 Web-WebEngine
pip install PyQt6 PyQt6-WebEngine


// 相关代码 : 
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtCore import QUrl

class WebBrowser(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('PyQt6 百度一下')
        self.setGeometry(100, 100, 1200, 800)

        # 创建 QWebEngineView
        self.browser = QWebEngineView()

        # 加载 URL
        self.browser.setUrl(QUrl("https://www.baidu.com/"))

        # 将浏览器设置为主窗口的小部件
        self.setCentralWidget(self.browser)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = WebBrowser()
    window.show()
    sys.exit(app.exec())

image.png

  • 在这个里面实现了基础的浏览器功能 ,让 PyQT 应用里面可以展示Web端的页面

直接展示 HTML 脚本


html_content = '''
<!DOCTYPE html>
<html>
<head>
    <title>PyQt6 WebEngine Example</title>
</head>
<body>
    <p>在 PyQt6 App 中展示一段 HTML 文本</p>
</body>
</html>
'''

# 加载 HTML 内容
self.browser.setHtml(html_content)

  • 还可以通过传入一段 HTML 脚本 + setHtml 方法 ,展示 HTML 文本
  • 但是这样并不满足我们的复杂需求 ,我们需要一定的 JS 的能力

2.2 运行相关的 Java Script

第一种方式 : 把 JavaScript 代码写在脚本文件中

import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget
from PyQt6.QtWebEngineWidgets import QWebEngineView


class WebBrowser(QMainWindow):
    def __init__(self): 
        super().__init__()
        self.setWindowTitle('PyQt6 JS 代码')
        self.setGeometry(100, 100, 1200, 800)

        # 创建主布局
        self.layout = QVBoxLayout()

        # 创建浏览器
        self.browser = QWebEngineView()

        # HTML 内容
        html_content = '''
        <!DOCTYPE html>
        <html>
        <head>
            <title>JavaScript Example</title>
        </head>
        <body>
            <h1 id="title">这是一段文本!</h1>
            <button onclick="changeTitle()">Change Title</button>
            <script>
                function changeTitle() {
                    document.getElementById("title").innerText = "我点了一下,所以它变了";
                }
            </script>
        </body>
        </html>
        '''

        # 加载 HTML
        self.browser.setHtml(html_content)
        self.setCentralWidget(self.browser)

// 省略 main 方法
  • 把 HTML 相关内容聚合在自己的 HTML 文件里面 ,然后让它的模块内部流转是 一种比较好的隔离方式
  • 但是 ,在 PyQT 上面我们无法进行完全的隔离 ,因为数据要进行流转 ,功能要回调 ,这样就要进行耦合交互

方式二 : Python 和 Web 交互进行耦合

import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget
from PyQt6.QtWebEngineWidgets import QWebEngineView


class WebBrowser(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('JavaScript 脚本的使用')
        self.setGeometry(100, 100, 1200, 800)

        # 创建主布局
        self.layout = QVBoxLayout()

        # 创建浏览器
        self.browser = QWebEngineView()

        # HTML 内容
        html_content = '''
        <!DOCTYPE html>
        <html>
        <head>
            <title>JavaScript Example</title>
        </head>
        <body>
            <h1 id="title">Hello, World!</h1>
        </body>
        </html>
        '''

        # 加载 HTML
        self.browser.setHtml(html_content)

        # 添加按钮用于执行 JavaScript
        self.button = QPushButton('点击修改')
        self.button.clicked.connect(self.run_js)

        # 布局 (此处添加了按钮和展示窗)
        self.layout.addWidget(self.browser)
        self.layout.addWidget(self.button)

        # 创建中央小部件并设置布局
        container = QWidget()
        container.setLayout(self.layout)
        self.setCentralWidget(container)

    def run_js(self):
        # 运行 JavaScript 并获取结果
        # 前面是要执行的 JS 脚本 ,后面是回调的方法
        self.browser.page().runJavaScript(
            'document.getElementById("title").innerText="点了一下最下面的按钮"', self.js_callback)

    def js_callback(self, result):
        print(f"JavaScript Result: {result}")

// 省略 main 方法

在这个代码里面 ,就实现了 PyQT 的工具组件和 JS / HTML 的交互关系

2.3 加载 HTML 文件

  • 不过 ,直接把 HTML 内容写在代码里面是很不合理的 ,所以可以通过引入 HTML 文件进行更轻量化的处理

# 加载本地 HTML 文件
current_dir = os.path.dirname(os.path.abspath(__file__))
local_file_path = os.path.join(current_dir, 'web.html')
local_url = QUrl.fromLocalFile(local_file_path)
self.browser.setUrl(local_url)

# 将浏览器设置为主窗口的小部件
self.setCentralWidget(self.browser)
  • 这种方式也是比较理想的 HTML 集成方式

三. 集成 EChats

上面学习完了 WebEngine , 剩下就是 集成 Echats 了, 先来一个简易版的 :

基础简易版 :

image.png

代码就不贴了 ,我这里直接使用的本地JS文件 :

  • S1 : 下载 echats 的代码 Echats.main.js
  • S2 : 准备好 HTML 文件
  • S3 : 通过注入 HTML 的方式进行注入

从外部导入数据

image.png

❓ 为了让 Data 数据的导入更加方便 ,所以数据源要在 Python 代码中生成 ,并且导入到 HTML 文件中。

  • 由于 Echats 的良好支持 ,可以通过 JSON 文件进行数据的传递
  • 这里从一个 data.json 文件中读取到数据格式 ,通过 runJavaScript 运行 JS 脚本
  • 以下是完整代码 👇👇👇

import sys
import os
import json
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtCore import QUrl


class WebBrowser(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('PyQt6 WebEngine JavaScript Example')
        self.setGeometry(100, 100, 1200, 800)

        # 创建主布局
        self.layout = QVBoxLayout()

        # 创建浏览器
        self.browser = QWebEngineView()

        # 获取当前目录的绝对路径
        current_dir = os.path.dirname(os.path.abspath(__file__))
        local_file_path = os.path.join(current_dir, 'no_data.html')
        url = QUrl.fromLocalFile(local_file_path)
        self.browser.setUrl(url)

        # 添加按钮用于执行 JavaScript
        self.button = QPushButton('点击修改')
        self.button.clicked.connect(self.run_js)

        # 布局
        self.layout.addWidget(self.browser)
        self.layout.addWidget(self.button)

        # 创建中央小部件并设置布局
        container = QWidget()
        container.setLayout(self.layout)
        self.setCentralWidget(container)

    # 此处为了方便展示 ,把数据先存到了data.json 文件中 
    # 在复杂的场景中 ,可以通过更多其他的方式生成 json 数据
    def run_js(self):
        # 在 EChartsWindow 类中加载完 HTML 后调用
        data_str = self.load_json_data('data.json')
        print(f"JavaScript Result: {data_str}")
        js_code = f'initChart({data_str});'
        self.browser.page().runJavaScript(js_code)
    # 回调函数
    def js_callback(self, result):
        print(f"JavaScript Result: {result}")
    # 加载本地的 JSON 文件
    def load_json_data(self, filename):
        current_directory = os.path.dirname(os.path.abspath(__file__))
        json_file_path = os.path.join(current_directory, filename)
        with open(json_file_path, 'r', encoding='utf-8') as json_file:
            data = json.load(json_file)
        return data


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = WebBrowser()
    window.show()
    sys.exit(app.exec())



data.json + html

{
    "title": {
        "text": "ECharts 入门示例"
    },
    "tooltip": {},
    "legend": {
        "data": ["销量"]
    },
    "xAxis": {
        "data": ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"
        ]
    },
    "yAxis": {},
    "series": [
        {
        "name": "销量",
        "type": "bar",
        "data": [
                5,
                20,
                36,
                10,
                10,
                20
            ]
        }
    ]
}


<!DOCTYPE html>
<html style="height: 100%">

<head>
    <meta charset="utf-8">
    <title>ECharts Example</title>
    <!-- 引入 ECharts -->
    <script src="echarts.min.js"></script>
    <script type="text/javascript">
        if (typeof echarts === 'undefined') {
            alert('ECharts 加载失败');
        }
    </script>
</head>

<body style="height: 100%; margin: 0">
    <div id="main" style="height: 100%"></div>

    <script type="text/javascript">
        function initChart(data) {
            // 基于准备好的 DOM,初始化 echarts 实例
            myChart = echarts.init(document.getElementById('main'));
            myChart.setOption(data);
        }
    </script>
</body>

</html>


  • 这里点击后 ,数据就自然生成了。👇👇👇
  • 复杂的操作就不展示了 ,能到这一步后面都唰唰唰的上

image.png

总结

使用感受 :

  • 论美观 ,JS 库还是要比 Python 的库强太多了
  • 上手难度不高 ,花1-2个小时就可以创建复杂的图表
  • 性能还不错 ,展示的很快 ,用法也很灵活

🎉🎉🎉❤️❤️❤️ 祝大家国庆节快乐 !!!

最后的最后 ❤️❤️❤️👇👇👇