平台修改意见20251120-点位图表的日数据采用箱线图展示

This commit is contained in:
zq
2025-12-09 14:28:52 +08:00
parent b7aaf85a3f
commit 5f1e621da2
5 changed files with 325 additions and 5 deletions

View File

@ -40,6 +40,12 @@ public class DevicePointDataList
this.minDate = minDate; this.minDate = minDate;
} }
public DevicePointDataList(String deviceId, String parentDeviceId, List<GeneralQueryDataVo> pointValueList) {
this.deviceId = deviceId;
this.parentDeviceId = parentDeviceId;
this.pointValueList = pointValueList;
}
public DevicePointDataList() { public DevicePointDataList() {
} }

View File

@ -14,6 +14,33 @@ public class GeneralQueryDataVo {
private Object pointValue; private Object pointValue;
private String parentDeviceId; private String parentDeviceId;
//箱线图数据
/** 最小值(Minimum) */
private BigDecimal min;
/** 第一四分位数(Q1) */
private BigDecimal q1;
/** 中位数(Median/Q2) */
private BigDecimal median;
/** 第三四分位数(Q3) */
private BigDecimal q3;
/** 最大值(Maximum) */
private BigDecimal max;
public GeneralQueryDataVo() {
}
public GeneralQueryDataVo(String siteId, String deviceId, String valueDate, String parentDeviceId, BigDecimal min, BigDecimal q1, BigDecimal median, BigDecimal q3, BigDecimal max) {
this.siteId = siteId;
this.deviceId = deviceId;
this.valueDate = valueDate;
this.parentDeviceId = parentDeviceId;
this.min = min;
this.q1 = q1;
this.median = median;
this.q3 = q3;
this.max = max;
}
public String getSiteId() { public String getSiteId() {
return siteId; return siteId;
} }
@ -53,4 +80,44 @@ public class GeneralQueryDataVo {
public void setParentDeviceId(String parentDeviceId) { public void setParentDeviceId(String parentDeviceId) {
this.parentDeviceId = parentDeviceId; this.parentDeviceId = parentDeviceId;
} }
public BigDecimal getMin() {
return min;
}
public void setMin(BigDecimal min) {
this.min = min;
}
public BigDecimal getQ1() {
return q1;
}
public void setQ1(BigDecimal q1) {
this.q1 = q1;
}
public BigDecimal getMedian() {
return median;
}
public void setMedian(BigDecimal median) {
this.median = median;
}
public BigDecimal getQ3() {
return q3;
}
public void setQ3(BigDecimal q3) {
this.q3 = q3;
}
public BigDecimal getMax() {
return max;
}
public void setMax(BigDecimal max) {
this.max = max;
}
} }

View File

@ -101,6 +101,15 @@ public interface EmsPointMatchMapper
@Param("params") Map<String, List<String>> params, @Param("params") Map<String, List<String>> params,
@Param("clusterDeviceId")String clusterDeviceId); @Param("clusterDeviceId")String clusterDeviceId);
// 根据条件查询箱线图数据-按天-单体电池特殊处理
public List<GeneralQueryDataVo> getBatteryPointDataForBoxPlot(@Param("siteIds")List<String> siteIds,
@Param("tableName")String tableName,
@Param("tableField")String tableField,
@Param("startDate")Date startDate,
@Param("endDate")Date endDate,
@Param("params") Map<String, List<String>> params,
@Param("clusterDeviceId")String clusterDeviceId);
// 根据条件查询数据-按分钟-其他设备 // 根据条件查询数据-按分钟-其他设备
public List<GeneralQueryDataVo> getCommonPointDataByMinutes(@Param("siteIds")List<String> siteIds, public List<GeneralQueryDataVo> getCommonPointDataByMinutes(@Param("siteIds")List<String> siteIds,
@Param("tableName")String tableName, @Param("tableName")String tableName,
@ -122,6 +131,13 @@ public interface EmsPointMatchMapper
@Param("startDate")Date startDate, @Param("startDate")Date startDate,
@Param("endDate")Date endDate, @Param("endDate")Date endDate,
@Param("deviceId")String deviceId); @Param("deviceId")String deviceId);
// 根据条件查询箱线图数据-按天-其他设备
public List<GeneralQueryDataVo> getCommonPointDataForBoxPlot(@Param("siteIds")List<String> siteIds,
@Param("tableName")String tableName,
@Param("tableField")String tableField,
@Param("startDate")Date startDate,
@Param("endDate")Date endDate,
@Param("deviceId")String deviceId);
// 单个站点单个设备点位查询-除了电池簇其他设备使用 // 单个站点单个设备点位查询-除了电池簇其他设备使用
public List<PointQueryResponse> getSingleSiteDevicePoints(@Param("siteId")String siteId, public List<PointQueryResponse> getSingleSiteDevicePoints(@Param("siteId")String siteId,

View File

@ -1,5 +1,6 @@
package com.xzzn.ems.service.impl; package com.xzzn.ems.service.impl;
import com.sun.org.glassfish.external.statistics.Stats;
import com.xzzn.common.enums.DeviceCategory; import com.xzzn.common.enums.DeviceCategory;
import com.xzzn.common.utils.DateUtils; import com.xzzn.common.utils.DateUtils;
import com.xzzn.ems.domain.EmsPointMatch; import com.xzzn.ems.domain.EmsPointMatch;
@ -20,6 +21,8 @@ import java.time.temporal.TemporalAdjusters;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.lang.model.util.ElementScanner6;
/** /**
* 综合查询 服务层实现 * 综合查询 服务层实现
* *
@ -142,11 +145,18 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
dataVoList = emsPointMatchMapper.getCommonPointDataByHours(querySiteIds,tableName,tableField,startDate,endDate,deviceId); dataVoList = emsPointMatchMapper.getCommonPointDataByHours(querySiteIds,tableName,tableField,startDate,endDate,deviceId);
} else if (dataUnit == 3) { // 天yyyy-MM-dd 00:00:00 } else if (dataUnit == 3) { // 天yyyy-MM-dd 00:00:00
endDate = DateUtils.adjustToEndOfDay(request.getEndDate()); endDate = DateUtils.adjustToEndOfDay(request.getEndDate());
dataVoList = emsPointMatchMapper.getCommonPointDataByDays(querySiteIds,tableName,tableField,startDate,endDate,deviceId); dataVoList = emsPointMatchMapper.getCommonPointDataForBoxPlot(querySiteIds,tableName,tableField,startDate,endDate,deviceId);
}
if (dataUnit == 3) {
// 箱线图数据特殊处理
result = dealCommonWithBoxPlotData(dataVoList, dataType);
} else {
// 曲线图数据特殊处理
// 数据转换+计算曲线数据最大最小平均和差值
result = convertCommonToResultList(dataVoList, dataType);
} }
// 数据转换+计算曲线数据最大最小平均和差值
result = convertCommonToResultList(dataVoList, dataType);
return result; return result;
} }
@ -175,13 +185,113 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
} else if (dataUnit == 3) { // 天 } else if (dataUnit == 3) { // 天
endDate = DateUtils.adjustToEndOfDay(request.getEndDate()); endDate = DateUtils.adjustToEndOfDay(request.getEndDate());
tableName = "ems_battery_data_day"; tableName = "ems_battery_data_day";
dataVoList = emsPointMatchMapper.getBatteryPointDataByDays(querySiteIds,tableName,tableField,startDate,endDate,siteDeviceMap,clusterDeviceId); dataVoList = emsPointMatchMapper.getBatteryPointDataForBoxPlot(querySiteIds,tableName,tableField,startDate,endDate,siteDeviceMap,clusterDeviceId);
} }
// 数据转换 // 数据转换
result = convertBatteryToResultList(dataVoList,dataType); if (dataUnit == 3) {
// 箱线图数据特殊处理
result = dealBatteryWithBoxPlotData(dataVoList, dataType);
} else {
// 曲线图数据特殊处理
// 数据转换+计算曲线数据最大最小平均和差值
result = convertBatteryToResultList(dataVoList, dataType);
}
return result; return result;
} }
private List<GeneralQueryResponse> dealCommonWithBoxPlotData(List<GeneralQueryDataVo> dataVoList, Long dataType) {
// 数据转换: 先按siteId分组再按deviceId分组
return dataVoList.stream()
// 第一层分组按siteId分组得到 <siteId, 该站点所有数据>
.collect(Collectors.groupingBy(GeneralQueryDataVo::getSiteId))
.entrySet().stream()
.map(siteEntry -> {
String siteId = siteEntry.getKey();
List<GeneralQueryDataVo> siteAllData = siteEntry.getValue();
// 第二层分组在当前站点下按deviceId分组得到 <deviceId, 该设备所有数据>
List<DevicePointDataList> deviceList = siteAllData.stream()
.collect(Collectors.groupingBy(GeneralQueryDataVo::getDeviceId))
.entrySet().stream()
.map(deviceEntry -> {
String deviceId = deviceEntry.getKey();
List<GeneralQueryDataVo> deviceDataList = deviceEntry.getValue();
// 第三层分组:在当前设备下,按时间分组,得到 <时间, 该时间段数据>
List<GeneralQueryDataVo> dataList = deviceDataList.stream().sorted(Comparator.comparing(GeneralQueryDataVo::getValueDate))
.collect(Collectors.groupingBy(
GeneralQueryDataVo::getValueDate,
TreeMap::new,
Collectors.toList()))
.entrySet().stream().map(timeEntry -> {
String valueDate = timeEntry.getKey();
// 计算当前设备数据的统计值(最小值,第一四分位数,中位数,第三四分位数, 最大值)
List<BigDecimal> values = timeEntry.getValue().stream().map(data -> new BigDecimal(String.valueOf(data.getPointValue()))).collect(Collectors.toList());
BoxPlotData data = calculateBoxPlotData(values);
return new GeneralQueryDataVo(siteId, deviceId, valueDate, null, data.min, data.q1, data.median, data.q3, data.max);
}).collect(Collectors.toList());
return new DevicePointDataList(deviceId, null, dataList);
})
.collect(Collectors.toList());
return new GeneralQueryResponse(siteId, deviceList, dataType);
})
.collect(Collectors.toList());
}
private List<GeneralQueryResponse> dealBatteryWithBoxPlotData(List<GeneralQueryDataVo> dataVoList, Long dataType) {
// 先按siteId分组
return dataVoList.stream()
.collect(Collectors.groupingBy(GeneralQueryDataVo::getSiteId))
.entrySet().stream()
.map(siteEntry -> {
String siteId = siteEntry.getKey();
List<GeneralQueryDataVo> siteData = siteEntry.getValue();
// 2. 按(deviceId + parentDeviceId)组合分组生成deviceList
List<DevicePointDataList> deviceList = siteData.stream()
// 分组键deviceId + parentDeviceId确保唯一设备
.collect(Collectors.groupingBy(data ->
data.getDeviceId() + "_" + data.getParentDeviceId()
))
.entrySet().stream()
.map(deviceEntry -> {
// 解析分组键获取deviceId和parentDeviceId
String[] keyParts = deviceEntry.getKey().split("_");
String deviceId = keyParts[0];
String parentDeviceId = keyParts[1];
// 3. 第三层分组:在当前设备下,按时间分组,得到 <时间, 该时间段数据>
List<GeneralQueryDataVo> deviceDataList = deviceEntry.getValue().stream()
.collect(Collectors.groupingBy(
GeneralQueryDataVo::getValueDate,
TreeMap::new,
Collectors.toList()))
.entrySet().stream().map(timeEntry -> {
String valueDate = timeEntry.getKey();
// 计算当前设备数据的统计值(最小值,第一四分位数,中位数,第三四分位数, 最大值)
List<BigDecimal> values = timeEntry.getValue().stream().map(data -> new BigDecimal(String.valueOf(data.getPointValue()))).collect(Collectors.toList());
BoxPlotData data = calculateBoxPlotData(values);
return new GeneralQueryDataVo(siteId, deviceId, valueDate, parentDeviceId, data.min, data.q1, data.median, data.q3, data.max);
}).collect(Collectors.toList());
// 4. 构建DeviceItem
return new DevicePointDataList(deviceId, parentDeviceId, deviceDataList);
})// 关键排序步骤先按deviceId升序再按parentDeviceId升序
.sorted(
Comparator.comparing(DevicePointDataList::getDeviceId) // 第一排序键deviceId
.thenComparing(DevicePointDataList::getParentDeviceId) // 第二排序键parentDeviceId
)
.collect(Collectors.toList());
// 5. 构建SiteData
return new GeneralQueryResponse(siteId, deviceList, dataType);
})
.collect(Collectors.toList());
}
private List<GeneralQueryResponse> convertBatteryToResultList(List<GeneralQueryDataVo> dataVoList, Long dataType) { private List<GeneralQueryResponse> convertBatteryToResultList(List<GeneralQueryDataVo> dataVoList, Long dataType) {
// 先按siteId分组 // 先按siteId分组
return dataVoList.stream() return dataVoList.stream()
@ -492,6 +602,49 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
return minutes; return minutes;
} }
public BoxPlotData calculateBoxPlotData(List<BigDecimal> values) {
if (values == null || values.isEmpty()) {
return null;
}
// 排序
Collections.sort(values);
// 计算五个统计量
BigDecimal min = values.get(0);
BigDecimal max = values.get(values.size() - 1);
BigDecimal median = calculateMedian(values);
BigDecimal q1 = calculateQuartile(values, new BigDecimal("0.25"));
BigDecimal q3 = calculateQuartile(values, new BigDecimal("0.75"));
return new BoxPlotData(min, q1, median, q3, max);
}
private BigDecimal calculateMedian(List<BigDecimal> values) {
int size = values.size();
if (size % 2 == 0) {
return (values.get(size/2 - 1).add(values.get(size/2))).divide(new BigDecimal(2));
} else {
return values.get(size/2);
}
}
private BigDecimal calculateQuartile(List<BigDecimal> values, BigDecimal quartile) {
int size = values.size();
BigDecimal pos = new BigDecimal(size - 1).multiply(quartile);
int lowerIndex = (int) Math.floor(pos.doubleValue());
int upperIndex = (int) Math.ceil(pos.doubleValue());
if (lowerIndex == upperIndex) {
return values.get(lowerIndex);
}
BigDecimal lowerValue = values.get(lowerIndex);
BigDecimal upperValue = values.get(upperIndex);
// return lowerValue + (upperValue - lowerValue) * (pos - lowerIndex);
return lowerValue.add(upperValue.subtract(lowerValue)).multiply((pos.subtract(new BigDecimal(lowerIndex))));
}
/** /**
* 内部辅助类:存储统计结果 * 内部辅助类:存储统计结果
*/ */
@ -527,4 +680,31 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
} }
} }
/**
* 内部辅助类:存储箱线图统计结果
*/
private static class BoxPlotData {
/** 最小值(Minimum) */
private final BigDecimal min;
/** 第一四分位数(Q1) */
private final BigDecimal q1;
/** 中位数(Median/Q2) */
private final BigDecimal median;
/** 第三四分位数(Q3) */
private final BigDecimal q3;
/** 最大值(Maximum) */
private final BigDecimal max;
public BoxPlotData(BigDecimal min, BigDecimal q1, BigDecimal median, BigDecimal q3, BigDecimal max) {
this.min = min;
this.q1 = q1;
this.median = median;
this.q3 = q3;
this.max = max;
}
}
} }

View File

@ -273,6 +273,30 @@
ORDER BY t.site_id, t.device_id, t.cluster_device_id, valueDate ASC ORDER BY t.site_id, t.device_id, t.cluster_device_id, valueDate ASC
</select> </select>
<select id="getBatteryPointDataForBoxPlot" resultType="com.xzzn.ems.domain.vo.GeneralQueryDataVo">
SELECT DATE_FORMAT(t.create_time, '%Y-%m-%d') AS valueDate,
t.site_id as siteId,
t.device_id as deviceId,
t.cluster_device_id as parentDeviceId,
t.${tableField} as pointValue
FROM ${tableName} t
INNER JOIN ( SELECT site_id, device_id, cluster_device_id,
DATE_FORMAT(create_time, '%Y-%m-%d') AS day_group
FROM ${tableName}
WHERE create_time &gt;= #{startDate}
AND create_time &lt;= #{endDate}
AND ${tableField} is not null
<include refid="commonFilter"/>
GROUP BY site_id, device_id, cluster_device_id, day_group
) tmp ON t.site_id = tmp.site_id
AND t.device_id = tmp.device_id
AND t.cluster_device_id = tmp.cluster_device_id
AND DATE_FORMAT(t.create_time, '%Y-%m-%d') = tmp.day_group
WHERE t.${tableField} is not null
GROUP BY t.site_id, t.device_id, t.cluster_device_id, pointValue, valueDate
ORDER BY t.site_id, t.device_id, t.cluster_device_id, valueDate ASC
</select>
<select id="getCommonPointDataByMinutes" resultType="com.xzzn.ems.domain.vo.GeneralQueryDataVo"> <select id="getCommonPointDataByMinutes" resultType="com.xzzn.ems.domain.vo.GeneralQueryDataVo">
SELECT DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:00') AS valueDate, SELECT DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:00') AS valueDate,
site_id as siteId, site_id as siteId,
@ -351,6 +375,33 @@
ORDER BY t.site_id, t.device_id, valueDate ASC ORDER BY t.site_id, t.device_id, valueDate ASC
</select> </select>
<select id="getCommonPointDataForBoxPlot" resultType="com.xzzn.ems.domain.vo.GeneralQueryDataVo">
SELECT DATE_FORMAT(t.create_time, '%Y-%m-%d') AS valueDate,
t.site_id as siteId,
t.device_id as deviceId,
t.${tableField} as pointValue
FROM ${tableName} t
INNER JOIN ( SELECT site_id, device_id, DATE_FORMAT(create_time, '%Y-%m-%d') AS day_group
FROM ${tableName}
WHERE create_time &gt;= #{startDate}
AND create_time &lt;= #{endDate}
AND ${tableField} is not null
AND site_id IN
<foreach collection="siteIds" item="siteId" open="(" close=")" separator=",">
#{siteId}
</foreach>
<if test="deviceId != null and deviceId != ''">
and device_id = #{deviceId}
</if>
GROUP BY site_id, device_id, day_group
) tmp ON t.site_id = tmp.site_id
AND t.device_id = tmp.device_id
AND DATE_FORMAT(t.create_time, '%Y-%m-%d') = tmp.day_group
WHERE t.${tableField} is not null
GROUP BY t.site_id, t.device_id, pointValue, valueDate
ORDER BY t.site_id, t.device_id, valueDate ASC
</select>
<select id="getSingleSiteDevicePoints" resultType="com.xzzn.ems.domain.vo.PointQueryResponse"> <select id="getSingleSiteDevicePoints" resultType="com.xzzn.ems.domain.vo.PointQueryResponse">
select t.point_name as pointName, select t.point_name as pointName,
t.data_point as dataPoint, t.data_point as dataPoint,