前一篇博文记录了echart图例诸如柱图、折线图、饼图、map图等的前后台交互和最终显示结果,但是Map图还有两个小问题没有解决,正好今天解决这两个问题。
第一个问题:
Map图中数据根据value的排序问题,这个问题比较简单,就是利用Collections.sort()进行排序就可以了
List> list = new ArrayList>(size);
list.addAll(map.entrySet());
// 通过Collections.sort()排序
Collections.sort(list, new Comparator>() {
public int compare(Map.Entry o1, Map.Entry o2) {
return o1.getValue().compareTo(o2.getValue());
};
});
第二个问题:
Map图的图例范围如果设置为动态,需要根据数据的输入范围来界定其图例范围。但是如果是简单的利用输入数据的最小值和最大值来界定范围就会在一些特殊情况下发生范围不明显的情况,比如有一组数:1,2,3,2,6,2,5,324;如果简单的根据输入数据的最大值和最小值进行界定,则会发生最大值为324导致大部分数据集中在特别小的局部,从而导致层次不明显,无法起到该有的作用,因此需要寻找一种算法,实现异常数据的剔除。
此过程中发现格拉布斯准则是比较贴近这种情况的。格拉布斯准则解决的是同一个参数经过若干次测量,但是测量结果偏差较大的问题。本次问题虽然不是同一参数的多个数据,而是不同省份的value值,但是还是可以看成类似的模型。
首先,说一下格拉布斯准则的主要内容:
第一步:根据数据输入,计算得出数据的算数平均数x;
第二步:根据数据输入和算数平均数,计算得出数据集的标准差standard;
第三步:对数据进行排序,计算最大值和最小值的偏差|(num-x)/standard|,找到可疑值,根据检出水平alpha查询临界值,从而确定异常值
第四步:剔除异常值,重新寻找新的异常值。
更详细的解释可以查询:格拉布斯准则剔除异常值
回到之前Map的图例展示,之前由于只是传的data数据,没有传其他参数,所以只是传了一个数组而已,而现在要传map的visualMap的max和min值,所以需要更多的参数,为了数据的简单化,就不在echartData中增加字段,而是新建一个mapChartData 的series来进行数据的传递。
public class MapSeries {
public Integer Max;
public Integer Min;
public List
按照上述格拉布斯准则做出的格拉布斯方法:
public class Grubbs {
public ArrayList comp(ArrayList list){
Collections.sort(list, new Comparator(){
@Override
public int compare(Double o1, Double o2) {
return o1.compareTo(o2);
}
});
// System.out.println(list);
double average = getAverage(list);
double standard = getStandard(list,list.size(),average);
ArrayList gData = new ArrayList<>();
ArrayList tempList = new ArrayList<>();
tempList = list;
for (int i=0;igrubbsP(tempList.size(),0.05)){
tempList.remove(i);
comp(tempList);
}
}
// System.out.println(gData);
return tempList;
}
public static double getAverage(ArrayList sample) {
double sum = 0;
int cnt = 0;
for (int i = 0; i < sample.size(); i++) {
sum += sample.get(i);
cnt++;
}
return sum / cnt;
}
public static double getStandard(ArrayList array, int n, double average) {
double sum = 0;
for (int i = 0; i < n; i++) {
sum += (array.get(i) - average) * (array.get(i) - average);
}
return Math.sqrt((sum / (n - 1)));
}
public static Double grubbsP(Integer n,Double alpha){
Double[] gp1={1.135,1.463,1.672,1.822,1.938,2.032,2.110,2.176,2.234,2.285,2.331,2.371,2.409,2.443,2.475,2.504,2.532,2.557};//检出水平alpha=0.05,n=3--->n=20
Double[] gp2={1.155,1.492,1.749,1.944,2.097,2.231,2.323,2.410,2.485,2.550,2.607,2.659,2.705,2.747,2.785,2.821,2.854,2.884};//检出水平alpha=0.01, n=3--->n=20
if(alpha == 0.05){
return gp1[n-3];
}
if(alpha == 0.01){
return gp2[n-3];
}
return null;
}
}
Map数据的Controller方法,还是利用直接赋值,而不是数据库传值的方法,因为,数据库数据不全。。。
@PostMapping(value = "/getMapChartData", produces = "application/json;charset=utf-8")
public EchartData getMapChartData() {
String title = "MapChartData";
List category = new ArrayList<>();
List Legend = new ArrayList<>();
Map map = new HashMap();
map.put("山西", 11.3);
map.put("北京", 7.8);
map.put("浙江", 3.2);
map.put("上海", 6.3);
map.put("西藏", 0.6);
map.put("天津", 31.1);
map.put("广西", 2.1);
map.put("广东", 11.1);
map.put("山东", 15.1);
map.put("福建", 31.1);
map.put("湖北", 2.1);
map.put("湖南", 411.1);
map.put("浙江", 39.1);
map.put("江苏", 17.1);
map.put("青海", 31.1);
map.put("香港", 13.6);
map.put("台湾", 2.8);
map.put("河南", 42.1);
map.put("河北", 5.9);
map.put("吉林", 11.2);
int size = map.size();
List> list = new ArrayList>(size);
list.addAll(map.entrySet());
// 通过Collections.sort()排序
Collections.sort(list, new Comparator>() {
public int compare(Map.Entry o1, Map.Entry o2) {
return o1.getValue().compareTo(o2.getValue());
};
});
List mapData = new ArrayList<>();
ArrayList valueData = new ArrayList<>();
for (Map.Entry tt : list) {
Map temp = new HashMap<>();
temp.put("name", tt.getKey());
temp.put("value", tt.getValue());
valueData.add(tt.getValue());
mapData.add(JSONObject.fromObject(temp));
}
Grubbs grubbs = new Grubbs();
grubbs.comp(valueData);
Integer Max = (int) (Math.ceil(valueData.get(valueData.size() - 1)) / 10) * 10;
Integer Min = (int) Math.floor(valueData.get(0));
List series = new ArrayList<>();
series.add(new MapSeries(Max, Min, mapData));
EchartData echartData = new EchartData(title, category, Legend, series);
return echartData;
}
还是先看后续的json输出:
{
"title": "MapChartData",
"series": [{
"Max": 40,
"Min": 0,
"list": [{
"name": "西藏",
"value": 0.6
}, {
"name": "广西",
"value": 2.1
}, {
"name": "湖北",
"value": 2.1
}, {
"name": "台湾",
"value": 2.8
}, {
"name": "河北",
"value": 5.9
}, {
"name": "上海",
"value": 6.3
}, {
"name": "北京",
"value": 7.8
}, {
"name": "广东",
"value": 11.1
}, {
"name": "吉林",
"value": 11.2
}, {
"name": "山西",
"value": 11.3
}, {
"name": "香港",
"value": 13.6
}, {
"name": "山东",
"value": 15.1
}, {
"name": "江苏",
"value": 17.1
}, {
"name": "福建",
"value": 31.1
}, {
"name": "天津",
"value": 31.1
}, {
"name": "青海",
"value": 31.1
}, {
"name": "浙江",
"value": 39.1
}, {
"name": "河南",
"value": 42.1
}, {
"name": "湖南",
"value": 411.1
}]
}]
}
在大多数省份都是几十,而湖南确实411.1,如果不对这个数据进行晒出,那么就会出现下面这种情况:
这种显示,可以看出湖南一个省份特别突出,而其他省份的数据相互之间的差异则非常小,显示效果很差,而如果剔除异常出数据后:
这种显示就好多了,层次分明,除了审美有问题,选色太乏味,没什么大问题~即使是比较接近的数据,其相互之间的差异还是很明显的,所以这种方法还是可取的,而且从右侧的柱状图也可以看出,排序问题也解决了。
其实中间还有一个小问题,就是visualMap的显示问题,官方文档上说,如果visualMap的range不做设置默认为空,也就是min和max的范围就是range的范围,但是如果之前设置的range是固定的,那动态加载之后还是原来的range,除非重新设置range。所以就出现了这种情况:
当时仔细查了官方文档才知道visualMap的range这个参数,而解决的办法也很简单,就是在ajax动态加载max和min时,同时将range设置为null就可以了。
options.visualMap[0].range = null;