如何在 Java 应用程序的 excel 动态报告中包含仪表图
How to include gauge graph in an excel report on the fly in a Java application
我打算从我的应用程序下载 excel 报告
单击 excel 上的下载按钮将生成报告。这已经在没有太多担忧的情况下实施。然而,下一个要求是我正在努力寻找答案的地方。我的要求是在 excel 文档中包含仪表图表,如下所示。我无法在 Apache POI 中看到相同的规定。得到一些指点会很有帮助。
Microsoft Excel
不提供规格图表。您的屏幕截图显示的是圆环图和饼图的组合。
圆环图有每个仪表段的数据点(段)和最后一个填满整个圆的段的数据点(段)。第一段根据需要着色(例如红色、黄色、绿色),而最后一段不可见(隐藏)。所以它看起来像一个半圆甜甜圈。
饼图有指针位置、指针粗细和最后一个填满整个圆的数据点(段)。第一个数据点的值决定了指针的位置。第一段和最后一段是不可见的(隐藏的)。只有指针粗细部分是可见的,并且指针应该如何显示是彩色的。
Apache poi
在当前版本 apache poi 5.0.0
中提供圆环图和饼图。不幸的是 XDDFDoughnutChartData
到目前为止还不完整。它缺少设置孔大小和第一切片角度的方法。因此,虽然可以使用默认 XDDF
方法创建圆环图,但需要额外的方法来设置孔大小和第一个切片角度。在这种情况下需要设置第一个切片角度,因为它需要为 270 度才能在正确位置显示半圆图。
下面是一个完整的例子,显示了上面的内容。
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.XDDFNoFillProperties;
import org.apache.poi.xddf.usermodel.XDDFLineProperties;
import org.apache.poi.xddf.usermodel.XDDFShapeProperties;
import org.apache.poi.xddf.usermodel.chart.XDDFChart;
import org.apache.poi.xddf.usermodel.chart.XDDFChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory;
import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFPieChartData;
import org.apache.poi.xddf.usermodel.chart.ChartTypes;
import org.apache.poi.xssf.usermodel.XSSFChart;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public class DoughnutAndPieChart {
//method to get shape properties from XDDFChart
private static XDDFShapeProperties getOrAddChartSpaceShapeProperties(XDDFChart chart) {
if (chart.getCTChartSpace().getSpPr() == null) chart.getCTChartSpace().addNewSpPr();
return new XDDFShapeProperties(chart.getCTChartSpace().getSpPr());
}
//XDDFDoughnutChartData lacks method setHoleSize. This provides such method for CTDoughnutChart.
private static void setHoleSize(org.openxmlformats.schemas.drawingml.x2006.chart.CTDoughnutChart chart, Short size) {
if (size == null) {
if (chart.isSetHoleSize()) {
chart.unsetHoleSize();
}
} else {
if (size < 0 || 100 < size) {
throw new IllegalArgumentException("size must be between 0 and 100");
}
if (chart.isSetHoleSize()) {
chart.getHoleSize().setVal(size);
} else {
chart.addNewHoleSize().setVal(size);
}
}
}
//XDDFDoughnutChartData lacks method setFirstSliceAngle. This provides such method for CTDoughnutChart
private static void setFirstSliceAngle(org.openxmlformats.schemas.drawingml.x2006.chart.CTDoughnutChart chart, Integer angle) {
if (angle == null) {
if (chart.isSetFirstSliceAng()) {
chart.unsetFirstSliceAng();
}
} else {
if (angle < 0 || 360 < angle) {
throw new IllegalArgumentException("angle must be between 0 and 360");
}
if (chart.isSetFirstSliceAng()) {
chart.getFirstSliceAng().setVal(angle);
} else {
chart.addNewFirstSliceAng().setVal(angle);
}
}
}
public static void main(String[] args) throws IOException {
try (XSSFWorkbook wb = new XSSFWorkbook()) {
XSSFSheet sheet = wb.createSheet("gauge chart");
//set data
Row row;
row = sheet.createRow(0);
row.createCell(0).setCellValue("Ptr.Pos.:");
row.createCell(1).setCellValue(75); // cell B1 is pointer position = first pie chart segment
row = sheet.createRow(1);
row.createCell(0).setCellValue("Ptr.Thickn.:");
row.createCell(1).setCellValue(1); // cell B2 is pointer thickness = second pie chart segment
row = sheet.createRow(2);
row.createCell(0).setCellValue("Helper:");
row.createCell(1).setCellFormula("200-B1-B2"); // cell B3 is helper formula needed to calculate third pie chart segment size up to full circle
row = sheet.createRow(3);
row.createCell(0).setCellValue("Helper:"); // row 4 is needed as chart will have 4 categories; needs more when more categories used
XSSFDrawing drawing = sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 6, 5, 20);
XSSFChart chart = drawing.createChart(anchor);
//set chart's background to no fill and no line
XDDFShapeProperties shapeProperties = getOrAddChartSpaceShapeProperties(chart);
shapeProperties.setFillProperties(new XDDFNoFillProperties());
shapeProperties.setLineProperties(new XDDFLineProperties(new XDDFNoFillProperties()));
//data source for categories
XDDFDataSource<String> cat = XDDFDataSourcesFactory.fromArray(new String[]{"1", "2", "3", "4"});
//doughnut chart = three segments (red yellow, green) plus one segment to be invisible (hidden)
XDDFNumericalDataSource<Double> val = XDDFDataSourcesFactory.fromArray(new Double[]{25d, 50d, 25d, 100d});
XDDFChartData data = chart.createData(ChartTypes.DOUGHNUT, null, null);
data.setVaryColors(true);
XDDFChartData.Series series = data.addSeries(cat, val);
chart.plot(data);
//set hole size and first slice angle for the doughnut chart
setHoleSize(chart.getCTChart().getPlotArea().getDoughnutChartArray(0), (short)50);
setFirstSliceAngle(chart.getCTChart().getPlotArea().getDoughnutChartArray(0), 270);
//set data point (segments) color
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(0);
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDPtArray(0)
.addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{(byte)255, 0, 0}); //red
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(1);
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDPtArray(1)
.addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{(byte)255, (byte)255, 0}); //yellow
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(2);
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDPtArray(2)
.addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{0, (byte)255, 0}); //green
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(3);
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDPtArray(3).addNewSpPr().addNewNoFill(); //invisible (hidden)
//pie chart = segments: pointer position, pointer thickness, up to full circle
val = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(0, 3, 1, 1));
data = chart.createData(ChartTypes.PIE, null, null);
data.setVaryColors(true);
((XDDFPieChartData)data).setFirstSliceAngle(270);
series = data.addSeries(cat, val);
chart.plot(data);
//correct the id and order, must not start 0 again because there is a doughnut series already
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getIdx().setVal(1);
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getOrder().setVal(1);
//set data point (segments) color
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(0);
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDPtArray(0).addNewSpPr().addNewNoFill(); //invisible (hidden)
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(1);
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDPtArray(1)
.addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{0, 0, 0}); //black
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(2);
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDPtArray(2).addNewSpPr().addNewNoFill(); //invisible (hidden)
//write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("ooxml-doughnut-and-pie-chart.xlsx")) {
wb.write(fileOut);
}
}
}
}
结果是:
更改 B1
以更改指针位置。
这不适用于 apache poi 4.1.2
及更低版本。对于那些较低版本的圆环图,可以按照 Doughnut Chart Apache-POI.
中的描述创建
我打算从我的应用程序下载 excel 报告
单击 excel 上的下载按钮将生成报告。这已经在没有太多担忧的情况下实施。然而,下一个要求是我正在努力寻找答案的地方。我的要求是在 excel 文档中包含仪表图表,如下所示。我无法在 Apache POI 中看到相同的规定。得到一些指点会很有帮助。
Microsoft Excel
不提供规格图表。您的屏幕截图显示的是圆环图和饼图的组合。
圆环图有每个仪表段的数据点(段)和最后一个填满整个圆的段的数据点(段)。第一段根据需要着色(例如红色、黄色、绿色),而最后一段不可见(隐藏)。所以它看起来像一个半圆甜甜圈。
饼图有指针位置、指针粗细和最后一个填满整个圆的数据点(段)。第一个数据点的值决定了指针的位置。第一段和最后一段是不可见的(隐藏的)。只有指针粗细部分是可见的,并且指针应该如何显示是彩色的。
Apache poi
在当前版本 apache poi 5.0.0
中提供圆环图和饼图。不幸的是 XDDFDoughnutChartData
到目前为止还不完整。它缺少设置孔大小和第一切片角度的方法。因此,虽然可以使用默认 XDDF
方法创建圆环图,但需要额外的方法来设置孔大小和第一个切片角度。在这种情况下需要设置第一个切片角度,因为它需要为 270 度才能在正确位置显示半圆图。
下面是一个完整的例子,显示了上面的内容。
import java.io.FileOutputStream;
import java.io.IOException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.XDDFNoFillProperties;
import org.apache.poi.xddf.usermodel.XDDFLineProperties;
import org.apache.poi.xddf.usermodel.XDDFShapeProperties;
import org.apache.poi.xddf.usermodel.chart.XDDFChart;
import org.apache.poi.xddf.usermodel.chart.XDDFChartData;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFDataSourcesFactory;
import org.apache.poi.xddf.usermodel.chart.XDDFNumericalDataSource;
import org.apache.poi.xddf.usermodel.chart.XDDFPieChartData;
import org.apache.poi.xddf.usermodel.chart.ChartTypes;
import org.apache.poi.xssf.usermodel.XSSFChart;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFDrawing;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public class DoughnutAndPieChart {
//method to get shape properties from XDDFChart
private static XDDFShapeProperties getOrAddChartSpaceShapeProperties(XDDFChart chart) {
if (chart.getCTChartSpace().getSpPr() == null) chart.getCTChartSpace().addNewSpPr();
return new XDDFShapeProperties(chart.getCTChartSpace().getSpPr());
}
//XDDFDoughnutChartData lacks method setHoleSize. This provides such method for CTDoughnutChart.
private static void setHoleSize(org.openxmlformats.schemas.drawingml.x2006.chart.CTDoughnutChart chart, Short size) {
if (size == null) {
if (chart.isSetHoleSize()) {
chart.unsetHoleSize();
}
} else {
if (size < 0 || 100 < size) {
throw new IllegalArgumentException("size must be between 0 and 100");
}
if (chart.isSetHoleSize()) {
chart.getHoleSize().setVal(size);
} else {
chart.addNewHoleSize().setVal(size);
}
}
}
//XDDFDoughnutChartData lacks method setFirstSliceAngle. This provides such method for CTDoughnutChart
private static void setFirstSliceAngle(org.openxmlformats.schemas.drawingml.x2006.chart.CTDoughnutChart chart, Integer angle) {
if (angle == null) {
if (chart.isSetFirstSliceAng()) {
chart.unsetFirstSliceAng();
}
} else {
if (angle < 0 || 360 < angle) {
throw new IllegalArgumentException("angle must be between 0 and 360");
}
if (chart.isSetFirstSliceAng()) {
chart.getFirstSliceAng().setVal(angle);
} else {
chart.addNewFirstSliceAng().setVal(angle);
}
}
}
public static void main(String[] args) throws IOException {
try (XSSFWorkbook wb = new XSSFWorkbook()) {
XSSFSheet sheet = wb.createSheet("gauge chart");
//set data
Row row;
row = sheet.createRow(0);
row.createCell(0).setCellValue("Ptr.Pos.:");
row.createCell(1).setCellValue(75); // cell B1 is pointer position = first pie chart segment
row = sheet.createRow(1);
row.createCell(0).setCellValue("Ptr.Thickn.:");
row.createCell(1).setCellValue(1); // cell B2 is pointer thickness = second pie chart segment
row = sheet.createRow(2);
row.createCell(0).setCellValue("Helper:");
row.createCell(1).setCellFormula("200-B1-B2"); // cell B3 is helper formula needed to calculate third pie chart segment size up to full circle
row = sheet.createRow(3);
row.createCell(0).setCellValue("Helper:"); // row 4 is needed as chart will have 4 categories; needs more when more categories used
XSSFDrawing drawing = sheet.createDrawingPatriarch();
XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 6, 5, 20);
XSSFChart chart = drawing.createChart(anchor);
//set chart's background to no fill and no line
XDDFShapeProperties shapeProperties = getOrAddChartSpaceShapeProperties(chart);
shapeProperties.setFillProperties(new XDDFNoFillProperties());
shapeProperties.setLineProperties(new XDDFLineProperties(new XDDFNoFillProperties()));
//data source for categories
XDDFDataSource<String> cat = XDDFDataSourcesFactory.fromArray(new String[]{"1", "2", "3", "4"});
//doughnut chart = three segments (red yellow, green) plus one segment to be invisible (hidden)
XDDFNumericalDataSource<Double> val = XDDFDataSourcesFactory.fromArray(new Double[]{25d, 50d, 25d, 100d});
XDDFChartData data = chart.createData(ChartTypes.DOUGHNUT, null, null);
data.setVaryColors(true);
XDDFChartData.Series series = data.addSeries(cat, val);
chart.plot(data);
//set hole size and first slice angle for the doughnut chart
setHoleSize(chart.getCTChart().getPlotArea().getDoughnutChartArray(0), (short)50);
setFirstSliceAngle(chart.getCTChart().getPlotArea().getDoughnutChartArray(0), 270);
//set data point (segments) color
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(0);
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDPtArray(0)
.addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{(byte)255, 0, 0}); //red
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(1);
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDPtArray(1)
.addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{(byte)255, (byte)255, 0}); //yellow
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(2);
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDPtArray(2)
.addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{0, (byte)255, 0}); //green
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(3);
chart.getCTChart().getPlotArea().getDoughnutChartArray(0).getSerArray(0).getDPtArray(3).addNewSpPr().addNewNoFill(); //invisible (hidden)
//pie chart = segments: pointer position, pointer thickness, up to full circle
val = XDDFDataSourcesFactory.fromNumericCellRange(sheet, new CellRangeAddress(0, 3, 1, 1));
data = chart.createData(ChartTypes.PIE, null, null);
data.setVaryColors(true);
((XDDFPieChartData)data).setFirstSliceAngle(270);
series = data.addSeries(cat, val);
chart.plot(data);
//correct the id and order, must not start 0 again because there is a doughnut series already
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getIdx().setVal(1);
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getOrder().setVal(1);
//set data point (segments) color
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(0);
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDPtArray(0).addNewSpPr().addNewNoFill(); //invisible (hidden)
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(1);
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDPtArray(1)
.addNewSpPr().addNewSolidFill().addNewSrgbClr().setVal(new byte[]{0, 0, 0}); //black
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).addNewDPt().addNewIdx().setVal(2);
chart.getCTChart().getPlotArea().getPieChartArray(0).getSerArray(0).getDPtArray(2).addNewSpPr().addNewNoFill(); //invisible (hidden)
//write the output to a file
try (FileOutputStream fileOut = new FileOutputStream("ooxml-doughnut-and-pie-chart.xlsx")) {
wb.write(fileOut);
}
}
}
}
结果是:
更改 B1
以更改指针位置。
这不适用于 apache poi 4.1.2
及更低版本。对于那些较低版本的圆环图,可以按照 Doughnut Chart Apache-POI.