Browse Source

研判分析--基础模板step4-周六解决图表

master
buaixuexideshitongxue 2 weeks ago
parent
commit
45d54d72a4
  1. 1
      src/main/java/com/biutag/supervision/SupervisionApplication.java
  2. 2
      src/main/java/com/biutag/supervision/pojo/dto/report/ReportViewModel.java
  3. 5
      src/main/java/com/biutag/supervision/pojo/dto/report/accountability/AccountabilityDepartmentSection.java
  4. 4
      src/main/java/com/biutag/supervision/pojo/dto/report/accountability/AccountabilityPersonalSection.java
  5. 5
      src/main/java/com/biutag/supervision/pojo/dto/report/businessLine/BusinessLineOverviewSection.java
  6. 5
      src/main/java/com/biutag/supervision/pojo/dto/report/unitInvestigation/UnitInvestigationOverviewSection.java
  7. 2
      src/main/java/com/biutag/supervision/service/NegativeQueryService.java
  8. 61
      src/main/java/com/biutag/supervision/service/report/ReportDataServiceImpl.java
  9. 417
      src/main/java/com/biutag/supervision/util/ChartRenderUtil.java
  10. BIN
      src/main/resources/static/templates/督审一体化平台研判分析报告.docx

1
src/main/java/com/biutag/supervision/SupervisionApplication.java

@ -15,6 +15,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
public class SupervisionApplication {
public static void main(String[] args) {
System.setProperty("java.awt.headless", "true");
SpringApplication.run(SupervisionApplication.class, args);
}

2
src/main/java/com/biutag/supervision/pojo/dto/report/ReportViewModel.java

@ -41,7 +41,7 @@ public class ReportViewModel {
@Schema(description = "单位查处情况")
private UnitInvestigationOverviewSection unitInvestigationSection;
private UnitInvestigationOverviewSection unitInvestigationOverviewSection;
// @Schema(description = "单位查处情况--详情预留")

5
src/main/java/com/biutag/supervision/pojo/dto/report/accountability/AccountabilityDepartmentSection.java

@ -1,5 +1,6 @@
package com.biutag.supervision.pojo.dto.report.accountability;
import com.deepoove.poi.data.PictureRenderData;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@ -16,4 +17,8 @@ public class AccountabilityDepartmentSection {
@Schema(description = "单位问责类型列表")
private List<AccountabilityTypeItem> typeItems;
@Schema(description = "单位问责饼状图")
private PictureRenderData departPieChart;
}

4
src/main/java/com/biutag/supervision/pojo/dto/report/accountability/AccountabilityPersonalSection.java

@ -1,5 +1,6 @@
package com.biutag.supervision.pojo.dto.report.accountability;
import com.deepoove.poi.data.PictureRenderData;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@ -16,4 +17,7 @@ public class AccountabilityPersonalSection {
@Schema(description = "个人问责类型列表")
private List<AccountabilityTypeItem> typeItems;
@Schema(description = "个人问责饼状图")
private PictureRenderData personPieChart;
}

5
src/main/java/com/biutag/supervision/pojo/dto/report/businessLine/BusinessLineOverviewSection.java

@ -1,5 +1,6 @@
package com.biutag.supervision.pojo.dto.report.businessLine;
import com.deepoove.poi.data.PictureRenderData;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@ -22,4 +23,8 @@ public class BusinessLineOverviewSection {
@Schema(description = "各业务条线情况-总览文本(已拼接)")
private String businessLineOverviewText;
@Schema(description = "业务总览饼状图")
private PictureRenderData problemPieChart;
}

5
src/main/java/com/biutag/supervision/pojo/dto/report/unitInvestigation/UnitInvestigationOverviewSection.java

@ -1,5 +1,6 @@
package com.biutag.supervision.pojo.dto.report.unitInvestigation;
import com.deepoove.poi.data.PictureRenderData;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@ -30,4 +31,8 @@ public class UnitInvestigationOverviewSection {
private List<UnitInvestigationItem> topUnits;
@Schema(description = "单位查处情况柱状图")
private PictureRenderData unitBarChart;
}

2
src/main/java/com/biutag/supervision/service/NegativeQueryService.java

@ -212,7 +212,7 @@ public class NegativeQueryService {
NegativeQueryVo vo = new NegativeQueryVo();
BeanUtils.copyProperties(item, vo);
if (Objects.nonNull(item.getFirstDistributeTime()) && !ProcessingStatusEnum.completed.name().equals(item.getProcessingStatus())) {
vo.setRemainingDuration(TimeUtil.getRemainingDuration(item.getFirstDistributeTime(), item.getMaxSignDuration(), item.getMaxHandleDuration(), item.getExtensionDays(), item.getFlowKey()));
// vo.setRemainingDuration(TimeUtil.getRemainingDuration(item.getFirstDistributeTime(), item.getMaxSignDuration(), item.getMaxHandleDuration(), item.getExtensionDays(), item.getFlowKey()));
}
return vo;
}).toList();

61
src/main/java/com/biutag/supervision/service/report/ReportDataServiceImpl.java

@ -30,11 +30,16 @@ import com.biutag.supervision.repository.negativeBlame.NegativeBlameResourceServ
import com.biutag.supervision.repository.supdepart.SupDepartResourceService;
import com.biutag.supervision.service.NegativeQueryService;
import com.biutag.supervision.service.SupDictProblemSourceService;
import com.biutag.supervision.util.ChartRenderUtil;
import com.biutag.supervision.util.DateCompareRangeUtil;
import com.biutag.supervision.util.ReportTrendUtil;
import com.biutag.supervision.util.TimeUtil;
import com.deepoove.poi.data.PictureRenderData;
import com.deepoove.poi.data.PictureType;
import com.deepoove.poi.data.Pictures;
import jakarta.annotation.Resource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@ -43,6 +48,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
@Service
@Slf4j
public class ReportDataServiceImpl implements ReportDataService {
@Resource
@ -70,10 +76,11 @@ public class ReportDataServiceImpl implements ReportDataService {
vm.setOverviewSection(buildOverviewSection(request, periodStart, periodEnd));
vm.setBusinessLineOverviewSection(buildBusinessLineOverviewSection(request, periodStart, periodEnd));
vm.setBusinessLineDetailSections(buildBusinessLineDetailSections(request, periodStart, periodEnd));
vm.setUnitInvestigationSection(buildUnitInvestigationSection(request, periodStart, periodEnd));
vm.setUnitInvestigationOverviewSection(buildUnitInvestigationOverviewSection(request, periodStart, periodEnd));
vm.setAccountabilityOverviewSection(buildAccountabilityOverviewSection(request, periodStart, periodEnd));
vm.setAccountabilityUnitDetailSection(buildAccountabilityUnitDetailSection(request, periodStart, periodEnd));
vm.setAccountabilityPersonOverviewSection(buildAccountabilityPersonOverviewSection(request, periodStart, periodEnd));
// System.out.println(1/0);
return vm;
}
@ -106,6 +113,18 @@ public class ReportDataServiceImpl implements ReportDataService {
.sorted(Comparator.comparing(AccountabilityTypeItem::getCount).reversed())
.toList();
accountabilityPersonalSection.setTypeItems(typeItems);
// 饼图
Map<String, Number> pieData = new LinkedHashMap<>();
for (AccountabilityTypeItem typeItem : typeItems) {
pieData.put(typeItem.getTypeName(), typeItem.getCount());
}
byte[] pieBytes = ChartRenderUtil.piePng("单位问责情况", pieData, 5, 900, 520);
PictureRenderData picture = Pictures.ofBytes(pieBytes, PictureType.PNG)
.size(500, 300)
.create();
accountabilityPersonalSection.setPersonPieChart(picture);
return accountabilityPersonalSection;
}
@ -138,6 +157,17 @@ public class ReportDataServiceImpl implements ReportDataService {
.sorted(Comparator.comparing(AccountabilityTypeItem::getCount).reversed())
.toList();
accountabilityDepartmentSection.setTypeItems(typeItems);
// 饼图
Map<String, Number> pieData = new LinkedHashMap<>();
for (AccountabilityTypeItem typeItem : typeItems) {
pieData.put(typeItem.getTypeName(), typeItem.getCount());
}
byte[] pieBytes = ChartRenderUtil.piePng("单位问责情况", pieData, 5, 900, 520);
PictureRenderData picture = Pictures.ofBytes(pieBytes, PictureType.PNG)
.size(500, 300)
.create();
accountabilityDepartmentSection.setDepartPieChart(picture);
return accountabilityDepartmentSection;
}
@ -164,7 +194,7 @@ public class ReportDataServiceImpl implements ReportDataService {
return accountabilityOverviewSection;
}
private UnitInvestigationOverviewSection buildUnitInvestigationSection(NegativeQueryParam request,
private UnitInvestigationOverviewSection buildUnitInvestigationOverviewSection(NegativeQueryParam request,
String periodStart,
String periodEnd) {
DateCompareRangeUtil.CompareDateRange compareDateRange = DateCompareRangeUtil.buildCompareDateRange(request.getCrtTime().get(0), request.getCrtTime().get(1));
@ -179,6 +209,7 @@ public class ReportDataServiceImpl implements ReportDataService {
for (DepartAndSubDepartDto value : departAndSubDepart.values()) {
Set<String> allDepartIds = value.getAllDepartIds();
List<NegativeQueryVo> voList = ztNegativeList.stream().filter(one -> allDepartIds.contains(one.getInvolveDepartId())).toList();
log.info(value.getParentName() + "的数量=====================" + voList.size());
if (CollectionUtil.isNotEmpty(voList)) {
UnitInvestigationItem unitInvestigationItem = new UnitInvestigationItem();
unitInvestigationItem.setUnitName(value.getParentName());
@ -193,10 +224,26 @@ public class ReportDataServiceImpl implements ReportDataService {
}
}
topUnits.sort(Comparator.comparing(UnitInvestigationItem::getVerifiedProblemCount).reversed());
topUnits = topUnits.stream().limit(3).toList();
UnitInvestigationOverviewSection section = new UnitInvestigationOverviewSection();
section.setPeriodStart(periodStart);
section.setPeriodEnd(periodEnd);
section.setTopUnits(topUnits);
// 柱状图
Map<String, Map<String, Number>> seriesData = new LinkedHashMap<>();
Map<String, Number> issuedMap = new LinkedHashMap<>();
Map<String, Number> verifiedMap = new LinkedHashMap<>();
for (UnitInvestigationItem item : topUnits) {
issuedMap.put(item.getUnitName(), item.getIssuedProblemCount());
verifiedMap.put(item.getUnitName(), item.getVerifiedProblemCount());
}
seriesData.put("下发问题数", issuedMap);
seriesData.put("查实问题数", verifiedMap);
byte[] barBytes = ChartRenderUtil.groupedBarPng("单位查处情况", seriesData, "单位", "数量", 1000, 520);
PictureRenderData picture = Pictures.ofBytes(barBytes, PictureType.PNG)
.size(500, 300)
.create();
section.setUnitBarChart(picture);
return section;
}
@ -372,6 +419,16 @@ public class ReportDataServiceImpl implements ReportDataService {
businessLineOverviewSection.setPeriodEnd(periodEnd);
businessLineOverviewSection.setBusinessLineTotal(ztNegativeList.size());
businessLineOverviewSection.setBusinessLineOverviewText(businessLineOverviewText.toString());
Map<String, Number> pieData = new LinkedHashMap<>();
for (Map.Entry<String, Long> entry : sorted) {
pieData.put(entry.getKey(), entry.getValue());
}
byte[] pieBytes = ChartRenderUtil.piePng("占比统计", pieData, 5, 900, 520);
PictureRenderData picture = Pictures.ofBytes(pieBytes, PictureType.PNG)
.size(500, 300)
.create();
businessLineOverviewSection.setProblemPieChart(picture);
return businessLineOverviewSection;
}

417
src/main/java/com/biutag/supervision/util/ChartRenderUtil.java

@ -10,47 +10,123 @@ import org.jfree.chart.plot.PiePlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.ui.HorizontalAlignment;
import org.jfree.chart.ui.RectangleEdge;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.data.general.DefaultPieDataset;
import java.awt.*;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.io.ByteArrayOutputStream;
import java.text.NumberFormat;
import java.text.DecimalFormat;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 图表渲染工具PNG
* - 适配报告输出白底抗锯齿中文字体标签更清晰柱状图显示数值
* <p>
* 适用场景
* 1. JFreeChart 生成图表
* 2. poi-tl 插入 Word 模板
* 3. 报表类固定输出场景
*/
public class ChartRenderUtil {
private ChartRenderUtil() {
}
/**
* 饼图配色
*/
private static final Color[] PIE_COLORS = new Color[]{
new Color(91, 155, 213),
new Color(237, 125, 49),
new Color(165, 165, 165),
new Color(255, 192, 0),
new Color(68, 114, 196),
new Color(112, 173, 71),
new Color(38, 68, 120),
new Color(158, 72, 14),
new Color(99, 99, 99),
new Color(153, 115, 0)
};
/**
* 柱状图系列配色
*/
private static final Color[] BAR_COLORS = new Color[]{
new Color(91, 155, 213),
new Color(237, 125, 49),
new Color(112, 173, 71),
new Color(165, 165, 165),
new Color(255, 192, 0),
new Color(68, 114, 196),
new Color(158, 72, 14),
new Color(38, 68, 120)
};
private static final String DEFAULT_OTHER_NAME = "其余问题类型";
private static final String DEFAULT_EMPTY_NAME = "暂无数据";
// =========================
// 对外 API:根据数据生成图
// 对外 API:饼图(唯一入口)
// =========================
public static byte[] piePng(Map<String, Number> data, int w, int h) {
/**
* 生成饼图 PNG
*
* @param title 标题
* @param data 数据key=名称value=数值
* @param topN 前N项<=0表示不截断超出部分合并为其他
* @param w
* @param h
*/
public static byte[] piePng(String title, Map<String, Number> data, int topN, int w, int h) {
Map<String, Number> safeData = normalizePieData(data);
Map<String, Number> chartData = mergeTopNAsOther(safeData, topN, DEFAULT_OTHER_NAME);
DefaultPieDataset<String> dataset = new DefaultPieDataset<>();
data.forEach(dataset::setValue);
chartData.forEach(dataset::setValue);
JFreeChart chart = ChartFactory.createPieChart(
"占比统计", // 你不想要标题可以传 null
title,
dataset,
true,
false,
false
);
beautifyPie(chart);
beautifyPie(chart, chartData);
return toPng(chart, w, h, "生成饼图失败");
}
public static byte[] barPng(Map<String, Number> data, String xLabel, String yLabel, int w, int h) {
// =========================
// 对外 API:柱状图(单系列)
// =========================
/**
* 单系列柱状图
*
* @param title 标题
* @param data 数据key=类目value=数值
* @param xLabel X轴标题
* @param yLabel Y轴标题
* @param w
* @param h
*/
public static byte[] barPng(String title, Map<String, Number> data, String xLabel, String yLabel, int w, int h) {
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
// series 用一个固定值即可(yLabel 也行)
data.forEach((k, v) -> dataset.addValue(v, yLabel, k));
Map<String, Number> safeData = normalizeBarData(data);
safeData.forEach((category, value) -> dataset.addValue(value, yLabel, category));
JFreeChart chart = ChartFactory.createBarChart(
"统计分析", // 你不想要标题可以传 null
title,
xLabel,
yLabel,
dataset,
@ -60,75 +136,161 @@ public class ChartRenderUtil {
false
);
beautifyBar(chart);
beautifyBar(chart, 1);
return toPng(chart, w, h, "生成柱状图失败");
}
// =========================
// Demo:随便生成一个图(可留可删)
// 对外 API:柱状图(多系列)
// =========================
/**
* 多系列分组柱状图
*
* @param title 标题
* @param seriesData 多系列数据
* 外层key = 系列名例如下发问题数查实问题数
* 内层key = 类目名例如单位A单位B
* 内层value = 数值
* @param xLabel X轴标题
* @param yLabel Y轴标题
* @param w
* @param h
*/
public static byte[] groupedBarPng(String title,
Map<String, Map<String, Number>> seriesData,
String xLabel,
String yLabel,
int w,
int h) {
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
Map<String, Map<String, Number>> safeSeriesData = normalizeGroupedBarData(seriesData);
for (Map.Entry<String, Map<String, Number>> seriesEntry : safeSeriesData.entrySet()) {
String seriesName = seriesEntry.getKey();
Map<String, Number> categoryData = seriesEntry.getValue();
for (Map.Entry<String, Number> categoryEntry : categoryData.entrySet()) {
dataset.addValue(categoryEntry.getValue(), seriesName, categoryEntry.getKey());
}
}
JFreeChart chart = ChartFactory.createBarChart(
title,
xLabel,
yLabel,
dataset,
PlotOrientation.VERTICAL,
true,
false,
false
);
beautifyBar(chart, safeSeriesData.size());
return toPng(chart, w, h, "生成分组柱状图失败");
}
// =========================
// Demo
// =========================
public static byte[] createPieChart() {
return piePng(Map.of("查实", 40, "基本属实", 30, "不属实", 30), 900, 520);
Map<String, Number> data = new LinkedHashMap<>();
data.put("查实", 40);
data.put("基本属实", 30);
data.put("不属实", 30);
return piePng("查实情况分布", data, 0, 900, 520);
}
public static byte[] createBarChart() {
return barPng(Map.of("市局", 120, "分县市局", 80, "基层单位", 50), "单位", "数量", 900, 520);
Map<String, Map<String, Number>> seriesData = new LinkedHashMap<>();
Map<String, Number> issued = new LinkedHashMap<>();
issued.put("单位A", 120);
issued.put("单位B", 80);
issued.put("单位C", 50);
Map<String, Number> verified = new LinkedHashMap<>();
verified.put("单位A", 100);
verified.put("单位B", 60);
verified.put("单位C", 30);
seriesData.put("下发问题数", issued);
seriesData.put("查实问题数", verified);
return groupedBarPng("单位问题分布", seriesData, "单位", "数量", 1000, 520);
}
// =========================
// 美化:饼图
// =========================
private static void beautifyPie(JFreeChart chart) {
private static void beautifyPie(JFreeChart chart, Map<String, Number> data) {
chart.setAntiAlias(true);
chart.setTextAntiAlias(true);
chart.setBackgroundPaint(Color.WHITE);
chart.setBorderVisible(false);
Font titleFont = pickFont(Font.BOLD, 16);
Font titleFont = pickFont(Font.BOLD, 18);
Font legendFont = pickFont(Font.PLAIN, 12);
Font labelFont = pickFont(Font.PLAIN, 12);
Font labelFont = pickFont(Font.PLAIN, 11);
if (chart.getTitle() != null) {
chart.getTitle().setFont(titleFont);
chart.getTitle().setPaint(new Color(51, 51, 51));
chart.getTitle().setHorizontalAlignment(HorizontalAlignment.CENTER);
chart.getTitle().setMargin(0, 0, 12, 0);
}
LegendTitle legend = chart.getLegend();
if (legend != null) {
legend.setItemFont(legendFont);
legend.setBackgroundPaint(Color.WHITE);
legend.setBorder(0, 0, 0, 0);
legend.setPosition(RectangleEdge.TOP);
}
PiePlot plot = (PiePlot) chart.getPlot();
PiePlot<?> plot = (PiePlot<?>) chart.getPlot();
plot.setBackgroundPaint(Color.WHITE);
plot.setOutlineVisible(false);
plot.setShadowPaint(null);
plot.setInteriorGap(0.08);
// label 样式:白底半透明,边框淡一点,去阴影
plot.setLabelFont(labelFont);
plot.setLabelPaint(new Color(51, 51, 51));
plot.setLabelBackgroundPaint(new Color(255, 255, 255, 235));
plot.setLabelOutlinePaint(new Color(220, 220, 220));
plot.setLabelShadowPaint(null);
plot.setLabelLinkPaint(new Color(160, 160, 160));
plot.setLabelLinkStroke(new BasicStroke(1.0f));
plot.setSimpleLabels(false);
// 标签内容:名称 + 百分比(例:查实 40.0%)
plot.setLabelGenerator(new StandardPieSectionLabelGenerator(
"{0} {2}",
NumberFormat.getNumberInstance(),
NumberFormat.getPercentInstance()
"{0} {2}({1})",
new DecimalFormat("0"),
new DecimalFormat("0.0%")
));
// 让饼图别贴边
plot.setInteriorGap(0.03);
plot.setSectionOutlinesVisible(true);
plot.setForegroundAlpha(0.95f);
// 分离效果(可选:更“立体”,如果你觉得花哨可以注释)
// plot.setExplodePercent("查实", 0.04);
// 分类太多时隐藏扇区标签,只保留图例
if (data.size() > 6) {
plot.setLabelGenerator(null);
}
int index = 0;
for (String key : data.keySet()) {
plot.setSectionPaint(key, PIE_COLORS[index % PIE_COLORS.length]);
index++;
}
}
// =========================
// 美化:柱状图
// =========================
private static void beautifyBar(JFreeChart chart) {
private static void beautifyBar(JFreeChart chart, int seriesCount) {
chart.setAntiAlias(true);
chart.setTextAntiAlias(true);
chart.setBackgroundPaint(Color.WHITE);
@ -137,45 +299,221 @@ public class ChartRenderUtil {
Font titleFont = pickFont(Font.BOLD, 16);
Font legendFont = pickFont(Font.PLAIN, 12);
Font axisFont = pickFont(Font.PLAIN, 12);
Font valueFont = pickFont(Font.PLAIN, 12);
Font valueFont = pickFont(Font.PLAIN, 11);
if (chart.getTitle() != null) {
chart.getTitle().setFont(titleFont);
chart.getTitle().setPaint(new Color(51, 51, 51));
chart.getTitle().setHorizontalAlignment(HorizontalAlignment.CENTER);
}
LegendTitle legend = chart.getLegend();
if (legend != null) {
legend.setItemFont(legendFont);
legend.setBackgroundPaint(Color.WHITE);
legend.setBorder(0, 0, 0, 0);
legend.setPosition(RectangleEdge.TOP);
}
CategoryPlot plot = chart.getCategoryPlot();
plot.setBackgroundPaint(Color.WHITE);
plot.setOutlineVisible(false);
// 网格线淡一点(更干净)
plot.setRangeGridlinePaint(new Color(230, 230, 230));
plot.setRangeGridlinesVisible(true);
// 轴字体
plot.getDomainAxis().setTickLabelFont(axisFont);
plot.getDomainAxis().setLabelFont(axisFont);
plot.getRangeAxis().setTickLabelFont(axisFont);
plot.getRangeAxis().setLabelFont(axisFont);
// 柱体渲染:更清爽
BarRenderer renderer = (BarRenderer) plot.getRenderer();
renderer.setDrawBarOutline(false);
renderer.setShadowVisible(false);
renderer.setMaximumBarWidth(0.12);
renderer.setItemMargin(0.10);
// 在柱子上方显示数值
// 多系列时适当放宽柱宽
if (seriesCount <= 1) {
renderer.setMaximumBarWidth(0.12);
} else if (seriesCount == 2) {
renderer.setMaximumBarWidth(0.18);
} else {
renderer.setMaximumBarWidth(0.25);
}
for (int i = 0; i < seriesCount; i++) {
renderer.setSeriesPaint(i, BAR_COLORS[i % BAR_COLORS.length]);
}
renderer.setDefaultItemLabelGenerator(new StandardCategoryItemLabelGenerator());
renderer.setDefaultItemLabelsVisible(true);
renderer.setDefaultItemLabelFont(valueFont);
}
// =========================
// 数据预处理:饼图
// =========================
/**
* 过滤无效数据并按数值倒序
*/
private static Map<String, Number> normalizePieData(Map<String, Number> data) {
Map<String, Number> filtered = new LinkedHashMap<>();
if (data != null) {
data.forEach((k, v) -> {
if (k != null && !k.isBlank() && v != null && v.doubleValue() > 0) {
filtered.put(k.trim(), v);
}
});
}
if (filtered.isEmpty()) {
filtered.put(DEFAULT_EMPTY_NAME, 1);
return filtered;
}
LinkedHashMap<String, Number> sorted = new LinkedHashMap<>();
filtered.entrySet().stream()
.sorted((a, b) -> Double.compare(b.getValue().doubleValue(), a.getValue().doubleValue()))
.forEachOrdered(e -> sorted.put(e.getKey(), e.getValue()));
return sorted;
}
/**
* 仅保留前 topN 其余合并为其他
*/
private static Map<String, Number> mergeTopNAsOther(Map<String, Number> data, int topN, String otherName) {
if (data == null || data.isEmpty()) {
Map<String, Number> result = new LinkedHashMap<>();
result.put(DEFAULT_EMPTY_NAME, 1);
return result;
}
if (topN <= 0 || data.size() <= topN) {
return data;
}
String finalOtherName = (otherName == null || otherName.isBlank())
? DEFAULT_OTHER_NAME
: otherName.trim();
List<Map.Entry<String, Number>> sorted = data.entrySet().stream().toList();
Map<String, Number> result = new LinkedHashMap<>();
double otherTotal = 0D;
for (int i = 0; i < sorted.size(); i++) {
Map.Entry<String, Number> entry = sorted.get(i);
if (i < topN) {
result.put(entry.getKey(), entry.getValue());
} else {
otherTotal += entry.getValue().doubleValue();
}
}
if (otherTotal > 0D) {
if (isIntegerLike(otherTotal)) {
result.put(finalOtherName, (long) otherTotal);
} else {
result.put(finalOtherName, otherTotal);
}
}
return result;
}
// =========================
// 数据预处理:单系列柱状图
// =========================
private static Map<String, Number> normalizeBarData(Map<String, Number> data) {
Map<String, Number> result = new LinkedHashMap<>();
if (data != null) {
data.forEach((k, v) -> {
if (k != null && !k.isBlank() && v != null && v.doubleValue() >= 0) {
result.put(k.trim(), v);
}
});
}
if (result.isEmpty()) {
result.put(DEFAULT_EMPTY_NAME, 0);
}
return result;
}
// =========================
// 数据预处理:多系列柱状图
// =========================
private static Map<String, Map<String, Number>> normalizeGroupedBarData(Map<String, Map<String, Number>> seriesData) {
Map<String, Map<String, Number>> result = new LinkedHashMap<>();
if (seriesData == null || seriesData.isEmpty()) {
Map<String, Number> emptySeries = new LinkedHashMap<>();
emptySeries.put(DEFAULT_EMPTY_NAME, 0);
result.put("数量", emptySeries);
return result;
}
// 收集所有类目,保证多系列类目对齐
Set<String> allCategories = new LinkedHashSet<>();
for (Map.Entry<String, Map<String, Number>> seriesEntry : seriesData.entrySet()) {
Map<String, Number> item = seriesEntry.getValue();
if (item == null) {
continue;
}
item.forEach((k, v) -> {
if (k != null && !k.isBlank()) {
allCategories.add(k.trim());
}
});
}
if (allCategories.isEmpty()) {
Map<String, Number> emptySeries = new LinkedHashMap<>();
emptySeries.put(DEFAULT_EMPTY_NAME, 0);
result.put("数量", emptySeries);
return result;
}
for (Map.Entry<String, Map<String, Number>> seriesEntry : seriesData.entrySet()) {
String seriesName = seriesEntry.getKey();
if (seriesName == null || seriesName.isBlank()) {
continue;
}
Map<String, Number> rawCategoryData = seriesEntry.getValue();
Map<String, Number> normalizedCategoryData = new LinkedHashMap<>();
for (String category : allCategories) {
Number value = 0;
if (rawCategoryData != null && rawCategoryData.containsKey(category)) {
Number raw = rawCategoryData.get(category);
if (raw != null && raw.doubleValue() >= 0) {
value = raw;
}
}
normalizedCategoryData.put(category, value);
}
result.put(seriesName.trim(), normalizedCategoryData);
}
if (result.isEmpty()) {
Map<String, Number> emptySeries = new LinkedHashMap<>();
emptySeries.put(DEFAULT_EMPTY_NAME, 0);
result.put("数量", emptySeries);
}
return result;
}
private static boolean isIntegerLike(double value) {
return Math.abs(value - Math.rint(value)) < 0.0000001D;
}
// =========================
// 输出 PNG
// =========================
@ -194,6 +532,7 @@ public class ChartRenderUtil {
// =========================
private static Font pickFont(int style, int size) {
try {
// 按优先级尝试(Windows 常见:微软雅黑;Linux 常见:Noto/文泉驿/DejaVu)
String[] candidates = new String[]{
"Microsoft YaHei", "微软雅黑",
@ -214,6 +553,10 @@ public class ChartRenderUtil {
}
}
}
} catch (Throwable e) {
// 字体系统初始化失败时兜底
System.err.println("Font detection failed, fallback to SansSerif: " + e.getMessage());
}
return new Font("SansSerif", style, size);
}
}

BIN
src/main/resources/static/templates/督审一体化平台研判分析报告.docx

Binary file not shown.
Loading…
Cancel
Save