QCustomPlot(二)基本使用

1,268 阅读11分钟

2.QCustomPlot绘图基本使用

教程中使用customPlot作为指向QCustomPlot实例的指针。

如果您在QtCreator中进行了提升操作,您可能会通过ui->customPlot(或您给该小部件的名称)来访问相应的小部件。

你可以通过customPlot->addGraph()在绘图中创建一个新的图形。

然后,你可以为该图形分配一些数据点,例如通过customPlot->graph(0)->setData(..),其中使用两个QVector<double>来表示x和y(键和值)。QCustomPlot之所以使用键和值的术语而不是x和y,是为了在分配哪个轴具有什么角色方面提供更大的灵活性。因此,如果将左轴定义为"键轴",底部轴定义为"值轴",那么您可以在绘图的左侧绘制一个直立的图形。

默认情况下,QCustomPlot小部件具有四个轴:customPlot->xAxisyAxisxAxis2yAxis2,它们的类型为QCPAxis,对应于底部、左侧、顶部和右侧轴。它们的范围定义了当前可见的绘图部分:customPlot->xAxis->setRange(-1, 1)

要使对绘图的任何更改在屏幕上显示出来,调用customPlot->replot()。请注意,当小部件调整大小和触发内置用户交互时,将自动进行重新绘制。这样的用户交互包括使用鼠标拖动轴范围和使用鼠标滚轮进行缩放。

// generate some data:
QVector<double> x(101), y(101); // initialize with entries 0..100
for (int i=0; i<101; ++i)
{
  x[i] = i/50.0 - 1; // x goes from -1 to 1
  y[i] = x[i]*x[i]; // let's plot a quadratic function
}
// create graph and assign data to it:
customPlot->addGraph();
customPlot->graph(0)->setData(x, y);
// give the axes some labels:
customPlot->xAxis->setLabel("x");
customPlot->yAxis->setLabel("y");
// set axes ranges, so we see all data:
customPlot->xAxis->setRange(-1, 1);
customPlot->yAxis->setRange(0, 1);
customPlot->replot();

输出应该类似于下面显示的内容。

刻度步长和标签是由轴当前使用的轴刻度器自动选择的。这是QCPAxisTicker类型的实例,可以通过xAxis->ticker()访问。你可以通过xAxis->ticker()->setTickCount(6)调整刻度器尝试创建的近似刻度数。默认的轴刻度器非常适合简单的数值显示,但也有专门的类用于时间跨度、日历日期、类别、π(或其他符号单位)和对数轴等。请参阅QCPAxisTicker文档了解详细信息。

轴的刻度标签(数字)永远不会超出小部件边界,即使它们变得更宽。这是由于自动边距计算,默认情况下启用。如果刻度标签和轴标签需要更多的空间,它会使轴矩形缩小。如果您不希望边距自动确定,可以通过调用customPlot->axisRect()->setAutoMargins(QCP::msNone)来禁用此行为。然后,您可以通过customPlot->axisRect()->setMargins(..)手动调整边距。

2.1 改变外观

2.1.1 图形的外观

图形的外观由许多因素决定,所有这些因素都可以进行修改。以下是最重要的几个因素:

  • 线条样式:调用graph->setLineStyle(..)。有关所有可能的线条样式,请参阅QCPGraph::LineStyle文档或介绍页面上的线条样式演示截图。
  • 线条笔:所有QPainter框架提供的笔都可以使用,例如实线、虚线、点线、不同宽度、颜色、透明度等。通过graph->setPen(..)设置配置好的笔。
  • 散点符号:调用graph->setScatterStyle(..)来改变散点符号的外观。有关所有可能的散点符号样式,请参阅QCPScatterStyle文档或介绍页面上显示的散点符号演示截图。如果您不希望在数据点上显示任何散点符号,请将图形的散点样式设置为QCPScatterStyle::ssNone
  • 填充图形或两个图形之间的区域:QPainter框架提供的所有画刷都可以用于图形填充:实心、各种图案、纹理、渐变、颜色、透明度等。通过graph->setBrush(..)设置配置好的画刷。

2.1.2 轴的外观

通过更改轴绘制时使用的笔和标签使用的字体,可以修改轴的外观。查看QCPAxis文档应该是不言自明的。

以下是最重要的属性的简要总结:setBasePensetTickPensetTickLengthsetSubTickLengthsetSubTickPensetTickLabelFontsetLabelFontsetTickLabelPaddingsetLabelPadding

你可以使用setRangeReversed来反转轴(例如,使值从左到右减小而不是增加)。如果您希望在轴的端点上添加装饰(例如箭头),请使用setLowerEndingsetUpperEnding

2.1.3 网格线的外观

通过访问轴的相应QCPGrid实例来修改网格。例如,要更改与左轴关联的水平网格线的外观,可以通过访问customPlot->yAxis->grid()来完成。网格线的外观基本上是由绘制它们的笔决定的,可以通过yAxis->grid()->setPen()来设置。可以使用setZeroLinePen来配置刻度为0的网格线的笔。如果您不希望使用特殊的笔绘制零线,只需将其设置为Qt::NoPen,刻度为0的网格线将使用普通的网格笔绘制。

子网格线默认情况下是不可见的。可以使用grid()->setSubGridVisible(true)来激活它们。

2.2 例子

2.2.1 两个图形的简单绘图

以下是一个示例,它创建了余弦函数的衰减图像及其指数包络线的图像:

// add two new graphs and set their look:
customPlot->addGraph();
customPlot->graph(0)->setPen(QPen(Qt::blue)); // line color blue for first graph
customPlot->graph(0)->setBrush(QBrush(QColor(0, 0, 255, 20))); // first graph will be filled with translucent blue
customPlot->addGraph();
customPlot->graph(1)->setPen(QPen(Qt::red)); // line color red for second graph
// generate some points of data (y0 for first, y1 for second graph):
QVector<double> x(251), y0(251), y1(251);
for (int i=0; i<251; ++i)
{
  x[i] = i;
  y0[i] = qExp(-i/150.0)*qCos(i/10.0); // exponentially decaying cosine
  y1[i] = qExp(-i/150.0);              // exponential envelope
}
// configure right and top axis to show ticks but no labels:
// (see QCPAxisRect::setupFullAxesBox for a quicker method to do this)
customPlot->xAxis2->setVisible(true);
customPlot->xAxis2->setTickLabels(false);
customPlot->yAxis2->setVisible(true);
customPlot->yAxis2->setTickLabels(false);
// make left and bottom axes always transfer their ranges to right and top axes:
connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), customPlot->xAxis2, SLOT(setRange(QCPRange)));
connect(customPlot->yAxis, SIGNAL(rangeChanged(QCPRange)), customPlot->yAxis2, SLOT(setRange(QCPRange)));
// pass data points to graphs:
customPlot->graph(0)->setData(x, y0);
customPlot->graph(1)->setData(x, y1);
// let the ranges scale themselves so graph 0 fits perfectly in the visible area:
customPlot->graph(0)->rescaleAxes();
// same thing for graph 1, but only enlarge ranges (in case graph 1 is smaller than graph 0):
customPlot->graph(1)->rescaleAxes(true);
// Note: we could have also just called customPlot->rescaleAxes(); instead
// Allow user to drag axis ranges with mouse, zoom with mouse wheel and select graphs by clicking:
customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);

正如您所看到的,将填充应用于图形就像设置一个不是Qt::NoBrush的画刷一样简单。填充将从图形(这里是图形0)延伸到与键(这里是x)轴平行的零值线。如果我们想要在此图形和另一个图形之间创建一个通道填充,我们还需要调用graph->setChannelFillGraph(otherGraph)。要删除通道填充,只需将0作为另一个图形传递,填充将像以前一样延伸到零值线。要完全删除填充,请调用graph->setBrush(Qt::NoBrush)

2.2.2 使用多个轴和更高级的样式进行绘图

现在,让我们看一个更复杂的例子,创建包含五个图形在四个轴上的演示截图,其中包含纹理填充,垂直误差条,图例,小数分隔符等。

customPlot->setLocale(QLocale(QLocale::English, QLocale::UnitedKingdom)); // period as decimal separator and comma as thousand separator
customPlot->legend->setVisible(true);
QFont legendFont = font();  // start out with MainWindow's font..
legendFont.setPointSize(9); // and make a bit smaller for legend
customPlot->legend->setFont(legendFont);
customPlot->legend->setBrush(QBrush(QColor(255,255,255,230)));
// by default, the legend is in the inset layout of the main axis rect. So this is how we access it to change legend placement:
customPlot->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignBottom|Qt::AlignRight);
 
// setup for graph 0: key axis left, value axis bottom
// will contain left maxwell-like function
customPlot->addGraph(customPlot->yAxis, customPlot->xAxis);
customPlot->graph(0)->setPen(QPen(QColor(255, 100, 0)));
customPlot->graph(0)->setBrush(QBrush(QPixmap("./balboa.jpg"))); // fill with texture of specified image
customPlot->graph(0)->setLineStyle(QCPGraph::lsLine);
customPlot->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 5));
customPlot->graph(0)->setName("Left maxwell function");
 
// setup for graph 1: key axis bottom, value axis left (those are the default axes)
// will contain bottom maxwell-like function with error bars
customPlot->addGraph();
customPlot->graph(1)->setPen(QPen(Qt::red));
customPlot->graph(1)->setBrush(QBrush(QPixmap("./balboa.jpg"))); // same fill as we used for graph 0
customPlot->graph(1)->setLineStyle(QCPGraph::lsStepCenter);
customPlot->graph(1)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, Qt::red, Qt::white, 7));
customPlot->graph(1)->setName("Bottom maxwell function");
QCPErrorBars *errorBars = new QCPErrorBars(customPlot->xAxis, customPlot->yAxis);
errorBars->removeFromLegend();
errorBars->setDataPlottable(customPlot->graph(1));
 
// setup for graph 2: key axis top, value axis right
// will contain high frequency sine with low frequency beating:
customPlot->addGraph(customPlot->xAxis2, customPlot->yAxis2);
customPlot->graph(2)->setPen(QPen(Qt::blue));
customPlot->graph(2)->setName("High frequency sine");
 
// setup for graph 3: same axes as graph 2
// will contain low frequency beating envelope of graph 2
customPlot->addGraph(customPlot->xAxis2, customPlot->yAxis2);
QPen blueDotPen;
blueDotPen.setColor(QColor(30, 40, 255, 150));
blueDotPen.setStyle(Qt::DotLine);
blueDotPen.setWidthF(4);
customPlot->graph(3)->setPen(blueDotPen);
customPlot->graph(3)->setName("Sine envelope");
 
// setup for graph 4: key axis right, value axis top
// will contain parabolically distributed data points with some random perturbance
customPlot->addGraph(customPlot->yAxis2, customPlot->xAxis2);
customPlot->graph(4)->setPen(QColor(50, 50, 50, 255));
customPlot->graph(4)->setLineStyle(QCPGraph::lsNone);
customPlot->graph(4)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 4));
customPlot->graph(4)->setName("Some random data around\na quadratic function");
 
// generate data, just playing with numbers, not much to learn here:
QVector<double> x0(25), y0(25);
QVector<double> x1(15), y1(15), y1err(15);
QVector<double> x2(250), y2(250);
QVector<double> x3(250), y3(250);
QVector<double> x4(250), y4(250);
for (int i=0; i<25; ++i) // data for graph 0
{
  x0[i] = 3*i/25.0;
  y0[i] = qExp(-x0[i]*x0[i]*0.8)*(x0[i]*x0[i]+x0[i]);
}
for (int i=0; i<15; ++i) // data for graph 1
{
  x1[i] = 3*i/15.0;;
  y1[i] = qExp(-x1[i]*x1[i])*(x1[i]*x1[i])*2.6;
  y1err[i] = y1[i]*0.25;
}
for (int i=0; i<250; ++i) // data for graphs 2, 3 and 4
{
  x2[i] = i/250.0*3*M_PI;
  x3[i] = x2[i];
  x4[i] = i/250.0*100-50;
  y2[i] = qSin(x2[i]*12)*qCos(x2[i])*10;
  y3[i] = qCos(x3[i])*10;
  y4[i] = 0.01*x4[i]*x4[i] + 1.5*(rand()/(double)RAND_MAX-0.5) + 1.5*M_PI;
}
 
// pass data points to graphs:
customPlot->graph(0)->setData(x0, y0);
customPlot->graph(1)->setData(x1, y1);
errorBars->setData(y1err);
customPlot->graph(2)->setData(x2, y2);
customPlot->graph(3)->setData(x3, y3);
customPlot->graph(4)->setData(x4, y4);
// activate top and right axes, which are invisible by default:
customPlot->xAxis2->setVisible(true);
customPlot->yAxis2->setVisible(true);
// set ranges appropriate to show data:
customPlot->xAxis->setRange(0, 2.7);
customPlot->yAxis->setRange(0, 2.6);
customPlot->xAxis2->setRange(0, 3.0*M_PI);
customPlot->yAxis2->setRange(-70, 35);
// set pi ticks on top axis:
customPlot->xAxis2->setTicker(QSharedPointer<QCPAxisTickerPi>(new QCPAxisTickerPi));
// add title layout element:
customPlot->plotLayout()->insertRow(0);
customPlot->plotLayout()->addElement(0, 0, new QCPTextElement(customPlot, "Way too many graphs in one plot", QFont("sans", 12, QFont::Bold)));
// set labels:
customPlot->xAxis->setLabel("Bottom axis with outward ticks");
customPlot->yAxis->setLabel("Left axis label");
customPlot->xAxis2->setLabel("Top axis label");
customPlot->yAxis2->setLabel("Right axis label");
// make ticks on bottom axis go outward:
customPlot->xAxis->setTickLength(0, 5);
customPlot->xAxis->setSubTickLength(0, 3);
// make ticks on right axis go inward and outward:
customPlot->yAxis2->setTickLength(3, 3);
customPlot->yAxis2->setSubTickLength(1, 1);

正如您所看到的,您可以自由定义每个轴在图形中扮演的角色。例如,索引为0的图形将左侧轴(y轴)作为其键轴,底部轴(x轴)作为其值轴。因此,该图形相对于左侧轴是竖直向上的:

为了显示图形1的误差条,我们创建了一个QCPErrorBars实例,它可以附加到其他可绘制对象(如QCPGraph)并为它们提供误差条。有关所使用方法的进一步解释,请参阅相应的文档。

2.2.3 绘制日期和时间数据

接下来,我们将看一下如何绘制与日期和/或时间相关的数据。基本上,这涉及在相应的轴上安装一个类型为QCPAxisTickerDateTime的不同轴刻度器。

// set locale to english, so we get english month names:
customPlot->setLocale(QLocale(QLocale::English, QLocale::UnitedKingdom));
// seconds of current time, we'll use it as starting point in time for data:
double now = QDateTime::currentDateTime().toTime_t();
srand(8); // set the random seed, so we always get the same random data
// create multiple graphs:
for (int gi=0; gi<5; ++gi)
{
  customPlot->addGraph();
  QColor color(20+200/4.0*gi,70*(1.6-gi/4.0), 150, 150);
  customPlot->graph()->setLineStyle(QCPGraph::lsLine);
  customPlot->graph()->setPen(QPen(color.lighter(200)));
  customPlot->graph()->setBrush(QBrush(color));
  // generate random walk data:
  QVector<QCPGraphData> timeData(250);
  for (int i=0; i<250; ++i)
  {
    timeData[i].key = now + 24*3600*i;
    if (i == 0)
      timeData[i].value = (i/50.0+1)*(rand()/(double)RAND_MAX-0.5);
    else
      timeData[i].value = qFabs(timeData[i-1].value)*(1+0.02/4.0*(4-gi)) + (i/50.0+1)*(rand()/(double)RAND_MAX-0.5);
  }
  customPlot->graph()->data()->set(timeData);
}
// configure bottom axis to show date instead of number:
QSharedPointer<QCPAxisTickerDateTime> dateTicker(new QCPAxisTickerDateTime);
dateTicker->setDateTimeFormat("d. MMMM\nyyyy");
customPlot->xAxis->setTicker(dateTicker);
// configure left axis text labels:
QSharedPointer<QCPAxisTickerText> textTicker(new QCPAxisTickerText);
textTicker->addTick(10, "a bit\nlow");
textTicker->addTick(50, "quite\nhigh");
customPlot->yAxis->setTicker(textTicker);
// set a more compact font size for bottom and left axis tick labels:
customPlot->xAxis->setTickLabelFont(QFont(QFont().family(), 8));
customPlot->yAxis->setTickLabelFont(QFont(QFont().family(), 8));
// set axis labels:
customPlot->xAxis->setLabel("Date");
customPlot->yAxis->setLabel("Random wobbly lines value");
// make top and right axes visible but without ticks and labels:
customPlot->xAxis2->setVisible(true);
customPlot->yAxis2->setVisible(true);
customPlot->xAxis2->setTicks(false);
customPlot->yAxis2->setTicks(false);
customPlot->xAxis2->setTickLabels(false);
customPlot->yAxis2->setTickLabels(false);
// set axis ranges to show all data:
customPlot->xAxis->setRange(now, now+24*3600*249);
customPlot->yAxis->setRange(0, 60);
// show legend with slightly transparent background brush:
customPlot->legend->setVisible(true);
customPlot->legend->setBrush(QColor(255, 255, 255, 150));

传递给dateTicker->setDateTimeFormat()的字符串具有与传递给QDateTime::toString的字符串相同的日期格式选项,请参阅Qt文档。QCustomPlot中的所有日期/时间坐标都以自1970年1月1日午夜以来的秒数表示,使用的是UTC时间(也称为Unix/Epoch时间)。这也是在Qt日期/时间类上调用QDateTime::toTime_tsetTime_t时使用的单位。

为了获得亚秒级的准确性,轴刻度器使用浮点数。因此,小于1.0的值表示相应的秒的分数。您可以使用QCPAxisTickerDateTime::dateTimeToKey和keyToDateTime在浮点数Unix时间和QDateTime之间进行转换,而不受Qt版本的影响(Qt的QDateTime::toMSecsSinceEpoch仅在Qt 4.7中引入)。