一、前提说明
1.今天给大家带来的是Echarts的中国地图可视化(数据从后台获取),这是很多Echarts教程没有提到的知识,因自己去过北京参加了一次高校教师培训会(本人是学生),用的是Echarts获取Servlet类中的数据。刚好本次假期又有学校老师让做疫情地图,所以便结合所学,对学校提供数据(sql数据),用Echarts的map系列做了一个疫情地图。
2.本人开发环境 (jdk 1.8) (mysql5.6) (tomcat 7),强调该点是因为笔者以前用 jdk10 执行同样代码,有时候会获取不到后台servlet的数据,换成较低的jdk1.8后,相对来说,少了很多问题,若只是学习所用,jdk1.8已经足够。
3.该篇博客需要一些Echarts基础知识,笔者在此提供学习途径:https://www.imooc.com/qa/687/t/0,只需要两小时,对Echarts的使用就会有个基础的认识,以及基本案例的实现。
4.本人没有系统学习过Web开发,对web前后台交互并不熟悉,但学校有教过JSP,Js,Struts2等知识外加自己也做过一些小的web项目,对web开发基本的一套有个基本的了解。
5.该博客涉及的配套资源链接:https://pan.baidu.com/s/1XE5AEGXZHUr6LqYvC70-1A 提取码:0w4i
二、效果展示
三、开发步骤
1.项目结构(js文件下的provinces文件夹放的是各个省的js文件,NewFile.jsp为中国地图)
注意:虽然该Web项目中用到了Servlet类,但无需创建动态Web项目后在web.xml中进行servlet配置,如果配置可能会导致ajax无法获取到servlet中数据,大家可结合实际情况测试笔者所述。
2.NewFile.jsp代码(中国地图前端实现代码)
该部分代码重难点:(笔者在此提供本人解决这些问题的参考网址,如下)
后台传向前端时JSON数据格式的转化:
https://blog.csdn.net/qq_37855506/article/details/79423161 该博主对Json格式各种转化做了一个概括
https://blog.csdn.net/zhang33565417/article/details/100020313 该博主针对Echarts中国地图所需数据格式进行了实现,从后台加载到前端地图的数据没有问题,但会存在某些省浮标显示存在问题
tooltip.formatter属性地图浮标实现解决:https://segmentfault.com/q/1010000008776041
echarts官网提供案例:https://www.echartsjs.com/examples/zh/editor.html?c=doc-example/map-example
虽然笔者结合这些博客对存在问题进行了解决并实现,但也想分享给各位读者,让各位读者有更深刻的认识。
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>大学师生疫情去向</title>
<!-- 引入 echarts.js -->
<script src="js/echarts.min.js"></script>
<script src="js/china.js"></script>
<script src="js/jquery.min.js"></script>
<script src="js/provinces/hunan.js"></script>
</head>
<body onload="showLeftTime()">
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<label id="show" style="position: absolute; right: 100px; top: 45px">当前时间</label>
<div id="main" align="center" style="width: 1250px;height:650px;"></div>
<script type="text/javascript">
var myChart = echarts.init(document.getElementById('main'));
// var oBack = document.getElementById("back");
var provinces = ['shanghai', 'hebei', 'shanxi', 'neimenggu', 'liaoning', 'jilin', 'heilongjiang', 'jiangsu', 'zhejiang', 'anhui', 'fujian', 'jiangxi', 'shandong', 'henan', 'hubei', 'hunan', 'guangdong', 'guangxi', 'hainan', 'sichuan', 'guizhou', 'yunnan', 'xizang', 'shanxi1', 'gansu', 'qinghai', 'ningxia', 'xinjiang', 'beijing', 'tianjin', 'chongqing', 'xianggang', 'aomen','taiwan'];
var provincesText = ['上海', '河北', '山西', '内蒙古', '辽宁', '吉林', '黑龙江', '江苏', '浙江', '安徽', '福建', '江西', '山东', '河南', '湖北', '湖南', '广东', '广西', '海南', '四川', '贵州', '云南', '西藏', '陕西', '甘肃', '青海', '宁夏', '新疆', '北京', '天津', '重庆', '香港', '澳门','台湾'];
function showLeftTime()
{
var now=new Date();
var year=now.getFullYear();
var month=now.getMonth();
var day=now.getDate();
var hours=now.getHours();
var minutes=now.getMinutes();
document.all.show.innerHTML=""+year+"年"+month+"月"+day+"日 "+hours+":"+minutes;
//一秒刷新一次显示时间
var timeID=setTimeout(showLeftTime,1000);
}
//echarts点击事件函数,echarts官网提供使用案例
myChart.on('click', function (params) {
var provinceName=params.name;
console.log(params.name); // 控制台打印数据的名称
showProvince(provinceName);
//myChart.off("click");
});
// 展示对应的省
function showProvince(pName) {
for (var i = 0; i < provincesText.length; i++) {
if (pName === provincesText[i]) {
//显示对应省份的方法
window.open(provinces[i]+".jsp",'_self','',true);
break;
}
}
}
$.ajax({
type:'post',
dataType:'json',
url:'/d4/infoServlet',
success:function(data){
var userObj1 = eval(data.list1); //转为JSON格式
var userObj2 = eval(data.list2);
// alert(userObj1[0].value);
// alert(userObj2[0].value); //打印内容,监测是否从后台获取到数据
var sum1=0;
var sum2=0;
for(var a in userObj1) {
sum1=sum1+userObj1[a].value;
}
console.log(sum1);
for(var a in userObj2){
sum2=sum2+userObj2[a].value;
}
console.log(sum2); //打印到浏览器控制台
var option = {
title : {
text: '大学师生疫情去向图',
subtext: '总人数:'+(sum1+sum2)+'人 教师:'+sum1+'人 学生:'+sum2+'人',
left: 'center',
textStyle: {
fontFamily: 'Arial',
fontSize: 50,
fontStyle: 'normal',
fontWeight: 'normal',
},
subtextStyle: {//副标题文本样式{"color": "#aaa"}
fontSize: 20,
fontStyle: 'normal',
fontWeight: 'normal',
}
},
tooltip : {
trigger: 'item',
formatter:function(params){
//定义一个res变量来保存最终返回的字符结果,并且先把地区名称放到里面
var res=params.name+'<br/>'+'总数:';
//定义一个变量来保存series数据系列
var myseries=option.series;
var sum=0;
//循环遍历series数据系列
for(var i=0;i<myseries.length;i++){
//在内部继续循环series[i],从data中判断:当地区名称等于params.name的时候就将当前数据和名称添加到res中供显示
for(var k=0;k<myseries[i].data.length;k++){
//console.log(myseries[i].data[k].name);
//如果data数据中的name和地区名称一样
if(myseries[i].data[k].name==params.name){
//将series数据系列每一项中的name和数据系列中当前地区的数据添加到res中
if(i==0){
for(var j=0;j<myseries[1].data.length;j++){
if(myseries[1].data[j].name&&myseries[1].data[j].name==params.name){
sum=myseries[1].data[j].value+myseries[i].data[k].value;
break;
}
else if(j==myseries[1].data.length-1){
sum=myseries[i].data[k].value;
}
}
res+=sum+'<br/>'+myseries[i].name+':'+myseries[i].data[k].value;
}
else{
if(sum==0){
sum=myseries[i].data[k].value;
res+=sum+'<br/>'+myseries[i].name+':'+myseries[i].data[k].value+'<br/>';
}
else{
res+='<br/>'+myseries[i].name+':'+myseries[i].data[k].value+'<br/>';
}
}
}
}
}
return res;
},
},
legend: {
orient: 'vertical',
left: '6%',
top:'12%',
align:'left',
itemHeight:'20',
data:['教师','学生']
},
visualMap: {
min: 0,
max: 5000,
left: '6%',
bottom: '4%',
text:['高','低'], // 文本,默认为数值文本
calculable : true
},
toolbox: {
show: true,
orient : 'vertical',
left: 'right',
top: 'center',
itemSize:'20',
feature : {
mark : {show: true},
dataView : {show: true, readOnly: false},
restore : {show: true},
saveAsImage : {show: true}
}
},
series : [
{
name: '教师',
type: 'map',
mapType: 'china',
roam: true,
label: {
normal: {
show: true
},
emphasis: {
show: true
}
},
data: userObj1
},
{
name: '学生',
type: 'map',
mapType: 'china',
roam: true,
label: {
normal: {
show: true
},
emphasis: {
show: true
}
},
data: userObj2
}
]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
},
error:function () {
alert("出错了");
}
});
</script>
</body>
</html>
3.com.bean包中infoBean.java类代码
package com.bean;
public class infoBean {
private String name; //省名
private int value; //省对应人数值
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public infoBean(String name, int value) {
super();
this.name = name;
this.value = value;
}
}
4.com.dao包中的infoDao.java类代码(含表结构及查询语句结果展示)
package com.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.bean.infoBean;
public class infoDao {
static{
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Connection getConn()
{
try {
Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/xzmu_yqm?characterEncoding=utf8","root","root"); //数据库驱动语句,请自行修改库名、用户名、密码
System.out.println("链接成功");
return conn;
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public Map<String, Object> getAllInfo() throws SQLException
{
ArrayList<infoBean> list1=new ArrayList<>();
ArrayList<infoBean> list2=new ArrayList<>();
Map<String, Object> map=new HashMap<String, Object>();
String Sql = "SELECT province,category,count(*) AS counts from table_db GROUP BY province,category"; //数据库查询语句,根据需要自己替换
Connection conn=getConn();
PreparedStatement pre=conn.prepareStatement(Sql);
ResultSet rs=pre.executeQuery();
while(rs.next())
{
if(rs.getString("category").trim().equals("教师"))
{
infoBean info=new infoBean(rs.getString("province").trim(),rs.getInt("counts"));
list1.add(info);
}
else if(rs.getString("category").trim().equals("学生"))
{
infoBean info=new infoBean(rs.getString("province").trim(),rs.getInt("counts"));
list2.add(info);
}
}
map.put("list1", list1);
map.put("list2", list2);
return map;
}
public static void main(String[] args)
{ //单独运行该类,可检测是否从数据库中查询到了数据,若检测到数据,可屏蔽该主类
try {
System.out.println(new infoDao().getAllInfo());
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
5.com.servlet包的infoServlet.java类代码(创建时请直接创建servlet类)
创建servlet类后,请直接运行该类,若无报错或有如下结果,证明servlet类可正常响应(jdk10不配置web.xml直接运行创建的servlet类可能会报错,这也是为啥我会换成jdk1.8的原因)
确保servlet正常响应以后,代码如下:
package com.servlet;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.bean.infoBean;
import com.dao.infoDao;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
/**
* Servlet implementation class infoServlet
*/
@WebServlet("/infoServlet")
public class infoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public infoServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
// response.getWriter().append("Served at: ").append(request.getContextPath());
response.setContentType("text/html;charset=utf-8");
infoDao dao =new infoDao();
Map<String, Object> map = new HashMap<>();
try {
map=dao.getAllInfo();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JSONObject fb = JSONObject.fromObject(map); //Json格式转化
response.getWriter().print(fb);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
至此,中国地图加载数据库中数据已经可以实现。
6.点击某省后进入省级地图代码实现(实现方法类似中国地图的实现,以安徽为例)
注意各省jsp取名为各省 拼音.jsp,如anhui.jsp,代码如下
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>安徽</title>
<!-- 引入 echarts.js -->
<script src="js/echarts.min.js"></script>
<script src="js/china.js"></script>
<script src="js/jquery.min.js"></script>
<script src="js/provinces/anhui.js"></script>
</head>
<body>
<!-- 为ECharts准备一个具备大小(宽高)的Dom -->
<button id="back" align='left'>返回全国</button>
<div id="main" style="width: 900px;height:600px;" align="center"></div>
<script type="text/javascript">
var myChart = echarts.init(document.getElementById('main'));
var oBack = document.getElementById("back");
// var provinces = ['shanghai', 'hebei', 'shanxi', 'neimenggu', 'liaoning', 'jilin', 'heilongjiang', 'jiangsu', 'zhejiang', 'anhui', 'fujian', 'jiangxi', 'shandong', 'henan', 'hubei', 'hunan', 'guangdong', 'guangxi', 'hainan', 'sichuan', 'guizhou', 'yunnan', 'xizang', 'shanxi1', 'gansu', 'qinghai', 'ningxia', 'xinjiang', 'beijing', 'tianjin', 'chongqing', 'xianggang', 'aomen'];
// var provincesText = ['上海', '河北', '山西', '内蒙古', '辽宁', '吉林', '黑龙江', '江苏', '浙江', '安徽', '福建', '江西', '山东', '河南', '湖北', '湖南', '广东', '广西', '海南', '四川', '贵州', '云南', '西藏', '陕西', '甘肃', '青海', '宁夏', '新疆', '北京', '天津', '重庆', '香港', '澳门'];
myChart.on('click', function (params) {
// 控制台打印数据的名称
// var provinceName=params.name;
console.log(params.name);
// showProvince(provinceName);
myChart.off("click");
});
//按钮点击事件
oBack.onclick = function () {
window.open("NewFile.jsp",'_self','',true);
};
$.ajax({
type:'post',
dataType:'json',
url:'/d4/anhui',
success:function(data){
var option = {
title : {
text: '大学师生疫情去向图--安徽',
subtext: '模拟测试',
left: 'center'
},
tooltip : {
trigger: 'item',
formatter:function(params){
//定义一个res变量来保存最终返回的字符结果,并且先把地区名称放到里面
var res=params.name+'<br/>'+'总数:';
//定义一个变量来保存series数据系列
var myseries=option.series;
var sum=0;
//循环遍历series数据系列
for(var i=0;i<myseries.length;i++){
//在内部继续循环series[i],从data中判断:当地区名称等于params.name的时候就将当前数据和名称添加到res中供显示
for(var k=0;k<myseries[i].data.length;k++){
//console.log(myseries[i].data[k].name);
//如果data数据中的name和地区名称一样
if(myseries[i].data[k].name==params.name){
//将series数据系列每一项中的name和数据系列中当前地区的数据添加到res中
if(i==0){
for(var j=0;j<myseries[1].data.length;j++){
if(myseries[1].data[j].name&&myseries[1].data[j].name==params.name){
sum=myseries[1].data[j].value+myseries[i].data[k].value;
break;
}
else if(j==myseries[1].data.length-1){
sum=myseries[i].data[k].value;
}
}
res+=sum+'<br/>'+myseries[i].name+':'+myseries[i].data[k].value;
}
else{
if(sum==0){
sum=myseries[i].data[k].value;
res+=sum+'<br/>'+myseries[i].name+':'+myseries[i].data[k].value+'<br/>';
}
else{
res+='<br/>'+myseries[i].name+':'+myseries[i].data[k].value+'<br/>';
}
}
}
}
}
return res;
},
},
legend: {
orient: 'vertical',
left: 'left',
data:['教师','学生']
},
visualMap: {
min: 0,
max: 2500,
left: 'left',
top: 'bottom',
text:['高','低'], // 文本,默认为数值文本
calculable : true
},
toolbox: {
show: true,
orient : 'vertical',
left: 'right',
top: 'center',
feature : {
mark : {show: true},
dataView : {show: true, readOnly: false},
restore : {show: true},
saveAsImage : {show: true}
}
},
series : [
{
name: '教师',
type: 'map',
mapType: '安徽',
roam: true,
label: {
normal: {
show: true
},
emphasis: {
show: true
}
},
data: data.list1
},
{
name: '学生',
type: 'map',
mapType: '安徽',
roam: true,
label: {
normal: {
show: true
},
emphasis: {
show: true
}
},
data: data.list2
}
]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
},
error:function () {
alert("出错了");
}
});
</script>
</body>
</html>
创建anhui.java的servlet类,写入如下代码
package com.servlet;
import java.io.IOException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.dao.infoDao;
import net.sf.json.JSONObject;
/**
* Servlet implementation class anhui
*/
@WebServlet("/anhui")
public class anhui extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public anhui() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
infoDao dao =new infoDao();
Map<String, Object> map = new HashMap<>();
/* ArrayList<infoBean> list1=new ArrayList<infoBean>();
ArrayList<infoBean> list2=new ArrayList<infoBean>();*/
try {
map=dao.getAllInfo18();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JSONObject fb = JSONObject.fromObject(map);
response.getWriter().print(fb);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
在infoDao.java类中加入如下代码段,即可实现中国地图到安徽省的数据下钻功能
public Map<String, Object> getAllInfo18() throws SQLException
{
ArrayList<infoBean> list1=new ArrayList<>();
ArrayList<infoBean> list2=new ArrayList<>();
Map<String, Object> map=new HashMap<String, Object>();
String Sql = "SELECT province,category,town,count(*) AS counts from table_db GROUP BY province,category,town HAVING province='安徽' ";
Connection conn=getConn();
PreparedStatement pre=conn.prepareStatement(Sql);
ResultSet rs=pre.executeQuery();
while(rs.next())
{
if(rs.getString("category").trim().equals("教师"))
{
infoBean info=new infoBean(rs.getString("town").trim(),rs.getInt("counts"));
list1.add(info);
}
else if(rs.getString("category").trim().equals("学生"))
{
infoBean info=new infoBean(rs.getString("town").trim(),rs.getInt("counts"));
list2.add(info);
}
}
map.put("list1", list1);
map.put("list2", list2);
return map;
}
下图为安徽省查询语句结果图