添加模板生成工具类

This commit is contained in:
duwenyuan 2026-01-07 20:42:29 +08:00
parent 64d4900aa3
commit 0786fb99a8
2 changed files with 546 additions and 0 deletions

View File

@ -0,0 +1,367 @@
package org.jeecg.common.util;
import lombok.Data;
import org.jeecg.modules.entity.vo.NuclideAnalysisInfo;
import org.jeecg.modules.entity.vo.NuclideRatioResult;
import java.util.*;
import java.util.stream.Collectors;
public class DistributionAnalysisToolkit {
/**
* 区间统计结果
*/
@Data
public static class IntervalStat {
private final String interval;
private final int count;
private final List<Double> values;
private final Map<String, Integer> levelDistribution;
public IntervalStat(String interval, int count, List<Double> values, Map<String, Integer> levelDistribution) {
this.interval = interval;
this.count = count;
this.values = values;
this.levelDistribution = levelDistribution;
}
public IntervalStat(String interval, List<NuclideAnalysisInfo> nuclideData) {
values = new ArrayList<>();
levelDistribution = new HashMap<>();
this.interval = interval;
this.count = nuclideData.size();
for (NuclideAnalysisInfo nuclideAnalysis : nuclideData) {
values.add(nuclideAnalysis.getConc());
levelDistribution.merge(nuclideAnalysis.getCategory(), 1, Integer::sum);
}
}
public String getInterval() {
return interval;
}
public int getCount() {
return count;
}
public List<Double> getValues() {
return values;
}
}
/**
* KDE曲线点
*/
public static class KDEPoint {
private final double x;
private final double density;
public KDEPoint(double x, double density) {
this.x = x;
this.density = density;
}
public double getX() {
return x;
}
public double getDensity() {
return density;
}
}
/**
* CDF曲线点
*/
public static class CDFPoint {
private final double value;
private final double cumulativeProb;
public CDFPoint(double value, double cumulativeProb) {
this.value = value;
this.cumulativeProb = cumulativeProb;
}
public double getValue() {
return value;
}
public double getCumulativeProb() {
return cumulativeProb;
}
}
/**
* 数据区间统计
*
* @param data 原始数据
* @param start 起始值
* @param step 区间宽度
* @return 区间统计结果列表
*/
public static List<IntervalStat> calculateIntervalStats(List<NuclideAnalysisInfo> data, double start, double step) {
if (data == null || data.isEmpty()) {
throw new IllegalArgumentException("数据不能为空");
}
try {
// 计算结束边界
double max = data.stream()
.mapToDouble(NuclideAnalysisInfo::getConc)
.max().orElse(0);
double end = Math.ceil(max / step) * step + step;
// 初始化区间映射
Map<String, List<NuclideAnalysisInfo>> intervalMap = new TreeMap<>();
for (double lower = start; lower < end; lower += step) {
double upper = lower + step;
String key = String.format("[%.1f, %.1f)", lower, upper);
intervalMap.put(key, new ArrayList<>());
}
// 分配数据到区间
for (NuclideAnalysisInfo nuclide : data) {
try {
double value = nuclide.getConc();
double lower = Math.floor(value / step) * step;
String key = String.format("[%.1f, %.1f)", lower, lower + step);
intervalMap.get(key).add(nuclide);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
// 转换为统计结果对象
List<IntervalStat> stats = new ArrayList<>();
for (Map.Entry<String, List<NuclideAnalysisInfo>> entry : intervalMap.entrySet()) {
stats.add(new IntervalStat(entry.getKey(), entry.getValue()));
}
return stats;
} catch (Exception e) {
System.out.println(e.getMessage());
}
return new ArrayList<>();
}
public static List<IntervalStat> calculateIntervalStats(List<Double> data, double start, double step,String ratio) {
if (data == null || data.isEmpty()) {
throw new IllegalArgumentException("数据不能为空");
}
try {
// 计算结束边界
double max =data.stream().mapToDouble(Double::doubleValue).max().orElse(0) ;
double end = Math.ceil(max / step) * step + step;
// 初始化区间映射
Map<String, List<Double>> intervalMap = new TreeMap<>();
for (double lower = start; lower < end; lower += step) {
double upper = lower + step;
String key = String.format("[%.1f, %.1f)", lower, upper);
intervalMap.put(key, new ArrayList<>());
}
// 分配数据到区间
for (double value : data) {
try {
double lower = Math.floor(value / step) * step;
String key = String.format("[%.1f, %.1f)", lower, lower + step);
intervalMap.get(key).add(value);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
// 转换为统计结果对象
List<IntervalStat> stats = new ArrayList<>();
for (Map.Entry<String, List<Double>> entry : intervalMap.entrySet()) {
stats.add(new IntervalStat(entry.getKey(), entry.getValue().size(),entry.getValue(),new HashMap<>()));
}
return stats;
} catch (Exception e) {
System.out.println(e.getMessage());
}
return new ArrayList<>();
}
/**
* 计算95%累积线
*
* @param data 原始数据
* @return 95%累积线值
*/
public static double calculate95thPercentile(List<Double> data) {
if (data == null || data.isEmpty()) {
throw new IllegalArgumentException("数据不能为空");
}
// 排序数据
List<Double> sortedData = new ArrayList<>(data);
Collections.sort(sortedData);
int n = sortedData.size();
double position = 0.95 * (n - 1);
int lowerIndex = (int) Math.floor(position);
int upperIndex = (int) Math.ceil(position);
if (lowerIndex == upperIndex) {
return sortedData.get(lowerIndex);
}
// 线性插值
double lowerValue = sortedData.get(lowerIndex);
double upperValue = sortedData.get(upperIndex);
double fraction = position - lowerIndex;
return lowerValue + fraction * (upperValue - lowerValue);
}
/**
* 计算累积分布函数(CDF)
*
* @param data 原始数据
* @return CDF点列表
*/
public static List<CDFPoint> calculateCDF(List<Double> data) {
if (data == null || data.isEmpty()) {
throw new IllegalArgumentException("数据不能为空");
}
// 排序数据
List<Double> sortedData = new ArrayList<>(data);
Collections.sort(sortedData);
// 计算累积分布
List<CDFPoint> cdfPoints = new ArrayList<>();
int n = sortedData.size();
for (int i = 0; i < n; i++) {
double value = sortedData.get(i);
double cumulativeProbability = (i + 1.0) / n;
cdfPoints.add(new CDFPoint(value, cumulativeProbability));
}
return cdfPoints;
}
/**
* 核函数接口
*/
@FunctionalInterface
public interface KernelFunction {
double apply(double u);
}
// 常用核函数实现
public static final KernelFunction GAUSSIAN_KERNEL = u -> Math.exp(-0.5 * u * u) / Math.sqrt(2 * Math.PI);
public static final KernelFunction EPANECHNIKOV_KERNEL = u -> (Math.abs(u) <= 1) ? 0.75 * (1 - u * u) : 0;
public static final KernelFunction TRIANGULAR_KERNEL = u -> (Math.abs(u) <= 1) ? 1 - Math.abs(u) : 0;
/**
* 使用Silverman规则计算最佳带宽
*/
public static double calculateBandwidthSilverman(List<Double> data) {
int n = data.size();
if (n <= 1) return 1.0;
// 计算标准差
double mean = data.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
double variance = data.stream()
.mapToDouble(x -> Math.pow(x - mean, 2))
.average()
.orElse(0.0);
double sigma = Math.sqrt(variance);
// 计算四分位距(IQR)
List<Double> sortedData = new ArrayList<>(data);
Collections.sort(sortedData);
double q1 = sortedData.get((int) Math.ceil(0.25 * n - 1));
double q3 = sortedData.get((int) Math.ceil(0.75 * n - 1));
double iqr = q3 - q1;
// Silverman规则
return 0.9 * Math.min(sigma, iqr / 1.34) * Math.pow(n, -0.2);
}
/**
* 核密度估计
*/
public static List<KDEPoint> kernelDensityEstimate(
List<Double> data, KernelFunction kernel, double bandwidth) {
if (data == null || data.isEmpty()) {
throw new IllegalArgumentException("数据不能为空");
}
// 计算数据范围
double min = Collections.min(data);
double max = Collections.max(data);
double range = max - min;
double minX = min - 0.1 * range;
double maxX = max + 0.1 * range;
// 生成评估点
int numPoints = 200;
double step = (maxX - minX) / (numPoints - 1);
List<KDEPoint> kdePoints = new ArrayList<>();
int n = data.size();
for (int i = 0; i < numPoints; i++) {
double x = minX + i * step;
double sum = 0.0;
for (double value : data) {
double u = (x - value) / bandwidth;
sum += kernel.apply(u);
}
double density = sum / (n * bandwidth);
kdePoints.add(new KDEPoint(x, density));
}
return kdePoints;
}
/**
* 自动KDE计算自动选择带宽
*/
public static List<KDEPoint> autoKDE(List<Double> data, KernelFunction kernel) {
double bandwidth = calculateBandwidthSilverman(data);
return kernelDensityEstimate(data, kernel, bandwidth);
}
//获取浓度值集合
public static List<Double> convertConcToDoubleList(List<NuclideAnalysisInfo> nuclideList) {
return nuclideList.stream() // 创建流
.map(NuclideAnalysisInfo::getConc) // 提取conc值
.collect(Collectors.toList()); // 收集为List<Double>
}
public static List<Double> convertRatioToDoubleList(List<NuclideRatioResult> nuclideList) {
return nuclideList.stream() // 创建流
.map(NuclideRatioResult::getRatio) // Ratio
.collect(Collectors.toList()); // 收集为List<Double>
}
//累积和
public static List<Double> cumulativeSum(List<Double> data) {
List<Double> result = new ArrayList<>();
double sum = 0.0;
for (double value : data) {
sum += value;
result.add(sum);
}
return result;
}
}

View File

@ -0,0 +1,179 @@
package org.jeecg.modules.service.impl;
import org.jeecg.modules.service.IGenerateHtmlReport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.List;
import java.util.Map;
@Service
public class HtmlReportGenerator implements IGenerateHtmlReport {
@Autowired
private TemplateEngine templateEngine;
/**
* 构造方法初始化Thymeleaf模板引擎
* 默认从resources/templates/目录加载HTML模板
*/
public void generatorInit() {
ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
templateResolver.setPrefix("/templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCharacterEncoding("UTF-8");
this.templateEngine.setTemplateResolver(templateResolver);
}
/**
* 生成基础HTML报告
* @param data 报告数据键值对形式
* @return 生成的HTML字符串
*/
@Override
public String generateBasicHtmlReport(Map<String, Object> data) {
Context context = new Context();
context.setVariables(data);
return templateEngine.process("report", context);
}
/**
* 生成带自定义样式的HTML报告
* @param data 报告数据键值对形式
* @param style 自定义CSS样式字符串
* @return 生成的HTML字符串
*/
@Override
public String generateStyledHtmlReport(Map<String, Object> data, String style) {
Context context = new Context();
context.setVariables(data);
context.setVariable("customStyle", style);
return templateEngine.process("styled-report", context);
}
/**
* 生成HTML报告并保存到文件
* @param data 报告数据键值对形式
* @param filePath 文件保存路径包含文件名
* @return 保存成功返回true失败返回false
*/
@Override
public boolean generateHtmlReportToFile(Map<String, Object> data, String filePath) {
try {
String html = generateBasicHtmlReport(data);
Files.write(Paths.get(filePath), html.getBytes(StandardCharsets.UTF_8));
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 使用指定模板生成HTML报告
* @param data 报告数据键值对形式
* @param templatePath 模板文件路径相对于classpath
* @return 生成的HTML字符串
*/
@Override
public String generateHtmlReportWithTemplate(Map<String, Object> data, String templatePath) {
ClassLoaderTemplateResolver tempResolver = new ClassLoaderTemplateResolver();
tempResolver.setPrefix("/");
tempResolver.setSuffix("");
tempResolver.setTemplateMode(TemplateMode.HTML);
TemplateEngine tempEngine = new TemplateEngine();
tempEngine.setTemplateResolver(tempResolver);
Context context = new Context();
context.setVariables(data);
return tempEngine.process(templatePath, context);
}
/**
* 生成高级HTML报告可选包含图表和交互功能
* @param data 报告数据键值对形式
* @param includeCharts 是否包含图表需要data中包含chartData
* @param interactive 是否添加交互功能
* @return 生成的HTML字符串
*/
@Override
public String generateAdvancedHtmlReport(Map<String, Object> data, boolean includeCharts, boolean interactive) {
Context context = new Context();
context.setVariables(data);
context.setVariable("includeCharts", includeCharts);
context.setVariable("interactive", interactive);
return templateEngine.process("advanced-report", context);
}
/**
* 生成多页HTML报告
* @param pagesData 多页数据Key为页面名称Value为页面数据
* @return 生成的HTML字符串
*/
@Override
public String generateMultiPageHtmlReport(Map<String, Map<String, Object>> pagesData) {
Context context = new Context();
context.setVariable("pages", pagesData);
return templateEngine.process("multi-page-report", context);
}
/**
* 生成HTML报告字节数组便于网络传输
* @param data 报告数据键值对形式
* @return UTF-8编码的HTML字节数组
*/
@Override
public byte[] generateHtmlReportAsBytes(Map<String, Object> data) {
return generateBasicHtmlReport(data).getBytes(StandardCharsets.UTF_8);
}
/**
* 生成分页HTML报告
* @param data 数据列表每个元素为一组键值对
* @param itemsPerPage 每页显示的项目数
* @return 生成的HTML字符串包含分页导航
*/
@Override
public String generatePaginatedHtmlReport(List<Map<String, Object>> data, int itemsPerPage) {
Context context = new Context();
int totalPages = (int) Math.ceil((double) data.size() / itemsPerPage);
context.setVariable("totalPages", totalPages);
context.setVariable("itemsPerPage", itemsPerPage);
context.setVariable("data", data);
return templateEngine.process("paginated-report", context);
}
/**
* 生成响应式HTML报告适配移动设备
* @param data 报告数据键值对形式
* @return 生成的HTML字符串包含响应式CSS
*/
@Override
public String generateResponsiveHtmlReport(Map<String, Object> data) {
Context context = new Context();
context.setVariables(data);
return templateEngine.process("responsive-report", context);
}
/**
* 生成Base64编码的HTML报告便于嵌入其他系统
* @param data 报告数据键值对形式
* @return Base64编码的HTML字符串
*/
@Override
public String generateHtmlReportAsBase64(Map<String, Object> data) {
return Base64.getEncoder().encodeToString(generateBasicHtmlReport(data).getBytes(StandardCharsets.UTF_8));
}
}