之前ECharts+js实现了动态曲线,这次稍稍做了一些改动。
代码结构
详细代码
浏览器端实现
chart组件
MyChart.js
import React from 'react';
import * as echarts from 'echarts/lib/echarts';
import 'echarts/lib/chart/line';
import 'echarts/lib/component/grid';
import 'echarts/lib/component/tooltip';
import "./MyChart.scss";
import MyWorker from "../worker/MyWorker.js";
class MyChart extends React.Component{
constructor(props){
super(props);
this.chart = Object.create(null);
}
componentDidMount(){
this.chart = echarts.init(document.getElementById("chart"));
this.chart.gap = 40;
this.init();
MyWorker.addEventListener("message",(event) => {
const {type,datas} = event.data;
if(type === "datas"){
this.update(datas);
}
});
}
componentDidUpdate(){
}
init(){
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);
}
update(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
}]
});
}
render(){
return <div id="chart" className="chart"></div>
}
}
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;
}
export default MyChart;
MyChart.scss
.chart{
display:inline-block;
width:400px;height:200px;
border:1px solid lightgray;
border-right:none;
vertical-align:middle;
}
button组件
MyButton.js
import React from 'react';
import './MyButton.scss';
import MyWorker from "../worker/MyWorker.js";
class MyButton extends React.Component{
constructor(props){
super(props);
this.startElm = Object.create(null);
this.endElm = Object.create(null);
this.startHandler = Object.create(null);
this.endHandler = Object.create(null);
}
componentDidMount(){
this.startElm = document.querySelector("#start");
this.endElm = document.querySelector("#end");
this.startHandler = () => {
this.disable(this.startElm);
startTimer();
};
this.endHandler = () => {
closeTimer();
}
this.addEventListener(this.startElm,"click",this.startHandler);
this.addEventListener(this.endElm,"click",this.endHandler);
MyWorker.addEventListener("message",(event) => {
const {type,datas} = event.data;
if(type === "closed"){
this.enable(this.startElm);
// MyWorker.terminate();
}
})
}
componentWillUnMount(){
this.removeEventListener(this.startElm,"click",this.startHandler);
this.removeEventListener(this.endElm,"click",this.endHandler);
}
addEventListener(elm,type,handler){
if(elm.addEventListener){
elm.addEventListener(type,handler,false);
}else {
elm["on"+type] = handler;
}
}
removeEventListener(elm,type,handler){
if(elm.removeEventListener){
elm.removeEventListener(type,handler,false);
}else{
elm["on"+type] = null;
}
}
disable(elm){
elm.classList.add("disabled");
}
enable(elm){
elm.classList.remove("disabled");
}
render(){
return <div className="container">
<div className="buttons">
<div className="button" id="start">开始</div>
<div className="button" id="end"> 结束</div>
</div>
</div>
}
}
function startTimer(){
MyWorker.postMessage({
type:"opening timer"
});
}
function closeTimer(timer){
MyWorker.postMessage({
type:"closing timer"
});
}
export default MyButton;
MyButton.scss
.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;
}
worker组件
MyWorker.js
export default new Worker("/worker/worker.js");
woker.js
var timer;
const worker = self;
worker.onmessage = function(event){
const {type} = event.data;
switch(type){
case "opening timer": openTimer();break;
case "closing timer":closeTimer();break;
}
}
function openTimer(){
timer = setInterval(getDataFromServer,250);
}
function closeTimer(){
clearInterval(timer);
worker.postMessage({
type:"closed"
});
// worker.close();
}
const createXHR = createXHRCreator();
function getDataFromServer(){
const xhr = createXHR();
xhr.open("GET","/getData");
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
worker.postMessage({
type:"datas",
datas:JSON.parse(xhr.response)
})
}
}
xhr.send();
}
function createXHRCreator(){
if(typeof XMLHttpRequest !== "undefined"){
return function createXHR(){
return new XMLHttpRequest();
}
}else if("ActiveXObject" in window){
return function createXHR(){
if(typeof arguments.callee.activeXString !== "string"){
var versions = [
"MSXML2.XMLHttp.6.0",
"MSXML2.XMLHttp.3.0",
"MSXML2.XMLHttp"];
for(var i=0;i<versions.length;i++){
try{
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
}catch(e){
throw new Error(e);
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
}
}else{
return function createXHR(){
throw new Error("No XHR object supported");
}
}
}
其他
index.js
import React from "react";
import ReactDOM from "react-dom";
import Dline from "./Dline.js";
ReactDOM.render(<Dline/>,document.getElementById("root"));
Dline.js
import React from "react";
import MyChart from "./chart/MyChart.js";
import MyButton from "./button/MyButton.js";
class Dline extends React.Component{
render(){
return <div>
<MyChart/>
<MyButton/>
</div>
}
}
export default Dline;
index.html
<body>
<div id="root">
</div>
<script src="../dist/bundle.js"></script>
</body>
webpack.config.js
const path = require("path");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
module.exports = {
mode:"development",
devtool:"cheap-source-map",
entry:"./src/index.js",
output:{
filename:"bundle.js",
path:path.resolve(__dirname,"dist")
},
module:{
rules:[
{
include:/src/,
exclude:/node_modules/,
test:/\.js$/,
use:{
loader:"babel-loader",
options:{
presets:["@babel/preset-react"]
}
}
},
{
test:/\.(css|scss)$/,
use:["style-loader","css-loader","sass-loader"]
}
]
},
plugins:[
new CleanWebpackPlugin()
],
// optimization:{
// runtimeChunk:true
// }
}
package.json
{
"name": "dynamic-smooth-line",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack --config webpack.config.js",
"watch": "webpack --watch --config webpack.config.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"echarts": "^4.7.0",
"epxress": "0.0.1-security",
"express": "^4.17.1",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"devDependencies": {
"@babel/core": "^7.9.6",
"@babel/preset-react": "^7.9.4",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^3.5.3",
"node-sass": "^4.14.1",
"sass-loader": "^8.0.2",
"style-loader": "^1.2.1",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"
}
}
服务器端实现
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("src"));
const idx = __dirname.lastIndexOf("\\");
const root = __dirname.slice(0,idx);
server.use(express.static(path.join(root,"src")));
server.use("/dist",express.static(path.join(root,"/dist")));
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;