MPAndroidChart 折线图高亮线交点绘制解决方案(多线多交点)

3,083 阅读3分钟

背景和问题

项目中使用到了大量图表,于是采用了 MPAndroidChart 作为解决方案。不得不说,这个库非常的强大,项目中大部分需求都可以直接使用项目提供的方法解决,但是也有一些需求需要自己的定制,比如本文主题:折线图高亮线交点绘制(多线多交点)。

项目本身支持选择是否绘制所有点的图标,但是没有支持仅在选中高亮时显示交点图标。

image.png 然而设计师给出的图是这样的:

image.png 也就是仅在选中时绘制所有交点,并且交点的颜色和折线一致。

解决过程

为了更好的修改源码实现项目需求,我采用导入模块的方式引入项目,详情不表。

最开始我的想法是在绘制Marker时绘制交点,因为Marker绘制的位置正是交点的位置,只要重写draw方法就可以绘制一个交点。

于是我在自定义Marker里面增加了如下代码。

    @Override
    public void draw(Canvas canvas, float posX, float posY) {
        super.draw(canvas, posX, posY);

        Paint paint = new Paint();//绘制圆点的画笔
        paint.setAntiAlias(true);//去锯齿


        //绘制内圆
        paint.setColor(ContextCompat.getColor(mContext, R.color.green));
        canvas.drawCircle(posX, posY, QMUIDisplayHelper.dp2px(mContext, 5), paint);

        //绘制外圆(空心)
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(QMUIDisplayHelper.dp2px(mContext, 3));
        paint.setColor(ContextCompat.getColor(mContext, R.color.white));
        canvas.drawCircle(posX, posY, QMUIDisplayHelper.dp2px(mContext, 5), paint);
    }

于是就完成了单个点的绘制。 image.png 但是!问题来了!如果是图表有两条折线的话就悲剧了。因为在这个方法下拿到的坐标是当前点击选中点的位置,拿不到另一个交点的坐标,也就意味着没办法绘制另一个交点,因此放弃从marker突破。

终极方案

既然交点一定是在高亮线上的,那么能否在高亮线绘制时绘制交点呢?我们在LineChartRenderer类中可以看到绘制高亮线的方法是drawHighlighted在这个方法下既可以获取当前选中点的索引,也可以拿到 Chart 对象,那么就可以拿到所有 dataset 的交点坐标,我们只需要遍历所有交点并绘制出来即可最终实现多条线的多交点绘制。

话不多说,给出最终的解决方案代码,注释很详细。

1、在LineChartRenderer类中修改如下。

两个画笔用于绘制内圆和外部圆环
private final Paint mHighlightPointStokePaint = new Paint();
private final Paint mHighlightPointInnerPaint = new Paint();


@Override
public void drawHighlighted(Canvas c, Highlight[] indices) {

    LineData lineData = mChart.getLineData();
    int entryIndex = -1;

    for (Highlight high : indices) {

        ILineDataSet set = lineData.getDataSetByIndex(high.getDataSetIndex());

        if (set == null || !set.isHighlightEnabled())
            continue;

        Entry e = set.getEntryForXValue(high.getX(), high.getY());

        if (!isInBoundsX(e, set))
            continue;

        //记录点击索引
        entryIndex = set.getEntryIndex(e);
        MPPointD pix = mChart.getTransformer(set.getAxisDependency()).getPixelForValues(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);
    }

    if (entryIndex < 0) {
        return;
    }

    if (mChart instanceof BarLineChartBase) {
        BarLineChartBase chart = (BarLineChartBase) this.mChart;
        if (chart.isDrawHighlightPoint()) {
            mHighlightPointInnerPaint.reset();
            mHighlightPointInnerPaint.setAntiAlias(true);
            mHighlightPointInnerPaint.setStyle(Paint.Style.FILL);

            //交点外圈白环
            mHighlightPointStokePaint.reset();
            mHighlightPointStokePaint.setAntiAlias(true);
            mHighlightPointStokePaint.setStyle(Paint.Style.STROKE);
            mHighlightPointStokePaint.setStrokeWidth(chart.getHighLightPointStrokeWidth());
            mHighlightPointStokePaint.setColor(Color.WHITE);

            List<ILineDataSet> dataSets = lineData.getDataSets();
            for (ILineDataSet set : dataSets) {
                //遍历所有dataset
                if (entryIndex < set.getEntryCount()) {
                //避免数组越位
                    Entry e = set.getEntryForIndex(entryIndex);
                    MPPointD pix = mChart.getTransformer(set.getAxisDependency())
                            .getPixelForValues(e.getX(), e.getY() * mAnimator.getPhaseY());

                    drawHighlightPoint(c, (float) pix.x, (float) pix.y, chart, set);
                }
            }
        }
    }
}

private void drawHighlightPoint(Canvas c, float x, float y, BarLineChartBase chart, ILineDataSet set) {
    //点内圆的颜色和图表线条一致,且将颜色的不透明度调满!
    mHighlightPointInnerPaint.setColor(((255) << 24) | set.getColor());
    //绘制内圆
    c.drawCircle(x, y, chart.getHighLightPointInnerRadius(), mHighlightPointInnerPaint);
    //绘制外圆
    c.drawCircle(x, y, chart.getHighLightPointInnerRadius(), mHighlightPointStokePaint);
}

2、在 BarLineChartBase 类中增加三个配置。


/**
 * flag that indicates if draw highlight point is enabled or not
 */
protected boolean isDrawHighlightPoint = false;

/**
 * the highlight point inner radius (px)
 */
protected int mHighLightPointInnerRadius = 1;

/**
 * the highlight point stroke width (px)
 */
protected int mHighLightPointStrokeWidth = 1;


public boolean isDrawHighlightPoint() {
    return isDrawHighlightPoint;
}

public void setDrawHighlightPoint(boolean drawHighlightPoint) {
    isDrawHighlightPoint = drawHighlightPoint;
}

public int getHighLightPointInnerRadius() {
    return mHighLightPointInnerRadius;
}

public void setHighLightPointInnerRadius(int mHighLightPointStrokeWidth) {
    this.mHighLightPointInnerRadius = mHighLightPointStrokeWidth;
}

public int getHighLightPointStrokeWidth() {
    return mHighLightPointStrokeWidth;
}

public void setHighLightPointStrokeWidth(int mHighLightPointStrokeWidth) {
    this.mHighLightPointStrokeWidth = mHighLightPointStrokeWidth;
}

3、最终应用于chart。

//绘制高亮线交点
lineChart.setDrawHighlightPoint(true);
lineChart.setHighLightPointInnerRadius(Utils.dp2px(5, context));
lineChart.setHighLightPointStrokeWidth(Utils.dp2px(3, context));

最终实现的效果:

image.png