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、線的樣式
有哪些線的樣式,可以看QCPGraph::LineStyle。
2.1.2、畫筆
graph->setPen(..)
2.1.3、點的樣式
有哪些點的樣式,可以看QCPScatterStyle
2.1.4、填充色
graph->setBrush(..):填充該線和0刻度線圍成的區域。
graph->setChannelFillGraph(otherGraph):填充兩條線之間的區域;如果要移除線間填充,只需要把參數設置為0即可,這時填充的區域為該線與0刻度線圍成的區域。
如果要完全移除區域填充,用graph->setBrush(Qt::NoBrush)
2.2、坐標軸
坐標軸多是通過改變畫筆和字體進行修改的。可以看QCPAxis。
這里給出一些比較重要的特性:
setBasePen
, setTickPen
, setTickLength
, setSubTickLength
, setSubTickPen
, setTickLabelFont
, setLabelFont
, setTickLabelPadding
, setLabelPadding
.
我們可以用setRangeReversed來顛倒一個坐標軸(例如按照從高到低的順序繪制)。
如果想修飾坐標軸的尾部(例如添加箭頭),可以用setLowerEnding或setUpperEnding。
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::dateTimeToKey和keyToDateTime用於浮點Unix Time和QDateTime間的轉換。
而對於QDateTime::toMSecsSinceEpoch,則是以毫秒為單位,也可以使用這種方式構建更微小的時間精度。
3、圖形之外:曲線、圖標、統計圖
目前為止,我們只應用了Graph,因為Graph是我們使用QCustomPlot的主要內容,QCustomPlot為我們使用Graph提供了專門的接口,我們也一直在使用它們,比如QCustomPlot::addGraph、QCustomPlot::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();