You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何调整Apache POI中XDDFChartLegend图例的位置及适配问题

解决Apache POI环形图图例覆盖、显示不全问题

问题场景

使用Apache POI创建环形图时,遇到以下问题:

  • 图例标签较长或数量较多时,长标签会覆盖图表区域
  • 10个图例仅能显示8个,短标签则可全部显示
  • 未找到XDDFChartLegend类中调整边距的方法,需要实现标签大小不影响布局,图例与图表自动适配空间

图例问题示例

核心调整方案

  1. 关闭图例覆盖模式:禁止图例覆盖绘图区域,让图表自动为图例分配空间
  2. 优化图例布局:通过底层XML对象设置图例自动换行、调整内部边距,解决长标签拥挤问题
  3. 适配绘图区域:调整绘图区域的高度比例,为图例预留足够空间;或取消固定高度,让图表自动计算布局

修改后的完整代码

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;
}

关键修改说明

  1. 关闭图例覆盖:legend.setOverlay(false)是核心,确保图例不会占用绘图区域空间,图表会自动分配区域给图例
  2. 图例自动换行:通过CTLegend设置Wrap为true,长标签会自动换行,避免横向溢出
  3. 绘图区域适配:将绘图区域高度从60%调整为70%,为底部图例预留更多空间;若需要完全自动适配,可直接注释掉addNewH()的设置

内容的提问来源于stack exchange,提问作者raymax

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 13:25:55