[转] MPAndroidChart之LinChart(3)scale缩放
转自: https://blog.csdn.net/u014769864/article/details/72723180
MPAndroidChart系列:
MPAndroidChart 之LineChart(1)
MPAndroidChart之LineChart(2)MarkerView
MPAndroidChart之LinChart(3)scale缩放
MPAndroidChart LineChart 缩放
MPAndroidChart使用版本:
compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'
对于MPAndroidChart 折线图的基本设置和属性不懂得建议先去了解也可以看这篇 MPAndroidChart 之LineChart(1) 。如果对最新版本使用过并且有点熟悉的话,我们接下来看看最新版本中折线图的缩放。
下面都是以LineChart为例子 下面都是以LineChart为例子 下面都是以LineChart为例子
1、 为什么要缩放?
答:看上面的截图,借用官方的demo截的图,第一幅是数据大小挺合适的时候,第二幅是数据超级多的时候,什么感觉?明白缩放能干嘛了吗?当然是为了解决上面的这种数据过多,一屏显示不下问题,提高用户体验。
2、 缩放是什么样子的?
答:上面就是对X轴进行了缩放一定比例,然后你就可以左右拖动了,就算数据再多也不怕密密麻麻了,当然你也可以对Y轴进行缩放,那样就能上下拖动。
3、怎么设置缩放?
答:我们可以
- 双击进行缩放;
- 手势双指缩放;
- 通过代码进行设置缩放。
下面是在缩放前可以选择设置的些我们需要设置的属性:
//设置chart是否可以触摸
mLineChart.setTouchEnabled(true);
//设置是否可以拖拽
mLineChart.setDragEnabled(true);
//设置是否可以缩放 x和y,默认true
mLineChart.setScaleEnabled(false);
//是否缩放X轴
mLineChart.setScaleXEnabled(true);
//X轴缩放比例
mLineChart.setScaleX(1.5f);
//Y轴缩放比例
mLineChart.setScaleY(1.5f);
//是否缩放Y轴
mLineChart.setScaleYEnabled(true);
//设置是否可以通过双击屏幕放大图表。默认是true
mLineChart.setDoubleTapToZoomEnabled(false);
一、例子
这里以一种情况为例:不能双击缩放,不能手势手指缩放,只能通过代码设置固定的缩放
基础属性的设置自己去了解,下面只是是缩放代码设置
//缩放第一种方式
Matrix matrix = new Matrix();
//1f代表不缩放
matrix.postScale(3f, 1f);
mLineChart.getViewPortHandler().refresh(matrix, mLineChart, false);
//重设所有缩放和拖动,使图表完全适合它的边界(完全缩小)。
mLineChart.fitScreen();
//缩放第二种方式
mLineChart.getViewPortHandler().getMatrixTouch().postScale(3f, 1f);
在最新版本里(当前是3.0.2),LineChart设置好基础属性后(如果基本设置和属性不懂的可以看看 MPAndroidChart 之LineChart(1) ),http://www.ramlife.org/2021/12/05/479.html 在按上面代码其中之一进行设置缩放3f后(这里为了简单些,只缩放X轴),效果图如下:
蛋疼的地方来了,大家系好安全带,因为下面全是解决缩放问题的东西,而且篇幅也不会很短,我尽可能写得详细点。
上面gif图我代码设置了X轴缩放是3f,单纯的我以为设置了缩放就能达到我想要的预期效果了,我认为的预期效果如下图
如果眼睛近视程度没有很深的话,上面两幅gif图区别还是能看出来的,不过我有个疑问,为什么设置了缩放后(从这里倒数第二幅gif图)是那样的,而不是我预期的那样(从这里倒数第一幅gif图)? --- 哎 也无所谓了,太多需求不一样吧。
实现了预期效果后看起来好简单,可是走过的路确是那么的艰难和漫长,官网没有提供好点的demo,博客10篇9篇是转载或乱七八糟的(也可能是我搜索关键词不对?),搜索后除了坑爹就剩下蛋疼,一脸茫然无助,只好自己研究研究了。
二、存在问题
X轴设置缩放后问题:
- 我们看到图表左右随着你拖动而拖动了,但是X轴的竖线(X轴竖下来的3条线)是定死的,不管怎样左右拖动,图表是动了但是那3条竖线没动,而是一直固定在那里;
- 当左右拖动时,X轴的label(也就是字符串1、字符串2、字符串3....)只是改变了数值,也没有和图表一起联动起来;
- .....你来提....
三、解决问题
解决问题1步骤:
1、初始化chart基本属性
首先是把LineChart基本属性设置了,这里如果还不清楚一些基本属性设置的话,可以看看 MPAndroidChart 之LineChart(1) ,http://www.ramlife.org/2021/12/05/479.html 然后记得要设置缩放的倍数,设置好后大概就像下面的gif图一样,当然,数据不一样没什么影响,关键是效果一样,OK;
2、从何处入手
从哪里入手呢?在MPAndroidChart中有一个类“AxisRenderer”,翻译过来Axis就是渲染器的意思,该类是干什么的,你可以点击进去查看源码有哪些方法并且做了什么,也可以看官方给的doc中查看该类做什么的, 前往官方doc , https://javadoc.jitpack.io/com/github/PhilJay/MPAndroidChart/v3.0.2/javadoc/ 如下图,官方对“AxisRenderer”类说得很清楚了,下图中是该累拥有的方法,并且说明了每个方法做了什么(看不懂英文就去在线翻译),其中看到1个我圈出来的方法,也就是我解决上面提到存在问题1的关键方法
我为什么知道该类?首先我是一步一步来的并且在我这篇 ” MPAndroidChart 之LineChart(1) http://www.ramlife.org/2021/12/05/479.html 博客中,解决了几个问题(主要是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轴标签竖线的源码,可以看到思路:
- 创建一个标签数乘以2大小的数组;
- 把需要画的竖线的位置数据赋值给positions数组;
- 调用mTrans.pointValuesToPixel(positions)方法把数组positions里的数据变换;
- 根据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个问题,后面有时间会把联动和蜡烛图也写了。