MPAndroidChart(6)——LineChart实现 X
一. 目标效果
左图为需要实现的效果,右图为当前的效果,对比X Y轴,当前的图表未展示刻度,而且X轴在0的下方。
总结需要实现的功能
- X Y轴展示刻度
- X轴位置从原点(0,0)开始
二. 实现效果
因未找到相关方法设置显示类似直尺样式的刻度(若有相关方法可实现,请告诉我一下),所以只有修改下源码
修改源码之前首先自己在View中画出刻度,然后找到MPAndroidChart库X轴的绘制代码,将自己写的方法添加进去。
注:本文会将长短刻度线都绘制出来,不想要短刻度线,去掉绘制方法即可。绘制效果如下:
3.png
2.1 绘制刻度线
2.1.1 绘制方式
绘制刻度线有很多方式,在此简单说一下本文中使用的方式。
先画一条长竖线(长刻度线),然后平移画布一段距离(即刻度线间的间距),在画短刻度线(坐标轴未变,只是画布位置变了,至使表面上看起来线位置变了)
2.1.2 画线
手机坐标轴:
4.png
画刻度线:
新建MyScaleView 重写 onDraw()方法
public class MyScaleView extends View {
private Paint scalePaint;
public MyScaleView(Context context) {
this(context, null);
}
public MyScaleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MyScaleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
}
private void initPaint() {
scalePaint = new Paint();
scalePaint.setAntiAlias(true);
scalePaint.setColor(Color.RED);
scalePaint.setStrokeWidth(2);
scalePaint.setStyle(Paint.Style.FILL);
scalePaint.setDither(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawScale(canvas);
}
private void drawScale(Canvas canvas) {
int scaleGroup = 5; // 默认1个长刻度间隔4个短刻度,加起来一组5
int mScaleCount = 101; //刻度的总个数
canvas.save();
int startX = 30;
int startY = 500; //即在坐标轴(30,500)处开始画刻度线
for (int i = 0; i < mScaleCount; i++) {
canvas.save(); //记录画布状态
canvas.translate(10 * i, 0); //画布平移,即刻度线的间距
if (i == 0 || i % scaleGroup == 0) {
canvas.drawLine(startX, startY, startX, startY - 20, scalePaint);//画长刻度线
} else {
canvas.drawLine(startX, startY, startX, startY - 10, scalePaint);//画短刻度线
}
canvas.restore();//取出保存的状态
}
canvas.restore();
}
}
最后应用MyScaleView 效果如下
5.png
2.2 修改源码
步骤2.1.2中已经在实现了刻度线的绘制,现在只需要将绘制代码,加入MPAndroidChart中即可实现效果
2.2.1 下载源码
下载源码:https://github.com/PhilJay/MPAndroidChart/releases
点击 Downloads Source code(zip) 我下载的版本为v3.0.3 解压后里面的MPChartLib即为MPAndroidChart库源码
2.2.2 寻找X Y轴的绘制类
说下我的思路
首先找到 X轴 XAxis 并没有什么相关绘制代码
接着找到父类 AxisBase 发现 X Y 轴都继承于该类,那肯定和这类相关了
6.png
然后发现 AxisBase 类下定义了 相关属性 和 绘制图形相关
7.png
沿着 mGridLineWidth 属性找 mGridLineWidth --> getGridLineWidth() --> XAxisRenderer 与 YAxisRenderer 类
8.png
那可以确定 绘制X Y轴的类 就是 XAxisRenderer 与 YAxisRenderer !
9.png
而且在类中找到了对应的方法
2.2.3 绘制X轴刻度
观察平常的LineChart折线图,会发现我们需要的长刻度线与 网格线的位置相对应,所以可以模仿着网格线的绘制代码 绘制 刻度线
绘制网格线的源码如下
仿照上述两个方法,编写我们的刻度线绘制方法
首先,支持提供 方法 是否绘制刻度线 仿照 isDrawGridLinesEnabled() 找到其所在位置在package com.github.mikephil.charting.components; AxisBase类中 ,我们可以 设置是否绘制刻度线
/**
* 默认不绘制X Y轴刻度
*/
protected boolean mIsDrawScale = false;
public void setDrawScale(boolean mIsDrawScale) {
this.mIsDrawScale = mIsDrawScale;
}
public boolean isDrawScale() {
return mIsDrawScale;
}
然后回到 package com.github.mikephil.charting.renderer;包下 XAxisRenderer类中编写
/**
* 绘X轴制刻度线
* @param c
*/
public void renderScaleLines(Canvas c) {
if (!mXAxis.isDrawScale() || !mXAxis.isEnabled())
return;
if (mRenderGridLinesBuffer.length != mAxis.mEntryCount * 2) {
mRenderGridLinesBuffer = new float[mXAxis.mEntryCount * 2];
}
float[] positions = mRenderGridLinesBuffer;
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); //获得X轴点对应的像素点位置 即坐标系位置
for (int i = 0; i < positions.length - 2; i += 2) {
//计算X轴两个值之间的间距/5 = 画布偏移量 即 刻度间距 还是默认5个刻度一组
float offset = (positions[2] - positions[0]) / 5;
drawScale(c, positions[i], offset);
}
}
/**
* 绘制线
*/
protected void drawScale(Canvas canvas, float startX, float offset) {
float topY = mViewPortHandler.contentTop(); //顶部X轴所在的位置
float bottomY = mViewPortHandler.contentBottom(); //底部X轴所在的位置
canvas.save();
if (mXAxis.getPosition() == XAxisPosition.BOTTOM) { //X轴位置在下方时
for (int i = 0; i <= 5; i++) {
canvas.save();
canvas.translate(offset * i, 0);
if (i % 5 == 0) {
canvas.drawLine(startX, bottomY - 20, startX, bottomY, mAxisLinePaint);//画长刻度线
} else {
canvas.drawLine(startX, bottomY - 10, startX, bottomY, mAxisLinePaint);//画短刻度线
}
canvas.restore();
}
} else if (mXAxis.getPosition() == XAxisPosition.TOP) { //X轴位置在上方时
for (int i = 0; i <= 5; i++) {
canvas.save();
canvas.translate(offset * i, 0);
if (i % 5 == 0) {
canvas.drawLine(startX, topY + 20, startX, topY, mAxisLinePaint);//画长刻度线
} else {
canvas.drawLine(startX, topY + 10, startX, topY, mAxisLinePaint);//画短刻度线
}
canvas.restore();
}
} else if (mXAxis.getPosition() == XAxisPosition.BOTH_SIDED) { //上下都有X轴时
for (int i = 0; i <= 5; i++) {
canvas.save();
canvas.translate(offset * i, 0);
if (i % 5 == 0) {
//画长刻度线
canvas.drawLine(startX, topY + 20, startX, topY, mAxisLinePaint);//顶部X轴的刻度
canvas.drawLine(startX, bottomY - 20, startX, bottomY, mAxisLinePaint);//底部X轴的刻度
} else {
//画短刻度线
canvas.drawLine(startX, topY + 10, startX, topY, mAxisLinePaint);//顶部X轴的刻度
canvas.drawLine(startX, bottomY - 10, startX, bottomY, mAxisLinePaint);//底部X轴的刻度
}
canvas.restore();
}
}
canvas.restore();
}
写好了在哪里调用呢?
同样点击源码绘制网格线的方法 renderGridLines(Canvas c) 可以查看到
在 package com.github.mikephil.charting.charts;包下 BarLineChartBase类中 onDraw方法中,第 210行 调用了 绘制XY轴 网格线 直线 绘制方法
因此我们的方法也应在此被调用
mXAxisRenderer.renderAxisLine(canvas);
mAxisRendererLeft.renderAxisLine(canvas);
mAxisRendererRight.renderAxisLine(canvas);
/**********上面是源码的 下面是自己的方法***********************/
mXAxisRenderer.renderScaleLines(canvas);
因为在绘制网格线时,使用的 画笔 mAxisLinePaint 是绘制X轴直线的画笔 所以可以保证刻度线的颜色 粗细 与X轴同步
当前效果
2.2.4 绘制Y轴刻度
绘制Y轴与绘制X轴类似
在 package com.github.mikephil.charting.renderer;包下 YAxisRenderer类中
/**
* 绘制Y轴刻度线
*/
public void renderScaleLines(Canvas c) {
if (!mYAxis.isDrawScale() || !mYAxis.isEnabled())
return;
float[] positions = getTransformedPositions();
//因为 正常情况下 图表坐标轴在 左下方,所以此处 倒序 由下至上 绘制刻度
for (int i = positions.length; i > 2; i -= 2) {
float offset = (positions[i - 1] - positions[i - 3]) / 5; //偏移量
drawScale(c, positions[i - 3], offset);
}
}
protected void drawScale(Canvas canvas, float startY, float offset) {
float leftX = mViewPortHandler.contentLeft(); //Y轴在左边的位置
float rightX = mViewPortHandler.contentRight();//Y轴在右边的位置
canvas.save();
if (mYAxis.getAxisDependency() == AxisDependency.LEFT) { //Y轴位置在左边时
for (int i = 0; i <= 5; i++) {
canvas.save();
canvas.translate(0, offset * i);
if (i % 5 == 0) {
canvas.drawLine(leftX, startY, leftX + 20, startY, mAxisLinePaint);//画长刻度线
} else {
canvas.drawLine(leftX, startY, leftX + 10, startY, mAxisLinePaint);//画短刻度线
}
canvas.restore();
}
}
if (mYAxis.getAxisDependency() == AxisDependency.RIGHT) { //Y轴位置在右边时
for (int i = 0; i <= 5; i++) {
canvas.save();
canvas.translate(0, offset * i);
if (i % 5 == 0) {
canvas.drawLine(rightX, startY, rightX - 20, startY, mAxisLinePaint);//画长刻度线
} else {
canvas.drawLine(rightX, startY, rightX - 10, startY, mAxisLinePaint);//画短刻度线
}
canvas.restore();
}
}
canvas.restore();
}
然后在 前面调用X轴绘制刻度线的位置 调用绘制Y轴刻度线的方法
mXAxisRenderer.renderAxisLine(canvas);
mAxisRendererLeft.renderAxisLine(canvas);
mAxisRendererRight.renderAxisLine(canvas);
/**********上面是源码的 下面是自己的方法***********************/
//绘制X轴刻度
mXAxisRenderer.renderScaleLines(canvas);
//绘制Y轴刻度
mAxisRendererLeft.renderScaleLines(canvas);
mAxisRendererRight.renderScaleLines(canvas);
2.3 使用修改的源码
源码修改了,可以分别控制XY 轴是否绘制刻度线,在初始话设置 xAxis yAxis时设置即可,如下:
X轴绘制刻度,Y轴不绘制刻度
xAxis.setLabelCount(6, false);
//设置是否绘制刻度
xAxis.setDrawScale(true);
leftYAxis.setDrawScale(false);
XY轴都显示刻度
xAxis.setLabelCount(6, true);
//设置是否绘制刻度
xAxis.setDrawScale(true);
leftYAxis.setDrawScale(true);
效果图对比
12.png 13.png
左图 未设置绘制Y轴刻度,X轴设置非均分,最右不是完整的一个刻度,短刻度就无法绘制出来,如果只是炸死你长刻度还是无影响。
右图 X Y轴均绘制刻度,因X轴设置的是均分,都是完整的一个刻度,所以长短刻度都能绘制出来。
如果不想绘制短刻度线,可以在源码中添加一个boolean值,判断是否绘制短刻度即可,示例
boolean isDrawShortLine = false;
if (i % 5 == 0) {
canvas.drawLine(startX, bottomY - 20, startX, bottomY, mAxisLinePaint);//画长刻度线
} else if (isDrawShortLine){
canvas.drawLine(startX, bottomY - 10, startX, bottomY, mAxisLinePaint);//画短刻度线
}
效果图:
14.png
Y轴刻度与网格线重合了,难怪目标图设计 刻度线在外边,个人还是喜欢刻度线在里面,如果与网格线重合,不绘制即可。
如果刻度线非要绘制在外面,在绘制的时候 + - 符号对调一下即可,示例如下,XY轴绘制类似。
if (mXAxis.getPosition() == XAxisPosition.BOTTOM) { //X轴位置在下方时
for (int i = 0; i <= 5; i++) {
canvas.save();
canvas.translate(offset * i, 0);
boolean isDrawShortLine = false;
if (i % 5 == 0) {
//刻度线在图表内部
//canvas.drawLine(startX, bottomY - 20, startX, bottomY, mAxisLinePaint);//画长刻度线
//刻度线在图表外面
canvas.drawLine(startX, bottomY + 20, startX, bottomY, mAxisLinePaint);//画长刻度线
} else if (isDrawShortLine){
//canvas.drawLine(startX, bottomY - 10, startX, bottomY, mAxisLinePaint);//画短刻度线
canvas.drawLine(startX, bottomY + 10, startX, bottomY, mAxisLinePaint);//画短刻度线
}
canvas.restore();
}
如图:
15.png
2.4 修改X轴的位置
X,Y轴的刻度实现了,还差X轴位置的调整,需要从原点(0,0)开始,MPAndroidChart库为我们提供了几个方法可以在原点处绘制 一条线
leftYAxis.setDrawZeroLine(true); // draw a zero line
leftYAxis.setZeroLineColor(Color.GRAY);
leftYAxis.setZeroLineWidth(1f);
顺便在此修改一下Y轴的线条宽度、颜色。
leftYAxis.setAxisLineWidth(1f);
leftYAxis.setAxisLineColor(Color.GRAY);
此时的效果离目标图越来越近了
只需要将X轴的线条与刻度去掉,保留值的显示,然后在将绘制刻度线的方法,应用到与方法leftYAxis.setDrawZeroLine(true); 相关联的线条绘制类中即可。
经查找可以发现在 package com.github.mikephil.charting.renderer; 下的 YAxisRenderer类中可以找到protected void drawZeroLine(Canvas c) 绘制原点的线条,修改此方法即可。
在绘制X轴刻度的时候,我们知道必须要知道X轴值的数量,才能绘制对应数量的刻度线,而zeroLine 是在Y轴中绘制的,所以需要在传递一个XAxis对象给YAxisRenderer。
和前面一样,模仿drawZeroLine 在YAxisRenderer 类中新建方法,
/**
* 绘制原点线的刻度线
*
* @param c
* @param mXAxis
*/
public void drawZeroLineScale(Canvas c, XAxis mXAxis) {
if (!mYAxis.isDrawZeroLineEnabled()) {
return;
}
MPPointD pos = mTrans.getPixelForValues(0f, 0f);
if (mYAxis != null) {
float[] positions = new float[mXAxis.mEntryCount * 2];
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); //获得X轴点对应的像素点位置 即坐标系位置
for (int i = 0; i < positions.length - 2; i += 2) {
//计算X轴两个值之间的间距/5 = 画布偏移量 即 刻度间距 还是默认5个刻度一组
float offset = (positions[2] - positions[0]) / 5;
drawZeroScale(c, (float) pos.y, positions[i], offset);
}
}
}
/**
* 绘制原点线的刻度线
*
* @param zeroPosition 原点位置
* @param startX X轴起始位置
* @param offset 偏移量
*/
protected void drawZeroScale(Canvas canvas, float zeroPosition, float startX, float offset) {
canvas.save();
for (int i = 0; i <= 5; i++) {
canvas.save();
canvas.translate(offset * i, 0);
boolean isDrawShortLine = false;
if (i % 5 == 0) {
//刻度线在图表内部
//canvas.drawLine(startX, zeroPosition - 20, startX, zeroPosition, mAxisLinePaint);//画长刻度线
//刻度线在图表外面
canvas.drawLine(startX, zeroPosition + 20, startX, zeroPosition, mAxisLinePaint);//画长刻度线
} else if (isDrawShortLine) {
//canvas.drawLine(startX, zeroPosition - 10, startX, zeroPosition, mAxisLinePaint);//画短刻度线
canvas.drawLine(startX, zeroPosition + 10, startX, zeroPosition, mAxisLinePaint);//画短刻度线
}
canvas.restore();
}
canvas.restore();
}
然后在 package com.github.mikephil.charting.charts;包下的 BarLineChartBase 第 210行左右 调用我们新增的方法
/**********上面是源码的 下面是自己的方法***********************/
//绘制X轴刻度
mXAxisRenderer.renderScaleLines(canvas);
//绘制Y轴刻度
mAxisRendererLeft.renderScaleLines(canvas);
mAxisRendererRight.renderScaleLines(canvas);
//绘制原点线的刻度线
mAxisRendererLeft.drawZeroLineScale(canvas, mXAxis);
mAxisRendererRight.drawZeroLineScale(canvas, mXAxis);
源码修改部分完成。
然后在将X轴设置一下,在改下颜色就完成目标图的实现。
//设置是否绘制刻度
xAxis.setDrawScale(false);
//是否绘制X轴线
xAxis.setDrawAxisLine(false);
最终效果图如下
17.png
三 总结
修改源码时,最好是新增方法,而不是在原有的方法中修改,改了一个地方可能会导致其他地方出现问题,而新增方法并未修改到原有的东西。
MPAndroidChart 源码
转载链接:https://blog.csdn.net/ww897532167/article/details/78520548