MPAndroidChart之LinChart(3)scale縮放



MPAndroidChart系列:


MPAndroidChart 之LineChart(1)

MPAndroidChart之LineChart(2)MarkerView

MPAndroidChart之LinChart(3)scale縮放


MPAndroidChart LineChart 縮放


MPAndroidChart使用版本:
[java] view plain copy
  1. compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'  

對於MPAndroidChart 折線圖的基本設置和屬性不懂得建議先去了解也可以看這篇MPAndroidChart 之LineChart(1)。如果對最新版本使用過並且有點熟悉的話,我們接下來看看最新版本中折線圖的縮放。

下面都是以LineChart爲例子 下面都是以LineChart爲例子 下面都是以LineChart爲例子

1、爲什麼要縮放?






答:看上面的截圖,借用官方的demo截的圖,第一幅是數據大小挺合適的時候,第二幅是數據超級多的時候,什麼感覺?明白縮放能幹嘛了嗎?當然是爲了解決上面的這種數據過多,一屏顯示不下問題,提高用戶體驗。

2、縮放是什麼樣子的?



答:上面就是對X軸進行了縮放一定比例,然後你就可以左右拖動了,就算數據再多也不怕密密麻麻了,當然你也可以對Y軸進行縮放,那樣就能上下拖動。

3、怎麼設置縮放?

答:我們可以   1)雙擊進行縮放;
                       2)手勢雙指縮放;
                       3)通過代碼進行設置縮放。


下面是在縮放前可以選擇設置的些我們需要設置的屬性:
[java] view plain copy
  1. //設置chart是否可以觸摸  
  2. mLineChart.setTouchEnabled(true);  
  3. //設置是否可以拖拽  
  4. mLineChart.setDragEnabled(true);  
  5. //設置是否可以縮放 x和y,默認true  
  6. mLineChart.setScaleEnabled(false);  
  7. //是否縮放X軸  
  8. mLineChart.setScaleXEnabled(true);  
  9. //X軸縮放比例  
  10. mLineChart.setScaleX(1.5f);  
  11. //Y軸縮放比例  
  12. mLineChart.setScaleY(1.5f);  
  13. //是否縮放Y軸  
  14. mLineChart.setScaleYEnabled(true);  
  15. //設置是否可以通過雙擊屏幕放大圖表。默認是true  
  16. mLineChart.setDoubleTapToZoomEnabled(false);  


一、例子

這裏以一種情況爲例:不能雙擊縮放,不能手勢手指縮放,只能通過代碼設置固定的縮放


基礎屬性的設置自己去了解,下面只是是縮放代碼設置

[java] view plain copy
  1. //縮放第一種方式  
  2. Matrix matrix = new Matrix();  
  3. //1f代表不縮放  
  4. matrix.postScale(3f, 1f);  
  5. mLineChart.getViewPortHandler().refresh(matrix, mLineChart, false);  
  6. //重設所有縮放和拖動,使圖表完全適合它的邊界(完全縮小)。  
  7. mLineChart.fitScreen();  
  8. //縮放第二種方式  
  9. mLineChart.getViewPortHandler().getMatrixTouch().postScale(3f, 1f);  

在最新版本里(當前是3.0.2),LineChart設置好基礎屬性後(如果基本設置和屬性不懂的可以看看MPAndroidChart 之LineChart(1)),在按上面代碼其中之一進行設置縮放3f後(這裏爲了簡單些,只縮放X軸),效果圖如下:





蛋疼的地方來了,大家系好安全帶,因爲下面全是解決縮放問題的東西,而且篇幅也不會很短,我儘可能寫得詳細點。


上面gif圖我代碼設置了X軸縮放是3f,單純的我以爲設置了縮放就能達到我想要的預期效果了,我認爲的預期效果如下圖





如果眼睛近視程度沒有很深的話,上面兩幅gif圖區別還是能看出來的,不過我有個疑問,爲什麼設置了縮放後(從這裏倒數第二幅gif圖)是那樣的,而不是我預期的那樣(從這裏倒數第一幅gif圖)? --- 哎 也無所謂了,太多需求不一樣吧。


實現了預期效果後看起來好簡單,可是走過的路確是那麼的艱難和漫長,官網沒有提供好點的demo,博客10篇9篇是轉載或亂七八糟的(也可能是我搜索關鍵詞不對?),搜索後除了坑爹就剩下蛋疼,一臉茫然無助,只好自己研究研究了。



二、存在問題


X軸設置縮放後問題:

1、我們看到圖表左右隨着你拖動而拖動了,但是X軸的豎線(X軸豎下來的3條線)是定死的,不管怎樣左右拖動,圖表是動了但是那3條豎線沒動,而是一直固定在那裏;

2、當左右拖動時,X軸的label(也就是字符串1、字符串2、字符串3....)只是改變了數值,也沒有和圖表一起聯動起來;

3、.....你來提....




三、解決問題



解決問題1步驟:


1、初始化chart基本屬性

首先是把LineChart基本屬性設置了,這裏如果還不清楚一些基本屬性設置的話,可以看看MPAndroidChart 之LineChart(1),然後記得要設置縮放的倍數,設置好後大概就像下面的gif圖一樣,當然,數據不一樣沒什麼影響,關鍵是效果一樣,OK;




2、從何處入手

從哪裏入手呢?在MPAndroidChart中有一個類“AxisRenderer”,翻譯過來Axis就是渲染器的意思,該類是幹什麼的,你可以點擊進去查看源碼有哪些方法並且做了什麼,也可以看官方給的doc中查看該類做什麼的,前往官方doc,如下圖,官方對“AxisRenderer”類說得很清楚了,下圖中是該累擁有的方法,並且說明了每個方法做了什麼(看不懂英文就去在線翻譯),其中看到1個我圈出來的方法,也就是我解決上面提到存在問題1的關鍵方法




我爲什麼知道該類?首先我是一步一步來的並且在我這篇MPAndroidChart 之LineChart(1)博客中,解決了幾個問題(主要是X軸第一個和最後一個label超出了Y軸的左右兩邊的軸線),自然而然就知道“AxisRenderer”類的存在並且有哪些方法,能幹嘛的了
繼承“AxisRenderer”有“XAxisRenderer”和“YAxisRenderer”2個類,這裏要解決的問題,只用到“XAxisRenderer”類(也就是解決問題1和2都是是屬於X軸的東西嘛),OK


下面是源碼AxisRenderer類裏的renderGridLines方法,方法裏面也是源碼代碼,經過打印測試,我把重要的地方都註釋有了

代碼:

 // x軸垂直豎線線
    @Override
    public void renderGridLines(Canvas c) {
        //源碼
        if (!mXAxis.isDrawGridLinesEnabled() || !mXAxis.isEnabled())
            return;

        int clipRestoreCount = c.save();
        c.clipRect(getGridClippingRect());

        //if保證mRenderGridLinesBuffer長度爲X軸標籤數乘以2(mAxis.mEntryCount爲label數)
        if (mRenderGridLinesBuffer.length != mAxis.mEntryCount * 2) {
            mRenderGridLinesBuffer = new float[mXAxis.mEntryCount * 2];
        }
        float[] positions = mRenderGridLinesBuffer;

        //mEntries數組裏面裝的是x軸setValueFormatter裏的value中從最小到大開始取的值
        for (int i = 0; i < positions.length; i += 2) {
            positions[i] = mXAxis.mEntries[i / 2];
            positions[i + 1] = mXAxis.mEntries[i / 2];
        }

        //用所有矩陣變換點數組。非常重要:保持矩陣順序“值觸摸偏移”時轉化。
        mTrans.pointValuesToPixel(positions);

        //設置畫筆屬性
        setupGridPaint();

        Path gridLinePath = mRenderGridLinesPath;
        gridLinePath.reset();

        for (int i = 0; i < positions.length; i += 2) {
            //根據positions數組裏的值畫出X軸豎線
            drawGridLine(c, positions[i], positions[i + 1], gridLinePath);
        }

        c.restoreToCount(clipRestoreCount);
    }


上面是官方畫X軸標籤豎線的源碼,可以看到思路:1)創建一個標籤數乘以2大小的數組;
                                                                             2)把需要畫的豎線的位置數據賦值給positions數組;
                                                                             3)調用mTrans.pointValuesToPixel(positions)方法把數組positions裏的數據變換;
                                                                             4)根據mTrans.pointValuesToPixel(positions)方法變換好的數據,調用drawGridLine方法,把豎線畫出來

ok,這樣畫出來的Chart,如果設置了X軸縮放大小,X軸的豎線是固定的,並不隨左右拖動而拖動(也就是豎線並沒有縮放),也就會存在前面說的問題1,如下gif圖



存在這樣的問題,原因就在於源碼是設置完數組positions數據後(這就等於固定了X軸豎線的位置了,同時也代表positions裏的數據是不縮放的值)才調用mTrans.pointValuesToPixel(positions)方法。


3、具體解決問題1

上面我應該算很清楚的註釋了,要解決問題1,最重要的是mTrans.pointValuesToPixel(positions)方法,點擊進入源碼我們看到如下圖




翻譯過來如下圖





恩,也就是說我想要豎線不固定(隨着X軸縮放豎線也縮放)那就得調下順序,得設置一條豎線的數據(每條線的數據就得自己摸索了)就調用mTrans.pointValuesToPixel(positions)方法,然後在調用drawGridLine方法畫出來(和源碼設置完所有豎線數據在調用mTrans.pointValuesToPixel(positions)和drawGridLine不一樣)。


想要看得懂後面的某段代碼,下面例子一定要看明白。。。。。。。。。。。。。。。。。。。。。。。。。。。。

每條線數據例子:

如下圖,有一根線,現在只知道第一個刻度和最後一個刻度值爲1和17,每個刻度是平均分的,現在我要求出打問號的另外3個刻度的值,怎麼算呢?




第一個問號:((17-1)/4)*1+1;                                                                      (最後一個值-第一個值)
第二個問號:((17-1)/4)*2+1;    ========》得出公式:每個刻度值 = [--------------------------------------]*下標+第一個值;
第三個問號:((17-1)/4)*3+1;                                                                          份數(多少份)


上面的例子和公式對於下面修改源碼中如何獲取每條線X軸座標值思路基本是一樣的。



恩,按照這個思路我們修改一下下源碼,先寫個類MyXAxisRenderer繼承XAxisRenderer類,然後重寫renderGridLines方法

代碼

import android.graphics.Canvas;
import android.graphics.Path;

import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.renderer.XAxisRenderer;
import com.github.mikephil.charting.utils.ViewPortHandler;
import com.mpandroidchartcsdn.mychart.MyLineChart;

import static android.R.attr.label;

public class MyXAxisRenderer extends XAxisRenderer {

    private MyLineChart myLineChart;

    public MyXAxisRenderer(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans, MyLineChart myLineChart) {
        super(viewPortHandler, xAxis, trans);
        this.myLineChart = myLineChart;
    }

    // x軸垂直線
    @Override
    public void renderGridLines(Canvas c) {
        //源碼拷貝過來
        if (!mXAxis.isDrawGridLinesEnabled() || !mXAxis.isEnabled())
            return;

        int clipRestoreCount = c.save();
        c.clipRect(getGridClippingRect());

        //if保證mRenderGridLinesBuffer長度爲X軸標籤數乘以2(mAxis.mEntryCount爲label數)
        if (mRenderGridLinesBuffer.length != mAxis.mEntryCount * 2) {
            mRenderGridLinesBuffer = new float[mXAxis.mEntryCount * 2];
        }
        float[] positions = mRenderGridLinesBuffer;

        //mEntries數組裏面裝的是x軸setValueFormatter裏的value中從最小到大開始取的值
        for (int i = 0; i < positions.length; i += 2) {
            positions[i] = mXAxis.mEntries[i / 2];
            positions[i + 1] = mXAxis.mEntries[i / 2];
        }

       /* //用所有矩陣變換點數組。非常重要:保持矩陣順序“值觸摸偏移”時轉化。
        mTrans.pointValuesToPixel(positions);*/

        //設置畫筆屬性
        setupGridPaint();

        Path gridLinePath = mRenderGridLinesPath;
        gridLinePath.reset();

        for (int i = 0; i < positions.length; i += 2) {
            /*
             *                        最後一個座標X座標值-第一個座標X座標值
             * X軸豎線X座標 = (---------------------------------------------)*(i/2)+第一個座標X座標值
             *                                  label-1
             */
            //下面4行是調整的代碼
            

            //第一個座標X座標值
            float fX = myLineChart.getData().getDataSets().get(0).getEntryForIndex(0).getX();
            //最後一個座標X座標值
            float eX = myLineChart.getData().getDataSets().get(0).getEntryForIndex(myLineChart.getData().getEntryCount() - 1).getX();
            positions[i] = ((eX - fX) / (mXAxis.mEntryCount - 1)) * (i / 2) + (fX);
            mTrans.pointValuesToPixel(positions);
            //根據positions數組裏的值畫出X軸豎線
            drawGridLine(c, positions[i], positions[i + 1], gridLinePath);
        }

        c.restoreToCount(clipRestoreCount);

    }






然後在創建一個MyLineChart

代碼

package com.mpandroidchartcsdn.mychart;

        import android.content.Context;
        import android.util.AttributeSet;

        import com.github.mikephil.charting.charts.LineChart;

/**
 * Created by tujingwu on 2017/5/4 
 * . 
 */

public class MyLineChart extends LineChart {


    public MyLineChart(Context context) {
        super(context);
    }

    public MyLineChart(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void init() {
        super.init();
        mXAxisRenderer = new MyXAxisRenderer(mViewPortHandler, mXAxis, mLeftAxisTransformer,this);
    }

} 



最後像使用LineChart一樣使用MyLineChart就行了,最後gif結果圖如下




ok,對於X軸的豎線已經解決了問題1,也達到了預期效果,對於問題2,也就是X軸的標籤,那就更好解決了,同樣的道理。






解決問題2步驟:

1、先寫個類MyXAxisRenderer繼承XAxisRenderer類,然後重寫drawLabels方法

代碼

package com.mpandroidchartcsdn.mychart;

import android.graphics.Canvas;

import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.renderer.XAxisRenderer;
import com.github.mikephil.charting.utils.MPPointF;
import com.github.mikephil.charting.utils.Transformer;
import com.github.mikephil.charting.utils.Utils;
import com.github.mikephil.charting.utils.ViewPortHandler;


public class MyXAxisRenderer extends XAxisRenderer {

    private MyLineChart myLineChart;

    public MyXAxisRenderer(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans, MyLineChart myLineChart) {
        super(viewPortHandler, xAxis, trans);
       /* this.mChart = mChart;
        this.mXAxis = xAxis;*/
        this.myLineChart = myLineChart;
    }

    @Override
    protected void drawLabels(Canvas c, float pos, MPPointF anchor) {
        //把代碼複製過來
        final float labelRotationAngleDegrees = mXAxis.getLabelRotationAngle();
        boolean centeringEnabled = mXAxis.isCenterAxisLabelsEnabled();

        float[] positions = new float[mXAxis.mEntryCount * 2];

        for (int i = 0; i < positions.length; i += 2) {

            // only fill x values
            if (centeringEnabled) {
                positions[i] = mXAxis.mCenteredEntries[i / 2];
            } else {
                positions[i] = mXAxis.mEntries[i / 2];
            }
        }

        // mTrans.pointValuesToPixel(positions);
        for (int i = 0; i < positions.length; i += 2) {

            float x = positions[i];
            if (mViewPortHandler.isInBoundsX(x)) {

                String label = mXAxis.getValueFormatter().getFormattedValue(mXAxis.mEntries[i / 2], mXAxis);

                if (mXAxis.isAvoidFirstLastClippingEnabled()) {

                    // avoid clipping of the last mXAxis.mEntryCount-1爲x軸標籤數
                    if (i == mXAxis.mEntryCount - 1 && mXAxis.mEntryCount > 1) {
                        float width = Utils.calcTextWidth(mAxisLabelPaint, label);

                        if (width > mViewPortHandler.offsetRight() * 2
                                && x + width > mViewPortHandler.getChartWidth())
                            x -= width / 2;
                        // avoid clipping of the first
                    } else if (i == 0) {

                        float width = Utils.calcTextWidth(mAxisLabelPaint, label);
                        x += width / 2;
                    }
                }
                drawLabel(c, label, positions[0], pos, anchor, labelRotationAngleDegrees);
            }
        }


    }

}


2、思路還是和解決問題1一樣,稍微修改一下下源碼,上面代碼修改後

代碼

import android.graphics.Canvas;

import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.renderer.XAxisRenderer;
import com.github.mikephil.charting.utils.MPPointF;
import com.github.mikephil.charting.utils.Utils;
import com.github.mikephil.charting.utils.ViewPortHandler;
import com.mpandroidchartcsdn.mychart.MyLineChart;

import static android.R.attr.x;

public class MyXAxisRenderer extends XAxisRenderer {

    private MyLineChart myLineChart;

    public MyXAxisRenderer(ViewPortHandler viewPortHandler, XAxis xAxis, Transformer trans, MyLineChart myLineChart) {
        super(viewPortHandler, xAxis, trans);
        this.myLineChart = myLineChart;
    }

    @Override
    protected void drawLabels(Canvas c, float pos, MPPointF anchor) {
        //把代碼複製過來
        final float labelRotationAngleDegrees = mXAxis.getLabelRotationAngle();
        boolean centeringEnabled = mXAxis.isCenterAxisLabelsEnabled();

        float[] positions = new float[mXAxis.mEntryCount * 2];

        for (int i = 0; i < positions.length; i += 2) {

            // only fill x values
            if (centeringEnabled) {
                positions[i] = mXAxis.mCenteredEntries[i / 2];
            } else {
                positions[i] = mXAxis.mEntries[i / 2];
            }
        }

        // mTrans.pointValuesToPixel(positions);

        for (int i = 0; i < positions.length; i += 2) {
            
            /*   
             *                  最後一個座標X座標值-第一個座標X座標值
             * X軸豎線X座標 = (---------------------------------------------)*(i/2)+第一個座標X座標值
             *                                  label-1
             */
            //下面四行是修改後的代碼

            //第一個座標X座標值
            float fX = myLineChart.getData().getDataSets().get(0).getEntryForIndex(0).getX();
            //最後一個座標X座標值
            float eX = myLineChart.getData().getDataSets().get(0).getEntryForIndex(myLineChart.getData().getEntryCount() - 1).getX();
            positions[i] = ((eX - fX) / (mXAxis.mEntryCount - 1)) * (i / 2) + (fX);
            mTrans.pointValuesToPixel(positions);
            if (mViewPortHandler.isInBoundsX(positions[i])) {

                String label = mXAxis.getValueFormatter().getFormattedValue(mXAxis.mEntries[i / 2], mXAxis);

                if (mXAxis.isAvoidFirstLastClippingEnabled()) {

                    // avoid clipping of the last mXAxis.mEntryCount-1爲x軸標籤數
                    if (i == mXAxis.mEntryCount - 1 && mXAxis.mEntryCount > 1) {
                        float width = Utils.calcTextWidth(mAxisLabelPaint, label);

                        if (width > mViewPortHandler.offsetRight() * 2
                                && x + width > mViewPortHandler.getChartWidth())
                            positions[i] -= width / 2;
                        // avoid clipping of the first
                    } else if (i == 0) {

                        float width = Utils.calcTextWidth(mAxisLabelPaint, label);
                        positions[i] += width / 2;
                    }
                }
                drawLabel(c, label, positions[i], pos, anchor, labelRotationAngleDegrees);
            }
        }

    }

}




3、創建一個MyLineChart

代碼

package com.mpandroidchartcsdn.mychart;

        import android.content.Context;
        import android.util.AttributeSet;

        import com.github.mikephil.charting.charts.LineChart;

/**
 * Created by tujingwu on 2017/5/4 
 * . 
 */

public class MyLineChart extends LineChart {


    public MyLineChart(Context context) {
        super(context);
    }

    public MyLineChart(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void init() {
        super.init();
        mXAxisRenderer = new MyXAxisRenderer(mViewPortHandler, mXAxis, mLeftAxisTransformer,this);
    }

} 


最後運行效果如下gif圖






這樣一來上面存在的2個問題都得的解決後,得到下面最終gif圖





總結:對於縮放最終得到解決,給我的一些思路還是來自http://blog.csdn.net/qqyanjiang/article/details/51442120這位兄弟的博客,雖然博客裏沒有怎麼詳細說,不過還是提供了一些思路,現在解決了上面我提到的2個問題,後面有時間會把聯動和蠟燭圖也寫了。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章