package com.ruoyi.report.service.impl; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.date.DateUtil; import com.alibaba.excel.EasyExcel; import com.ruoyi.framework.exception.ErrorException; import com.ruoyi.report.dto.NormalDistributionDto; import com.ruoyi.report.mapper.NormalDistributionMapper; import com.ruoyi.report.service.NormalDistributionService; import com.ruoyi.report.vo.NormalDistributionVo; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.*; import java.util.stream.Collectors; /** * 正态分布图服务实现 */ @Service @AllArgsConstructor public class NormalDistributionServiceImpl implements NormalDistributionService { private NormalDistributionMapper normalDistributionMapper; @Override public NormalDistributionVo analyze(NormalDistributionDto dto) { // 查询数据 List> itemData = normalDistributionMapper.getItemData(dto); if (CollectionUtil.isEmpty(itemData)) { return null; } if (itemData.size() < 10) { throw new ErrorException("数据量不足,至少需要10个数据点"); } // 获取数值列表 List values = itemData.stream() .map(m -> new BigDecimal((String) m.get("lastValue"))) .sorted() .collect(Collectors.toList()); // 计算统计量 BigDecimal min = values.get(0); BigDecimal max = values.get(values.size() - 1); BigDecimal sum = values.stream().reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal mean = sum.divide(new BigDecimal(values.size()), 10, RoundingMode.HALF_UP); // 计算标准差 BigDecimal variance = BigDecimal.ZERO; for (BigDecimal v : values) { BigDecimal diff = v.subtract(mean); variance = variance.add(diff.multiply(diff)); } variance = variance.divide(new BigDecimal(values.size() - 1), 10, RoundingMode.HALF_UP); BigDecimal stdDev = sqrt(variance).setScale(6, RoundingMode.HALF_UP); // 直方图分组 int binCount = dto.getBinCount() != null ? dto.getBinCount() : 10; BigDecimal range = max.subtract(min); BigDecimal binWidth = range.divide(new BigDecimal(binCount), 10, RoundingMode.HALF_UP); // 计算分组边界 List binEdges = new ArrayList<>(); BigDecimal edge = min; for (int i = 0; i <= binCount; i++) { binEdges.add(edge.setScale(4, RoundingMode.HALF_UP)); edge = edge.add(binWidth); } // 计算频数 List frequencies = new ArrayList<>(); for (int i = 0; i < binCount; i++) { BigDecimal lower = binEdges.get(i); BigDecimal upper = binEdges.get(i + 1); int count = 0; for (BigDecimal v : values) { if (v.compareTo(lower) >= 0 && (i == binCount - 1 ? v.compareTo(upper) <= 0 : v.compareTo(upper) < 0)) { count++; } } frequencies.add(count); } // 正态分布曲线 List normalX = new ArrayList<>(); List normalY = new ArrayList<>(); // 生成曲线点 BigDecimal step = range.divide(new BigDecimal(100), 10, RoundingMode.HALF_UP); BigDecimal x = min; for (int i = 0; i <= 100; i++) { normalX.add(x.setScale(4, RoundingMode.HALF_UP)); // 正态分布公式: f(x) = (1/(σ√(2π))) * e^(-(x-μ)^2/(2σ^2)) BigDecimal exponent = x.subtract(mean).pow(2) .divide(stdDev.pow(2).multiply(new BigDecimal(2)), 10, RoundingMode.HALF_UP); BigDecimal expValue = BigDecimal.valueOf(Math.exp(-exponent.doubleValue())); BigDecimal coefficient = BigDecimal.ONE.divide( stdDev.multiply(BigDecimal.valueOf(Math.sqrt(2 * Math.PI))), 10, RoundingMode.HALF_UP); BigDecimal y = coefficient.multiply(expValue) .multiply(new BigDecimal(values.size())) .multiply(binWidth) .setScale(4, RoundingMode.HALF_UP); normalY.add(y); x = x.add(step); } // 构建结果 NormalDistributionVo vo = new NormalDistributionVo(); vo.setBinEdges(binEdges); vo.setFrequencies(frequencies); vo.setNormalX(normalX); vo.setNormalY(normalY); vo.setMean(mean.setScale(4, RoundingMode.HALF_UP)); vo.setStdDev(stdDev.setScale(4, RoundingMode.HALF_UP)); vo.setMin(min.setScale(4, RoundingMode.HALF_UP)); vo.setMax(max.setScale(4, RoundingMode.HALF_UP)); vo.setSampleSize(values.size()); return vo; } @Override public void export(NormalDistributionDto dto, HttpServletResponse response) { List> itemData = normalDistributionMapper.getItemData(dto); if (CollectionUtil.isEmpty(itemData)) { return; } try { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String fileName = "正态分布分析数据_" + DateUtil.format(new Date(), "yyyyMMddHHmmss"); response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); // 构建导出数据 List> exportData = new ArrayList<>(); for (Map item : itemData) { Map row = new HashMap<>(); row.put("sampleCode", item.get("sampleCode")); row.put("sampleName", item.get("sampleName")); row.put("itemName", item.get("itemName")); row.put("lastValue", item.get("lastValue")); row.put("insTime", item.get("insTime")); exportData.add(row); } EasyExcel.write(response.getOutputStream()) .sheet("正态分布分析数据") .doWrite(exportData); } catch (IOException e) { e.printStackTrace(); } } @Override public List getItemNames(NormalDistributionDto dto) { return normalDistributionMapper.getItemNames(dto); } @Override public List getSampleNames(NormalDistributionDto dto) { return normalDistributionMapper.getSampleNames(dto); } /** * 平方根计算 */ private BigDecimal sqrt(BigDecimal value) { BigDecimal x = value; BigDecimal tolerance = new BigDecimal("1E-10"); BigDecimal guess = value.divide(BigDecimal.valueOf(2), MathContext.DECIMAL128); while (x.subtract(guess).abs().compareTo(tolerance) > 0) { x = guess; guess = x.add(value.divide(x, MathContext.DECIMAL128)).divide(new BigDecimal("2"), MathContext.DECIMAL128); } return guess; } }