当前位置: 首页 > news >正文

Java 导出word 实现饼状图导出--可编辑数据

📊 支持图表导出功能!

支持将 柱状图折线图 图表以 Word 文档格式导出,并保留图例、坐标轴、颜色、数据标签等完整信息。

如需使用该功能,请私聊我,备注 “导出柱状图 / 折线图”

生成的效果图如下:

在这里插入图片描述

示例调用方式

package com.gemantic.qflow.word.utils;import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.util.Units;
import org.apache.poi.xddf.usermodel.XDDFColor;
import org.apache.poi.xddf.usermodel.XDDFShapeProperties;
import org.apache.poi.xddf.usermodel.XDDFSolidFillProperties;
import org.apache.poi.xddf.usermodel.chart.ChartTypes;
import org.apache.poi.xddf.usermodel.chart.LegendPosition;
import org.apache.poi.xddf.usermodel.chart.XDDFCategoryDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFChartLegend;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory;
import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFPieChartData;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFChart;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTDLbls;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTPieSer;import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;/*** 饼图渲染工具类:用于在Word文档中生成饼图。* 支持自定义颜色、图例位置、数据标签显示等参数。* 当单个数据点包含多个值时,自动取第一个值作为饼图数据。*/
public class PieChartRenderer {/*** 测试主方法,直接运行可生成示例Word文档并导出到本地。* @param args 命令行参数* @throws IOException 文件写入异常* @throws InvalidFormatException 图表格式异常*/public static void main(String[] args) throws IOException, InvalidFormatException {// 使用用户提供的JSON数据结构进行测试String jsonData = "{\n" +"    \"type\": \"chart_pie\",\n" +"    \"chartType\": \"pie\",\n" +"    \"title\": \"股价对比图表\",\n" +"    \"xAxisTitle\": \"日期\",\n" +"    \"yAxisTitle\": \"价格\",\n" +"    \"legend\": [],\n" +"    \"value\": [\n" +"        { \"name\": \"2022/11/12\", \"value\": [120.99,2000] },\n" +"        { \"name\": \"2022/11/13\", \"value\": [null] },\n" +"        { \"name\": \"2022/11/14\", \"value\": [150] },\n" +"        { \"name\": \"2022/11/15\", \"value\": [160] },\n" +"        { \"name\": \"2022/11/16\", \"value\": [180] }\n" +"    ],\n" +"    \"colors\": [\n" +"        \"#4E79A7\",\n" +"        \"#29A0CA\"\n" +"    ],\n" +"    \"showTitle\": true,\n" +"    \"showGrid\": true,\n" +"    \"showLegend\": true,\n" +"    \"showAxisLabel\": true,\n" +"    \"showDataLabel\": true,\n" +"    \"showAxis\": true,\n" +"    \"width\": 600,\n" +"    \"height\": 400\n" +"}";new PieChartRenderer().renderFromJson(new XWPFDocument(), jsonData);}/*** 基于JSON字符串渲染饼图到Word文档* @param doc 目标XWPFDocument文档对象* @param jsonData JSON字符串,包含饼图配置和数据* @throws IOException 文件写入异常* @throws InvalidFormatException 图表格式异常*/public void renderFromJson(XWPFDocument doc, String jsonData) throws IOException, InvalidFormatException {ObjectMapper mapper = new ObjectMapper();JsonNode rootNode = mapper.readTree(jsonData);// 解析基本配置String chartType = rootNode.get("chartType").asText(); // 应该是 "pie"String title = rootNode.get("title").asText();// 【图表标题】是否显示图表主标题。true:显示。false:不显示。boolean showTitle = rootNode.get("showTitle").asBoolean();// 【图例】是否显示图例。boolean showLegend = rootNode.get("showLegend").asBoolean();// 【数据标签】是否在图表上直接显示每个数据点的数值标签。true:显示。false:不显示。boolean showDataLabel = rootNode.get("showDataLabel").asBoolean();// 饼图的尺寸配置int width = rootNode.has("width") ? rootNode.get("width").asInt() : 600;int height = rootNode.has("height") ? rootNode.get("height").asInt() : 400;// 解析颜色配置List<String> colors = new ArrayList<>();JsonNode colorsNode = rootNode.get("colors");if (colorsNode != null) {for (JsonNode color : colorsNode) {colors.add(color.asText());}}// 解析饼图数据JsonNode valueNode = rootNode.get("value");List<String> categories = new ArrayList<>();List<Double> values = new ArrayList<>();// 解析每个数据点,如果有多个值则取第一个,过滤掉null或无效值for (JsonNode dataPoint : valueNode) {String name = dataPoint.get("name").asText();JsonNode valueArray = dataPoint.get("value");Double validValue = null;if (valueArray.isArray() && valueArray.size() > 0) {// 遍历值数组,找到第一个有效的非null数值for (int i = 0; i < valueArray.size(); i++) {JsonNode valueNode2 = valueArray.get(i);if (!valueNode2.isNull() && valueNode2.isNumber()) {double val = valueNode2.asDouble();// 只接受大于0的有效值(饼图不能有负值或0值)if (val > 0) {validValue = val;break;}}}}// 只添加有有效值的数据点到饼图中if (validValue != null) {categories.add(name);values.add(validValue);System.out.println("✅ 添加饼图数据点:" + name + " = " + validValue);} else {System.out.println("⚠️ 跳过无效数据点:" + name + "(值为null、0或负数)");}}// 转换为数组String[] categoryArray = categories.toArray(new String[0]);Double[] valueArray = values.toArray(new Double[0]);// 创建饼图createPieChart(doc, title, categoryArray, valueArray, colors,showDataLabel, showTitle, showLegend, width, height);// 保存文件String outputPath = "/Users/wtm/Desktop/output/pie_chart_" + System.currentTimeMillis() + ".docx";try (FileOutputStream out = new FileOutputStream(outputPath)) {doc.write(out);}System.out.println("✅ 饼图导出完成,路径:" + outputPath);}/*** 创建饼图的核心方法* @param doc 目标XWPFDocument文档对象* @param chartTitle 图表标题* @param categories 饼图分类标签数组* @param values 饼图数值数组* @param colors 颜色列表* @param showDataLabels 是否显示数据标签* @param showTitle 是否显示图表标题* @param showLegend 是否显示图例* @param width 图表宽度(像素)* @param height 图表高度(像素)* @throws IOException 文件写入异常* @throws InvalidFormatException 图表格式异常*/private void createPieChart(XWPFDocument doc,String chartTitle,String[] categories,Double[] values,List<String> colors,boolean showDataLabels,boolean showTitle,boolean showLegend,int width,int height) throws IOException, InvalidFormatException {// 创建段落标题XWPFParagraph p = doc.createParagraph();p.setAlignment(ParagraphAlignment.CENTER);XWPFRun r = p.createRun();r.setText(chartTitle);r.setBold(true);r.setFontSize(16);// 创建图表对象 - 使用JSON提供的尺寸,转换为EMU单位int widthEMU = (int) (width * Units.EMU_PER_PIXEL);int heightEMU = (int) (height * Units.EMU_PER_PIXEL);XWPFChart chart = doc.createChart(widthEMU, heightEMU);// 首先填充嵌入的Excel数据,确保数据源正确建立populateEmbeddedExcelDataForPie(chart, categories, values);// 设置图表标题显示/隐藏if (showTitle) {chart.setTitleText(chartTitle);chart.setTitleOverlay(false);} else {// 隐藏图表标题chart.setTitleText("");chart.setTitleOverlay(true);}// 设置图例显示/隐藏if (showLegend) {XDDFChartLegend legend = chart.getOrAddLegend();legend.setPosition(LegendPosition.BOTTOM); // 【修改】图例位置设置为底部} else {// 隐藏图例if (chart.getCTChart().isSetLegend()) {chart.getCTChart().unsetLegend();}}// 使用Excel工作表数据作为数据源XDDFCategoryDataSource categoryDataSource = createCategoryDataSourceFromExcelForPie(chart, categories.length);XDDFNumericalDataSource<Double> valuesDataSource = createNumericalDataSourceFromExcelForPie(chart, categories.length);// 创建饼图数据XDDFPieChartData data = (XDDFPieChartData) chart.createData(ChartTypes.PIE, null, null);// 添加饼图系列XDDFPieChartData.Series series = (XDDFPieChartData.Series) data.addSeries(categoryDataSource, valuesDataSource);series.setTitle("饼图数据", null);// 设置数据标签setPieDataLabels(series, showDataLabels, values);// 设置饼图扇形颜色setPieSeriesColors(series, colors, categories.length);// 绘制图表chart.plot(data);System.out.println("✅ 饼图创建完成,包含 " + categories.length + " 个扇形");}/*** 填充嵌入的Excel数据,专门为饼图设计* @param chart XWPFChart对象* @param categories 饼图分类标签* @param values 饼图数值*/private void populateEmbeddedExcelDataForPie(XWPFChart chart, String[] categories, Double[] values) {try {// 获取嵌入的Excel工作簿if (chart.getWorkbook() != null) {org.apache.poi.ss.usermodel.Workbook workbook = chart.getWorkbook();// 获取第一个工作表,如果不存在则创建org.apache.poi.ss.usermodel.Sheet sheet = workbook.getNumberOfSheets() > 0 ?workbook.getSheetAt(0) : workbook.createSheet("PieChartData");// 设置工作表名称if (workbook.getNumberOfSheets() > 0) {workbook.setSheetName(0, "PieChartData");}// 清空现有数据for (int i = sheet.getLastRowNum(); i >= 0; i--) {org.apache.poi.ss.usermodel.Row row = sheet.getRow(i);if (row != null) {sheet.removeRow(row);}}// 创建表头行org.apache.poi.ss.usermodel.Row headerRow = sheet.createRow(0);headerRow.createCell(0).setCellValue("分类"); // 第一列为分类标题headerRow.createCell(1).setCellValue("数值"); // 第二列为数值标题// 填充数据行for (int i = 0; i < categories.length && i < values.length; i++) {org.apache.poi.ss.usermodel.Row dataRow = sheet.createRow(i + 1);dataRow.createCell(0).setCellValue(categories[i]);dataRow.createCell(1).setCellValue(values[i] != null ? values[i] : 0.0);}// 自动调整列宽sheet.autoSizeColumn(0);sheet.autoSizeColumn(1);// 设置数据区域名称,便于图表引用org.apache.poi.ss.usermodel.Name dataRange = workbook.createName();dataRange.setNameName("PieChartDataRange");String rangeFormula = "PieChartData!$A$1:$B$" + (categories.length + 1);dataRange.setRefersToFormula(rangeFormula);System.out.println("✅ 已填充饼图嵌入Excel数据,包含 " + (categories.length + 1) + " 行 2 列");System.out.println("✅ 饼图数据范围设置为:" + rangeFormula);}} catch (Exception e) {System.err.println("警告:填充饼图嵌入Excel数据时出错:" + e.getMessage());e.printStackTrace();}}/*** 从Excel工作表创建分类数据源(专门为饼图设计)* @param chart XWPFChart对象* @param categoryCount 分类数量* @return 分类数据源*/private XDDFCategoryDataSource createCategoryDataSourceFromExcelForPie(XWPFChart chart, int categoryCount) {try {// 创建引用Excel第一列的数据源(A2:A[n],跳过标题行)return XDDFDataSourcesFactory.fromStringCellRange(chart.getWorkbook().getSheetAt(0),new org.apache.poi.ss.util.CellRangeAddress(1, categoryCount, 0, 0));} catch (Exception e) {System.err.println("警告:无法创建饼图Excel分类数据源,使用默认数据源:" + e.getMessage());// 如果失败,返回默认的字符串数组数据源String[] defaultCategories = new String[categoryCount];for (int i = 0; i < categoryCount; i++) {defaultCategories[i] = "分类" + (i + 1);}return XDDFDataSourcesFactory.fromArray(defaultCategories);}}/*** 从Excel工作表创建数值数据源(专门为饼图设计)* @param chart XWPFChart对象* @param dataCount 数据行数* @return 数值数据源*/private XDDFNumericalDataSource<Double> createNumericalDataSourceFromExcelForPie(XWPFChart chart, int dataCount) {try {// 创建引用Excel第二列的数据源(B2:B[n],跳过标题行)return XDDFDataSourcesFactory.fromNumericCellRange(chart.getWorkbook().getSheetAt(0),new org.apache.poi.ss.util.CellRangeAddress(1, dataCount, 1, 1));} catch (Exception e) {System.err.println("警告:无法创建饼图Excel数值数据源,使用默认数据源:" + e.getMessage());// 如果失败,返回默认的数值数组数据源Double[] defaultData = new Double[dataCount];for (int i = 0; i < dataCount; i++) {defaultData[i] = (double) (i + 1) * 10; // 简单的递增数据}return XDDFDataSourcesFactory.fromArray(defaultData);}}/*** 设置饼图数据标签* @param series 饼图系列* @param showDataLabels 是否显示数据标签* @param values 数值数组(用于确定哪些点需要标签)*/private void setPieDataLabels(XDDFPieChartData.Series series, boolean showDataLabels, Double[] values) {if (!showDataLabels) {// 关闭所有标签CTPieSer ctSer = series.getCTPieSer();if (ctSer.isSetDLbls()) {ctSer.unsetDLbls();}return;}try {// 为饼图显示数据标签CTPieSer ctSer = series.getCTPieSer();CTDLbls dLbls = ctSer.isSetDLbls() ? ctSer.getDLbls() : ctSer.addNewDLbls();// 清空原有标签dLbls.setDLblArray(null);// 全局标签设置:显示数值dLbls.addNewShowVal().setVal(true);dLbls.addNewShowLegendKey().setVal(false);dLbls.addNewShowCatName().setVal(false);dLbls.addNewShowSerName().setVal(false);dLbls.addNewShowPercent().setVal(false);dLbls.addNewShowLeaderLines().setVal(true); // 饼图特有:显示引导线// 为每个有值的数据点设置标签for (int i = 0; i < values.length; i++) {if (values[i] != null && values[i] > 0) {org.openxmlformats.schemas.drawingml.x2006.chart.CTDLbl lbl = dLbls.addNewDLbl();lbl.addNewIdx().setVal(i);lbl.addNewShowVal().setVal(true);lbl.addNewShowLegendKey().setVal(false);lbl.addNewShowCatName().setVal(false);lbl.addNewShowSerName().setVal(false);lbl.addNewShowPercent().setVal(false);}}System.out.println("✅ 已设置饼图数据标签,显示 " + values.length + " 个数据点的标签");} catch (Exception e) {System.err.println("警告:设置饼图数据标签时出错:" + e.getMessage());}}/*** 设置饼图扇形颜色* @param series 饼图系列* @param colors 颜色列表* @param pointCount 数据点数量*/private void setPieSeriesColors(XDDFPieChartData.Series series, List<String> colors, int pointCount) {if (colors == null || colors.isEmpty()) {System.out.println("⚠️ 未提供颜色配置,将使用默认颜色");return;}try {// 为每个饼图扇形设置颜色for (int i = 0; i < pointCount; i++) {// 使用模运算实现颜色循环:当颜色数量少于数据点时循环使用String colorHex = colors.get(i % colors.size());setPieSliceColor(series, i, colorHex);}System.out.println("✅ 已设置饼图扇形颜色,使用 " + colors.size() + " 种颜色为 " + pointCount + " 个扇形着色");} catch (Exception e) {System.err.println("警告:设置饼图颜色时出错:" + e.getMessage());}}/*** 设置单个饼图扇形的颜色* @param series 饼图系列* @param pointIndex 数据点索引* @param colorHex 十六进制颜色值(如 #4E79A7)*/private void setPieSliceColor(XDDFPieChartData.Series series, int pointIndex, String colorHex) {try {// 移除颜色字符串前的#号String hex = colorHex.startsWith("#") ? colorHex.substring(1) : colorHex;// 将十六进制颜色转换为RGBint r = Integer.parseInt(hex.substring(0, 2), 16);int g = Integer.parseInt(hex.substring(2, 4), 16);int b = Integer.parseInt(hex.substring(4, 6), 16);// 创建颜色对象XDDFColor xddfColor = XDDFColor.from(new byte[]{(byte)r, (byte)g, (byte)b});XDDFSolidFillProperties fillProperties = new XDDFSolidFillProperties(xddfColor);// 设置饼图扇形颜色XDDFShapeProperties shapeProperties = new XDDFShapeProperties();shapeProperties.setFillProperties(fillProperties);// 通过底层CT对象设置特定数据点的颜色CTPieSer ctSer = series.getCTPieSer();if (ctSer.getDPtArray().length <= pointIndex) {// 如果数据点不存在,创建新的数据点while (ctSer.getDPtArray().length <= pointIndex) {org.openxmlformats.schemas.drawingml.x2006.chart.CTDPt dPt = ctSer.addNewDPt();dPt.addNewIdx().setVal(ctSer.getDPtArray().length - 1);}}org.openxmlformats.schemas.drawingml.x2006.chart.CTDPt dPt = ctSer.getDPtArray(pointIndex);if (dPt == null) {dPt = ctSer.addNewDPt();dPt.addNewIdx().setVal(pointIndex);}// 设置数据点的填充属性if (!dPt.isSetSpPr()) {dPt.addNewSpPr();}if (!dPt.getSpPr().isSetSolidFill()) {dPt.getSpPr().addNewSolidFill();}if (!dPt.getSpPr().getSolidFill().isSetSrgbClr()) {dPt.getSpPr().getSolidFill().addNewSrgbClr();}// 设置RGB颜色值dPt.getSpPr().getSolidFill().getSrgbClr().setVal(new byte[]{(byte)r, (byte)g, (byte)b});} catch (Exception e) {// 如果颜色格式错误,记录错误但不中断流程System.err.println("警告:无法解析颜色 " + colorHex + " 用于数据点 " + pointIndex + ",将使用默认颜色。错误:" + e.getMessage());}}
}
http://www.dtcms.com/a/272661.html

相关文章:

  • CIEDE2000 色差公式C++及MATLAB实现
  • 【零基础学AI】第35讲:策略梯度方法 - 连续控制任务实战
  • Swift 图论实战:DFS 算法解锁 LeetCode 323 连通分量个数
  • 快速搭建服务器,fetch请求从服务器获取数据
  • ReentrantLock 与 Synchronized 的区别
  • 给MySQL做定时备份,一天3次
  • method_name字段是什么
  • 单片机基础(STM32-DAY2(GPIO))
  • Linux驱动06 --- UDP
  • 飞书AI技术体系
  • web 系统对接飞书三方登录完整步骤实战使用示例
  • 低温冷启动 高温热启动
  • OpenCV 图像进阶处理:特征提取与车牌识别深度解析
  • 醋酸镨:闪亮的稀土宝藏,掀开科技应用新篇章
  • Spring IoC 如何注入一些简单的值(比如配置文件里的字符串、数字)?
  • 【文献阅读】Depth Anything: Unleashing the Power of Large-Scale Unlabeled Data
  • MyBatis 使用教程及插件开发
  • 自动驾驶环境感知:天气数据采集与融合技术实战
  • AI-Sphere-Butler项目语音切换数字人管家形象功能老是开发不成功。
  • Oracle 数据库管理与维护实战指南(用户权限、备份恢复、性能调优)
  • 深度学习与图像处理案例 │ 基于深度学习的自动驾驶小车
  • GitHub上优秀的开源播放器项目介绍及优劣对比
  • 申请注册苹果iOS企业级开发者证书需要公司拥有什么规模条件
  • Nacos的基本功能以及使用Feign进行微服务间的通信
  • 【网络编程】 TCP 协议栈的知识汇总
  • ZW3D 二次开发-创建圆柱体
  • Qt cannot find C:\WINDOWS\TEMP\cctVBBgu: Invalid argument
  • QT5使用cmakelists引入Qt5Xlsx库并使用
  • 达梦数据库不兼容 SQL_NO_CACHE 报错解决方案
  • C++交叉编译工具链制作以及QT交叉编译环境配置