数据可视化插件echarts【前端】
- 前言
- 版权
- 开源
- 推荐
- 数据可视化插件echarts
- 一、如何使用
- 1.1 下载
- 1.2 找到js文件
- 1.3 入门使用
- 1.4 我的使用
- 二、前后端交互:入门demo
- 2.1 前端
- html
- js
- 2.2 后端
- entity
- controller
- service
- mapper
- 三、前后端交互:动态数据
- 3.1 前端
- js
- 3.2 后端
- service
- 四、前后端交互:动态数据
- 4.1 前端
- js
- 4.2 后端
- ChineseName注解
- EldData
- DataService
- 五、测试扩展性
- 5.0 开发说明
- 5.1 测试结果
- 5.2 Eld多加一个属性
- 5.3 加入测试数据
- 六、注解优化
- 6.0 开发说明
- 6.1 测试结果
- 6.2 前端
- 6.2 后端
- ChineseName
- EldData
- DataService
- 七、实际项目开发
- EldData
- Constant
- 测试数据
- 最后
前言
2024-4-12 16:08:09
开源
日星月云 / echarts数据可视化
v1:二、入门demo
v2:三、动态数据
v3:四、动态数据
v4:六、注解优化
v5:七、项目开发
推荐
echarts入门教程(超级详细带案例)
数据可视化插件echarts
一、如何使用
1.1 下载
(1)从 npm 获取
npm install echarts --save
(2)从 CDN 获取
(3)从 GitHub 获取
1.2 找到js文件
在node_modules\echarts\dist
中找到echart.js
或echarts.min,js
1.3 入门使用
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<!-- 01 导入js -->
<script src="js/echarts.js"></script>
<!-- 03 设置容器的样式 -->
<style>
#container{
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<!-- 02 创建个容器 -->
<div id="container"></div>
</body>
<script>
//04 实例化echarts
// 4.1 创建一个实例
var echart = echarts.init(document.getElementById("container"))
// 4.2 定义配置项
var option = {
// 图表的标题
title:{
text:"我的第一个图表"
},
// 图表的提示
tooltip:{},
// 图例
legend:{data:["睡眠时长"]},
// x轴线
xAxis:{data:["周一","周二","周三","周四","周五","周六","周日"]},
// y轴线
yAxis:{},
// 设置数据
series:[
{
// 数据名称
name:"睡眠时长",
// 类型为柱状图
type:"bar",
// 数据data
data:[8,10,4,5,9,4,8]
}
]
}
// 4.3 更新配置
echart.setOption(option);
// chart图表,set设置 Option选项 data数据 type类型 bar条(柱状条),series系列(数据) Axis轴线 xAxis水平轴线
// legend传奇(图例) tooltip 提示 init初始化 document文档
</script>
</html>
1.4 我的使用
下面的代码根据此代码修改
https://www.isqqw.com/?t=line
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<!-- 01 导入js -->
<script src="js/echarts.js"></script>
<!-- 03 设置容器的样式 -->
<style>
#container{
width: 800px;
height: 600px;
}
</style>
</head>
<body>
<!-- 02 创建个容器 -->
<div id="container"></div>
</body>
<script>
//实例化echarts
// 1 创建一个实例
var echart = echarts.init(document.getElementById("container"));
let data1 = [175, 160, 153, 121, 156]
let data2 = [200, 140, 205, 162, 175]
let data3 = []
let data4 = ['13:00', '14:00', '15:00', '16:00', '17:00']
data1.forEach((item1,index)=>{
if(item1>data2[index]){
data3.push(
{
yAxis: item1, //标注的Y轴位置
xAxis: data4[index], //标注的X轴位置
value: item1 //标注的value值
}
)
}else{
data3.push(
{
yAxis: data2[index], //标注的Y轴位置
xAxis: data4[index], //标注的X轴位置
value: data2[index] //标注的value值
}
)
}
})
// 2 定义配置项
var option = {
backgroundColor: 'white',
grid: {
top: '20%',
left: '5%',
right: '5%',
bottom: '8%',
containLabel: true
},
tooltip: {
trigger: 'axis',
borderWidth: 1,
axisPointer: {
type: 'shadow'
},
extraCssText: 'z-index:2'
},
legend: [{
top: 'top',
left: 'center',
orient: 'horizontal',
data: ['进水量', '出水量'],
itemWidth: 15,
itemHeight: 10,
itemGap: 15,
borderRadius: 4,
textStyle: {
color: '#000',
fontFamily: 'Alibaba PuHuiTi',
fontSize: 14,
fontWeight: 400
}
}],
xAxis: {
type: 'category',
data: data4,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
show: true,
textStyle: {
color: '#393939' //X轴文字颜色
}
}
},
yAxis: [
{
type: 'value',
name: '',
nameTextStyle: {
color: '#000',
fontFamily: 'Alibaba PuHuiTi',
fontSize: 14,
fontWeight: 600
// padding: [0, 0, 0, 40], // 四个数字分别为上右下左与原位置距离
},
nameGap: 30, // 表现为上下位置
axisLine: {
show: true,
lineStyle: {
color: '#eeeeee'
}
},
axisTick: {
show: false
},
axisLabel: {
color: '#393939',
fontSize: 14
},
splitLine: {
show: true,
lineStyle: {
color: '#eeeeee'
}
}
}
],
series: [
{
name: '进水量',
type: 'line',
showAllSymbol: true, //显示所有图形。
//标记的图形为实心圆
symbolSize: 8, //标记的大小
itemStyle: {
//折线拐点标志的样式
color: 'white',
borderWidth: '2',
borderColor: '#5470c6',
normal: {
color: '#5470c6'//拐点颜色
}
},
lineStyle: {
color: '#5470c6'
},
markPoint:{
data: data3
},
data: data1
},
{
name: '出水量',
type: 'line',
showAllSymbol: true, //显示所有图形。
symbolSize: 8, //标记的大小
itemStyle: {
//折线拐点标志的样式
color: 'white',
borderWidth: '2',
borderColor: '#91cc75',
normal: {
color: '#91cc75'//拐点颜色
}
},
lineStyle: {
color: '#91cc75'
},
data: data2
}
]
}
// 3 更新配置
echart.setOption(option);
</script>
</html>
二、前后端交互:入门demo
2.1 前端
html
<div class="data-container">
</div>
js
$(document).ready(function () {
list();
});
function list() {
$.ajax({
type: "GET",
url: SERVER_PATH + "/data/list",
xhrFields: {withCredentials: true},
success: function (result) {
if (result.status) {
alertBox(result.data.message);
return false;
}
init(result.data);
}
});
}
function init(dataLists) {
//实例化echarts
// 1 创建一个实例
var echart = echarts.init(document.querySelector(".data-container"));
//进水量
let data1 = dataLists.inWaterList;
//出水量
let data2 = dataLists.outWaterList;
//标注
let data3 = []
//横轴时间
let data4 = dataLists.dateList;
data1.forEach((item1, index) => {
if (item1 > data2[index]) {
data3.push(
{
yAxis: item1, //标注的Y轴位置
xAxis: data4[index], //标注的X轴位置
value: item1 //标注的value值
}
)
} else {
data3.push(
{
yAxis: data2[index], //标注的Y轴位置
xAxis: data4[index], //标注的X轴位置
value: data2[index] //标注的value值
}
)
}
})
// 2 定义配置项
var option = {
backgroundColor: 'white',
grid: {
top: '20%',
left: '5%',
right: '5%',
bottom: '8%',
containLabel: true
},
tooltip: {
trigger: 'axis',
borderWidth: 1,
axisPointer: {
type: 'shadow'
},
extraCssText: 'z-index:2'
},
legend: [{
top: 'top',
left: 'center',
orient: 'horizontal',
data: ['进水量', '出水量'],
itemWidth: 15,
itemHeight: 10,
itemGap: 15,
borderRadius: 4,
textStyle: {
color: '#000',
fontFamily: 'Alibaba PuHuiTi',
fontSize: 14,
fontWeight: 400
}
}],
xAxis: {
type: 'category',
data: data4,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
show: true,
textStyle: {
color: '#393939' //X轴文字颜色
}
}
},
yAxis: [
{
type: 'value',
name: '',
nameTextStyle: {
color: '#000',
fontFamily: 'Alibaba PuHuiTi',
fontSize: 14,
fontWeight: 600
// padding: [0, 0, 0, 40], // 四个数字分别为上右下左与原位置距离
},
nameGap: 30, // 表现为上下位置
axisLine: {
show: true,
lineStyle: {
color: '#eeeeee'
}
},
axisTick: {
show: false
},
axisLabel: {
color: '#393939',
fontSize: 14
},
splitLine: {
show: true,
lineStyle: {
color: '#eeeeee'
}
}
}
],
series: [
{
name: '进水量',
type: 'line',
showAllSymbol: true, //显示所有图形。
//标记的图形为实心圆
symbolSize: 8, //标记的大小
itemStyle: {
//折线拐点标志的样式
color: 'white',
borderWidth: '2',
borderColor: '#5470c6',
normal: {
color: '#5470c6'//拐点颜色
}
},
lineStyle: {
color: '#5470c6'
},
markPoint: {
data: data3
},
data: data1
},
{
name: '出水量',
type: 'line',
showAllSymbol: true, //显示所有图形。
symbolSize: 8, //标记的大小
itemStyle: {
//折线拐点标志的样式
color: 'white',
borderWidth: '2',
borderColor: '#91cc75',
normal: {
color: '#91cc75'//拐点颜色
}
},
lineStyle: {
color: '#91cc75'
},
data: data2
}
]
}
// 3 更新配置
echart.setOption(option);
}
2.2 后端
entity
@lombok.Data
@NoArgsConstructor
@AllArgsConstructor
public class Data {
Integer InWater;
Integer OutWater;
Date date;
}
controller
@GetMapping("/list")
public ResponseModel getDataList(){
HashMap<String, Object> search = dataService.searchMap();
return new ResponseModel(search);
}
service
public HashMap<String,Object> searchMap(){
HashMap<String,Object> map=new HashMap<>();
List<Data> dataList = search();
List<Integer> inWaterList = new ArrayList<>();
List<Integer> outWaterList = new ArrayList<>();
List<Date> dateList = new ArrayList<>();
for(Data data : dataList){
inWaterList.add(data.getInWater());
outWaterList.add(data.getOutWater());
dateList.add(data.getDate());
}
map.put("inWaterList",inWaterList);
map.put("outWaterList",outWaterList);
map.put("dateList",dateList);
return map;
}
public List<Data> search() {
return dataDao.list();
}
mapper
@Select("SELECT * FROM test limit 20")
List<Data> list();
三、前后端交互:动态数据
3.1 前端
js
$(document).ready(function () {
list();
});
function list() {
$.ajax({
type: "GET",
url: SERVER_PATH + "/data/list",
data: {
userId: 1
},
xhrFields: {withCredentials: true},
success: function (result) {
if (result.status) {
alertBox(result.data.message);
return false;
}
init(result.data);
}
});
}
function init(dataLists) {
//实例化echarts
// 1 创建一个实例
var echart = echarts.init(document.querySelector(".data-container"));
//进水量
let data1 = dataLists.rate1;
//出水量
let data2 = dataLists.rate2;
//横轴时间
let data4 = dataLists.date;
//标注数据
let data3 = [];
//只需修改以下对应
let names=["rate1","rate2"];
let datas=[data1,data2];
let colors=[
'#5470c6','#91cc75'
]
//动态生成下面的数据
for(let i=0;i<names.length;i++){
data3.push([]);
}
datas.forEach((data, index) => {
data.forEach((item, i) => {
data3[index].push({
yAxis: item, // 标注的Y轴位置
xAxis: data4[i], // 标注的X轴位置
value: item // 标注的value值
});
});
});
let seriesData=[];
for (var i = 0; i < datas.length; i++) {
seriesData.push({
name: names[i],
type: 'line',
showAllSymbol: true, //显示所有图形。
//标记的图形为实心圆
symbolSize: 8, //标记的大小
itemStyle: {
//折线拐点标志的样式
color: 'white',
borderWidth: '2',
borderColor: colors[i],
normal: {
color: colors[i]//拐点颜色
}
},
lineStyle:{
color: colors[i]
},
markPoint: {
data: data3[i]
},
data: datas[i]
});
}
// 2 定义配置项
var option = {
backgroundColor: 'white',
grid: {
top: '20%',
left: '5%',
right: '5%',
bottom: '8%',
containLabel: true
},
tooltip: {
trigger: 'axis',
borderWidth: 1,
axisPointer: {
type: 'shadow'
},
extraCssText: 'z-index:2',
// formatter: function(params) {
// var tooltipContent = params[0].name + '<br/>'; // 显示日期
// params.forEach(function(param) {
// tooltipContent += param.seriesName + ': ' + param.value + '<br>';
// });
// return tooltipContent;
// }
},
legend: [{
top: 'top',
left: 'center',
orient: 'horizontal',
data: names,
itemWidth: 15,
itemHeight: 10,
itemGap: 15,
borderRadius: 4,
textStyle: {
color: '#000',
fontFamily: 'Alibaba PuHuiTi',
fontSize: 14,
fontWeight: 400
}
}],
xAxis: {
type: 'category',
data: data4,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
show: true,
textStyle: {
color: '#393939' //X轴文字颜色
}
}
},
yAxis: [
{
type: 'value',
name: '',
nameTextStyle: {
color: '#000',
fontFamily: 'Alibaba PuHuiTi',
fontSize: 14,
fontWeight: 600
// padding: [0, 0, 0, 40], // 四个数字分别为上右下左与原位置距离
},
nameGap: 30, // 表现为上下位置
axisLine: {
show: true,
lineStyle: {
color: '#eeeeee'
}
},
axisTick: {
show: false
},
axisLabel: {
color: '#393939',
fontSize: 14
},
splitLine: {
show: true,
lineStyle: {
color: '#eeeeee'
}
}
}
],
series: seriesData
}
// 3 更新配置
echart.setOption(option);
}
3.2 后端
service
//返回数据链表
public HashMap<String,ArrayList> searchMap(Integer userId){
HashMap<String,ArrayList> map=new HashMap<>();
List<EldData> dataList = search(userId);
List<String> fieldNames = new ArrayList<>();
Class<?> dataClass = EldData.class;
// 获取 OldData 类的所有属性名
Field[] fields = dataClass.getDeclaredFields();
for (Field field : fields) {
fieldNames.add(field.getName());
map.put(field.getName(),new ArrayList<>());
}
for (EldData data : dataList) {
for (String fieldName : fieldNames) {
ArrayList<Object> rowData =map.get(fieldName);
try {
Field field = dataClass.getDeclaredField(fieldName);
field.setAccessible(true);
rowData.add(field.get(data));
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
map.put(fieldName,rowData);
}
}
return map;
}
//搜索数据
public List<EldData> search(Integer userId) {
String key= ELD_DATA +userId;
Set set = redisTemplate.opsForZSet().reverseRange(key, 0, 19);// 获取分数最高的20个数据
return set !=null?new ArrayList<>(set):new ArrayList<>();
}
四、前后端交互:动态数据
后端name根据注解ChineseName
前端颜色是随机生成的
hashMap导致没有顺序,换成LinkedHashMap
在seachMap()中,重新定义
// HashMap<String,ArrayList> map=new HashMap<>();
HashMap<String,ArrayList> map=new LinkedHashMap<>();
发现它是时间顺序是反这的
修改一下
//搜索数据
// public List<EldData> search(Integer userId) {
// String key= ELD_DATA +userId;
// Set set = redisTemplate.opsForZSet().reverseRange(key, 0, limit-1);// 获取分数最高的20个数据
//
// return set !=null?new ArrayList<>(set):new ArrayList<>();
//
// }
//搜索数据
public List<EldData> search(Integer userId) {
String key= ELD_DATA +userId;
Set set = redisTemplate.opsForZSet().reverseRange(key, 0, limit-1);// 获取分数最高的limit个数据
if(set==null){
return new ArrayList<>();
}
ArrayList<EldData> resList = new ArrayList<>(set);
Collections.reverse(resList);
return resList;
}
4.1 前端
js
变成真实登录的用户
而不是固定userId是1
$(document).ready(function () {
list();
});
function list() {
var id=sessionStorage.getItem("id");
$.ajax({
type: "GET",
url: SERVER_PATH + "/data/list",
data: {
// userId: 1
userId: id
},
xhrFields: {withCredentials: true},
success: function (result) {
if (result.status) {
alertBox(result.data.message);
return false;
}
init(result.data);
}
});
}
function init(dataLists) {
//实例化echarts
// 1 创建一个实例
var echart = echarts.init(document.querySelector(".data-container"));
//横轴时间
let datax = dataLists.date;
//标注数据
let data0 = [];
//只需修改不需要展示的name
var noNeed=["id","date"];
//原始数据的所有name
let keys=Object.keys(dataLists);
//只需添加足够的颜色
// let colors=[
// '#5470c6','#91cc75'
// ]
//动态生成下面的数据,不需要修改
//随机生成相同数量的颜色
let colors=generateRandomColors(keys.length);
//名称和对应的数据
let names = []
let datas = [];
keys.forEach((name) => {
if(!noNeed.includes(name)){
names.push(name);
datas.push(dataLists[name]);
}
});
for(let i=0;i<names.length;i++){
data0.push([]);
}
datas.forEach((data, index) => {
data.forEach((item, i) => {
data0[index].push({
yAxis: item, // 标注的Y轴位置
xAxis: datax[i], // 标注的X轴位置
value: item // 标注的value值
});
});
});
let seriesData=[];
for (var i = 0; i < datas.length; i++) {
seriesData.push({
name: names[i],
type: 'line',
showAllSymbol: true, //显示所有图形。
//标记的图形为实心圆
symbolSize: 8, //标记的大小
itemStyle: {
//折线拐点标志的样式
color: 'white',
borderWidth: '2',
borderColor: colors[i],
normal: {
color: colors[i]//拐点颜色
}
},
lineStyle:{
color: colors[i]
},
markPoint: {
data: data0[i]
},
data: datas[i]
});
}
// 2 定义配置项
var option = {
backgroundColor: 'white',
grid: {
top: '20%',
left: '5%',
right: '5%',
bottom: '8%',
containLabel: true
},
tooltip: {
trigger: 'axis',
borderWidth: 1,
axisPointer: {
type: 'shadow'
},
extraCssText: 'z-index:2',
// formatter: function(params) {
// var tooltipContent = params[0].name + '<br/>'; // 显示日期
// params.forEach(function(param) {
// tooltipContent += param.seriesName + ': ' + param.value + '<br>';
// });
// return tooltipContent;
// }
},
legend: [{
top: 'top',
left: 'center',
orient: 'horizontal',
data: names,
itemWidth: 15,
itemHeight: 10,
itemGap: 15,
borderRadius: 4,
textStyle: {
color: '#000',
fontFamily: 'Alibaba PuHuiTi',
fontSize: 14,
fontWeight: 400
}
}],
xAxis: {
type: 'category',
data: datax,
axisLine: {
show: false
},
axisTick: {
show: false
},
axisLabel: {
show: true,
textStyle: {
color: '#393939' //X轴文字颜色
}
}
},
yAxis: [
{
type: 'value',
name: '',
nameTextStyle: {
color: '#000',
fontFamily: 'Alibaba PuHuiTi',
fontSize: 14,
fontWeight: 600
// padding: [0, 0, 0, 40], // 四个数字分别为上右下左与原位置距离
},
nameGap: 30, // 表现为上下位置
axisLine: {
show: true,
lineStyle: {
color: '#eeeeee'
}
},
axisTick: {
show: false
},
axisLabel: {
color: '#393939',
fontSize: 14
},
splitLine: {
show: true,
lineStyle: {
color: '#eeeeee'
}
}
}
],
series: seriesData
}
// 3 更新配置
echart.setOption(option);
}
function generateRandomColors(num) {
let randomColors = [];
let characters = '0123456789ABCDEF';
for (let i = 0; i < num; i++) {
let color = '#';
for (let j = 0; j < 6; j++) {
color += characters[Math.floor(Math.random() * 16)];
}
randomColors.push(color);
}
return randomColors;
}
4.2 后端
ChineseName注解
package com.jsss.echarts.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ChineseName {
String value();
}
EldData
package com.jsss.echarts.entity;
import com.jsss.echarts.annotation.ChineseName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.sql.Date;
import java.util.Objects;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EldData {
@ChineseName("id")
String id;
@ChineseName("率1")
Integer rate1;
@ChineseName("率2")
Integer rate2;
@ChineseName("date")
Date date;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EldData eldData = (EldData) o;
return Objects.equals(id, eldData.id) && Objects.equals(date, eldData.date);
}
@Override
public int hashCode() {
return Objects.hash(id, date);
}
@Override
public String toString() {
return "EldData{" +
"rate1=" + rate1 +
", rate2=" + rate2 +
", date=" + date +
'}';
}
}
DataService
package com.jsss.echarts.service;
import com.jsss.echarts.annotation.ChineseName;
import com.jsss.echarts.entity.EldData;
import com.jsss.utils.Constant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;
import java.lang.reflect.Field;
import java.util.*;
@Service
public class DataService implements Constant {
@Autowired
RedisTemplate redisTemplate;
//返回数据链表
public HashMap<String,ArrayList> searchMap(Integer userId){
// HashMap<String,ArrayList> map=new HashMap<>();
HashMap<String,ArrayList> map=new LinkedHashMap<>();
List<EldData> dataList = search(userId);
List<String> fieldNames = new ArrayList<>();
Class<?> dataClass = EldData.class;
// 获取 OldData 类的所有属性名
Field[] fields = dataClass.getDeclaredFields();
for (Field field : fields) {
fieldNames.add(field.getName());
// map.put(field.getName(),new ArrayList<>());
map.put(field.getAnnotation(ChineseName.class).value(), new ArrayList<>());
}
for (EldData data : dataList) {
for (String fieldName : fieldNames) {
// ArrayList<Object> rowData = map.get(fieldName);
try {
Field field = dataClass.getDeclaredField(fieldName);
ArrayList<Object> rowData =map.get(field.getAnnotation(ChineseName.class).value());
field.setAccessible(true);
rowData.add(field.get(data));
map.put(field.getAnnotation(ChineseName.class).value(),rowData);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
// map.put(fieldName, rowData);
}
}
return map;
}
//搜索数据
// public List<EldData> search(Integer userId) {
// String key= ELD_DATA +userId;
// Set set = redisTemplate.opsForZSet().reverseRange(key, 0, 19);// 获取分数最高的20个数据
//
// return set !=null?new ArrayList<>(set):new ArrayList<>();
//
// }
//搜索数据
public List<EldData> search(Integer userId) {
String key= ELD_DATA +userId;
Set set = redisTemplate.opsForZSet().reverseRange(key, 0, limit-1);// 获取分数最高的limit个数据
if(set==null){
return new ArrayList<>();
}
ArrayList<EldData> resList = new ArrayList<>(set);
Collections.reverse(resList);
return resList;
}
// 将数据存储到有序集合中,分数为日期的时间戳
public boolean addData(Integer userId, EldData data) {
String key= ELD_DATA +userId;
return redisTemplate.opsForZSet().add(key, data,data.getDate().getTime());
}
// 更新数据,先删除数据,后增加新数据
public boolean updateData(Integer userId,EldData oldData,EldData newData) {
long res=removeData(userId,oldData);
if (res==0){
//没有旧数据,就修改失败
return false;
}
return addData(userId, newData);
}
// 删除指定的数据
public long removeData(Integer userId, EldData data) {
String key= ELD_DATA +userId;
return redisTemplate.opsForZSet().remove(key, data);
}
}
五、测试扩展性
5.0 开发说明
后端:
只需要更改EldData的属性就好了
并且添加对应注解
如果有一个属性没有注解会报错,由于searchMap()中默认是所有属性都有此注解。
如果有属性不需要前端展示,可以在前端noNeed中添加
也可以后端修改注解,增加need属性,增加代码逻辑
另外:注解也可增加:x轴属性
@ChineseName("id")
String id;
@ChineseName("率1")
Integer rate1;
@ChineseName("率2")
Integer rate2;
@ChineseName("率3")
Integer rate3;
@ChineseName("date")
Date date;
前端:
修改横轴:datax
怎么标注数据:data0
以及不需要展示的name
链表:noNeed
//横轴时间
let datax = dataLists.date;
//标注数据
let data0 = [];
//只需修改不需要展示的name
var noNeed=["id","date"];
5.1 测试结果
5.2 Eld多加一个属性
package com.jsss.echarts.entity;
import com.jsss.echarts.annotation.ChineseName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.sql.Date;
import java.util.Objects;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EldData {
@ChineseName("id")
String id;
@ChineseName("率1")
Integer rate1;
@ChineseName("率2")
Integer rate2;
@ChineseName("率3")
Integer rate3;
@ChineseName("date")
Date date;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EldData eldData = (EldData) o;
return Objects.equals(id, eldData.id) && Objects.equals(date, eldData.date);
}
@Override
public int hashCode() {
return Objects.hash(id, date);
}
}
5.3 加入测试数据
@Autowired
RedisTemplate redisTemplate;
@Autowired
DataService dataService;
Integer userId=1;
//清空数据
@Test
public void restore() {
String key= ELD_DATA +userId;
redisTemplate.delete(key);
testSearchMap();
}
@Test
public void testSearchMap(){
HashMap<String, ArrayList> map = dataService.searchMap(userId);
System.out.println(map);
}
@Test
public void testValidAdd(){
EldData eldData=new EldData(Toolbox.getRandomString(),175,160,111,new Date(2024-1900,4-1,8));
dataService.addData(userId,eldData);
eldData=new EldData(Toolbox.getRandomString(),160,140,121,new Date(2024-1900,4-1,9));
dataService.addData(userId,eldData);
eldData=new EldData(Toolbox.getRandomString(),153,205,131,new Date(2024-1900,4-1,10));
dataService.addData(userId,eldData);
eldData=new EldData(Toolbox.getRandomString(),121,162,141,new Date(2024-1900,4-1,11));
dataService.addData(userId,eldData);
eldData=new EldData(Toolbox.getRandomString(),156,175,151,new Date(2024-1900,4-1,12));
dataService.addData(userId,eldData);
testSearchMap();
}
运行前端,登录admin:admin用户
在加入测试数据
userId=2
登录jsss:123456,也是可以的
2024-4-13 16:26:41
六、注解优化
6.0 开发说明
后端:
在EldData中
如果某个属性需要前端展示,就添加ChineseName
注解
如果某个属性是横轴数据,其value
值就是datax
前端:
不需要修改任何代码
public class EldData {
@ChineseName("率1")
Integer rate1;
@ChineseName("率2")
Integer rate2;
//没有注解,就不展示
Integer rate3;
//横轴的注解的值是datax
@ChineseName("datax")
Date date;
}
6.1 测试结果
rate3没有注解
rate3添加注解
6.2 前端
对应的改一下这两个就行了
//横轴数据
let datax = dataLists.datax;
//不需要展示的name
var noNeed=["datax"];
6.2 后端
ChineseName
/**
* 如果某个属性需要展示,就添加`ChineseName`注解 <br>
* 如果某个属性是横轴数据,其`value`值是`datax` <br>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ChineseName {
//中文name
String value();
}
EldData
redis判断是根据序列化的结果判断是否相同,
equals和hashCode不起作用
package com.jsss.echarts.entity;
import com.jsss.echarts.annotation.ChineseName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.sql.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class EldData {
@ChineseName("率1")
Integer rate1;
@ChineseName("率2")
Integer rate2;
//没有注解,就不展示
Integer rate3;
//横轴的注解的值是datax
@ChineseName("datax")
Date date;
}
DataService
//返回数据链表
public HashMap<String,ArrayList> searchMap(Integer userId){
HashMap<String,ArrayList> map=new LinkedHashMap<>();
List<EldData> dataList = search(userId);
List<String> fieldNames = new ArrayList<>();
Class<?> dataClass = EldData.class;
// 获取 OldData 类的所有被ChineseName注解的属性名
Field[] fields = dataClass.getDeclaredFields();
for (Field field : fields) {
//添加是否有注解的判断
ChineseName chineseNameAnnotation = field.getAnnotation(ChineseName.class);
if (chineseNameAnnotation!=null){
fieldNames.add(field.getName());
map.put(field.getAnnotation(ChineseName.class).value(), new ArrayList<>());
}
}
for (EldData data : dataList) {
for (String fieldName : fieldNames) {
try {
Field field = dataClass.getDeclaredField(fieldName);
field.setAccessible(true);
ArrayList<Object> rowData =map.get(field.getAnnotation(ChineseName.class).value());
rowData.add(field.get(data));
map.put(field.getAnnotation(ChineseName.class).value(),rowData);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
return map;
}
//搜索数据
public List<EldData> search(Integer userId) {
String key= ELD_DATA +userId;
Set set = redisTemplate.opsForZSet().reverseRange(key, 0, limit-1);// 获取分数最高的limit个数据
if(set==null){
return new ArrayList<>();
}
ArrayList<EldData> resList = new ArrayList<>(set);
Collections.reverse(resList);
return resList;
}
七、实际项目开发
加入分栏就更好了
EldData
package com.jsss.echarts.entity;
import com.jsss.echarts.annotation.ChineseName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.sql.Date;
/**
* 字段名称 类型 字段说明 备注
* <p>
* HT Integer 通用-身高(HT) 单位:m
* WT Integer 通用-体重(WT) 单位:kg
* BMI Double 通用-身高体重指数(BMI) 正常范围 18.5~23.9,超重24.0~27.9,肥胖≥28.0
* SBP Integer 血压-收缩压(SBP) 正常成年人的收缩压范围为90-140毫米汞柱(mmHg)。
* DBP Integer 血压-舒张压(DBP) 正常成年人的舒张压范围为60-90毫米汞柱(mmHg)。
* Hb Integer 血常规-血红蛋白(Hb) 正常成年人的正常范围为120-175克/升。
* WBC Integer 血常规-白细胞计数(WBC) 正常成年人的白细胞计数范围为4-10 × 10^9/L。
* PLT Integer 血常规-血小板计数(PLT) 正常成年人的血小板计数范围为100-300 × 10^9/L。
* ALT Integer 肝功能-谷丙转氨酶(ALT) 正常成年人的ALT范围为10-40单位/升。
* AST Integer 肝功能-谷草转氨酶(AST) 正常成年人的AST范围为10-35单位/升。
* TBIL Double 肝功能-总胆红素(TBIL) 正常成年人的总胆红素范围为3.4-17.1毫摩尔/升。
* BUN Double 肾功能-血尿素氮(BUN) 正常成年人的BUN范围为2.5-7.1毫摩尔/升。
* Cr Integer 肾功能-血肌酐(Cr) 正常成年人的血肌酐范围为53-115微摩尔/升。
* TC Double 血脂-总胆固醇(TC) 正常成年人的总胆固醇范围为3.1-5.2毫摩尔/升。
* TG Double 血脂-甘油三酯(TG) 正常成年人的甘油三酯范围为0.4-1.7毫摩尔/升。
* LDL_C Double 血脂-低密度脂蛋白胆固醇(LDL-C) 正常成年人的LDL-C范围为2.6-3.4毫摩尔/升。
* FPG Double 血糖-空腹血糖(FPG) 正常成年人的空腹血糖范围为3.9-6.1毫摩尔/升。
* twohPG Double 血糖-餐后2小时血糖(2hPG) 正常成年人的餐后2小时血糖范围为3.9-7.8毫摩尔/升。
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class EldData {
@ChineseName("通用-身高(米)")
Integer HT;
@ChineseName("通用-体重(千克)")
Integer WT;
@ChineseName("通用-身高体重指数(千克/米2)")
Double BMI;
@ChineseName("血压-收缩压(毫米汞柱)")
Integer SBP;
@ChineseName("血压-舒张压(毫米汞柱)")
Integer DBP;
@ChineseName("血常规-血红蛋白(克/升)")
Integer Hb;
@ChineseName("血常规-白细胞计数(10^9/升)")
Integer WBC;
@ChineseName("血常规-血小板计数(10^9/升)")
Integer PLT;
@ChineseName("肝功能-谷丙转氨酶(单位/升)")
Integer ALT;
@ChineseName("肝功能-谷草转氨酶(单位/升)")
Integer AST;
@ChineseName("肝功能-总胆红素(毫摩尔/升)")
Double TBIL;
@ChineseName("肾功能-血尿素氮(毫摩尔/升)")
Double BUN;
@ChineseName("肾功能-血肌酐(微摩尔/升)")
Integer Cr;
@ChineseName("血脂-总胆固醇(毫摩尔/升)")
Double TC;
@ChineseName("血脂-甘油三酯(毫摩尔/升)")
Double TG;
@ChineseName("血脂-低密度脂蛋白胆固醇(毫摩尔/升)")
Double LDL_C;
@ChineseName("血糖-空腹血糖(毫摩尔/升)")
Double FPG;
@ChineseName("血糖-餐后2小时血糖(毫摩尔/升)")
Double twohPG;
//横轴的注解的值是datax
@ChineseName("datax")
Date date;
}
Constant
package com.jsss.utils;
import com.jsss.echarts.entity.EldData;
import java.sql.Date;
public interface Constant {
/**
* redis键:老人的体检数据
*/
String ELD_DATA = "eld_data:";
EldData MIN_DATA=new EldData(
Integer.MIN_VALUE,Integer.MIN_VALUE,18.5,
90,60,
120,4,100,
10,10,3.4,
2.5,53,
3.1,0.4,2.6,
63.9,7.8,
new Date(System.currentTimeMillis())
);
EldData MAX_DATA=new EldData(
Integer.MAX_VALUE,Integer.MAX_VALUE,23.9,
140,90,
175,10,300,
40,35,17.1,
7.1,115,
5.2,1.7,2.4,
6.1,7.8,
new Date(System.currentTimeMillis())
);
}
测试数据
package com.jsss.echarts;
import com.jsss.echarts.entity.EldData;
import com.jsss.echarts.service.DataService;
import com.jsss.utils.Constant;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import java.sql.Date;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;
@SpringBootTest
public class EchartsTest implements Constant {
@Autowired
RedisTemplate redisTemplate;
@Autowired
DataService dataService;
Integer userId=1;
Date date=new Date(2024-1900,4-1,14);
@Test
public void test(){
System.out.println(date);
}
//清空数据
@Test
public void restore() {
String key= ELD_DATA +userId;
redisTemplate.delete(key);
testSearchMap();
}
@Test
public void testSearchMap(){
HashMap<String, ArrayList> map = dataService.searchMap(userId);
System.out.println(map);
}
@Test
public void testValidAdd(){
EldData eldData=randomData();
eldData.setDate(new Date(2024-1900,4-1,8));
dataService.addData(userId,eldData);
eldData=randomData();
eldData.setDate(new Date(2024-1900,4-1,9));
dataService.addData(userId,eldData);
eldData=randomData();
eldData.setDate(new Date(2024-1900,4-1,10));
dataService.addData(userId,eldData);
eldData=randomData();
eldData.setDate(new Date(2024-1900,4-1,11));
dataService.addData(userId,eldData);
eldData=randomData();
eldData.setDate(new Date(2024-1900,4-1,12));
dataService.addData(userId,eldData);
testSearchMap();
}
EldData minData=MIN_DATA;
EldData maxData=MAX_DATA;
public EldData randomData(){
EldData data=new EldData(
randomInt(160,190), randomInt(45,80), randomDouble(minData.getBMI(),maxData.getBMI()),
randomInt(minData.getSBP(),maxData.getSBP()),randomInt(minData.getDBP(),maxData.getDBP()),
randomInt(minData.getHb(),maxData.getHb()),randomInt(minData.getWBC(),maxData.getWBC()),randomInt(minData.getPLT(),maxData.getPLT()),
randomInt(minData.getALT(),maxData.getALT()),randomInt(minData.getAST(),maxData.getAST()),randomDouble(minData.getTBIL(),maxData.getTBIL()),
randomDouble(minData.getBUN(),maxData.getBUN()),randomInt(minData.getCr(),maxData.getCr()),
randomDouble(minData.getTC(),maxData.getTC()),randomDouble(minData.getTG(),maxData.getTG()),randomDouble(minData.getLDL_C(),maxData.getLDL_C()),
randomDouble(minData.getFPG(),maxData.getFPG()),randomDouble(minData.getTwohPG(),maxData.getTwohPG()),
new Date(System.currentTimeMillis())
);
return data;
}
public Integer randomInt(int min,int max){
Random random = new Random();
return random.nextInt(max - min + 1) + min; // 生成 min 到 max 范围内的随机 int 数
}
public Double randomDouble(double min,double max){
Random random = new Random();
return min + (max - min) * random.nextDouble(); // 生成 min 到 max 范围内的随机 double 值
}
}
最后
2024-4-13 18:49:18
迎着日光月光星光,直面风霜雨霜雪霜。