Qt:QCustomPlot使用教程(二)——基本绘图
阅读原文时间:2023年07月10日阅读:11

本节翻译总结自:Qt Plotting Widget QCustomPlot - Basic Plotting

本节内容是使用QCustomPlot进行基本绘图。

本节教程都使用customPlot这个变量,它是一个指向QCustomPlot实例的指针,当然,在我们的项目中,我们更可能是通过ui->customPlot来访问这些QCustomPlot实例。

1.1、创建新画布

customPlot -> addGraph();

每个Graph及其上的线构成一幅图。

1.2、给画布分配数据点

customPlot->graph(0)->setData(x,y)

x、y是大小相等的一组数据,其中x中存储的是横坐标,y中存储的是纵坐标。

1.3、轴

假设我们有两个QVector,分别存放了一组点的x和y坐标(Key与Value)。不过QCustomPlot更倾向于使用Key与Value而非x与y,这样可以更灵活地区分哪个轴具有什么样的功能。所以当我们定义左边轴为Key轴而底部轴为Value轴时,我们就可以沿着左边的轴绘制一幅直立的图。

QCustomPlot有4个轴customPlot->xAxis, yAxis, xAxis2, 和 yAxis2,它们都是QCPAxis类型的,分别对应下、左、上、右。

如果要设置轴的范围为(-1,1),可以用:

customPlot->xAxis->setRange(-1,1);

1.4、重绘

如果对画布做了任何修改,可以用

customPlot->replot();

进行重绘。

不过每当Widget被拉伸或者触发了内置用户交互时,都会自动进行重绘;这里的交互是指通过鼠标拖动坐标轴的范围、用鼠标滚轮缩放等。

1.5、例子

绘制一个y=x2的曲线:

QVector<double> x(101),y(101);  
for(int i=0;i<101;i++){  
    x\[i\]=i/50.0-1;//设置x的范围为-1~1  
    y\[i\]=x\[i\]\*x\[i\];  
}  
//创建画布,设置画布上的点数据  
ui->customPlot->addGraph();  
ui->customPlot->graph(0)->setData(x,y);  
//设置坐标轴标签  
ui->customPlot->xAxis->setLabel("x");  
ui->customPlot->yAxis->setLabel("y");  
//设置坐标轴范围,以便我们可以看到全部数据  
ui->customPlot->xAxis->setRange(-1,1);  
ui->customPlot->yAxis->setRange(0,1);  
ui->customPlot->replot();

坐标轴刻度、间隔和显示的数字是由axis ticker决定的,它是QCPAxisTicker类型的变量,通过xAxis->ticker()访问。

可以通过xAxis->ticker()->setTickCount(6)来调整坐标轴上显示的刻度数值的个数。默认情况下,这个个数是自适应的最少的个数。不过,也有一些特殊的情况,比如坐标轴是时间、日期、分类、对数坐标系的情况。具体可以看QCPAxisTicker

2.1、画布

2.1.1、线的样式

graph->setLineStyle(..)

有哪些线的样式,可以看QCPGraph::LineStyle

2.1.2、画笔

graph->setPen(..)

2.1.3、点的样式

graph->setScatterStyle(..)

有哪些点的样式,可以看QCPScatterStyle

2.1.4、填充色

graph->setBrush(..):填充该线和0刻度线围成的区域。

graph->setChannelFillGraph(otherGraph):填充两条线之间的区域;如果要移除线间填充,只需要把参数设置为0即可,这时填充的区域为该线与0刻度线围成的区域。

如果要完全移除区域填充,用graph->setBrush(Qt::NoBrush)

2.2、坐标轴

坐标轴多是通过改变画笔和字体进行修改的。可以看QCPAxis

这里给出一些比较重要的特性:

[setBasePen](https://www.qcustomplot.com/doc.php/setBasePen)[setTickPen](https://www.qcustomplot.com/doc.php/setTickPen)[setTickLength](https://www.qcustomplot.com/doc.php/setTickLength)[setSubTickLength](https://www.qcustomplot.com/doc.php/setSubTickLength)[setSubTickPen](https://www.qcustomplot.com/doc.php/setSubTickPen)[setTickLabelFont](https://www.qcustomplot.com/doc.php/setTickLabelFont)[setLabelFont](https://www.qcustomplot.com/doc.php/setLabelFont)[setTickLabelPadding](https://www.qcustomplot.com/doc.php/setTickLabelPadding)[setLabelPadding](https://www.qcustomplot.com/doc.php/setLabelPadding).

我们可以用setRangeReversed来颠倒一个坐标轴(例如按照从高到低的顺序绘制)。

如果想修饰坐标轴的尾部(例如添加箭头),可以用setLowerEndingsetUpperEnding

2.3、网格线

网格线是QCPGrid对象。

网格线是和坐标轴绑定的,水平网格线是与左坐标轴绑定的,通过customPlot->yAxis->grid()访问。网格线的外观由绘制它的画笔决定,通过yAxis->grid()->setPen()进行设置。

位于0刻度处的网格线可以用不同的画笔绘制,该画笔样式通过setZeroLinePen进行配置。如果我们不想用特殊的画笔绘制零刻度线,只需要把它设置为Qt::NoPen即可,这样0刻度线就和普通刻度线的绘制方法一样了。

子网格线默认是隐藏的,可以用grid()->setSubGridVisible(true)激活。

2.4、例1:两幅图叠加绘制

下边的代码是绘制一幅衰减的余弦曲线和它的指数包裹曲线,每条曲线即是一条Graph

//添加两幅图并填充内容  
//第一幅图  
ui->customPlot->addGraph();  
//线的颜色  
ui->customPlot->graph(0)->setPen(QPen(Qt::blue));  
//用半透明的蓝色填充  
ui->customPlot->graph(0)->setBrush(QBrush(QColor(0,0,255,20)));

//第二幅图  
ui->customPlot->addGraph();  
ui->customPlot->graph(1)->setPen(QPen(Qt::red));

//生成数据点  
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);//指数衰减的余弦曲线  
    y1\[i\]=qExp(-i/150.0);//指数包裹曲线  
}  
//配置上和右坐标轴来显式刻度,但是不显示数字  
ui->customPlot->xAxis2->setVisible(true);  
ui->customPlot->xAxis2->setTickLabels(false);  
ui->customPlot->yAxis2->setVisible(true);  
ui->customPlot->yAxis->setTickLabels(false);

//修改左和底坐标轴,使之与右和上坐标轴始终匹配  
connect(ui->customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), ui->customPlot->xAxis2, SLOT(setRange(QCPRange)));  
connect(ui->customPlot->yAxis, SIGNAL(rangeChanged(QCPRange)), ui->customPlot->yAxis2, SLOT(setRange(QCPRange)));

//为每幅图设置点  
ui->customPlot->graph(0)->setData(x,y0);  
ui->customPlot->graph(1)->setData(x,y1);

//使图0自适应范围  
ui->customPlot->graph(0)->rescaleAxes();  
//图1页一样,但是只允许放大,以免比图0小  
ui->customPlot->graph(1)->rescaleAxes(true);  
//注意,以上两步也可以用ui->customPlot->rescaleAxes();来代替

//允许用户用鼠标拖拉、缩放、选择任一幅图  
ui->customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);

结果:

正如上文所写,为Graph进行色彩填充,只需要用setBrush()就可以实现。

2.5、例2:多轴、多样式绘图

接下来,我们来看一个更复杂的例子,这个例子中包含了4个坐标轴上的4个Graph,纹理填充,垂直误差、图例、分割点等内容

QCustomPlot \* cp = ui->customPlot;  
QCustomPlot \* customPlot = cp;  
//设置区域,点号作为小数分隔符、逗号作为千位分隔符  
cp->setLocale(QLocale(QLocale::English,QLocale::UnitedKingdom));  
cp->legend->setVisible(true);  
//用MainWindow字体,减小字体大小  
QFont legendFont=font();  
legendFont.setPointSize(9);  
cp->legend->setFont(legendFont);  
cp->legend->setBrush(QBrush(QColor(255,255,255,230)));  
//默认情况下,图例是嵌入主框架之中,接下来演示如何修改它的布局  
cp->axisRect()->insetLayout()->setInsetAlignment(0,Qt::AlignBottom | Qt::AlignRight);

//配置第一幅图,Key轴是左,Value轴是底  
cp->addGraph(cp->yAxis,cp->xAxis);  
cp->graph(0)->setPen(QPen(QColor(255,100,0)));  
cp->graph(0)->setBrush(QBrush(QPixmap("./balboa.jpg")));  
cp->graph(0)->setLineStyle(QCPGraph::lsLine);  
cp->graph(0)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc,5));  
cp->graph(0)->setName("Left maxwell function");

//配置第二幅图,Key是底,Value是左  
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(cp->graph(1));

//配置第三幅图,Key是顶,Value是右  
customPlot->addGraph(customPlot->xAxis2, customPlot->yAxis2);  
customPlot->graph(2)->setPen(QPen(Qt::blue));  
customPlot->graph(2)->setName("High frequency sine");  
//配置第四幅图,轴与第三幅图相同  
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");  
//配置第五幅图,右为Key轴,顶为Value轴  
//第五幅图中包含一些随机扰动的点  
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");

//生成数据  
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;  
}

//为每幅图设置数据  
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);  
//激活顶和右坐标轴  
customPlot->xAxis2->setVisible(true);  
customPlot->yAxis2->setVisible(true);  
//设置显示数据的合适的范围  
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);  
//为顶轴设置刻度为pi相关刻度  
customPlot->xAxis2->setTicker(QSharedPointer<QCPAxisTickerPi>(new QCPAxisTickerPi));  
//添加标题布局  
customPlot->plotLayout()->insertRow(0);  
customPlot->plotLayout()->addElement(0, 0, new QCPTextElement(customPlot, "Way too many graphs in one plot", QFont("sans", 12, QFont::Bold)));  
//设置各个轴的标签  
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");  
//使底轴刻度向外延伸  
customPlot->xAxis->setTickLength(0, 5);  
customPlot->xAxis->setSubTickLength(0, 3);  
//使右轴刻度向内和向外延伸  
customPlot->yAxis2->setTickLength(3, 3);  
customPlot->yAxis2->setSubTickLength(1, 1);

结果:

正如结果所示,我们可以自由定义哪个轴占主导地位。

为了展示图2的误差线,我们构造了QCPErrorBars实例用以绘制其他Graph的误差线。

2.6、例3:绘制日期、时间数据

本例介绍如何绘制时间、日期相关的数。要实现这一目的,需要在每个轴上使用QCPAxisTickerDateTime作为轴刻度。

QCustomPlot \* cp = ui->customPlot;  
QCustomPlot \* customPlot = ui->customPlot;

cp->setLocale(QLocale(QLocale::English,QLocale::UnitedKingdom));  
//当前时间戳,用它作为起始点  
double now = QDateTime::currentDateTime().toSecsSinceEpoch();  
//设置随机数种子  
srand(8);  
//生成多幅graph  
for(int gi=0;gi<5;gi++){  
    cp->addGraph();  
    QColor color(20+200/4.0\*gi,70\*(1.6-gi/4.0),150,150);  
    cp->graph()->setLineStyle(QCPGraph::lsLine);  
    cp->graph()->setPen(QPen(color.lighter(200)));  
    cp->graph()->setBrush(QBrush(color));  
    //产生随机数据  
    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);  
        cp->graph()->data()->set(timeData);

    }  
    //配置底轴以显示日期而非数字  
    QSharedPointer<QCPAxisTickerDateTime>dateTicker(new QCPAxisTickerDateTime);  
    dateTicker->setDateTimeFormat("d.\_MMMM\\nyyyy");  
    cp->xAxis->setTicker(dateTicker);  
    //配置左轴  
    QSharedPointer<QCPAxisTickerText> textTicker(new QCPAxisTickerText);  
    textTicker->addTick(10,"a bit\\nlow");  
    textTicker->addTick(50,"quite/nhigh");  
    cp->yAxis->setTicker(textTicker);  
    //为左轴和底轴的刻度设置合适的字体  
    cp->xAxis->setTickLabelFont(QFont(QFont().family(),8));  
    cp->yAxis->setTickLabelFont(QFont(QFont().family(),8));  
    //设置轴标签  
    cp->xAxis->setLabel("Date");  
    cp->yAxis->setLabel("Random wobbly lines value");  
    //使顶轴和右轴可见,但是并不显示刻度值和标签  
    customPlot->xAxis2->setVisible(true);  
    customPlot->yAxis2->setVisible(true);  
    customPlot->xAxis2->setTicks(false);  
    customPlot->yAxis2->setTicks(false);  
    customPlot->xAxis2->setTickLabels(false);  
    customPlot->yAxis2->setTickLabels(false);  
    //设置轴范围以显示所有数据  
    cp->xAxis->setRange(now,now+24\*3600\*249);  
    cp->yAxis->setRange(0,60);  
    //显示图例,图例背景轻微透明  
    cp->legend->setVisible(true);  
    cp->legend->setBrush(QColor(255,255,255,150));

结果:

我们传入dateTicker->setDateTimeFormat()中的字符串,需要符合ISO8601时间格式,除了时间格式之外的其他字符都会原封不动的保留,而时间格式的字符会被正确填充。

需要注意的是,QCustomPlot处理的时间/日期坐标系,都是以时间戳0起始坐标点的,,单位是s。所以当我们构造时,也要以时间戳为横坐标,只是在显示的时候通过时间/日期占位符转化为指定的时间/日期。

如果精度是比秒更小的单元,那么可以用浮点数。我们可以用QCPAxisTickerDateTime::dateTimeToKeykeyToDateTime用于浮点Unix Time和QDateTime间的转换。

而对于QDateTime::toMSecsSinceEpoch,则是以毫秒为单位,也可以使用这种方式构建更微小的时间精度。

目前为止,我们只应用了Graph,因为Graph是我们使用QCustomPlot的主要内容,QCustomPlot为我们使用Graph提供了专门的接口,我们也一直在使用它们,比如QCustomPlot::addGraphQCustomPlot::graph等等。但是这些并不是全部。

QCustomPlot也有许多更通用的绘图接口,称为Plottable,这个接口是围绕抽象类QCPAbstractPlottable构建的。所有的Plottables都是从这个类继承而来,当然也包括了最熟悉的QCPGraph类。现在介绍一些QCustomPlot提供的Plottable类:

  • QCPGraph:这是我们本节经常用的一个Plottable类。用于点、线、填充的方式展示一系列的数据;
  • QCPCurve:与QCPgraph类似,不同之处在于它用于展示参数曲线。不同于函数Graph,使用这个类时可能有循环;
  • QCPBars:柱状图;
  • QCPStatisticalBox::箱型图;
  • QCPColorMap:通过使用颜色梯度将第三个维度可视化的2D图;
  • QCPFinancial: 用于可视化股价的Plottable;
  • QCPErrorBars: 一个特殊的Plottable,附加在另一个Plottable上用于显示数据点的误差情况。

不同于Graph,其他的Plottable需要用new进行构造,而不是用addCurve、addBars等函数创建。已经存在的Plottable可以通过QCustomPlot::plottable(int index)访问,plottable的数量可以用QCustomPlot::plottableCount访问。

下文是构造柱状图的例子:

QCPBars *myBars = new QCPBars(customPlot->xAxis, customPlot->yAxis);
// now we can modify properties of myBars:
myBars->setName("Bars Series 1");
QVector keyData;
QVector valueData;
keyData << 1 << 2 << 3; valueData << 2 << 4 << 8; myBars->setData(keyData, valueData);
customPlot->rescaleAxes();
customPlot->replot();

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章