如何调整Apache POI中XDDFChartLegend图例的位置及适配问题
解决Apache POI环形图图例覆盖、显示不全问题
问题场景
使用Apache POI创建环形图时,遇到以下问题:
- 图例标签较长或数量较多时,长标签会覆盖图表区域
- 10个图例仅能显示8个,短标签则可全部显示
- 未找到
XDDFChartLegend类中调整边距的方法,需要实现标签大小不影响布局,图例与图表自动适配空间

核心调整方案
- 关闭图例覆盖模式:禁止图例覆盖绘图区域,让图表自动为图例分配空间
- 优化图例布局:通过底层XML对象设置图例自动换行、调整内部边距,解决长标签拥挤问题
- 适配绘图区域:调整绘图区域的高度比例,为图例预留足够空间;或取消固定高度,让图表自动计算布局
修改后的完整代码
public static XSSFChart createDoughnutChart(XSSFSheet sheet, String[] categories, Number[] values, String titleText, int col1, int row1, int col2, int row2) { XSSFDrawing drawing = sheet.createDrawingPatriarch(); XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, col1, row1, col2, row2); XSSFChart chart = drawing.createChart(anchor); chart.setTitleText(titleText); chart.setTitleOverlay(false); XDDFChartLegend legend = chart.getOrAddLegend(); legend.setPosition(LegendPosition.BOTTOM); // 关闭图例覆盖,让图表自动为图例预留空间 legend.setOverlay(false); // 设置图例字体大小 XDDFTextBody legendTextBody = new XDDFTextBody(legend); legendTextBody.getXmlObject().addNewBodyPr(); legendTextBody.addNewParagraph().addDefaultRunProperties().setFontSize(6d); legend.setTextBody(legendTextBody); // 通过底层CTLegend对象设置图例布局:允许自动换行并调整内部边距 org.openxmlformats.schemas.drawingml.x2006.chart.CTLegend ctLegend = legend.getCTLegend(); ctLegend.addNewLayout().addNewManualLayout().addNewWrap().setVal(true); if (ctLegend.isSetSpPr()) { ctLegend.getSpPr().addNewLn().addNewNoFill(); org.openxmlformats.schemas.drawingml.x2006.main.CTInsets insets = ctLegend.getSpPr().addNewBodyPr().addNewIns(); insets.setL(1000); // 左边距 insets.setR(1000); // 右边距 insets.setT(500); // 上边距 insets.setB(500); // 下边距 } XDDFDataSource<String> cat = XDDFDataSourcesFactory.fromArray(categories); XDDFNumericalDataSource<Number> val = XDDFDataSourcesFactory.fromArray(values); XDDFDoughnutChartData data = (XDDFDoughnutChartData) chart.createData(ChartTypes.DOUGHNUT, null, null); data.setVaryColors(false); data.setHoleSize(50); XDDFChartData.Series series = data.addSeries(cat, val); chart.plot(data); // 禁止自动删除标题 if (chart.getCTChart().getAutoTitleDeleted() == null) chart.getCTChart().addNewAutoTitleDeleted(); chart.getCTChart().getAutoTitleDeleted().setVal(false); // 设置数据点颜色 int pointCount = series.getCategoryData().getPointCount(); for (int p = 0; p < pointCount; p++) { chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(p); chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDPtArray(p) .addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(XlsxService.getColor(p)); } // 添加数据标签 if (!chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).isSetDLbls()) { chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDLbls(); } chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDLbls().addNewShowVal().setVal(true); chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDLbls().addNewShowSerName().setVal(false); chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDLbls().addNewShowCatName().setVal(false); chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDLbls().addNewShowPercent().setVal(false); chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDLbls().addNewShowLegendKey().setVal(false); // 图表区域无边框 chart.getCTChartSpace().addNewSpPr().addNewLn().addNewNoFill(); // 设置背景色 if (chart.getCTChartSpace().getSpPr() == null) chart.getCTChartSpace().addNewSpPr(); if (chart.getCTChartSpace().getSpPr().isSetSolidFill()) chart.getCTChartSpace().getSpPr().unsetSolidFill(); chart.getCTChartSpace().getSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{(byte)242, (byte)242, (byte)242}); // 调整绘图区域布局:预留底部空间给图例 if (!chart.getCTChart().getPlotArea().isSetLayout()) { chart.getCTChart().getPlotArea().addNewLayout(); } if (chart.getCTChart().getPlotArea().getLayout().isSetManualLayout()) { chart.getCTChart().getPlotArea().getLayout().unsetManualLayout(); } chart.getCTChart().getPlotArea().getLayout().addNewManualLayout(); chart.getCTChart().getPlotArea().getLayout().getManualLayout().addNewLayoutTarget().setVal( org.openxmlformats.schemas.drawingml.x2006.chart.STLayoutTarget.INNER); chart.getCTChart().getPlotArea().getLayout().getManualLayout().addNewXMode().setVal( org.openxmlformats.schemas.drawingml.x2006.chart.STLayoutMode.EDGE); chart.getCTChart().getPlotArea().getLayout().getManualLayout().addNewYMode().setVal( org.openxmlformats.schemas.drawingml.x2006.chart.STLayoutMode.EDGE); chart.getCTChart().getPlotArea().getLayout().getManualLayout().addNewX().setVal(0.10); // 左偏移10% chart.getCTChart().getPlotArea().getLayout().getManualLayout().addNewY().setVal(0.10); // 上偏移10% chart.getCTChart().getPlotArea().getLayout().getManualLayout().addNewW().setVal(0.80); // 宽度80% // 调整高度为70%,给底部图例预留30%空间;也可注释此行,让图表自动计算高度 chart.getCTChart().getPlotArea().getLayout().getManualLayout().addNewH().setVal(0.70); return chart; }
关键修改说明
- 关闭图例覆盖:
legend.setOverlay(false)是核心,确保图例不会占用绘图区域空间,图表会自动分配区域给图例 - 图例自动换行:通过
CTLegend设置Wrap为true,长标签会自动换行,避免横向溢出 - 绘图区域适配:将绘图区域高度从60%调整为70%,为底部图例预留更多空间;若需要完全自动适配,可直接注释掉
addNewH()的设置
内容的提问来源于stack exchange,提问作者raymax
相关产品推荐
相关产品推荐

