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<Map<String, Object>> itemData = normalDistributionMapper.getItemData(dto);
|
|
if (CollectionUtil.isEmpty(itemData)) {
|
return null;
|
}
|
|
if (itemData.size() < 10) {
|
throw new ErrorException("数据量不足,至少需要10个数据点");
|
}
|
|
// 获取数值列表
|
List<BigDecimal> 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<BigDecimal> 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<Integer> 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<BigDecimal> normalX = new ArrayList<>();
|
List<BigDecimal> 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<Map<String, Object>> 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<Map<String, Object>> exportData = new ArrayList<>();
|
for (Map<String, Object> item : itemData) {
|
Map<String, Object> 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<String> getItemNames(NormalDistributionDto dto) {
|
return normalDistributionMapper.getItemNames(dto);
|
}
|
|
@Override
|
public List<String> 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;
|
}
|
|
}
|