是否可以使用 Apache POI 创建将源数据作为数据透视表的数据透视图?

问题描述 投票:0回答:1

我可以单独使用 apache POI 创建数据透视表和数据透视图。但我正在尝试创建柱形图形式的数据透视表,而不是直接从工作表数据创建。我尝试在这里寻找指导,但找不到任何指导。如果可能的话,您能指导我正确的方向吗?谢谢你。

更新

下面是我之前的示例代码,它在一个工作表中填充数据,然后使用第一个工作表中填充的数据在另一个工作表中创建数据透视表和数据透视图。

但我希望将数据透视表作为数据透视图的源,而不是其他工作表中的原始数据。这样我就可以动态隐藏某些记录。例如,如果我需要在图表中仅显示亚洲国家,我可以在数据透视表中过滤它们,该数据透视表将仅显示图表中的这些国家。我不知道如何才能做到这一点。

import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.DataConsolidateFunction;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xssf.usermodel.*;
import org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor;
import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

public class ColumnChart {

    public static void main(String[] args) {
        String workbookName = "Population.xlsx";
        String dataSheetName = "DataSheet";
        String pivotSheetName = "PivotSheet";
        try (XSSFWorkbook workbook = new XSSFWorkbook();
             FileOutputStream fos = new FileOutputStream(workbookName)) {
            prepareDataSheet(workbook, dataSheetName);
            createPivotTable(workbook, pivotSheetName, dataSheetName);
            createPivotChart(workbook, pivotSheetName, dataSheetName);
            workbook.write(fos);

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

    }


    private static void prepareDataSheet(XSSFWorkbook workbook, String dataSheetName) {
        List<String> continentList = Arrays.asList("Asia", "Asia", "America", "Asia", "Asia", "America", "Africa", "Asia");
        List<String> countryList = Arrays.asList("China", "India", "United States", "Indonesia", "Pakistan", "Brazil", "Nigeria", "Bangladesh");
        List<Long> populationList = Arrays.asList(1411778724L, 1386020955L, 332943364L, 271350000L, 225200000L, 214130142L, 211401000L, 171933178L);
        XSSFSheet dataSheet = workbook.createSheet(dataSheetName);
        Row row = dataSheet.createRow(0);
        row.createCell(0).setCellValue("Continent");
        row.createCell(1).setCellValue("Country");
        row.createCell(2).setCellValue("Population");
        int rowCounter = 1;
        for (rowCounter = 1; rowCounter <= countryList.size(); rowCounter++) {
            row = dataSheet.createRow(rowCounter);
            row.createCell(0).setCellValue(continentList.get(rowCounter - 1));
            row.createCell(1).setCellValue(countryList.get(rowCounter - 1));
            row.createCell(2).setCellValue(populationList.get(rowCounter - 1));
        }


    }

    private static void createPivotTable(XSSFWorkbook workbook, String pivotSheetName, String dataSheetName) {
        XSSFSheet pivotSheet = workbook.createSheet(pivotSheetName);
        XSSFSheet dataSheet = workbook.getSheet(dataSheetName);
        CellReference leftTop = new CellReference(0, 0);
        CellReference rightBottom = new CellReference(8, 2);
        CellReference pivotLocation = new CellReference(1, 1);
        AreaReference sourceDataAreaRef = new AreaReference(leftTop, rightBottom, SpreadsheetVersion.EXCEL2007);
        XSSFPivotTable pivotTable = pivotSheet.createPivotTable(sourceDataAreaRef, pivotLocation, dataSheet);
        pivotTable.addRowLabel(0);
        pivotTable.addRowLabel(1);
        pivotTable.addColumnLabel(DataConsolidateFunction.SUM, 2);

    }

    private static void createPivotChart(XSSFWorkbook workbook, String pivotSheetName, String dataSheetName) {
        XSSFSheet pivotSheet = workbook.getSheet(pivotSheetName);
        XSSFSheet dataSheet = workbook.getSheet(dataSheetName);
        XSSFDrawing drawing = pivotSheet.createDrawingPatriarch();
        XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 4, 2, 10, 20);

        XSSFChart chart = drawing.createChart(anchor);
        chart.setTitleText("Population By Countries");
        chart.setTitleOverlay(false);

        XDDFChartLegend legend = chart.getOrAddLegend();
        legend.setPosition(LegendPosition.BOTTOM);

        XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
        bottomAxis.setTitle("Country");
        XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
        leftAxis.setTitle("Population");
        leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);

        XDDFDataSource<String> countries = XDDFDataSourcesFactory.fromStringCellRange(dataSheet,
                new CellRangeAddress(0, 8, 1, 1));

        XDDFNumericalDataSource<Double> values = XDDFDataSourcesFactory.fromNumericCellRange(dataSheet,
                new CellRangeAddress(0, 8, 2, 2));

        XDDFChartData data = chart.createData(ChartTypes.BAR, bottomAxis, leftAxis);
        XDDFChartData.Series series1 = data.addSeries(countries, values);
        series1.setTitle("Country", null);
        data.setVaryColors(false);
        XDDFBarChartData bar = (XDDFBarChartData) data;
        bar.setBarDirection(BarDirection.COL);



        CTSolidColorFillProperties fillProp = CTSolidColorFillProperties.Factory.newInstance();
        CTSRgbColor rgb = CTSRgbColor.Factory.newInstance();
        rgb.setVal(new byte[]{(byte) 233, (byte) 87, (byte)162});
        fillProp.setSrgbClr(rgb);
        CTShapeProperties ctShapeProperties = CTShapeProperties.Factory.newInstance();

        ctShapeProperties.setSolidFill(fillProp);

        chart.getCTChart().getPlotArea().getBarChartList().get(0).getSerList().get(0).setSpPr(ctShapeProperties);

        chart.plot(data);
    }
}

excel apache-poi pivot-table pivot-chart
1个回答
1
投票

对于使用 Microsoft Excel 的使用,就像向图表添加数据透视源一样简单。之后,Excel 根据给定的数据透视表呈现图表。

Office Open XML
(
XSSF
) 中,数据透视源由
PivotSource
元素给出,该元素将数据透视表的限定名称设置为名称。合格名称是:
[workbookName]worksheetName!pivotTableName

这是方括号中的工作簿名称,后跟工作表名称,后跟感叹号,后跟数据透视表名称。

在代码中,这看起来像这样:

...
String pivotTableName = pivotTable.getCTPivotTableDefinition().getName();
String qualifiedPivotSourceName = "[" + workbookName + "]" + pivotSheet.getSheetName() + "!" + pivotTableName;
chart.getCTChartSpace().addNewPivotSource().setName(qualifiedPivotSourceName);
...
    

使用它甚至不需要在图表中设置源数据范围,因为 Excel 会从给定的数据透视表中获取数据。因此,只有虚拟数据必须如下给出:

...
XDDFDataSource<String> countries = XDDFDataSourcesFactory.fromArray(new String[]{"dummy"});
XDDFNumericalDataSource<Double> values = XDDFDataSourcesFactory.fromArray(new Double[]{1d});
...

但请注意,这种图表只会在 Microsoft Excel 中显示。其他电子表格计算软件无法正确显示此类图表。因此,应该让图表中的源数据作为后备。

并且您需要设置AxisCrossBetween,以便左轴与类别之间的类别轴交叉。否则,第一个和最后一个类别正好位于交叉点上,并且条形图只有一半可见。

并且您应该将默认值(不使用多级标签)设置为 false。因此各大洲和国家将有多个级别轴标签。

我已经更新了您的完整示例:

import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.DataConsolidateFunction;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xssf.usermodel.*;
import org.openxmlformats.schemas.drawingml.x2006.main.CTLineProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor;
import org.openxmlformats.schemas.drawingml.x2006.main.CTShapeProperties;
import org.openxmlformats.schemas.drawingml.x2006.main.CTSolidColorFillProperties;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

public class ColumnChart {

    public static void main(String[] args) {
        String pathToSave = "./";
        String workbookName = "Population.xlsx";
        String dataSheetName = "DataSheet";
        String pivotSheetName = "PivotSheet";
        try (XSSFWorkbook workbook = new XSSFWorkbook();
             FileOutputStream fos = new FileOutputStream(pathToSave + workbookName)) {
            prepareDataSheet(workbook, dataSheetName);
            XSSFPivotTable pivotTable = createPivotTable(workbook, pivotSheetName, dataSheetName);
            createPivotChart(workbook, workbookName, pivotTable, dataSheetName);
            workbook.getSheetAt(0).setSelected(false);
            workbook.setActiveSheet(1);
            workbook.write(fos);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void prepareDataSheet(XSSFWorkbook workbook, String dataSheetName) {
        List<String> continentList = Arrays.asList("Asia", "Asia", "America", "Asia", "Asia", "America", "Africa", "Asia");
        List<String> countryList = Arrays.asList("China", "India", "United States", "Indonesia", "Pakistan", "Brazil", "Nigeria", "Bangladesh");
        List<Long> populationList = Arrays.asList(1411778724L, 1386020955L, 332943364L, 271350000L, 225200000L, 214130142L, 211401000L, 171933178L);
        XSSFSheet dataSheet = workbook.createSheet(dataSheetName);
        Row row = dataSheet.createRow(0);
        row.createCell(0).setCellValue("Continent");
        row.createCell(1).setCellValue("Country");
        row.createCell(2).setCellValue("Population");
        int rowCounter = 1;
        for (rowCounter = 1; rowCounter <= countryList.size(); rowCounter++) {
            row = dataSheet.createRow(rowCounter);
            row.createCell(0).setCellValue(continentList.get(rowCounter - 1));
            row.createCell(1).setCellValue(countryList.get(rowCounter - 1));
            row.createCell(2).setCellValue(populationList.get(rowCounter - 1));
        }
    }

    private static XSSFPivotTable createPivotTable(XSSFWorkbook workbook, String pivotSheetName, String dataSheetName) {
        XSSFSheet pivotSheet = workbook.createSheet(pivotSheetName);
        XSSFSheet dataSheet = workbook.getSheet(dataSheetName);
        CellReference leftTop = new CellReference(0, 0);
        CellReference rightBottom = new CellReference(8, 2);
        CellReference pivotLocation = new CellReference(1, 1);
        AreaReference sourceDataAreaRef = new AreaReference(leftTop, rightBottom, SpreadsheetVersion.EXCEL2007);
        XSSFPivotTable pivotTable = pivotSheet.createPivotTable(sourceDataAreaRef, pivotLocation, dataSheet);
        pivotTable.addRowLabel(0);
        pivotTable.addRowLabel(1);
        pivotTable.addColumnLabel(DataConsolidateFunction.SUM, 2);
        return pivotTable;
    }

    private static void createPivotChart(XSSFWorkbook workbook, String workbookName, XSSFPivotTable pivotTable, String dataSheetName) {
        XSSFSheet dataSheet = workbook.getSheet(dataSheetName);
        XSSFSheet pivotSheet = (XSSFSheet)pivotTable.getParentSheet();
        XSSFDrawing drawing = pivotSheet.createDrawingPatriarch();
        XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 4, 2, 10, 20);

        XSSFChart chart = drawing.createChart(anchor);
        
        /*Set pivot source of the chart to a qualified name.
        Qualified name is [workbookName]worksheetName!pivotTableName. */
        String pivotTableName = pivotTable.getCTPivotTableDefinition().getName();
        String qualifiedPivotSourceName = "[" + workbookName + "]" + pivotSheet.getSheetName() + "!" + pivotTableName;
        chart.getCTChartSpace().addNewPivotSource().setName(qualifiedPivotSourceName);
        
        chart.setTitleText("Population By Countries");
        chart.setTitleOverlay(false);

        XDDFChartLegend legend = chart.getOrAddLegend();
        legend.setPosition(LegendPosition.BOTTOM);

        XDDFCategoryAxis bottomAxis = chart.createCategoryAxis(AxisPosition.BOTTOM);
        bottomAxis.setTitle("Country");
        XDDFValueAxis leftAxis = chart.createValueAxis(AxisPosition.LEFT);
        leftAxis.setTitle("Population");
        leftAxis.setCrosses(AxisCrosses.AUTO_ZERO);
        
        /*You need set AxisCrossBetween, so the left axis crosses the category axis between the categories. 
        Else first and last category is exactly on cross points and the bars are only half visible.*/
        leftAxis.setCrossBetween(AxisCrossBetween.BETWEEN);

        XDDFDataSource<String> countries = XDDFDataSourcesFactory.fromStringCellRange(dataSheet,
                new CellRangeAddress(1, 8, 1, 1));

        XDDFNumericalDataSource<Double> values = XDDFDataSourcesFactory.fromNumericCellRange(dataSheet,
                new CellRangeAddress(1, 8, 2, 2));
        
        //XDDFDataSource<String> countries = XDDFDataSourcesFactory.fromArray(new String[]{"dummy"});
        //XDDFNumericalDataSource<Double> values = XDDFDataSourcesFactory.fromArray(new Double[]{1d});

        XDDFChartData data = chart.createData(ChartTypes.BAR, bottomAxis, leftAxis);
        XDDFChartData.Series series1 = data.addSeries(countries, values);
        series1.setTitle("Country", null);
        data.setVaryColors(false);
        XDDFBarChartData bar = (XDDFBarChartData) data;
        bar.setBarDirection(BarDirection.COL);

        CTSolidColorFillProperties fillProp = CTSolidColorFillProperties.Factory.newInstance();
        CTSRgbColor rgb = CTSRgbColor.Factory.newInstance();
        rgb.setVal(new byte[]{(byte) 233, (byte) 87, (byte)162});
        fillProp.setSrgbClr(rgb);
        CTShapeProperties ctShapeProperties = CTShapeProperties.Factory.newInstance();

        ctShapeProperties.setSolidFill(fillProp);

        chart.getCTChart().getPlotArea().getBarChartArray(0).getSerArray(0).setSpPr(ctShapeProperties);
        
        /*Set the default, which is no using multi level labels, to false.
        So continents and countries will have multiple level axis labels.*/
        chart.getCTChart().getPlotArea().getCatAxArray(0).addNewNoMultiLvlLbl().setVal(false);
        
        chart.plot(data);
    }
}

当前的 Excel 版本提供了在数据透视图中显示或隐藏字段按钮的设置。默认情况下不显示按钮。要显示按钮,需要具有

ext
元素的
c14:pivotOptions
元素。因此,为了完整起见,如何将这些内容放入 XML 中:

...
    // show all field buttons in pivot chart 
    try {
        org.openxmlformats.schemas.drawingml.x2006.chart.CTExtensionList ctExtLst = chart.getCTChartSpace().addNewExtLst();
        org.openxmlformats.schemas.drawingml.x2006.chart.CTExtension ctExt = ctExtLst.addNewExt();
        org.apache.xmlbeans.XmlObject xmlObject = org.apache.xmlbeans.XmlObject.Factory.parse(
             "<c14:pivotOptions xmlns:c14=\"http://schemas.microsoft.com/office/drawing/2007/8/2/chart\">"
            +"<c14:dropZoneFilter val=\"1\"/>"
            +"<c14:dropZoneCategories val=\"1\"/>"
            +"<c14:dropZoneData val=\"1\"/>"
            +"<c14:dropZoneSeries val=\"1\"/>"
            +"<c14:dropZonesVisible val=\"1\"/>"
            +"</c14:pivotOptions>"
        );
        ctExt.set(xmlObject);
        ctExt.setUri("{781A3756-C4B2-4CAC-9D66-4F8BD8637D16}");
        ctExt = ctExtLst.addNewExt();
        xmlObject = org.apache.xmlbeans.XmlObject.Factory.parse(
             "<c16:pivotOptions16 xmlns:c16=\"http://schemas.microsoft.com/office/drawing/2014/chart\">"
            +"<c16:showExpandCollapseFieldButtons  val=\"1\"/>"
            +"</c16:pivotOptions16>"
        );
        ctExt.set(xmlObject);
        ctExt.setUri("{E28EC0CA-F0BB-4C9C-879D-F8772B89E7AC}");
    } catch (Exception ex) {
        ex.printStackTrace();
    }
...

从 Apache POI 4.1.2 到 5.2.4 存在以下错误:使用 Apache POI 创建的 Excel 文件将不再显示折线图。因此需要实现这两行代码来修复轴编号格式设置。

...
   //repair axes number format settings
   if (bottomAxis.hasNumberFormat()) bottomAxis.setNumberFormat("@");
   if (leftAxis.hasNumberFormat()) leftAxis.setNumberFormat("#,##0.00");
...

5.2.5 版本修复了此错误

© www.soinside.com 2019 - 2024. All rights reserved.