【问题标题】:Is it possible to create Pivot Chart with source data as Pivot Table using Apache POI?是否可以使用 Apache POI 将源数据作为数据透视表创建数据透视图?
【发布时间】:2021-12-27 03:25:02
【问题描述】:

我能够单独使用 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】:

对于使用 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 版本提供了在数据透视图中显示或隐藏字段按钮的设置。默认不显示按钮。要显示具有c14:pivotOptions 元素的按钮ext 元素是必需的。因此,为了完整起见,如何将它们放入 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();
    }
...

【讨论】:

  • 非常感谢阿克塞尔。您的解决方案正是我想要的。
猜你喜欢
  • 2010-11-03
  • 1970-01-01
  • 2013-12-18
  • 1970-01-01
  • 2014-05-13
  • 2022-01-03
相关资源
最近更新 更多