添加模板生成工具类
This commit is contained in:
parent
64d4900aa3
commit
0786fb99a8
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user