Python桌面应用也能开发ECharts
Python桌面应用也能开发ECharts
很多刚学 Python 的小伙伴,一提到图表,第一反应往往是:
- Web 项目里用前端图表库
- 数据分析里用
matplotlib - 报表导出时做成 Excel
但如果你正在做 Python 桌面应用,其实还有一个很值得打开视野的方向:
直接在桌面程序里嵌入 ECharts。
这件事听起来像“前端玩法”,但放到 Python 桌面应用里其实非常顺手。只要你使用 PyQt6 / PySide6,再配合 QWebEngineView,就能把一个 Chromium 内核的网页视图嵌进窗口里,然后把 ECharts 图表直接显示在桌面应用中。
这意味着什么?
- 你可以继续用 Python 写业务逻辑、数据处理、接口调用
- 你可以继续用 Qt 写桌面窗口、按钮、表单、菜单
- 你还可以把 ECharts 这种前端报表能力直接拿来用
对于正在学习 Python 的同学来说,这是一个非常适合“扩展眼界”的组合。
因为它会让你意识到一件事:
Python 桌面开发,并不等于只能用传统控件画界面。很多前端生态里的能力,也能被桌面应用直接吸收。
这篇文章我就带你从 0 到 1 跑通这个方案,快速上手:
- 什么是
QWebEngineView - 为什么
PyQt6 / PySide6 + ECharts很适合桌面报表 - 如何写一个最小可运行示例
- Python 如何把数据传给 ECharts
- 实际开发里有哪些坑要提前注意
1. 这个方案的核心思路是什么
先把思路讲清楚。
ECharts 本质上是一个运行在浏览器里的 JavaScript 图表库。
而 QWebEngineView 可以理解成 Qt 提供的“内嵌浏览器控件”。你把它放进 PyQt6 或 PySide6 的窗口里,它就能像一个网页容器一样显示 HTML、CSS、JavaScript。
于是整件事就串起来了:
- Python 桌面程序创建窗口
- 窗口中放一个
QWebEngineView QWebEngineView加载一段包含 ECharts 的 HTML- Python 再把数据传给页面里的 JavaScript
- JavaScript 调用 ECharts 渲染图表
这套方式最大的优点,不是“能显示图”,而是:
- 图表能力很强,样式现代
- 柱状图、折线图、饼图、地图、仪表盘都能做
- 前端里成熟的图表配置思路,桌面端也能复用
- Python 和图表展示职责分离,代码结构更清晰
你可以把它理解成:
Python 负责数据,Qt 负责桌面容器,ECharts 负责可视化。
2. 为什么这个方案对 Python 学习者很有价值
很多初学者学桌面开发时,容易只盯着“按钮、输入框、表格”这些传统控件。
但实际项目里,经常会遇到下面这些需求:
- 设备监控面板
- 销售统计看板
- 学习数据趋势图
- 库存变化报表
- 任务执行状态面板
如果只靠传统桌面控件硬画,开发体验和视觉效果都很一般。
这时候 QWebEngineView + ECharts 的优势就出来了:
2.1 学习成本不算高
如果你已经会一点 HTML 和 JavaScript,那上手会非常快。
即使你前端基础不多,也不用害怕,因为在很多场景里,你只需要掌握:
- HTML 容器怎么放一个
div - ECharts 怎么初始化
setOption()怎么配置图表- Python 怎么把 JSON 数据传进去
会这几件事,就已经能做出很像样的桌面报表界面。
2.2 图表表现力远强于传统桌面控件
ECharts 的交互能力非常成熟,比如:
- 鼠标悬停提示
- 缩放
- 图例切换
- 动画过渡
- 多序列对比
- 数据更新重绘
这些放在桌面报表里会非常自然。
2.3 有助于建立“跨技术栈组合”的思维
学习 Python 不能只停留在“只会 Python 自己这一层”。
真正做项目时,往往是多种技术一起配合:
- Python 处理数据
- SQL 存储数据
- Qt 负责界面
- Web 技术负责图表或富文本
当你能把这些东西组合起来时,做项目的视角就会完全不一样。
3. PyQt6 和 PySide6 该怎么选
这两个库本质上都是 Qt for Python 的常用绑定,API 风格非常接近。
如果你只是为了学习和快速做项目,先记住下面这件事就够了:
PyQt6:生态资料很多,但QWebEngineView需要额外安装PyQt6-WebEnginePySide6:官方系绑定,直接安装PySide6通常就能使用 WebEngine 模块
快速安装可以这样做。
如果你想用 PyQt6:
pip install PyQt6 PyQt6-WebEngine
如果你想用 PySide6:
pip install PySide6
初学阶段不用把选择问题想得太复杂。
你完全可以先学一套写法,因为这篇文章下面给你的示例会同时兼容这两种方案。
4. 先看一个最小可运行示例
下面这个示例的目标很明确:
- 创建一个桌面窗口
- 中间放一个
QWebEngineView - 加载一段 HTML
- HTML 里初始化 ECharts
- Python 点击按钮后刷新图表数据
我已经把完整示例整理成单独文件,方便你本地直接运行:
pyqt_qwebengine_echarts_demo.py
完整代码如下:
import json
import random
import sys
USING_PYQT6 = False
try:
from PyQt6.QtCore import QTimer, QUrl
from PyQt6.QtWidgets import (
QApplication,
QHBoxLayout,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)
from PyQt6.QtWebEngineWidgets import QWebEngineView
USING_PYQT6 = True
except ImportError:
from PySide6.QtCore import QTimer, QUrl
from PySide6.QtWidgets import (
QApplication,
QHBoxLayout,
QLabel,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)
from PySide6.QtWebEngineWidgets import QWebEngineView
HTML_TEMPLATE = """<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ECharts Desktop Demo</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<style>
html, body {
margin: 0;
height: 100%;
background: #f5f7fb;
font-family: "Microsoft YaHei", sans-serif;
}
#chart {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="chart"></div>
<script>
const chart = echarts.init(document.getElementById("chart"));
window.renderChart = function (payload) {
const option = {
title: {
text: payload.title,
left: "center"
},
tooltip: {
trigger: "axis"
},
grid: {
left: 48,
right: 24,
top: 70,
bottom: 40
},
xAxis: {
type: "category",
data: payload.categories
},
yAxis: {
type: "value"
},
series: [
{
type: "bar",
data: payload.values,
barWidth: 34,
itemStyle: {
borderRadius: [6, 6, 0, 0],
color: "#3b82f6"
}
}
]
};
chart.setOption(option, true);
};
window.addEventListener("resize", function () {
chart.resize();
});
</script>
</body>
</html>
"""
class MainWindow(QMainWindow):
def __init__(self) -> None:
super().__init__()
self.setWindowTitle("Python 桌面应用中的 ECharts")
self.resize(960, 640)
self.browser = QWebEngineView()
self.status_label = QLabel("准备加载图表...")
self.refresh_button = QPushButton("刷新随机数据")
self.refresh_button.clicked.connect(self.update_chart)
top_bar = QHBoxLayout()
top_bar.addWidget(QLabel("PyQt6 / PySide6 + QWebEngineView + ECharts"))
top_bar.addStretch()
top_bar.addWidget(self.refresh_button)
layout = QVBoxLayout()
layout.addLayout(top_bar)
layout.addWidget(self.browser, stretch=1)
layout.addWidget(self.status_label)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
self.browser.loadFinished.connect(self.on_load_finished)
self.browser.setHtml(HTML_TEMPLATE, QUrl("https://local.chart/"))
def generate_chart_payload(self) -> dict[str, object]:
categories = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
values = [random.randint(40, 180) for _ in categories]
return {
"title": "最近 7 天学习时长统计",
"categories": categories,
"values": values,
}
def on_load_finished(self, ok: bool) -> None:
if not ok:
self.status_label.setText("页面加载失败,请检查 ECharts 资源是否可访问。")
return
self.update_chart()
def update_chart(self) -> None:
payload = self.generate_chart_payload()
script = f"window.renderChart({json.dumps(payload, ensure_ascii=False)});"
self.browser.page().runJavaScript(script)
self.status_label.setText(
f"图表已更新,当前运行环境:{'PyQt6' if USING_PYQT6 else 'PySide6'}"
)
def main() -> None:
app = QApplication(sys.argv)
window = MainWindow()
window.show()
QTimer.singleShot(200, window.update_chart)
sys.exit(app.exec())
if __name__ == "__main__":
main()
5. 这段代码到底做了什么
很多同学第一次看这种写法,会觉得“Python 和网页怎么连起来的?”
别急,我们一层一层拆开看。
5.1 QWebEngineView 就是嵌入窗口的网页容器
下面这句是核心:
self.browser = QWebEngineView()
它创建了一个可以显示网页内容的控件。
然后这句:
self.browser.setHtml(HTML_TEMPLATE, QUrl("https://local.chart/"))
表示把一段 HTML 字符串直接塞进这个网页控件里显示。
也就是说,你并不一定非得先准备一个单独的 .html 文件,完全可以在 Python 里直接拼出页面。
对初学者来说,这是最容易上手的方式。
5.2 HTML 中真正负责画图的是 ECharts
这句是加载 ECharts:
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
这句是图表容器:
<div id="chart"></div>
这句是初始化图表:
const chart = echarts.init(document.getElementById("chart"));
然后我们把渲染逻辑封装成了一个全局函数:
window.renderChart = function (payload) {
chart.setOption(...);
};
这么做的好处是,Python 后面可以直接调用这个函数,把数据传进去。
5.3 Python 用 runJavaScript() 把数据送进页面
这一句是整套方案的关键连接点:
self.browser.page().runJavaScript(script)
这里的 script 本质上是一段要在网页里执行的 JavaScript。
例如:
payload = {
"title": "最近 7 天学习时长统计",
"categories": ["周一", "周二", "周三"],
"values": [80, 120, 95],
}
script = f"window.renderChart({json.dumps(payload, ensure_ascii=False)});"
执行之后,就相当于网页里运行了:
window.renderChart({
title: "最近 7 天学习时长统计",
categories: ["周一", "周二", "周三"],
values: [80, 120, 95]
});
这样图表就刷新出来了。
你可以把这个过程理解成:
Python 把字典转成 JSON,再让浏览器控件执行对应的 JavaScript 函数。
6. 实战里最常见的两种组织方式
学会最小示例之后,真正开发时通常会用下面两种方式。
6.1 方式一:setHtml() 内嵌页面
这就是本文示例采用的方式。
优点:
- 上手快
- 代码集中
- 做原型非常方便
缺点:
- HTML、CSS、JavaScript 都写在 Python 字符串里,项目变大后不太好维护
适合场景:
- 学习
- Demo
- 小工具
- 原型验证
6.2 方式二:加载本地 HTML 文件
项目稍微正式一点,更推荐把前端页面拆出去,比如:
project/
main.py
web/
index.html
echarts.min.js
app.js
style.css
然后在 Python 中这样加载:
from pathlib import Path
from PyQt6.QtCore import QUrl
html_path = Path(__file__).parent / "web" / "index.html"
self.browser.load(QUrl.fromLocalFile(str(html_path)))
这种方式更像一个小型前后端分层项目。
优点很明显:
- HTML / CSS / JS 分离,维护更舒服
- 可以把
echarts.min.js放本地,避免依赖外网 CDN - 图表页面复杂时更容易扩展
如果你准备长期维护一个桌面项目,我更建议你往这个方向走。
7. 如何让 Python 和 ECharts 做更灵活的交互
初学阶段,掌握 runJavaScript() 已经够用了。
但你可能很快会遇到更进一步的需求,比如:
- 点击柱子后把名称回传给 Python
- 桌面按钮切换不同图表
- 定时刷新监控数据
- 列表选中哪一行,图表就高亮哪一项
这时候你可以用两种思路继续升级。
7.1 Python 主动推送数据
这是最简单也最常见的。
比如点击刷新按钮后,重新查询数据库,再次调用:
self.browser.page().runJavaScript(...)
本质上就是让 Python 继续做“主导方”。
7.2 用 QWebChannel 做双向通信
如果你想让 JavaScript 主动调用 Python,就可以引入 QWebChannel。
思路大概是这样:
Python 端:
from PyQt6.QtCore import QObject, pyqtSlot
from PyQt6.QtWebChannel import QWebChannel
class Bridge(QObject):
@pyqtSlot(str)
def on_bar_clicked(self, name: str) -> None:
print("用户点击了:", name)
bridge = Bridge()
channel = QWebChannel()
channel.registerObject("bridge", bridge)
self.browser.page().setWebChannel(channel)
JavaScript 端:
new QWebChannel(qt.webChannelTransport, function (channel) {
window.bridge = channel.objects.bridge;
});
chart.on("click", function (params) {
window.bridge.on_bar_clicked(params.name);
});
这时候 ECharts 图表的点击事件就能直接通知 Python。
如果你以后要做实时监控、交互式看板、带钻取逻辑的桌面报表,这一步会非常有用。
8. 开发中最容易踩的坑
这部分很重要,因为很多人第一次失败,不是思路不会,而是细节没处理好。
8.1 安装了 Qt,但没装 WebEngine
尤其是 PyQt6,很多人装完:
pip install PyQt6
然后发现:
from PyQt6.QtWebEngineWidgets import QWebEngineView
导入失败。
原因通常就是少装了:
pip install PyQt6-WebEngine
8.2 页面还没加载完,就急着执行 JavaScript
如果你在页面尚未准备完成时就调用:
runJavaScript(...)
很可能没有任何效果。
所以更稳妥的做法是监听:
self.browser.loadFinished.connect(self.on_load_finished)
等页面加载成功后再推送数据。
8.3 直接手写 JS 字符串,结果引号把自己绕晕了
很多初学者会这样拼:
script = "window.renderChart({'title': '测试'})"
这样很容易因为引号、转义、中文字符导致 JavaScript 执行失败。
更稳妥的方式永远是:
json.dumps(payload, ensure_ascii=False)
把 Python 数据先序列化成标准 JSON,再拼进脚本。
8.4 只在开发机能跑,换机器就不行
因为本文示例使用了 CDN:
https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js
这很适合演示,但如果你要发布给别人使用,更建议把 echarts.min.js 下载到本地项目目录,再从本地 HTML 引用。
这样即使目标机器离线,也能正常显示图表。
8.5 窗口大小变化后图表不自适应
这个问题在桌面端很常见。
所以页面里最好加上:
window.addEventListener("resize", function () {
chart.resize();
});
否则窗口拖动后,图表可能显示不完整。
9. 这套方案适合哪些项目
如果你正在做下面这些类型的桌面程序,这个方案会非常顺手:
- 数据分析工具
- 教学演示软件
- 设备状态监控客户端
- ERP / MES 辅助报表
- 金融、销售、库存统计面板
- 带图形趋势展示的内部管理工具
尤其是当你的项目同时满足这两个条件时,非常值得考虑:
- Python 适合处理你的业务逻辑
- 传统桌面控件不够优雅地展示图表
这时候 PyQt6 / PySide6 + QWebEngineView + ECharts 基本就是一个很自然的答案。
10. 给初学者的建议:先跑通,再升级
如果你是第一次接触这个方向,不要一上来就做复杂架构。
更推荐这样学:
- 先跑通本文这个最小示例
- 把柱状图改成折线图、饼图、双轴图
- 把随机数据改成你自己的真实数据
- 再把内嵌 HTML 拆成独立文件
- 最后再研究
QWebChannel做双向交互
你会发现,这条学习路线其实很顺:
- 第一步理解 Qt 里怎么嵌网页
- 第二步理解 Python 怎么传数据
- 第三步理解桌面和前端如何协作
一旦这套思路打通,你以后做桌面报表、监控看板、混合式客户端,都会轻松很多。
11. 总结
很多人学 Python 桌面开发时,会默认把自己限制在传统 GUI 控件的范围里。
但 QWebEngineView 提供了一个非常实用的突破口:
你可以在桌面应用里直接借力 Web 可视化生态。
而 ECharts 恰好就是这个方向里非常值得优先掌握的工具之一。
记住这套组合的核心分工:
- Python:处理数据和业务逻辑
- Qt:提供桌面容器和交互界面
- ECharts:负责图表渲染和视觉表达
如果你现在正在学习 Python,又希望做出更像“真实项目”的桌面程序,那么这套方案非常值得你亲手试一遍。
先把示例跑起来,再把数据换成自己的,成长会非常快。
如果你愿意,我后面还可以继续给你展开这一系列内容,比如:
- Python 桌面应用中如何本地离线部署 ECharts
QWebChannel双向通信完整实战- PyQt6 仪表盘、监控大屏、统计后台的图表封装思路
- 如何把多个 ECharts 卡片组织成桌面数据看板
从“会写 Python”走到“能做像样的 Python 桌面项目”,这类跨栈能力非常关键。
更多推荐


所有评论(0)