MPAndroidChart 笔记(LineChart 多个相同 x 不同 y 数据处理)

2,143 阅读7分钟
原文链接: blog.csdn.net

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画点的时候,设置每个点的颜色(设置每个点颜色不同)