MPAndroidChart结构和源码分析

使用

在分析之前,先了解如何使用该库。然后根据使用流程开枝散叶般去分析其中的具体分支。首先MPAndroidChart的使用大致可以总结为下面的使用流程图:

使用流程

下面根据流程图来一步步的粗略介绍下代码中的使用步骤

  • 假设我们从后台接口中成功获取到图表中绘制所需的数据集
  • 然后将数据包装成MPAndroidChart能够识别的数据类型。
  • 最后将数据传给Chart,并通知其刷新绘制

包装过程的代码大致如下:

   //可以通俗的理解一个Entry对应于图表中的一个点
   Entry c1e1 = new Entry(0f, 100000f);    
   Entry c1e2 = new Entry(1f, 140000f);
    ...

   //将Entry转换成DateSet(可以理解为图表中的一条线)
   List<Entry> valsComp1 = new ArrayList<Entry>();
   valsComp1.add(c1e1);
   valsComp1.add(c1e2);
   ...
   LineDataSet setComp1 = new LineDataSet(valsComp1, "Company 1")
   
    //将所有的数据集DateSet包装成ChartData  然后set给Chart
    List<ILineDataSet> dataSets = new ArrayList<ILineDataSet>();
    dataSets.add(setComp1);
    dataSets.add(setComp2);
    ...
    LineData data = new LineData(dataSets);
    mLineChart.setData(data);
    mLineChart.invalidate(); // refresh

更多使用方法详解查看官网Wiki

结构分析

如果仔细的去看MPAndroidChart中的每一个图表或者细节的实现是非常枯燥和耗时间的。最好的方法是先选取一个分支了解其大致结构,然后了解细节,最后再了解其他分支。这里根据分析LineChart的源码,大致的总结整理出一张流程图。

MPAndroidChart结构图.png

根据上面的结构图,可以直观的发现。其实Chart的作用主要是中心调度.具体的实现由其他部分实现,达到各部分解耦的效果。下面我们遵循由点到面的思路去分析整个结构。

1.先看组件部分,该部分主要是包括X、Y轴线,限制线、标注等等。同样的选取AxisBase进行源码分析,其主要是包括轴线宽度、最大最小值、颜色、是否绘制网格线等等,并提供相对于的Set和Get方法。我们也可以根据自己想需求在它的基础上去进一步设置。

2.再看数据部分,大致使用过程在开始就介绍过了。主要就是对数据进行一层一层的装换。同时,其内部提供了相关的增删查减操作。
3.第三部分数据渲染。其主体实现主要是Render及其子类。观察源码可以清楚的看到Render的作用可以总结为两点,一是计算,二是绘制。Chart通过Render先进行图表数据和参数的准备,既计算操作。比如计算出Y轴上的最大最小值,以此可以算出X轴之前的间距。比如如下代码:

/**
     * Sets up the axis values. Computes the desired number of labels between the two given extremes.
     *
     * @return
     */
    protected void computeAxisValues(float min, float max) {

        float yMin = min;
        float yMax = max;

        int labelCount = mAxis.getLabelCount();
        double range = Math.abs(yMax - yMin);

        if (labelCount == 0 || range <= 0 || Double.isInfinite(range)) {
            mAxis.mEntries = new float[]{};
            mAxis.mCenteredEntries = new float[]{};
            mAxis.mEntryCount = 0;
            return;
        }

        // Find out how much spacing (in y value space) between axis values
        double rawInterval = range / labelCount;
        double interval = Utils.roundToNextSignificant(rawInterval);
        ......
        
   }

对于图表是如何控制Render计算和绘制的,这里整理出一张时序图方便理解:

MPAndroidChart数据渲染时序图.png

大致的解释一下就是,当我们通过Chart.SetData传入数据到图表中,会调用到内部的notifyDataSetChanged()方法,然后会通知Render进行相关的计算操作。值得一提的是,在这个时候会调用Transformer完成"value-touch-offset"的转换过程,其内部是通过操作Matrix实现的。而在图表第一次显示或者后续发生移动、缩放、平移等操作的时候,会调用到Chart.OnDraw方法进行绘制。而在Chart.OnDraw会重新进行计算操作,同时会调用Render的renderXXX方法进行具体的绘制。可以通过观看如下代码更加直观明白:

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

        if (mData == null)
            return;

        long starttime = System.currentTimeMillis();

        // execute all drawing commands
        drawGridBackground(canvas);

        if (mAutoScaleMinMaxEnabled) {
            autoScale();
        }

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

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

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

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

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

        // make sure the data cannot be drawn outside the content-rect
        int clipRestoreCount = canvas.save();
        canvas.clipRect(mViewPortHandler.getContentRect());

        mRenderer.drawData(canvas);

        // if highlighting is enabled
        if (valuesToHighlight())
            mRenderer.drawHighlighted(canvas, mIndicesToHighlight);

        // Removes clipping rectangle
        canvas.restoreToCount(clipRestoreCount);

        mRenderer.drawExtras(canvas);

        if (mXAxis.isEnabled() && !mXAxis.isDrawLimitLinesBehindDataEnabled())
            mXAxisRenderer.renderLimitLines(canvas);

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

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

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

        if (mLogEnabled) {
            long drawtime = (System.currentTimeMillis() - starttime);
            totalTime += drawtime;
            drawCycles += 1;
            long average = totalTime / drawCycles;
            Log.i(LOG_TAG, "Drawtime: " + drawtime + " ms, average: " + average + " ms, cycles: "
                    + drawCycles);
        }
    }

4.结构图中将ViewPortHandler单独放出的原因是因为它在Chart和Render之间担任一个“信使”的作用(这里的理解不知道是否有误),Chart将一些数据和数据告诉ViewPortHandler,比如最大最小缩放,Chart的长度和宽度,偏移量等等,而Render则从其中获取这部分信息。对于MPAndroidChart中的其他部分,没做过多的分析,比如数据格式器、动画等需进一步了解分析。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_x阅读 15,967评论 3 119
  • 月悬枝头疑地霜, 了星四缀许羽傍。 忽闻犬吠十里传, 原为天狗食月亮。 天寒足凉浑不知, 霜消星现人心凉。 天涯此...
    宝政阅读 269评论 0 0
  • 回顾上一章 天空湛蓝,白云在天上悠闲地飘着,阳光透过窗帘撒在顾小烟的脸上,有一种温暖的感觉。 一切,都显得刚刚好。...
    黑猫by西瓷阅读 1,698评论 0 1