这几天接触的一个项目需要使用到百度的echart,所以简单查找并实验了几款比较简单且适用性较广的例子,做了几个简单的Demo,一来是对echart有所了解,二来也是记录一下,方便以后进一步的优化。
首先,是echart的js文件,一般情况下,这一个文件基本就可以了,但是由于本文还需要涉及到地图类型的处理,所以还需要一个地图包,
,这个echart官网已经下架了,说是因为部分矢量数据不正确,建议用百度地图,但是在相关的实例中还是可以找到的,也挺好找的,就先拿这个练手,为了不暴露,代码中涉及到的url都用urlurlrul替代。
然后就是把相关的包导入项目,并且做引入
其实对于每一个实例而言,其过程都是类似的,先初始化相关参数,然后通过ajax动态加载需要更新的数据,
比如第一个柱图,首先初始化
option = {
title: { //标题组件
text: '消费金额统计图',
x: 'center',
y: 'top',
textAlign: 'left'
},
tooltip: { //提示框组件
trigger: 'axis',
showDelay: 5
},
legend: { //图例组件
left: 'left',
data: ['消费金额(元)', '周环比(100%)']
},
grid: { //直角坐标系内绘图网格
x: 40,
x2: 100,
y2: 150,
containLabel: true
},
toolbox: { //工具栏
feature: {
magicType: {
type: ['line', 'bar', 'tiled']
},
dataView: {readOnly: false},
saveAsImage: {}
}
},
xAxis: { //直角坐标系 grid 中的 x 轴
type: 'category',
boundaryGap: true,
axisLabel: {
interval: 0,
rotate: 40,
},
textStyle: '#333',
name: "时间",
axisTick: {
show: false
},
data: []
},
yAxis: { //直角坐标系 grid 中的 y 轴
// min: 0,
// max: 4000,
// interval:500,
name: "值",
type: 'value'
},
series: [ //系列列表
{
name: '消费金额(元)',
type: 'line',
stack: '总量',
itemStyle: {normal: {label: {show: true}}},
data: [133, 21, 321, 2]
},
{
name: '周环比(100%)',
type: 'line',
itemStyle: {normal: {label: {show: true}}},
stack: '总量',
data: [12, 32, 11, 45]
}
]
};
myChart.setOption(option);
然后ajax动态加载数据:
function getChartData() {
//获得图表的options对象
var options = myChart.getOption();
//通过Ajax获取数据
$.ajax({
type: "post",
async: false, //同步执行
url: "urlurlrul/getEchartData",
data: {},
dataType: "json", //返回数据形式为json
success: function (result) {
if (result) {
options.title[0].text=result.title;
options.legend[0].data = result.legend;
options.xAxis[0].data = result.category;
for (var i = 0; i < result.series.length; i++) {
options.series[i].name = result.series[i].name;
options.series[i].type = result.series[i].type;
options.series[i].data = result.series[i].data;
}
myChart.hideLoading();
myChart.setOption(options);
}
},
error: function (errorMsg) {
alert("图表请求数据失败!");
myChart.hideLoading();
}
});
}
其实这部分最关键,也是整个项目最关键的部分,就是ajax动态加载之后的数据接收处理,由于都是自己的练手代码,所以我都在后台把数据处理好再发送。后端主要有三个主要的方法,EchartData(),Series(),和echartGetData()。EchartData()用于处理图表Option的主要参数,Series()处理series中data和其他相关参数,echartGetData()则是用于处理数据和传输数据的方法。
@PostMapping(value = "/getEchartData",produces = "application/json;charset=utf-8")
public EchartData echartGetData() {
String title = "折线图和柱图";
List<Object> seriesList = new ArrayList<>();//动态数据集
List<String> legend = new ArrayList<>();//图例
legend.add("销量");
legend.add("售价");
List<String> category = new ArrayList<>();//坐标轴
category.add("一月份");
category.add("二月份");
category.add("三月份");
category.add("四月份");
List<Integer> xList = new ArrayList<>();
xList.add(11);
xList.add(24);
xList.add(31);
xList.add(14);
seriesList.add(new Series("销量","line",xList));
List<Integer> xList2 = new ArrayList<>();
xList2.add(31);
xList2.add(44);
xList2.add(51);
xList2.add(36);
seriesList.add(new Series("售价","bar",xList2));
EchartData echartData = new EchartData(title,category,legend,seriesList);
return echartData;
}
public class EchartData {
public String title;
public List<String> category = new ArrayList<>();
public List<String> legend = new ArrayList<>();
public List<Object> series = new ArrayList<>();
public EchartData(String title,List<String> categoryList,List<String> legendList,List<Object> seriesList) {
// TODO Auto-generated constructor stub
this.title=title;
this.legend = legendList;
this.category = categoryList;
this.series = seriesList;
}
}
public class Series {
public String name;
public String type;
public List<Integer> data;
public Series(String name,String type,List<Integer> data) {
// TODO Auto-generated constructor stub
this.name = name;
this.type = type;
this.data = data;
}
}
由于主要是为了看echart的动态加载过程,所以没有从数据库中直接取数,而是直接赋值进行操作的,如果是在实际项目中,用一个java bean,一个mabitis的mapper和一个service就可以直接获取到相关的数据集,再利用for循环进行循环导入就可以了。
echartGetData()传出的json数据为:
{
"title": "折线图和柱图",
"category": ["一月份", "二月份", "三月份", "四月份"],
"legend": ["销量", "售价"],
"series": [{
"name": "销量",
"type": "line",
"data": [11, 24, 31, 14]
}, {
"name": "售价",
"type": "bar",
"data": [31, 44, 51, 36]
}]
}
实际加载数据后的echart图
由于折线图和柱图都比较简单,其data直接传一个数组就可以了,而在pie图以及一些其他复杂的一些echart图往往比较复杂,需要在数组中嵌套map,然后主要就是在哪里取数和处理,比如我这次pie图就是在后端直接处理好,然后传给前端一个封装好的Json,这样处理起来相对更加方便,话不多说,首先看pie图的前端处理
<script>
var myChart = echarts.init(document.getElementById("pieChart"));
option = {
title: {
text: '某站点用户访问来源',
subtext: '纯属虚构',
x: 'center'
},
tooltip: {
trigger: 'item',
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
toolbox: { //工具栏
feature: {
dataView: {readOnly: false},
restore: {},
saveAsImage: {}
}
},
legend: {
orient: 'vertical',
left: 'left',
data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎']
},
series: [
{
name: '访问来源',
type: 'pie',
// radius: ['55%','70%'],
radius: '55%',
center: ['50%', '60%'],
data: [
{value: 335, name: '直接访问'},
{value: 310, name: '邮件营销'},
{value: 234, name: '联盟广告'},
{value: 135, name: '视频广告'},
{value: 1548, name: '搜索引擎'}
]
}
]
};
myChart.setOption(option);
myChart.hideLoading();
getChartData();
function getChartData() {
var options = myChart.getOption();
$.ajax({
type: "post",
sync: false,
url: "urlurlrul/getPieChartData",
data: {},
dataType: "json",
success: function (result) {
options.title[0].text=result.title;
options.legend[0].data = result.legend;
options.series[0].name = result.series[0].name;
options.series[0].data = result.series[0].data;
myChart.hideLoading();
myChart.setOption(options);
}
})
}
</script>
可以看出我的data直接接收的就是可以直接使用的json,不需要再重新循环导入,当然如果在这里循环导入也是可以的,只是后端的处理方式不同而已。然后后端的处理是这样的
@PostMapping(value="/getPieChartData",produces="application/json;charset=utf-8")
public EchartData getPieChartData() {
String title = "Pie图和Doughnut图";
List<String> category = new ArrayList<>();
List<String> legend = new ArrayList<>();
legend.add("Cola");
legend.add("Wine");
legend.add("Tea");
legend.add("GreenTea");
List<Object> dataList = new ArrayList<>();
Map<String,Object> data0= new HashMap<>();
data0.put("name","Cola");
data0.put("value",32);
dataList.add(JSONObject.fromObject(data0));
data0.put("name","Wine");
data0.put("value",23);
dataList.add(JSONObject.fromObject(data0));
data0.put("name","Tea");
data0.put("value",12);
dataList.add(JSONObject.fromObject(data0));
data0.put("name","GreenTea");
data0.put("value",35);
dataList.add(JSONObject.fromObject(data0));
List<Object> pieSeries = new ArrayList<>();
pieSeries.add(new PieSeries("饮料分布",dataList));
EchartData echartData = new EchartData(title,category,legend,pieSeries);
return echartData;
}
public class PieSeries {
public String name;
// public Map<String,Integer> data;
public List<Object> data;
public PieSeries(String name,List<Object> obj) {
// TODO Auto-generated constructor stub
this.name= name;
this.data=obj;
}
}
getPieChartData传出的json文件为:
{
"title": "Pie图和Doughnut图",
"legend": ["Cola", "Wine", "Tea", "GreenTea"],
"series": [{
"name": "饮料分布",
"data": [{
"name": "Cola",
"value": 32
}, {
"name": "Wine",
"value": 23
}, {
"name": "Tea",
"value": 12
}, {
"name": "GreenTea",
"value": 35
}]
}]
}
加载之后的echart图表为:
这种后端处理后,直接向前端传处理好的json数据,实际用起来是非常方便的。另外对于饼图而言,其数据结构与Douhnut图和南丁格尔玫瑰图其实格式都是非常类似的,基本上一种就可以覆盖好几种的写法。其实这里最主要的麻烦是数据的格式问题,后端怎么传以及前端怎么处理,如果没有很好的需求文档,配合起来是非常痛苦的,所以最好的办法就是自己都会,自己都干了就行了(~.~)
有了这么一个练习基础,可以进行一个比较复杂的练习,其实也不复杂,只是看起来内容更加丰富而已。就是下面这个图
这是echart社区的一个例子,把它应用在项目中试试。这个图有两个比较重要的点,其一就是map图的处理,这种格式一般都会在输入端有所限制,只允许导入省份之类的信息,而且由于矢量信息存在问题,目前已经不支持省级以下比如市级甚至更低的缩放。其二就是这个例子给了我们一个非常好的思路,就是他将两种数据展示图用于展示同一组数据,这个功能的实现关键就在于echart 每个功能块强大的功能,如果仔细观察,我们发现上面无论是Legend,series等功能块其实都是数组形式的,而我们对于series这部分就可以定义不同的数据展现形式,比如bar、pie之类的,剩余的就是一些美化工作了。
当然,如果对于map这个图进一步进行仔细研究就会发现,还有很多需要进行进一步优化的,比如颜色范围的调节如何与实际的输入契合,避免局部大数导致整体没有区分。然后还有就是数据的排序,我们可以看出右边本意是为了进行排序,将前十列出来,但是这个一般是在后端数据有意义的情况下才会进行的,所以后续的工作还有很多。
首先,先看一下其js代码
<!DICTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>ECharts图</title>
<script src="js/echarts.min.js" type="text/javascript"></script>
<script src="js/china.js" type="text/javascript"></script>
<!-- <script src="http://echarts.baidu.com/build/dist/echarts.js">-->
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
</head>
<body>
<div id="MapChart" style="height: 800px;width:1000px;border: 6px solid #ccc9cb; padding: 100px;"></div>
<script>
var myChart = echarts.init(document.getElementById('MapChart'));
var data = [{
name: '江苏省',
value: 5.3
},
{
name: '北京市',
value: 3.8
},
{
name: '上海',
value: 4.6
},
{
name: '重庆',
value: 3.6
},
{
name: '河北',
value: 3.4
},
{
name: '河南',
value: 3.2
},
{
name: '云南',
value: 1.6
},
{
name: '辽宁',
value: 4.3
},
{
name: '黑龙江',
value: 4.1
},
{
name: '湖南',
value: 2.4
},
{
name: '安徽',
value: 3.3
},
{
name: '山东',
value: 3.0
},
{
name: '新疆',
value: 1
},
{
name: '江苏',
value: 3.9
},
{
name: '浙江',
value: 3.5
},
{
name: '江西',
value: 2.0
},
{
name: '湖北',
value: 2.1
},
{
name: '广西',
value: 3.0
},
{
name: '甘肃',
value: 1.2
},
{
name: '山西',
value: 3.2
},
{
name: '内蒙古',
value: 3.5
},
{
name: '陕西',
value: 2.5
},
{
name: '吉林',
value: 4.5
},
{
name: '福建',
value: 2.8
},
{
name: '贵州',
value: 1.8
},
{
name: '广东',
value: 3.7
},
{
name: '青海',
value: 0.6
},
{
name: '西藏',
value: 0.4
},
{
name: '四川',
value: 3.3
},
{
name: '宁夏',
value: 0.8
},
{
name: '海南',
value: 1.9
},
{
name: '台湾',
value: 0.1
},
{
name: '香港',
value: 0.1
},
{
name: '澳门',
value: 0.1
}
];
var yData = [];
var barData = [];
for (var i = 0; i < 5; i++) {
barData.push(data[i]);
yData.push(i + data[i].name);
}
var option = {
title: [{
show: true,
text: '排名情况',
textStyle: {
color: '#2D3E53',
fontSize: 18
},
right: 180,
top: 100
}],
tooltip: {
show: true,
formatter: function(params) {
return params.name + ':' + params.data['value'] + '%'
},
},
visualMap: {
type: 'continuous',
orient: 'horizontal',
itemWidth: 10,
itemHeight: 80,
text: ['高', '低'],
showLabel: true,
seriesIndex: [0],
min: 0,
max: 2,
inRange: {
color: ['#6FCF6A', '#FFFD64', '#FF5000']
},
textStyle: {
color: '#7B93A7'
},
bottom: 30,
left: 'left',
},
grid: {
right: 10,
top: 135,
bottom: 100,
width: '20%'
},
xAxis: {
show: false
},
yAxis: {
type: 'category',
inverse: true,
nameGap: 16,
axisLine: {
show: false,
lineStyle: {
color: '#ddd'
}
},
axisTick: {
show: false,
lineStyle: {
color: '#ddd'
}
},
axisLabel: {
interval: 0,
margin: 85,
textStyle: {
color: '#455A74',
align: 'left',
fontSize: 14
},
rich: {
a: {
color: '#fff',
backgroundColor: '#FAAA39',
width: 20,
height: 20,
align: 'center',
borderRadius: 2
},
b: {
color: '#fff',
backgroundColor: '#4197FD',
width: 20,
height: 20,
align: 'center',
borderRadius: 2
}
},
formatter: function(params) {
if (parseInt(params.slice(0, 1)) < 3) {
return [
'{a|' + (parseInt(params.slice(0, 1)) + 1) + '}' + ' ' + params.slice(1)
].join('\n')
} else {
return [
'{b|' + (parseInt(params.slice(0, 1)) + 1) + '}' + ' ' + params.slice(1)
].join('\n')
}
}
},
data: yData
},
geo: {
// roam: true,
map: 'china',
left: 'left',
right: '300',
// layoutSize: '80%',
label: {
emphasis: {
show: false
}
},
itemStyle: {
emphasis: {
areaColor: '#fff464'
}
}
},
series: [{
name: 'mapSer',
type: 'map',
roam: false,
geoIndex: 0,
label: {
show: false,
},
data: data
}, {
name: 'barSer',
type: 'bar',
roam: false,
visualMap: false,
zlevel: 2,
barMaxWidth: 8,
barGap: 0,
itemStyle: {
normal: {
color: function(params) {
// build a color map as your need.
var colorList = [{
colorStops: [{
offset: 0,
color: '#FFD119' // 0% 处的颜色
}, {
offset: 1,
color: '#FFAC4C' // 100% 处的颜色
}]
},
{
colorStops: [{
offset: 0,
color: '#00C0FA' // 0% 处的颜色
}, {
offset: 1,
color: '#2F95FA' // 100% 处的颜色
}]
}
];
if (params.dataIndex < 3) {
return colorList[0]
} else {
return colorList[1]
}
},
barBorderRadius: 15
}
},
data: barData
}]
};
myChart.setOption(option)
myChart.hideLoading();
getChartData();
function getChartData(){
var options = myChart.getOption();
$.ajax({
type: "post",
async: false, //同步执行
url: "urlurlrul/getMapChartData",
data: {},
dataType: "json", //返回数据形式为json
success: function (result) {
if (result) {
options.series[0].data = result.series;
var tempData = result.series;
var yData = [];
var barData = [];
for (var i = 0; i < result.series.length; i++) {
barData.push(result.series[i]);
yData.push(i + result.series[i].name);
}
options.yAxis[0].data=yData;
options.series[1].data = barData;
myChart.hideLoading();
myChart.setOption(options);
}
},
error: function (errorMsg) {
alert("图表请求数据失败!");
myChart.hideLoading();
}
});
}
</script>
<script type="text/javascript">
</script>
</body>
</html>
其实这两部分的数据来源都是相同的,所以可以使用同一个数据元,然后进行进一步的处理就可以了。
后端的controller 为:
@PostMapping(value="/getMapChartData",produces="application/json;charset=utf-8")
public EchartData getMapChartData() {
String title ="MapChartData";
List<String> category = new ArrayList<>();
List<String> Legend = new ArrayList<>();
List<Object> mapDataList = new ArrayList();
Map<String,Object> map = new HashMap<>();
map.put("name", "山西");
map.put("value", 1.3);
mapDataList.add(JSONObject.fromObject(map));
map.put("name","北京");
map.put("value",7.8);
mapDataList.add(JSONObject.fromObject(map));
map.put("name","浙江");
map.put("value",3.2);
mapDataList.add(JSONObject.fromObject(map));
map.put("name","上海");
map.put("value",6.3);
mapDataList.add(JSONObject.fromObject(map));
map.put("name","西藏");
map.put("value",0.6);
mapDataList.add(JSONObject.fromObject(map));
map.put("name","天津");
map.put("value",0.1);
mapDataList.add(JSONObject.fromObject(map));
EchartData echartData = new EchartData(title,category,Legend,mapDataList);
return echartData;
}
这个处理是非常简单的,当然也很粗糙,需要进一步的优化,我们先看一下传出的json:
{
"title": "MapChartData",
"series": [{
"name": "山西",
"value": 1.3
}, {
"name": "北京",
"value": 7.8
}, {
"name": "浙江",
"value": 3.2
}, {
"name": "上海",
"value": 6.3
}, {
"name": "西藏",
"value": 0.6
}, {
"name": "天津",
"value": 0.1
}]
}
其最终显示效果:
基本功能算是实现了,后续的工作就是在实际的项目中实际的优化了。
这三个图例加上最后的整理总结,大概花了一天的时间,其实最主要的问题还是数据格式的问题,但是多看一些例子之后,自己也慢慢学会了到底该如何处理类似的数据。除了对echart图有了一丝的了解,也对前后端的交互有了更深层次的理解,同时,我觉得自己收获最大的应该是似乎在与echart的开发者对话,理解了某些巧妙的设计,的确佩服,自己不是做前端的,写js对于我来说算是一个挑战,虽然简单的可以读懂,写起来还是没那么容易。如果有想进一步交流的,欢迎评论(~.~)