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();

 

 坐標軸刻度、間隔和顯示的數字是由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);

結果:

正如上文所寫,為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,則是以毫秒為單位,也可以使用這種方式構建更微小的時間精度。

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上用於顯示數據點的誤差情況。

不同於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<double> keyData;
QVector<double> valueData;
keyData << 1 << 2 << 3;
valueData << 2 << 4 << 8;
myBars->setData(keyData, valueData);
customPlot->rescaleAxes();
customPlot->replot();

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM