美文网首页
6柱状图数据的绘制(MPAndroidChart源码解析)

6柱状图数据的绘制(MPAndroidChart源码解析)

作者: 世界是一个圆_ | 来源:发表于2018-06-08 11:49 被阅读0次

    Chart中数据的绘制时通过一系列的Renderer类来实现的,类结构如下:


    Renderer.png

    下面以BarChartRenderer为例讲解。

    1. BarBuffer(防止对象重复创建)

    由于Renderer中方法的执行大部分都是在onDraw方法中,为了防止对象的频繁创建而引起内存抖动问题(会造成卡段现象),这里使用了Buffer的方式。
    BarBuffer中维护了一个float[],用来存储所绘制的所有的点信息(Bar就是一个矩形,大小由left,top,right,bottom四个点所决定),BarBuffer保存了所有数据的原始value值,当我们缩放或者平移之后,会遍历buffer中的每一个数据,先判断这个数据是否在可见范围内才绘制,这样至始至终,无论是动画还是缩放或者是拖拽,我们都是从buffer中取数据,然后根据Utils中的matrix的信息映射出px值进行绘制,防止每次绘制都要重新创建一系列的点。
    先来看一下BarBuffer中的主要字段:

     /** float-buffer that holds the data points to draw, order: x,y,x,y,... */
        public final float[] buffer;
    //设置为1每一个bar会充满它所在的x-index组,如果设置为0.9,相邻两个bar之间就会有一个0.1的间隔
        protected float mBarWidth = 1f;
    

    BarBuffer中最重要的一个方法是feed(IBarDataSet data),它会遍历传入的BarDataSet中所有的BarEntry,根据BarEntry的x,y生成当前bar的left,top,right,bottom,然后存如buffer中:

     @Override
        public void feed(IBarDataSet data) {
            //phaseX 是x轴动画的进度,可以先认为是1
            float size = data.getEntryCount() * phaseX;
            //1.根据mBarWidth计算出bar的一半宽度,注意此时是百分比值
            float barWidthHalf = mBarWidth / 2f;
    
            for (int i = 0; i < size; i++) {
    
                BarEntry e = data.getEntryForIndex(i);
    
                if(e == null)
                    continue;
    
                float x = e.getX();
                float y = e.getY();
                float[] vals = e.getYVals();
                //非堆叠图
                if (!mContainsStacks || vals == null) {
                    //2.以barEntry的x为中心点,计算出left,top,right,bottom
                    float left = x - barWidthHalf;
                    float right = x + barWidthHalf;
                    float bottom, top;
    
                    if (mInverted) {
                        bottom = y >= 0 ? y : 0;
                        top = y <= 0 ? y : 0;
                    } else {
                        top = y >= 0 ? y : 0;
                        bottom = y <= 0 ? y : 0;
                    }
    
                    // multiply the height of the rect with the phase
                    if (top > 0)
                        top *= phaseY;
                    else
                        bottom *= phaseY;
                    //3.添加到buffer中
                    addBar(left, top, right, bottom);
                } else {//堆叠图
                 ...
                }
            }
            //重置index
            reset();
        }
    

    2.BarBuffer的初始化

    在BarLineChartBase的中会调用Renderer的initBuffers()方法

     @Override
        public void notifyDataSetChanged() {
            ...
            if (mRenderer != null)
                mRenderer.initBuffers();
         ...
        }
    
       @Override
        public void initBuffers() {
            BarData barData = mChart.getBarData();
            mBarBuffers = new BarBuffer[barData.getDataSetCount()];
    
            for (int i = 0; i < mBarBuffers.length; i++) {
                IBarDataSet set = barData.getDataSetByIndex(i);
                mBarBuffers[i] = new BarBuffer(set.getEntryCount() * 4 * (set.isStacked() ? set.getStackSize() : 1),
                        barData.getDataSetCount(), set.isStacked());
            }
        }
    

    上面的代码只是初始化了Barbuffer中长度,向其填充内容实在绘制每一个DataSet中进行的。

     protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
            Transformer trans = mChart.getTransformer(dataSet.getAxisDependency());
            float phaseX = mAnimator.getPhaseX();
            float phaseY = mAnimator.getPhaseY();
            ...
             BarBuffer buffer = mBarBuffers[index];
            buffer.setPhases(phaseX, phaseY);
            buffer.setDataSet(index);
            buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency()));
            buffer.setBarWidth(mChart.getBarData().getBarWidth());
            //调用feed方法,填充一系列的point
            buffer.feed(dataSet);
            //上面的buffer只是原始值value,需要通过Transformer映射成px值
            trans.pointValuesToPixel(buffer.buffer);
            ...
     }
    

    通过上面的操作,Barbuffer中就填充好了所有要绘制的数据(px值),剩下的就是遍历这个buffer,四个点为一组绘制矩形了。
    下面来看一下具体的绘制过程。

    3.具体绘制流程

    首先会遍历BarData绘制所有的DataSet:

     @Override
        public void drawData(Canvas c) {
            BarData barData = mChart.getBarData();
            for (int i = 0; i < barData.getDataSetCount(); i++) {
                IBarDataSet set = barData.getDataSetByIndex(i);
                if (set.isVisible()) {
                    drawDataSet(c, set, i);
                }
            }
        }
    

    在drawDataSet()中会先想BarBuffer中填充数据,然后遍历buffer绘制矩形,需要注意的是,只会绘制在可见区域中的数据,可见区域就是ViewPortHandler中的mContentRect:

     protected void drawDataSet(Canvas c, IBarDataSet dataSet, int index) {
    
            Transformer trans = mChart.getTransformer();
            //TODO 动画暂不处理
    //        float phaseX = mAnimator.getPhaseX();
    //        float phaseY = mAnimator.getPhaseY();
    
            float phaseX = 1f;
            float phaseY = 1f;
            
            //1.初始化buffer
            BarBuffer buffer = mBarBuffers[index];
            buffer.setPhases(phaseX, phaseY);
            buffer.setDataSet(index);
            buffer.setInverted(false);
            buffer.setBarWidth(mChart.getBarData().getBarWidth());
            buffer.feed(dataSet);
            trans.pointValuesToPixel(buffer.buffer);
    
            final boolean isSingleColor = dataSet.getColors().size() == 1;
            if (isSingleColor) {
                mRenderPaint.setColor(dataSet.getColor());
            }
            
            //2.遍历buffer绘制矩形
            for (int j = 0; j < buffer.size(); j += 4) {
                //如果这个bar的right 小于 content的left就不绘制
                if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2]))
                    continue;
                //如果这个bar的left大于content的right就不绘制
                if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j]))
                    break;
    
                if (!isSingleColor) {
                    mRenderPaint.setColor(dataSet.getColor(j / 4));
                }
                c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2],
                        buffer.buffer[j + 3], mRenderPaint);
            }
        }
    

    4.绘制柱状图上面的文字

    文字的绘制是在drawValues方法中,类似于矩形的绘制,也是遍历BarBuffer进行绘制,我们只看单独绘制一个dataSet中文字的流程:

     //0.获取要绘制的dataSet
                    IBarDataSet dataSet = datasets.get(i);
                    //1.是否要绘制文字
                    if (!shouldDrawValues(dataSet)) {
                        continue;
                    }
    //                mValuePaint.setTextSize(dataSet.getValueTextSize());
    //                boolean isInverted = mChart.isInverted(dataSet.getAxisDependency());
                    boolean isInverted = false;
                    //2.计算文字的高度
                    float valueTextHeight = Utils.calcTextHeight(mValuePaint, "8");
                    posOffset = (drawValueAboveBar ? -valueOffsetPlus : valueTextHeight + valueOffsetPlus);
                    negOffset = (drawValueAboveBar ? valueTextHeight + valueOffsetPlus : -valueOffsetPlus);
    
                    if (isInverted) {
                        posOffset = -posOffset - valueTextHeight;
                        negOffset = -negOffset - valueTextHeight;
                    }
                    //3.获取buffer
                    BarBuffer buffer = mBarBuffers[i];
                    //TODO 动画相关,暂不考虑
    //                final float phaseY = mAnimator.getPhaseY();
                    final float phaseY = 1;
    
                    //4.遍历属于该dataSet下的buffer绘制文字
                    if (!dataSet.isStacked()) {
                        // TODO 动画相关,暂不考虑
    //                    for (int j = 0; j < buffer.buffer.length * mAnimator.getPhaseX(); j += 4) {
                        //
                        for (int j = 0; j < buffer.buffer.length; j += 4) {
                            //5.文字的中心位置在bar的中心
                            float x = (buffer.buffer[j] + buffer.buffer[j + 2]) / 2f;
                            //6.从左向右绘制,如果某个value的x超过了content的right则不绘制
                            if (!mViewPortHandler.isInBoundsRight(x))
                                break;
                            //7.不在可见区域中的数据不会只文字
                            if (!mViewPortHandler.isInBoundsY(buffer.buffer[j + 1])
                                    || !mViewPortHandler.isInBoundsLeft(x))
                                continue;
                            BarEntry entry = dataSet.getEntryForIndex(j / 4);
                            //8.获取绘制的文字原始值,如果设置了valueFormatter,就会格式化一下这个原始值
                            float val = entry.getY();
    
                            if (dataSet.isDrawValuesEnabled()) {
                                //9.开始绘制文字
                                drawValue(c, dataSet.getValueFormatter(), val, entry, i, 
                                        x,//绘制文字的x中心点
                                        val >= 0 ? (buffer.buffer[j + 1] + posOffset) : (buffer.buffer[j + 3] + negOffset),//文字的top点,就是bar的top加上文字的高度
                                        dataSet.getValueTextColor()
                                );
                            }
                        }
    

    drawValue方法如下,这里就不讲解了:

     public void drawValue(Canvas c, IValueFormatter formatter, float value, Entry entry, int dataSetIndex, float x, float y, int color) {
            mValuePaint.setColor(color);
            c.drawText(formatter.getFormattedValue(value, entry, dataSetIndex, mViewPortHandler), x, y, mValuePaint);
        }
    

    到这里,数据的绘制就分析完了,后面将会分析Highlight的处理。

    相关文章

      网友评论

          本文标题:6柱状图数据的绘制(MPAndroidChart源码解析)

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