I would make the structures for headers and data a little bit different:
The headers for example:
String[][] headers = new String[][] {
new String[] {"Quarter", "Subcategory", "Year", "2016", null, null, null, null},
new String[] { null, null, "Metrics", "Profit", "Profit Forecast", "Profit Margin", "Revenue", "Revenue Forecast"}
};
Maybe List<List<String>>
also would be possible.
The row headers:
String[] quarters = new String[]{"2016 Q1", "2016 Q2", "2016 Q3", "2016 Q4"};
String[] subcategories = new String[]{"Audio Equipment", "Cameras", "Computers", "Electronics - Miscellaneous", "TV's", "Video Equipment"};
Maybe List<String>
also would be possible.
The values:
Double[][] values = new Double[][]{
new Double[]{9579d, 8823d, .1942, 49320d,39456d},
new Double[]{11449d, 9619d, .2007, 57040d, 53047d},
...
new Double[]{13864d, 11328d, .1687, 82203d, 70695d}
};
Maybe List<List<Double>>
also would be possible.
Then I would also provide basic formatting instructions in structures. So it is later possible to set them using loops.
For example the basic table structure:
int[] columnWidths = new int[]{15*256, 15*256, 15*256, 13*256, 13*256, 13*256, 13*256, 13*256};
For example the headers horizontal alignment:
HorizontalAlignment[][] horizontalAlignments = new HorizontalAlignment[][]{
new HorizontalAlignment[]{HorizontalAlignment.LEFT, HorizontalAlignment.CENTER, HorizontalAlignment.LEFT, HorizontalAlignment.CENTER, null, null, null, null},
new HorizontalAlignment[]{null, null, HorizontalAlignment.RIGHT, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER}
};
And the merged regions for the headers:
int[][] headerMergedRegions = new int[][]{
new int[]{0, 0, 3, 7},
new int[]{0, 1, 0, 0},
new int[]{0, 1, 1, 1}
};
Then I would set the much used number formats in default column styles:
String[] columnCellStyles = new String[]{"default", "default", "default", "currency", "currency", "percent", "currency", "currency"};
...
// creating default cell style having default font
CellStyle defaultCellStyle = workbook.createCellStyle();
defaultCellStyle.setFont(defaultFont);
// we need currency style for numbers later
CellStyle currency = workbook.createCellStyle();
currency.cloneStyleFrom(defaultCellStyle);
currency.setDataFormat(format.getFormat("$#,##0"));
// we need percent style for numbers later
CellStyle percent = workbook.createCellStyle();
percent.cloneStyleFrom(defaultCellStyle);
percent.setDataFormat(format.getFormat("0.00%"));
...
// set default column styles
for (int c = 0; c < columnCellStyles.length; c++) {
String style = columnCellStyles[c];
if ("default".equals(style)) {
sheet.setDefaultColumnStyle(c, defaultCellStyle);
} else if ("currency".equals(style)) {
sheet.setDefaultColumnStyle(c, currency);
} else if ("percent".equals(style)) {
sheet.setDefaultColumnStyle(c, percent);
}
}
So when we set the values later, the cell styles can come from default column style. For this a method CellStyle getPreferredCellStyle(Cell cell)
could be used.
For formatting I would rely on methods from CellUtil. So not all needed CellStyles
needs to be created on workbook level but can be set to the cell into the loops.
For cell borders I would use PropertyTemplate. This makes setting borders much more easier.
Complete example:
import java.io.FileOutputStream;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.CellUtil;
import org.apache.poi.ss.util.PropertyTemplate;
import java.util.Map;
import java.util.HashMap;
class CreateExcelSpecial {
static CellStyle getPreferredCellStyle(Cell cell) {
// a method to get the preferred cell style for a cell
// this is either the already applied cell style
// or if that not present, then the row style (default cell style for this row)
// or if that not present, then the column style (default cell style for this column)
CellStyle cellStyle = cell.getCellStyle();
// if no explicit cell style applied then cellStyle.getIndex() is 0 for XSSF
// or 15 (0xF = the index to the default ExtendedFormatRecord (0xF)) for HSSF
if ((cell instanceof XSSFCell && cellStyle.getIndex() == 0) || (cell instanceof HSSFCell && cellStyle.getIndex() == 15)) cellStyle = cell.getRow().getRowStyle();
if (cellStyle == null) cellStyle = cell.getSheet().getColumnStyle(cell.getColumnIndex());
if (cellStyle == null) cellStyle = cell.getCellStyle();
return cellStyle;
}
public static void main(String[] args) throws Exception {
// the data
// basic table structure
int[] columnWidths = new int[]{15*256, 15*256, 15*256, 13*256, 13*256, 13*256, 13*256, 13*256};
String[] columnCellStyles = new String[]{"default", "default", "default", "currency", "currency", "percent", "currency", "currency"};
// headers content and formatting
String[][] headers = new String[][] {
new String[] {"Quarter", "Subcategory", "Year", "2016", null, null, null, null},
new String[] { null, null, "Metrics", "Profit", "Profit Forecast", "Profit Margin", "Revenue", "Revenue Forecast"}
};
HorizontalAlignment[][] horizontalAlignments = new HorizontalAlignment[][]{
new HorizontalAlignment[]{HorizontalAlignment.LEFT, HorizontalAlignment.CENTER, HorizontalAlignment.LEFT, HorizontalAlignment.CENTER, null, null, null, null},
new HorizontalAlignment[]{null, null, HorizontalAlignment.RIGHT, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER, HorizontalAlignment.CENTER}
};
VerticalAlignment[][] verticalAlignments = new VerticalAlignment[][]{
new VerticalAlignment[]{VerticalAlignment.TOP, VerticalAlignment.TOP, VerticalAlignment.CENTER, VerticalAlignment.CENTER, null, null, null, null},
new VerticalAlignment[]{null, null, VerticalAlignment.BOTTOM, VerticalAlignment.BOTTOM, VerticalAlignment.BOTTOM, VerticalAlignment.BOTTOM, VerticalAlignment.BOTTOM, VerticalAlignment.BOTTOM}
};
Boolean[][] wrapTexts = new Boolean[][]{
new Boolean[]{false, false, false, false, null, null, null, null},
new Boolean[]{null, null, false, false, true, true, false, true}
};
int[][] headerMergedRegions = new int[][]{
new int[]{0, 0, 3, 7},
new int[]{0, 1, 0, 0},
new int[]{0, 1, 1, 1}
};
// row headers
String[] quarters = new String[]{"2016 Q1", "2016 Q2", "2016 Q3", "2016 Q4"};
String[] subcategories = new String[]{"Audio Equipment", "Cameras", "Computers", "Electronics - Miscellaneous", "TV's", "Video Equipment"};
// data
Double[][] values = new Double[][]{
new Double[]{9579d, 8823d, .1942, 49320d,39456d},
new Double[]{11449d, 9619d, .2007, 57040d, 53047d},
new Double[]{4901d, 3784d, .2002, 24480d, 21298d},
new Double[]{12444d, 9525d, .1989, 62576d, 53815d},
new Double[]{8820d, 8059d, .2019, 43675d, 35814d},
new Double[]{16375d, 12986d, .2044, 80130d, 70514d},
new Double[]{8526d, 6929d, .1578, 54020d, 49698d},
new Double[]{11602d, 9578d, .1731, 67032d, 63680d},
new Double[]{4675d, 4380d, .1683, 27780d, 25752d},
new Double[]{11699d, 9421d, .1660, 70469d, 54966d},
new Double[]{9386d, 7179d, .1752, 53563d, 49974d},
new Double[]{10150d, 9213d, .1696, 59864d, 48490d},
new Double[]{8508d, 6772d, .1716, 49571d, 47092d},
new Double[]{16429d, 13529d, .1852, 88712d, 83389d},
new Double[]{6009d, 5391d, .1805, 33295d, 29200d},
new Double[]{11792d, 9791d, .1721, 68534d, 64285d},
new Double[]{9243d, 7952d, .1715, 53886d, 49953d},
new Double[]{14282d, 11679d, .1803, 79193d, 74441d},
new Double[]{10999d, 8538d, .1560, 70511d, 65575d},
new Double[]{14475d, 11433d, .1632, 88718d, 78515d},
new Double[]{5765d, 5029d, .1677, 34373d, 31847d},
new Double[]{11335d, 9567d, .1528, 74168d, 62672d},
new Double[]{11990d, 11230d, .1683, 71255d, 64414d},
new Double[]{13864d, 11328d, .1687, 82203d, 70695d}
};
try (Workbook workbook = new XSSFWorkbook();
FileOutputStream fileout = new FileOutputStream("./Excel.xlsx") ) {
// we need PropertyTemplate later
PropertyTemplate propertyTemplate = new PropertyTemplate();
// we need properties map for cell styles later
Map<String, Object> properties;
// we need DataFormat later
DataFormat format = workbook.createDataFormat();
// creating default font
Font defaultFont = workbook.createFont();
defaultFont.setFontName("Calibri");
defaultFont.setFontHeightInPoints((short)12);
// we need font in bold and white for headings
Font defaultFontWhite = workbook.createFont();
defaultFontWhite.setFontName("Calibri");
defaultFontWhite.setFontHeightInPoints((short)12);
defaultFontWhite.setBold(true);
defaultFontWhite.setColor(IndexedColors.WHITE.getIndex());
// creating default cell style having default font
CellStyle defaultCellStyle = workbook.createCellStyle();
defaultCellStyle.setFont(defaultFont);
// we need currency style for numbers later
CellStyle currency = workbook.createCellStyle();
currency.cloneStyleFrom(defaultCellStyle);
currency.setDataFormat(format.getFormat("$#,##0"));
// we need percent style for numbers later
CellStyle percent = workbook.createCellStyle();
percent.cloneStyleFrom(defaultCellStyle);
percent.setDataFormat(format.getFormat("0.00%"));
Sheet sheet = workbook.createSheet();
// set column widths
for (int c = 0; c < columnWidths.length; c++) sheet.setColumnWidth(c, columnWidths[c]);
// set default column styles
for (int c = 0; c < columnCellStyles.length; c++) {
String style = columnCellStyles[c];
if ("default".equals(style)) {
sheet.setDefaultColumnStyle(c, defaultCellStyle);
} else if ("currency".equals(style)) {
sheet.setDefaultColumnStyle(c, currency);
} else if ("percent".equals(style)) {
sheet.setDefaultColumnStyle(c, percent);
}
}
int rowIdx = 0; // looping row index
int colIdx = 0; // looping column index
// input and format headers
int i = 0;
for (String[] headerRow : headers) {
Row row = sheet.createRow(rowIdx++);
colIdx = 0;
int j = 0;
for (String header : headerRow) {
Cell cell = row.createCell(colIdx++);
cell.setCellValue(header);
properties = new HashMap<String, Object>();
properties.put(CellUtil.FILL_PATTERN, FillPatternType.SOLID_FOREGROUND);
properties.put(CellUtil.FILL_FOREGROUND_COLOR, IndexedColors.BLUE.getIndex()); //do using only IndexedColors for fills
properties.put(CellUtil.FONT, defaultFontWhite.getIndexAsInt()); // white font
if (horizontalAlignments.length == headers.length) {
HorizontalAlignment[] hAligns = horizontalAlignments[i];
if (hAligns.length == headerRow.length) {
HorizontalAlignment hAlign = hAligns[j];
properties.put(CellUtil.ALIGNMENT, hAlign); // horizontal alignment
}
}
if (verticalAlignments.length == headers.length) {
VerticalAlignment[] hAligns = verticalAlignments[i];
if (hAligns.length == headerRow.length) {
VerticalAlignment vAlign = hAligns[j];
properties.put(CellUtil.VERTICAL_ALIGNMENT, vAlign); // vertical alignment
}
}
if (wrapTexts.length == headers.length) {
Boolean[] isWrapTexts = wrapTexts[i];
if (isWrapTexts.length == headerRow.length) {
Boolean isWrapText = isWrapTexts[j];
properties.put(CellUtil.WRAP_TEXT, isWrapText); // wrap text
}
}
CellUtil.setCellStyleProperties(cell, properties); //do using CellUtil for **add** new properties to already applied cell styles
j++;
}
i++;
}
// set header merged regions
for (int[] mergedRegion : headerMergedRegions) {
if (mergedRegion.length == 4) {
sheet.addMergedRegion(new CellRangeAddress(mergedRegion[0], mergedRegion[1], mergedRegion[2], mergedRegion[3]));
}
}
// draw header borders
propertyTemplate.drawBorders(new CellRangeAddress(0, headers.length-1, 0, headers[0].length-1), BorderStyle.MEDIUM, IndexedColors.WHITE.getIndex(), BorderExtent.ALL); //since we have merged regions we can simply drawing all borders here
// input and format row headers
for (String quarter : quarters) {
Row row = sheet.createRow(rowIdx++);
Cell cell = row.createCell(0); // quarter in column 0 (A)
cell.setCellValue(quarter);
CellUtil.setVerticalAlignment(cell, VerticalAlignment.TOP); // quarter cells are top aligned
for (String subcategory : subcategories) {
cell = row.createCell(1); // subcategory in column 1 (B)
cell.setCellValue(subcategory);
sheet.addMergedRegion(new CellRangeAddress(rowIdx, rowIdx, 1, 2));
row = sheet.createRow(rowIdx++);
}
rowIdx--;
sheet.addMergedRegion(new CellRangeAddress(rowIdx-subcategories.length, rowIdx-1, 0, 0));
}
rowIdx -= quarters.length * subcategories.length; // reset the rowIdx
// input values - number formats came from default column style
for (Double[] rowValues : values) {
Row row = sheet.getRow(rowIdx++); //get the row instead of creating it
colIdx = 3; // values are from column 3 (D) on
for (Double value : rowValues) {
Cell cell = row.createCell(colIdx++);
cell.setCellValue(value);
cell.setCellStyle(getPreferredCellStyle(cell));
}
}
// draw data borders
propertyTemplate.drawBorders(new CellRangeAddress(headers.length, headers.length+values.length-1, 0, headers[0].length-1), BorderStyle.MEDIUM, IndexedColors.LIGHT_ORANGE.getIndex(), BorderExtent.ALL); //since we have merged regions we can simply drawing all borders here
//apply the PropertyTemplate borders
propertyTemplate.applyBorders(sheet);
workbook.write(fileout);
}
}
}