基于MPAndroidChart实现股票K线图之自定义Y轴标签

1 前言

BarChart用来显示股票成交量。由于股票交易成交量数额巨大,且在滑动查看K线图历史数据时,BarChart的Y轴标签需要动态变化,原生的chart.setAutoScaleMinMaxEnabled(true);只是实现了标签的数值大小适配滑动,但是下图这种更加合理的效果(股/万股/亿股的动态显示)却无法做到。

↑请看左下角

本文就从源码出发,尝试实现这种动态化Y轴标签。

2 基本思路

基于MPAndroidChart实现股票K线图之自定义HighLight样式及源码浅析这篇文章提到不同的Renderer类负责绘制整个Chart的不同部分,自然本文的重点也是在负责Y轴绘制逻辑的YAxisRenderer

整体上看,绘制的过程是:
1、根据可见的直方图内的最大值和最小值,计算出要在Y轴上显示的label数组(是一个float数组);
2、确定label的x坐标 ;
3、根据计算好的位置,绘制label数组里的数字。
以上对应YAxisRenderer类里的方法是:

  1. computeAxis(float yMin, float yMax);
    根据框架使用者设置的labelCount,从可见范围内的最小到最大值等差地生成一个label 数组(是一个float数组)mEntries
  2. renderAxisLabels(Canvas c);
    根据框架使用者设置的 AxisDependency(LEFT or RIGHT)
    以及 YAxisLabelPosition(OUTSIDE_CHART or INSIDE_CHART)来确定label的x坐标。
  3. drawYLabels(Canvas c, float fixedPosition, float[] positions, float offset);
    通过canvas.drawText( String text, float x, float y, Paint paint)进行绘制。
   protected void drawYLabels(Canvas c, float fixedPosition, float[] positions, float offset) {

        // draw
        for (int i = 0; i < mYAxis.mEntryCount; i++) {
//调用mYAxisValueFormatter.getFormattedValue(mEntries[index], this)得到绘制的文本
            String text = mYAxis.getFormattedLabel(i);

            if (!mYAxis.isDrawTopYLabelEntryEnabled() && i >= mYAxis.mEntryCount - 1)
                return;

            c.drawText(text, fixedPosition, positions[i * 2 + 1] + offset, mAxisLabelPaint);
        }
    }

以上三个方法由上向下也是代码执行的顺序。而且computeAxis(float yMin, float yMax);计算得到的float数组mEntries,经过mYAxisValueFormatter格式化以后的文本才是被绘制的label文本。
那么理论上说,只要在mEntries确定之后,drawYLabels()发生之前,动态地去设置YAxisValueFormatter就能够达到文章开头想要的效果。

3 实践

3.1 YAxisValueFormatter

已知成交量直方图Y轴标签的规则是 基于可见范围内的最大值来确定量级:

  1. 成交量<10000,显示 xx股;
  2. 成交量>10000 && 成交量<100000000,显示 xx万股;
  3. 成交量>100000000,显示 xx亿股。
    所以YAxisValueFormatter 的实现如下:
public class CustomYAxisFormatter implements YAxisValueFormatter {
    private static final int TEN_THOUSAND = 10000;
    private static final int HUNDRED_MILLION = 100000000;

    private String unit;
    private int divisor;

    public USVolumeYAxisFormatter(float maxVolume) {
        this.unit = getUnit(maxVolume);
        this.divisor = getDivisor(maxVolume);
    }

    @Override
    public String getFormattedValue(float value, YAxis yAxis) {
        if (value == 0) {
//坐标原点,显示单位(股/万股/亿股)
            return unit;
        }
        return String.format("%.2f", value / divisor);
    }

    /** 根据最大值的大小来确定单位*/
    public String getUnit(double num) {
        int e = (int) Math.floor(Math.log10(num));
        if (e >= 8) {
            return "亿股";
        } else if (e >= 4) {
            return "万股";
        } else {
            return "股";
        }
    }

    /** 根据最大值的大小来确定除数*/
    public int getDivisor(float maxValue) {
        int e = (int) Math.floor(Math.log10(maxValue));
        if (e >= 8) {
            return HUNDRED_MILLION;
        } else if (e >= 4) {
            return TEN_THOUSAND;
        } else {
            return 1;
        }
    }
}

3.2 YAxisRenderer

然后按照前面所述,在YAxisRenderer 的computeAxis()完成label数组的计算后,根据最大值来动态设置YAxisValueFormatter

public class CustomeYAxisRenderer extends YAxisRenderer {

    public CustomeYAxisRenderer(ViewPortHandler viewPortHandler, YAxis yAxis, Transformer trans) {
        super(viewPortHandler, yAxis, trans);
    }

    @Override
    public void computeAxis(float yMin, float yMax) {
        super.computeAxis(yMin, yMax);
        if (mYAxis.mEntryCount > 1) {
            float topLabel = Math.abs(mYAxis.mEntries[mYAxis.mEntryCount - 1]);
            mYAxis.setValueFormatter(new USVolumeYAxisFormatter(topLabel));
        }
    }
}

3.3 chart.setRendererLeftYAxis

最后,在BarChart控件初始化的时候,将自定义的YAxisRenderer设置上去:

CustomeYAxisRenderer yAxisRenderer = new CustomeYAxisRenderer (barChart.getViewPortHandler(), barChart.getAxisLeft(), barChart.getTransformer(YAxis.AxisDependency.LEFT));
chart.setRendererLeftYAxis(yAxisRenderer);

大功告成!


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

推荐阅读更多精彩内容