美文网首页Android干货Android知识Android开发
给 Android 图表库 MPAndroidChart 的坐标

给 Android 图表库 MPAndroidChart 的坐标

作者: NanBox | 来源:发表于2016-12-13 22:13 被阅读1759次

    做统计图时,经常会有这样的需求:x 坐标需要换行显示,日和月或者是月和年需要分两行显示。但是找遍 MPAndroidChart 的各种教程,好像也没有相关的方法可以实现......

    以 LineChart 为例,我们想要的效果是类似这样子的:

    首先,MPAndroidChart 确实是没有提供更改坐标换行显示的方法。在看这篇文章之前,你可能已经尝试过实现一个 ValueFormatter,或者是 "\r\n" 这样的挣扎,发现并没有什么卵用。但就算真给你换行了,如何居中,如何设置两行不同字体也是问题。

    咋办?看一下源码吧。看看设进去的数据是如何显示出来的。

    我们是这样来设置坐标轴要显示的内容的:

    LineData data = new LineData(xValues, dataSets);
    mChart.setData(data);
    mChart.invalidate();
    

    跟着 setData 方法进源码,一路经过 Chart,BarLineChartBase,最终来到了 XAxisRenderer 这个类,我们的 xValues 被赋值给了它的 mXAxis。然后,好像就没有然后了......

    不对,别忘了我们 setData 之后还调用了 invalidate 方法请求重绘,重绘会去调用 Chart 的 onDraw 方法。再看看继承 Chart 的 BarLineChartBase,它的 onDraw 方法里出现了这一句:

     this.mXAxisRenderer.renderGridLines(canvas);
    

    感觉离真相越来越近了。我们再跟着这个方法进来,看到里面调用了 drawLabels 方法,然后 drawLabels 又调用了 drawLabel 方法,最终我们来到了这里:

    protected void drawLabel(Canvas c, String label, int xIndex, float x, float y, PointF anchor,float angleDegrees) {
        String formattedLabel = mXAxis.getValueFormatter().getXValue(label, xIndex,mViewPortHandler);
        Utils.drawXAxisValue(c, formattedLabel, x, y, mAxisLabelPaint, anchor, angleDegrees);
    }
    

    最后进入 Utils.drawXAxisValue 方法,终于看到了这样的一句:

    c.drawText(text, drawOffsetX, drawOffsetY, paint);
    

    哎呦我去,终于找到了,藏这么深。

    可以看到坐标轴是用 Canvas.drawText 显示出来的。了解一下你会发现,通常情况下,drawText 方法是不支持 "\n\r" 的。看来我们只要改 drawLabel 这个方法就可以了。由于源码几个类的耦合程度比较高,我并没有找到可以通过继承重写的方法实现,不得已只好改源代码了。

    我修改后的代码是这样的:

    protected void drawLabel(Canvas c, String label, int xIndex, float x, float y, PointF anchor, float angleDegrees) {
        String formattedLabel = mXAxis.getValueFormatter().getXValue(label, xIndex, mViewPortHandler);
        
        float labelHeight = mXAxis.getTextSize();
        float labelInterval = 25f;
        String[] labels = label.split(" ");
    
        Paint mFirstLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mFirstLinePaint.setColor(Color.WHITE);
        mFirstLinePaint.setTextAlign(Align.CENTER);
        mFirstLinePaint.setTextSize(Utils.convertDpToPixel(15f));
        mFirstLinePaint.setTypeface(mXAxis.getTypeface());
    
        Paint mSecondLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mSecondLinePaint.setColor(0xFF9b9b9b);
        mSecondLinePaint.setTextAlign(Align.CENTER);
        mSecondLinePaint.setTextSize(Utils.convertDpToPixel(10f));
        mSecondLinePaint.setTypeface(mXAxis.getTypeface());
    
        if (labels.length > 1) {
            Utils.drawXAxisValue(c, labels[0], x, y, mFirstLinePaint, anchor, angleDegrees);
            Utils.drawXAxisValue(c, labels[1], x, y + labelHeight + labelInterval, mSecondLinePaint, anchor, angleDegrees);
        } else {
            Utils.drawXAxisValue(c, formattedLabel, x, y, mFirstLinePaint, anchor, angleDegrees);
        }
    }
    

    我在要分行显示的字符串间加了个空格,在这里再用空格切割成两个字符串。我们可以分别给两行设置不同的格式,并且让他们都居中显示。最后绘制的时候,把第二行的 y 坐标改一下,在第一行的基础上加上一定的高度就可以了。妥妥的。

    另外,那条黄色的高亮线也是改源码实现的。


    2017.2.19 更新

    MPAndroidChart 3.0 之后改变比较大,和这里相关的主要有两点:

    1. 取消了 LineData(List<String> xVals, List<ILineDataSet> dataSets) 这个构造方法,不再传 x 轴坐标数据,直接从 LineDataSet 坐标中获取。
    2. MPAndroidChart 底层绘制 x 轴坐标,是先格式化之后才执行 drawLabel 这个方法。

    处理的思路跟之前是一致的,可以用 ValueFormatter 格式化一下 x 轴的数据(比如用空格将要分行显示的数据分开),然后修改源码 XAxisRenderer 里面的 drawLabel,类似下面这样:

    protected void drawLabel(Canvas c, String formattedLabel, float x, float y, MPPointF anchor, float angleDegrees) {
        float labelHeight = mXAxis.getTextSize();
        float labelInterval = 25f;
        String[] labels = formattedLabel.split(" ");
    
        Paint mFirstLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mFirstLinePaint.setColor(Color.WHITE);
        mFirstLinePaint.setTextAlign(Align.CENTER);
        mFirstLinePaint.setTextSize(Utils.convertDpToPixel(15f));
        mFirstLinePaint.setTypeface(mXAxis.getTypeface());
    
        Paint mSecondLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mSecondLinePaint.setColor(0xFF9b9b9b);
        mSecondLinePaint.setTextAlign(Align.CENTER);
        mSecondLinePaint.setTextSize(Utils.convertDpToPixel(10f));
        mSecondLinePaint.setTypeface(mXAxis.getTypeface());
    
        if (labels.length > 1) {
            Utils.drawXAxisValue(c, labels[0], x, y, mFirstLinePaint, anchor, angleDegrees);
            Utils.drawXAxisValue(c, labels[1], x, y + labelHeight + labelInterval, mSecondLinePaint, anchor, angleDegrees);
        } else {
            Utils.drawXAxisValue(c, formattedLabel, x, y, mFirstLinePaint, anchor, angleDegrees);
        }
    }
    

    妥妥的。


    2017.3.17 更新

    补充一下高亮线的修改方法吧。

    如果用的是不规则的高亮线(像图片里面上下有个球的),需要用自己的图片资源来替换。为了适配,建议使用高度可以拉伸的点九图。

    我们先找到接口 ILineScatterCandleRadarDataSet,增加一个获取 Bitmap 的方法:

    /**
     * @return 获取高亮线图片 bitmap
     */
    Bitmap getHighLightBitmap();
    

    再找到 LineScatterCandleRadarDataSet,增加这两个方法:

    protected Bitmap mHighlightBitmap = null;
    
    /**
     * 设置高亮线图片
     *
     * @param bitmap 高亮线图片 bitmap
     */
    public void setHighlightBitmap(Bitmap bitmap) {
        this.mHighlightBitmap = bitmap;
    }
    
    /**
     * @return 获取高亮线图片 bitmap
     */
    public Bitmap getHighLightBitmap() {
        return this.mHighlightBitmap;
    }
    

    最后找到设置高亮线的地方,它在 LineScatterCandleRadarRenderer 的 drawHighlightLines 方法里,可以把它修改成这个样子:

    /**
     * Draws vertical & horizontal highlight-lines if enabled.
     *
     * @param c
     * @param x   x-position of the highlight line intersection
     * @param y   y-position of the highlight line intersection
     * @param set the currently drawn dataset
     */
    protected void drawHighlightLines(Canvas c, float x, float y, ILineScatterCandleRadarDataSet set) {
    
        // set color and stroke-width
        mHighlightPaint.setColor(set.getHighLightColor());
        mHighlightPaint.setStrokeWidth(set.getHighlightLineWidth());
    
        // draw highlighted lines (if enabled)
        mHighlightPaint.setPathEffect(set.getDashPathEffectHighlight());
    
        //优先使用高亮线图片显示,没有则使用默认样式
        if (set.getHighLightBitmap() != null) {
            if (set.isVerticalHighlightIndicatorEnabled()) {
                Bitmap highLightBitmap = set.getHighLightBitmap();
                NinePatch ninePatch = new NinePatch(highLightBitmap,
                        highLightBitmap.getNinePatchChunk(), null);
                int highLightWidth = (int) Utils.convertDpToPixel(8.0F);
                RectF rectF = new RectF(x - (float) (highLightWidth / 2),
                        this.mViewPortHandler.contentTop(),
                        x + (float) (highLightWidth / 2),
                        this.mViewPortHandler.contentBottom());
                ninePatch.draw(c, rectF);
            }
        } else {
            // draw vertical highlight lines
            if (set.isVerticalHighlightIndicatorEnabled()) {
    
                // create vertical path
                mHighlightLinePath.reset();
                mHighlightLinePath.moveTo(x, mViewPortHandler.contentTop());
                mHighlightLinePath.lineTo(x, mViewPortHandler.contentBottom());
    
                c.drawPath(mHighlightLinePath, mHighlightPaint);
            }
    
            // draw horizontal highlight lines
            if (set.isHorizontalHighlightIndicatorEnabled()) {
    
                // create horizontal path
                mHighlightLinePath.reset();
                mHighlightLinePath.moveTo(mViewPortHandler.contentLeft(), y);
                mHighlightLinePath.lineTo(mViewPortHandler.contentRight(), y);
    
                c.drawPath(mHighlightLinePath, mHighlightPaint);
            }
        }
    }
    

    上面代码基于目前最新的 MPAndroidChart 3.0.1 ,旧版在细节上会有不同,但实现思路是一致的。


    2017.4.1 更新

    增加一个简单的示例

    效果如下:

    源码地址

    相关文章

      网友评论

      • dde82929c761:请求怎么隐藏XY坐标轴吗,我已经设置不显示了,只需要显示曲线部分,但是四周还是有个边距存在
        NanBox:默认是有边距的,试一下 mChart.setViewPortOffsets(0, 0, 0, 0);
      • 788a2830621d:你好,我X抽坐标换行后,超出了画布值显示一部分,并且如果我把图例放在底部的话,会重叠,怎么处理这个问题,我用的是老版的MPCHART
        NanBox:@ISNULL_cece
        你好!可以尝试给图表视图设置偏移量,比如:
        mChart.setViewPortOffsets(0, 0, 0, 100);
      • Jafir:你好,想问问,横坐标显示的是日期,是用valueformatter么
        NanBox: @Jafir 不好意思,刚看到。可以用 value formatter 来格式化横坐标的。
      • upuper:修改源码之后重新生成一个jar包置于libs之下,但我修改的那个类没有被重新编译,它不执行怎么弄啊,帮帮忙!
      • c332392f879a:你好,请问https://github.com/SouthernBox/MPAndroidChart-Newline是将X轴标签的数据进行竖直显示吗?并不是旋转90度的效果,而是竖直显示,不知道这个能做到吗?
        c332392f879a:@SouthernBox 好的,我试试,谢谢
        NanBox:可以的,你直接遍历 formattedLabel 的每个字符就好了,像这样:
        for (int i = 0; i < formattedLabel.length(); i++) {
        Utils.drawXAxisValue(c, String.valueOf(formattedLabel.charAt(i)), x, y + labelHeight * i + labelInterval, mAxisLabelPaint, anchor, angleDegrees);
        }
        c332392f879a:另外,在 drawLabel()方法中这样修改: if (labels.length > 1) {
        for (int i = 0; i <labels.length ; i++) {
        Utils.drawXAxisValue(c, labels[i], x, y + labelHeight* i+ labelInterval, mSecondLinePaint, anchor, angleDegrees);
        }

        }

        能否将数据单行竖直显示?
      • 蚂蚁放风筝:你好,我想对X轴标签换行,你这具体怎么用呢,能否给个Demo?
        NanBox:@蚂蚁放风筝 刚写了一个简单的 demo,源码在这里:
        https://github.com/SouthernBox/MPAndroidChart-Newline。
        可以先参考一下。时间比较仓促,可能还有些不太合理的地方。
        蚂蚁放风筝: @SouthernBox 能给个demo吗?
        NanBox:需要修改源码的。
      • 6389ec32e197:请问这根黄色高亮线是在哪个方法里改的呢?
        NanBox:使用图片资源显示高亮线,修改方法已经更新在文章里了,可以参考一下。
        NanBox:请问你的高亮线也是不规则的吗?是的话也要修改源码。
      • 梨落520:能不能给个小demo,引入你这个方法出现问题
        NanBox:@梨落520 MPAndroidChart不同版本好像差异挺大的,我稍后看一下最新版的代码。
        梨落520:@SouthernBox
        (1)String formattedLabel = mXAxis.getValueFormatter().getXValue(label, xIndex, mViewPortHandler);
        这个方法我现在用最新的MPAndroidChart 中都没了
        (2)能不能具体说一下你重写的代码怎么使用

        非常感谢!
        NanBox:请问具体是什么问题呢?

      本文标题:给 Android 图表库 MPAndroidChart 的坐标

      本文链接:https://www.haomeiwen.com/subject/oywbmttx.html