ECharts+React+webpack實現動態曲線

之前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;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章