MPAndroidChart系列:
MPAndroidChart之LineChart(2)MarkerView
MPAndroidChart之LinChart(3)scale縮放
MPAndroidChart LineChart 縮放
MPAndroidChart使用版本:
對於MPAndroidChart 折線圖的基本設置和屬性不懂得建議先去了解也可以看這篇MPAndroidChart
之LineChart(1)。如果對最新版本使用過並且有點熟悉的話,我們接下來看看最新版本中折線圖的縮放。
下面都是以LineChart爲例子 下面都是以LineChart爲例子 下面都是以LineChart爲例子
1、爲什麼要縮放?
答:看上面的截圖,借用官方的demo截的圖,第一幅是數據大小挺合適的時候,第二幅是數據超級多的時候,什麼感覺?明白縮放能幹嘛了嗎?當然是爲了解決上面的這種數據過多,一屏顯示不下問題,提高用戶體驗。
2、縮放是什麼樣子的?
答:上面就是對X軸進行了縮放一定比例,然後你就可以左右拖動了,就算數據再多也不怕密密麻麻了,當然你也可以對Y軸進行縮放,那樣就能上下拖動。
3、怎麼設置縮放?
答:我們可以 1)雙擊進行縮放;
2)手勢雙指縮放;
3)通過代碼進行設置縮放。
下面是在縮放前可以選擇設置的些我們需要設置的屬性:
一、例子
這裏以一種情況爲例:不能雙擊縮放,不能手勢手指縮放,只能通過代碼設置固定的縮放
基礎屬性的設置自己去了解,下面只是是縮放代碼設置
在最新版本里(當前是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個問題,後面有時間會把聯動和蠟燭圖也寫了。