目录
一、项目背景
在停车项目中Qt自带的QChart模块在可视化数据方面有着很好的应用,但是在开发道路车流数据可视化大屏项目时由于数据量大,相机和雷达数据实时刷新频繁,QChart性能上有点跟不上;后续更换QCustomPlot库后解决该问题,促进项目落地验收。
使用避坑:确保正确添加 printsupport 模块;绘图画板用QWidget并准确提升控件QCustomPlot;下载稳定的版本注意适用的Qt版本。
二、常用的绘图以及注意事项
以下是关于 QCustomPlot 绘制不同类型图表的核心方法、常用函数和核心类的详细表格。所有条目基于典型用法,并已按图表类型分类说明:
| 图表类型 | 核心类/对象 | 核心方法/步骤 | 常用函数 | 注意事项与关键特性 |
|---|---|---|---|---|
| 折线图 | QCPGraph | 1. 调用 2. 准备 QVector 类型的 x、y 数据 3. 使用 4. 可选设置线型、颜色、图例名称等 |
| 确保 x、y 向量长度一致;大数据量时优先使用 setData(QCPGraph::lsNone) 关闭插值以提高性能 |
| 散点图 | QCPGraph | 1. 使用 2. 关闭线条显示 3. 设置 |
| 可搭配误差棒使用;支持自定义形状(如 QCPScatterStyle::ssCircle) |
| 柱状图 | QCPBars | 1. 使用 2. 设置柱宽、偏移量、填充颜色等 3. 调用 |
| x 值为分类位置(如 1,2,3…);支持堆叠显示;需手动设置 x 轴标签 |
| 曲线图(平滑) | QCPGraph | 1. 创建图形( 2. 设置 |
| 平滑处理会增加计算量,大数据量慎用;可通过 setSmoothScaler() 调整平滑度 |
| 等高线/热图 | QCPColorMap | 1. 实例化 2. 使用 3. 设置色阶( |
| 数据需为规整网格;支持交互缩放时动态更新色阶范围 |
| 误差棒图 | QCPErrorBars | 1. 创建误差棒对象( 2. 使用 3. 调用 |
| 误差数据需与关联图形的数据点一一对应;支持只显示正误差、负误差或两者同时 |
| 饼图 | 无内置类(需自定义) | 1. 通常继承 2. 使用 3. 计算扇形角度、颜色并调用 QPainter 绘制 | 自定义绘制函数 | QCustomPlot 未原生支持,需结合 QPainter 实现;可参考社区扩展或示例代码 |
| 金融图(K线) | QCPFinancial | 1. 使用 2. 设置开盘、最高、最低、收盘数据(OHLC) 3. 调用 |
| 支持蜡烛图、美国线等样式;时间序列需转换为 double 类型(如 QDateTime::toTime_t()) |
通用流程与核心类说明:
- QCustomPlot:主控件类,负责管理所有元素(坐标轴、图层、图例等)。
- QCPAxisRect:坐标轴矩形区域,可创建多个实现多图表布局。
- QCPLegend:图例管理,通过
legend->setVisible(true)显示。 - 通用步骤:
- 初始化 QCustomPlot 控件(如在 UI 中提升或代码创建)。
- 添加图形/图表对象(
addGraph()或addPlottable())。 - 准备数据(通常为 QVector
<double>或专用数据类如 QCPGraphData)。 - 设置数据并调用
rescaleAxes()自动调整坐标范围。 - 定制样式(线型、颜色、标签等)。
- 调用
replot()刷新显示。
常见问题提醒:
- 数据不显示:检查数据范围是否在坐标轴可见区间内,或调用
rescaleAxes()。 - 性能差:大数据量时关闭抗锯齿(
setAntialiasedElements())或使用setData(QCPGraphDataContainer)优化。 - 编译错误:确保 pro 文件包含
QT += widgets printsupport,并正确包含头文件。
三、曲线图绘制实例
3.1 绘制主代码
void Qmultiwidgettest::on_pB1_clicked()
{
auto& customPlot = ui.customePlotWgt;
connect(customPlot, &QCustomPlot::mousePress, this, &Qmultiwidgettest::on_lineMouseClick);//鼠标点击显示附近点坐标
connect(customPlot, &QCustomPlot::mouseRelease, this, &Qmultiwidgettest::on_lineMouseRelease);//鼠标释放清除功能
// 启用自定义Plot的右键菜单交互
customPlot->setContextMenuPolicy(Qt::CustomContextMenu);
// 连接右键菜单信号
connect(customPlot, &QCustomPlot::customContextMenuRequested,
this, &Qmultiwidgettest::showContextMenu);
// 为了精确捕获鼠标事件,我们需要启用轨迹追踪
customPlot->setMouseTracking(true);
customPlot->clearGraphs();
customPlot->addGraph();
customPlot->graph(0)->setPen(QPen(Qt::blue));
//曲线与X轴之间蓝色透明度20
//customPlot->graph(0)->setBrush(QBrush(QColor(0, 0, 255, 20)));
customPlot->addGraph();
customPlot->graph(1)->setPen(QPen(Qt::red));
//两条曲线之间的填充红色透明度50
customPlot->graph(0)->setBrush(QBrush(QColor(255, 0, 0, 50)));
customPlot->graph(0)->setChannelFillGraph(customPlot->graph(1));
// 显示图例
customPlot->legend->setVisible(true);
// 设置曲线名称(将显示在图例中)
customPlot->graph(0)->setName("曲线1");
// 可选:设置图例位置
customPlot->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignTop | Qt::AlignRight);
customPlot->graph(1)->setName("曲线2");
customPlot->axisRect()->insetLayout()->setInsetAlignment(1, Qt::AlignTop | Qt::AlignRight);
//添加高亮散点图作为双击提示
QCPGraph* highlightGraph = customPlot->addGraph();
highlightGraph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, Qt::red, 8)); // 红色圆点,稍大
highlightGraph->setLineStyle(QCPGraph::lsNone);
highlightGraph->setName("HighLight"); // 可选的,方便识别
highlightGraph->setVisible(false);
highlightGraph->removeFromLegend();
QVector<double> x(251), y0(251), y1(251);
for (int i = 0; i<251; ++i)
{
x[i] = i;
y0[i] = 5*qExp(-i / 150.0)*qCos(i / 10.0);
y1[i] = 5*qExp(-i / 150.0);
}
//顶上和右边的xy副座标显示但隐藏刻度标签
customPlot->xAxis2->setVisible(true);
customPlot->xAxis2->setTickLabels(false);
customPlot->yAxis2->setVisible(true);
customPlot->yAxis2->setTickLabels(false);
//主坐标轴范围变化时自动同步到辅助坐标轴
//确保拖动 / 缩放时四个坐标轴保持联动
connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), customPlot->xAxis2, SLOT(setRange(QCPRange)));
connect(customPlot->yAxis, SIGNAL(rangeChanged(QCPRange)), customPlot->yAxis2, SLOT(setRange(QCPRange)));
// 将数据绑定到对应图形
customPlot->graph(0)->setData(x, y0);
customPlot->graph(1)->setData(x, y1);
//rescaleAxes():坐标轴范围会被设置为刚好包围图形的所有数据点。如果之前坐标轴的范围比数据范围大,它会被缩小;如果之前范围小,则会被扩大。
//rescaleAxes(true):确保坐标轴范围至少能容纳该图形的所有数据,但如果当前范围已经比数据范围大,则会保持不变
customPlot->graph(0)->rescaleAxes();
customPlot->graph(1)->rescaleAxes(true);
//交互设置 启用鼠标拖动缩放、滚轮缩放和图形选择功能 选中后默认在原有基础上添加半透明蓝色
//customPlot->graph(0)->setSelectionDecorator(nullptr);禁用选中高亮
customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);
//自定义选中效果默认叠加半透明蓝色 现在改为半透明橙色
QCPSelectionDecorator *decorator = customPlot->graph(0)->selectionDecorator();
// 如果装饰器不存在,先创建一个
if (!decorator)
{
decorator = new QCPSelectionDecorator;
customPlot->graph(0)->setSelectionDecorator(decorator);
}
// 设置选中时的笔刷(如填充色)和画笔(如边框色)
decorator->setBrush(QBrush(QColor(255, 200, 0, 100))); // 半透明橙色填充
decorator->setPen(QPen(Qt::red, 2)); // 红色边框,宽度为2
customPlot->replot(); // 强制重绘图表
}
3.2点击显示值功能
void Qmultiwidgettest::on_lineMouseClick(QMouseEvent* event)
{
auto& customPlot = ui.customePlotWgt;
// 获取鼠标双击处的x, y坐标(相对于QCustomPlot绘图区域的坐标)
//pixelToCoord 将像素位置转换为图表坐标轴值
double x = customPlot->xAxis->pixelToCoord(event->pos().x());
double y = customPlot->yAxis->pixelToCoord(event->pos().y());
// 初始化一个变量来存储找到的数据点索引
int dataIndex = -1;
QCPGraph* targetGraph = nullptr;
double minDistance = std::numeric_limits<double>::max();
//遍历所有图形,寻找距离双击点最近的曲线和数据点
for (int graphIndex = 0; graphIndex < customPlot->graphCount(); ++graphIndex)
{
QCPGraph* graph = customPlot->graph(graphIndex);
if (!graph || graph->data()->isEmpty()) continue; // 跳过空图形
// 在图形的数据中寻找与鼠标x坐标最接近的数据点
QSharedPointer<QCPGraphDataContainer> data = graph->data();
auto it = std::lower_bound(data->begin(), data->end(), x,
[](const QCPGraphData& data, double value) { return data.key < value; });
// 检查找到的点及其前一个点,计算与鼠标点的实际距离
if (it != data->end())
{
double distance = calculateDistance(x, y, it->key, it->value);
if (distance < minDistance)
{
minDistance = distance;
dataIndex = std::distance(data->begin(), it);
targetGraph = graph;
}
}
if (it != data->begin())
{
--it;
double distance = calculateDistance(x, y, it->key, it->value);
if (distance < minDistance)
{
minDistance = distance;
dataIndex = std::distance(data->begin(), it);
targetGraph = graph;
}
}
}
// 如果找到了一个足够近的数据点(例如距离小于2像素的容差),则显示其坐标
double toleranceInPixels = 2.0;
double toleranceInX = customPlot->xAxis->pixelToCoord(toleranceInPixels) - customPlot->xAxis->pixelToCoord(0);
double toleranceInY = customPlot->yAxis->pixelToCoord(0) - customPlot->yAxis->pixelToCoord(toleranceInPixels); // 注意Y轴方向
if (targetGraph && minDistance < sqrt(toleranceInX*toleranceInX + toleranceInY*toleranceInY))
{
const QCPGraphData* dataPoint = targetGraph->data()->at(dataIndex);
QString coordinateText = QString("曲线%1: X=%2, Y=%3")
.arg(targetGraph->name())
.arg(dataPoint->key, 0, 'f', 2) // 保留两位小数
.arg(dataPoint->value, 0, 'f', 2);
// 显示坐标信息(使用QToolTip或自定义QLabel)
QToolTip::showText(event->globalPos(), coordinateText, customPlot);
// 或者自定义的QLabel显示:
// label->setText(coordinateText);
// label->move(event->globalPos());
// label->show();
// 在显示坐标信息的同时,添加一个高亮的数据点(散点图)
auto*highlightGraph = customPlot->graph(2);
highlightGraph->data()->clear();
highlightGraph->addData(dataPoint->key, dataPoint->value);
highlightGraph->setVisible(true);
customPlot->replot(); // 重绘图表以显示高亮点
}
else
{
// 如果点击在曲线之外,可以隐藏提示或显示其他信息
QToolTip::hideText();
auto*highlightGraph = customPlot->graph(2);
highlightGraph->setVisible(false);
customPlot->replot();
}
}
void Qmultiwidgettest::on_lineMouseRelease(QMouseEvent* event)
{
auto*highlightGraph = ui.customePlotWgt->graph(2);
if (highlightGraph) {
highlightGraph->setVisible(false);
ui.customePlotWgt->replot();
}
}
3.3右键导出功能
void Qmultiwidgettest::showContextMenu(const QPoint &pos)
{
QMenu *menu = new QMenu(ui.customePlotWgt);
// 添加导出动作
QAction *exportPngAction = menu->addAction("导出为PNG");
QAction *exportJpgAction = menu->addAction("导出为JPG");
QAction *exportPdfAction = menu->addAction("导出为PDF");
QAction *exportBmpAction = menu->addAction("导出为BMP");
// 连接动作到槽函数
connect(exportPngAction, &QAction::triggered, this, [this]() { exportPlot("PNG"); });
connect(exportJpgAction, &QAction::triggered, this, [this]() { exportPlot("JPG"); });
connect(exportPdfAction, &QAction::triggered, this, [this]() { exportPlot("PDF"); });
connect(exportBmpAction, &QAction::triggered, this, [this]() { exportPlot("BMP"); });
// 显示菜单
menu->popup(ui.customePlotWgt->mapToGlobal(pos));
}
void Qmultiwidgettest::exportPlot(const QString &format, int factor)
{
auto& customPlot = ui.customePlotWgt;
QString fileName = QFileDialog::getSaveFileName(
this,
"保存图表",
QDir::homePath() + "/chart." + format.toLower(),
format + " Files (*." + format.toLower() + ")"
);
if (fileName.isEmpty()) return;
bool success = false;
//分辨率调整 对PDF清晰度无效矢量
int width = customPlot->width()*factor;
int height = customPlot->height()*factor;
if (format == "PNG") {
// PNG 是无损压缩格式,不支持“质量参数”
success = customPlot->savePng(fileName, width, height, factor);
}
else if (format == "JPG") {
//quality 1-100 质量好压缩率小
success = customPlot->saveJpg(fileName, width, height, factor, 100);
}
else if (format == "PDF") {
// 计算尺寸:以300 DPI的A4纸为例 (8.27 x 11.69英寸)
int widthA4 = 8.27 * 300; // 2481 pixels
int heightA4 = 11.69 * 300; // 3507 pixels
// 保存PDF,使用适合打印的pen模式
success = customPlot->savePdf(fileName, widthA4, heightA4, QCP::epNoCosmetic);
}
else if (format == "BMP") {//BMP无压缩像素数据直接存储不考虑quality 只考虑插值
success = customPlot->saveBmp(fileName, width, height, factor);
}
if (success) {
QMessageBox::information(this, "导出成功",
QString("图表已成功导出为%1格式").arg(format));
}
else {
QMessageBox::warning(this, "导出失败",
QString("导出%1格式失败,请检查文件路径和权限").arg(format));
}
}

849

被折叠的 条评论
为什么被折叠?



