可视化展示中, 向下钻取的功能很常见. 这是有必要掌握的技能之一.
首先看官方示例.
实现这种图形展示, 关键点和难点在于数据格式的准备上. 通过查看示例代码, 可以看出, 主要需要两个参数:
series
和drilldown:series
.
经过测试发现,series
可以支持两种格式: 1.json
格式和数组
格式.
为了叙述方便, 这里定义
省
,市
,区/县
表示3级. 本文以实现这样的3级下钻为例.
关键图形参数
series
series: [{
name: '浏览器品牌',
colorByPoint: true,
data: [{
name: 'Microsoft Internet Explorer',
y: 56.33,
drilldown: 'Microsoft Internet Explorer'
}, {..},..]
其中, data中的元素笔者将其称作点(point)
, 包含name
-鼠标悬停显示的分类名称,y
-数值, drilldown
-寻找下钻后的数据的锚, drilldown:series
中会有对应的id
. (注: 最低一级一般不需要drilldown参数, 当然有不会影响显示, 这就为封装数据提供了小便利).
drilldown:series
drilldown: {
series: [{
name: 'Microsoft Internet Explorer',
id: 'Microsoft Internet Explorer',
data: [
[
'v11.0',
24.13
],
[..],...
}]
}
-
所有的需要向下钻取的数据都在
drilldown
字段里. 即市和区的数据都在drilldown
里配置. - 这里的
series
格式和上文的series
格式一样. - 这里的
data
格式为数组
样式. 经测试, 也可以用上文的json
样式. 这又为封装数据提供了便利. 注意:省级和市级由于需要向下钻取, 所以data不能为数组格式(没有那么智能, 差评).
需求
省市区对应了若干小分区. 现需要分组统计出每个省的分区个数占比, 即分布情况. 当下钻某个省时, 显示该省下所有市的分布情况...
数据库格式:
省 | 市 | 区 | 分区 |
---|---|---|---|
安徽 | 合肥 | 滨湖 | 007分区 |
后台数据封装
由于数据需要后台利用
java
查询数据库, 封装数据, 以json
格式响应给highchart. 所以, 封装数据上需要一番研究.
javabean设计
下文两种方式, 基于相同的Javabean设计.
-
Series
对应了highchart
中的series
字段. -
Point
代表一个点的数据, 多个Point构成了data
.
方式一:
思路:
- 先查询出所有省的分组统计结果.
- 准备用两个变量,
series
存储省相关数据,drilldownSeries
存储drilldown
相关数据, 即市和区/县的. - 取数据封装数据, 构建series类, 添加至
drilldownSeries
. 同时, 根据当前遍历到的省份, 根据名称查询旗下的所有市统计数据. - 然后遍历市级数据封装数据, 添加至
drilldownSeries
, 同时根据当前遍历到的市, 查询旗下的所有区/县统计数据. 封装成series类, 添加至drilldownSeries
.
代码:
public Map<String, Object> getData4DrillDownChart() {
// 一级: 省; 二级: 市; 三级: 区/县
// 获取分区总数, 作为分母, 计算百分比.
long s = dao.count();
double sum = s;
// 查询所有省及分组统计数据
List<Object[]> objecList = dao.findAreaByGroup();
// 定义省级series, 对应highchart的series属性
Series series = new Series();
series.setName("分区统计分布图");
// 省级(第一级)统计数据处理
for (Object[] province : objecList) {
Series.Point point = new Series.Point();
point.setName((String) province[0]);
long y = (long) province[1];
double perc = y/sum;
// 设置占比
point.setY(perc);
// drilldown ID就用省份名称
point.setDrilldown(point.getName());
// 添加至data
series.getData().add(point);
}
// 省级数据封装完成
/*[[下钻数据*/
// drilldown:series
List<Series> drilldownSeries = new ArrayList<>();
List<Series.Point> level_1_data = series.getData();
// 遍历所有省的名称
for (Series.Point p : level_1_data) {
// 查找对应的市级统计数据
List<Object[]> city_DB = dao.findByProvinceNameAndCountDistrictByProvinceAndCity(p.getName());
//定义drilldown里面的series, 市级series
Series series1 = new Series();
series1.setId(p.getDrilldown());
series1.setName(p.getName());
/*市级
* 遍历市级数据设置点集合(即前端的data属性)
* 并向下获取当前市的县级数据,构造series,添加至drilldownSeries*/
for (Object[] city : city_DB) {
Series.Point point = new Series.Point();
point.setName((String) city[0]);
Long y = (Long) city[1];
double perc = y/sum;
point.setY(perc);
// 二级drilldown用二级的名称
point.setDrilldown(point.getName());
// 添加点
series1.getData().add(point);
/*县级
* 根据当前市的名称获取对应县的数据, 并构造县级series, 添加至drilldownSeries*/
String cityName = point.getName();
// 获取当前市下的所有县级统计数据
// TODO: 2018/1/11 考虑到不同省可能有重名的城市名==>根据省名和市名联合查询 or 优化表设计,设置好ID.
List<Object[]> district_DB = dao.findByCityNameAndCountDistrictByProvinceCityAndDistrict(cityName);
//定义drilldown里面的series, 县级series
Series series2 = new Series();
series2.setId(point.getDrilldown());
series2.setName(point.getName());
// 遍历县级统计数据,设置data
for (Object[] district : district_DB) {
Series.Point point1 = new Series.Point();
point1.setName((String) district[0]);
Long y1 = (Long) district[1];
double perc1 = y1/sum;
point1.setY(perc1);
// 到县一级结束了, 不再drilldown
point1.setDrilldown(null);
// 添加至点集合
series2.getData().add(point1);
// TODO: 2018/1/11 如果还需要往下钻取, 则继续向下抓取相应数据\\程序需要优化(可以用递归)
}
// 添加至drilldownSeries
drilldownSeries.add(series2);
}
// 市级的series属性设置完成, 现在将其添加至drilldownSeries
drilldownSeries.add(series1);
}
/*]]下钻数据*/
// 将series和drilldownSeries返回给action
HashMap<String, Object> drilldownData = new HashMap<>();
drilldownData.put("series", series);
drilldownData.put("drilldownSeries", drilldownSeries);
return drilldownData;
}
方式2:
方式1虽然能够实现功能, 但是, 1.查询数据库的次数太多; 2.封装数据的业务逻辑复杂, 且不具有通用性, 硬编码.
方式2内容精彩, 所以另起一篇.
参见: java+highchart实现分类下钻柱形图[续]
附录
Series接口bean
import java.util.ArrayList;
import java.util.List;
/**
* @author Nisus-Liu
* @version 1.0.0
* @email liuhejun108@163.com
* @date 2018-01-10-22:53
*/
// name: '浏览器品牌',
// id:'brands',
// colorByPoint: true,
// data: [{},{},..]
// 其中data:
// name: 'Chrome',
// y: 24.03,
// drilldown: 'Chrome'
public class Series {
private String name;
private String id;
private boolean colorByPoint = true;
private List<Point> data = new ArrayList<Point>();
public List<Point> getData() {
return data;
}
public void setData(List<Point> data) {
this.data = data;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public boolean isColorByPoint() {
return colorByPoint;
}
public void setColorByPoint(boolean colorByPoint) {
this.colorByPoint = colorByPoint;
}
public static class Point{
private String name;
private double y;
private String drilldown;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public String getDrilldown() {
return drilldown;
}
public void setDrilldown(String drilldown) {
this.drilldown = drilldown;
}
}
}