QCustomPlot(三):图形绘制基础之样式修改
修改图形样式
图表
图形的外观由许多因素决定,所有这些因素都可以修改。以下是最重要的:
- 线型:(https://www.qcustomplot.com/doc.php/QCPGraph)->setLineStyle(..)。对于所有可能的线型,请参阅QCPGraph ::LineStyle文档或介绍页面上的线型演示屏幕截图。[graph]
-
线条笔:QPainter 框架提供的所有笔都可用,例如实线、虚线、点线、不同的宽度、颜色、透明度等。通过graph->setPen(..)
设置配置的笔。 - 散点符号:调用graph->setScatterStyle(..)以更改散点符号的外观。对于所有可能的散布样式,请参阅QCPScatterStyle文档或介绍页面上显示的散布样式演示屏幕截图。如果您不希望在数据点处显示任何散点符号,请将图形的散点样式设置为 。QCPScatterStyle::ssNone
- 在图形下或两个图形之间填充:QPainter 框架提供的所有画笔都可以用于图形填充:实心、各种图案、纹理、渐变、颜色、透明度等。通过graph->setBrush(..)设置配置的画笔。
坐标轴
轴的外观可以通过改变它们所用的笔和它们的标签使用的字体来修改。具体可以查看QCPAxis的文档。以下是最重要属性的快速摘要:setBasePen, setTickPen, setTickLength, setSubTickLength, setSubTickPen, setTickLabelFont, setLabelFont, setTickLabelPadding, setLabelPadding. 您可以使用 setRangeReversed反转轴(例如,使值从左到右减少而不是增加)。如果您想在轴端装饰(例如箭头),请使用setLowerEnding或setUpperEnding。
网格线
通过访问轴的相应QCPGrid实例来修改网格。例如,更改与左轴相连的水平网格线的外观是通过访问customPlot->yAxis->grid(). 网格线的外观基本上是它们所用的笔,可以通过yAxis->grid()->setPen()设置。刻度 0 处的网格线可以用不同的笔绘制,它可以配置为setZeroLinePen. 如果您不想用特殊的笔画零线,只需将其设置为Qt::NoPen,刻度 0 处的网格线将使用普通网格笔绘制。
子网格线默认设置为不可见。它们可以用grid()->setSubGridVisible(true) 激活。
例子
两个图的简单绘图
这是一个创建具有指数包络的衰减余弦函数图像的示例:
// 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)。
使用多个轴和更高级的样式进行绘图
现在,让我们看一个更复杂的示例,用于创建演示屏幕截图,其中包含四个轴上的五个图形、纹理填充、垂直误差线、图例、作为小数分隔符的点等。
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 的图形使用左轴 ( yAxis) 作为其键,底部轴 ( xAxis) 作为其值。因此,该图相对于左轴向上站立:
为了显示图 1 的误差线,我们创建了一个QCPErrorBars实例,它可以附加到其他绘图表(如QCPGraph)并为它们提供误差线。有关所用方法的进一步说明,请查看相应的文档。
绘制日期和时间数据
接下来,我们将看看如何绘制与日期和/或时间相关的数据。它基本上归结为在各自的轴上安装不同类型的轴QCPAxisTickerDateTime的ticker 。
// 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 日午夜 1 日以来的秒数处理,UTC(称为 Unix/Epoch 时间)。这也是您在调用日期/时间类QDateTime::toTime_t/或setTime_t时使用的单位。
对于亚秒级精度,轴ticker使用浮点数。因此,小于 1.0 的值表示相应的秒数。您可以使用QCPAxisTickerDateTime::dateTimeToKey和keyToDateTime和 QDateTime
之间进行转换,这与 Qt 版本无关(QDateTime::toMSecsSinceEpoch
仅在 Qt 4.7 中引入)。
图表之外:曲线、条形图、统计箱线图
到目前为止,我们只查看了图表。由于它们是如此占主导地位的用例,QCustomPlot 为它们提供了一个专门的接口。我们一直在使用它:QCustomPlot::addGraph、QCustomPlot::graph等等。但这还不是全部。QCustomPlot 有一个更通用的接口,用于在绘图内绘制数据的类,称为 Plottables。这个接口是围绕抽象基类QCPAbstractPlottable构建的。所有 Plottables 都派生自此类,也是熟悉的QCPGraph类。QCustomPlot 提供了许多其他的可绘图类:
- QCPGraph:这就是我们一直在使用的绘图类。将一系列数据点显示为具有不同线型、填充和散点的图形。
- QCPCurve:与 QCPGraph 类似,不同之处在于它用于显示参数曲线。与函数图不同,它们可能有循环。
- QCPBars:条形图。获取一系列数据点并用条形表示它们。如果绘图中有多个QCPBars绘图表,它们可以相互堆叠,如介绍页面上的屏幕截图所示。
- QCPStatisticalBox:统计箱线图。采用五数汇总(最小值、下四分位数、中位数、上四分位数、最大值)并将其表示为统计框。也可以显示异常值。
- QCPColorMap : 一个 2D 地图,它通过使用颜色渐变来可视化第三个数据维度。类QCPColorScale伴随此绘图表以可视化绘图中的数据比例。
- QCPFinancial:一个绘图表,可用于通过使用烛台或 OHLC 条来可视化例如股票价格的开盘价、最高价、最低价、收盘价信息。
- QCPErrorBars:这是一个特殊的绘图表,它附加到第二个绘图表,以允许在其他绘图表的数据点上显示误差线。
与图表不同,其他绘图表需要在new
QCustomPlot 之外创建。这意味着有addGraph函数的方式没有addCurve或addBars函数。绘图表应属于的 QCustomPlot 实例是从绘图表的构造函数中传递的轴推断出来的。QCustomPlot 然后取得绘图表的所有权。可以使用QCustomPlot::plottable(int index)访问现有的绘图表, 并且可以使用 QCustomPlot::plottableCount检索绘图中的绘图表总数(包括图形)
这是一个创建包含三个条形图的条形图的快速示例:
QCPBars *myBars = new QCPBars(customPlot->xAxis, customPlot->yAxis);
// now we can modify properties of myBars:
myBars->setName("Bars Series 1");
QVector<double> keyData;
QVector<double> valueData;
keyData << 1 << 2 << 3;
valueData << 2 << 4 << 8;
myBars->setData(keyData, valueData);
customPlot->rescaleAxes();
customPlot->replot();
有关其他绘图表的更多详细信息可以在示例项目和其他教程中找到。此外,每种可绘图类型在相应类的文档页面上都有详细描述。
当然,绝对有可能编写自己的绘图表以使任何数据看起来完全符合您的需要。您应该查看QCPAbstractPlottable文档以获取如何开始对其进行子类化的指南。您还可以查看现有的绘图表以了解它们是如何工作的。为此,建议先看看 QCPBars或QCPCurve开始。QCPGraph功能非常丰富,因此可能不适合作为起点。
QCustomPlot(一):基础
QCustomPlot(二):图形绘制基础