MPAndroidChart讲解网上已经有很多比较好的文章了。我之前参考过觉得比较好的juejin.cn/post/684490…
(当然也没说其他文章不好)
基本用法这里就不多说了,网上可以找到大把这类文章。
这里我说说自己改源码的一些经历,(这里只说LineChart),如果对你有用可以看看。可能会比较啰嗦,有错别字什么的,谅解。。。。
(主要讲LineChart中数据有相同x,不用y的处理方法,没有什么高难度技术点,对遇到问题的人有点用,没遇到该问题的一文不值)
MPAndroidChart github地址github.com/PhilJay/MPA…
先来看一张图
01
1
.看到上图中橙色虚线没,当时我想找到设置自带属性吧他去掉。可惜没找到(如果有小伙伴找到可以告诉我),然后就去找它在哪里画的。
在renderer包下面有一个LineChartRenderer类,有一个drawHighlighted()方法,我们来看看源码
public void drawHighlighted(Canvas c, Highlight[] indices) {
LineData lineData =mChart.getLineData();
for(Highlight high : indices) {
ILineDataSet set = lineData.getDataSetByIndex(high.getDataSetIndex());
if(set ==null|| !set.isHighlightEnabled())
continue;
Entry e = set.getEntryForXPos(high.getX());
if(!isInBoundsX(e, set))
continue;
MPPointD pix =mChart.getTransformer(set.getAxisDependency()).getPixelsForValues(e.getX(), e.getY() *mAnimator
.getPhaseY());
high.setDraw((float) pix.x, (float) pix.y);
// draw the lines
drawHighlightLines(c, (float) pix.x, (float) pix.y, set);
}
}
然后找到哪里调用了这个方法,OK,在BarLineChartBase的onDraw()里面有一个
// if highlighting is enabled
if(valuesToHighlight()){
mRenderer.drawHighlighted(canvas,mIndicesToHighlight);
}
我们在看看valuesToHighlight()这个方法
public boolean valuesToHighlight() {
return mIndicesToHighlight==null||mIndicesToHighlight.length<=0
||mIndicesToHighlight[0] ==null?false:true;
}
这里只判断是mIndicesToHighlight,所以貌似是没有设置属性的地方去掉。这里最好的方法就是改改源码,自己加一个变量去控制画不画。怎么写我就不多说了。
(或者你把drawHighlightLines(c, (float) pix.x, (float) pix.y, set);注掉,永远不用)
2
.图01中的气泡,当我们点击或者移动的时候,气泡会显示出来,这个气泡的样子可以自定义。有一个叫MarkerView的类,基本用法这里不多说。我来说说我做的一个需求。
第一次进来的时候需要LineChart最后一个点的气泡显示处理啊。就是默认显示最后一点的气泡。点击其他点的时候默认气泡消失,气泡在点击那个点显示。其实就是气泡只能存在一个。当然,如果改源码的话也可以让多个同时存在,不过貌似没有这种需求。好,废话扯多了,直接查看源码。
Chart类里面有一个drawMarkers()方法,这个就是画气泡用的。在BarLineChartBase的onDraw()里面有调用。我们用LineChart是集成BarLineChartBase的,也就是说每次刷新LineChart的时候都会调用。
protected void drawMarkers(Canvas canvas) {
if (mMarkerView == null || !mDrawMarkerViews || !valuesToHighlight())
return;
for (int i = 0; i < mIndicesToHighlight.length; i++) {
Highlight highlight = mIndicesToHighlight[i];
IDataSet set = mData.getDataSetByIndex(highlight.getDataSetIndex());
Entry e = mData.getEntryForHighlight(mIndicesToHighlight[i]);
int entryIndex = set.getEntryIndex(e);
// make sure entry not null
if (e == null || entryIndex > set.getEntryCount() * mAnimator.getPhaseX())
continue;
float[] pos = getMarkerPosition(highlight);
// check bounds
if (!mViewPortHandler.isInBounds(pos[0], pos[1]))
continue;
// callbacks to update the content
mMarkerView.refreshContent(e, highlight);
mMarkerView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
mMarkerView.layout(0, 0, mMarkerView.getMeasuredWidth(),
mMarkerView.getMeasuredHeight());
if (pos[1] - mMarkerView.getHeight() <= 0) {
float y = mMarkerView.getHeight() - pos[1];
mMarkerView.draw(canvas, pos[0], pos[1] + y);
} else {
mMarkerView.draw(canvas, pos[0], pos[1]);
}
}
}
我们可以看到,这个方法是循环mIndicesToHighlight[]数据来draw的markerView的,第一次的时候因为没有点击任何点,所以mIndicesToHighlight[]肯定是没有值的,那么我们可以在初始化LineChart的时候给mIndicesToHighlight[]加一个值。
就这么简单么,当然没有。因为如果只是加一个值进去的话,当点击其他点的时候,默认设置点的气泡是不会消失的。原因在我们点击的时候有一个ChartTouchListener这个类,这个类记住了一个叫做mLastHighlighted的东西。光看名字就知道是啥意思了。所以我们还需要设置mLastHighlighted的值,Chart类里面正好有这个方法,setLastHighlighted()。
有人可能会问。mIndicesToHighlight[]怎么初始化的时候加值,他的类型是Highlight。Chart类里面有个方法,
public Highlight getHighlightByTouchPoint(float x,float y) {
if(mData==null) {
Log.e(LOG_TAG,"Can't select by touch. No data set.");
return null;
}else
returngetHighlighter().getHighlight(x, y);
}
参数x、y是屏幕的坐标,那么怎么Entry里面的x,y转成坐标x、y呢?
在Chart的lib包里面有一个工具类包。com.mpandroidchart.charts.utils
里面有一个Transformer类,在BarLineChartBase类里面有一个mLeftAxisTransformer和mRightAxisTransformer对象。
MPPointD mpPointD =mLeftAxisTransformer.getPixelsForValues(Entry.getX(),Entry.getY());
这样我们就得到了一个MPPointD,然后可以用上面的方法得到我们所需的Highlight。如下
Highlight highlight =null;
highlight = getHighlightByTouchPoint((float) mpPointD.x, (float) mpPointD.y);
这里我还要给highlight设置他的mDrawX、mDrawX。因为在找draw的时候在通过这两个值来定位位置的。这也是我之前遇到的坑。
这样 highlight.setDraw((float) mpPointD.x, (float) mpPointD.y);
OK,现在就可以了。
3
.LineChart在setData的时候,如果数据里面有些Entry的getX()是相等的,getY()又不相等的情况下。在draw线时候没有问题,无非就是话一条竖线,或者两个点一上一下。
但是。点击的时候MarkerView显示的地方会出错。。。我们先用Demo测试。看图
02
在demo里面的LineChartActivity1初始化数据的时候,x=20的时候,我加了两条数据
03
点击的时候,MarkerView只会出现在62上面,永远不会出现在31和47这两个点上。
这个原因可能是作者没有考虑到吧。怎么搞?还是查源码呗。
后来发现,是因为点击的时候只判断了X轴,没有判断Y轴。追代码—–
Chart类—–drawMarkers()—–Entry e =mData.getEntryForHighlight(mIndicesToHighlight[i]);
—-getEntryForHighlight()—-
return mDataSets.get(highlight.getDataSetIndex()).getEntryForXPos(highlight.getX());
看到没,getEntryForXPos()方法的参数只传了X。
getEntryForXPos()是调的DataSet类里面的方法。我们来看看DataSet里面的方法。
public T getEntryForXPos(float xPos, Rounding rounding) {
int index =getEntryIndex(xPos, rounding);
if(index > -1)
returnmValues.get(index);
return null;
}
主要是getEntryIndex()这个方法寻找index。
既然X判断的index是对的,那么我们只需要在getEntryIndex()后,在判断一下离Y最近的点的Entry就行了。方法可以参考getEntryIndex();最终返回index就好了。当然上面getEntryForXPos()里面参数就得传(x,y)了,这样才能拿y去比较
我们在来看看getEntryIndex()方法。
public int getEntryIndex(float xPos, Rounding rounding) {
if (mValues == null || mValues.isEmpty())
return -1;
int low = 0;
int high = mValues.size() - 1;
while (low < high) {
int m = (low + high) / 2;
float d1 = Math.abs(mValues.get(m).getX() - xPos);
float d2 = Math.abs(mValues.get(m + 1).getX() - xPos);
if (d2 <= d1) {
low = m + 1;
} else {
high = m;
}
}
if (high != -1) {
float closestXPos = mValues.get(high).getX();
if (rounding == Rounding.UP) {
if (closestXPos < xPos && high < mValues.size() - 1) {
++high;
}
} else if (rounding == Rounding.DOWN) {
if (closestXPos > xPos && high > 0) {
--high;
}
}
}
return high;
}
(注意:这里只是针对LineChart,因为LineChart和BarChart都是继承BarLineChartBase的,BarChart点击的时候也调用的这里,而BarChart的点击是不用判断Y的,所以如果BarChart也用改过的DataSet的话会出现问题)
使用的是二分法寻找最近的一个点。代码没问题。不过我们来关心下第二个参数Rounding。
public enum Rounding {
UP,
DOWN,
CLOSEST,
}
枚举这里只说UP和DOWN。开发过程中了解到,他们的作用是来获取屏幕最left到最right的值。如果按照我们刚才的情况,有多个X相同,Y不相同的数据,那么这里就有问题了。
二分法如果正好分到了几个前后X相同的值。那么getEntryIndex()里面的while循环的high可能就不对了,如果Rounding是UP的话可能前面还有值没有包含进来,DOWN的话就是后面的值没有包含进来。例如,现在有100条数据。里面有多个X相同的值。在getEntryIndex()的时候正好分到是几个X相同的值。假如size=50附近的几个值的X都相同。结果可能是size从0-50的数据draw了,后面的数据都没draw,或者相反(真实经历过)。好,改源码吧。。。
if (high != -1) {
float closestXPos = mValues.get(high).getX();
if (rounding == Rounding.UP) {
if (high < mValues.size() - 1) {
for (int i = high; i > mValues.size() - 1; i++) {
if (mValues.get(i).getX() <= xPos) {
high = i;
} else {
break;
}
}
}
} else if (rounding == Rounding.DOWN) {
if (high > 0) {
for (int i = high; i > 0; i--) {
if (mValues.get(i).getX() > xPos) {
high = i;
} else {
break;
}
}
}
}
}
一个循环。让UP的时候返回条件范围内最小index。DOWN的时候返回条件范围内最大index。这样就没问题了。
稍后补充:
4.LineChart画点的时候,设置每个点的颜色(设置每个点颜色不同)