前言
图表绘制可能是我们项目开发过程中比较常见的需求,简单点儿的需求,我们通过自定义控件就能完成,但是像那种比较复杂的图表,通过自定义的方式实现起来就比较麻烦了,这个时候,我们就需要借助第三方的开源库来实现。
Android 平台绘图的开源库有好几个,最成熟最出名的就属于***MPAndroidChart***了,他能帮我们实现曲线图、折线图、柱状图、饼状图,分布图等等。同时还可以实现混合图表。
-
曲线图
-
下方填充曲线图,可以设置纯色和渐变色
-
混合图表
-
柱状图
如上所示,实现的图表种类非常多,还没有列举完全,功能非常强大,正好最近在项目中有使用到MPAndroidChart,在此做一个总结。
项目中使用效果截图如下:
基础设置相关API
-
- Chart 的基础设置
// 设置是否绘制背景 mChart.setDrawGridBackground(false); // 设置是否绘制边框 mChart.setDrawBorders(false); // 设置是否可以缩放图表 mChart.setScaleEnabled(true); // 设置是否可以用手指移动图表 mChart.setDragEnabled(true);复制代码
注意:这里说一下后面2个属性
setScaleEnabled
和setDragEnabled
,设置图表是佛可以缩放和移动,当手机屏幕一屏显示不下时,我们希望能通过缩放或者滑动图表。但是经过试验,仅仅设置这两个属性是不行的,还需要配合Matrix来实现,代码如下:
Matrix matrix = new Matrix(); // x轴放大4倍,y不变 matrix.postScale(4.0f, 1.0f); // 设置缩放 mChart.getViewPortHandler().refresh(matrix, mChart, false);复制代码
- 2,图表描述相关设置
// 不显示描述数据 mChart.getDescription().setEnabled(true); mChart.getAxisRight().setEnabled(false); // 设置描述 mChart.getDescription().setText("text desc"); // 设置描述显示的位置,默认是显示在图表的右下角的 mChart.getDescription().setPosition(200,100);复制代码
- 3,是否显示右侧y轴
mChart.getAxisRight().setEnabled(false);复制代码
- 4, 图例相关设置
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);复制代码
- 5,x轴先关设置
XAxis xAxis = mChart.getXAxis(); // 是否显示x轴线 xAxis.setDrawAxisLine(true); // 设置x轴线的颜色 xAxis.setAxisLineColor(Color.parseColor("#4cffffff")); // 是否绘制x方向网格线 xAxis.setDrawGridLines(false); //x方向网格线的颜色 xAxis.setGridColor(Color.parseColor("#30FFFFFF")); // 设置x轴数据的位置 xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); // 设置x轴文字的大小 xAxis.setTextSize(12); // 设置x轴数据偏移量 xAxis.setYOffset(5); final Listlabels = 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); // return labels.get(Math.min(Math.max((int) value, 0), labels.size() - 1)); } }; // 引用标签 xAxis.setValueFormatter(formatter); // 设置x轴文字颜色 xAxis.setTextColor(mChart.getResources().getColor(R.color.char_text_color)); // 设置x轴每最小刻度 interval xAxis.setGranularity(1f);复制代码
6,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.setDrawGridLines(false); // 是否显示y轴坐标线 yAxis.setDrawZeroLine(true); // 设置y轴的文字颜色 yAxis.setTextColor(mChart.getResources().getColor(R.color.char_text_color)); // 设置y轴文字的大小 yAxis.setTextSize(12); // 设置y轴数据偏移量 //yAxis.setXOffset(30); // yAxis.setYOffset(-3); yAxis.setXOffset(15); // 设置y轴label 数量 yAxis.setLabelCount(5, false); // 设置y轴的最小刻度 yAxis.setGranularity(5);//interval复制代码
- 7,缩放和动画设置
Matrix matrix = new Matrix(); // 根据数据量来确定 x轴缩放大倍 if (mLabels.size() <= 10) { matrix.postScale(1.0f, 1.0f); } else if (mLabels.size() <= 15) { matrix.postScale(1.5f, 1.0f); } else if (mLabels.size() <= 20) { matrix.postScale(2.0f, 1.0f); } else { matrix.postScale(3.0f, 1.0f); } // 在图表动画显示之前进行缩放 mChart.getViewPortHandler().refresh(matrix, mChart, false); // x轴执行动画 mChart.animateX(500);复制代码
- 8, 无数据时,显示文案
/** * 显示无数据的提示 * * @param mChart */ public static void NotShowNoDataText(Chart mChart) { mChart.clear(); mChart.notifyDataSetChanged(); mChart.setNoDataText("你还没有记录数据"); mChart.setNoDataTextColor(Color.WHITE); // 记得最后要刷新一下 mChart.invalidate(); }复制代码
- 9,设置marker (1)首先需要继承MarkerView 实现一个自定义View
public class ChartMarkerView extends MarkerView { private TextView textView; /** * Constructor. Sets up the MarkerView with a custom layout resource. * * @param context * @param layoutResource the layout resource to use for the MarkerView */ public ChartMarkerView(Context context, int layoutResource) { super(context, layoutResource); textView = (TextView) findViewById(R.id.marker_view_text); } @Override public void refreshContent(Entry e, Highlight highlight) { float value = e.getY(); textView.setText(DecimalFormatUtils.getIntOrFloatRemainOne(value)); } @Override public MPPointF getOffsetForDrawingAtPoint(float posX, float posY) { return new MPPointF(-getWidth() / 2f, -getHeight() - 10); }}复制代码
(2), 通过addMaker
添加到图上
combinedChart.setMarker(new ChartMarkerView(context, R.layout.chart_markerview_layout));复制代码
以上就是对Chart 的一些基础设置,包括x,y轴改如何显示(颜色、大小)、图例、描述等等。
下面就看看如何来绘制曲线图呢?
数据设置
当我们要绘制曲线图、折线图、和柱状图等等的时候,怎样将我们要绘制的一个个数据点显示在图上的呢?MPAndroidChart 是用dataSet来表示的,我们需要将数据点包装成DataSet,然后设置到Chart,刷新绘制就ok了。MPAndroidChart 库中有很多DataSet,如下所示:
以曲线图或者折线图为例,我们使用LineDataSet:
/** * 获取LineDataSet * * @param entries * @param label * @param textColor * @param lineColor * @return */ public static LineDataSet getLineData(Listentries, String label, @ColorInt int textColor, @ColorInt int lineColor, boolean isFill) { LineDataSet dataSet = new LineDataSet(entries, label); // 设置曲线的颜色 dataSet.setColor(lineColor); //数值文字颜色 dataSet.setValueTextColor(textColor); // 模式为贝塞尔曲线 dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER); // 是否绘制数据值 dataSet.setDrawValues(false); // 是否绘制圆点 dataSet.setDrawCircles(true); dataSet.setDrawCircleHole(false); // 这里有一个坑,当我们想隐藏掉高亮线的时候,MarkerView 跟着不见了 // 因此只有将它设置成透明色 dataSet.setHighlightEnabled(true);// 隐藏点击时候的高亮线 //设置高亮线为透明色 dataSet.setHighLightColor(Color.TRANSPARENT); if (isFill) { //是否设置填充曲线到x轴之间的区域 dataSet.setDrawFilled(true); // 填充颜色 dataSet.setFillColor(lineColor); } //设置圆点的颜色 dataSet.setCircleColor(lineColor); // 设置圆点半径 dataSet.setCircleRadius(3.5f); // 设置线的宽度 dataSet.setLineWidth(1f); return dataSet; }复制代码
将数据绘制到图表上
如果是一条曲线,直接用LineChart 和LineData就好,如果有多条曲线,就需要用CombinedChart 和 CombinedData (混合图、混合数据)。可以是LineData 和BarData 混合来绘制曲线和柱状的混合图。
/** * 初始化数据 * * @param chart * @param lineDatas */ public static void initData(CombinedChart chart, LineData... lineDatas) { CombinedData combinedData = new CombinedData(); for (LineData lineData : lineDatas) { combinedData.setData(lineData); } chart.setData(combinedData); chart.invalidate(); }复制代码
最后调用invalidate 绘制。
柱状图
柱状图跟曲线图其实是一样的,图表基础配置一样,然后将数据点包装成BarData,最后包装成BarDataSet。
/** * 初始化柱状图图表数据 * @param chart * @param entries * @param title * @param barColor */ public static void initBarChart(BarChart chart, Listentries, String title, @ColorInt int barColor) { BarDataSet set1 = new BarDataSet(entries, title); set1.setValueTextColor(Color.WHITE); set1.setColor(barColor); ArrayList dataSets = new ArrayList<>(); dataSets.add(set1); BarData data = new BarData(dataSets); data.setValueTextSize(10f); // 设置bar的宽度,但是点很多少的时候好像没作用,会拉得很宽 data.setBarWidth(0.1f); // 设置value值 颜色 data.setValueTextColor(Color.WHITE); //设置y轴显示的标签 data.setValueFormatter(new IValueFormatter() { @Override public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { return ((int) (value * 100)) + "%"; } }); chart.setData(data); chart.invalidate(); }复制代码
工具类
项目中有很多出使用曲线图和柱状图的地方,因此抽取了一个工具类,完整代码如下:
public class MPChartUtils { /** * 不显示无数据的提示 * * @param mChart */ public static void NotShowNoDataText(Chart mChart) { mChart.clear(); mChart.notifyDataSetChanged(); mChart.setNoDataText("你还没有记录数据"); mChart.setNoDataTextColor(Color.WHITE); mChart.invalidate(); } /** * 配置Chart 基础设置 * @param mChart 图表 * @param mLabels x 轴标签 * @param yMax y 轴最大值 * @param yMin y 轴最小值 * @param isShowLegend 是否显示图例 */ public static void configChart(CombinedChart mChart, ListmLabels, float yMax, float yMin, boolean isShowLegend) { mChart.setDrawGridBackground(false); mChart.setDrawBorders(false); mChart.setScaleEnabled(false); mChart.setDragEnabled(true); mChart.setNoDataText(""); // 不显示描述数据 mChart.getDescription().setEnabled(false); mChart.getAxisRight().setEnabled(false); Legend legend = mChart.getLegend(); // 是否显示图例 if (isShowLegend) { legend.setEnabled(true); legend.setTextColor(Color.WHITE); legend.setForm(Legend.LegendForm.CIRCLE); legend.setHorizontalAlignment(Legend.LegendHorizontalAlignment.LEFT); legend.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM); legend.setOrientation(Legend.LegendOrientation.HORIZONTAL); legend.setYEntrySpace(20f); //图例的大小 legend.setFormSize(7f); // 图例描述文字大小 legend.setTextSize(10); legend.setXEntrySpace(20f); } else { legend.setEnabled(false); } XAxis xAxis = mChart.getXAxis(); // 是否显示x轴线 xAxis.setDrawAxisLine(true); // 设置x轴线的颜色 xAxis.setAxisLineColor(Color.parseColor("#4cffffff")); // 是否绘制x方向网格线 xAxis.setDrawGridLines(false); //x方向网格线的颜色 xAxis.setGridColor(Color.parseColor("#30FFFFFF")); // 设置x轴数据的位置 xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); // 设置x轴文字的大小 xAxis.setTextSize(12); // 设置x轴数据偏移量 xAxis.setYOffset(5); final List 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); // return labels.get(Math.min(Math.max((int) value, 0), labels.size() - 1)); } }; // 引用标签 xAxis.setValueFormatter(formatter); // 设置x轴文字颜色 xAxis.setTextColor(mChart.getResources().getColor(R.color.char_text_color)); // 设置x轴每最小刻度 interval xAxis.setGranularity(1f); 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.setDrawGridLines(false); // 是否显示y轴坐标线 yAxis.setDrawZeroLine(true); // 设置y轴的文字颜色 yAxis.setTextColor(mChart.getResources().getColor(R.color.char_text_color)); // 设置y轴文字的大小 yAxis.setTextSize(12); // 设置y轴数据偏移量 //yAxis.setXOffset(30); // yAxis.setYOffset(-3); yAxis.setXOffset(15); // yAxis.setGranularity(yGranularity); yAxis.setLabelCount(5, false); //yAxis.setGranularity(5);//interval Matrix matrix = new Matrix(); // 根据数据量来确定 x轴缩放大倍 if (mLabels.size() <= 10) { matrix.postScale(1.0f, 1.0f); } else if (mLabels.size() <= 15) { matrix.postScale(1.5f, 1.0f); } else if (mLabels.size() <= 20) { matrix.postScale(2.0f, 1.0f); } else { matrix.postScale(3.0f, 1.0f); } // 在图表动画显示之前进行缩放 mChart.getViewPortHandler().refresh(matrix, mChart, false); // x轴执行动画 mChart.animateX(500); } /** * 初始化数据 * * @param chart * @param lineDatas */ public static void initData(CombinedChart chart, LineData... lineDatas) { CombinedData combinedData = new CombinedData(); for (LineData lineData : lineDatas) { combinedData.setData(lineData); } chart.setData(combinedData); chart.invalidate(); } /** * 获取LineDataSet * * @param entries * @param label * @param textColor * @param lineColor * @return */ public static LineDataSet getLineData(List entries, String label, @ColorInt int textColor, @ColorInt int lineColor, boolean isFill) { LineDataSet dataSet = new LineDataSet(entries, label); // 设置曲线的颜色 dataSet.setColor(lineColor); //数值文字颜色 dataSet.setValueTextColor(textColor); // 模式为贝塞尔曲线 dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER); // 是否绘制数据值 dataSet.setDrawValues(false); // 是否绘制圆点 dataSet.setDrawCircles(true); dataSet.setDrawCircleHole(false); // 这里有一个坑,当我们想隐藏掉高亮线的时候,MarkerView 跟着不见了 // 因此只有将它设置成透明色 dataSet.setHighlightEnabled(true);// 隐藏点击时候的高亮线 //设置高亮线为透明色 dataSet.setHighLightColor(Color.TRANSPARENT); if (isFill) { //是否设置填充曲线到x轴之间的区域 dataSet.setDrawFilled(true); // 填充颜色 dataSet.setFillColor(lineColor); } //设置圆点的颜色 dataSet.setCircleColor(lineColor); // 设置圆点半径 dataSet.setCircleRadius(3.5f); // 设置线的宽度 dataSet.setLineWidth(1f); return dataSet; } /** * 获取barDataSet * @param entries * @param label * @param textColor * @param lineColor * @return */ public static BarDataSet getBarDataSet(List entries, String label, @ColorInt int textColor, @ColorInt int lineColor) { BarDataSet dataSet = new BarDataSet(entries, label); dataSet.setBarBorderWidth(5); dataSet.setBarShadowColor(lineColor); dataSet.setValueTextColor(textColor); dataSet.setDrawValues(false); return dataSet; } /** * 配置柱状图基础设置 * @param barChart * @param xLabels */ public static void configBarChart(BarChart barChart, final List xLabels) { barChart.getDescription().setEnabled(false);//设置描述 barChart.setPinchZoom(false);//设置按比例放缩柱状图 barChart.setScaleEnabled(false); barChart.setDragEnabled(true); barChart.setNoDataText(""); // 没有数据时的提示文案 //x坐标轴设置 // IAxisValueFormatter xAxisFormatter = new StringAxisValueFormatter(xAxisValue);//设置自定义的x轴值格式化器 XAxis xAxis = barChart.getXAxis();//获取x轴 xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);//设置X轴标签显示位置 xAxis.setDrawGridLines(false);//不绘制格网线 xAxis.setGranularity(1f);//设置最小间隔,防止当放大时,出现重复标签。 // 显示x轴标签 IAxisValueFormatter formatter = new IAxisValueFormatter() { @Override public String getFormattedValue(float value, AxisBase axis) { return xLabels.get(Math.min(Math.max((int) value, 0), xLabels.size() - 1)); } }; xAxis.setValueFormatter(formatter); xAxis.setTextSize(10);//设置标签字体大小 xAxis.setTextColor(barChart.getResources().getColor(R.color.char_text_color)); xAxis.setAxisLineColor(Color.parseColor("#4cffffff")); xAxis.setLabelCount(xLabels.size());//设置标签显示的个数 //y轴设置 YAxis leftAxis = barChart.getAxisLeft();//获取左侧y轴 leftAxis.setPosition(YAxis.YAxisLabelPosition.OUTSIDE_CHART);//设置y轴标签显示在外侧 leftAxis.setAxisMinimum(0f);//设置Y轴最小值 leftAxis.setDrawGridLines(false); leftAxis.setDrawLabels(true);//禁止绘制y轴标签 leftAxis.setDrawAxisLine(false);//禁止绘制y轴 leftAxis.setAxisLineColor(Color.parseColor("#4cffffff")); leftAxis.setTextColor(barChart.getResources().getColor(R.color.char_text_color)); leftAxis.setValueFormatter(new IAxisValueFormatter() { @Override public String getFormattedValue(float value, AxisBase axis) { return ((int) (value * 100)) + "%"; } }); barChart.getAxisRight().setEnabled(false);//禁用右侧y轴 barChart.getLegend().setEnabled(false); //图例设置 /* Legend legend = barChart.getLegend(); legend.setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER);//图例水平居中 legend.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP);//图例在图表上方 legend.setOrientation(Legend.LegendOrientation.HORIZONTAL);//图例的方向为水平 legend.setDrawInside(false);//绘制在chart的外侧 legend.setDirection(Legend.LegendDirection.LEFT_TO_RIGHT);//图例中的文字方向 legend.setForm(Legend.LegendForm.SQUARE);//图例窗体的形状 legend.setFormSize(0f);//图例窗体的大小 legend.setTextSize(16f);//图例文字的大小*/ //legend.setYOffset(-2f); Matrix matrix = new Matrix(); // 根据数据量来确定 x轴缩放大倍 if (xLabels.size() <= 10) { matrix.postScale(1.0f, 1.0f); } else if (xLabels.size() <= 15) { matrix.postScale(1.5f, 1.0f); } else if (xLabels.size() <= 20) { matrix.postScale(2.0f, 1.0f); } else { matrix.postScale(3.0f, 1.0f); } barChart.getViewPortHandler().refresh(matrix, barChart, false); barChart.setExtraBottomOffset(10);//距视图窗口底部的偏移,类似与paddingbottom barChart.setExtraTopOffset(30);//距视图窗口顶部的偏移,类似与paddingtop barChart.setFitBars(true);//使两侧的柱图完全显示 barChart.animateX(1500);//数据显示动画,从左往右依次显示 } /** * 初始化柱状图图表数据 * @param chart * @param entries * @param title * @param barColor */ public static void initBarChart(BarChart chart, List entries, String title, @ColorInt int barColor) { BarDataSet set1 = new BarDataSet(entries, title); set1.setValueTextColor(Color.WHITE); set1.setColor(barColor); ArrayList dataSets = new ArrayList<>(); dataSets.add(set1); BarData data = new BarData(dataSets); data.setValueTextSize(10f); // 设置bar的宽度,但是点很多少的时候好像没作用,会拉得很宽 data.setBarWidth(0.1f); // 设置value值 颜色 data.setValueTextColor(Color.WHITE); //设置y轴显示的标签 data.setValueFormatter(new IValueFormatter() { @Override public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { return ((int) (value * 100)) + "%"; } }); chart.setData(data); chart.invalidate(); }}复制代码
最终使用
最后,调用工具类的三个方法,三行代码就ok :
// 1.配置基础图表配置 MPChartUtils.configChart(mWeightChart, xLabels, maxWeight, minWeight, true); // 2,获取数据Data,这里有2条曲线 LineDataSet tartgetDataSet = MPChartUtils.getLineData(targetEntries, "目标体重", Color.WHITE, Color.WHITE, false); LineDataSet lineDataSet = MPChartUtils.getLineData(weightEntries, "当前体重", Color.WHITE, getResources().getColor(R.color.chart_dot_color), false); // 3,初始化数据并绘制 MPChartUtils.initData(mWeightChart, new LineData(lineDataSet, tartgetDataSet)); 复制代码
更多Android干货文章,关注公众号 【Android技术杂货铺】