ECharts實現動態曲線(上),模擬數據是在瀏覽器端生成的。
現在,我們這樣做:把模擬數據的生產放到服務器端,瀏覽器通過Ajax
發送請求獲取服務端數據。
效果圖
實現
服務器使用了一個定時器。只要服務器一啓動,定時器就會開始工作,每隔1000ms
生成一次新數據。
瀏覽器端也使用了一個定時器,點擊“開始”按鈕,該定時器纔會開啓,每間隔250ms
就會向服務端發送一次請求,以獲取新數據來更新圖表。
遺留問題
服務端定時器的時間間隔是1000ms
,瀏覽器這邊定時器的時間間隔是250ms
。忽略因不同步可能導致的時間差,理論上講,1s
內,瀏覽器能發出4次
請求,服務器因此返回4份
響應。就像這樣,
現在遇到這麼一個問題:在圖表動態更新的過程中,切換到了其他標籤頁,瀏覽器裏定時器的工作受到了影響,即1s
內,瀏覽器只能發出了1次
請求,服務器因此只返回1份
響應。更糟糕的事情是,曲線上出現斷點,丟數據了。不過,切回到原標籤頁,定時器又恢復正常了。
想想辦法吧。
目錄結構
瀏覽器端
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>動態曲線</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"
integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
crossorigin="anonymous">
</script>
<link rel="stylesheet" href="./index.css">
</head>
<body>
<div id="myChart"></div>
<div class="container">
<div class="buttons">
<div class="button" id="start">開始</div>
<div class="button" id="end"> 結束</div>
</div>
</div>
<script src="./MyButton.js"></script>
<script src="./MyChart.js"></script>
<script src="./index.js"></script>
</body>
</html>
//index.js
initButtons();
const myChart = new MyChart(document.getElementById("myChart"));
myChart.init();
function initButtons(){
var timer;
const startBtn = new MyButton(document.querySelector("#start"));
startBtn.addEventListener("click",function(){
timer = startTimer();
startBtn.disable();
});
const endBtn = new MyButton(document.querySelector("#end"));
endBtn.addEventListener("click",function(){
closeTimer(timer);
startBtn.enable();
});
}
function startTimer(){
return timer = setInterval(function(){
myChart.getDataFromServer();
},250,myChart);
}
function closeTimer(timer){
clearInterval(timer)
}
//MyButton.js
(function(win){
function MyButton(elm){
this.elm = elm;
}
MyButton.prototype.addEventListener = function(type,handler){
if(this.elm.addEventListener){
this.elm.addEventListener(type,handler,false);
}else {
this.elm["on"+type] = handler;
}
}
MyButton.prototype.disable = function(){
this.elm.classList.add("disabled");
}
MyButton.prototype.enable = function(){
this.elm.classList.remove("disabled");
}
win.MyButton = MyButton;
}(window));
//MyChart.js
(function(win){
function MyChart(elm){
this.elm = elm;
this.chart = echarts.init(elm);
this.chart.gap = 40;
}
MyChart.prototype.init = function(){
const times = [];
const values = [];
const gap = this.chart.gap;
const option = {
grid:{
bottom:60,
top:gap,
left:gap,
right:gap,
},
xAxis: {
type: 'category',
data:times,
axisLabel:{
rotate:45
},
axisTick: {
alignWithLabel: true
},
name:"時間"
},
yAxis: {
type: 'value',
min:0,
max:1,
name:"帶寬利用率"
},
series: [{
data:values,
type: 'line',
smooth: true
}],
tooltip: {
trigger: 'axis',
axisPointer: {
type:"line"
}
}
};
this.chart.setOption(option);
}
MyChart.prototype.update = function(data){
const {times,values} = data;
const formatedTimes = times.map(t => formatTime(new Date(t)));
console.log(formatedTimes);
this.chart.setOption({
xAxis:{
data:formatedTimes
},
series:[{
data:values
}]
});
}
MyChart.prototype.getDataFromServer = function(){
const myChart = this;
$.ajax({
type:"get",
url:"/getData",
success:function(res){
myChart.update(res);
}
})
}
function formatTime(time){
const hour = time.getHours();
const min = time.getMinutes();
const sec = time.getSeconds();
const h = hour<10 ? "0"+hour : hour;
const m = min<10 ? "0"+min : min;
const s = sec<10 ? "0"+sec : sec;
return h+":"+m+":"+s;
}
win.MyChart = MyChart;
}(window))
//index.css
#myChart{
display:inline-block;
width:400px;height:200px;
border:1px solid lightgray;
border-right:none;
vertical-align:middle;
}
.container{
display:inline-block;
width:80px;height:200px;
border:1px solid lightgray;
border-left:none;
margin:-5px;
vertical-align:middle;
}
.buttons{
display:table-cell;
width:inherit;height: inherit;
vertical-align: middle;
}
.button{
font-size:0.75em;
padding:.3em;
background-color:rgba(0,0,255,.5);
border:1px solid transparent;
border-radius:.5em;
box-shadow:1px 1px 1px black;
text-align:center;
margin:1em;
}
.button:hover{
cursor:pointer;
background-color:rgba(0,0,255,1);
color:white;
}
.disabled{
background-color:lightgray;
color:lavender;
box-shadow:1px 1px 1px lightslategray;
}
.disabled:hover{
cursor:not-allowed;
background-color:lightgray;
color:lavender;
}
服務器端
//server.js
const DataCreator = require("./DataCreator.js");
const dc = new DataCreator();
dc.startTimer();
const express = require("express");
const server = express();
const path = require("path");
server.use(express.static(path.join(__dirname,"src")));
server.get("/getData",function(req,res){
res.writeHead(200,{"Content-Type":"application/json"});
const {times,values} = dc;
res.end(JSON.stringify({
times,
values
}));
});
server.listen(3000,function(){
console.log("listening on*:3000");
})
//DataCreator.js
function DataCreator(){
this.dotNum = 10;
this.interval = 1000;
this.lastStartTime = undefined;
this.lastValues = [];
this.times = [];
this.values = [];
}
DataCreator.prototype.createTimes = function(){
let {lastStartTime,dotNum,interval} = this;
let startTime = lastStartTime?lastStartTime:new Date().getTime();
this.lastStartTime = startTime + interval;
let times = [];
for(var i=0;i<dotNum;++i){
var time = new Date(startTime+i*interval);
times.push(time);
}
return times;
}
DataCreator.prototype.createValues = function(){
let {lastValues,dotNum} = this;
let values;
if(lastValues.length === 0){
values = [];
for(var i=0;i<dotNum;++i){
var value = parseFloat(Math.random().toFixed(2));
values.push(value);
}
}else {
values = lastValues.slice(1);
values.push(parseFloat(Math.random().toFixed(2)));
}
this.lastValues = values.slice();
return values;
}
DataCreator.prototype.initData = function(){
var startTime = new Date().getTime();
this.times = this.createTimes();
this.values = this.createValues();
}
DataCreator.prototype.startTimer = function(){
this.initData();
const self = this;
const {interval} = self;
const fn = function(){
self.times = self.createTimes();
self.values = self.createValues();
timer = setTimeout(fn,interval);
};
fn();
}
module.exports = DataCreator;