公司的日报系统中,有个查看报表功能。但是现在只能查看表格形式的数据,不直观。
这次的工作就是将数据画成图表,数据可视化工作。
开源图表库项目简介
之前做web开发时,有一个项目是vo,visual office,给客户网上办公用的。里面也涉及到数据可视化的工作,那时候了解到了有d3.js这样一个图表库。那么安卓上有没有类似的图表库呢?
我上github上找了下,找到了如下:
1. MPAndroidChart
https://github.com/PhilJay/MPAndroidChart 用的比较多的一个库。支持Android 2.2以上的项目。
2. achartengine
http://www.achartengine.org/ 比较老的一个库了
3. XCL- Charts
https://github.com/xcltapestry/XCL-Charts
XCL-Charts基于Android原生Canvas来绘制各种图表,使用简便,定制灵活。
4. WilliamChart
https://github.com/diogobernardino/WilliamChart
绘制图表的库,支持LineChartView、BarChartView和StackBarChartView三中图表类型,并且支持 Android 2.2及以上的系统。有很漂亮的动画。
5. HelloCharts for Android
https://github.com/lecho/hellocharts-android
支持折线图、柱状图、饼图、气泡图、组合图;支持预览、放大缩小,滚动,部分图表支持动画;支持 Android 2.2 以上。
图表库的应用
1.导入jar包
在eclipse中导入MPChartLib之后,build一下,拿到bin文件夹下的jar包。
将jar包放到目标项目的lib文件夹下,右击add to build path
2.柱状图的绘画
创建一个ReportChartsActivity,用来进行数据可视化的处理。为其创建界面activity_report_chart.xml如图
设置全屏
requestWindowFeature(Window.FEATURE_NO_TITLE);//隐藏标题
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);//设置全屏
setContentView(R.layout.activity_report_chart);
设置横屏
@Override
protected void onResume() {
if(getRequestedOrientation()!=ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE){
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
super.onResume();
}
图表初始化
private void initChart() {
// TODO Auto-generated method stub
//混合柱状图的初始化
arChart = (BarChart) findViewById(R.id.chart_bar);
barChart.setDrawBarShadow(false);
barChart.setDrawGridBackground(false);
<span > </span>barChart.setDrawHorizontalGrid(false);
barChart.setDrawYValues(true); //画出X,Y坐标系
barChart.setDescription(""); //说明
barChart.setMaxVisibleValueCount(40); //一屏超过25列时不显示具体数值,设置超过60无效
MyValueFormatter valueformatter = new MyValueFormatter();
barChart.setValueFormatter(valueformatter); //设置自定义格式化方式
// barChart.setValueFormatter(new LargeValueFormatter());
barChart.setDrawValuesForWholeStack(false); //是否显示每部分的数字,false则为显示和
barChart.set3DEnabled(false); //3D视图
barChart.setPinchZoom(false); //是否只能根据X,Y轴放大缩小
barChart.setDrawBarShadow(false); //设置该列空白部分是否用灰色补全
//X,Y轴设定
YLabels yLabels = barChart.getYLabels();
<span > </span>yLabels.setPosition(YLabelPosition.LEFT);
yLabels.setLabelCount(5);
yLabels.setFormatter(valueformatter);
XLabels xLabels = barChart.getXLabels();
xLabels.setPosition(XLabelPosition.BOTTOM);
xLabels.setCenterXLabelText(true);
}
取得数据
在活动开始的时候,就向服务器发送一个请求,然后我们得到返回的json数据。
private void init() {
handler=new ReportDetailJsonHandler();
formUtil = new GetServerReportInfoUtil(handler, this);
startMonth=getIntent().getExtras().getString("startMonth");
endMonth=getIntent().getExtras().getString("endMonth");
currentYear=getIntent().getExtras().getString("currentYear");
formUtil.getJsonInfoIdFromServer(startMonth,endMonth ,currentYear);
}
class ReportDetailJsonHandler extends Handler {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
switch (msg.what) {
...
...
...
case HandlerCASE.MSG_DONE_SECOND:
for (ReportDetailInfoBean bean : formUtil.getResponseForm()
.getData()) {
if (!"部门名称".equals(bean.getAttr1())) {
dataList.add(bean);
}
}
drawChart();
break;
case HandlerCASE.MSG_NODATA:
...
...
...
default:
break;
}
}
}
至此我们得到了List<ReportDetailInfoBean> dataList
画出图表
然后根据dataList画出我们要的图表。于是调用drawChart().
private void drawChart() {
// TODO Auto-generated method stub
int StartMonth = Integer.valueOf(startMonth); //开始月份
int SumMonth = Integer.valueOf(endMonth) - StartMonth + 1; //总月份
name = new ArrayList<String>();
yVals = new ArrayList<ArrayList<BarEntry>>();
for(int n = 0; n < SumMonth; n++){
yVals.add(new ArrayList<BarEntry>());
}
name.add(dataList.get(0).getAttr1());
for (int i = 0; i < dataList.size(); i++) {
ReportDetailInfoBean bean = dataList.get(i);
if(!name.get(name.size() - 1).equals(bean.getAttr1())){
name.add(bean.getAttr1());
}
}
int month=0;
int barIndex=0;
int dataIndex=0;
for (int i = 0; i < name.size()*SumMonth; i++) {
ReportDetailInfoBean bean = dataList.get(dataIndex);
if (month>=SumMonth) {
month=0;
barIndex++;
}
if (month+StartMonth==Integer.valueOf(bean.getAttr2())) {
yVals.get(month).add(new BarEntry(Form(bean), barIndex));
dataIndex++;
} else {
yVals.get(month).add(new BarEntry(new float[] {0,0,0}, barIndex));
}
month++;
}
ArrayList<BarDataSet> dataSets = new ArrayList<BarDataSet>();
for(int n = 0; n < SumMonth; n++){
BarDataSet set = new BarDataSet(yVals.get(n), (StartMonth + n) + "月数据");
set.setStackLabels(new String[] {"已完成", "进行中", "未开始"});
set.setColors(colorList.get(n));
dataSets.add(set);
}
BarData data = new BarData(name, dataSets);
data.setGroupSpace(110f);
barChart.setData(data);
barChart.animateY(500);
barChart.invalidate();
initPieChart();
//BarData的各个属性,未设置StackLables直接设置此项会报空指针异常
Legend legend = barChart.getLegend();
legend.setPosition(LegendPosition.RIGHT_OF_CHART_INSIDE);
legend.setFormSize(8f);
legend.setTextSize(8f);
legend.setFormToTextSpace(4f);
legend.setXEntrySpace(6f);
}
效果图:
3.饼状图的绘画
我们用的是堆积条形图,现在我们添加一个功能在:在单击柱状图的时候,弹出饼状图现实各个部分所占比例。
设置监听器
在initChart()中为barChart设置单击事件
barChart.setOnChartValueSelectedListener(new OnChartValueSelectedListener() {
@Override
public void onValueSelected(Entry entry, int barIndex) {
// TODO Auto-generated method stub
if (entry == null)
return;
float[] vals = ((BarEntry)entry).getVals();
String dev = name.get(entry.getXIndex());
String month = (Integer.valueOf(startMonth) + barIndex) + "";
ArrayList<String> choosen = new ArrayList<String>();
ArrayList<Entry> yVals = new ArrayList<Entry>();
choosen.add("已完成");
choosen.add("进行中");
choosen.add("未开始");
for(int n = 0; n < vals.length; n++){
if (vals[n]!=0) {
yVals.add(new Entry(vals[n], n));
}
}
pieChart.setCenterText(dev + "\n" + month + "月数据一览");
PieDataSet dataSet = new PieDataSet(yVals, "");
dataSet.setColors(ColorTemplate.APRIL_COLORS);
PieData data = new PieData(choosen, dataSet);
pieChart.setData(data);
pieChart.invalidate();
Legend legend = pieChart.getLegend(); //说明模块设置
legend.setPosition(LegendPosition.RIGHT_OF_CHART);
legend.setTextColor(Color.WHITE);
legend.setXEntrySpace(7f);
legend.setYEntrySpace(5f);
piechart.showAtLocation((View)findViewById(R.id.chart), Gravity.CENTER, 0, 0);
}
初始化饼状图
可以看到刚刚的代码中,在drawChart()中有个initPieChart(),这个就是初始化饼状图的方法。
private void initPieChart(){
//PieChart的PopupWindow初始化
View view = ReportChartsActivity.this.getLayoutInflater()
.inflate(R.layout.popup_report_chart_pie, null);
piechart = new PopupWindow(view, fins, fins);
piechart.setFocusable(true);
piechart.setOutsideTouchable(false);
piechart.setBackgroundDrawable(new BitmapDrawable());
piechart.setAnimationStyle(R.style.AnimationFade);
pieChart = (PieChart) view.findViewById(R.id.chart_pie);
pieChart.setHoleRadius(60f);
pieChart.setDescription("");
pieChart.setDrawYValues(true);
pieChart.setDrawCenterText(true); //中间字
pieChart.setDrawHoleEnabled(true);
pieChart.setRotationAngle(0);
pieChart.setDrawXValues(true); //画出各部分说明
pieChart.setRotationEnabled(false); //可旋转
pieChart.setUsePercentValues(false); //显示百分数
}
效果图:
4.折线图的绘画
柱状图饼状图都有了,现在我们要查看单个部门的数据趋势。这个时候用折线图来处理吧。
入口怎么处理呢?用OptionsMenu吧。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
getMenuInflater().inflate(R.menu.chart, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
int id = item.getItemId();
if (id == R.id.action_chart) {
Intent intent=new Intent(ReportChartsActivity.this, ReportDetialChartsActivity.class);
Bundle detailData = new Bundle();
detailData.putString("reportDetailTitle", "部门计划折线图");
detailData.putSerializable("departmentName", name);
detailData.putSerializable("dataList", (Serializable) dataList);
intent.putExtras(detailData);
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
我们要新写一个yReportDetialChartsActivit活动,然后再写出对应的界面,由于步骤差不多就略过了。这里可以看到我将ReportChartsActivity中已经取得的数据直接塞到Intent中带给下个活动了,这样ReportDetialChartsActivity中我就不再需要发送请求来取得dataList了。
布局是这样的:
图表上方添加一个下拉菜单选择部门,然后下方显示对应部门的折线图。
初始化数据
private void init() {
String titleText = getIntent().getExtras().getString("reportDetailTitle");
name=(ArrayList<String>) getIntent().getExtras().getSerializable("departmentName");
dataList=(List<ReportDetailInfoBean>) getIntent().getExtras().getSerializable("dataList");
}
初始化折线图
private void initChart() {
// TODO Auto-generated method stub
lineChart = (LineChart) findViewById(R.id.chart1);
lineChart.setOnChartValueSelectedListener(this);
//是否显示网格线
lineChart.setDrawGridBackground(false);
// mChart.setStartAtZero(true);
// disable the drawing of values into the chart 画出X,Y坐标系
lineChart.setDrawYValues(true);
lineChart.setMaxVisibleValueCount(30);
// enable value highlighting
lineChart.setHighlightEnabled(true);
lineChart.setHighlightIndicatorEnabled(false);
// enable touch gestures
lineChart.setTouchEnabled(true);
// enable scaling and dragging
lineChart.setDragEnabled(true);
lineChart.setScaleEnabled(true);
// if disabled, scaling can be done on x- and y-axis separately 是否只能根据X,Y轴放大缩小
lineChart.setPinchZoom(false);
lineChart.setDescription("");
// create a custom MarkerView (extend MarkerView) and specify the layout
// to use for it
MyMarkerView mv = new MyMarkerView(this, R.layout.custom_marker_view);
// define an offset to change the original position of the marker
// (optional)
mv.setOffsets(-mv.getMeasuredWidth() / 2, -mv.getMeasuredHeight());
// set the marker to the chart
lineChart.setMarkerView(mv);
dep_tv.setText(name.get(0));
setData(name.get(0));
}
设置数据
private void setData(String department){
ArrayList<String> xVals = new ArrayList<String>();
ArrayList<LineDataSet> dataSets = new ArrayList<LineDataSet>();
ArrayList<Entry> values1 = new ArrayList<Entry>();
ArrayList<Entry> values2 = new ArrayList<Entry>();
ArrayList<Entry> values3 = new ArrayList<Entry>();
for(int i = 0; i < dataList.size(); i++){
ReportDetailInfoBean reportDetail = dataList.get(i);
if(reportDetail.getAttr1().equals(department)){
xVals.add(reportDetail.getAttr2());
values1.add(new Entry(Integer.valueOf(reportDetail.getAttr4()), xVals.size() - 1));
values2.add(new Entry(Integer.valueOf(reportDetail.getAttr5()), xVals.size() - 1));
values3.add(new Entry(Integer.valueOf(reportDetail.getAttr3()), xVals.size() - 1));
}
}
LineDataSet d1 = new LineDataSet(values1, "已完成");
LineDataSet d2 = new LineDataSet(values2, "进行中");
LineDataSet d3 = new LineDataSet(values3, "总任务");
d1.setColor(ColorTemplate.VORDIPLOM_COLORS[2]);
d1.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[2]);
d1.setLineWidth(2.5f);
d1.setCircleSize(4f);
d2.setColor(ColorTemplate.VORDIPLOM_COLORS[3]);
d2.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[3]);
d2.setLineWidth(2.5f);
d2.setCircleSize(4f);
d3.setColor(ColorTemplate.VORDIPLOM_COLORS[4]);
d3.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[4]);
d3.setLineWidth(2.5f);
d3.setCircleSize(4f);
dataSets.add(d1);
dataSets.add(d2);
dataSets.add(d3);
LineData data = new LineData(xVals, dataSets);
lineChart.setData(data);
lineChart.animateY(500);
lineChart.invalidate();
}
设置markView
class MyMarkerView extends MarkerView {
private TextView tvContent;
public MyMarkerView(Context context, int layoutResource) {
super(context, layoutResource);
tvContent = (TextView) findViewById(R.id.tvContent);
}
// callbacks everytime the MarkerView is redrawn, can be used to update the
// content
@Override
public void refreshContent(Entry e, int dataSetIndex) {
if (e instanceof CandleEntry) {
CandleEntry ce = (CandleEntry) e;
tvContent.setText("" + Utils.formatNumber(ce.getHigh(), 0, true));
} else {
tvContent.setText("" + Utils.formatNumber(e.getVal(), 0, true));
}
}
}
效果图:
实现部门选择
在init()方法中,设置部门的单击事件。
private void init() {
String titleText = getIntent().getExtras().getString("reportDetailTitle");
name=(ArrayList<String>) getIntent().getExtras().getSerializable("departmentName");
dataList=(List<ReportDetailInfoBean>) getIntent().getExtras().getSerializable("dataList");
setTitleBarText(titleText);
initTitleBarBack();
dep_lin = (LinearLayout) findViewById(R.id.dep_lin);
dep_img = (ImageView) findViewById(R.id.dep_img);
dep_tv = (TextView) findViewById(R.id.dep_tv);
dep_tv.setText(name.get(0));
View layout_area = LayoutInflater.from(ReportDetialChartsActivity.this).inflate(
R.layout.pop_list, null);
ListView listview = (ListView) layout_area.findViewById(R.id.listView1);
listview.setAdapter(getMenuAdapter(name));
listview.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
menu_dep.dismiss();
currentDep = name.get(arg2);
dep_tv.setText(currentDep);
setData(currentDep);
}
});
menu_dep = new PopupWindow(layout_area,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT);
menu_dep.setFocusable(true);
menu_dep.setBackgroundDrawable(getResources().getDrawable(
android.R.color.transparent));
menu_dep.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss() {
dep_img.setBackgroundResource(R.drawable.arrow_blue_down);
dep_tv.setTextColor(getResources().getColor(
R.color.textcontentcolor));
}
});
menu_dep.setAnimationStyle(R.style.AnimationFade);
dep_lin.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
menu_dep.showAsDropDown(dep_lin);
dep_img.setBackgroundResource(R.drawable.arrow_blue_up);
}
});
}
效果图
折线图效果
完成效果图