Qt:QCustomPlot使用教程(二)——基本绘图

0、说明

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

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

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

1、基本用法

1.1、创建新画布

customPlot -> addGraph();

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

1.2、给画布分配数据点

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

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

1.3、轴

假设我们有两个QVector<double>,分别存放了一组点的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();

Qt:QCustomPlot使用教程(二)——基本绘图

 

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

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

2、改变样式

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

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

setBasePensetTickPensetTickLengthsetSubTickLengthsetSubTickPensetTickLabelFontsetLabelFontsetTickLabelPaddingsetLabelPadding.

我们可以用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);

结果:

Qt:QCustomPlot使用教程(二)——基本绘图

正如上文所写,为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);

结果:

Qt:QCustomPlot使用教程(二)——基本绘图

 

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

为了展示图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));

结果:

Qt:QCustomPlot使用教程(二)——基本绘图

 

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

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

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

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

3、图形之外:曲线、图标、统计图

目前为止,我们只应用了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上用于显示数据点的误差情况。
上一篇:2. tcp stream graph


下一篇:day09-----5-----FFmpeg filter补充之使用复杂的filter过滤视频