|
|
|
|
@ -0,0 +1,443 @@
|
|
|
|
|
package org.dromara.mes.service.impl;
|
|
|
|
|
|
|
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
|
|
|
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
|
|
|
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
|
|
|
|
import org.dromara.mes.domain.vo.*;
|
|
|
|
|
import org.dromara.mes.mapper.ProdMixTraceReportMapper;
|
|
|
|
|
import org.dromara.mes.service.IProdMixTraceReportService;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
|
|
import java.math.MathContext;
|
|
|
|
|
import java.math.RoundingMode;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.function.Function;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 密炼追溯报表Service业务层处理
|
|
|
|
|
*
|
|
|
|
|
* @author Yinq
|
|
|
|
|
* @date 2026-02-14
|
|
|
|
|
*/
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
@Service
|
|
|
|
|
public class ProdMixTraceReportServiceImpl implements IProdMixTraceReportService {
|
|
|
|
|
|
|
|
|
|
private final ProdMixTraceReportMapper mixTraceReportMapper;
|
|
|
|
|
|
|
|
|
|
/** SPC计算精度 */
|
|
|
|
|
private static final int SCALE = 4;
|
|
|
|
|
private static final RoundingMode RM = RoundingMode.HALF_UP;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Xbar-R 图常量表(子组大小 n -> A2, D3, D4)
|
|
|
|
|
* 仅列出常用子组大小 2~10
|
|
|
|
|
*/
|
|
|
|
|
private static final double[][] XBAR_R_CONSTANTS = {
|
|
|
|
|
// n=2: A2=1.880, D3=0, D4=3.267
|
|
|
|
|
{1.880, 0.0, 3.267},
|
|
|
|
|
// n=3: A2=1.023, D3=0, D4=2.575
|
|
|
|
|
{1.023, 0.0, 2.575},
|
|
|
|
|
// n=4: A2=0.729, D3=0, D4=2.282
|
|
|
|
|
{0.729, 0.0, 2.282},
|
|
|
|
|
// n=5: A2=0.577, D3=0, D4=2.115
|
|
|
|
|
{0.577, 0.0, 2.115},
|
|
|
|
|
// n=6: A2=0.483, D3=0, D4=2.004
|
|
|
|
|
{0.483, 0.0, 2.004},
|
|
|
|
|
// n=7: A2=0.419, D3=0.076, D4=1.924
|
|
|
|
|
{0.419, 0.076, 1.924},
|
|
|
|
|
// n=8: A2=0.373, D3=0.136, D4=1.864
|
|
|
|
|
{0.373, 0.136, 1.864},
|
|
|
|
|
// n=9: A2=0.337, D3=0.184, D4=1.816
|
|
|
|
|
{0.337, 0.184, 1.816},
|
|
|
|
|
// n=10: A2=0.308, D3=0.223, D4=1.777
|
|
|
|
|
{0.308, 0.223, 1.777}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ==================== 追溯列表(图5) ====================
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TableDataInfo<MixTraceListVo> queryTraceList(Map<String, Object> params, PageQuery pageQuery) {
|
|
|
|
|
Page<MixTraceListVo> page = mixTraceReportMapper.selectTraceList(params, pageQuery.build());
|
|
|
|
|
return TableDataInfo.build(page);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public List<MixTraceListVo> queryTraceList(Map<String, Object> params) {
|
|
|
|
|
return mixTraceReportMapper.selectTraceList(params);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 追溯详情(图9) ====================
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public MixTraceDetailVo queryTraceDetail(Long recipeId) {
|
|
|
|
|
MixTraceListVo recipeInfo = mixTraceReportMapper.selectTraceRecipeInfo(recipeId);
|
|
|
|
|
if (recipeInfo == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
MixTraceDetailVo detail = new MixTraceDetailVo();
|
|
|
|
|
detail.setRecipeInfo(recipeInfo);
|
|
|
|
|
// 称量明细按 weight_seq 排序
|
|
|
|
|
detail.setWeightList(mixTraceReportMapper.selectWeightListByRecipeId(recipeId));
|
|
|
|
|
// 混炼明细按 mix_id 排序
|
|
|
|
|
detail.setMixingList(mixTraceReportMapper.selectMixingListByRecipeId(recipeId));
|
|
|
|
|
return detail;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== SPC样本(图6) ====================
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public TableDataInfo<MixTraceSpcSampleVo> querySpcSamples(Map<String, Object> params, PageQuery pageQuery) {
|
|
|
|
|
Page<MixTraceSpcSampleVo> page = mixTraceReportMapper.selectSpcSamples(params, pageQuery.build());
|
|
|
|
|
return TableDataInfo.build(page);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== SPC能力分析(图7) ====================
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public MixTraceSpcResultVo calculateSpcCapability(Map<String, Object> params) {
|
|
|
|
|
// 获取分析参数名称,默认分析混炼温度
|
|
|
|
|
String paramName = getParamName(params);
|
|
|
|
|
|
|
|
|
|
// 查询全量样本
|
|
|
|
|
List<MixTraceSpcSampleVo> samples = mixTraceReportMapper.selectSpcSamples(params);
|
|
|
|
|
if (samples == null || samples.isEmpty()) {
|
|
|
|
|
return buildEmptyResult(paramName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 提取指定参数的实测值和设定值
|
|
|
|
|
Function<MixTraceSpcSampleVo, BigDecimal> actualExtractor = getActualExtractor(paramName);
|
|
|
|
|
Function<MixTraceSpcSampleVo, BigDecimal> setExtractor = getSetExtractor(paramName);
|
|
|
|
|
|
|
|
|
|
List<BigDecimal> values = samples.stream()
|
|
|
|
|
.map(actualExtractor)
|
|
|
|
|
.filter(v -> v != null)
|
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
|
|
|
|
if (values.size() < 2) {
|
|
|
|
|
return buildEmptyResult(paramName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 基本统计
|
|
|
|
|
MixTraceSpcResultVo result = new MixTraceSpcResultVo();
|
|
|
|
|
result.setParamName(paramName);
|
|
|
|
|
result.setParamLabel(getParamLabel(paramName));
|
|
|
|
|
result.setSampleCount(values.size());
|
|
|
|
|
|
|
|
|
|
BigDecimal mean = calcMean(values);
|
|
|
|
|
BigDecimal sigma = calcStdDev(values, mean);
|
|
|
|
|
BigDecimal min = values.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
|
|
|
|
|
BigDecimal max = values.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
|
|
|
|
|
|
|
|
|
|
result.setMean(mean);
|
|
|
|
|
result.setSigma(sigma);
|
|
|
|
|
result.setMinValue(min);
|
|
|
|
|
result.setMaxValue(max);
|
|
|
|
|
|
|
|
|
|
// 规格限:从设定值中取平均作为Target,USL/LSL可按 Target ± 容差 或取最大/最小设定值
|
|
|
|
|
List<BigDecimal> setValues = samples.stream()
|
|
|
|
|
.map(setExtractor)
|
|
|
|
|
.filter(v -> v != null)
|
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
|
|
|
|
if (!setValues.isEmpty()) {
|
|
|
|
|
BigDecimal target = calcMean(setValues);
|
|
|
|
|
result.setTarget(target);
|
|
|
|
|
BigDecimal setMin = setValues.stream().min(BigDecimal::compareTo).orElse(target);
|
|
|
|
|
BigDecimal setMax = setValues.stream().max(BigDecimal::compareTo).orElse(target);
|
|
|
|
|
// 若设定值有范围差异,使用最大/最小作为USL/LSL;否则使用 target ± 10%
|
|
|
|
|
if (setMax.compareTo(setMin) > 0) {
|
|
|
|
|
result.setUsl(setMax);
|
|
|
|
|
result.setLsl(setMin);
|
|
|
|
|
} else {
|
|
|
|
|
BigDecimal tolerance = target.abs().multiply(new BigDecimal("0.10"));
|
|
|
|
|
result.setUsl(target.add(tolerance));
|
|
|
|
|
result.setLsl(target.subtract(tolerance));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 能力指数
|
|
|
|
|
if (sigma.compareTo(BigDecimal.ZERO) > 0 && result.getUsl() != null && result.getLsl() != null) {
|
|
|
|
|
BigDecimal sixSigma = sigma.multiply(new BigDecimal("6"));
|
|
|
|
|
BigDecimal threeSigma = sigma.multiply(new BigDecimal("3"));
|
|
|
|
|
BigDecimal usl = result.getUsl();
|
|
|
|
|
BigDecimal lsl = result.getLsl();
|
|
|
|
|
|
|
|
|
|
result.setCp(usl.subtract(lsl).divide(sixSigma, SCALE, RM));
|
|
|
|
|
result.setCpu(usl.subtract(mean).divide(threeSigma, SCALE, RM));
|
|
|
|
|
result.setCpl(mean.subtract(lsl).divide(threeSigma, SCALE, RM));
|
|
|
|
|
result.setCpk(result.getCpu().min(result.getCpl()));
|
|
|
|
|
|
|
|
|
|
// Pp/Ppk 使用总体标准差(此处简化为与Cp/Cpk相同口径)
|
|
|
|
|
result.setPp(result.getCp());
|
|
|
|
|
result.setPpk(result.getCpk());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 直方图数据
|
|
|
|
|
buildHistogram(result, values);
|
|
|
|
|
|
|
|
|
|
// 运行图数据
|
|
|
|
|
result.setSampleValues(values);
|
|
|
|
|
List<String> labels = new ArrayList<>();
|
|
|
|
|
for (int i = 0; i < samples.size(); i++) {
|
|
|
|
|
MixTraceSpcSampleVo s = samples.get(i);
|
|
|
|
|
BigDecimal v = actualExtractor.apply(s);
|
|
|
|
|
if (v != null) {
|
|
|
|
|
labels.add(s.getRecipeCode() != null ? s.getRecipeCode() : String.valueOf(i + 1));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
result.setSampleLabels(labels);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== SPC运行图(图8) ====================
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public MixTraceSpcResultVo calculateSpcRunChart(Map<String, Object> params) {
|
|
|
|
|
// 运行图与能力分析共用数据,直接复用
|
|
|
|
|
return calculateSpcCapability(params);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== SPC Xbar-R图(图10) ====================
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public MixTraceSpcResultVo calculateSpcXbarR(Map<String, Object> params) {
|
|
|
|
|
String paramName = getParamName(params);
|
|
|
|
|
|
|
|
|
|
List<MixTraceSpcSampleVo> samples = mixTraceReportMapper.selectSpcSamples(params);
|
|
|
|
|
if (samples == null || samples.isEmpty()) {
|
|
|
|
|
return buildEmptyResult(paramName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Function<MixTraceSpcSampleVo, BigDecimal> actualExtractor = getActualExtractor(paramName);
|
|
|
|
|
|
|
|
|
|
List<BigDecimal> values = samples.stream()
|
|
|
|
|
.map(actualExtractor)
|
|
|
|
|
.filter(v -> v != null)
|
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
|
|
|
|
|
// 子组大小,默认5,可通过参数配置
|
|
|
|
|
int subgroupSize = 5;
|
|
|
|
|
if (params.containsKey("subgroupSize")) {
|
|
|
|
|
try {
|
|
|
|
|
subgroupSize = Integer.parseInt(String.valueOf(params.get("subgroupSize")));
|
|
|
|
|
} catch (NumberFormatException ignored) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 子组大小范围限制 2~10
|
|
|
|
|
subgroupSize = Math.max(2, Math.min(10, subgroupSize));
|
|
|
|
|
|
|
|
|
|
if (values.size() < subgroupSize * 2) {
|
|
|
|
|
// 样本不足,至少需要2个子组
|
|
|
|
|
MixTraceSpcResultVo result = buildEmptyResult(paramName);
|
|
|
|
|
result.setSampleCount(values.size());
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 按子组分组
|
|
|
|
|
int groupCount = values.size() / subgroupSize;
|
|
|
|
|
List<BigDecimal> xbarValues = new ArrayList<>();
|
|
|
|
|
List<BigDecimal> rValues = new ArrayList<>();
|
|
|
|
|
List<String> subgroupLabels = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < groupCount; i++) {
|
|
|
|
|
int start = i * subgroupSize;
|
|
|
|
|
List<BigDecimal> group = values.subList(start, start + subgroupSize);
|
|
|
|
|
BigDecimal groupMean = calcMean(group);
|
|
|
|
|
BigDecimal groupMax = group.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
|
|
|
|
|
BigDecimal groupMin = group.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
|
|
|
|
|
BigDecimal groupRange = groupMax.subtract(groupMin);
|
|
|
|
|
|
|
|
|
|
xbarValues.add(groupMean);
|
|
|
|
|
rValues.add(groupRange);
|
|
|
|
|
subgroupLabels.add("G" + (i + 1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 总均值与平均极差
|
|
|
|
|
BigDecimal xbarbar = calcMean(xbarValues);
|
|
|
|
|
BigDecimal rbar = calcMean(rValues);
|
|
|
|
|
|
|
|
|
|
// 查找常量 A2, D3, D4
|
|
|
|
|
int constIndex = subgroupSize - 2; // 数组从 n=2 开始
|
|
|
|
|
double a2 = XBAR_R_CONSTANTS[constIndex][0];
|
|
|
|
|
double d3 = XBAR_R_CONSTANTS[constIndex][1];
|
|
|
|
|
double d4 = XBAR_R_CONSTANTS[constIndex][2];
|
|
|
|
|
|
|
|
|
|
BigDecimal A2 = BigDecimal.valueOf(a2);
|
|
|
|
|
BigDecimal D3 = BigDecimal.valueOf(d3);
|
|
|
|
|
BigDecimal D4 = BigDecimal.valueOf(d4);
|
|
|
|
|
|
|
|
|
|
// 构建结果
|
|
|
|
|
MixTraceSpcResultVo result = new MixTraceSpcResultVo();
|
|
|
|
|
result.setParamName(paramName);
|
|
|
|
|
result.setParamLabel(getParamLabel(paramName));
|
|
|
|
|
result.setSampleCount(values.size());
|
|
|
|
|
result.setSubgroupSize(subgroupSize);
|
|
|
|
|
result.setMean(calcMean(values));
|
|
|
|
|
result.setSigma(calcStdDev(values, result.getMean()));
|
|
|
|
|
|
|
|
|
|
result.setXbarValues(xbarValues);
|
|
|
|
|
result.setRValues(rValues);
|
|
|
|
|
result.setSubgroupLabels(subgroupLabels);
|
|
|
|
|
result.setXbarbar(xbarbar);
|
|
|
|
|
result.setRbar(rbar);
|
|
|
|
|
|
|
|
|
|
// Xbar 控制限
|
|
|
|
|
result.setUclX(xbarbar.add(A2.multiply(rbar)).setScale(SCALE, RM));
|
|
|
|
|
result.setClX(xbarbar.setScale(SCALE, RM));
|
|
|
|
|
result.setLclX(xbarbar.subtract(A2.multiply(rbar)).setScale(SCALE, RM));
|
|
|
|
|
|
|
|
|
|
// R 控制限
|
|
|
|
|
result.setUclR(D4.multiply(rbar).setScale(SCALE, RM));
|
|
|
|
|
result.setClR(rbar.setScale(SCALE, RM));
|
|
|
|
|
result.setLclR(D3.multiply(rbar).setScale(SCALE, RM));
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 工具方法 ====================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取分析参数名称,默认 mixingTemp
|
|
|
|
|
*/
|
|
|
|
|
private String getParamName(Map<String, Object> params) {
|
|
|
|
|
Object paramNameObj = params.get("paramName");
|
|
|
|
|
return (paramNameObj != null && !String.valueOf(paramNameObj).isEmpty())
|
|
|
|
|
? String.valueOf(paramNameObj) : "mixingTemp";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 参数名称 -> 中文标签映射
|
|
|
|
|
*/
|
|
|
|
|
private String getParamLabel(String paramName) {
|
|
|
|
|
switch (paramName) {
|
|
|
|
|
case "mixingTemp": return "混炼温度";
|
|
|
|
|
case "mixingTime": return "混炼时间";
|
|
|
|
|
case "mixingEnergy": return "混炼能量";
|
|
|
|
|
case "mixingPower": return "混炼功率";
|
|
|
|
|
case "mixingPress": return "混炼压力";
|
|
|
|
|
case "mixingSpeed": return "混炼转速";
|
|
|
|
|
default: return paramName;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据参数名获取实测值提取器
|
|
|
|
|
*/
|
|
|
|
|
private Function<MixTraceSpcSampleVo, BigDecimal> getActualExtractor(String paramName) {
|
|
|
|
|
switch (paramName) {
|
|
|
|
|
case "mixingTemp": return MixTraceSpcSampleVo::getMixingTemp;
|
|
|
|
|
case "mixingTime": return s -> s.getMixingTime() != null ? BigDecimal.valueOf(s.getMixingTime()) : null;
|
|
|
|
|
case "mixingEnergy": return MixTraceSpcSampleVo::getMixingEnergy;
|
|
|
|
|
case "mixingPower": return MixTraceSpcSampleVo::getMixingPower;
|
|
|
|
|
case "mixingPress": return MixTraceSpcSampleVo::getMixingPress;
|
|
|
|
|
case "mixingSpeed": return s -> s.getMixingSpeed() != null ? BigDecimal.valueOf(s.getMixingSpeed()) : null;
|
|
|
|
|
default: return MixTraceSpcSampleVo::getMixingTemp;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据参数名获取设定值提取器
|
|
|
|
|
*/
|
|
|
|
|
private Function<MixTraceSpcSampleVo, BigDecimal> getSetExtractor(String paramName) {
|
|
|
|
|
switch (paramName) {
|
|
|
|
|
case "mixingTemp": return s -> s.getSetTemp() != null ? BigDecimal.valueOf(s.getSetTemp()) : null;
|
|
|
|
|
case "mixingTime": return s -> s.getSetTime() != null ? BigDecimal.valueOf(s.getSetTime()) : null;
|
|
|
|
|
case "mixingEnergy": return MixTraceSpcSampleVo::getSetEnergy;
|
|
|
|
|
case "mixingPower": return MixTraceSpcSampleVo::getSetPower;
|
|
|
|
|
case "mixingPress": return MixTraceSpcSampleVo::getSetPres;
|
|
|
|
|
case "mixingSpeed": return s -> s.getSetRota() != null ? BigDecimal.valueOf(s.getSetRota()) : null;
|
|
|
|
|
default: return s -> s.getSetTemp() != null ? BigDecimal.valueOf(s.getSetTemp()) : null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 计算均值
|
|
|
|
|
*/
|
|
|
|
|
private BigDecimal calcMean(List<BigDecimal> values) {
|
|
|
|
|
if (values == null || values.isEmpty()) {
|
|
|
|
|
return BigDecimal.ZERO;
|
|
|
|
|
}
|
|
|
|
|
BigDecimal sum = values.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
|
|
return sum.divide(BigDecimal.valueOf(values.size()), SCALE, RM);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 计算样本标准差
|
|
|
|
|
*/
|
|
|
|
|
private BigDecimal calcStdDev(List<BigDecimal> values, BigDecimal mean) {
|
|
|
|
|
if (values == null || values.size() < 2) {
|
|
|
|
|
return BigDecimal.ZERO;
|
|
|
|
|
}
|
|
|
|
|
BigDecimal sumSq = values.stream()
|
|
|
|
|
.map(v -> v.subtract(mean).pow(2))
|
|
|
|
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
|
|
// 样本标准差使用 n-1
|
|
|
|
|
BigDecimal variance = sumSq.divide(BigDecimal.valueOf(values.size() - 1), SCALE * 2, RM);
|
|
|
|
|
return BigDecimal.valueOf(Math.sqrt(variance.doubleValue())).setScale(SCALE, RM);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构建空结果
|
|
|
|
|
*/
|
|
|
|
|
private MixTraceSpcResultVo buildEmptyResult(String paramName) {
|
|
|
|
|
MixTraceSpcResultVo result = new MixTraceSpcResultVo();
|
|
|
|
|
result.setParamName(paramName);
|
|
|
|
|
result.setParamLabel(getParamLabel(paramName));
|
|
|
|
|
result.setSampleCount(0);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构建直方图数据(等宽分箱,默认10个区间)
|
|
|
|
|
*/
|
|
|
|
|
private void buildHistogram(MixTraceSpcResultVo result, List<BigDecimal> values) {
|
|
|
|
|
if (values == null || values.size() < 2) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
int binCount = 10;
|
|
|
|
|
BigDecimal min = result.getMinValue();
|
|
|
|
|
BigDecimal max = result.getMaxValue();
|
|
|
|
|
BigDecimal range = max.subtract(min);
|
|
|
|
|
|
|
|
|
|
if (range.compareTo(BigDecimal.ZERO) == 0) {
|
|
|
|
|
// 所有值相同
|
|
|
|
|
List<Integer> counts = new ArrayList<>();
|
|
|
|
|
List<String> bins = new ArrayList<>();
|
|
|
|
|
counts.add(values.size());
|
|
|
|
|
bins.add(min.setScale(2, RM).toPlainString());
|
|
|
|
|
result.setHistogramCounts(counts);
|
|
|
|
|
result.setHistogramBins(bins);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BigDecimal binWidth = range.divide(BigDecimal.valueOf(binCount), SCALE + 2, RM);
|
|
|
|
|
List<Integer> counts = new ArrayList<>();
|
|
|
|
|
List<String> bins = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < binCount; i++) {
|
|
|
|
|
BigDecimal lower = min.add(binWidth.multiply(BigDecimal.valueOf(i)));
|
|
|
|
|
BigDecimal upper = min.add(binWidth.multiply(BigDecimal.valueOf(i + 1)));
|
|
|
|
|
int finalI = i;
|
|
|
|
|
long count = values.stream()
|
|
|
|
|
.filter(v -> {
|
|
|
|
|
if (finalI == binCount - 1) {
|
|
|
|
|
return v.compareTo(lower) >= 0 && v.compareTo(upper) <= 0;
|
|
|
|
|
}
|
|
|
|
|
return v.compareTo(lower) >= 0 && v.compareTo(upper) < 0;
|
|
|
|
|
})
|
|
|
|
|
.count();
|
|
|
|
|
counts.add((int) count);
|
|
|
|
|
bins.add(lower.setScale(2, RM).toPlainString() + "~" + upper.setScale(2, RM).toPlainString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.setHistogramCounts(counts);
|
|
|
|
|
result.setHistogramBins(bins);
|
|
|
|
|
}
|
|
|
|
|
}
|