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/echarts@4.7.0/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;