☰
PyQt6-第15讲-数据可视化QChart
2026/11/6 3:32:54 网站建设 项目流程

第 15 讲 | 数据可视化:QChart 三种图

本讲目标:用 Qt 自带的 QtCharts 画出柱状图、饼图、折线图,理解 QChart 的结构和那个必踩的
attachAxis坑。

配套代码:cases/c15_charts.py完整下载地址请看底部


一、为什么用 QtCharts 而不是 matplotlib

在 PyQt 里画图有两条路:

QtCharts(本讲)matplotlib 嵌入
安装pip install PyQt6-Chartspip install matplotlib
渲染引擎和界面同一个(Qt)另一套(自己画到 QPixmap 上)
缩放的清晰度矢量,放大不糊位图,放大模糊
动画内置,一行开启需要自己写
鼠标交互内置(悬停高亮、缩放平移)需要自己接事件
图表种类常用类型齐全极其丰富(3D、极坐标、误差棒…)
体积小大(拖进来几十 MB)

结论:

  • 界面里嵌图表(仪表盘、监控面板、报表工具)→QtCharts
  • 做数据分析图(统计分布、复杂科学图表)→ matplotlib,甚至直接生成图片存文件

还有一个实用理由:QtCharts 的图表是 Qt 对象,能享受 Qt 的全部能力——
你可以把图表放进布局里、能响应鼠标事件、能保存 QPixmap。
而 matplotlib 只是"在一张画布上画",和界面的融合度差一层。


二、QChart 和 QChartView

先记住这两个类的关系:

类角色类比
QChart画布:装数据系列、坐标轴、标题、图例一台显示器的"画面内容"
QChartView显示器:把 QChart 显示出来显示器本身
fromPyQt6.QtChartsimportQChart,QChartView chart=QChart()chart.setTitle("我的第一张图")# ... 往 chart 里加数据 ...view=QChartView(chart)view.setRenderHint(QPainter.RenderHint.Antialiasing)# ★ 开抗锯齿

setRenderHint(Antialiasing)一定要加。不加的话斜线是锯齿状的,
尤其是折线图和饼图的斜边,一眼就能看出"很土"。


三、柱状图:最常用的图

柱状图由三个对象组成:数据集合(QBarSet)→ 系列(QBarSeries)→ 画布(QChart)

fromPyQt6.QtChartsimportQBarCategoryAxis,QBarSeries,QBarSet,QValueAxisdefbuild_bar_chart()->QChart:rows=load_products()# [(名称, 分类, 单价, 库存), ...]bar_set=QBarSet("单价(元)")# ① 一个 BarSet = 一组数据categories=[]forname,_cat,price,_stockinrows:bar_set.append([price])# 注意:参数是列表,不是单个数字categories.append(name)series=QBarSeries()# ② 系列series.append(bar_set)chart=QChart()# ③ 画布chart.addSeries(series)chart.setTitle("产品单价对比")chart.setAnimationOptions(QChart.AnimationOption.SeriesAnimations)# 横轴:文字类目轴axis_x=QBarCategoryAxis()axis_x.append(categories)chart.addAxis(axis_x,Qt.AlignmentFlag.AlignBottom)series.attachAxis(axis_x)# ★ 必须挂上,否则轴不显示# 纵轴:数值轴axis_y=QValueAxis()axis_y.setRange(0,max(r[2]forrinrows)*1.2)axis_y.setTitleText("单价(元)")axis_y.setLabelFormat("%.0f")chart.addAxis(axis_y,Qt.AlignmentFlag.AlignLeft)series.attachAxis(axis_y)# ★returnchart

四个必须记住的点

①bar_set.append([price])参数是列表

不是append(price)。因为它支持多组数据系列,
append([12, 15, 18])表示这一组要画三根柱子(三组系列对比)。
写错会报参数类型错误。

② 坐标轴必须attachAxis()

这是新手第一大坑。chart.addAxis()只是把轴加到画布上,
系列和轴还是两条平行线。必须series.attachAxis(axis)建立关联,
数据才会按这个轴的范围映射到屏幕上。

忘了 attachAxis 的症状:坐标轴显示出来了(有刻度和标签),
但图里什么都没有。遇到"图是空的但轴在"的情况,先检查这一行。

③ 两条轴都要挂

横轴和纵轴都要attachAxis。只挂一条,图会画得乱七八糟。

④ 纵轴范围要留边距

axis_y.setRange(0,max(...)*1.2)

不乘 1.2 的话,最高的那根柱子会顶到画面顶部,看起来很挤(甚至被裁掉)。
留 20% 余量是通用做法。


四、饼图:注意不要传表头

fromPyQt6.QtChartsimportQPieSeriesdefbuild_pie_chart()->QChart:series=QPieSeries()data=load_category_share()# [(分类, 金额), ...]total=sum(vfor_k,vindata)forname,valueindata:sl=series.append(name,value)sl.setLabel(f"{name}{value/total*100:.1f}%")# 标签带百分比# 占比最大的那块"炸开",视觉重点更清楚ifvalue==max(vfor_k,vindata):sl.setExploded(True)sl.setLabelVisible(True)chart=QChart()chart.addSeries(series)chart.setTitle("各类产品库存金额占比")chart.setAnimationOptions(QChart.AnimationOption.SeriesAnimations)chart.legend().setAlignment(Qt.AlignmentFlag.AlignRight)returnchart

QPieSeries.append(标签, 数值)直接两个参数,比柱状图简单,不需要坐标轴。

两个实用技巧:

①setExploded(True)让某一块"炸开"
占比最大的那块单独分离出来,视觉上立刻有了重点。这是饼图最容易被忽略但效果最好的设置。

② 标签带百分比
setLabel(f"{name} {ratio:.1f}%")—— 光看图看不出精确占比,加上数字才实用。

③ 饼图不要传表头数据

如果你把"分类名称"那一列也当数据传进去,它会变成一个巨大的扇形。
饼图的数据范围只包含数值列。


五、折线图:x 轴用序号,再用文字轴标注

折线图的 x 轴是连续的数值,但我们的横轴往往是"1月、2月、3月"这样的文字。
标准的处理方式是:x 用 0,1,2,… 序号,再用 QBarCategoryAxis 标注文字。

defbuild_line_chart()->QChart:months,values=load_monthly_sales()series=QLineSeries()series.setName("销售额(万元)")fori,vinenumerate(values):series.append(float(i),v)# x 用序号chart=QChart()chart.addSeries(series)chart.setTitle("2026 年销售额趋势")chart.setAnimationOptions(QChart.AnimationOption.SeriesAnimations)axis_x=QBarCategoryAxis()axis_x.append(months)# ★ 用类目轴标注月份文字chart.addAxis(axis_x,Qt.AlignmentFlag.AlignBottom)series.attachAxis(axis_x)axis_y=QValueAxis()axis_y.setRange(0,max(values)*1.2)axis_y.setTitleText("销售额(万元)")chart.addAxis(axis_y,Qt.AlignmentFlag.AlignLeft)series.attachAxis(axis_y)returnchart

注意series.append(float(i), v)的 x 是浮点数,
QLineSeries 的 append 签名是(x: float, y: float),传 int 也能跑但建议统一用 float。

折线图的样式设置(可选):

pen=series.pen()pen.setWidth(3)pen.setColor(QColor("#2d7ff9"))series.setPen(pen)series.setPointsVisible(True)# 显示数据点

六、主题、动画、导出图片

主题

chart.setTheme(QChart.ChartTheme.ChartThemeLight)# 浅色chart.setTheme(QChart.ChartTheme.ChartThemeDark)# 深色chart.setTheme(QChart.ChartTheme.ChartThemeBlueCerulean)# 蓝色系

主题必须在加数据之前设置,否则部分样式不生效。
本案例的"切换主题"按钮就是重新构建图表(保证主题生效):

defshow_chart(self,name:str)->None:builder=BUILDERS.get(name)chart=builder()chart.setTheme(QChart.ChartTheme.ChartThemeDarkifself.darkelseQChart.ChartTheme.ChartThemeLight)self.view.setChart(chart)

注意要重新setChart():改变已有 chart 的主题会有残留样式,
重新构建最干净。

动画

chart.setAnimationOptions(QChart.AnimationOption.SeriesAnimations)# 系列动画chart.setAnimationOptions(QChart.AnimationOption.AllAnimations)# 所有动画chart.setAnimationDuration(800)# 时长(毫秒)

动画不只是好看:数据刷新时,柱子从旧高度长到新高度,
用户能直观感受到"数值变了"。在实时监控面板里这个效果很有价值(下一讲)。

导出图片

self.view.grab().save("图表导出.png")

QWidget.grab()抓的是"控件当前显示的样子",分辨率跟控件大小一致。
想要高分辨率,可以在抓之前临时放大控件,或者用QChart的QPixmap导出能力。


七、一个完整案例的界面

┌──────────────┬────────────────────────────────┐ │ 图表类型 │ │ │ ▸ 柱状图 │ │ │ 饼图 │ QChartView │ │ 折线图 │ (显示选中的图) │ │ │ │ │ [导出为图片] │ │ │ [切换主题] │ │ └──────────────┴────────────────────────────────┘

左侧列表切换图表,右侧一个 QChartView 复用:

self.list_types.currentTextChanged.connect(self.show_chart)defshow_chart(self,name:str)->None:chart=BUILDERS[name]()# ★ 每次重新构建chart.setTheme(...)self.view.setChart(chart)

用字典管理构建函数是个好模式:

BUILDERS={CHART_TYPES[0]:build_bar_chart,CHART_TYPES[1]:build_pie_chart,CHART_TYPES[2]:build_line_chart,}

要加新图表类型,只需写一个函数 + 加一行字典,不用改界面逻辑。
这是"可扩展"的具体写法,比一串 if-elif 好得多。

八、四个坑

坑 1:图是空的,但坐标轴显示出来了
忘记series.attachAxis(axis_x)和attachAxis(axis_y)。

坑 2:QBarSet.append传了单个数字
参数必须是列表形式append([12]),因为它支持多系列对比。

坑 3:饼图把标签列当数据传了
饼图只传数值列。

坑 4:忘了装 PyQt6-Charts
ModuleNotFoundError: No module named 'PyQt6.QtCharts'。
QtCharts 不在 PyQt6 主包里,必须单独pip install PyQt6-Charts。


九、小结

  • QChart 是画布,QChartView 是显示器,setChart()把两者连起来
  • 柱状图三步:QBarSet → QBarSeries → QChart,两条轴都要 attachAxis
  • 饼图最简单:series.append(标签, 值)+setExploded突出重点
  • 折线图 x 用序号,文字标注交给 QBarCategoryAxis
  • 主题要在加数据前设;切换主题最干净的做法是重建图表

现在你能画出静态的图表了。但真实场景里,数据是一直在变的——
设备温度每秒上报一次、股价一直在跳、日志一直在滚动。

下一讲:实时数据监控面板。我们会用QTimer驱动界面刷新,
画出"一直在滚动的曲线",并讲清两个性能关键点:
为什么series.replace()比逐点 append 快得多,以及
QTimer 的间隔该怎么定。


本讲是《PyQt6 客户端编程从入门到实战》专栏的第 15 讲。专栏共 22 讲、22 个可运行案例。
完整下载地址:git clone https://gitee.com/wuyikui555/pyqt6-client.git。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询