MPAndroidChart - is it possible to control z-index

2019-02-27 11:26发布

问题:

I would like to have the following order of drawing in my MPAndroidChart (from bottom to top):

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

Is it possible? I am aware of the method com.github.mikephil.charting.components.AxisBase#setDrawLimitLinesBehindData. It is working as expected except for one case. When the Y value of all data points is the same, the effect is:

or this:

I would like it to look like:

The first 2 pictures are from MPAndroidChart Android library. The 3rd one is from iOS port of the library: Charts

I looked at the order or drawing the chart in Android and iOS versions and they look the same.

Questions:

  1. Is it possible to control the drawing order?
  2. What comes the difference from between system versions?
  3. Is there any other open source library that can do that?

Additional info: all images, lines, circles are drawn by the library, custom images are not used.

回答1:

Similar to a previous answer here there is no public API exposed to directly set the z-index of the various drawing features.

Instead, components are drawn in order on the canvas with later components being drawn over earlier ones. This means you change the rendering order, you can change the z-index.

You say you would like the following drawing order:

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

Let's find the methods in the source code that deal with each of those:

  1. Data connecting line is drawn inside LineChartRenderer in a method called:

    protected drawLinear(Canvas c, ILineDataSet dataSet)
    
  2. The limit line is drawn inside XAxisRenderer called:

    public void renderLimitLines(Canvas c)
    
  3. The data points (circles) are drawn inside LineChartRenderer in a method that looks like this:

    public void drawExtras(Canvas c)
    

The calling order of these three methods is determined inside BarLineChartBase in the method overriden from Android's View:

    protected onDraw(Canvas canvas);

So to get the order you want you will have to simply re-arrange the order of calling of the above 3 methods inside onDraw(Canvas canvas):

Here is the complete code for a custom line chart that should meet the requirement. By design, you will still have to call:

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

But you could easily remove the 3 if statements and hard-code the order should you so desire.

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();
    }
}