給 Android 圖表庫 MPAndroidChart 的坐標換行

做統(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)的主要有兩點:

  1. 取消了 LineData(List<String> xVals, List<ILineDataSet> dataSets) 這個構(gòu)造方法,不再傳 x 軸坐標數(shù)據(jù)嘱支,直接從 LineDataSet 坐標中獲取蚓胸。
  2. 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 更新

增加一個簡單的示例

效果如下:

源碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市研乒,隨后出現(xiàn)的幾起案子汹忠,更是在濱河造成了極大的恐慌,老刑警劉巖雹熬,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宽菜,死亡現(xiàn)場離奇詭異,居然都是意外死亡竿报,警方通過查閱死者的電腦和手機铅乡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烈菌,“玉大人阵幸,你說我怎么就攤上這事⊙渴溃” “怎么了侨嘀?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捂襟。 經(jīng)常有香客問我咬腕,道長,這世上最難降的妖魔是什么葬荷? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任涨共,我火速辦了婚禮纽帖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘举反。我一直安慰自己懊直,他們只是感情好,可當我...
    茶點故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布火鼻。 她就那樣靜靜地躺著室囊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪魁索。 梳的紋絲不亂的頭發(fā)上融撞,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機與錄音粗蔚,去河邊找鬼尝偎。 笑死,一個胖子當著我的面吹牛鹏控,可吹牛的內(nèi)容都是我干的致扯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼当辐,長吁一口氣:“原來是場噩夢啊……” “哼抖僵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起缘揪,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤裆针,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后寺晌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡澡刹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年呻征,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罢浇。...
    茶點故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡陆赋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嚷闭,到底是詐尸還是另有隱情攒岛,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布胞锰,位于F島的核電站灾锯,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嗅榕。R本人自食惡果不足惜顺饮,卻給世界環(huán)境...
    茶點故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一吵聪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧兼雄,春花似錦吟逝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至佃乘,卻和暖如春囱井,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恕稠。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工琅绅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鹅巍。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓千扶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親骆捧。 傳聞我的和親對象是個殘疾皇子澎羞,可洞房花燭夜當晚...
    茶點故事閱讀 45,675評論 2 359

推薦閱讀更多精彩內(nèi)容