diff --git a/pqs-common/common-echarts/src/main/java/com/njcn/echarts/json/LineGenerator.java b/pqs-common/common-echarts/src/main/java/com/njcn/echarts/json/LineGenerator.java index 9c28debb3..bb1beabee 100644 --- a/pqs-common/common-echarts/src/main/java/com/njcn/echarts/json/LineGenerator.java +++ b/pqs-common/common-echarts/src/main/java/com/njcn/echarts/json/LineGenerator.java @@ -1,11 +1,7 @@ package com.njcn.echarts.json; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.collection.ListUtil; import cn.hutool.json.JSONUtil; -import cn.hutool.json.ObjectMapper; -import com.google.gson.Gson; -import com.google.gson.JsonObject; import com.njcn.echarts.pojo.bo.TolerateData; import com.njcn.echarts.pojo.constant.PicCommonData; import org.icepear.echarts.Option; @@ -29,7 +25,6 @@ import org.icepear.echarts.render.Engine; import java.text.DecimalFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -43,6 +38,17 @@ import java.util.stream.Collectors; public class LineGenerator { private final static Engine ENGINE = new Engine(); + // null段在图表上的固定视觉宽度(数据点数量) + private static final int NULL_PLACEHOLDER_COUNT = 90; + // 压缩结果 + private static class CompressedResult { + // 压缩后的数据 + List> data; + // 每个null段的[起始索引, 结束索引](占位点范围) + List nullRegionIndices; + // null段边界处的真实x值(entry, exit交替) + List nullBoundaryRealXValues; + } /*** @@ -529,6 +535,478 @@ public class LineGenerator { return ENGINE.renderJsonOption(instantOption); } + public static String generateWaveOption(String title, List> aValue, List> bValue, List> cValue, String unit, Float max, Float min, String a, String b, String c, List colors, Boolean isOpen, Double lastTime, List sharedNullBoundaryXValues, Integer nPush) { + DecimalFormat df1 = new DecimalFormat("#.00"); + // 压缩null数据:如果有共享边界则用共享边界,否则独立压缩 + CompressedResult aCompressed; + CompressedResult bCompressed; + CompressedResult cCompressed; + if (sharedNullBoundaryXValues != null && !sharedNullBoundaryXValues.isEmpty()) { + aCompressed = compressNullDataWithSharedBoundaries(aValue, sharedNullBoundaryXValues); + bCompressed = compressNullDataWithSharedBoundaries(bValue, sharedNullBoundaryXValues); + cCompressed = compressNullDataWithSharedBoundaries(cValue, sharedNullBoundaryXValues); + } else { + aCompressed = compressNullData(aValue); + bCompressed = compressNullData(bValue); + cCompressed = compressNullData(cValue); + } + + Option instantOption = new Option(); + //取消渲染动画 + instantOption.setAnimation(false); + //背景色 + instantOption.setBackgroundColor(PicCommonData.PIC_BACK_COLOR); + //标题 + instantOption.setTitle(new Title() + .setText(title) + .setLeft(PicCommonData.CENTER) + .setTextStyle(new Label().setFontSize(14)) + ); + //配置颜色 + instantOption.setColor(colors.toArray(new String[colors.size()])); + //配置图例 + if (isOpen) { + instantOption.setLegend(new Legend().setData(new String[]{a, b}).setLeft("10px")); + } else { + instantOption.setLegend(new Legend().setData(new String[]{a, b, c}).setLeft("10px")); + } + //上下左右的图内间距 + instantOption.setGrid(new Grid().setTop("60px").setLeft("70px").setRight("40px").setBottom("10%")); + //横坐标(使用压缩后的数据) + instantOption.setXAxis(new ValueAxis().setName("ms") + .setSplitLine(new SplitLine().setShow(false)) + .setAxisLine(new AxisLine().setOnZero(false)) + .setType("category") + .setMinInterval(1) + ); + //纵坐标 + instantOption.setYAxis(new ValueAxis() + .setName(unit) + .setMax(df1.format(max * 1.1)) + .setMin(df1.format(min * 1.1)) + .setPosition(PicCommonData.LEFT) + .setAxisLine(new AxisLine().setShow(false).setOnZero(false)) + ); + //数据信息(使用压缩后的数据) + LineSeries aSeries = new LineSeries() + .setName(a) + .setSmooth(true) + .setSymbol("none") + .setConnectNulls(false) + .setData(aCompressed.data); + LineSeries bSeries = new LineSeries() + .setName(b) + .setSmooth(true) + .setSymbol("none") + .setConnectNulls(false) + .setData(bCompressed.data); + LineSeries cSeries = new LineSeries() + .setName(c) + .setSmooth(true) + .setSymbol("none") + .setConnectNulls(false) + .setData(cCompressed.data); + instantOption.setSeries(new LineSeries[]{aSeries, bSeries, cSeries}); + + // 渲染基础JSON + String jsonStr = ENGINE.renderJsonOption(instantOption); + cn.hutool.json.JSONObject optionJson = JSONUtil.parseObj(jsonStr); + cn.hutool.json.JSONArray seriesArray = optionJson.getJSONArray("series"); + + // 隐藏x轴默认标签,设置boundaryGap=false,只通过markLine显示关键节点 + Object xAxisRaw = optionJson.get("xAxis"); + cn.hutool.json.JSONObject xAxisJson; + if (xAxisRaw instanceof cn.hutool.json.JSONArray) { + xAxisJson = ((cn.hutool.json.JSONArray) xAxisRaw).getJSONObject(0); + } else { + xAxisJson = (cn.hutool.json.JSONObject) xAxisRaw; + } + cn.hutool.json.JSONObject axisLabelJson = new cn.hutool.json.JSONObject(); + axisLabelJson.set("show", false); + xAxisJson.set("axisLabel", axisLabelJson); + xAxisJson.set("boundaryGap", false); + + // 添加null markLine/markArea(基于压缩后的数据) + addNullMarkLinesForCategoryAxis(seriesArray.getJSONObject(0), aCompressed, lastTime); + addNullMarkLinesForCategoryAxis(seriesArray.getJSONObject(1), bCompressed, lastTime); + addNullMarkLinesForCategoryAxis(seriesArray.getJSONObject(2), cCompressed, lastTime); + // 在第一个series追加自定义横坐标标签markLine + addAxisLabelMarkLines(seriesArray.getJSONObject(0), aCompressed, lastTime, nPush); + return optionJson.toString(); + } + + /** 提取null边界的真实x值,供其他数据集共享使用 */ + public static List extractNullBoundaryXValues(List> data) { + List boundaries = findNullSegmentBoundaryIndices(data); + List xValues = new ArrayList<>(); + for (int idx : boundaries) { + xValues.add(data.get(idx).get(0)); + } + return xValues; + } + + /** 使用共享的null边界x值来压缩数据,确保不同数据集压缩结果结构一致 */ + private static CompressedResult compressNullDataWithSharedBoundaries(List> originalData, List sharedBoundaryXValues) { + if (sharedBoundaryXValues == null || sharedBoundaryXValues.isEmpty()) { + return compressNullData(originalData); + } + + // 用共享的x值找到本数据集中对应的索引 + List boundaries = new ArrayList<>(); + for (Float x : sharedBoundaryXValues) { + boundaries.add(findClosestValidIndex(originalData, x)); + } + + CompressedResult result = new CompressedResult(); + result.data = new ArrayList<>(); + result.nullRegionIndices = new ArrayList<>(); + result.nullBoundaryRealXValues = new ArrayList<>(sharedBoundaryXValues); + + int srcIdx = 0; + for (int b = 0; b < boundaries.size(); b += 2) { + int entryBoundary = boundaries.get(b); + int exitBoundary = boundaries.get(b + 1); + + while (srcIdx <= entryBoundary) { + result.data.add(originalData.get(srcIdx)); + srcIdx++; + } + + int nullStartIdx = result.data.size(); + float entryX = sharedBoundaryXValues.get(b); + float exitX = sharedBoundaryXValues.get(b + 1); + + for (int p = 0; p < NULL_PLACEHOLDER_COUNT; p++) { + List point = new ArrayList<>(); + point.add(entryX + (exitX - entryX) * p / NULL_PLACEHOLDER_COUNT); + point.add(null); + result.data.add(point); + } + + int nullEndIdx = result.data.size() - 1; + result.nullRegionIndices.add(new int[]{nullStartIdx, nullEndIdx}); + + srcIdx = exitBoundary; + } + + while (srcIdx < originalData.size()) { + result.data.add(originalData.get(srcIdx)); + srcIdx++; + } + + return result; + } + + + + /** + * 压缩null数据:删除所有null数据点,替换为固定NULL_PLACEHOLDER_COUNT个占位点 + * 这样null段在图表上永远只占固定视觉宽度,前后有效数据正常展开 + */ + private static CompressedResult compressNullData(List> originalData) { + List boundaries = findNullSegmentBoundaryIndices(originalData); + + CompressedResult result = new CompressedResult(); + result.data = new ArrayList<>(); + result.nullRegionIndices = new ArrayList<>(); + result.nullBoundaryRealXValues = new ArrayList<>(); + + if (boundaries.isEmpty()) { + result.data.addAll(originalData); + return result; + } + + int srcIdx = 0; + for (int b = 0; b < boundaries.size(); b += 2) { + // 最后一个有效点的索引(null段入口边界) + int entryBoundary = boundaries.get(b); + // 第一个恢复有效点的索引(null段出口边界) + int exitBoundary = boundaries.get(b + 1); + + // 复制有效数据:从srcIdx到entryBoundary + while (srcIdx <= entryBoundary) { + result.data.add(originalData.get(srcIdx)); + srcIdx++; + } + + // 记录边界真实x值 + result.nullBoundaryRealXValues.add(originalData.get(entryBoundary).get(0)); + result.nullBoundaryRealXValues.add(originalData.get(exitBoundary).get(0)); + + // 插入固定数量的null占位点 + int nullStartIdx = result.data.size(); + float entryX = originalData.get(entryBoundary).get(0); + float exitX = originalData.get(exitBoundary).get(0); + + for (int p = 0; p < NULL_PLACEHOLDER_COUNT; p++) { + List point = new ArrayList<>(); + point.add(entryX + (exitX - entryX) * p / NULL_PLACEHOLDER_COUNT); + point.add(null); + result.data.add(point); + } + + int nullEndIdx = result.data.size() - 1; + result.nullRegionIndices.add(new int[]{nullStartIdx, nullEndIdx}); + + // 跳过原始null数据点,srcIdx跳到exitBoundary + srcIdx = exitBoundary; + } + + // 复制最后一个null段之后的剩余有效数据 + while (srcIdx < originalData.size()) { + result.data.add(originalData.get(srcIdx)); + srcIdx++; + } + + return result; + } + + /** + * 在压缩数据中查找最接近目标x值的有效数据点索引(跳过null点) + */ + private static int findClosestValidIndex(List> data, float targetX) { + int closestIdx = -1; + float minDiff = Float.MAX_VALUE; + for (int i = 0; i < data.size(); i++) { + Float y = data.get(i).get(1); + if (y == null) continue; + float diff = Math.abs(data.get(i).get(0) - targetX); + if (diff < minDiff) { + minDiff = diff; + closestIdx = i; + } + } + return closestIdx; + } + + private static void addAxisLabelMarkLines(cn.hutool.json.JSONObject seriesObj, CompressedResult compressed, Double lastTime, Integer nPush) { + boolean hasNull = !compressed.nullRegionIndices.isEmpty(); + + // 收集需要标注的横坐标位置:[数据索引, 显示的时间值] + List labelPositions = new ArrayList<>(); + // 开头ms(始终显示) + labelPositions.add(new int[]{findClosestValidIndex(compressed.data, -nPush), -nPush}); + // 0ms(始终显示) + labelPositions.add(new int[]{findClosestValidIndex(compressed.data, 0f), 0}); + // npush ms(仅在有null时显示) + if (hasNull) { + labelPositions.add(new int[]{findClosestValidIndex(compressed.data, nPush), nPush}); + } + // null段边界真实时间值(仅在有null时显示) + if (hasNull) { + for (Float realX : compressed.nullBoundaryRealXValues) { + int idx = findClosestValidIndex(compressed.data, realX); + if (idx >= 0) { + labelPositions.add(new int[]{idx, (int) Math.round(realX)}); + } + } + } + // lastTime(始终显示) + if (lastTime != null) { + labelPositions.add(new int[]{findClosestValidIndex(compressed.data, lastTime.floatValue()), lastTime.intValue()}); + } + + // lastTime+20(始终显示,数据结束位置横坐标) + if (lastTime != null) { + float lastTime20 = lastTime.floatValue() + nPush; + int lastTime20Idx = findClosestValidIndex(compressed.data, lastTime20); + if (lastTime20Idx >= 0) { + labelPositions.add(new int[]{(lastTime20Idx-2), (int) Math.round(lastTime20)}); + } + } + + // 获取或创建markLine + cn.hutool.json.JSONObject existingMarkLine = seriesObj.getJSONObject("markLine"); + cn.hutool.json.JSONArray markLineData; + + if (existingMarkLine == null) { + // 无null的情况:自己创建markLine,包含虚线和横坐标标签 + markLineData = new cn.hutool.json.JSONArray(); + + // x=0 虚线 + int zeroIdx = findClosestValidIndex(compressed.data, 0f); + if (zeroIdx >= 0) { + cn.hutool.json.JSONObject zeroLine = new cn.hutool.json.JSONObject(); + zeroLine.set("xAxis", zeroIdx); + cn.hutool.json.JSONObject zeroLabel = new cn.hutool.json.JSONObject(); + zeroLabel.set("show", false); + zeroLine.set("label", zeroLabel); + markLineData.add(zeroLine); + } + // lastTime 虚线 + if (lastTime != null) { + int lastTimeIdx = findClosestValidIndex(compressed.data, lastTime.floatValue()); + if (lastTimeIdx >= 0) { + cn.hutool.json.JSONObject lastTimeLine = new cn.hutool.json.JSONObject(); + lastTimeLine.set("xAxis", lastTimeIdx); + cn.hutool.json.JSONObject lastTimeLabelObj = new cn.hutool.json.JSONObject(); + lastTimeLabelObj.set("show", false); + lastTimeLine.set("label", lastTimeLabelObj); + markLineData.add(lastTimeLine); + } + } + + // 先收集所有标签到markLineData + for (int[] pos : labelPositions) { + cn.hutool.json.JSONObject line = new cn.hutool.json.JSONObject(); + line.set("xAxis", pos[0]); + cn.hutool.json.JSONObject lineLineStyle = new cn.hutool.json.JSONObject(); + lineLineStyle.set("width", 0); + line.set("lineStyle", lineLineStyle); + cn.hutool.json.JSONObject lineLabel = new cn.hutool.json.JSONObject(); + lineLabel.set("show", true); + lineLabel.set("formatter", String.valueOf(pos[1])); + lineLabel.set("position", "start"); + lineLabel.set("color", "#333"); + lineLabel.set("fontSize", 10); + line.set("label", lineLabel); + markLineData.add(line); + } + // 最后一次性设置到markLine和series + existingMarkLine = new cn.hutool.json.JSONObject(); + existingMarkLine.set("symbol", new String[]{"none", "none"}); + cn.hutool.json.JSONObject lineStyle = new cn.hutool.json.JSONObject(); + lineStyle.set("type", "dashed"); + lineStyle.set("color", "#999"); + lineStyle.set("width", 1); + existingMarkLine.set("lineStyle", lineStyle); + cn.hutool.json.JSONObject globalLabel = new cn.hutool.json.JSONObject(); + globalLabel.set("show", false); + existingMarkLine.set("label", globalLabel); + existingMarkLine.set("data", markLineData); + seriesObj.set("markLine", existingMarkLine); + } else { + // 有null的情况:追加标签到已有的markLine + markLineData = existingMarkLine.getJSONArray("data"); + for (int[] pos : labelPositions) { + cn.hutool.json.JSONObject line = new cn.hutool.json.JSONObject(); + line.set("xAxis", pos[0]); + cn.hutool.json.JSONObject lineLineStyle = new cn.hutool.json.JSONObject(); + lineLineStyle.set("width", 0); + line.set("lineStyle", lineLineStyle); + cn.hutool.json.JSONObject lineLabel = new cn.hutool.json.JSONObject(); + lineLabel.set("show", true); + lineLabel.set("formatter", String.valueOf(pos[1])); + lineLabel.set("position", "start"); + lineLabel.set("color", "#333"); + lineLabel.set("fontSize", 10); + line.set("label", lineLabel); + markLineData.add(line); + } + } + } + + private static List findNullSegmentBoundaryIndices(List> data) { + List indices = new ArrayList<>(); + boolean inNullSegment = false; + for (int i = 0; i < data.size(); i++) { + Float y = data.get(i).get(1); + if (y == null) { + if (!inNullSegment) { + indices.add(i > 0 ? i - 1 : i); + inNullSegment = true; + } + } else { + if (inNullSegment) { + indices.add(i); + inNullSegment = false; + } + } + } + if (inNullSegment) { + indices.add(data.size() - 1); + } + return indices; + } + + private static void addNullMarkLinesForCategoryAxis(cn.hutool.json.JSONObject seriesObj, CompressedResult compressed, Double lastTime) { + if (compressed.nullRegionIndices.isEmpty()) { + return; + } + cn.hutool.json.JSONArray markLineData = new cn.hutool.json.JSONArray(); + // x=0 虚线 + int zeroIdx = findClosestValidIndex(compressed.data, 0f); + if (zeroIdx >= 0) { + cn.hutool.json.JSONObject zeroLine = new cn.hutool.json.JSONObject(); + zeroLine.set("xAxis", zeroIdx); + cn.hutool.json.JSONObject zeroLabel = new cn.hutool.json.JSONObject(); + zeroLabel.set("show", false); + zeroLine.set("label", zeroLabel); + markLineData.add(zeroLine); + } + // null段边界的垂直虚线 + for (int r = 0; r < compressed.nullRegionIndices.size(); r++) { + int[] region = compressed.nullRegionIndices.get(r); + int nullStartIdx = region[0]; // 第一个占位点索引 + int nullEndIdx = region[1]; // 最后一个占位点索引 + + // 入口虚线:nullStartIdx - 1(null前最后一个有效数据点) + cn.hutool.json.JSONObject entryLine = new cn.hutool.json.JSONObject(); + entryLine.set("xAxis", nullStartIdx - 1); + cn.hutool.json.JSONObject entryLabel = new cn.hutool.json.JSONObject(); + entryLabel.set("show", false); + entryLine.set("label", entryLabel); + markLineData.add(entryLine); + + // 出口虚线:nullEndIdx(null后第一个有效数据点) + cn.hutool.json.JSONObject exitLine = new cn.hutool.json.JSONObject(); + exitLine.set("xAxis", nullEndIdx); + cn.hutool.json.JSONObject exitLabel = new cn.hutool.json.JSONObject(); + exitLabel.set("show", false); + exitLine.set("label", exitLabel); + markLineData.add(exitLine); + } + // 时间持续时间结束的数据点 + if (lastTime != null) { + int lastTimeIdx = findClosestValidIndex(compressed.data, lastTime.floatValue()); + if (lastTimeIdx >= 0) { + cn.hutool.json.JSONObject lastTimeLine = new cn.hutool.json.JSONObject(); + lastTimeLine.set("xAxis", lastTimeIdx); + cn.hutool.json.JSONObject lastTimeLabel = new cn.hutool.json.JSONObject(); + lastTimeLabel.set("show", false); + lastTimeLine.set("label", lastTimeLabel); + markLineData.add(lastTimeLine); + } + } + + cn.hutool.json.JSONObject markLine = new cn.hutool.json.JSONObject(); + markLine.set("symbol", new String[]{"none", "none"}); + cn.hutool.json.JSONObject lineStyle = new cn.hutool.json.JSONObject(); + lineStyle.set("type", "dashed"); + lineStyle.set("color", "#999"); + lineStyle.set("width", 1); + markLine.set("lineStyle", lineStyle); + cn.hutool.json.JSONObject globalLabel = new cn.hutool.json.JSONObject(); + globalLabel.set("show", false); + markLine.set("label", globalLabel); + markLine.set("data", markLineData); + seriesObj.set("markLine", markLine); + + // markArea - 灰色背景覆盖null区域(从入口边界到出口边界) + cn.hutool.json.JSONArray markAreaData = new cn.hutool.json.JSONArray(); + for (int r = 0; r < compressed.nullRegionIndices.size(); r++) { + int[] region = compressed.nullRegionIndices.get(r); + int nullStartIdx = region[0]; + int nullEndIdx = region[1]; + + cn.hutool.json.JSONArray areaPair = new cn.hutool.json.JSONArray(); + cn.hutool.json.JSONObject start = new cn.hutool.json.JSONObject(); + start.set("xAxis", nullStartIdx - 1); // 从入口边界有效数据点 + cn.hutool.json.JSONObject end = new cn.hutool.json.JSONObject(); + end.set("xAxis", nullEndIdx); // 到出口边界有效数据点 + areaPair.add(start); + areaPair.add(end); + markAreaData.add(areaPair); + } + cn.hutool.json.JSONObject markArea = new cn.hutool.json.JSONObject(); + cn.hutool.json.JSONObject itemStyle = new cn.hutool.json.JSONObject(); + itemStyle.set("color", "rgba(180, 180, 180, 0.2)"); + itemStyle.set("borderWidth", 0); + markArea.set("itemStyle", itemStyle); + markArea.set("data", markAreaData); + seriesObj.set("markArea", markArea); + } + /** * @param title 标题 * @param values 数据值 diff --git a/pqs-common/common-echarts/src/main/java/com/njcn/echarts/util/DrawPicUtil.java b/pqs-common/common-echarts/src/main/java/com/njcn/echarts/util/DrawPicUtil.java index 8301490a0..b58b82c76 100644 --- a/pqs-common/common-echarts/src/main/java/com/njcn/echarts/util/DrawPicUtil.java +++ b/pqs-common/common-echarts/src/main/java/com/njcn/echarts/util/DrawPicUtil.java @@ -10,9 +10,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; -import org.springframework.web.client.RestTemplate; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Objects; /** * @author hongawen @@ -55,7 +56,6 @@ public class DrawPicUtil { return Objects.requireNonNull(picResult.getBody()).indexOf("image/png") > 0 ? picResult.getBody() : ""; } - /*** * @author hongawen * 绘制波形图 @@ -77,6 +77,11 @@ public class DrawPicUtil { return drawPic(instantJson, width, height); } + public String drawWavePic(String title, List> aValue, List> bValue, List> cValue, String unit, Float max, Float min, String a, String b, String c, List colors, Boolean isOpen, Double lastTime, List sharedNullBoundaryXValues,Integer nPush) { + String instantJson = LineGenerator.generateWaveOption(title, aValue, bValue, cValue, unit, max, min, a, b, c, colors, isOpen, lastTime, sharedNullBoundaryXValues,nPush); + return drawPic(instantJson, 0, 0); + } + /*** * 绘制itic曲线图 diff --git a/pqs-common/common-event/src/main/java/com/njcn/event/file/component/WavePicComponent.java b/pqs-common/common-event/src/main/java/com/njcn/event/file/component/WavePicComponent.java index 96067485f..068c6c574 100644 --- a/pqs-common/common-event/src/main/java/com/njcn/event/file/component/WavePicComponent.java +++ b/pqs-common/common-event/src/main/java/com/njcn/event/file/component/WavePicComponent.java @@ -1,10 +1,9 @@ package com.njcn.event.file.component; import cn.hutool.core.img.ImgUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.CharsetUtil; import com.njcn.common.pojo.exception.BusinessException; import com.njcn.common.utils.FileUtil; +import com.njcn.echarts.json.LineGenerator; import com.njcn.echarts.pojo.constant.PicCommonData; import com.njcn.echarts.util.DrawPicUtil; import com.njcn.event.file.pojo.bo.WaveDataDetail; @@ -12,21 +11,17 @@ import com.njcn.event.file.pojo.dto.WaveDataDTO; import com.njcn.event.file.pojo.enums.WaveFileResponseEnum; import com.njcn.oss.constant.OssPath; import com.njcn.oss.utils.FileStorageUtil; -import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import sun.awt.image.BufferedImageGraphicsConfig; -import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; +import java.util.Base64; import java.util.List; +import java.util.Map; /** * @author hongawen @@ -49,36 +44,39 @@ public class WavePicComponent { * @date 2023/9/21 15:32 * @return String 文件地址 */ - public String generateInstantImageZl(List waveDataDetails) { + public String generateInstantImageZl(Integer nPush, List waveDataDetails, Double lastTime) { String firstPic = null, secondPic = null, thirdPic = null, forthPic = null; + WaveDataDetail waveDataDetail0 = waveDataDetails.get(0); + // 从instantData提取null边界x值,作为共享参考(与Shun图保持一致) + List sharedNullBoundaries = LineGenerator.extractNullBoundaryXValues(waveDataDetail0.getInstantData().getAValue()); for (WaveDataDetail waveDataDetail : waveDataDetails) { if (waveDataDetail.getChannelName().toUpperCase().startsWith("SU")) { firstPic = drawPicUtil.drawWavePic("电压-电网侧", waveDataDetail.getInstantData().getAValue(), waveDataDetail.getInstantData().getBValue(), waveDataDetail.getInstantData().getCValue(), waveDataDetail.getUnit(), waveDataDetail.getInstantData().getMax(), waveDataDetail.getInstantData().getMin(), waveDataDetail.getA(), waveDataDetail.getB(), waveDataDetail.getC(), - waveDataDetail.getColors(), waveDataDetail.getIsOpen() + waveDataDetail.getColors(), waveDataDetail.getIsOpen(),lastTime,sharedNullBoundaries,nPush ); } else if (waveDataDetail.getChannelName().toUpperCase().startsWith("SI")) { thirdPic = drawPicUtil.drawWavePic("电流-电网侧", waveDataDetail.getInstantData().getAValue(), waveDataDetail.getInstantData().getBValue(), waveDataDetail.getInstantData().getCValue(), waveDataDetail.getUnit(), waveDataDetail.getInstantData().getMax(), waveDataDetail.getInstantData().getMin(), waveDataDetail.getA(), waveDataDetail.getB(), waveDataDetail.getC(), - waveDataDetail.getColors(), waveDataDetail.getIsOpen() + waveDataDetail.getColors(), waveDataDetail.getIsOpen(),lastTime,sharedNullBoundaries,nPush ); } else if (waveDataDetail.getChannelName().toUpperCase().startsWith("LU")) { secondPic = drawPicUtil.drawWavePic("电压-负载侧", waveDataDetail.getInstantData().getAValue(), waveDataDetail.getInstantData().getBValue(), waveDataDetail.getInstantData().getCValue(), waveDataDetail.getUnit(), waveDataDetail.getInstantData().getMax(), waveDataDetail.getInstantData().getMin(), waveDataDetail.getA(), waveDataDetail.getB(), waveDataDetail.getC(), - waveDataDetail.getColors(), waveDataDetail.getIsOpen() + waveDataDetail.getColors(), waveDataDetail.getIsOpen(),lastTime,sharedNullBoundaries,nPush ); } else { forthPic = drawPicUtil.drawWavePic("电流-负载侧", waveDataDetail.getInstantData().getAValue(), waveDataDetail.getInstantData().getBValue(), waveDataDetail.getInstantData().getCValue(), waveDataDetail.getUnit(), waveDataDetail.getInstantData().getMax(), waveDataDetail.getInstantData().getMin(), waveDataDetail.getA(), waveDataDetail.getB(), waveDataDetail.getC(), - waveDataDetail.getColors(), waveDataDetail.getIsOpen() + waveDataDetail.getColors(), waveDataDetail.getIsOpen(),lastTime,sharedNullBoundaries,nPush ); } } @@ -91,36 +89,39 @@ public class WavePicComponent { * @date 2023/9/21 15:32 * @return String 文件地址 */ - public String generateRmsImageZl(List waveDataDetails) { + public String generateRmsImageZl(Integer nPush, List waveDataDetails, Double lastTime) { String firstPic = null, secondPic = null, thirdPic = null, forthPic = null; + WaveDataDetail waveDataDetail0 = waveDataDetails.get(0); + // 从instantData提取null边界x值,作为共享参考(与Shun图保持一致) + List sharedNullBoundaries = LineGenerator.extractNullBoundaryXValues(waveDataDetail0.getInstantData().getAValue()); for (WaveDataDetail waveDataDetail : waveDataDetails) { if (waveDataDetail.getChannelName().toUpperCase().startsWith("SU")) { firstPic = drawPicUtil.drawWavePic("电压-电网侧", waveDataDetail.getRmsData().getAValue(), waveDataDetail.getRmsData().getBValue(), waveDataDetail.getRmsData().getCValue(), waveDataDetail.getUnit(), waveDataDetail.getRmsData().getMax(), waveDataDetail.getRmsData().getMin(), waveDataDetail.getA(), waveDataDetail.getB(), waveDataDetail.getC(), - waveDataDetail.getColors(), waveDataDetail.getIsOpen() + waveDataDetail.getColors(), waveDataDetail.getIsOpen(),lastTime,sharedNullBoundaries,nPush ); } else if (waveDataDetail.getChannelName().toUpperCase().startsWith("SI")) { thirdPic = drawPicUtil.drawWavePic("电流-电网侧", waveDataDetail.getRmsData().getAValue(), waveDataDetail.getRmsData().getBValue(), waveDataDetail.getRmsData().getCValue(), waveDataDetail.getUnit(), waveDataDetail.getRmsData().getMax(), waveDataDetail.getRmsData().getMin(), waveDataDetail.getA(), waveDataDetail.getB(), waveDataDetail.getC(), - waveDataDetail.getColors(), waveDataDetail.getIsOpen() + waveDataDetail.getColors(), waveDataDetail.getIsOpen(),lastTime,sharedNullBoundaries,nPush ); } else if (waveDataDetail.getChannelName().toUpperCase().startsWith("LU")) { secondPic = drawPicUtil.drawWavePic("电压-负载侧", waveDataDetail.getRmsData().getAValue(), waveDataDetail.getRmsData().getBValue(), waveDataDetail.getRmsData().getCValue(), waveDataDetail.getUnit(), waveDataDetail.getRmsData().getMax(), waveDataDetail.getRmsData().getMin(), waveDataDetail.getA(), waveDataDetail.getB(), waveDataDetail.getC(), - waveDataDetail.getColors(), waveDataDetail.getIsOpen() + waveDataDetail.getColors(), waveDataDetail.getIsOpen(),lastTime,sharedNullBoundaries,nPush ); } else { forthPic = drawPicUtil.drawWavePic("电流-负载侧", waveDataDetail.getRmsData().getAValue(), waveDataDetail.getRmsData().getBValue(), waveDataDetail.getRmsData().getCValue(), waveDataDetail.getUnit(), waveDataDetail.getRmsData().getMax(), waveDataDetail.getRmsData().getMin(), waveDataDetail.getA(), waveDataDetail.getB(), waveDataDetail.getC(), - waveDataDetail.getColors(), waveDataDetail.getIsOpen() + waveDataDetail.getColors(), waveDataDetail.getIsOpen(),lastTime,sharedNullBoundaries,nPush ); } } @@ -209,6 +210,47 @@ public class WavePicComponent { return picPath; } + /*** + * 绘制瞬时波形图 App端用来绘制图片,会去掉部分数据,只保留开始数据 结束数据 + * @author xy + * @return String 文件地址 + */ + public String generateImageShun(Integer nPush, List waveDataDetails, Double lastTime) { + String picPath = null; + WaveDataDetail waveDataDetail = waveDataDetails.get(0); + // 从instantData提取null边界x值,作为共享参考 + List sharedNullBoundaries = LineGenerator.extractNullBoundaryXValues(waveDataDetail.getInstantData().getAValue()); + + String firstPic = drawPicUtil.drawWavePic(getTitle(waveDataDetail.getChannelName()), waveDataDetail.getInstantData().getAValue(), + waveDataDetail.getInstantData().getBValue(), waveDataDetail.getInstantData().getCValue(), + waveDataDetail.getUnit(), waveDataDetail.getInstantData().getMax(), waveDataDetail.getInstantData().getMin(), + waveDataDetail.getA(), waveDataDetail.getB(), waveDataDetail.getC(), + waveDataDetail.getColors(), waveDataDetail.getIsOpen(), lastTime, sharedNullBoundaries,nPush + ); + String secondPic; + if (waveDataDetails.size() == 1) { + if (firstPic.contains(PicCommonData.PNG_PREFIX)) { + firstPic = firstPic.replace(PicCommonData.PNG_PREFIX, ""); + } + byte[] bytes = Base64.getDecoder().decode(firstPic); + picPath = fileStorageUtil.uploadStream(new ByteArrayInputStream(bytes), OssPath.EVENT_WAVE_PIC, FileUtil.generateFileName("png")); + } else if (waveDataDetails.size() == 2) { + waveDataDetail = waveDataDetails.get(1); + secondPic = drawPicUtil.drawWavePic(getTitle(waveDataDetail.getChannelName()), waveDataDetail.getInstantData().getAValue(), + waveDataDetail.getInstantData().getBValue(), waveDataDetail.getInstantData().getCValue(), + waveDataDetail.getUnit(), waveDataDetail.getInstantData().getMax(), waveDataDetail.getInstantData().getMin(), + waveDataDetail.getA(), waveDataDetail.getB(), waveDataDetail.getC(), + waveDataDetail.getColors(), waveDataDetail.getIsOpen(), lastTime, sharedNullBoundaries,nPush + ); + picPath = composeImage(firstPic, secondPic); + } + return picPath; + } + + public String getTitle(String channelName) { + return channelName.toUpperCase().startsWith("U1") ? "电压" : "电流"; + } + /*** * 绘制RMS波形图 * @author hongawen @@ -248,6 +290,44 @@ public class WavePicComponent { return picPath; } + /*** + * 绘制RMS波形图 App端用来绘制图片,会去掉部分数据,只保留开始数据 结束数据 + * @author xy + * @return String 文件地址 + */ + public String generateImageRms(Integer nPush, List waveDataDetails, Double lastTime) { + String picPath = null; + WaveDataDetail waveDataDetail = waveDataDetails.get(0); + + // 从instantData提取null边界x值,作为共享参考(与Shun图保持一致) + List sharedNullBoundaries = LineGenerator.extractNullBoundaryXValues(waveDataDetail.getInstantData().getAValue()); + + String firstPic = drawPicUtil.drawWavePic(getTitle(waveDataDetail.getChannelName()), waveDataDetail.getRmsData().getAValue(), + waveDataDetail.getRmsData().getBValue(), waveDataDetail.getRmsData().getCValue(), + waveDataDetail.getUnit(), waveDataDetail.getRmsData().getMax(), waveDataDetail.getRmsData().getMin(), + waveDataDetail.getA(), waveDataDetail.getB(), waveDataDetail.getC(), + waveDataDetail.getColors(), waveDataDetail.getIsOpen(), lastTime, sharedNullBoundaries,nPush + ); + String secondPic; + if (waveDataDetails.size() == 1) { + if (firstPic.contains(PicCommonData.PNG_PREFIX)) { + firstPic = firstPic.replace(PicCommonData.PNG_PREFIX, ""); + } + byte[] bytes = Base64.getDecoder().decode(firstPic); + picPath = fileStorageUtil.uploadStream(new ByteArrayInputStream(bytes), OssPath.EVENT_WAVE_PIC, FileUtil.generateFileName("png")); + } else if (waveDataDetails.size() == 2) { + waveDataDetail = waveDataDetails.get(1); + secondPic = drawPicUtil.drawWavePic(getTitle(waveDataDetail.getChannelName()), waveDataDetail.getRmsData().getAValue(), + waveDataDetail.getRmsData().getBValue(), waveDataDetail.getRmsData().getCValue(), + waveDataDetail.getUnit(), waveDataDetail.getRmsData().getMax(), waveDataDetail.getRmsData().getMin(), + waveDataDetail.getA(), waveDataDetail.getB(), waveDataDetail.getC(), + waveDataDetail.getColors(), waveDataDetail.getIsOpen(), lastTime, sharedNullBoundaries,nPush + ); + picPath = composeImage(firstPic, secondPic); + } + return picPath; + } + /*** * 合成图片 * @author hongawen diff --git a/pqs-common/common-event/src/main/java/com/njcn/event/file/utils/WaveUtil.java b/pqs-common/common-event/src/main/java/com/njcn/event/file/utils/WaveUtil.java index 218418d09..833771fad 100644 --- a/pqs-common/common-event/src/main/java/com/njcn/event/file/utils/WaveUtil.java +++ b/pqs-common/common-event/src/main/java/com/njcn/event/file/utils/WaveUtil.java @@ -7,6 +7,7 @@ import com.njcn.event.file.pojo.dto.WaveDataDTO; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * @author hongawen @@ -80,28 +81,49 @@ public class WaveUtil { //根据相别来确认标题的名称 for (int m = 0; m < iPhase; m++) { if (waveTitle.get(iPhase * i + m + 1).substring(1).contains("A")) { - float tmpShunFirstA = sunData.get(j).get(iPhase * i + m + 1) * xishu; - shunFirstA = tmpShunFirstA; - sAValue.add(new ArrayList() {{ - add(x); - add(tmpShunFirstA); - }}); + if (Objects.isNull(sunData.get(j).get(iPhase * i + m + 1))) { + sAValue.add(new ArrayList() {{ + add(x); + add(null); + }}); + } else { + float tmpShunFirstA = sunData.get(j).get(iPhase * i + m + 1) * xishu; + shunFirstA = tmpShunFirstA; + sAValue.add(new ArrayList() {{ + add(x); + add(tmpShunFirstA); + }}); + } } if (waveTitle.get(iPhase * i + m + 1).substring(1).contains("B")) { - float tmpShunFirstB = sunData.get(j).get(iPhase * i + m + 1) * xishu; - shunFirstB = tmpShunFirstB; - sBValue.add(new ArrayList() {{ - add(x); - add(tmpShunFirstB); - }}); + if (Objects.isNull(sunData.get(j).get(iPhase * i + m + 1))) { + sBValue.add(new ArrayList() {{ + add(x); + add(null); + }}); + } else { + float tmpShunFirstB = sunData.get(j).get(iPhase * i + m + 1) * xishu; + shunFirstB = tmpShunFirstB; + sBValue.add(new ArrayList() {{ + add(x); + add(tmpShunFirstB); + }}); + } } if (waveTitle.get(iPhase * i + m + 1).substring(1).contains("C")) { - float tmpShunFirstC = sunData.get(j).get(iPhase * i + m + 1) * xishu; - shunFirstC = tmpShunFirstC; - sCValue.add(new ArrayList() {{ - add(x); - add(tmpShunFirstC); - }}); + if (Objects.isNull(sunData.get(j).get(iPhase * i + m + 1))) { + sCValue.add(new ArrayList() {{ + add(x); + add(null); + }}); + } else { + float tmpShunFirstC = sunData.get(j).get(iPhase * i + m + 1) * xishu; + shunFirstC = tmpShunFirstC; + sCValue.add(new ArrayList() {{ + add(x); + add(tmpShunFirstC); + }}); + } } } sfMax = getMax(sfMax, shunFirstA, shunFirstB, shunFirstC); @@ -124,28 +146,50 @@ public class WaveUtil { //根据相别来确认标题的名称 for (int m = 0; m < iPhase; m++) { if (waveTitle.get(iPhase * i + m + 1).substring(1).contains("A")) { - float tmpRmsFirstA = rmsData.get(k).get(iPhase * i + m + 1) * xishu; - rmsFirstA = tmpRmsFirstA; - rAValue.add(new ArrayList() {{ - add(x); - add(tmpRmsFirstA); - }}); + if (Objects.isNull(rmsData.get(k).get(iPhase * i + m + 1))) { + rAValue.add(new ArrayList() {{ + add(x); + add(null); + }}); + } else { + float tmpRmsFirstA = rmsData.get(k).get(iPhase * i + m + 1) * xishu; + rmsFirstA = tmpRmsFirstA; + rAValue.add(new ArrayList() {{ + add(x); + add(tmpRmsFirstA); + }}); + } } if (waveTitle.get(iPhase * i + m + 1).substring(1).contains("B")) { - float tmpRmsFirstB = rmsData.get(k).get(iPhase * i + m + 1) * xishu; - rmsFirstB = tmpRmsFirstB; - rBValue.add(new ArrayList() {{ - add(x); - add(tmpRmsFirstB); - }}); + if (Objects.isNull(rmsData.get(k).get(iPhase * i + m + 1))) { + rBValue.add(new ArrayList() {{ + add(x); + add(null); + }}); + } else { + float tmpRmsFirstB = rmsData.get(k).get(iPhase * i + m + 1) * xishu; + rmsFirstB = tmpRmsFirstB; + rBValue.add(new ArrayList() {{ + add(x); + add(tmpRmsFirstB); + }}); + } + } if (waveTitle.get(iPhase * i + m + 1).substring(1).contains("C")) { - float tmpRmsFirstC = rmsData.get(k).get(iPhase * i + m + 1) * xishu; - rmsFirstC = tmpRmsFirstC; - rCValue.add(new ArrayList() {{ - add(x); - add(tmpRmsFirstC); - }}); + if (Objects.isNull(rmsData.get(k).get(iPhase * i + m + 1))) { + rCValue.add(new ArrayList() {{ + add(x); + add(null); + }}); + } else { + float tmpRmsFirstC = rmsData.get(k).get(iPhase * i + m + 1) * xishu; + rmsFirstC = tmpRmsFirstC; + rCValue.add(new ArrayList() {{ + add(x); + add(tmpRmsFirstC); + }}); + } } } rfMax = getMax(sfMax, rmsFirstA, rmsFirstB, rmsFirstC); diff --git a/pqs-common/common-web/src/main/java/com/njcn/web/utils/RestTemplateUtil.java b/pqs-common/common-web/src/main/java/com/njcn/web/utils/RestTemplateUtil.java index ed3b7f868..91d8b1e62 100644 --- a/pqs-common/common-web/src/main/java/com/njcn/web/utils/RestTemplateUtil.java +++ b/pqs-common/common-web/src/main/java/com/njcn/web/utils/RestTemplateUtil.java @@ -1,5 +1,8 @@ package com.njcn.web.utils; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.*; @@ -149,7 +152,8 @@ public class RestTemplateUtil { * @return ResponseEntity 响应对象封装类 */ public static ResponseEntity post(String url, Object requestBody, Class responseType) { - return restTemplate.postForEntity(url, requestBody, responseType); + Object actualBody = convertHutoolJsonToStandard(requestBody); + return restTemplate.postForEntity(url, actualBody, responseType); } /** @@ -162,7 +166,8 @@ public class RestTemplateUtil { * @return ResponseEntity 响应对象封装类 */ public static ResponseEntity post(String url, Object requestBody, Class responseType, Object... uriVariables) { - return restTemplate.postForEntity(url, requestBody, responseType, uriVariables); + Object actualBody = convertHutoolJsonToStandard(requestBody); + return restTemplate.postForEntity(url, actualBody, responseType, uriVariables); } /** @@ -175,7 +180,21 @@ public class RestTemplateUtil { * @return ResponseEntity 响应对象封装类 */ public static ResponseEntity post(String url, Object requestBody, Class responseType, Map uriVariables) { - return restTemplate.postForEntity(url, requestBody, responseType, uriVariables); + Object actualBody = convertHutoolJsonToStandard(requestBody); + return restTemplate.postForEntity(url, actualBody, responseType, uriVariables); + } + + private static Object convertHutoolJsonToStandard(Object requestBody) { + if (requestBody instanceof JSONObject || requestBody instanceof JSONArray) { + try { + com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); + return objectMapper.readValue(JSONUtil.toJsonStr(requestBody), Object.class); + } catch (Exception e) { + log.warn("Hutool JSON转标准结构失败,尝试直接使用字符串", e); + return requestBody; + } + } + return requestBody; } /**