本文是官方例子的分析: Interaction Example
推荐笔记: qcustomplot使用教程–基本绘图
推荐笔记: 4.QCustomPlot使用-坐标轴常用属性
- 官方例子需要用到很多槽函数, 这里先一次性列举, 自行加入到
qt
的.h
中.下面开始从简单的开始一个个分析.
void qcustomplot_main_init(void); // 初始化void titleDoubleClick(QMouseEvent* event); // 标题 双击void axisLabelDoubleClick(QCPAxis *axis, QCPAxis::SelectablePart part); // 标签 双击void legendDoubleClick(QCPLegend *legend, QCPAbstractLegendItem *item); // 图例 双击void selectionChanged(void); // 选择已更改void mousePress(void); // 鼠标按下void mouseWheel(void); // 鼠标滚轮void addRandomGraph(void); // 添加随机图void removeSelectedGraph(void); // 删除选定图形void removeAllGraphs(void); // 删除所有图形void contextMenuRequest(QPoint pos); // 上下文菜单请求void moveLegend(void); // 移动图例void graphClicked(QCPAbstractPlottable *plottable, int dataIndex); // 图表点击
1.图表点击
// 图表点击
void MainWindow::graphClicked(QCPAbstractPlottable *plottable, int dataIndex)
{//由于我们知道图中只有QCPG图,我们可以立即访问interface1D()//通常最好先检查interface1D()是否返回非零,然后再使用它。// plottable 表示绘图中对象的所有数据的抽象基类。更多。。。// 如果此plottable是一维plottable,即它实现了QCPPlottableInterface1D,则返回具有该类型的this指针。否则(例如在QCPColorMap的情况下)返回零。// interface1D() 您可以使用此方法获得对数据坐标的读取权限,同时只持有指向抽象基类的指针。// dataMainValue() 返回给定索引处数据点的主值。 主要值是什么,由plottable的数据类型定义。double dataValue = plottable->interface1D()->dataMainValue(dataIndex);QString message = QString("Clicked on graph '%1' at data point #%2 with value %3.").arg(plottable->name()).arg(dataIndex).arg(dataValue);// ui->statusBar->showMessage(message, 2500);qDebug() << message;
}
- 当鼠标点击图标中的曲线时, 会回调该函数, 函数内可以获取曲线的名称和数据等. 并打印出最近的点坐标. 如果之后要显示文本游标,可以在这里显示;
2.移动图例
// 图像 移动图例
void MainWindow::moveLegend(void)
{QCustomPlot * customPlot = ui->customPlot;// 确保此插槽确实由上下文菜单操作调用,以便它携带我们需要的数据if (QAction* contextAction = qobject_cast<QAction*>(sender())) // 通过上下文菜单传入{bool ok;int dataInt = contextAction->data().toInt(&ok); // 获取传入的参数if (ok) // 数据解析正确, 这里应该添加确保位置枚举正确{customPlot->axisRect()->insetLayout()->setInsetAlignment(0, static_cast<Qt::Alignment>(dataInt)); // 设置图例的位置customPlot->replot(); // 重绘图像}}
}
- 这个函数是用于移动图例时调用, 在右键菜单中点击选项后调用.
3.右键菜单/上下文菜单请求
// 图像 上下文菜单请求
void MainWindow::contextMenuRequest(QPoint pos)
{QCustomPlot * customPlot = ui->customPlot;QMenu *menu = new QMenu(this); // 创建一个菜单menu->setAttribute(Qt::WA_DeleteOnClose); // 当小部件接受关闭事件时,使Qt删除此小部件if (customPlot->legend->selectTest(pos, false) >= 0) // 请求图例的上下文菜单, 调用了上一个移动图例函数{menu->addAction("Move to top left", this, SLOT(moveLegend()))->setData(static_cast<int>(Qt::AlignTop|Qt::AlignLeft)); // 移动图例到左上角menu->addAction("Move to top center", this, SLOT(moveLegend()))->setData(static_cast<int>(Qt::AlignTop|Qt::AlignHCenter)); // 移动图例到中上角menu->addAction("Move to top right", this, SLOT(moveLegend()))->setData(static_cast<int>(Qt::AlignTop|Qt::AlignRight)); // 移动图例到右上角menu->addAction("Move to bottom right", this, SLOT(moveLegend()))->setData(static_cast<int>(Qt::AlignBottom|Qt::AlignRight)); // 移动图例到左下角menu->addAction("Move to bottom left", this, SLOT(moveLegend()))->setData(static_cast<int>(Qt::AlignBottom|Qt::AlignLeft)); // 移动图例到右下角}else // 所需图形上的通用上下文菜单{menu->addAction("Add random graph", this, SLOT(addRandomGraph())); // 添加一条曲线if (customPlot->selectedGraphs().size() > 0) // 如果选择的曲线数量大于0menu->addAction("Remove selected graph", this, SLOT(removeSelectedGraph())); // 删除曲线if (customPlot->graphCount() > 0) // 如果全部曲线数量大于0menu->addAction("Remove all graphs", this, SLOT(removeAllGraphs())); // 删除所有曲线}// 将小部件坐标pos转换为全局屏幕坐标。例如,mapToGlobal(QPoint(0,0))将给出小部件左上角像素的全局坐标。// 显示菜单,使动作atAction位于指定的全局位置p。要将小部件的局部坐标转换为全局坐标,menu->popup(customPlot->mapToGlobal(pos));
}
- 在图像内右键时调用, 设定弹出的上下文菜单.
- 先是判断点击位置是否在图例内, 如果是就弹出图例菜单, 否者就弹出曲线菜单;注意
- 注意选择 菜单后调用的函数,传参的方法.
4.删除所有曲线
// 图像 删除所有图形
void MainWindow::removeAllGraphs()
{QCustomPlot * customPlot = ui->customPlot;customPlot->clearGraphs(); // 删除所有曲线, 坐标轴等内容是保留的customPlot->replot(); // 重绘图像
}
- 注意是删除所有
graphs
,我发现其他例子中貌似有些曲线不算graphs
;
5.删除选择曲线
// 图像 删除选定图形
void MainWindow::removeSelectedGraph()
{QCustomPlot * customPlot = ui->customPlot;if (customPlot->selectedGraphs().size() > 0) // 选择的曲线数量大于0{customPlot->removeGraph(customPlot->selectedGraphs().first()); // 删除特定的曲线, 选择曲线列表中的第一条customPlot->replot(); // 重绘图像}
}
6.添加随机曲线
// 图像 添加图像
void MainWindow::addRandomGraph()
{QCustomPlot * customPlot = ui->customPlot;int n = 50; // 图中的点数double xScale = (rand()/static_cast<double>(RAND_MAX) + 0.5)*2;double yScale = (rand()/static_cast<double>(RAND_MAX) + 0.5)*2;double xOffset = (rand()/static_cast<double>(RAND_MAX) - 0.5)*4;double yOffset = (rand()/static_cast<double>(RAND_MAX) - 0.5)*10;double r1 = (rand()/static_cast<double>(RAND_MAX) - 0.5)*2;double r2 = (rand()/static_cast<double>(RAND_MAX) - 0.5)*2;double r3 = (rand()/static_cast<double>(RAND_MAX) - 0.5)*2;double r4 = (rand()/static_cast<double>(RAND_MAX) - 0.5)*2;QVector<double> x(n), y(n);for (int i=0; i<n; i++){x[i] = (i/static_cast<double>(n)-0.5)*10.0*xScale + xOffset;y[i] = (qSin(x[i]*r1*5)*qSin(qCos(x[i]*r2)*r4*3)+r3*qCos(qSin(x[i])*r4*2))*yScale + yOffset;}customPlot->addGraph(); // 添加曲线customPlot->graph()->setName(QString("New graph %1").arg(customPlot->graphCount()-1)); // 设置曲线图例名字customPlot->graph()->setData(x, y); // 设置数据customPlot->graph()->setLineStyle(static_cast<QCPGraph::LineStyle>(rand()%5+1)); // 设置曲线类型if (rand()%100 > 50)customPlot->graph()->setScatterStyle(QCPScatterStyle(static_cast<QCPScatterStyle::ScatterShape>(rand()%14+1))); // 设置点的类型QPen graphPen;graphPen.setColor(QColor(rand()%245+10, rand()%245+10, rand()%245+10)); // 设置颜色graphPen.setWidthF(rand()/static_cast<double>(RAND_MAX)*2+1); // 设置粗细customPlot->graph()->setPen(graphPen); // 设置线的颜色等customPlot->replot(); // 重绘
}
- 这就是添加一些随机数据然后显示.设置了图线的显示样式;
7.鼠标滚轮缩放图像
// 图像 鼠标滚轮
void MainWindow::mouseWheel()
{QCustomPlot * customPlot = ui->customPlot;//如果选择了一个轴,则只允许缩放该轴的方向//如果未选择轴,则可以缩放两个方向if (customPlot->xAxis->selectedParts().testFlag(QCPAxis::spAxis)) // 如果选择了坐标轴customPlot->axisRect()->setRangeZoom(customPlot->xAxis->orientation()); // 只缩放一个坐标轴else if (customPlot->yAxis->selectedParts().testFlag(QCPAxis::spAxis)) // 如果选择了坐标轴customPlot->axisRect()->setRangeZoom(customPlot->yAxis->orientation()); // 只缩放一个坐标轴elsecustomPlot->axisRect()->setRangeZoom(Qt::Horizontal|Qt::Vertical); // 同时缩放两个坐标轴
}
- 通过点击坐标来判断选择坐标轴的缩放,非常好的思路,就是不太习惯;
8.鼠标按下移动图像
// 图像 鼠标被按下
void MainWindow::mousePress()
{QCustomPlot * customPlot = ui->customPlot;//如果选择了轴,则只允许拖动该轴的方向//如果未选择轴,则可以拖动两个方向if (customPlot->xAxis->selectedParts().testFlag(QCPAxis::spAxis)) // 如果选择了坐标轴customPlot->axisRect()->setRangeDrag(customPlot->xAxis->orientation()); // 只移动一个坐标轴else if (customPlot->yAxis->selectedParts().testFlag(QCPAxis::spAxis)) // 如果选择了坐标轴customPlot->axisRect()->setRangeDrag(customPlot->yAxis->orientation()); // 只移动一个坐标轴elsecustomPlot->axisRect()->setRangeDrag(Qt::Horizontal|Qt::Vertical); // 同时移动两个坐标轴
}
9.选择坐标轴或图例或曲线
// 图像 选择被修改
void MainWindow::selectionChanged(void)
{QCustomPlot * customPlot = ui->customPlot;/*通常,坐标轴基线、坐标轴刻度标签和坐标轴标签是可以单独选择的,但我们希望用户只能将整个坐标轴作为一个整体来选择,因此我们将刻度标签和坐标轴基线的选中状态绑定在一起。然而,坐标轴标签仍然可以单独选择。左侧和右侧坐标轴的选中状态应该同步,底部和顶部坐标轴的选中状态也应该同步。此外,我们希望将图形的选中状态与其对应的图例项的选中状态同步。这样,用户既可以通过点击图形本身来选择图形,也可以通过点击其图例项来选择图形。*///使上下轴同步选择,并将轴和刻度标签作为一个可选对象处理:if (customPlot->xAxis->selectedParts().testFlag(QCPAxis::spAxis) || customPlot->xAxis->selectedParts().testFlag(QCPAxis::spTickLabels) ||customPlot->xAxis2->selectedParts().testFlag(QCPAxis::spAxis) || customPlot->xAxis2->selectedParts().testFlag(QCPAxis::spTickLabels)){customPlot->xAxis2->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels); // 如果其中一边坐标轴被选择, 那另一边的坐标轴也被同步选择customPlot->xAxis->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels);}//使左右轴同步选择,并将轴和刻度标签作为一个可选对象处理:if (customPlot->yAxis->selectedParts().testFlag(QCPAxis::spAxis) || customPlot->yAxis->selectedParts().testFlag(QCPAxis::spTickLabels) ||customPlot->yAxis2->selectedParts().testFlag(QCPAxis::spAxis) || customPlot->yAxis2->selectedParts().testFlag(QCPAxis::spTickLabels)){customPlot->yAxis2->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels); // 如果其中一边坐标轴被选择, 那另一边的坐标轴也被同步选择customPlot->yAxis->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels);}//将图形的选择与相应图例项的选择同步:for (int i=0; i<customPlot->graphCount(); ++i){QCPGraph *graph = customPlot->graph(i); // 获取图像对象QCPPlottableLegendItem *item = customPlot->legend->itemWithPlottable(graph); // 获取图例对象if (item->selected() || graph->selected()) // 如果图例或图像被选择, 就同步两个都被选择{item->setSelected(true);graph->setSelection(QCPDataSelection(graph->data()->dataRange()));}}
}
- 用于同步选择对象, 选择了一边坐标轴就同时设置另一边也被选择.图例和曲线同理;
- 注意函数的调用,暂时无法理解的话就照抄微改;
10.双击图例修改名字
// 图像 图例双击
void MainWindow::legendDoubleClick(QCPLegend *legend, QCPAbstractLegendItem *item)
{QCustomPlot * customPlot = ui->customPlot;//双击图例项重命名图形Q_UNUSED(legend) // 消除报错if (item) //仅当项目被点击时才会做出反应(用户本可以在没有项目的地方点击图例的边框填充,则项目为0){QCPPlottableLegendItem *plItem = qobject_cast<QCPPlottableLegendItem*>(item); // 对象类型转换bool ok;QString newName = QInputDialog::getText(this, "QCustomPlot example", "New graph name:", QLineEdit::Normal, plItem->plottable()->name(), &ok); // 使用弹窗获取文本if (ok) // 获取成功{plItem->plottable()->setName(newName); // 重新设置图例名字customPlot->replot(); // 重绘图像}}
}
- 注意函数调用,其他不赘述了;
11.双击坐标轴修改名字
// 图像 标签双击
void MainWindow::axisLabelDoubleClick(QCPAxis *axis, QCPAxis::SelectablePart part)
{QCustomPlot * customPlot = ui->customPlot;// Set an axis label by double clicking on itif (part == QCPAxis::spAxisLabel) // only react when the actual axis label is clicked, not tick label or axis backbone{bool ok;QString newLabel = QInputDialog::getText(this, "QCustomPlot example", "New axis label:", QLineEdit::Normal, axis->label(), &ok);if (ok){axis->setLabel(newLabel);customPlot->replot();}}
}
- 注意函数调用,和上一个功能类似,其他不赘述了;
12.双击标题修改名字
// 图像 标题双击
void MainWindow::titleDoubleClick(QMouseEvent* event)
{QCustomPlot * customPlot = ui->customPlot;Q_UNUSED(event)if (QCPTextElement *title = qobject_cast<QCPTextElement*>(sender())){// Set the plot title by double clicking on itbool ok;QString newTitle = QInputDialog::getText(this, "QCustomPlot example", "New plot title:", QLineEdit::Normal, title->text(), &ok);if (ok){title->setText(newTitle);customPlot->replot();}}
}
- 注意函数调用,和上一个功能类似,其他不赘述了;
13.初始化图表内容
// 图像 初始化
void MainWindow::qcustomplot_main_init(void)
{QCustomPlot * customPlot = ui->customPlot; // 方便调用customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes | QCP::iSelectLegend | QCP::iSelectPlottables); // 允许拖动,缩放,选择坐标轴,选择图例,选择曲线;customPlot->xAxis->setRange(-8, 8); // 设置初始范围customPlot->yAxis->setRange(-5, 5); // 设置初始范围customPlot->axisRect()->setupFullAxesBox(); // 相当于开启右和上的坐标轴, 方便功能,在每侧创建一个还没有轴的轴,并将其可见性设置为true。此外,顶部/右侧轴被分配了底部/左侧轴的以下属性:customPlot->plotLayout()->insertRow(0); // 插入一行?QCPTextElement *title = new QCPTextElement(customPlot, "Interaction Example", QFont("sans", 17, QFont::Bold));customPlot->plotLayout()->addElement(0, 0, title); // 在上面插入的那一行中? 添加标题customPlot->xAxis->setLabel("x Axis"); // 设置坐标轴名字customPlot->yAxis->setLabel("y Axis");customPlot->legend->setVisible(true); // 显示图例QFont legendFont = font();legendFont.setPointSize(10); // 设置字体大小customPlot->legend->setFont(legendFont); // 设置字体customPlot->legend->setSelectedFont(legendFont); // 设置选择后的字体customPlot->legend->setSelectableParts(QCPLegend::spItems); // 图例框不可选择,只能选择图例项addRandomGraph(); // 添加随机曲线addRandomGraph();addRandomGraph();addRandomGraph();customPlot->rescaleAxes(); // 自适应坐标轴范围// 连接将某些轴选择连接在一起的插槽(特别是相对的轴):connect(customPlot, SIGNAL(selectionChangedByUser()), this, SLOT(selectionChanged())); // 选择时调用// 连接插槽时,请注意在选择轴时,只能拖动和缩放该方向:connect(customPlot, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress())); // 缩放时调用connect(customPlot, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel())); // 拖动时调用// 使底部和左侧轴将其范围转移到顶部和右侧轴:connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), customPlot->xAxis2, SLOT(setRange(QCPRange))); // 同步显示范围connect(customPlot->yAxis, SIGNAL(rangeChanged(QCPRange)), customPlot->yAxis2, SLOT(setRange(QCPRange)));//连接一些交互插槽:connect(customPlot, SIGNAL(axisDoubleClick(QCPAxis*,QCPAxis::SelectablePart,QMouseEvent*)), this, SLOT(axisLabelDoubleClick(QCPAxis*,QCPAxis::SelectablePart))); // 双击时修改connect(customPlot, SIGNAL(legendDoubleClick(QCPLegend*,QCPAbstractLegendItem*,QMouseEvent*)), this, SLOT(legendDoubleClick(QCPLegend*,QCPAbstractLegendItem*)));connect(title, SIGNAL(doubleClicked(QMouseEvent*)), this, SLOT(titleDoubleClick(QMouseEvent*)));//当单击图形时,连接插槽会在状态栏中显示一条消息:connect(customPlot, SIGNAL(plottableClick(QCPAbstractPlottable*,int,QMouseEvent*)), this, SLOT(graphClicked(QCPAbstractPlottable*,int)));//为上下文菜单弹出设置策略和连接槽:customPlot->setContextMenuPolicy(Qt::CustomContextMenu); // 小部件如何显示上下文菜单connect(customPlot, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequest(QPoint)));
}
- 初始化内容, 设置初始的允许操作,设置初始化的图例, 添加初始化的曲线,连接交互用的槽函数;
- 注意拖动和缩放的交互是如何实现的.
14.总结
- 至此已经能实现一些基本的交互和操作, 接下来就是显示游标和文字.