最近做的一個在線氣象觀測網站要實現一個需求:使用圖表展示最近五天溫濕度等氣象要素的曲線變化
具體效果參考:http://www.weatherobserve.com/showInfoIndex.jsp
圖示如下(2016-5-25日的數據):
下面就詳述一下實現過程吧(注:相較於原網頁我隱去了很多內容,本實現過程就只專注於Echarts圖表實現)
一:
HTML頁面部分,代碼如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- 在IE運行最新的渲染模式 -->
<meta name="viewport" content="width=device-width, initial-scale=1"> <!-- 初始化移動瀏覽顯示 -->
<meta name="Author" content="Dreamer-1.">
<script type="text/javascript" src="js/jquery-1.12.3.min.js"></script>
<script type="text/javascript" src="js/echarts.common.min.js"></script>
<title>- 觀測數據 -</title>
</head>
<body>
<!-- 顯示Echarts圖表 -->
<div style="height:410px;min-height:100px;margin:0 auto;" id="main"></div>
<script type="text/javascript">
// 基於准備好的dom,初始化echarts實例
var myChart = echarts.init(document.getElementById('main'));
// 指定圖表的配置項和數據
var option = {
title: { //圖表標題
text: '過去五天數據圖表'
},
tooltip: {
trigger: 'axis', //坐標軸觸發提示框,多用於柱狀、折線圖中
/*
控制提示框內容輸出格式
formatter: '{b0}<br/><font color=#FF3333> ● </font>{a0} : {c0} ℃ ' +
'<br/><font color=#53FF53>● </font>{a1} : {c1} % ' +
'<br/><font color=#68CFE8> ● </font>{a3} : {c3} mm ' +
'<br/><font color=#FFDC35> ● </font>{a4} : {c4} m/s ' +
'<br/><font color=#B15BFF> ● </font>{a2} : {c2} hPa '
*/
},
dataZoom: [
{
type: 'slider', //支持鼠標滾輪縮放
start: 0, //默認數據初始縮放范圍為10%到90%
end: 100
},
{
type: 'inside', //支持單獨的滑動條縮放
start: 0, //默認數據初始縮放范圍為10%到90%
end: 100
}
],
legend: { //圖表上方的類別顯示
show:true,
data:['溫度(℃)','濕度(%)','雨量(mm)','風速(m/s)','壓強(hPa)']
},
color:[
'#FF3333', //溫度曲線顏色
'#53FF53', //濕度曲線顏色
'#B15BFF', //壓強圖顏色
'#68CFE8', //雨量圖顏色
'#FFDC35' //風速曲線顏色
],
toolbox: { //工具欄顯示
show: true,
feature: {
saveAsImage: {} //顯示“另存為圖片”工具
}
},
xAxis: { //X軸
type : 'category',
data : [] //先設置數據值為空,后面用Ajax獲取動態數據填入
},
yAxis : [ //Y軸(這里我設置了兩個Y軸,左右各一個)
{
//第一個(左邊)Y軸,yAxisIndex為0
type : 'value',
name : '溫度',
/* max: 120,
min: -40, */
axisLabel : {
formatter: '{value} ℃' //控制輸出格式
}
},
{
//第二個(右邊)Y軸,yAxisIndex為1
type : 'value',
name : '壓強',
scale: true,
axisLabel : {
formatter: '{value} hPa'
}
}
],
series : [ //系列(內容)列表
{
name:'溫度(℃)',
type:'line', //折線圖表示(生成溫度曲線)
symbol:'emptycircle', //設置折線圖中表示每個坐標點的符號;emptycircle:空心圓;emptyrect:空心矩形;circle:實心圓;emptydiamond:菱形
data:[] //數據值通過Ajax動態獲取
},
{
name:'濕度(%)',
type:'line',
symbol:'emptyrect',
data:[]
},
{
name:'壓強(hPa)',
type:'line',
symbol:'circle', //標識符號為實心圓
yAxisIndex: 1, //與第二y軸有關
data:[]
},
{
name:'雨量(mm)',
type:'bar', //柱狀圖表示
//barMinHeight: 10, //柱條最小高度,可用於防止某數據項的值過小而影響交互
/* label: { //顯示值
normal: {
show: true,
position: 'top'
}
}, */
data:[]
},
{
name:'風速(m/s)',
type:'line',
symbol:'emptydiamond',
data:[]
}
]
};
myChart.showLoading(); //數據加載完之前先顯示一段簡單的loading動畫
var tems=[]; //溫度數組(存放服務器返回的所有溫度值)
var hums=[]; //濕度數組
var pas=[]; //壓強數組
var rains=[]; //雨量數組
var win_sps=[]; //風速數組
var dates=[]; //時間數組
$.ajax({ //使用JQuery內置的Ajax方法
type : "post", //post請求方式
async : true, //異步請求(同步請求將會鎖住瀏覽器,用戶其他操作必須等待請求完成才可以執行)
url : "ShowInfoIndexServlet", //請求發送到ShowInfoIndexServlet處
data : {name:"A0001"}, //請求內包含一個key為name,value為A0001的參數;服務器接收到客戶端請求時通過request.getParameter方法獲取該參數值
dataType : "json", //返回數據形式為json
success : function(result) {
//請求成功時執行該函數內容,result即為服務器返回的json對象
if (result != null && result.length > 0) {
for(var i=0;i<result.length;i++){
tems.push(result[i].tem); //挨個取出溫度、濕度、壓強等值並填入前面聲明的溫度、濕度、壓強等數組
hums.push(result[i].hum);
pas.push(result[i].pa);
rains.push(result[i].rain);
win_sps.push(result[i].win_sp);
dates.push(result[i].dateStr);
}
myChart.hideLoading(); //隱藏加載動畫
myChart.setOption({ //載入數據
xAxis: {
data: dates //填入X軸數據
},
series: [ //填入系列(內容)數據
{
// 根據名字對應到相應的系列
name: '溫度',
data: tems
},
{
name: '濕度',
data: hums
},
{
name: '壓強',
data: pas
},
{
name: '雨量',
data: rains
},
{
name: '風速',
data: win_sps
}
]
});
}
else {
//返回的數據為空時顯示提示信息
alert("圖表請求數據為空,可能服務器暫未錄入近五天的觀測數據,您可以稍后再試!");
myChart.hideLoading();
}
},
error : function(errorMsg) {
//請求失敗時執行該函數
alert("圖表請求數據失敗,可能是服務器開小差了");
myChart.hideLoading();
}
})
myChart.setOption(option); //載入圖表
</script>
</body>
</html>
二:
Servlet部分,客戶端請求提交到 ShowInfoIndex 處,先在 web.xml 里配置一下Servlet映射:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
<display-name>BlogExample</display-name>
<servlet>
<servlet-name>ShowInfoIndexServlet</servlet-name>
<servlet-class>EchartsExample.ShowInfoIndexServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ShowInfoIndexServlet</servlet-name>
<url-pattern>/ShowInfoIndexServlet</url-pattern>
</servlet-mapping>
</web-app>
關於ShowInfoIndexServlet,簡單說一下請求-響應中間的過程:
客戶端發送圖表請求給Servlet,Servlet接收到請求后先獲取客戶端請求查看的氣象站名稱,然后從數據庫(SqlServer2005 Express版)中獲取最近五天內該氣象站所有的采集數據(裝在List中),再用Gson工具將該List轉換成Json對象返回給客戶端,客戶端接收到返回的Json對象后對其進行解析並將相應數據填入Echarts中,然后作顯示;
其中Record.java是只對外提供get/set方法的用於封裝數據的普通實體類,DBUtil.java是JDBC方式下專門提供Connection、Statement、ResultSet等的數據庫工具類。
(原本的連接數據庫並獲取數據過程需經過業務邏輯層與數據訪問層,較為復雜,這里隱去這兩層,直接在Servlet內連接數據庫並拿取數據)
Ps:牆裂建議使用PreparedStatement進行參數化查詢,這樣可以有效避免SQL注入!
ShowInfoIndexServlet代碼如下:
package EchartsExample;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.gson.Gson;
/**
* 響應觀測記錄展示頁的Echarts圖表數據請求(使用json格式返回客戶端需要的數據)
* @author zhong
*
*/
public class ShowInfoIndexServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8"); //設定客戶端提交給servlet的內容按UTF-8編碼
response.setCharacterEncoding("UTF-8"); //設定servlet傳回給客戶端的內容按UTF-8編碼
response.setContentType("text/html;charset=UTF-8"); //告知瀏覽器用UTF-8格式解析內容
String name = request.getParameter("name"); //獲取台站名參數
//獲取當天在內的五天以前的0點格式字符串(用於數據庫查詢)
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -4); //獲取當天在內的五天以前的日期時間
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd 00:00:00"); //設定日期格式
String fiveDaysAgoStr = sdf1.format(cal.getTime()); //將五天前的日期時間按指定格式轉換成字符串
//獲取當前時間並將其轉換成指定格式的字符串(用於數據庫查詢)
Date now = new Date();
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String nowStr = sdf2.format(now);
//System.out.println(nowStr);
//======================================連接數據庫操作============================================================================================
/*
* 連接數據庫並獲取五天內該名稱的氣象站的所有采集數據
*/
List<Record> records = new ArrayList<Record>(); //用一個ArrayList來盛裝封裝了各氣象數據的對象
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection(); //獲取與數據庫的連接
String sql = "select * from alldata where data_taizhan_num = ? and data_date >= ? and data_date <= ? order by data_date asc"; //初始化SQL查詢語句
pstmt = conn.prepareStatement(sql); //創建preparedStatement語句對象
pstmt.setString(1, name); //設定查詢參數
pstmt.setString(2, fiveDaysAgoStr);
pstmt.setString(3, nowStr);
rs = pstmt.executeQuery(); //獲取查詢到的結果集
while (rs.next()) {
//封裝Record對象
Record r = new Record();
r.setTaizhan_num(rs.getString(1));
r.setDate(rs.getTimestamp(2));
r.setTem(rs.getString(3));
r.setHum(rs.getString(4));
r.setPa(rs.getString(5));
r.setRain(rs.getString(6));
r.setWin_dir(rs.getString(7));
r.setWin_sp(rs.getString(8));
//將時間轉換成給定格式便於echarts的X軸日期坐標顯示
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String str = sdf.format(rs.getTimestamp(2));
r.setDateStr(str);
//System.out.println(r.getTem()+" | "+r.getHum()+" | "+r.getPa()+" | "+r.getRain()+" | "+r.getWin_dir()+" | "+r.getWin_sp());
records.add(r); //將封裝好的Record對象放入列表容器中
}
} catch (SQLException e) {
System.out.println("查詢出錯,操作未完成!");
e.printStackTrace();
} finally {
//查詢結束后釋放資源
DBUtil.close(rs);
DBUtil.close(pstmt);
DBUtil.close(conn);
}
//======================================連接數據庫操作(完)============================================================================================
//將list中的對象轉換為Json格式的數組
Gson gson = new Gson();
String json = gson.toJson(records);
//System.out.println(json);
//將json數據返回給客戶端
response.setContentType("text/html; charset=utf-8");
response.getWriter().write(json);
}
}
alldata表部分數據截圖:
Record類代碼如下:
package EchartsExample;
import java.sql.*;
/**
* 封裝氣象數據信息
* @author zhong
*
*/
public class Record {
private String taizhan_num; //台站名
private String tem; //溫度
private String hum; //濕度
private String pa; //壓強
private String rain; //雨量
private String win_dir; //風向
private String win_sp; //風速
private String dateStr; //觀測日期(用於Echarts顯示格式)
private Timestamp date; //觀測日期(原始格式)
/**
* 獲取觀測日期(用於echarts圖表展示);
* @return 觀測日期值
*/
public String getDateStr() {
return dateStr;
}
/**
* 設置觀測日期(用於echarts圖表展示);
* @param dateStr 待設置觀測日期值
*/
public void setDateStr(String dateStr) {
this.dateStr = dateStr;
}
/**
* 獲取產生該觀測記錄的台站名稱;
* @return 台站名稱
*/
public String getTaizhan_num() {
return taizhan_num;
}
/**
* 設置產生該觀測記錄的台站名稱;
* @param taizhan_num 待設置台站名稱
*/
public void setTaizhan_num(String taizhan_num) {
this.taizhan_num = taizhan_num;
}
/**
* 獲取溫度;
* @return 溫度值
*/
public String getTem() {
return tem;
}
/**
* 設置溫度;
* @param tem 待設置溫度值
*/
public void setTem(String tem) {
this.tem = tem;
}
/**
* 獲取濕度;
* @return 濕度值
*/
public String getHum() {
return hum;
}
/**
* 設置濕度;
* @param hum 待設置濕度值
*/
public void setHum(String hum) {
this.hum = hum;
}
/**
* 獲取壓強;
* @return 壓強值
*/
public String getPa() {
return pa;
}
/**
* 設置壓強;
* @param pa 待設置壓強值
*/
public void setPa(String pa) {
this.pa = pa;
}
/**
* 獲取雨量;
* @return 雨量值
*/
public String getRain() {
return rain;
}
/**
* 設置雨量;
* @param rain 待設置雨量值
*/
public void setRain(String rain) {
this.rain = rain;
}
/**
* 獲取風向;
* @return 風向值
*/
public String getWin_dir() {
return win_dir;
}
/**
* 設置風向;
* @param win_dir 待設置風向值
*/
public void setWin_dir(String win_dir) {
this.win_dir = win_dir;
}
/**
* 獲取風速;
* @return 風速值
*/
public String getWin_sp() {
return win_sp;
}
/**
* 設置風向;
* @param win_sp 待設置風向值
*/
public void setWin_sp(String win_sp) {
this.win_sp = win_sp;
}
/**
* 獲取觀測日期;
* @return 觀測日期
*/
public Timestamp getDate() {
return date;
}
/**
* 設置觀測日期;
* @param date 觀測日期值
*/
public void setDate(Timestamp date) {
this.date = date;
}
}
DBUitl類(數據庫工具類)代碼如下:
package EchartsExample;
import java.sql.*;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.apache.tomcat.jdbc.pool.PoolProperties;
/**
* 數據庫工具類(采用了tomcat jdbc pool)
* @author zhong
*
*/
public class DBUtil {
private static DataSource ds;
static {
//配置tomcat jdbc pool (連接池)
PoolProperties p = new PoolProperties();
p.setUrl("jdbc:sqlserver://localhost:1433; DatabaseName=weather"); //設置連接的url
p.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); //載入數據庫驅動
p.setUsername("sa"); //用於遠程連接的用戶名
p.setPassword("2003NianDeDiYiChangXue"); //密碼
p.setJmxEnabled(true);
p.setTestWhileIdle(false);
p.setTestOnBorrow(true);
p.setValidationQuery("SELECT 1");
p.setTestOnReturn(false);
p.setValidationInterval(30000);
p.setTimeBetweenEvictionRunsMillis(30000);
p.setMaxActive(100);
p.setInitialSize(10);
p.setMaxWait(10000);
p.setRemoveAbandonedTimeout(60);
p.setMinEvictableIdleTimeMillis(30000);
p.setMinIdle(10);
p.setLogAbandoned(true);
p.setRemoveAbandoned(true);
p.setJdbcInterceptors(
"org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;"+
"org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer");
ds = new DataSource();
ds.setPoolProperties(p);
}
private DBUtil() {}
/**
* 獲取一個數據庫連接(Connection);
* @return Database Connection
*/
public static Connection getConnection() {
Connection conn = null;
try {
conn = ds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/**
* 關閉傳入的Connection;
* @param conn 待關閉的Connection
*/
public static void close(Connection conn) {
try {
if (conn != null) {
conn.close();
conn = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 關閉傳入的Statement;
* @param stmt 待關閉的Statement
*/
public static void close(Statement stmt) {
try {
if (stmt != null) {
stmt.close();
stmt = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 關閉傳入的ResultSet;
* @param rs 待關閉的ResultSet
*/
public static void close(ResultSet rs) {
try {
if (rs != null) {
rs.close();
rs = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
——————————————————————————我是小小分割線————————————————————————————————————
關於遠程連接(包括使用Eclipse連接)SqlServer2005我再多嘴兩句:
當確認連接的URL,驅動加載,用戶名,密碼都配置正確時,仍然拋出 java.lang.NullPointerException 的話,請打開開始菜單Microsoft SQL Server 2005軟件目錄下的SQL Server配置管理器:
①:啟用SQL Server 2005網絡配置 下的 SQLEXPRESS協議 內的 TCP/IP 協議,並右鍵TCP/IP協議選擇屬性,確保IP地址一欄最末的TCP端口為1433,具體見下圖
②:啟用 SQL Native Client配置 下的 客戶端協議 內的 TCP/IP 協議,並右鍵查看TCP/IP 屬性,確保端口為1433,具體見下圖
③:重新啟動 SQL Server 2005服務 下的 SQL Server服務,詳情見下圖
至此,應該就解決了java.lang.NullPointerException這個錯誤了。
————————————————————————我是小小分割線————————————————————————————————————
讓我們再回到原來的Echarts圖表顯示過程上,你可以試着在后台打印看看轉換出的Json字符串,關於Json的使用這里我不再多言解釋,具體可參考簡單易懂的官方教程:http://www.w3school.com.cn/json/
最后再貼一下實現效果吧:







