一、概述
前邊已經寫了5篇對QCustomPlot的講解,看過上述的幾篇文章後,基本就能做一些簡單的使用了,但是如果想要做到高度的控制圖表,那麼座標軸將是很重要的一部分,因為座標軸就是圖表的一個參考系,沒有了參考系那麼一切都是天方夜譚。關於QCustomPlot的座標軸我還是會按照之前的套路,首先對比1.3.2版本和2.0.0beta版本,然後在深入的去分析座標軸使用。
二、歷史版本對比
首先我需要和大傢伙說明下,我個人覺著在QCustomPlot的定製過程中,座標軸定製算是比較困難的,因為座標軸如果要定製的話,那就是座標軸的刻度需要自己計算,如果這個時候相關的業務邏輯也影響座標軸的計算,那麼就更難計算了,呵呵。。。或許大傢伙可能也不會遇到這些問題,有興趣的同學也可以自己思考下。
1.3.2版本 | 2.0.0版本 | |
座標軸 |
1、QCPAxis:座標軸類,所有座標軸功能都在這一個類總實現,包括:刻度計算和繪製文字
|
1、QCPAxis:座標軸類,所有座標軸功能都在這一個類總實現,包括:刻度計算和繪製文字 2、預設刻度自動計算,負責計算大刻度和小刻度 3、如果需要外部計算刻度則處理ticksRequest請求 |
表1 1.3.2版本和2.0.0版本座標軸比較
下面我將針對2.0.0版本的座標軸刻度計算來加以解釋,為了方便起見,我只解釋QCPAxisTicker這個座標軸刻度計算基類,因為QCPAxis座標軸預設使用的就是這個類,其餘的座標軸刻度計算類比如QCPAxisTickerDateTime、QCPAxisTickerTime、QCPAxisTickerFixed、QCPAxisTickerText、QCPAxisTickerPi和QCPAxisTickerLog等都是根據不同業務需求,重新實現了vitural相關方法。
三、座標軸
1、QCPAxis,如下是QCPAxis的標頭檔案,我從中刪除了大量不需要註釋的部分,但是很是剩下許多,為了寫註釋方便所以我沒有把程式碼摺疊,有興趣的同學可以閱讀下其中的中文註釋,其實這個類僅僅是用來連線計算和繪製座標軸的一個類,也可以說是暴露給使用者的一個匯出類。
1 class QCP_LIB_DECL QCPAxis : public QCPLayerable 2 { 3 enum AxisType {//座標軸型別,在一個座標軸矩形QCPAxisRect中包含左、上、右和下四條座標軸 4 atLeft = 0x01 ///< <tt>0x01</tt> Axis is vertical and on the left side of the axis rect 5 , atRight = 0x02 ///< <tt>0x02</tt> Axis is vertical and on the right side of the axis rect 6 , atTop = 0x04 ///< <tt>0x04</tt> Axis is horizontal and on the top side of the axis rect 7 , atBottom = 0x08 ///< <tt>0x08</tt> Axis is horizontal and on the bottom side of the axis rect 8 }; 9 enum LabelSide {//座標軸刻度上的文字的位置,刻度線裡or外 10 lsInside ///< Tick labels will be displayed inside the axis rect and clipped to the inner axis rect 11 , lsOutside ///< Tick labels will be displayed outside the axis rect 12 }; 13 enum ScaleType {//座標軸型別,直線or對數線 14 stLinear ///< Linear scaling 15 , stLogarithmic ///< Logarithmic scaling with correspondingly transformed axis coordinates (possibly also \ref setTicker to a \ref QCPAxisTickerLog instance). 16 }; 17 enum SelectablePart {/座標軸可以被選中的部分 18 spNone = 0 ///< None of the selectable parts 19 , spAxis = 0x001 ///< The axis backbone and tick marks 20 , spTickLabels = 0x002 ///< Tick labels (numbers) of this axis (as a whole, not individually) 21 , spAxisLabel = 0x004 ///< The axis label 22 }; 23 24 explicit QCPAxis(QCPAxisRect *parent, AxisType type); 25 virtual ~QCPAxis(); 26 //所有的get介面已經被我刪除 看到對應的set介面,get介面的含義就不言而喻 27 // setters: 28 Q_SLOT void setScaleType(QCPAxis::ScaleType type);//設定座標軸型別 直線or對數 29 Q_SLOT void setRange(const QCPRange &range);//設定座標軸範圍 30 void setRange(double lower, double upper); 33 void setTicker(QSharedPointer<QCPAxisTicker> ticker);//設定座標軸計算刻度類,該引數是一個shared型智慧指標,因此可以被多個座標軸來同時使用 31 void setTicks(bool show);//是否顯示座標軸,如果不顯示座標軸,那麼網格線也就沒有啦,因為沒有了座標軸刻度 32 void setTickLabels(bool show);//是否顯示座標軸文字 33 void setTickLabelPadding(int padding);//設定座標走文字距離座標軸線距離 34 void setTickLabelFont(const QFont &font);//設定文字字型 35 void setTickLabelColor(const QColor &color);//設定文字顏色 36 void setTickLabelRotation(double degrees);//設定文字角度 37 void setTickLabelSide(LabelSide side);//設定文字在刻度線裡or外 38 void setNumberFormat(const QString &formatCode);//設定文字格式 39 void setNumberPrecision(int precision);//設定文字精度 40 void setTickLength(int inside, int outside = 0);//設定大刻度高度 41 void setTickLengthIn(int inside);//設定大刻度在裡邊長度 42 void setTickLengthOut(int outside);//設定大刻度在外邊長度 43 void setSubTicks(bool show);//設定是否顯示小刻度 44 void setSubTickLength(int inside, int outside = 0);//設定小刻度高度 45 void setSubTickLengthIn(int inside);//設定小刻度在座標軸線裡邊長度 46 void setSubTickLengthOut(int outside);//設定小刻度在座標軸線外邊長度 47 void setBasePen(const QPen &pen);//設定基礎線畫筆 基礎線是零線,即可以認為是座標軸刻度為0的線 48 void setTickPen(const QPen &pen);//設定大刻度畫筆 49 void setSubTickPen(const QPen &pen);//設定小刻度畫筆 50 void setLabelFont(const QFont &font);//設定座標軸名稱字型畫筆 51 void setLabelColor(const QColor &color);//設定座標軸名稱字型顏色 52 void setLabel(const QString &str);//設定座標軸名稱文字 53 void setLabelPadding(int padding);//設定座標軸名稱文字距離座標軸刻度線距離 54 void setPadding(int padding);//設定座標軸距離邊界距離 55 void setOffset(int offset);//設定偏移量 56 void setSelectedTickLabelFont(const QFont &font);//設定選中刻度文字時字型 57 void setSelectedLabelFont(const QFont &font);//設定選中座標軸名稱時字型 58 void setSelectedTickLabelColor(const QColor &color);//選中刻度文字時顏色 59 void setSelectedLabelColor(const QColor &color);//選中座標軸名稱時顏色 60 void setSelectedBasePen(const QPen &pen);//選中基礎線時畫筆 61 void setSelectedTickPen(const QPen &pen);//選中大刻度時畫筆 62 void setSelectedSubTickPen(const QPen &pen);//選中小刻度時畫筆 63 Q_SLOT void setSelectableParts(const QCPAxis::SelectableParts &selectableParts);//設定能選中項的型別 64 Q_SLOT void setSelectedParts(const QCPAxis::SelectableParts &selectedParts); 65 void setLowerEnding(const QCPLineEnding &ending);//設定座標軸小刻度端樣式 66 void setUpperEnding(const QCPLineEnding &ending);//座標軸大刻度端樣式 73 67 // non-property methods: 68 Qt::Orientation orientation() const { return mOrientation; }//座標軸朝向 69 int pixelOrientation() const { return rangeReversed() != (orientation() == Qt::Vertical) ? -1 : 1; } 70 void moveRange(double diff);//移動座標軸 71 void scaleRange(double factor);//按比例因子縮放 72 void scaleRange(double factor, double center);//按範圍縮放 81 void rescale(bool onlyVisiblePlottables = false);//重新適配座標軸範圍 73 double pixelToCoord(double value) const;//畫素到座標軸座標系 74 double coordToPixel(double value) const;//座標軸座標系到畫素 85 QList<QCPAbstractPlottable*> plottables() const;//所有的圖 75 QList<QCPGraph*> graphs() const;//所有的折線 76 QList<QCPAbstractItem*> items() const;//所有的示意項 92 77 protected: 78 AxisType mAxisType;//座標軸型別 79 QCPAxisRect *mAxisRect;//座標軸所在矩形116 80 // non-property members: 81 QCPGrid *mGrid;//網格120 QSharedPointer<QCPAxisTicker> mTicker;//座標軸刻度計算類 82 QVector<double> mTickVector;//大刻度 83 QVector<QString> mTickVectorLabels;//大刻度文字 84 QVector<double> mSubTickVector;//小刻度 85 bool mCachedMarginValid; 86 int mCachedMargin; 87 88 // introduced virtual methods: 89 virtual int calculateMargin(); 90 91 // reimplemented virtual methods: 92 virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const Q_DECL_OVERRIDE;//獲取預設的反鋸齒屬性 93 virtual void draw(QCPPainter *painter) Q_DECL_OVERRIDE;//畫座標軸 94 virtual QCP::Interaction selectionCategory() const Q_DECL_OVERRIDE;//選擇策略137 95 // non-virtual methods: 96 void setupTickVectors();//計算刻度 97 QPen getBasePen() const;//獲取基礎畫筆 98 QPen getTickPen() const;//獲取大刻度畫筆 99 QPen getSubTickPen() const;//獲取小刻度畫筆 100 QFont getTickLabelFont() const;//獲取刻度文字畫筆 101 QFont getLabelFont() const;//獲取座標軸名稱文字字型 102 QColor getTickLabelColor() const;//獲取大刻度文字顏色 103 QColor getLabelColor() const;..獲取座標軸名稱文字顏色 104 };
具體的繪製類其實是QCPAxisPainterPrivate類,這是一個私有類,從名字就可以看出,他是一個QCPAxis類的繪製私有類,事實確實如此。刻度計算類是QCPAxisTicker,這是一個刻度計算基類,也是QCPAxis預設使用的刻度計算類,當然了這個類還有一大堆子類,都是專門用於生成指定型別的座標軸。
2、QCPAxisTicker:刻度計算類,該類完成了大刻度、小刻度和大刻度文字的計算,供QCPAxis來呼叫繪製,其中generate方法是一個公有的虛方法,既可以被重寫,又可以被外部呼叫,QCPAxis座標軸就是呼叫該介面來重新計算刻度。
1 class QCP_LIB_DECL QCPAxisTicker 2 { 3 Q_GADGET 4 public: 5 enum TickStepStrategy//刻度生成策略 6 { 7 tssReadability ///< A nicely readable tick step is prioritized over matching the requested number of ticks (see \ref setTickCount) 8 , tssMeetTickCount ///< Less readable tick steps are allowed which in turn facilitates getting closer to the requested tick count 9 }; 10 11 QCPAxisTicker(); 12 virtual ~QCPAxisTicker(); 13 14 // setters: 15 void setTickStepStrategy(TickStepStrategy strategy);//設定刻度生成策略 16 void setTickCount(int count);//設定大刻度個數 有可能計算出來的刻度數不完全等於設定的刻度個數,取決於刻度生成策略 17 void setTickOrigin(double origin);//設定座標軸領刻度 18 19 // introduced virtual methods: 20 virtual void generate(const QCPRange &range, const QLocale &locale, QChar formatChar, int precision, QVector<double> &ticks, QVector<double> *subTicks, QVector<QString> *tickLabels); 21 22 protected: 23 // introduced virtual methods: 24 virtual double getTickStep(const QCPRange &range);//根據座標軸範圍計算步長 25 virtual int getSubTickCount(double tickStep);//根據步長計算自刻度個數 26 virtual QString getTickLabel(double tick, const QLocale &locale, QChar formatChar, int precision);//根據指定語言、文字格式和精度獲取文字 27 virtual QVector<double> createTickVector(double tickStep, const QCPRange &range);//生成大刻度 28 virtual QVector<double> createSubTickVector(int subTickCount, const QVector<double> &ticks);//生成小刻度 29 virtual QVector<QString> createLabelVector(const QVector<double> &ticks, const QLocale &locale, QChar formatChar, int precision);//生成刻度文字 30 31 // non-virtual methods: 32 void trimTicks(const QCPRange &range, QVector<double> &ticks, bool keepOneOutlier) const;//去除無效的刻度值 33 double pickClosest(double target, const QVector<double> &candidates) const;//該函式返回範圍內第一個不小於(大於或等於)指定target的值。 34 };
四、網格線
QCPGrid網格線,這個算是和QCPAxis座標軸類似的實現,和其他模組關係基本都不是很大,直接繼承自QCPLayerable,標頭檔案格式如下,同樣的,我刪除了其中無需註釋的一部分程式碼。
在QCustomPlot的原始碼設計中,一個QCPAxis座標軸對於一個QCPGrid,這同我之前理解的圖表繪製有些不大一樣,呵呵呵。。。但是QCustomPlot就是這麼幹了,如果想對網格線做一些控制,有時候從QCPAxis就可以做到,因為他們直接的資料在使用上還是比較依賴。
1 class QCP_LIB_DECL QCPGrid :public QCPLayerable 2 { 3 QCPGrid(QCPAxis *parentAxis); 4 5 // setters: 6 void setSubGridVisible(bool visible);//設定是否顯示自網格線 7 void setAntialiasedSubGrid(bool enabled);//設定子網格線是否反鋸齒 8 void setAntialiasedZeroLine(bool enabled);//設定零線(就是刻度值為0的線)是否反鋸齒 9 void setPen(const QPen &pen);//設定畫筆 10 void setSubGridPen(const QPen &pen);//設定子網格畫筆 11 void setZeroLinePen(const QPen &pen);//設定零線畫筆 12 13 protected: 14 bool mSubGridVisible;//子網格是否顯示標記 15 bool mAntialiasedSubGrid, mAntialiasedZeroLine;//子網格和零線是否反鋸齒標記 16 QPen mPen, mSubGridPen, mZeroLinePen;//這個就不用說了 17 QCPAxis *mParentAxis;//對於的座標軸,一個網格線對應一個座標軸 18 virtual void applyDefaultAntialiasingHint(QCPPainter *painter) const;//獲取預設的反鋸齒屬性 19 virtual void draw(QCPPainter *painter);//繪製網格線,內部呼叫drawGridLines和drawSubGridLines 20 21 // non-virtual methods: 22 void drawGridLines(QCPPainter *painter) const;//繪製網格線 23 void drawSubGridLines(QCPPainter *painter) const;//繪製子網格線 24 };
一般來說,網格線不需要重寫,頂多就是設定一個顏色,控制是否顯示,網格線的疏密成都市和座標軸刻度計算有關係的,因此關於網格線的計算我們就不要考慮了,下面我提供一個我自定義的刻度固定畫素計算類示例
五、簡單的示例
首先來看下效果,如圖1所示,當圖表放大時,y軸上的刻度間距是保持固定畫素的。
圖1 y軸固定畫素伸縮
如下是刻度計算類標頭檔案,這個類實現起來還是比較簡單的,根據螢幕畫素返回步長,每次步長都是按當前畫素比例下計算的。
1 class AxisFixedPixelTicker : public QCPAxisTicker 2 { 3 public: 4 AxisFixedPixelTicker(QCPAxis * axis); 5 ~AxisFixedPixelTicker(); 6 7 public: 8 void SetTickPixelStep(int pixel);//設定固定畫素 9 int GetTickPixelStep() const;//獲取固定畫素大小 10 11 protected: 12 //QCPAxisTicker 13 virtual double getTickStep(const QCPRange & range) override;//重寫父類方法,根據固定畫素返回步長值 14 15 private: 16 QScopedPointer<AxisFixedPixelTickerPrivate> d_ptr; 17 };
下面是重寫的父類介面getTickStep方法實現
1 double AxisFixedPixelTicker::getTickStep(const QCPRange & range) 2 { 3 if (d_ptr->m_pDependAxis) 4 { 5 bool vertical; 6 if (d_ptr->m_pDependAxis->axisType() == QCPAxis::atLeft 7 || d_ptr->m_pDependAxis->axisType() == QCPAxis::atRight) 8 { 9 vertical = true; 10 } 11 else 12 { 13 vertical = false; 14 } 15 16 int screenLength = vertical ? d_ptr->m_pDependAxis->axisRect()->rect().height() : d_ptr->m_pDependAxis->axisRect()->rect().width(); 17 return d_ptr->m_iPixel * range.size() / screenLength; 18 } 19 else 20 { 21 return __super::getTickStep(range); 22 } 23 }