说明
这篇主要介绍QCustomPlo的整体实现思路,理解是非常重要的。
QCustomPlot类
QCustomPlot类是直接面向用户来使用。具有默认的图层展示。
可以参见其源码:
QCustomPlot::QCustomPlot(QWidget *parent) :
QWidget(parent),
xAxis(0),
yAxis(0),
xAxis2(0),
yAxis2(0),
legend(0),
mBufferDevicePixelRatio(1.0), // will be adapted to primary screen below
mPlotLayout(0),
mAutoAddPlottableToLegend(true),
mAntialiasedElements(QCP::aeNone),
mNotAntialiasedElements(QCP::aeNone),
mInteractions(0),
mSelectionTolerance(8),
mNoAntialiasingOnDrag(false),
mBackgroundBrush(Qt::white, Qt::SolidPattern),
mBackgroundScaled(true),
mBackgroundScaledMode(Qt::KeepAspectRatioByExpanding),
mCurrentLayer(0),
mPlottingHints(QCP::phCacheLabels|QCP::phImmediateRefresh),
mMultiSelectModifier(Qt::ControlModifier),
mSelectionRectMode(QCP::srmNone),
mSelectionRect(0),
mOpenGl(false),
mMouseHasMoved(false),
mMouseEventLayerable(0),
mMouseSignalLayerable(0),
mReplotting(false),
mReplotQueued(false),
mOpenGlMultisamples(16),
mOpenGlAntialiasedElementsBackup(QCP::aeNone),
mOpenGlCacheLabelsBackup(true)
{
setAttribute(Qt::WA_NoMousePropagation);
setAttribute(Qt::WA_OpaquePaintEvent);
setFocusPolicy(Qt::ClickFocus);
setMouseTracking(true);
QLocale currentLocale = locale();
currentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
setLocale(currentLocale);
#ifdef QCP_DEVICEPIXELRATIO_SUPPORTED
# ifdef QCP_DEVICEPIXELRATIO_FLOAT
setBufferDevicePixelRatio(QWidget::devicePixelRatioF());
# else
setBufferDevicePixelRatio(QWidget::devicePixelRatio());
# endif
#endif
mOpenGlAntialiasedElementsBackup = mAntialiasedElements;
mOpenGlCacheLabelsBackup = mPlottingHints.testFlag(QCP::phCacheLabels);
// create initial layers:
mLayers.append(new QCPLayer(this, QLatin1String("background")));
mLayers.append(new QCPLayer(this, QLatin1String("grid")));
mLayers.append(new QCPLayer(this, QLatin1String("main")));
mLayers.append(new QCPLayer(this, QLatin1String("axes")));
mLayers.append(new QCPLayer(this, QLatin1String("legend")));
mLayers.append(new QCPLayer(this, QLatin1String("overlay")));
updateLayerIndices();
setCurrentLayer(QLatin1String("main"));
layer(QLatin1String("overlay"))->setMode(QCPLayer::lmBuffered);
// create initial layout, axis rect and legend:
mPlotLayout = new QCPLayoutGrid;
mPlotLayout->initializeParentPlot(this);
mPlotLayout->setParent(this); // important because if parent is QWidget, QCPLayout::sizeConstraintsChanged will call QWidget::updateGeometry
mPlotLayout->setLayer(QLatin1String("main"));
QCPAxisRect *defaultAxisRect = new QCPAxisRect(this, true);
mPlotLayout->addElement(0, 0, defaultAxisRect);
xAxis = defaultAxisRect->axis(QCPAxis::atBottom);
yAxis = defaultAxisRect->axis(QCPAxis::atLeft);
xAxis2 = defaultAxisRect->axis(QCPAxis::atTop);
yAxis2 = defaultAxisRect->axis(QCPAxis::atRight);
legend = new QCPLegend;
legend->setVisible(false);
defaultAxisRect->insetLayout()->addElement(legend, Qt::AlignRight|Qt::AlignTop);
defaultAxisRect->insetLayout()->setMargins(QMargins(12, 12, 12, 12));
defaultAxisRect->setLayer(QLatin1String("background"));
xAxis->setLayer(QLatin1String("axes"));
yAxis->setLayer(QLatin1String("axes"));
xAxis2->setLayer(QLatin1String("axes"));
yAxis2->setLayer(QLatin1String("axes"));
xAxis->grid()->setLayer(QLatin1String("grid"));
yAxis->grid()->setLayer(QLatin1String("grid"));
xAxis2->grid()->setLayer(QLatin1String("grid"));
yAxis2->grid()->setLayer(QLatin1String("grid"));
legend->setLayer(QLatin1String("legend"));
// create selection rect instance:
mSelectionRect = new QCPSelectionRect(this);
mSelectionRect->setLayer(QLatin1String("overlay"));
setViewport(rect()); // needs to be called after mPlotLayout has been created
replot(rpQueuedReplot);
}
QcustomPlot类中包含其他功能模块,来进行布局和绘制。需要理解各个类的关系,其中QCPLayer类继承于QObject类。QCPLayout是布局的基类。各个图表的绘制建立在坐标轴之上,其中坐标轴的绘制建立在布局窗口之上,一个QCustomPlot可以有多个布局窗口,一个布局窗口可以包含多个样式坐标轴。
类图
QCPLayerable是所有可绘制对象的基类,包括网格,坐标轴,装饰器,图标。关于具体的实现可以参见官网例子。
布局
先上一张官网图片
其中最外层是顶层窗口的边缘,每个布局和相邻的窗口都有默认的间隔(每个布局都有自己的索引,布局中可以插入子布局),同样布局中的元素也有默认的间隔(每个元素都有自己的索引)。正是由于源码这种灵活的实现,使得我们可以在布局上有极大的自主权。
数据关系
说一下,它内部是如何实现的。每个图表都有自己对应的数据类:
比如QCPGraph内部维护着QCPGraghData的数组。当我们进行绘制的时候,取出其中的数据进行每个点的绘制。同样当我们想获取某个区域,或者某个像素点的的数据时,可以先进行像素点到坐标轴(参见QCPAxis类函数)的数据转换,然后通过相应图表的接口函数获取当前区域或者点的值。如下
QCPDataSelection selection = graph->selection();
double sum = 0;
foreach (QCPDataRange dataRange, selection.dataRanges())
{
QCPGraphDataContainer::const_iterator begin = graph->data()->at(dataRange.begin()); // get range begin iterator from index
QCPGraphDataContainer::const_iterator end = graph->data()->at(dataRange.end()); // get range end iterator from index
for (QCPGraphDataContainer::const_iterator it=begin; it!=end; ++it)
{
// iterator "it" will go through all selected data points, as an example, we calculate the value average
sum += it->value;
}
}
double average = sum/selection.dataPointCount();
获取选中区域的数据,首先要设置选中属性。这个逻辑可以写在鼠标弹起事件中。
QCPGraphDataContainer::const_iterator it = graph->data()->constEnd();
QVariant details;
if (graph->selectTest(QPoint(123, 456), false, &details)) // QPoint could be e.g. event->pos() of a mouse event
{
QCPDataSelection dataPoints = details.value<QCPDataSelection>();
if (dataPoints.dataPointCount() > 0)
it = graph->data()->at(dataPoints.dataRange().begin());
}
// iterator "it" now carries the data point at pixel coordinates (123, 456), or constEnd if no data point was hit.
这个逻辑可以写在鼠标点击事件和鼠标弹起事件中