0

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

  • 如何在不将其添加到文档的情况下创建 XWPF/XDDF 图表(例如,不使用 document.createChart())?

  • 如何获取该图表并将其添加到特定段落/运行(例如,在表格单元格中创建的那个)?

已经尝试过:

  1. 创建一个图表镜像XWPFDocument.createChart()中的代码
  2. XWPFRun.addChart()与我使用的 R elationPart.getRelationship.getId()一起使用
  3. 到达表格的具体位置后使用document.createChart()
  4. 试图制作图表 - > 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, https://stackoverflow.com/questions/55192804/how-do-i-add-a-second-line-with-a-second-axis-to-an-xddfchart-in-poi-4-0-1
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() 添加图表时,什么都没有显示?所以也许我没有正确创建图表?或者我没有正确地将它添加到运行中?

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

这显示了我试图模仿的 XML(从另一张图片复制)

  1. 段落
  2. 绘画?
  3. 图表?

这是预期的输出

4

2 回答 2

2

由于关于哪些方法受保护或私有的奇怪决定,通常apache poi很难扩展他们的代码。在这种情况下,它缺少一种方法public XWPFChart createChart(int width, int height, XWPFRun run)XWPFDocument因为现有方法总是将图表放入文档正文中新创建的段落中的新创建运行中。但是简单地扩展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);
 }
}
于 2019-10-12T07:24:13.160 回答
1

这是一个在 XWPFRun 运行中添加 XWPFChart 图表的简单函数。我们还可以使用自定义 java 类扩展 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;
    }
于 2021-07-21T13:50:35.700 回答