做統(tǒng)計圖時,經(jīng)常會有這樣的需求:x 坐標需要換行顯示,日和月或者是月和年需要分兩行顯示。但是找遍 MPAndroidChart 的各種教程,好像也沒有相關(guān)的方法可以實現(xiàn)......
以 LineChart 為例栏账,我們想要的效果是類似這樣子的:
首先,MPAndroidChart 確實是沒有提供更改坐標換行顯示的方法义矛。在看這篇文章之前发笔,你可能已經(jīng)嘗試過實現(xiàn)一個 ValueFormatter,或者是 "\r\n" 這樣的掙扎凉翻,發(fā)現(xiàn)并沒有什么卵用了讨。但就算真給你換行了,如何居中制轰,如何設置兩行不同字體也是問題前计。
咋辦?看一下源碼吧垃杖∧需荆看看設進去的數(shù)據(jù)是如何顯示出來的。
我們是這樣來設置坐標軸要顯示的內(nèi)容的:
LineData data = new LineData(xValues, dataSets);
mChart.setData(data);
mChart.invalidate();
跟著 setData 方法進源碼调俘,一路經(jīng)過 Chart伶棒,BarLineChartBase旺垒,最終來到了 XAxisRenderer 這個類,我們的 xValues 被賦值給了它的 mXAxis肤无。然后先蒋,好像就沒有然后了......
不對,別忘了我們 setData 之后還調(diào)用了 invalidate 方法請求重繪宛渐,重繪會去調(diào)用 Chart 的 onDraw 方法竞漾。再看看繼承 Chart 的 BarLineChartBase,它的 onDraw 方法里出現(xiàn)了這一句:
this.mXAxisRenderer.renderGridLines(canvas);
感覺離真相越來越近了窥翩。我們再跟著這個方法進來业岁,看到里面調(diào)用了 drawLabels 方法,然后 drawLabels 又調(diào)用了 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 顯示出來的。了解一下你會發(fā)現(xiàn)爹梁,通常情況下,drawText 方法是不支持 "\n\r" 的提澎∫看來我們只要改 drawLabel 這個方法就可以了。由于源碼幾個類的耦合程度比較高盼忌,我并沒有找到可以通過繼承重寫的方法實現(xiàn)积糯,不得已只好改源代碼了。
我修改后的代碼是這樣的:
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 坐標改一下,在第一行的基礎上加上一定的高度就可以了祠乃。妥妥的梦重。
另外,那條黃色的高亮線也是改源碼實現(xiàn)的亮瓷。
2017.2.19 更新
MPAndroidChart 3.0 之后改變比較大琴拧,和這里相關(guān)的主要有兩點:
- 取消了 LineData(List<String> xVals, List<ILineDataSet> dataSets) 這個構(gòu)造方法,不再傳 x 軸坐標數(shù)據(jù)嘱支,直接從 LineDataSet 坐標中獲取蚓胸。
- MPAndroidChart 底層繪制 x 軸坐標挣饥,是先格式化之后才執(zhí)行 drawLabel 這個方法。
處理的思路跟之前是一致的沛膳,可以用 ValueFormatter 格式化一下 x 軸的數(shù)據(jù)(比如用空格將要分行顯示的數(shù)據(jù)分開)扔枫,然后修改源碼 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 更新
補充一下高亮線的修改方法吧茧吊。
如果用的是不規(guī)則的高亮線(像圖片里面上下有個球的),需要用自己的圖片資源來替換八毯。為了適配搓侄,建議使用高度可以拉伸的點九圖。
我們先找到接口 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());
//優(yōu)先使用高亮線圖片顯示乳讥,沒有則使用默認樣式
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 ,舊版在細節(jié)上會有不同廓俭,但實現(xiàn)思路是一致的云石。
2017.4.1 更新
增加一個簡單的示例
效果如下: