第24章 Qt Charts图表:折线图、柱状图、饼图、散点图、图表交互、动态数据更新
图表开发,说白了就是把枯燥的数字变成直观的图形。我做了这么多年Qt开发,几乎每个项目都离不开图表——监控系统要实时曲线,报表工具要柱状对比,数据分析要散点分布。Qt Charts模块帮了大忙,它原生集成在Qt框架里,不需要额外装第三方库。
这一章,我会带你走一遍Qt Charts的核心用法。折线图、柱状图、饼图、散点图,每种图我都会讲清楚怎么搭架子、怎么填数据。最后再聊聊图表交互和动态更新——嗯,这部分在实际项目中特别重要。
24.1 Qt Charts模块概览
Qt Charts是Qt官方提供的2D图表组件。它基于Graphics View框架,所以渲染性能不错。支持的图表类型包括:
- QLineSeries — 折线图/曲线图
- QBarSeries — 柱状图
- QPieSeries — 饼图
- QScatterSeries — 散点图
- QAreaSeries — 面积图
- QPolarChart — 极坐标图
使用前记得在.pro文件里加上:
QT += charts
头文件引用:
#include <QtCharts>
using namespace QtCharts;
24.2 折线图:最常用的趋势展示
折线图适合展示数据随时间的变化趋势。比如CPU使用率、温度曲线、股票走势。我当年做工业监控项目时,折线图几乎占了半个界面。
先看一个最简单的例子:
// 创建图表
QChart *chart = new QChart();
chart->setTitle("CPU温度监控");
// 创建折线序列
QLineSeries *series = new QLineSeries();
series->setName("CPU温度");
// 添加数据点
series->append(0, 36.5);
series->append(1, 38.2);
series->append(2, 42.1);
series->append(3, 45.8);
series->append(4, 44.3);
// 把序列加到图表
chart->addSeries(series);
// 创建坐标轴
QValueAxis *axisX = new QValueAxis();
axisX->setTitleText("时间(秒)");
axisX->setRange(0, 5);
chart->addAxis(axisX, Qt::AlignBottom);
series->attachAxis(axisX);
QValueAxis *axisY = new QValueAxis();
axisY->setTitleText("温度(℃)");
axisY->setRange(30, 50);
chart->addAxis(axisY, Qt::AlignLeft);
series->attachAxis(axisY);
// 显示图表
QChartView *chartView = new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
setCentralWidget(chartView);
这段代码跑起来,你会看到一条从(0,36.5)到(4,44.3)的折线。注意坐标轴要先addAxis再attachAxis,顺序错了图表会不显示。
24.3 柱状图:对比数据的好帮手
柱状图适合做分类对比。比如各月销售额、各产品销量。柱状图比折线图稍微复杂一点,因为它有分组的概念。
// 创建柱状图序列
QBarSet *set1 = new QBarSet("一季度");
QBarSet *set2 = new QBarSet("二季度");
*set1 << 120 << 150 << 180;
*set2 << 200 << 170 << 190;
QBarSeries *series = new QBarSeries();
series->append(set1);
series->append(set2);
// 创建图表
QChart *chart = new QChart();
chart->addSeries(series);
chart->setTitle("季度销售额对比");
// 分类轴
QBarCategoryAxis *axisX = new QBarCategoryAxis();
axisX->append("一月");
axisX->append("二月");
axisX->append("三月");
chart->addAxis(axisX, Qt::AlignBottom);
series->attachAxis(axisX);
// 数值轴
QValueAxis *axisY = new QValueAxis();
axisY->setRange(0, 250);
chart->addAxis(axisY, Qt::AlignLeft);
series->attachAxis(axisY);
这里用了QBarCategoryAxis做分类轴,它会自动把"一月""二月""三月"显示在X轴底部。每个QBarSet代表一组柱子,颜色会自动分配。
24.4 饼图:占比一目了然
饼图展示各部分占总体的比例。Qt的饼图用QPieSeries,用法很直观:
QPieSeries *series = new QPieSeries();
series->append("Android", 68.5);
series->append("iOS", 29.3);
series->append("其他", 2.2);
// 让某一块突出显示
QPieSlice *slice = series->slices().at(0);
slice->setExploded(true);
slice->setLabelVisible(true);
QChart *chart = new QChart();
chart->addSeries(series);
chart->setTitle("移动操作系统市场份额");
饼图的每个扇区叫QPieSlice。你可以单独设置它的颜色、标签、是否突出。setExploded(true)会让扇区往外弹出一段,视觉上更醒目。
我个人习惯在饼图上显示百分比,可以用setLabelVisible(true)配合setLabelFormat():
slice->setLabelVisible(true);
slice->setLabel(QString("%1: %2%")
.arg(slice->label())
.arg(slice->percentage() * 100, 0, 'f', 1));
24.5 散点图:发现数据分布规律
散点图用来观察两个变量之间的关系。比如身高和体重、广告投入和销售额。Qt用QScatterSeries实现:
QScatterSeries *series = new QScatterSeries();
series->setName("样本数据");
series->setMarkerSize(10); // 点的大小
// 随机添加50个点
for (int i = 0; i < 50; ++i) {
series->append(rand() % 100, rand() % 100);
}
QChart *chart = new QChart();
chart->addSeries(series);
chart->createDefaultAxes(); // 自动创建坐标轴
chart->setTitle("散点图示例");
createDefaultAxes()是个偷懒的好方法,它会根据数据自动创建合适的坐标轴。不过自动创建的轴不一定完美,我建议还是手动创建并设置范围。
散点图的标记形状可以改:
series->setMarkerShape(QScatterSeries::MarkerShapeCircle); // 圆形
series->setMarkerShape(QScatterSeries::MarkerShapeRectangle); // 方形
series->setMarkerShape(QScatterSeries::MarkerShapeTriangle); // 三角形
24.6 图表交互:让用户动起来
光显示图表还不够,用户需要交互。Qt Charts提供了几种内置交互方式:
- 缩放 — 鼠标滚轮缩放,按住右键拖动
- 平移 — 按住左键拖动
- 悬停提示 — 鼠标悬停时显示数据值
- 点击选中 — 点击某个数据点触发事件
启用交互很简单:
chartView->setRubberBand(QChartView::RectangleRubberBand);
这样用户就可以用鼠标框选区域进行缩放。如果想禁用缩放,可以设置:
chart->setZoomInFactor(1.0); // 禁止缩放
悬停提示需要连接信号:
connect(series, &QLineSeries::hovered,
[](const QPointF &point, bool state) {
if (state) {
qDebug() << "数据点:" << point.x() << point.y();
}
});
点击事件类似:
connect(series, &QLineSeries::clicked,
[](const QPointF &point) {
qDebug() << "点击了:" << point;
});
24.7 动态数据更新:让图表活起来
实际项目中,数据往往是实时更新的。比如传感器读数、股票价格。Qt Charts支持动态更新,核心思路是定时器+数据追加。
看一个实时折线图的例子:
// 头文件
QLineSeries *m_series;
QChart *m_chart;
int m_x = 0;
// 初始化
m_series = new QLineSeries();
m_chart->addSeries(m_series);
// 定时器更新
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, [this]() {
double y = qrand() % 100; // 模拟数据
m_series->append(m_x++, y);
// 限制数据点数量,防止内存爆炸
if (m_series->count() > 100) {
m_series->remove(0);
}
// 自动滚动X轴
if (m_x > 100) {
m_chart->axisX()->setRange(m_x - 100, m_x);
}
});
timer->start(100); // 每100毫秒更新一次
这里有几个关键点:
- 用append()添加新数据点
- 用remove(0)删除旧数据,保持固定窗口
- 动态调整X轴范围,实现滚动效果
对于柱状图的动态更新,可以用QBarSet的replace()方法:
QBarSet *set = series->barSets().at(0);
set->replace(0, newValue1);
set->replace(1, newValue2);
饼图更新更简单,直接修改QPieSlice的value:
QPieSlice *slice = series->slices().at(0);
slice->setValue(newValue);
24.8 综合示例:多图表联动
最后,我展示一个多图表联动的场景。假设我们要做一个设备监控面板,同时显示温度折线图、湿度柱状图、报警饼图。
核心思路是用QSplitter分割布局,每个图表独立更新:
// 主布局
QSplitter *splitter = new QSplitter(Qt::Horizontal);
// 温度折线图
QChartView *tempView = createTempChart();
splitter->addWidget(tempView);
// 湿度柱状图
QChartView *humidView = createHumidChart();
splitter->addWidget(humidView);
// 报警饼图
QChartView *alarmView = createAlarmChart();
splitter->addWidget(alarmView);
setCentralWidget(splitter);
每个图表的更新函数独立,通过一个全局定时器统一触发:
QTimer *globalTimer = new QTimer(this);
connect(globalTimer, &QTimer::timeout, [this]() {
updateTempChart(); // 更新温度
updateHumidChart(); // 更新湿度
updateAlarmChart(); // 更新报警
});
globalTimer->start(500); // 0.5秒刷新一次
这样做的好处是各图表互不干扰,更新频率可以单独控制。我在实际项目中就是这么做的,用户反馈很流畅。
24.9 本章知识体系
下面这张图总结了Qt Charts的核心知识点和它们之间的关系:
从这张图可以看出,Qt Charts的核心是五种图表类型,它们共享坐标轴和交互机制。动态更新和性能优化是实际项目中的关键,最后通过综合应用把各部分串联起来。
好了,这一章的内容就到这里。Qt Charts其实还有很多高级功能,比如自定义图例、动画效果、主题切换。但我觉得先把基础打牢更重要。你可以在自己的项目里试试这些代码,改改数据看看效果。遇到问题别急,多看看Qt官方文档,里面有很多示例。