MPAndroidChart - 是否可以控制图表元素的 z-index?

MPAndroidChart - is it possible to control z-index of the chart elements?

我想在我的 MPAndroidChart 中按以下顺序绘制(从下到上):

  1. 数据连接线
  2. 极限线
  3. 数据点

可能吗?我知道方法 com.github.mikephil.charting.components.AxisBase#setDrawLimitLinesBehindData。除一种情况外,它按预期工作。当所有数据点的Y值都相同时,效果为:

或者这个:

我希望它看起来像:

前2张图片来自MPAndroidChartAndroid图库。第 3 个来自库的 iOS 端口:Charts

我查看了 Android and iOS 版本的顺序或绘制图表,它们看起来一样。

问题:

  1. 是否可以控制绘制顺序?
  2. 系统版本之间有什么区别?
  3. 是否有任何其他开源库可以做到这一点?

附加信息:所有图像、线条、圆圈均由库绘制,未使用自定义图像。

类似于a previous answer here,没有public API直接设置各种绘图功能的z-index。

相反,组件在 canvas 上按顺序绘制,后面的组件绘制在前面的组件之上。这意味着你改变渲染顺序,你可以改变 z-index。

您说您想要以下绘图顺序:

  1. Data connecting line
  2. Limit line
  3. Data points

让我们在源代码中找到处理每个问题的方法:

  1. 数据连接线是在LineChartRenderer里面画的方法叫:

    protected drawLinear(Canvas c, ILineDataSet dataSet)
    
  2. 限制线画在里面XAxisRenderer叫做:

    public void renderLimitLines(Canvas c)
    
  3. 数据点(圆圈)在 LineChartRenderer 内部绘制,方法如下所示:

    public void drawExtras(Canvas c)
    

这三个方法的调用顺序在 BarLineChartBase 中由 Android 的 View:

覆盖的方法中确定
    protected onDraw(Canvas canvas);

因此,要获得您想要的顺序,您只需重新安排上述 3 个方法在 onDraw(Canvas canvas):

中的调用顺序

下面是应该满足要求的自定义折线图的完整代码。按照设计,您仍然需要调用:

com.github.mikephil.charting.components.AxisBase#setDrawLimitLinesBehindData

但如果您愿意,您可以轻松删除 3 个 if 语句并硬编码顺序。

CustomZIndexBarLineBase.java

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;

import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.data.LineData;

/**
 * Created by David on 11/01/2017.
 */

public class CustomZIndexLineChartBase extends BarLineChartBase<LineData> {

    public CustomZIndexLineChartBase(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public CustomZIndexLineChartBase(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomZIndexLineChartBase(Context context) {
        super(context);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mData == null)
            return;

        // execute all drawing commands
        drawGridBackground(canvas);

        if (mAxisLeft.isEnabled())
            mAxisRendererLeft.computeAxis(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisMaximum, mAxisLeft.isInverted());
        if (mAxisRight.isEnabled())
            mAxisRendererRight.computeAxis(mAxisRight.mAxisMinimum, mAxisRight.mAxisMaximum, mAxisRight.isInverted());
        if (mXAxis.isEnabled())
            mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false);

        mXAxisRenderer.renderAxisLine(canvas);
        mAxisRendererLeft.renderAxisLine(canvas);
        mAxisRendererRight.renderAxisLine(canvas);

        if (mAutoScaleMinMaxEnabled) {
            autoScale();
        }

        mXAxisRenderer.renderGridLines(canvas);
        mAxisRendererLeft.renderGridLines(canvas);
        mAxisRendererRight.renderGridLines(canvas);

        if (mXAxis.isDrawLimitLinesBehindDataEnabled())
            mXAxisRenderer.renderLimitLines(canvas);

        if (mAxisLeft.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererLeft.renderLimitLines(canvas);

        if (mAxisRight.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererRight.renderLimitLines(canvas);

        int clipRestoreCount = canvas.save();
        canvas.clipRect(mViewPortHandler.getContentRect());

        mRenderer.drawData(canvas); //NOTE: draws line between points


        if (valuesToHighlight())
            mRenderer.drawHighlighted(canvas, mIndicesToHighlight);

        canvas.restoreToCount(clipRestoreCount);

        //NOTE: draws limit line
        if (!mXAxis.isDrawLimitLinesBehindDataEnabled())
            mXAxisRenderer.renderLimitLines(canvas);

        if (!mAxisLeft.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererLeft.renderLimitLines(canvas);

        if (!mAxisRight.isDrawLimitLinesBehindDataEnabled())
            mAxisRendererRight.renderLimitLines(canvas);

        mRenderer.drawExtras(canvas); //NOTE: draws circles

        mXAxisRenderer.renderAxisLabels(canvas);
        mAxisRendererLeft.renderAxisLabels(canvas);
        mAxisRendererRight.renderAxisLabels(canvas);

        if (isClipValuesToContentEnabled()) {
            clipRestoreCount = canvas.save();
            canvas.clipRect(mViewPortHandler.getContentRect());

            mRenderer.drawValues(canvas);

            canvas.restoreToCount(clipRestoreCount);
        } else {
            mRenderer.drawValues(canvas);
        }

        mLegendRenderer.renderLegend(canvas);

        drawDescription(canvas);

        drawMarkers(canvas);
    }
}

CustomZIndexLineChart.java

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;

import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.interfaces.dataprovider.LineDataProvider;
import com.github.mikephil.charting.renderer.LineChartRenderer;

/**
 * Created by David on 11/01/2017.
 */

public class CustomZIndexLineChart extends CustomZIndexLineChartBase implements LineDataProvider {

    public CustomZIndexLineChart(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public CustomZIndexLineChart(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomZIndexLineChart(Context context) {
        super(context);
    }

    @Override
    protected void init() {
        super.init();

        mRenderer = new LineChartRenderer(this, mAnimator, mViewPortHandler);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

    @Override
    public LineData getLineData() {
        return mData;
    }

    @Override
    protected void onDetachedFromWindow() {
        // releases the bitmap in the renderer to avoid oom error
        if (mRenderer != null && mRenderer instanceof LineChartRenderer) {
            ((LineChartRenderer) mRenderer).releaseBitmap();
        }
        super.onDetachedFromWindow();
    }
}