上一篇文章我们具体介绍了使用MPAndroidChart来如何画饼图,结合项目经验来谈使用MPAndoridChart的总结(一)
这篇文章我们来看看如何画折线图。
画折线图
效果如上。
我们使用LineChart这个类。
1、在布局文件中,
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/lineChart"
android:layout_width="match_parent"
android:layout_height="250dp"/>
2、java代码,
我们可以根据我们最终想要的效果进行一系列的设置。
下面举例:
lineChart.setDrawBorders(true); //启用后,将渲染边框矩形。如果启用此选项,则无法绘制x轴和y轴的轴线。默认为false。
lineChart.setAutoScaleMinMaxEnabled(true); //指示是否启用y轴上的自动缩放的标志。这对于显示财务数据的图表尤其有用。默认为false。
注:Chart是PieChart和LineChart的基类,类似于setExtraLeftOffset、getDescription这些方法是Chart中的方法,因此PieChart和LineChart都可以调用。
涉及到折线图,那就必须讲到x轴和y轴了,x轴和y轴分别对应的类是XAxis和YAxis。
X轴只有一条,Y轴有两条。
XAxis xAxis = lineChart.getXAxis(); //得到X轴
YAxis leftYAxis = lineChart.getAxisLeft(); //得到左侧的Y轴
YAxis rightYAxis = lineChart.getAxisRight(); //得到右侧的Y轴
想要隐藏,只需调用setEnabled(false)即可。
xAxis.setEnabled(false); //不显示X轴
leftYAxis.setEnabled(false); //不显示左侧Y轴
rightYAxis.setEnabled(false); //不显示右侧Y轴
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); //设置X轴的位置在下方,默认在上方
对应的源码如下:
/**
* sets the position of the x-labels
*
* @param pos
*/
public void setPosition(XAxisPosition pos) {
mPosition = pos;
}
/**
* the position of the x-labels relative to the chart
*/
private XAxisPosition mPosition = XAxisPosition.TOP;
xAxis.setDrawGridLines(false); //设置不显示X轴对应的网格线(即垂直方向上的网格线),默认是显示的
leftYAxis.setDrawGridLines(false); //设置不显示左侧Y轴对应的网格线(即水平方向上的网格线),默认是显示的
接下来我们来看另外一个类:LineDataSet,一个LineDataSet对应一条折线/曲线(这里我们可以通过设置来指定线的效果)。
我们可以通过LineDataSet的构造方法得到LineDataSet的实例。
LineDataSet(List<Entry> yVals, String label) //LineDataSet的构造方法
lineDataSet.setColor(Color.parseColor("#3bb4fd")); //设置线的颜色
lineDataSet.setLineWidth(1.6f); //设置线的宽度
lineDataSet.setDrawCircles(true); //设置显示圆点(即线条中的顶点/峰点),默认为true
对应的源码如下:
/**
* set this to true to enable the drawing of circle indicators for this
* DataSet, default true
*
* @param enabled
*/
public void setDrawCircles(boolean enabled) {
this.mDrawCircles = enabled;
}
/**
* if true, drawing circles is enabled
*/
private boolean mDrawCircles = true;
lineDataSet.setDrawCircleHole(false); //设置显示实心圆点,默认为false
对应的源码如下:
/**
* Set this to true to allow drawing a hole in each data circle.
*
* @param enabled
*/
public void setDrawCircleHole(boolean enabled) {
mDrawCircleHole = enabled;
}
private boolean mDrawCircleHole = true;
lineDataSet.setCircleColor(Color.BLUE); //设置圆点的颜色
lineDataSet.setMode(LineDataSet.Mode.LINEAR); //设置线条的绘制模式,默认为折线。
对应的源码如下:
/**
* Returns the drawing mode for this LineDataSet
*
* @return
*/
public void setMode(LineDataSet.Mode mode) {
mMode = mode;
}
/**
* Drawing mode for this line dataset
**/
private LineDataSet.Mode mMode = Mode.LINEAR;
上面讲的都是针对折线图UI上进行设置
下面我们来看如何将我们的数据通过折线图展示出来
其实上面也略有介绍到,其实主要是三步:
第一步:得到LineDataSet对象。
第二步:得到LineData对象。
第三步:设置数据,lineChart.setData(lineData);
我们下面给出示例代码:
LineDataSet lineDataSet = new LineDataSet(entries, ""); //得到LineDataSet对象,构造方法中的第一个参数是源数据,第二个参数是label,label我们直接传一个空字符串即可。
LineData data = new LineData(lineDataSet); //得到LineData对象。这里我们提一下:如果是画两条折线,形成双折线对比效果,又该怎么实现呢?如果是三条,或者更多呢?我们后面再看。
data.setDrawValues(false); //折线图不显示数值
lineChart.setData(data); //设置数据
自定义x轴:
示例代码如下:
//1、声明并初始化存储x轴坐标的数组
final String[] xValues = new String[length]; //这里的length取决于业务
//2、x轴坐标赋值,并实例化Entry。sourceList为源数据。Entry表示图表中一个条目,在这里可以理解为一个点。
List<Entry> entries = new ArrayList<Entry>();
for (int i = 0; i < sourceList.size(); i++) {
xValues[i] = sourceList.get(i).getDateTime();
entries.add(new Entry(i, sourceList.get(i).getCounts()));
}
IAxisValueFormatter formatter = new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
if (value < (float) xValues.length) {
return xValues[(int) value];
} else {
return "";
}
}
}
xAxis.setValueFormatter(formatter);
下面我们再看一个项目中的真实效果图:
我们再来看下这个应该怎么实现。这次我们直接上代码(注:下面代码中使用到的OutletCheckItem是与业务相关的一个实体类,我们这里不用去管它):
private void drawLineChart(List<OutletCheckItem> lastWeekOutletCheckItemList, int lastWeekColor, List<OutletCheckItem> currentWeekOutletCheckItemList, int currentColor) {
Legend legend = lineChart.getLegend(); //得到图例
legend.setForm(Legend.LegendForm.CIRCLE); //设置形状为圆形
legend.setPosition(Legend.LegendPosition.ABOVE_CHART_RIGHT); //设置图例的位置为图表的右上方
legend.setOrientation(Legend.LegendOrientation.VERTICAL); //设置图例条目垂直排列
lineChart.getDescription().setEnabled(false); //隐藏描述
lineChart.setNoDataText("暂无数据"); //无数据时显示的文字
lineChart.setScaleEnabled(false); //禁止缩放
lineChart.setTouchEnabled(false); //禁止触摸
final String[] xValues = new String[]{"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
XAxis xAxis = lineChart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.setGranularity(1f); //设置x轴坐标之间的最小间隔
//设置x轴的值(最小值、最大值,然后会根据设置的刻度数量自动分配刻度显示)
xAxis.setAxisMinmum(0f);
xAxis.setAxisMaximum(xValues.length);
xAxis.setDrawGridLines(false); //不显示网格线
IAxisValueFormatter formatter = new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
if (value < (float) xValues.length) {
return xValues[(int) value];
} else {
return "";
}
}
};
xAxis.setValueFormatter(formatter); //自定义x轴
YAxis yAxis = lineChart.getAxisLeft(); //得到左侧Y轴
lineChart.getAxisRight().setEnabled(false); //右侧Y轴不显示
yAxis.setEnabled(false); //左侧Y轴不显示
yAxis.setAxisMinimum(0f); //设置Y轴的最小值
LineDataSet currentWeekLineDataList = getLineDataSet(currentWeekOutletCheckItemList, currentWeekColor, 1, "本周数据"); //得到本周折线图对应的LineDataSet对象
LineDataSet lastWeekLineDataSet = getLineDataSet(lastWeekOutletCheckItemList, lastWeekColor, 0, "上周数据"); //得到上周折线图对应的LineDataSet对象
List<ILineDataSet> lineDataSets = new ArrayList<ILineDataSet>();
lineDataSets.add(currentWeekLineDataSet);
lineDataSets.add(lastWeekLineDataSet);
LineData lineData = new LineData(lineDataSets); //得到LineData对象
lineData.setDrawValues(true); //折线图上显示值
lineChart.invalidate();
}
private LineDataSet getLineDataSet(List<OutletCheckItem> outletCheckItemList, int color, int type, String label) {
for (int i = 0; i < outletCheckItemList.size(); i++) {
OutletCheckItem outletCheckItem = outletCheckItemList.get(i);
String theDayOfTheWeek = TimeUtils.getTheDayOfTheWeek(outletCheckItem.getDateTime(), "yyyy-MM-dd"); //得到日期的星期表示,即将"yyyy-MM-dd HH:mm:ss"的表示改成"周一"的这种表示
outletCheckItem.setDataTime(theDayOfTheWeek);
}
List<Entry> entries = new ArrayList<Entry>();
final String xValues;
if (type == 0) { //上周
xValues = new String[]{"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
} else { //本周
Calendar calendar = Calendar.getInstance();
int week = calendar.get(Calendar.DAY_OF_WEEK);
switch (week) {
case 1:
xValues = new String[]{"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
break;
case 2:
xValues = new String[]{"周日", "周一"};
break;
case 3:
xValues = new String[]{"周日", "周一", "周二"};
break;
case 4:
xValues = new String[]{"周日", "周一", "周二", "周三"};
break;
case 5:
xValues = new String[]{"周日", "周一", "周二", "周三", "周四"};
break;
case 6:
xValues = new String[]{"周日", "周一", "周二", "周三", "周四", "周五"};
break;
case 7:
xValues = new String[]{"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
break;
default:
xValues = new String[]{"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
break;
}
}
for (int i = 0; i < xValues.length; i++) {
boolean bl = false;
for (int j = 0; j < outletCheckItemList.size(); j++) {
if (xValues[i].equals(outletCheckItemList.get(j).getDateTime())) {
entries.add(new Entry(i, outletCheckItemList.get(j).getCounts()));
bl = true;
break;
}
}
if (!bl) {
entries.add(new Entry(i, 0));
}
}
LineDataSet lineDataSet = new LineDataSet(entries, label);
lineDataSet.setColor(color);
lineDataSet.setLineWidth(2f);
lineDataSet.setDrawCircles(true);
lineDataSet.setDrawCircleHole(false);
lineDataSet.setCircleColor(color);
lineDataSet.setMode(LineDataSet.Mode.LINEAR);
lineDataSet.setValueFormatter(new IValueFormatter() {
@Override
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
return "" + (int) value;
}
});
return lineDataSet;
}
下面我们再来看一个项目中的真实效果图:
我们还是直接上代码:
注:图表上方一行是使用基本布局来实现的
privite int mMaxCount = 0;
private void drawLineChart(List lastWeekList, int lastWeekColor, List currentWeekList, int currentWeekColor) {
lineChart.getLegend().setEnabled(false);
lineChart.getDescription().setEnabled(false);
lineChart.setNoDataText("暂无数据");
lineChart.setScaleEnabled(false);
CustomMarkerView customMarkerView = new CustomMarkerView(this, R.layout.content_marker_view, lineChart);
lineChart.setMarker(customMarkerView); //设置MarkerView
final String[] xValues = new String[]{"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
XAxis xAxis = lineChart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.setGranularity(1f); //设置x轴坐标之间的最小间隔
//设置x轴的值(最小值、最大值,然后会根据设置的刻度数量自动分配刻度显示)
xAxis.setAxisMinmum(0f);
xAxis.setAxisMaximum(xValues.length);
xAxis.setTextColor(getResources().getColor(R.color.baseColor2)); //设置x轴坐标的颜色
xAxis.setGridColor(getResources().getColor(R.color.dark_blue_2)); //设置x轴方向上网格线的颜色
xAxis.setAxisLineColor(getResources().getColor(R.color.dark_blue_2)); //设置x轴的颜色
IAxisValueFormatter formatter = new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
if (value < (float) xValues.length) {
return xValues[(int) value];
} else {
return "";
}
}
};
xAxis.setValueFormatter(formatter); //自定义x轴
YAxis yAxis = lineChart.getAxisLeft();
lineChart.getAxisRight().setEnabled(false);
yAxis.setDrawGridLines(false);
yAxis.setGranularity(1f); //设置y轴坐标之间的最小间隔
yAxis.setAxisMinimum(0f); //设置y轴的最小值
yAxis.setTextColor(getResources().getColor(R.color.baseColor2)); //设置y轴坐标的颜色
yAxis.setAxisLineColor(getResources().getColor(R.color.dark_blue_2)); //设置y轴的颜色
mMaxCount = 0;
LineDataSet lastWeekLineDataSet = getLineDataSet(lastWeekList, lastWeekColor, 0);
LineDataSet currentWeekLineDataSet = getLineDataSet(currentWeekList, currentWeekColor, 1);
yAxis.setLabelCount(5);
yAxis.setAxisMaxium(maxCount + (5 - maxCount % 5));
List<ILineDataSet> lineDataSets = new ArrayList<ILineDataSet>();
lineDataSets.add(lastWeekLineDataSet);
lineDataSets.add(currentWeekLineDataSet);
LineData lineData = new LineData(lineDataSets);
lineData.setDrawValues(true);
lineChart.setData(lineData);
}
private LineDataSet getLineDataSet(List list, int color, int timeType) {
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getCounts() > maxCount) {
maxCount = list.get(i).getCounts();
}
}
/**
* 再进行一次数据处理,得到一个时间线为“周一、周二...”的集合
*/
for (int i = 0; i < list.size(); i++) {
OutletCheckItem outletCheckItem = list.get(i);
String theDayOfTheWeek = TimeUtils.getTheDayOfTheWeek(outletCheckItem.getDateTime(), "yyyy-MM-dd");
outletCheckItem.setDataTime(theDayOfTheWeek);
}
List<Entry> entries = new ArrayList<Entry>();
final String[] xValues;
if (timeType == 0) { //上周
xValues = new String[]{"周一", "周二", "周三", "周四", "周五", "周六", "周日"};
} else { //本周
Calendar calendar = Calendar.getInstance();
int week = calendar.get(Calendar.DAY_OF_WEEK);
switch (week) {
case 1:
xValues = new String[]{"周一", "周二", "周三", "周四", "周五", "周六", "周日"};
break;
case 2:
xValues = new String[]{"周一"};
break;
case 3:
xValues = new String[]{"周一", "周二"};
break;
case 4:
xValues = new String[]{"周一", "周二", "周三"};
break;
case 5:
xValues = new String[]{"周一", "周二", "周三", "周四"};
break;
case 6:
xValues = new String[]{"周一", "周二", "周三", "周四", "周五"};
break;
case 7:
xValues = new String[]{"周一", "周二", "周三", "周四", "周五", "周六"};
break;
default:
xValues = new String[]{"周一", "周二", "周三", "周四", "周五", "周六", "周日"};
break;
}
}
for (int i = 0; i < xValues.length; i++) {
boolean bl = false;
for (int j = 0; j < list.size(); j++) {
if (xValues[i].equals(list.get(j).getDateTime())) {
entries.add(new Entry(i, list.get(j).getCounts()));
bl = true;
break;
}
}
if (!bl) {
entries.add(new Entry(i, 0));
}
}
LineDataSet lineDataSet = new LineDataSet(entries, "");
lineDataSet.setColor(color);
lineDataSet.setLineWidth(2f);
if (entries.size() > 1) {
lineDataSet.setDrawCircles(false); //不显示圆点
} else {
lineDataSet.setDrawCircles(true); //显示圆点
lineDataSet.setDrawCircleHole(false); //显示实心点
lineDataSet.setCircleColor(color);
}
lineDataSet.setHighLightColor(Color.TRANSPARENT); //设置高亮网格线透明
lineDataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
lineDataSet.setValueFormatter(new IValueFormatter() {
@Override
public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) {
return "";
}
});
return lineDataSet;
}
CustomMarkerView.java
package xxx;
import android.content.Context;
import android.widget.TextView;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.MarkerView;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.utils.MPPointF;
import xxx.R;
public class CustomMarkerView extends MarkerView {
private TextView tv_content;
private LineChart lineChart;
/**
* Constructor. Sets up the MarkerView with a custom layout resource.
*
* @param context
* @param layoutResource the layout resource to use for the MarkerView
*/
public CustomMarkerView(Context context, int layoutResource, LineChart lineChart) {
super(context, layoutResource);
tv_content = findViewById(R.id.tv_content);
this.lineChart = lineChart;
}
@Override
public void refreshContent(Entry e, Highlight highlight) {
LineData lineData = lineChart.getLineData(); //得到已经绘制成型的折线图的数据
LineDataSet set1 = (LineDataSet) lineData.getDataSetByIndex(0); //获取第一条折线图Y轴数据
LineDataSet set2 = (LineDataSet) lineData.getDataSetByIndex(1); //获取第二条折线图Y轴数据
int dataSetIndex = highlight.getDataSetIndex(); //获取点击的是哪条折线上的交叉点,0就是第一条,以此类推
int index;
if (dataSetIndex == 0) {
index = set1.getEntryIndex(e); //根据点击的该条折线上的点,获取当前Y轴数据对应的index值
} else {
index = set2.getEntryIndex(e); //同上
}
//根据index值,分别获取当前Y轴上对应的两条折线的Y轴的值
Entry entry1 = set1.getEntryForIndex(index);
String week = "";
switch (index) {
case 0:
week = "周一";
break;
case 1:
week = "周二";
break;
case 2:
week = "周三";
break;
case 3:
week = "周四";
break;
case 4:
week = "周五";
break;
case 5:
week = "周六";
break;
case 6:
week = "周日";
break;
}
if (index < set2.getEntryCount()) {
Entry entry2 = set2.getEntryForIndex(index);
tv_content.setText(week + "\n\n上周:" + (int)entry1.getY() + "\n\n" + "本周:" + (int)entry2.getY());
} else {
tv_content.setText(week + "\n\n上周:" + (int)entry1.getY());
}
super.refreshContent(e, highlight);
}
@Override
public MPPointF getOffset() {
return new MPPointF(-(getWidth() / 2), -getHeight());
}
}
content_marker_view.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/shape_marker_view_background"
android:alpha="0.8"
android:padding="10dp">
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="@color/baseColor2"
android:textSize="12dp"/>
</RelativeLayout>
shape_marker_view_background.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="5dp"/>
<solid android:color="@color/baseColor5"/>
</shape>