如何在 Apache poi 图表中重新定位 XDDFChartLegend 图例

How to reposition XDDFChartLegend legends in Apache poi chart

提问人:raymax 提问时间:11/15/2023 最后编辑:Regraymax 更新时间:11/15/2023 访问量:39

问:

我正在使用 Apache POI 创建多个圆环图。当标签字符串大小很大并且添加更多标签时,我会遇到问题。在 Apache POI 文档中,我在 XDDFChartLegend 中没有找到可以调整边距的方法。我还希望这些标签适合分配给传奇的空间,无论大小。

enter image description here

在上图中,可以观察到

  1. 当图例的字符串大小较大时,它会叠加在图表上。此外,还有 10 个传奇。但只显示 8 个。

  2. 当图例的字符串大小较小时,将显示所有图例。

    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(true);
    
    //setting legend size
    XDDFTextBody legendTextBody = new XDDFTextBody(legend);
    legendTextBody.getXmlObject().addNewBodyPr();
    legendTextBody.addNewParagraph().addDefaultRunProperties().setFontSize(6d);
    legend.setTextBody(legendTextBody);
    
    
    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);
    
    // Do not auto delete the title; is necessary for showing title in Calc
    if (chart.getCTChart().getAutoTitleDeleted() == null) chart.getCTChart().addNewAutoTitleDeleted();
    chart.getCTChart().getAutoTitleDeleted().setVal(false);
    
    // Data point colors; is necessary for showing data points in Calc
    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));
    }
    
    // Add data labels
    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 area (chartspace) without border line
    chart.getCTChartSpace().addNewSpPr().addNewLn().addNewNoFill();
    // changing background color
    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});
    // set plot area size
    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% from left
    chart.getCTChart().getPlotArea().getLayout().getManualLayout().addNewY().setVal(0.10); //10% from top
    chart.getCTChart().getPlotArea().getLayout().getManualLayout().addNewW().setVal(0.80); //80% width
    chart.getCTChart().getPlotArea().getLayout().getManualLayout().addNewH().setVal(0.60); //60% height
    
    return chart;
    

    }

java spring-boot apache-poi

评论


答:

2赞 Axel Richter 11/15/2023 #1

这与图例的定位无关。这是通过这意味着图例位于图表空间的底部并增长到顶部来完成的。legend.setPosition(LegendPosition.BOTTOM);

但通常情况下,永远没有足够的空间。因此,传奇成长为适合内容的情节区域。

为了避免这种情况,唯一的可能是缩小图例区域的大小。这类似于设置绘图区域大小,您已经执行了的操作。

XSSFChart chart = ...

// set legend area size
if (!chart.getCTChart().getLegend().isSetLayout()) {
  chart.getCTChart().getLegend().addNewLayout();
}
if (chart.getCTChart().getLegend().getLayout().isSetManualLayout()) {
  chart.getCTChart().getLegend().getLayout().unsetManualLayout();
}
chart.getCTChart().getLegend().getLayout().addNewManualLayout();
chart.getCTChart().getLegend().getLayout().getManualLayout().addNewXMode().setVal(
  org.openxmlformats.schemas.drawingml.x2006.chart.STLayoutMode.EDGE);
chart.getCTChart().getLegend().getLayout().getManualLayout().addNewYMode().setVal(
  org.openxmlformats.schemas.drawingml.x2006.chart.STLayoutMode.EDGE);
chart.getCTChart().getLegend().getLayout().getManualLayout().addNewX().setVal(0.0); //0% from left
chart.getCTChart().getLegend().getLayout().getManualLayout().addNewY().setVal(0.70); //70% from top
chart.getCTChart().getLegend().getLayout().getManualLayout().addNewW().setVal(1.00); //100% width
chart.getCTChart().getLegend().getLayout().getManualLayout().addNewH().setVal(0.30); //30% height

但现在图例内容不会更适合图例大小。

可以为图例内容设置较小的字体大小,如下所示:

//set legend default font size
if (chart.getCTChart().getLegend().isSetTxPr()) {
  chart.getCTChart().getLegend().unsetTxPr();
}
chart.getCTChart().getLegend().addNewTxPr();
chart.getCTChart().getLegend().getTxPr().addNewBodyPr();
chart.getCTChart().getLegend().getTxPr().addNewP();
chart.getCTChart().getLegend().getTxPr().getPArray(0).addNewPPr();
chart.getCTChart().getLegend().getTxPr().getPArray(0).getPPr().addNewDefRPr().setSz(600); // default font size 6pt

但这很快导致无法阅读的图例文本。

然后,必须通过使锚点变大来设置图表空间。row2

评论

0赞 raymax 11/15/2023
嗨@Axel里希特,是否有可能为传奇人物提供固定的起始位置?
1赞 Axel Richter 11/15/2023
@raymax:不明白这个要求。通过,您设置起始位置。你的意思是以像素、点、厘米左右为单位的固定位置?那会有什么好处?.addNewX().setVal(0.0).addNewY().setVal(0.70)
0赞 raymax 11/15/2023
我想从特定的 Row 和 Col 开始图例。 但是,是的,我也可以通过 .addNewX().setVal(0.0) 和 .addNewY().setVal(0.70) 来做到这一点。
1赞 Axel Richter 11/15/2023
@raymax:“从特定的行和列开始图例”:不可能。图表图例未锚定到工作表。图表图例是图表的一部分,只有整个图表形状锚定到工作表。