使用
在分析之前,先了解如何使用該庫(kù)戒职。然后根據(jù)使用流程開枝散葉般去分析其中的具體分支劲绪。首先MPAndroidChart的使用大致可以總結(jié)為下面的使用流程圖:
下面根據(jù)流程圖來(lái)一步步的粗略介紹下代碼中的使用步驟
- 假設(shè)我們從后臺(tái)接口中成功獲取到圖表中繪制所需的數(shù)據(jù)集
- 然后將數(shù)據(jù)包裝成MPAndroidChart能夠識(shí)別的數(shù)據(jù)類型爷抓。
- 最后將數(shù)據(jù)傳給Chart,并通知其刷新繪制
包裝過程的代碼大致如下:
//可以通俗的理解一個(gè)Entry對(duì)應(yīng)于圖表中的一個(gè)點(diǎn)
Entry c1e1 = new Entry(0f, 100000f);
Entry c1e2 = new Entry(1f, 140000f);
...
//將Entry轉(zhuǎn)換成DateSet(可以理解為圖表中的一條線)
List<Entry> valsComp1 = new ArrayList<Entry>();
valsComp1.add(c1e1);
valsComp1.add(c1e2);
...
LineDataSet setComp1 = new LineDataSet(valsComp1, "Company 1")
//將所有的數(shù)據(jù)集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
更多使用方法詳解查看官網(wǎng)Wiki
結(jié)構(gòu)分析
如果仔細(xì)的去看MPAndroidChart中的每一個(gè)圖表或者細(xì)節(jié)的實(shí)現(xiàn)是非常枯燥和耗時(shí)間的步淹。最好的方法是先選取一個(gè)分支了解其大致結(jié)構(gòu)从隆,然后了解細(xì)節(jié),最后再了解其他分支缭裆。這里根據(jù)分析LineChart的源碼键闺,大致的總結(jié)整理出一張流程圖。
根據(jù)上面的結(jié)構(gòu)圖幼驶,可以直觀的發(fā)現(xiàn)艾杏。其實(shí)Chart的作用主要是中心調(diào)度.具體的實(shí)現(xiàn)由其他部分實(shí)現(xiàn),達(dá)到各部分解耦的效果盅藻。下面我們遵循由點(diǎn)到面的思路去分析整個(gè)結(jié)構(gòu)购桑。
1.先看組件部分,該部分主要是包括X氏淑、Y軸線勃蜘,限制線、標(biāo)注等等假残。同樣的選取AxisBase進(jìn)行源碼分析缭贡,其主要是包括軸線寬度炉擅、最大最小值、顏色阳惹、是否繪制網(wǎng)格線等等谍失,并提供相對(duì)于的Set和Get方法。我們也可以根據(jù)自己想需求在它的基礎(chǔ)上去進(jìn)一步設(shè)置莹汤。
2.再看數(shù)據(jù)部分快鱼,大致使用過程在開始就介紹過了。主要就是對(duì)數(shù)據(jù)進(jìn)行一層一層的裝換纲岭。同時(shí)抹竹,其內(nèi)部提供了相關(guān)的增刪查減操作。
3.第三部分?jǐn)?shù)據(jù)渲染止潮。其主體實(shí)現(xiàn)主要是Render及其子類窃判。觀察源碼可以清楚的看到Render的作用可以總結(jié)為兩點(diǎn),一是計(jì)算喇闸,二是繪制袄琳。Chart通過Render先進(jìn)行圖表數(shù)據(jù)和參數(shù)的準(zhǔn)備,既計(jì)算操作仅偎。比如計(jì)算出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);
......
}
對(duì)于圖表是如何控制Render計(jì)算和繪制的橘沥,這里整理出一張時(shí)序圖方便理解:
大致的解釋一下就是,當(dāng)我們通過Chart.SetData傳入數(shù)據(jù)到圖表中夯秃,會(huì)調(diào)用到內(nèi)部的notifyDataSetChanged()方法座咆,然后會(huì)通知Render進(jìn)行相關(guān)的計(jì)算操作。值得一提的是仓洼,在這個(gè)時(shí)候會(huì)調(diào)用Transformer完成"value-touch-offset"的轉(zhuǎn)換過程介陶,其內(nèi)部是通過操作Matrix實(shí)現(xiàn)的。而在圖表第一次顯示或者后續(xù)發(fā)生移動(dòng)色建、縮放哺呜、平移等操作的時(shí)候,會(huì)調(diào)用到Chart.OnDraw方法進(jìn)行繪制箕戳。而在Chart.OnDraw會(huì)重新進(jìn)行計(jì)算操作某残,同時(shí)會(huì)調(diào)用Render的renderXXX方法進(jìn)行具體的繪制×晡可以通過觀看如下代碼更加直觀明白:
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.結(jié)構(gòu)圖中將ViewPortHandler單獨(dú)放出的原因是因?yàn)樗贑hart和Render之間擔(dān)任一個(gè)“信使”的作用(這里的理解不知道是否有誤),Chart將一些數(shù)據(jù)和數(shù)據(jù)告訴ViewPortHandler玻墅,比如最大最小縮放,Chart的長(zhǎng)度和寬度壮虫,偏移量等等澳厢,而Render則從其中獲取這部分信息。對(duì)于MPAndroidChart中的其他部分,沒做過多的分析剩拢,比如數(shù)據(jù)格式器线得、動(dòng)畫等需進(jìn)一步了解分析。