使用
在分析之前,先了解如何使用该库。然后根据使用流程开枝散叶般去分析其中的具体分支。首先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的源码,大致的总结整理出一张流程图。
根据上面的结构图,可以直观的发现。其实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计算和绘制的,这里整理出一张时序图方便理解:
大致的解释一下就是,当我们通过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中的其他部分,没做过多的分析,比如数据格式器、动画等需进一步了解分析。