简体   繁体   English

Java 使用poi编辑ppt条形图

[英]Java edit bar chart in ppt by using poi

I am a newbie to using POI at work.Now i'm going to use POI in java to read a bar chart in the PPT.I've added several series x to it in advance,which are the column headers of the excel to which the bar graph belongs.我是在工作中使用POI的新手。现在我将使用java中的POI来读取PPT中的条形图。我已经提前添加了几个系列x,它们是excel的列标题到条形图属于哪个。 But i can only read the first three columns by default with the POI.In addition,once I modify the column header of the bar chart,or want to add a fourth column(thar is,add a color)to a bar chart with only three columns, the bar chart cannot be edited when I open the PPT,indicating that the node of the bar chart is damaged.但是我只能默认使用 POI 读取前三列。此外,一旦我修改了条形图的 header 列,或者想在条形图中添加第四列(即,添加颜色),只有三栏,打开PPT时无法编辑柱状图,说明柱状图的节点损坏。 So is there a master who can help talking how to use POI to prroperly add a color to the bar chart(add a series)?那么有没有高手可以帮忙讲讲如何使用POI来正确的给柱状图加颜色(加系列)?

Eg: when I debug to the " long ptCatCnt = catDataSource.getStrRef().getStrCache().getPtCount().getVal(); It show nullpointerexecption, I don't know how structure in ppt the bar-chart is.So I want know how to update the bar-chart。例如:当我调试到“ long ptCatCnt = catDataSource.getStrRef().getStrCache().getPtCount().getVal(); 它显示nullpointerexecption,我不知道条形图在ppt中的结构是什么。所以我想知道如何更新条形图。

The code is:代码是:

public class PPTDemo {公共 class PPTDemo {

public void run() {
    try {
        SlideShow slideShow = SlideShowFactory.create(new File("./res/1.pptx"));

        for (Object o : slideShow.getSlides()) {
            XSLFSlide slider = (XSLFSlide) o;

            // 第一页
            if (slider.getSlideNumber() == 1) {
                for (POIXMLDocumentPart.RelationPart part : slider.getRelationParts()) {
                    POIXMLDocumentPart documentPart = part.getDocumentPart();
                    // 是图表
                    if (documentPart instanceof XSLFChart) {
                        XSLFChart chart = (XSLFChart) documentPart;

                        // 查看里面的图表数据,才能知道是什么图表
                        CTPlotArea plot = chart.getCTChart().getPlotArea();

                        // 测试数据
                        List<SeriesData> seriesDatas = Arrays.asList(
                                new SeriesData("", Arrays.asList(
                                        new NameDouble("行1", Math.random() * 100),
                                        new NameDouble("行2", Math.random() * 100),
                                        new NameDouble("行3", Math.random() * 100),
                                        new NameDouble("行4", Math.random() * 100),
                                        new NameDouble("行5", Math.random() * 100)
                                )),
                                new SeriesData("", Arrays.asList(
                                        new NameDouble("行1", Math.random() * 100),
                                        new NameDouble("行2", Math.random() * 100),
                                        new NameDouble("行3", Math.random() * 100),
                                        new NameDouble("行4", Math.random() * 100),
                                        new NameDouble("行5", Math.random() * 100)
                                ))
                        );
                        XSSFWorkbook workbook = chart.getWorkbook();
                        XSSFSheet sheet = workbook.getSheetAt(0);


                        // 柱状图
                        if (!plot.getBarChartList().isEmpty()) {
                            CTBarChart barChart = plot.getBarChartArray(0);
                            updateChartExcelV(seriesDatas, workbook, sheet);
                            workbook.write(chart.getPackagePart().getOutputStream());

                            int i = 0;
                            for (CTBarSer ser : barChart.getSerList()) {
                                updateChartCatAndNum(seriesDatas.get(i), ser.getTx(), ser.getCat(), ser.getVal());
                                ++i;
                            }
                        }

                        // 饼图
                        else if (!plot.getPieChartList().isEmpty()) {
                            // 示例饼图只有一列数据
                            updateChartExcelV(Arrays.asList(seriesDatas.get(0)), workbook, sheet);
                            workbook.write(chart.getPackagePart().getOutputStream());

                            CTPieChart pieChart = plot.getPieChartArray(0);
                            int i = 0;
                            for (CTPieSer ser : pieChart.getSerList()) {
                                updateChartCatAndNum(seriesDatas.get(i), ser.getTx(), ser.getCat(), ser.getVal());
                                ++i;
                            }
                        }
                    }
                }
            }

        }

        try {
            try (FileOutputStream out = new FileOutputStream("./res/o1.pptx")) {
                slideShow.write(out);
            }
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        } catch (IOException e1) {
            e1.printStackTrace();
        }

    } catch (IOException e) {
        e.printStackTrace();
    } catch (InvalidFormatException e) {
        e.printStackTrace();
    }
}

/**
 * 更新图表的关联 excel, 值是纵向的
 *
 * @param param
 * @param workbook
 * @param sheet
 */
protected void updateChartExcelV(List<SeriesData> seriesDatas, XSSFWorkbook workbook, XSSFSheet sheet) {
    XSSFRow title = sheet.getRow(0);
    for (int i = 0; i < seriesDatas.size(); i++) {
        SeriesData data = seriesDatas.get(i);
        if (data.name != null && !data.name.isEmpty()) {
            // 系列名称,不能修改,修改后无法打开 excel
            //                title.getCell(i + 1).setCellValue(data.name);
        }
        int size = data.value.size();
        for (int j = 0; j < size; j++) {
            XSSFRow row = sheet.getRow(j + 1);
            if (row == null) {
                row = sheet.createRow(j + 1);
            }
            NameDouble cellValu = data.value.get(j);
            XSSFCell cell = row.getCell(0);
            if (cell == null) {
                cell = row.createCell(0);
            }
            cell.setCellValue(cellValu.name);

            cell = row.getCell(i + 1);
            if (cell == null) {
                cell = row.createCell(i + 1);
            }
            cell.setCellValue(cellValu.value);
        }
        int lastRowNum = sheet.getLastRowNum();
        if (lastRowNum > size) {
            for (int idx = lastRowNum; idx > size; idx--) {
                sheet.removeRow(sheet.getRow(idx));
            }
        }
    }
}

/**
 * 更新 chart 的缓存数据
 *
 * @param data          数据
 * @param serTitle      系列的标题缓存
 * @param catDataSource 条目的数据缓存
 * @param numDataSource 数据的缓存
 */
protected void updateChartCatAndNum(SeriesData data, CTSerTx serTitle, CTAxDataSource catDataSource,
                                    CTNumDataSource numDataSource) {

    // 更新系列标题
    //        serTitle.getStrRef().setF(serTitle.getStrRef().getF()); //
    //        serTitle.getStrRef().getStrCache().getPtArray(0).setV(data.name);

    // TODO cat 也可能是 numRef
    long ptCatCnt = catDataSource.getStrRef().getStrCache().getPtCount().getVal();
    long ptNumCnt = numDataSource.getNumRef().getNumCache().getPtCount().getVal();
    int dataSize = data.value.size();
    for (int i = 0; i < dataSize; i++) {
        NameDouble cellValu = data.value.get(i);
        CTStrVal cat = ptCatCnt > i ? catDataSource.getStrRef().getStrCache().getPtArray(i)
                : catDataSource.getStrRef().getStrCache().addNewPt();
        cat.setIdx(i);
        cat.setV(cellValu.name);

        CTNumVal val = ptNumCnt > i ? numDataSource.getNumRef().getNumCache().getPtArray(i)
                : numDataSource.getNumRef().getNumCache().addNewPt();
        val.setIdx(i);
        val.setV(String.format("%.2f", cellValu.value));

    }

    // 更新对应 excel 的range
    catDataSource.getStrRef().setF(
            replaceRowEnd(catDataSource.getStrRef().getF(),
                    ptCatCnt,
                    dataSize));
    numDataSource.getNumRef().setF(
            replaceRowEnd(numDataSource.getNumRef().getF(),
                    ptNumCnt,
                    dataSize));

    // 删除多的
    if (ptNumCnt > dataSize) {
        for (int idx = dataSize; idx < ptNumCnt; idx++) {
            catDataSource.getStrRef().getStrCache().removePt(dataSize);
            numDataSource.getNumRef().getNumCache().removePt(dataSize);
        }
    }
    // 更新个数
    catDataSource.getStrRef().getStrCache().getPtCount().setVal(dataSize);
    numDataSource.getNumRef().getNumCache().getPtCount().setVal(dataSize);
}

/**
 * 替换 形如: Sheet1!$A$2:$A$4 的字符
 *
 * @param range
 * @return
 */
public static String replaceRowEnd(String range, long oldSize, long newSize) {
    Pattern pattern = Pattern.compile("(:\\$[A-Z]+\\$)(\\d+)");
    Matcher matcher = pattern.matcher(range);
    if (matcher.find()) {
        long old = Long.parseLong(matcher.group(2));
        return range.replaceAll("(:\\$[A-Z]+\\$)(\\d+)", "$1" + Long.toString(old - oldSize + newSize));
    }
    return range;
}

/**
 * 一个系列的数据
 */
public static class SeriesData {

    /**
     * value 系列的名字
     */
    public String name;

    public List<NameDouble> value;

    public SeriesData(java.util.List<NameDouble> value) {
        this.value = value;
    }

    public SeriesData(String name, List<NameDouble> value) {
        this.name = name;
        this.value = value;
    }

    public SeriesData() {
    }
}


/**
 *
 */
public class NameDouble {

    public String name;

    /**
     */
    public double value;

    public NameDouble(String name, double value) {
        this.name = name;
        this.value = value;
    }

    @SuppressWarnings("unused")
    public NameDouble() {
    }

}

} }

Using current apache poi 5.0.0 updating a chart in PowerPoint is possible using the new XDDF classes.使用当前的apache poi 5.0.0可以使用新的XDDF类更新PowerPoint中的图表。 That avoids using the ooxml-schemas classes ( CT... classes) directly.这避免了直接使用ooxml-schemas类( CT...类)。 Using CT classes directly is error prone and needs very good knowlegde about the internally XML structure of Office Open XML .直接使用CT类容易出错,需要非常了解Office Open XML的内部XML结构。

What one needs to know is that chart data is stored in an embedded Excel workbook.需要知道的是,图表数据存储在嵌入式Excel工作簿中。 So while updating the data the need is always updating the data in that workbook and updating the data in the chart.因此,在更新数据时,需要始终更新该工作簿中的数据并更新图表中的数据。

The following example is a minimal reproducible example for how to do this.以下示例是如何执行此操作的最小可重现示例。

The template BarChartSample.pptx contained a bar chart having only one series an one category.模板BarChartSample.pptx包含一个条形图,其中只有一个系列和一个类别。 It defines the chart format.它定义了图表格式。 It looks like so:它看起来像这样:

在此处输入图像描述

The code is like this:代码是这样的:

import java.io.FileInputStream;
import java.io.FileOutputStream;

import org.apache.poi.xslf.usermodel.*;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.AreaReference;

import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumns;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;

public class PowerPointChangeChartData {

 //patched version of XSSFTable.updateHeaders, see https://stackoverflow.com/questions/55532006/renaming-headers-of-xssftable-with-apache-poi-leads-to-corrupt-xlsx-file/55539181#55539181
 static void updateHeaders(XSSFTable table) {
  XSSFSheet sheet = (XSSFSheet)table.getParent();
  CellReference ref = table.getStartCellReference();

  if (ref == null) return;

  int headerRow = ref.getRow();
  int firstHeaderColumn = ref.getCol();
  XSSFRow row = sheet.getRow(headerRow);
  DataFormatter formatter = new DataFormatter();

  if (row != null /*&& row.getCTRow().validate()*/) {
   int cellnum = firstHeaderColumn;
   CTTableColumns ctTableColumns = table.getCTTable().getTableColumns();
   if(ctTableColumns != null) {
    for (CTTableColumn col : ctTableColumns.getTableColumnList()) {
     XSSFCell cell = row.getCell(cellnum);
     if (cell != null) {
      col.setName(formatter.formatCellValue(cell));
     }
     cellnum++;
    }
   }
  }
 }

 static void updateChart(XSLFChart chart, Object[][] data) throws Exception {
  // get chart's data source which is a Excel sheet
  XSSFWorkbook chartDataWorkbook = chart.getWorkbook();
  String sheetName = chartDataWorkbook.getSheetName(0);
  XSSFSheet chartDataSheet = chartDataWorkbook.getSheet(sheetName);
  // current Office uses a table as data source
  // so get that table if present
  XSSFTable chartDataTable = null;
  if (chartDataSheet.getTables().size() > 0) {
   chartDataTable = chartDataSheet.getTables().get(0);
  }

  if (chart.getChartSeries().size() == 1) { // we will process only one chart data
   XDDFChartData chartData = chart.getChartSeries().get(0);
   if (chartData.getSeriesCount() == 1) { // we will process only templates having one series

    int rMin = 1; // first row (0) is headers row
    int rMax = data.length - 1;

    // set new category data
    XDDFCategoryDataSource category = null;
    int c = 0;
    for (int r = rMin; r <= rMax; r++) {
     XSSFRow row = chartDataSheet.getRow(r); if (row == null) row = chartDataSheet.createRow(r);
     XSSFCell cell = row.getCell(c); if (cell == null) cell = row.createCell(c);
     cell.setCellValue((String)data[r][c]); // in sheet
    }
    category = XDDFDataSourcesFactory.fromStringCellRange(chartDataSheet, new CellRangeAddress(rMin,rMax,c,c)); // in chart

    // series 1, is present already 
    c = 1;

    // set new values in sheet and in chart
    XDDFNumericalDataSource<Double> values = null;
    for (int r = rMin; r < rMax+1; r++) {
     XSSFRow row = chartDataSheet.getRow(r); if (row == null) row = chartDataSheet.createRow(r);
     XSSFCell cell = row.getCell(c); if (cell == null) cell = row.createCell(c);
     cell.setCellValue((Double)data[r][c]); // in sheet
    }
    values = XDDFDataSourcesFactory.fromNumericCellRange(chartDataSheet, new CellRangeAddress(rMin,rMax,c,c)); 
    XDDFChartData.Series series1 = chartData.getSeries(0);
    series1.replaceData(category, values); // in chart

    // set new title in sheet and in chart
    String series1Title = (String)data[0][c];
    chartDataSheet.getRow(0).getCell(c).setCellValue(series1Title); // in sheet
    series1.setTitle(series1Title, new CellReference(sheetName, 0, c, true, true)); // in chart

    series1.plot(); 

    //further series, all new created
    int seriesCount = data[0].length - 1;
    for (int s = 2; s <= seriesCount; s++) {
     c++;

     // set new values
     for (int r = rMin; r < rMax+1; r++) {
      XSSFRow row = chartDataSheet.getRow(r); if (row == null) row = chartDataSheet.createRow(r);
      XSSFCell cell = row.getCell(c); if (cell == null) cell = row.createCell(c);
      cell.setCellValue((Double)data[r][c]); // in sheet
     }
     values = XDDFDataSourcesFactory.fromNumericCellRange(chartDataSheet, new CellRangeAddress(rMin,rMax,c,c)); 
     XDDFChartData.Series series = chartData.addSeries(category, values); // in chart
 
     // set new title
     String seriesTitle = (String)data[0][c];
     XSSFCell cell = chartDataSheet.getRow(0).getCell(c); if (cell == null) cell = chartDataSheet.getRow(0).createCell(c);
     cell.setCellValue(seriesTitle); // in sheet
     series.setTitle(seriesTitle, new CellReference(sheetName, 0, c, true, true)); // in chart

     series.plot();
    }

    // update the table if present
    if (chartDataTable != null) {
     CellReference topLeft = new CellReference(chartDataSheet.getRow(0).getCell(0));
     CellReference bottomRight = new CellReference(chartDataSheet.getRow(rMax).getCell(c));
     AreaReference tableArea = chartDataWorkbook.getCreationHelper().createAreaReference(topLeft, bottomRight);
     chartDataTable.setArea(tableArea);
     updateHeaders(chartDataTable);
    }

   }
  }
 }  

 public static void main(String[] args) throws Exception {

  String filePath = "BarChartSample.pptx"; // has template line chart
  String filePathNew = "BarChartSample_New.pptx";

  Object[][] data = new Object[][] { // new data 3 series, 5 categories
   {"", "Amount", "Values", "Others"}, // series title
   {"Jan", 321d, 456d, 222d}, // category 1
   {"Feb", 543d, 567d, 111d}, // category 2
   {"Mar", 432d, 123d, 333d}, // category 3
   {"Apr", 210d, 234d, 444d}, // category 4
   {"May", 198d, 345d, 444d} // category 5
  };

  XMLSlideShow slideShow = new XMLSlideShow(new FileInputStream(filePath));

  XSLFChart chart = slideShow.getCharts().get(0);

  updateChart(chart, data);

  FileOutputStream out = new FileOutputStream(filePathNew); 
  slideShow.write(out);
  out.close();
  slideShow.close();
 }

}

The result looks like so:结果如下所示:

在此处输入图像描述

Hint: The code uses a patched version of XSSFTable.updateHeaders as the current version fails updating the table headers.提示:代码使用XSSFTable.updateHeaders的修补版本,因为当前版本无法更新表头。 See Renaming headers of XSSFTable with Apache Poi leads to corrupt XLSX-file .请参阅Renaming headers of XSSFTable with Apache Poi 导致损坏的 XLSX-file

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM