QCustomPlot 特定图表类型实战
QCustomPlot 特定图表类型实战
QCustomPlot 不仅擅长绘制基础折线图,还支持多种专业图表类型,包括柱状图、散点图、饼图甚至热力图。虽然其原生 API 主要围绕 QCPGraph
设计,但通过灵活组合图层、图元(QCPItem
)与数据映射,我们完全可以实现丰富的可视化效果。
本文将带你实战五类常见图表:多曲线折线图、柱状图家族、散点/气泡图、饼图/环形图、热力图,每类均提供可运行的核心代码与实现思路。
1. 折线图进阶:多曲线对比、曲线平滑处理与异常值标记
多曲线对比
只需多次调用 addGraph()
并设置不同样式:
// 曲线1:温度
plot->addGraph();
plot->graph(0)->setName("温度");
plot->graph(0)->setData(x, temp);
plot->graph(0)->setPen(QPen(Qt::red, 2));// 曲线2:湿度
plot->addGraph();
plot->graph(1)->setName("湿度");
plot->graph(1)->setData(x, humidity);
plot->graph(1)->setPen(QPen(Qt::blue, 2));
plot->graph(1)->setLineStyle(QCPGraph::lsLine);
plot->graph(1)->setScatterStyle(QCPScatterStyle::ssCircle);plot->legend->setVisible(true);
曲线平滑处理(简易移动平均)
QCustomPlot 本身不提供平滑算法,但可在数据层面预处理:
QVector<double> smooth(const QVector<double>& data, int window = 5) {QVector<double> smoothed;for (int i = 0; i < data.size(); ++i) {double sum = 0;int count = 0;for (int j = qMax(0, i - window/2); j <= qMin(data.size()-1, i + window/2); ++j) {sum += data[j];count++;}smoothed.append(sum / count);}return smoothed;
}auto smoothedY = smooth(yRaw);
plot->addGraph();
plot->graph(0)->setData(x, smoothedY);
plot->graph(0)->setPen(QPen(Qt::green, 1.5, Qt::DashLine));
异常值标记
使用 QCPItemTracer
或 QCPItemEllipse
在特定点添加标记:
for (int i = 0; i < y.size(); ++i) {if (qAbs(y[i] - mean) > 3 * stddev) { // 假设已计算均值与标准差QCPItemEllipse *ellipse = new QCPItemEllipse(plot);ellipse->topLeft->setType(QCPItemPosition::ptPlotCoords);ellipse->bottomRight->setType(QCPItemPosition::ptPlotCoords);ellipse->topLeft->setCoords(x[i] - 0.1, y[i] - 0.5);ellipse->bottomRight->setCoords(x[i] + 0.1, y[i] + 0.5);ellipse->setPen(QPen(Qt::red, 2));ellipse->setBrush(Qt::transparent);}
}
2. 柱状图实战:单组、分组与堆叠柱状图实现
QCustomPlot 没有原生柱状图类,但可通过 QCPBars
实现。
单组柱状图
QCPBars *bars = new QCPBars(plot->xAxis, plot->yAxis);
plot->addPlottable(bars);QVector<double> keys = {1, 2, 3, 4};
QVector<double> values = {2.1, 3.4, 2.8, 4.0};bars->setData(keys, values);
bars->setWidth(0.6);
bars->setPen(QPen(Qt::black));
bars->setBrush(QColor(50, 150, 250));
分组柱状图(多组并列)
为每组创建一个 QCPBars
,并通过偏移 keys
实现并列:
double width = 0.2;
int groupCount = 3;
QVector<QCPBars*> barGroups;for (int i = 0; i < groupCount; ++i) {QCPBars *bar = new QCPBars(plot->xAxis, plot->yAxis);plot->addPlottable(bar);QVector<double> keysOffset;for (double key : baseKeys) {keysOffset << key + (i - groupCount/2.0) * width;}bar->setData(keysOffset, values[i]);bar->setWidth(width * 0.9);bar->setBrush(QColor(100 + i*50, 150, 200));barGroups << bar;
}// 设置 X 轴标签
plot->xAxis->setAutoTicks(false);
plot->xAxis->setAutoTickLabels(false);
plot->xAxis->setTickVector(baseKeys);
plot->xAxis->setTickVectorLabels({"A", "B", "C", "D"});
堆叠柱状图
使用 moveAbove()
方法将柱子堆叠:
QCPBars *bar1 = new QCPBars(plot->xAxis, plot->yAxis);
QCPBars *bar2 = new QCPBars(plot->xAxis, plot->yAxis);bar1->setData(keys, values1);
bar2->setData(keys, values2);bar2->moveAbove(bar1); // bar2 堆叠在 bar1 之上plot->addPlottable(bar1);
plot->addPlottable(bar2);
3. 散点图与气泡图:根据数据维度定制散点大小、颜色映射
基础散点图
plot->addGraph();
plot->graph(0)->setLineStyle(QCPGraph::lsNone); // 无线条
plot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 6));
plot->graph(0)->setData(x, y);
气泡图(第三维:大小)
QCustomPlot 不直接支持变大小散点,但可通过循环添加 QCPItemEllipse
实现:
for (int i = 0; i < x.size(); ++i) {double radius = z[i] * 5; // z 为第三维数据QCPItemEllipse *bubble = new QCPItemEllipse(plot);bubble->topLeft->setCoords(x[i] - radius, y[i] - radius);bubble->bottomRight->setCoords(x[i] + radius, y[i] + radius);bubble->setPen(Qt::NoPen);bubble->setBrush(QColor(255, 100, 100, 150)); // 半透明
}
颜色映射(第四维)
结合 QCPColorGradient
与 QCPColorMap
更适合热力图,但对散点可手动映射:
QCPColorGradient gradient;
gradient.setColorStops({{0.0, Qt::blue}, {0.5, Qt::yellow}, {1.0, Qt::red}});for (int i = 0; i < x.size(); ++i) {double normValue = (z[i] - zMin) / (zMax - zMin); // 归一化QColor color = gradient.color(normValue);QCPItemEllipse *point = new QCPItemEllipse(plot);point->topLeft->setCoords(x[i] - 3, y[i] - 3);point->bottomRight->setCoords(x[i] + 3, y[i] + 3);point->setBrush(color);point->setPen(Qt::NoPen);
}
⚠️ 注意:大量散点时性能较低,建议数据量 < 1000。
4. 饼图与环形图:QCustomPlot 实现饼图及扇区交互效果
QCustomPlot 无原生饼图支持,但可通过 QCPItemEllipse
+ QPainterPath
手绘,或使用 QCPCurve
模拟。
简易饼图实现(使用 QCPItem)
class PieChart : public QObject {
public:static void drawPie(QCustomPlot *plot, const QVector<double> &values, const QStringList &labels) {double total = std::accumulate(values.begin(), values.end(), 0.0);double startAngle = 90; // 从顶部开始for (int i = 0; i < values.size(); ++i) {double spanAngle = 360.0 * values[i] / total;QCPItemPieSlice *slice = new QCPItemPieSlice(plot);slice->setStartAngle(startAngle);slice->setSpanAngle(spanAngle);slice->setRadius(80);slice->setBrush(QColor(QRandomGenerator::global()->bounded(256),QRandomGenerator::global()->bounded(256),QRandomGenerator::global()->bounded(256)));plot->addItem(slice);startAngle += spanAngle;}}
};
📌 实际项目中,更推荐使用
QPainter
自定义 widget 绘制饼图,或集成第三方库。QCustomPlot 并非为此类图表设计。
扇区交互(高亮)
监听鼠标点击,动态调整被点击扇区的 setRadius()
或 setOffset()
实现“弹出”效果。
5. 热力图入门:基于 QCustomPlot 的二维数据热力图绘制技巧
热力图是 QCustomPlot 的强项,通过 QCPColorMap
实现。
基础热力图
QCPColorMap *colorMap = new QCPColorMap(plot->xAxis, plot->yAxis);
plot->addPlottable(colorMap);// 设置数据维度
int nx = 50, ny = 50;
colorMap->data()->setSize(nx, ny);
colorMap->data()->setRange(QCPRange(0, 10), QCPRange(0, 10));// 填充数据
for (int xIndex = 0; xIndex < nx; ++xIndex) {for (int yIndex = 0; yIndex < ny; ++yIndex) {double x = colorMap->data()->key(xIndex);double y = colorMap->data()->value(yIndex);double z = qSin(x) * qCos(y); // 示例函数colorMap->data()->setCell(xIndex, yIndex, z);}
}// 设置颜色梯度
QCPColorGradient gradient;
gradient.setColorStops({{0.0, Qt::blue}, {0.5, Qt::white}, {1.0, Qt::red}});
colorMap->setGradient(gradient);// 显示颜色条
QCPColorScale *colorScale = new QCPColorScale(plot);
plot->plotLayout()->addElement(0, 1, colorScale);
colorScale->setType(QCPAxis::atRight);
colorMap->setColorScale(colorScale);
colorScale->axis()->setLabel("强度");plot->rescaleAxes();
plot->replot();
优化技巧
- 使用
setInterpolate(true)
平滑过渡; - 调用
setTightBoundary(true)
避免边缘空白; - 对于离散数据,可关闭插值并设置
setFillAlpha(1.0)
。
结语
尽管 QCustomPlot 以折线图起家,但通过 QCPBars
、QCPColorMap
、QCPItem
等组件,我们能实现柱状图、热力图、气泡图等丰富图表。对于饼图等非笛卡尔坐标系图表,虽可模拟,但建议评估是否更适合用其他可视化方案。
最佳实践建议:
- 折线/散点/热力图 → 优先使用 QCustomPlot;
- 柱状图 → 使用
QCPBars
;- 饼图/雷达图 → 考虑自定义
QWidget
+QPainter
。
附:关键类速查
图表类型 | 核心类 |
---|---|
折线图 | QCPGraph |
柱状图 | QCPBars |
热力图 | QCPColorMap |
自定义图形 | QCPItemXXX |
图例/标题 | QCPLegend , QCPTextElement |