ECharts类图表数据实时更新
以设备在线数量统计为例,实现环形图数据实时更新。
·
在开发过程中,可能会遇到类似需求场景,即不断变化的数据在页面图表组件中如何保持热更新,本文以设备在线数量统计为例,实现环形图数据实时更新。
一、后端模拟设备心跳包数据
使用WebSocket技术向前端应用不断推送设备心跳包数据,表示设备在线运行中。以Java语言主流开发框架SpringBoot为例,具体实现过程如下:
1、SpringBoot集成WebSocket
// pom文件中引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
// WebSocket配置文件
@Configuration
public class WebSocketConfig {
// 注入一个ServerEndpointExporter,该Bean会自动注册使用@ServerEndpoint注解申明的websocket endpoint
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
// WebSocket工具类文件
@Component
// 用户名结合实际情况选择取舍
@ServerEndpoint("/websocket/{userName}")
public class WebSocket {
private Session session;
private static CopyOnWriteArraySet<WebSocket> webSockets =new CopyOnWriteArraySet<>();
private static Map<String,Session> sessionPool = new HashMap<String,Session>();
@OnOpen
public void onOpen(Session session, @PathParam(value="userName")String userName) {
this.session = session;
webSockets.add(this);
sessionPool.put(userName, session);
System.out.println("[websocket消息]有新的连接,总数为:"+webSockets.size()+",新连接用户名为:"+userName);
}
@OnClose
public void onClose() {
webSockets.remove(this);
System.out.println("[websocket消息]连接断开,总数为:"+webSockets.size());
}
@OnMessage
public void onMessage(String message) {
System.out.println("[websocket消息]收到客户端消息:"+message);
}
public static void sendAllMessage(String message) {
for(WebSocket webSocket : webSockets) {
System.out.println("[websocket消息]广播消息:"+message);
try {
webSocket.session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void sendOneMessage(String userName, String message) {
System.out.println("[websocket消息]单点消息:"+message);
Session session = sessionPool.get(userName);
if (session != null) {
try {
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
System.out.println("[websocket消息]单点消息:"+message+"未发送");
}
}
}
}
2、使用WebSocket推送设备心跳包数据
@RequestMapping("/server")
@RestController
public class ServerController {
private static WebSocket webSocket;
@GetMapping("/sendHeartBeat")
public void sendHeartBeat() throws JSONException {
try{
JSONObject heartBeat = new JSONObject();
// 简单模拟一台设备不停的向已连接客户端发送心跳包数据,可结合项目实际情况采用多线程并发执行
while (true) {
heartBeat.put("devId", nowTimeStr("100100100100100"));
heartBeat.put("date", nowTimeStr("yyyy-MM-dd HH:mm:ss"));
Thread.currentThread().sleep(5000);
webSocket.sendAllMessage(heartBeat.toString());
}
} catch (Exception e){
e.printStackTrace();
}
}
}
二、前端图表数据热更新
通常中小型Web图表展示应用使用Vue+ECharts框架进行搭建,本文为能在Web、H5、小程序和APP等多端应用中实现图表数据的实时更新,采用uni-app+uCharts跨平台框架进行开发。具体实现过程如下:
1、使用Vuex管理心跳数据
// /store/modules/webSocket.js
export default {
namespaced: true,
state: {
webSocketInfo: []
},
getters: {},
mutations: {
SET_WS_INFO: (state, webSocketInfo) => {
state.webSocketInfo = webSocketInfo
},
REMOVE_WS_INFO: (state) => {
state.webSocketInfo = []
}
},
actions: {}
}
// /store/index.js
import webSocket from './modules/webSocket.js'
// #ifndef VUE3
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
webSocket
}
})
// #endif
// #ifdef VUE3
import {
createStore
} from 'vuex'
const store = createStore({
modules: {
webSocket
}
})
// #endif
export default store
2、uni-app集成WebSocket,监听心跳数据
// /app.config.js
export default {
// 可结合项目情况自行修改端口和地址
webSocketUrl: 'ws://localhost:8888/websocket'
}
// /plugin/modules/webSocket.js
import appConfig from '@/app.config.js'
import store from '@/store'
const WebSocket = {
url: appConfig.webSocketUrl,
isOpen: false,
socketTask: false,
// 连接
open() {
if (this.isOpen) return
this.socketTask = uni.connectSocket({
// 用户名结合实际情况选择取舍和更改
url: this.url + '/admin',
complete: (e) => {}
})
if (!this.socketTask) return
this.socketTask.onOpen(() => {
this.isOpen = true
})
// 监听信息
this.message()
// 监听关闭
this.socketTask.onClose(() => {
this.isOpen = false
this.socketTask = false
uni.showToast({
icon: "none",
title: '服务器通信连接断开',
});
})
// 监听错误
this.socketTask.onError((e) => {
this.isOpen = false
this.socketTask = false
uni.showToast({
icon: "none",
title: '服务器通信连接错误',
});
})
},
// 关闭连接
close() {
if (this.isOpen) {
this.socketTask.close()
return
}
},
// 心跳数据处理逻辑,可结合项目情况自行修改
message() {
this.socketTask.onMessage((e) => {
// 字符串转json
let data = JSON.parse(e.data)
// 读取Vuex中管理的心跳数据
const webSocketInfo = store.state.webSocket.webSocketInfo
const index = webSocketInfo.findIndex((v) =>
v.devId == data.devId
)
if (index == -1) {
webSocketInfo.push({
'devId': data.devId,
'date': data.date
})
}else{
webSocketInfo[index].date = data.date
}
// 向Vuex中重新写入心跳数据
store.commit('webSocket/SET_WS_INFO', webSocketInfo)
})
}
}
// #ifndef VUE3
export function initWebSocket(Vue) {
Vue.prototype.webSocket = WebSocket
}
// #endif
// #ifdef VUE3
export function initWebSocket(app) {
app.config.globalProperties.webSocket = WebSocket
}
// #endif
3、将Vuex和WebSocket挂载全局,开启WebSocket服务
// /main.js
import App from './App'
import config from "./app.config.js"
import store from './store'
import plugin from './plugin/index'
// #ifndef VUE3
import Vue from 'vue'
Vue.config.productionTip = false
// 挂载全局方法
Vue.prototype.config = config
Vue.use(plugin)
App.mpType = 'app'
const app = new Vue({
config,
store,
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
app.use(config)
app.use(store)
app.use(plugin)
return {
app
}
}
// #endif
// /App.vue
<script>
export default {
onLaunch: function() {
// 开启WebSocket服务
this.webSocket.open()
console.log('App Launch')
}
}
</script>
4、uni-app引入uCharts
参考官方使用文档https://ext.dcloud.net.cn/plugin?id=271,无需配置,直接导入即可。
5、页面渲染心跳数据并实时更新
// /pages/test/test.vue
<template>
<view>
<qiun-data-charts type="ring" :opts="chartOpts" :chartData="chartData" background="none" />
</view>
</template>
<script>
import {
mapState
} from 'vuex'
export default {
data() {
return {
// 直接初始化设备数量,可结合项目情况从后端请求数据
total: 5,
onLine: 0,
offLine: 0,
chartData: {
"series": [{
"data": [{
"name": "在线",
"value": 0
},
{
"name": "离线",
"value": 0
}
]
}]
},
chartOpts: {
"color": [
"#23eb6a",
"#ff0000"
],
"legend": {
"position": "bottom",
"fontSize": 20
},
"title": {
"name": "在线率",
"fontSize": 22,
"color": "#00aaff"
},
"subtitle": {
"name": "0.00%",
"fontSize": 24,
"color": "#00aaff"
}
}
}
},
onLoad() {
this.__init()
},
computed: {
...mapState('webSocket', ['webSocketInfo'])
},
// 监听心跳数据变化
watch: {
webSocketInfo: {
handler(newVal, oldVal) {
this.onLine = newVal.length
this.offLine = this.total - this.onLine
this.updateDevice()
}
}
},
methods: {
__init() {
// 首先默认设备全部处于离线状态
this.offLine = this.total
this.onLine = this.webSocketInfo.length
this.updateDevice()
},
//更新设备状态
updateDevice() {
this.chartData.series[0].data[0].value = this.onLine
this.chartData.series[0].data[1].value = this.offLine
this.chartOpts.subtitle.name = (this.onLine / this.total).toFixed(4) * 100 + '%'
}
}
}
</script>
三、效果展示
四、总结
此方式仅适用于小流量业务场景,对于高并发或海量数据集热更新等情况需后端统计计算直接推送结果或另辟蹊径。因作者技术能力有限,文中不足之处还望各位大佬直抒己见、批评指正,反馈邮箱:yourshare@foxmail.com。
更多推荐
已为社区贡献1条内容
所有评论(0)