MPAndroidChart 开发使用总结及采坑记录

背景

本文介绍了MPAndroidChart 使用时中通用的参数设置和三种图标的实现案例思路和采坑总结,供你参考。

一. MPAndroidChart 之柱状图开发总结

1.1 设置不同的图表

  • 折线图 LineChart

  • 条形图 BarChart

  • 条形折线图 Combined-Chart

  • 圆饼图 PieChart

  • 雷达图 ScatterChart

  • K线图 CandleStickChart

  • 泡泡图 BubbleChart

  • 网状图 RadarChart

1.2 BarChart 的使用流程

使用流程如下:

  • 得到BarChart对象 并初始化
  • 得到BarEntry对象,此处添加(X,Y)值
  • 得到BarDataSet对象,添加BarEntry对象
  • 得到BarData对象,添加BarDaraSet对象
  • 显示柱状图 BarChart.setData(BarData)

常用的参数设置相关 API:

1.3 Chart 的基础设置

       // 设置是否绘制背景
        mChart.setDrawGridBackground(false);
        // 设置是否绘制边框
        mChart.setDrawBorders(false);
        // 设置是否可以缩放图表
        mChart.setScaleEnabled(true);
        // 设置是否可以用手指移动图表
        mChart.setDragEnabled(false);

				// 设置没有数据的展示样式
        mChart.setNoDataText(“没有数据”)
        mChart.setNoDataTextColor(Color.parseColor("#FFFFFFFF")

1.4 图表描述相关设置

// 不显示描述数据
mChart.getDescription().setEnabled(false);
// 设置描述
 mChart.getDescription().setText("desc");

1.5 是否显示右侧 y 轴

 mChart.getAxisRight().setEnabled(false);
 mChart.getAxisLeft().setEnabled(false); //同理,是否显示左侧 y 轴

设置 为 false 后 y 轴和相关的刻度数值一起消失。

1.6 图例相关设置

    Legend legend = mChart.getLegend();
   //是否显示
    legend.setEnabled(true);
    //图例样式:有圆点,正方形,短线 几种样式
    legend.setForm(Legend.LegendForm.CIRCLE);
    // 图例显示的位置:如下2行代码设置图例显示在左下角
    legend.setHorizontalAlignment(Legend.LegendHorizontalAlignment.LEFT);
    legend.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM);
    // 图例的排列方式:水平排列和竖直排列2种
    legend.setOrientation(Legend.LegendOrientation.HORIZONTAL);
    // 图例距离x轴的距离
    legend.setXEntrySpace(10f);
    //图例距离y轴的距离
    legend.setYEntrySpace(10f);
     //图例的大小
     legend.setFormSize(7f);
     // 图例描述文字大小
     legend.setTextSize(10);

在项目中一般是隐藏处理,不显示。

1.7 x 轴设置

   XAxis xAxis = mChart.getXAxis();
		// 是否显示 x 轴线
    xAxis.setDrawAxisLine(true);
    // 设置 x 轴线的颜色(第一条)
    xAxis.setAxisLineColor(Color.parseColor("#FFFFFFFF"));
    // 是否绘制 x 方向网格线
    xAxis.setDrawGridLines(false);
    // x 方向网格线的颜色
    xAxis.setGridColor(Color.parseColor("#FFFFFFFF"));

		// 背景用虚线表格来绘制  给整成虚线
    xAxis.enableGridDashedLine(5f, 5f, 0f)

    // 设置x轴数据的位置
    xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
    // 设置x轴文字的大小
    xAxis.setTextSize(12);

    // 设置x轴数据偏移量
    xAxis.setYOffset(5);

    final List<String> labels = mLabels;
    // 显示 x 轴标签
    IAxisValueFormatter formatter = new IAxisValueFormatter() {

        @Override
        public String getFormattedValue(float value, AxisBase axis) {
            int index = (int) value;
            if (index < 0 || index >= labels.size()) {
                return "";
            }
            return labels.get(index);
        }

    };
    // 引用标签 (X轴的标签自定义)
    xAxis.setValueFormatter(formatter);
    // 设置x轴文字颜色
    xAxis.setTextColor(Color.parseColor("#FFFFFFFF"));
    // 设置x轴每最小刻度 interval
    xAxis.setGranularity(1f);

1.8 Y 轴设置

       YAxis yAxis = mChart.getAxisLeft();
        //设置 x 轴的最大值
        yAxis.setAxisMaximum(yMax);
        // 设置 y 轴的最大值
        yAxis.setAxisMinimum(yMin);
        // 不显示 y 轴
        yAxis.setDrawAxisLine(false);
        // 设置 y 轴数据的位置
        yAxis.setPosition(YAxis.YAxisLabelPosition.OUTSIDE_CHART);
        // 不从 y 轴发出横向直线
        yAxis.se tDrawGridLines(false);
        // 是否显示 y 轴坐标线
        yAxis.setDrawZeroLine(true);
        // 设置 y 轴的文字颜色
        yAxis.setTextColor(mChart.getResources().getColor(R.color.char_text_color));
        // 设置 y 轴文字的大小
        yAxis.setTextSize(12);
        // 设置y轴数据偏移量
 
        yAxis.setXOffset(15);
        // 设置y轴label 数量
        yAxis.setLabelCount(5, false);
        // 设置y轴的最小刻度
        yAxis.setGranularity(5);

其他设置,后期根据项目使用情况继续完善。

二、BarChart 开发实例

2.1 实现某一水平的高亮柱状图

实现效果:

水平图

  1. 布局使用
<com.github.mikephil.charting.charts.BarChart
    android:id="@+id/levelBarChart"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
  1. 初始化
private fun initLevelChart() {
     barChart = binding.levelBarChart
        barChart.apply {
            // 添加 padding 防止某些情景显示不完整
            extraTopOffset = 50f
            extraBottomOffset = 5f
            // 隐藏右下角英文
            description.isEnabled = false
            setTouchEnabled(false)
            setScaleEnabled(false)
            legend.isEnabled = false
            setBackgroundColor(Color.TRANSPARENT)
            // 不显示与图表网格线
            setDrawGridBackground(false)

            isClickable = false
            isEnabled = false

            setPinchZoom(true)
            data = BarData()
            // 边界
            setDrawBorders(false)
           // 没有数据时的文本设置
            setNoDataText(“暂无数据”)
            setNoDataTextColor(Color.White)
        }

        // x 轴样式设置
        barChart.xAxis.apply {
            // 不显示 x 轴网格线
            setDrawGridLines(false)
            // 不显示 X 轴线条
            setDrawAxisLine(false)
						// 背景用虚线表格来绘制  给整成虚线
            enableGridDashedLine(5f, 5f, 0f)

            // x 轴的颜色第一条颜色隐藏
            axisLineColor = R.color.transparent

            setLabelCount(8, true)

            axisLineWidth = 1f
            // 最小间距
            granularity = 1f
            // X 轴坐标的个数
            labelCount = 7
            // X 轴的位置 默认为上面
            position = XAxis.XAxisPosition.BOTTOM
            // X 轴上字的颜色值(eg: 小白、新手……)
            textColor = R.color.transparent
            textSize = 10f

            // 设置字体
            typeface = xTf
            valueFormatter = object : ValueFormatter() {
                // X 轴自定义坐标
                override fun getFormattedValue(value: Float): String {
                    return trainingLevel[value.toInt()]
                }
            }
        }

        // y 轴样式设置
        barChart.axisLeft.apply {
            // 显示 y 轴值
            isEnabled = false
            // 不显示 X 轴 Y轴线条
            setDrawAxisLine(false)
            // 设置背景表格线的颜色
            gridColor = R.color.white
            textColor = R.color.white
            textSize = 8f
            setPosition(YAxis.YAxisLabelPosition.OUTSIDE_CHART)
            // 默认宽度 避免不同值 UI 变化(xx%显示的宽度)
            minWidth = 28f
            // 保证 Y 轴从 0 开始,不然会上移一点
            axisMinimum = 0f
            setLabelCount(4, false)
            valueFormatter = object : ValueFormatter() {
                // Y 轴自定义坐标
                override fun getFormattedValue(value: Float): String {
                    return "${value.toInt()} %"
                }
            }
        }
        // 隐藏右侧 Y 轴   默认是左右两侧都有 Y 轴
        barChart.axisRight.isEnabled = false
    }
  1. 更新数据,显示图表

    private fun setChartData(list: ArrayList<Int>) { 
           if (list.isEmpty()) {
                barChart.clear()
                return
            }
            var yAxisMaximum = 40
            val entries = ArrayList<BarEntry>()
    
            list.forEachIndexed { index, i ->
                entries.add(BarEntry(index.toFloat(), i.toFloat()))
                yAxisMaximum = max(i, yAxisMaximum)
            }
    
            // 避免超出越界
            barChart.axisLeft.axisMaximum = yAxisMaximum.toFloat() * 1.1f
            barChart.xAxis.labelCount = recordsList.size
    
            if (barChart.data != null && barChart.data.dataSetCount > 0) {
                // 更新
                val barDataSet = barChart.data.getDataSetByIndex(0) as BarDataSet
                barDataSet.values = entries
                barChart.data.notifyDataChanged()
                barChart.notifyDataSetChanged()
            } else {
                BarDataSet(entries, "").apply {
                    barChart.data = BarData(listOf(this)).apply {
                        // 柱宽 百分比
                        barWidth = 0.9f
                        this.isHighlightEnabled = true
                    }
                    // 设置柱状的颜色值
                    color = R.color.white
                    // 设置选中的颜色
                    highLightColor = R.color.white
                    setDrawValues(false)
                }
            }
            // 设置高亮 第 x 个位置显示高亮
            val highlight = Highlight(highlightPos.toFloat(), 0f, 0)
            barChart.highlightValue(highlight, false)
    
            barChart.animateY(1500)
            barChart.invalidate()
        }
    

    注意点:

    1. 当没没有图表数据时 调用 barChart.clear()即可,此时会根据初始化中显示没有数据数据的样式。此处无需再调用barChart.invalidate(),

    网上有文章介绍说再调用一次barChart.invalidate,根据源码分析,此处多余。源码如下:

        public void clear() {
            this.mData = null;
            this.mOffsetsCalculated = false;
            this.mIndicesToHighlight = null;
            this.mChartTouchListener.setLastHighlighted((Highlight)null);
            this.invalidate();
        }
    
    1. 在想要某个柱状图高亮时的处理:

      // 1. 在BarDataSet数据中设置是否高亮
      BarDataSet.setDrawValues(false)
       //2.设置高亮的颜色值
      BarDataSet.color = R.color.white
      
      // 3.高亮的位置 
      val highlight = Highlight(highlightPos.toFloat(), 0f, 0)
       barChart.highlightValue(highlight, false)
      

2.2 实现训练的曲线图

近期训练图

  1. 初始化的流程,略

  2. 更新数据

    private void updateChart(List<MuscleRecord> records) {
        final ArrayList<Entry> entryList = new ArrayList<>();
        int maxWeight = 100;

        for (int i = 0; i < records.size(); i++) {
            maxWeight = Math.max(records.get(i).weight, maxWeight);
            // x 的值根据获取到服务器返回的时间戳去计算当前的 在最近一个月中的位置 0 - 30
            String time = DateTimeUtil.getMonthAndDay(records.get(i).trainingDate);
            int dayNum = getXValueDayNum(time);

            Entry entry = new Entry();
            if (dayNum >= MIN_LATELY_DAY && dayNum <= MAX_LATELY_DAY) {
                // 查询成功设置 x为当前的位置,查询失败 则默认生成x的位置。
                entry.setX(dayNum);
            }
            entry.setY(records.get(i).weight);
          // 设置坐标背景图
            entry.setIcon(ContextCompat.getDrawable(AppContext.getContext(),
                    xxx.getPointBackground(i)));
            entryList.add(entry);
        }
        // 注意:倒序日期显示时 前一个坐标值必须小于后一个坐标值,故升序处理
        Collections.sort(entryList, new EntryXComparator());
        updateLineChart(maxWeight, entryList);
    }

    private void updateLineChart(int maxWeight, ArrayList<Entry> entryList) {
        LogUtil.d(TAG, "updateLineChart,maxWeight: " + maxWeight + ",entryList size: " + entryList.size());
        mLeftAxis.setAxisMaximum((float) (maxWeight * 1.1));
        mDataSet.setValues(entryList);

        LineData data = new LineData();
        data.addDataSet(mDataSet);
        mLineChart.setData(data);
        // 更新界面绘制
        mLineChart.invalidate();
    }

    private String getTimeFormat(int value) {
        return mLastListDays.get(value);
    }

    /**
     * 根据时间得到对应 x 坐标值的位置
     * @param time 时间
     * @return 当前所在时间列表中的位置
     */
    private int getXValueDayNum(String time) {
        for (int dayNum = 0; dayNum < mLastListDays.size(); dayNum++) {
            if (time.equals(mLastListDays.get(dayNum))) {
                return dayNum;
            }
        }
        return -1;
    }

    /**
     * 获取最近 30 天的日期列表
     */
    private void initLatelyDays() {
        DateTime dateTime = new DateTime();
        for (int dayNum = 30; dayNum >= 0; dayNum--) {
            String time = dateTime.minusDays(dayNum).getMonthOfYear() + "月"
                    + dateTime.minusDays(dayNum).getDayOfMonth() + “日”;
            mLastListDays.add(time);
        }
    }
  1. 实现思路关键点小结:
1. 日历的时间:倒序 3.26 --> 4.26   list size: 30
2. x 坐标的顺序:0 - 30  --> 转换成 getFormat(value):0个: 3.2630 个: 4.26
3. x 顺序 3.26 --> 4.26 
  服务返回的绘制点顺序: 如 4.26 查询到位置为第 30,设置对应 x 坐标位置为 30
4.【重要】最后绘制时需要特别注意:在 x 的坐标的顺序,必须按升序排列,否则会出现找不到绘制坐标点的问题。
  1. 关于坐标的排序处理有两种方式:
  • 第一种实现方式:自定义实现
// 注意:倒序日期显示时 前一个坐标值必须小于后一个坐标值,故升序处理
Collections.sort(entryList, (entry1, entry2) -> (int) entry1.getX() - (int) entry2.getX());
  • 第二种实现方式:开源自带的方法
Collections.sort(entryList, new EntryXComparator());

public class EntryXComparator implements Comparator<Entry> {
    public EntryXComparator() {
    }

    public int compare(Entry entry1, Entry entry2) {
        float diff = entry1.getX() - entry2.getX();
        if (diff == 0.0F) {
            return 0;
        } else {
            return diff > 0.0F ? 1 : -1;
        }
    }
}

2.3 实现训练记录点状图

实现效果:

测试记录1

核心要点:数据根据数量多少实现居中

图表类型参考:气泡图

image-20210805155734732

关于居中的 x 轴显示居中的处理要点:

  1. x 轴 在初始化需要设置

    bubbleChart.xAxis.axisMinimum = 0.5f // 不要设置为0 否则 x 轴 会从 0 显示
    
  2. 在绘制点的处理时,注意从 第 1 个数据开始添加

    entries.add(
                    BubbleEntry(
                        // 从第一个开始绘制
                        (index + 1).toFloat(),
                        i.toFloat(),
                        size,
                        R.drawable.bg_circle_blue // 背景图
                    )
                )
    
  3. 在更新时设置

    // 通过设置最大值的偏移量 实现居中
    bubbleChart.xAxis.axisMaximum = entries.size.toFloat() + 0.5f // 根据数据的大小+ 0.5f 偏移量
    
  4. 最后代码实现代码

    // 图表数据设置
    private fun updateBubbleChart(recordList: ArrayList<Int>) {
            if (recordList.isEmpty()) {
                bubbleChart.clear()
                return
            }
            var yMaxValue = 20f
      			// 此处的大小为气泡图的大小,会随数据大小而动态变化,这里没有用,隐藏处理了
            val size = 1f  
            val entries = ArrayList<BubbleEntry>()
            // 根据数据量的大小居中显示
            recordList.forEachIndexed { index, i ->
                entries.add(
                    BubbleEntry(
                        // 从第一个开始绘制
                        (index + 1).toFloat(),
                        i.toFloat(),
                        size,
                      // 实际显示的蓝色小圆点
                        R.drawable.bg_circle_blue)
                    )
                )
                yMaxValue = max(i.toFloat(), yMaxValue)
            }
    
            val set = BubbleDataSet(entries, "").apply {
                // 设置自定义的蓝色图标
                setDrawIcons(true)
                // 思路:控制气泡的背景色为透明,从而实现对气泡隐藏功能
                color = ResourcesUtil.getColor(R.color.transparent)
            }
            val dataSets = ArrayList<IBubbleDataSet>()
            dataSets.add(set)
            val data = BubbleData(dataSets)
      			// 不显示值 
            data.setDrawValues(false)
    
            bubbleChart.axisLeft.axisMaximum = yMaxValue * 1.1f
    
            bubbleChart.xAxis.apply {
                labelCount = entries.size
                // 通过设置最大值的偏移量 实现居中
                axisMaximum = entries.size.toFloat() + 0.5f
    
                valueFormatter = object : ValueFormatter() {
                    // X 轴自定义坐标
                    override fun getFormattedValue(value: Float): String {
                        return “自定义显示逻辑”
                    }
                }
            }
            bubbleChart.data = data
      			// x y 轴绘制动画
            bubbleChart.animateXY(1500, 1500)
            bubbleChart.invalidate()
        }
    

三 、FAQ

3.1 如何实现自定义字体?

实现方案:

  Typeface tf = Typeface.createFromAsset(this.getAssets(), "fonts/worksenslight.ttf");
 xAxis.setTypeface(tf);

参考资料

三个优秀的 Android 图表开源控件

气泡图使用场景

MPAndroidChart之柱状图开发总结

MPAndroidChart issues

MPAndroidChart实现折线图不同区间范围的折线不同的颜色

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
要将横坐标设置为 List 储存的时间,并让时间分成两行显示(日期和时分),你可以自定义一个 `IAxisValueFormatter` 来实现这个功能。下面是一个示例代码: ```java import com.github.mikephil.charting.components.AxisBase; import com.github.mikephil.charting.formatter.IAxisValueFormatter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Locale; public class DateTimeAxisValueFormatter implements IAxisValueFormatter { private List<String> timeList; private SimpleDateFormat dateFormat = new SimpleDateFormat("MM.dd\nHH:mm", Locale.getDefault()); public DateTimeAxisValueFormatter(List<String> timeList) { this.timeList = timeList; } @Override public String getFormattedValue(float value, AxisBase axis) { int index = (int) value; if (index >= 0 && index < timeList.size()) { String time = timeList.get(index); try { Date date = dateFormat.parse(time); return dateFormat.format(date); } catch (ParseException e) { e.printStackTrace(); } } return ""; } } ``` 然后,在你的 `LineChart` 的配置中,设置 `xAxis` 的 `valueFormatter` 为自定义的 `DateTimeAxisValueFormatter` 对象: ```java LineChart lineChart = findViewById(R.id.lineChart); LineData lineData = new LineData(); // 假设你的时间数据存储在 List<String> timeList 中 // 设置 x 轴的值格式化器为自定义的 DateTimeAxisValueFormatter XAxis xAxis = lineChart.getXAxis(); xAxis.setValueFormatter(new DateTimeAxisValueFormatter(timeList)); // 设置 x 轴的数值为索引值 for (int i = 0; i < timeList.size(); i++) { lineData.addEntry(new Entry(i, yValues.get(i))); } lineChart.setData(lineData); ``` 在以上代码示例中,`DateTimeAxisValueFormatter` 类实现了 `IAxisValueFormatter` 接口,并通过 `SimpleDateFormat` 格式化时间字符串。我们将时间字符串列表传入构造函数,然后在 `getFormattedValue()` 方法中根据索引获取对应的时间字符串,并格式化为两行显示的格式。 接下来,我们在 `LineChart` 的配置中,将 x 轴的 `valueFormatter` 设置为自定义的 `DateTimeAxisValueFormatter` 对象,并将时间数据转换为索引值添加到 `LineData` 中。 希望这可以满足你的需求。如果你还有其他问题,请随时追问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小羊子说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值