如何将 XDDF 图表添加到特定段落 运行(即 table 中的单元格)?兴趣点4.0.1

How do you add an XDDF Chart to a specific paragraph run (i.e. a cell in a table)? POI 4.0.1

我想将图表添加到 XWPFDocument 中的特定 table 单元格。我想要 table 单元格中的图表,以便我可以确保与以后添加的其他元素对齐。那么,

已经尝试过:

  1. 创建图表以反映在 XWPFDocument.createChart()
  2. 中找到的代码
  3. 使用 XWPFRun.addChart() 和 RelationPart.getRelationship.getId() 我从
  4. 到达table
  5. 的具体位置后使用document.createChart()
  6. 尝试制作图表 -> XDDFDrawing 并将其添加到 运行 到 run.getCTR.addDrawing...我不认为它会这样方式?

/代码示例

// Create a document with some initial text
XWPFDocument document = new XWPFDocument();
XWPFParagraph tmpParagraph = document.createParagraph();
XWPFRun tmpRun = tmpParagraph.createRun();
tmpRun.setText("text");
tmpRun.setFontSize(18);

// Try making the chart
// the same code as here, 
try{
    String[] categories = new String[]{"1","2","3","4","5","6","7","8","9"};
    Double[] values1 = new Double[]{1d,2d,3d,4d,5d,6d,7d,8d,9d};
    Double[] values2 = new Double[]{200d,300d,400d,500d,600d,700d,800d,900d,1000d};

    // create the chart
    // XWPFChart chart = document.createChart(7* Units.EMU_PER_CENTIMETER, 7*Units.EMU_PER_CENTIMETER);

    // Try to make a chart stand alone
    // using the same code as here, 
    // https://svn.apache.org/viewvc/poi/tags/REL_4_0_1/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java?view=markup

    int chartNumber = document.getCharts().size();
    POIXMLDocumentPart.RelationPart rp = document.createRelationship(XWPFRelation.CHART, XWPFFactory.getInstance(), chartNumber, false);
    XWPFChart chart = rp.getDocumentPart();
    chart.setChartIndex(chartNumber);
    chart.setChartBoundingBox(7* Units.EMU_PER_CENTIMETER, 7*Units.EMU_PER_CENTIMETER);
    document.getCharts().add(chart);


    // This stuff to make the chart is not part of the question
        // create data sources
        int numOfPoints = categories.length;
        String categoryDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 0, 0));
        String valuesDataRange1 = chart.formatRange(new CellRangeAddress(1, numOfPoints, 1, 1));
        String valuesDataRange2 = chart.formatRange(new CellRangeAddress(1, numOfPoints, 2, 2));
        XDDFDataSource<String> categoriesData = XDDFDataSourcesFactory.fromArray(categories, categoryDataRange, 0);
        XDDFNumericalDataSource<Double> valuesData1 = XDDFDataSourcesFactory.fromArray(values1, valuesDataRange1, 1);
        XDDFNumericalDataSource<Double> valuesData2 = XDDFDataSourcesFactory.fromArray(values2, valuesDataRange2, 2);

        // first line chart
        XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
        XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
        leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
        XDDFChartData data = chart.createData(ChartTypes.LINE, bottomAxis, leftAxis);
        XDDFChartData.Series series = data.addSeries(categoriesData, valuesData1);
        chart.plot(data);

        solidLineSeries(data, 0, PresetColor.BLUE);

        // second line chart
        // bottom axis must be there but must not be visible
        bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
        bottomAxis.setVisible(false);

        XDDFValueAxis rightAxis = chart.createValueAxis(AxisPosition.RIGHT);
        rightAxis.setCrosses(AxisCrosses.MAX);

        // set correct cross axis
        bottomAxis.crossAxis(rightAxis);
        rightAxis.crossAxis(bottomAxis);

        data = chart.createData(ChartTypes.LINE, bottomAxis, rightAxis);
        series = data.addSeries(categoriesData, valuesData2);
        chart.plot(data);

        // correct the id and order, must not be 0 again because there is one line series already
        chart.getCTChart().getPlotArea().getLineChartArray(1).getSerArray(0).getIdx().setVal(1);
        chart.getCTChart().getPlotArea().getLineChartArray(1).getSerArray(0).getOrder().setVal(1);

        solidLineSeries(data, 0, PresetColor.RED);


// End of extra stuff
// Back to question

    // Add the chart by relation id
    XWPFParagraph p2 = document.createParagraph();
    XWPFRun r2 = p2.createRun();
    r2.addChart(rp.getRelationship().getId());

    // Add a new run to try to add a new drawing?
    XWPFRun r3 = p2.createRun();
    CTDrawing drawing = r3.getCTR().addNewDrawing();
    ????

}catch(Exception e){}

当我通过 r2.addChart() 添加图表时,没有任何显示?所以也许我没有正确创建图表?或者我没有把它添加到 运行 正确?

是否可以将图表转换为绘图?

This shows the XML I am trying to mimic (copied from the other image)

  1. 段落
  2. 运行
  3. 画画?
  4. 图表?

This is the expected output

通常 apache poi 很难扩展他们的代码,因为关于哪些方法是受保护或私有的奇怪决定。在这种情况下,它缺少 XWPFDocument 中的方法 public XWPFChart createChart(int width, int height, XWPFRun run),因为现有方法总是将图表放入文档正文中新创建的段落中的新创建的 运行 中。但是简单地扩展 XWPFDocument 几乎是不可能的,因为所需的方法是受保护的或私有的。

我发现的最简单的方法是首先使用 document.createChart() 将图表放在文档的第一段中。然后删除第一段。图表部分保留(至少使用 apache poi 4.1.0)。然后在需要的文本 运行 处附上新的图表部分。但即使这样也不是那么容易,因为 XWPFChart.attach 也受到保护。所以需要使用java.lang.reflect

完整示例:

import java.io.*;

import org.apache.poi.xwpf.usermodel.*;

import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.util.Units;

import org.apache.poi.xddf.usermodel.*;
import org.apache.poi.xddf.usermodel.chart.*;

public class CreateWordXDDFChartTwoLinesInTable {

 public static void main(String[] args) throws Exception {
  try (XWPFDocument document = new XWPFDocument()) {

   // create the data
   String[] categories = new String[]{"1","2","3","4","5","6","7","8","9"};
   Double[] values1 = new Double[]{1d,2d,3d,4d,5d,6d,7d,8d,9d};
   Double[] values2 = new Double[]{200d,300d,400d,500d,600d,700d,800d,900d,1000d};

   // create the chart
   // this also puts the chart into a run in a new created paragraph
   XWPFChart chart = createChart(document, categories, values1, values2);
   // remove the first paragraph since we need the chart being elsewhere
   document.removeBodyElement(0);

   XWPFParagraph paragraph = document.createParagraph();
   XWPFRun run = paragraph.createRun();
   run.setText("First paragraph having first text run.");

   // create the table
   XWPFTable table = document.createTable(1,2);
   table.setWidth("100%");
   // create first run in first table cell
   paragraph = table.getRow(0).getCell(0).getParagraphArray(0);
   run = paragraph.createRun();
   // attach the chart here
   java.lang.reflect.Method attach = XWPFChart.class.getDeclaredMethod("attach", String.class, XWPFRun.class);
   attach.setAccessible(true);
   attach.invoke(chart, document.getRelationId(chart), run);
   chart.setChartBoundingBox(7*Units.EMU_PER_CENTIMETER, 7*Units.EMU_PER_CENTIMETER);

   // set text in second table cell
   paragraph = table.getRow(0).getCell(1).getParagraphArray(0);
   run = paragraph.createRun();
   run.setText("Other text goes in the 2");
   run = paragraph.createRun();
   run.setSubscript(VerticalAlign.SUPERSCRIPT);
   run.setText("nd");
   run = paragraph.createRun();
   run.setText(" cell.");

   paragraph = document.createParagraph();
   run = paragraph.createRun();
   run.setText("Lorem ipsum...");

   // Write the output to a file
   try (FileOutputStream fileOut = new FileOutputStream("CreateWordXDDFChartTwoLinesInTable.docx")) {
    document.write(fileOut);
   }
  }
 }

 private static XWPFChart createChart(XWPFDocument document, 
   String[] categories, Double[] values1, Double[] values2) throws Exception {

   // create the chart
   XWPFChart chart = document.createChart();

   // create data sources
   int numOfPoints = categories.length;
   String categoryDataRange = chart.formatRange(new CellRangeAddress(1, numOfPoints, 0, 0));
   String valuesDataRange1 = chart.formatRange(new CellRangeAddress(1, numOfPoints, 1, 1));
   String valuesDataRange2 = chart.formatRange(new CellRangeAddress(1, numOfPoints, 2, 2));
   XDDFDataSource<String> categoriesData = XDDFDataSourcesFactory.fromArray(categories, categoryDataRange, 0);
   XDDFNumericalDataSource<Double> valuesData1 = XDDFDataSourcesFactory.fromArray(values1, valuesDataRange1, 1);
   XDDFNumericalDataSource<Double> valuesData2 = XDDFDataSourcesFactory.fromArray(values2, valuesDataRange2, 2);

   // first line chart
   XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
   XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
   leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
   XDDFChartData data = chart.createData(ChartTypes.LINE, bottomAxis, leftAxis);
   XDDFChartData.Series series = data.addSeries(categoriesData, valuesData1);
   chart.plot(data);

   solidLineSeries(data, 0, PresetColor.BLUE);

   // second line chart
   // bottom axis must be there but must not be visible
   bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
   bottomAxis.setVisible(false);

   XDDFValueAxis rightAxis = chart.createValueAxis(AxisPosition.RIGHT);
   rightAxis.setCrosses(AxisCrosses.MAX);

   // set correct cross axis
   bottomAxis.crossAxis(rightAxis);
   rightAxis.crossAxis(bottomAxis);

   data = chart.createData(ChartTypes.LINE, bottomAxis, rightAxis);
   series = data.addSeries(categoriesData, valuesData2);
   chart.plot(data);

   // correct the id and order, must not be 0 again because there is one line series already
   chart.getCTChart().getPlotArea().getLineChartArray(1).getSerArray(0).getIdx().setVal(1);
   chart.getCTChart().getPlotArea().getLineChartArray(1).getSerArray(0).getOrder().setVal(1);

   solidLineSeries(data, 0, PresetColor.RED);

   return chart;
 }

 private static void solidLineSeries(XDDFChartData data, int index, PresetColor color) {
  XDDFSolidFillProperties fill = new XDDFSolidFillProperties(XDDFColor.from(color));
  XDDFLineProperties line = new XDDFLineProperties();
  line.setFillProperties(fill);
  XDDFChartData.Series series = data.getSeries().get(index);
  XDDFShapeProperties properties = series.getShapeProperties();
  if (properties == null) {
   properties = new XDDFShapeProperties();
  }
  properties.setLineProperties(line);
  series.setShapeProperties(properties);
 }
}

这是在 XWPFRun 运行 中添加 XWPFChart 图表的简单函数。我们还可以使用自定义 java class 扩展 XWPFDocument 并创建一个可以轻松访问受保护变量的函数。

protected XWPFDocument addChartToRun(XWPFDocument document, XWPFChart oldChart, XWPFRun run) {
        try {
            //find number of charts available
            int chartNumber = 0;
            for (int i = 0; i < document.getRelations().size(); i++) {
                if (document.getRelations().get(i) instanceof XWPFChart) {
                    chartNumber++;
                }
            }

            //creating new chart from oldChart
            POIXMLDocumentPart.RelationPart rp = document.createRelationship(
                    XWPFRelation.CHART, XWPFFactory.getInstance(), chartNumber + 1, false);
            XWPFChart chart = rp.getDocumentPart();
            chart.setChartIndex(chartNumber);
            Method attach = XWPFChart.class.getDeclaredMethod("attach", String.class, XWPFRun.class);
            attach.setAccessible(true);
            attach.invoke(chart, rp.getRelationship().getId(), run);
            chart.setChartBoundingBox(200, 200);


            // copy old chart style
            if (oldChart != null) {
                chart.getCTChartSpace().setSpPr(oldChart.getCTChartSpace().getSpPr());
                chart.getCTChartSpace().setChart(oldChart.getCTChartSpace().getChart());
                chart.getChartSeries().addAll(oldChart.getChartSeries());
            } else {
                //oldChart not found
                throw new NullPointerException();
            }

            return document;
        } catch (Exception e) {
            log.error("Error while copying chart...");
        }
        return null;
    }