I am working on itext 5 using java. I have pages with mutiple tables with dynamic rows. In some instances, the table last row is splitted into next page with the folowing header. I am using setHeaderRows() and setSkipFirstHeader() to manage continuation of next page. The last row has enough space to fit on earlier page. I would like to fit that last row in same page instead of next page.
For example, on page 1, the last row is splitted into first row of next page. Instead I would like to fit that row in page 1 so save one extra page with all blanks.
I tried using setExtendLastRow(), but its not working. Does anyone know how to fix this problem. I am attaching a working sample code.
public class ProposalItextSplitLastRow {
public static void main(String[] args) {
try {
Document document = new Document();
document.setPageSize(PageSize.LETTER);
document.setMargins(16, 14, 14, 14);
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("C:/SplitLastRow.pdf"));
document.open();
document.setPageSize(PageSize.LETTER);
document.setMargins(16, 14, 42, 38);
for (int m = 1; m < 20; m++) {
int row = 0;
PdfPTable table = new PdfPTable(1);
table.setSpacingAfter(0);
table.setSpacingBefore(0);
table.setWidthPercentage(100);
table.setHeaderRows(1);
table.setSkipFirstHeader(true);
add(table, "Header Row continued " + m, BaseColor.LIGHT_GRAY, row++);
add(table, "Header Row normal " + m, BaseColor.LIGHT_GRAY, row++);
add(table, "Text Row 1 ", BaseColor.WHITE, row++);
add(table, "Text Row 2 ", BaseColor.WHITE, row++);
add(table, "Text Row 3 ", BaseColor.WHITE, row++);
addPadding(table);
document.add(table);
}
document.close();
} catch (Exception de) {
de.printStackTrace();
}
}
private static void add(PdfPTable table, String text, BaseColor color, int row) {
PdfPCell pdfCellHeader = new PdfPCell();
pdfCellHeader.setBackgroundColor(color);
pdfCellHeader.addElement(new Paragraph(new Phrase(text)));
table.addCell(pdfCellHeader);
}
private static void addPadding(PdfPTable table) {
PdfPCell cell = new PdfPCell();
cell.setFixedHeight(2f);
cell.setBorder(Rectangle.NO_BORDER);
cell.setColspan(table.getNumberOfColumns());
table.addCell(cell);
}
}
you can table.setKeepRowsTogather(true);
table.setHeaderRows(1) as well alongwith it
setKeepRowsTogather() checks if it can keep all the rows in page but splits the rows in case the table spans multiple pages. In that case setHeaderRows(1) will put the header rows again in the next page.
I had to execute the example to understand your question. You confused me by talking about a header that isn't a header (the rows with "Header Row normal" aren't header rows!) and your reference to setExtendLastRow() didn't help either (mentioning that method doesn't make sense to me; it's very confusing).
This being said, the solution to your problem is a no-brainer. I've rewritten the main class:
public static void main(String[] args) {
try {
Document document = new Document();
document.setPageSize(PageSize.LETTER);
document.setMargins(16, 14, 14, 14);
PdfWriter writer = PdfWriter.getInstance(document,
new FileOutputStream("SplitLastRow.pdf"));
document.open();
document.setPageSize(PageSize.LETTER);
document.setMargins(16, 14, 42, 38);
for (int m = 1; m < 20; m++) {
int row = 0;
PdfPTable table = new PdfPTable(1);
table.setSpacingAfter(0);
table.setSpacingBefore(0);
table.setTotalWidth(document.right() - document.left());
table.setLockedWidth(true);
table.setHeaderRows(1);
table.setSkipFirstHeader(true);
add(table, "Header Row continued " + m, BaseColor.LIGHT_GRAY, row++);
add(table, "Header Row normal " + m, BaseColor.LIGHT_GRAY, row++);
add(table, "Text Row 1 ", BaseColor.WHITE, row++);
add(table, "Text Row 2 ", BaseColor.WHITE, row++);
add(table, "Text Row 3 ", BaseColor.WHITE, row++);
addPadding(table);
if (writer.getVerticalPosition(true) - table.getRowHeight(0) - table.getRowHeight(1) < document.bottom()) {
document.newPage();
}
document.add(table);
}
document.close();
} catch (Exception de) {
de.printStackTrace();
}
}
Make sure you define a total width instead of a width percentage, and lock the width. As documented (and as common sense tells you), a PdfPTable object doesn't know its actual width if you define a width percentage. It goes without saying that you can't calculate the height of a table that doesn't know it's actual width.
Then use getVerticalPosition() method to get the current position of the cursor, and check if the first two rows fit on the page. If they don't go to a new page before adding the table. If you want to check if the complete table fits, use the getTotalHeight() method instead of the getRowHeight() method.
You can do
table.setSplitRows(false);
But I believe that when there is a row that wont fit it just wont be shown. It's worth a shot though
Related
I make a PDF table with three columns and the number of rows is determined by the arrays of data that I get from a device. These can vary anywhere from 1 to 200+ rows.
When the data that I get is less than how many rows can fit on a page, everything works fine, but when I get a lot of data, 40+ rows, I get the Document exception - infinite loop.
Here is the method where I make the table:
private static PdfPTable createTableSerial(String[] serialOcr, ArrayList<java.awt.Image> serialImage)
throws BadElementException {
PdfPTable table = new PdfPTable(3);
// t.setBorderColor(BaseColor.GRAY);
// t.setPadding(4);
// t.setSpacing(4);
// t.setBorderWidth(1);
PdfPCell c1 = new PdfPCell(new Phrase("Apoen"));
c1.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(c1);
c1 = new PdfPCell(new Phrase("Serijski broj"));
c1.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(c1);
c1 = new PdfPCell(new Phrase("Slika serijskog broja"));
c1.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(c1);
table.setHeaderRows(serialImage.size() - 1);
try {
int i = 0;
int j = 0;
while (j < serialImage.size()) {
table.addCell(serialOcr[i]);
table.addCell(serialOcr[i + 1]);
table.addCell(Image.getInstance(serialImage.get(j), null));
i += 2;
j++;
}
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
} catch (IOException e) {
throw new RuntimeException(e);
}
return table;
}
And I call it later like this:
document.add(createTableSerial(serialOcr, serialImage));
I tried using the method:
table.splitLateRows(false);
But it didn't work.
A version of Itext is itextpdf-5.5.13.2.jar
How can I split the table if it's bigger than one page?
Can I check how much free space there is on a pdf page?
Thanks in advance.
I tried a lot of things, but the one below is the only way I got it to work.
Maybe it could be easier with a newer version of IText, but with version 5.5 that I used this is the only way I could find.
PdfPTable largeTable = createTableSerial(serialOcr, serialImage);
List<PdfPRow> rows = largeTable.getRows();
int rowsPerTable = 20;
int currentRow = 0;
while(currentRow < rows.size()){
PdfPTable smallTable = new PdfPTable(largeTable.getNumberOfColumns());
smallTable.setWidthPercentage(100);
for (int i = currentRow; i < currentRow + rowsPerTable; i++) {
if (i >= rows.size()) {
break;
}
PdfPCell[] cells = rows.get(i).getCells();
for (PdfPCell cell : cells) {
smallTable.addCell(cell);
}
}
document.add(smallTable);
currentRow += rowsPerTable;
}
I first call the method that I posted in my question and create one large table.
After this I break the large table into multiple small tables and add them to the Pdf document one by one.
You can adjust the int rowsPerTable = 20; value to define on how many rows will the table break.
Right now, I'm trying to modify the following labels from an existing .xlsx
Graph Image:
The graph is already modified their formulas and values for the ones I want, but those numbers are still getting their value from previous values in the graph. How can I change them? I was looking for previous questions, and supposedly the method to use to get the current label values (before modifying them) would be the following one:
drawing.getCharts().get(0).getCTChart().getPlotArea().getLineChartList().get(0).getSerList().get(2).getVal().getNumRef().getNumCache()
The drawing obtained is the following one from my sheet:
XSSFDrawing drawing = sheet.createDrawingPatriarch();
But I get a list with 107 values... So I'm not sure if it's correct or not. I don't know what do I need to modify. I would appreciate some help please.
Minimal example about how did I modify the graph:
This excel sheet has five seriesList with a formula based on other excel sheets. So I did the following code:
drawing.getCharts().get(0).getCTChart().getPlotArea().getLineChartList().get(0).getSerList().get(0).getVal().getNumRef().setF("PERD_POLICY!$S$15:$S$" + lineasPerdPolicy + "");
drawing.getCharts().get(0).getCTChart().getPlotArea().getLineChartList().get(0).getSerList().get(0).getCat().getNumRef().setF("PERD_POLICY!$Q$15:$Q$" + lineasPerdPolicy);
drawing.getCharts().get(0).getCTChart().getPlotArea().getLineChartList().get(0).getSerList().get(1).getVal().getNumRef().setF("PERD_POLICY!$T$15:$T$" + lineasPerdPolicy);
drawing.getCharts().get(0).getCTChart().getPlotArea().getLineChartList().get(0).getSerList().get(1).getCat().getNumRef().setF("PERD_POLICY!$Q$15:$Q$" + lineasPerdPolicy);
drawing.getCharts().get(0).getCTChart().getPlotArea().getLineChartList().get(0).getSerList().get(2).getVal().getNumRef().setF("PERD_POLICY!$U$15:$U$" + lineasPerdPolicy);
drawing.getCharts().get(0).getCTChart().getPlotArea().getLineChartList().get(0).getSerList().get(2).getCat().getNumRef().setF("PERD_POLICY!$Q$15:$Q$" + lineasPerdPolicy);
drawing.getCharts().get(0).getCTChart().getPlotArea().getLineChartList().get(0).getSerList().get(3).getVal().getNumRef().setF("PERD_POLICY!$V$15:$V$" + lineasPerdPolicy);
drawing.getCharts().get(0).getCTChart().getPlotArea().getLineChartList().get(0).getSerList().get(3).getCat().getNumRef().setF("PERD_POLICY!$Q$15:$Q$" + lineasPerdPolicy);
drawing.getCharts().get(0).getCTChart().getPlotArea().getLineChartList().get(0).getSerList().get(4).getVal().getNumRef().setF("PERD_POLICY!$W$15:$W$" + lineasPerdPolicy);
drawing.getCharts().get(0).getCTChart().getPlotArea().getLineChartList().get(0).getSerList().get(4).getCat().getNumRef().setF("PERD_POLICY!$Q$15:$Q$" + lineasPerdPolicy);
drawing.getCharts().get(0).getCTChart().getPlotArea().getLineChartList().get(0).getSerList().get(5).getVal().getNumRef().setF("PERD_POLICY!$R$15:$R$" + lineasPerdPolicy);
drawing.getCharts().get(0).getCTChart().getPlotArea().getLineChartList().get(0).getSerList().get(5).getCat().getNumRef().setF("PERD_POLICY!$Q$15:$Q$" + lineasPerdPolicy);
lineasPerdPolicy is a variable I used to count the last row in the sheet we are getting the values from. The sheet "PERD_POLICY" . This graph is based on months from years. I added now to the current serList a new value, until December 2019. But the last label of the green chart, is showing 9,66. That value is from October 2019.
I think you will get it better with the following images. This one shows what is the value of the last label:
The current label value
And the selected value in this other picture is the one I want to show in the label, 9,75
The graph value I want to show in the label
If you don't understand any word please let me know, because my excel is in spanish.
Valor --> Value
Punto --> Point
Your code using the low level ooxml-schemas classes only updates the reference formulas of the series. It does not update the cached values in the chart.
Since the current apache poi 4.1.1 provides XDDFChartData.Series.replaceData to update the chart's data, we should use this instead of the low level ooxml-schemas classes.
Let's have a complete example to show how to do this.
We start having a ExcelWithChartMar.xlsx looking like so:
As you see there are the chart data in A1:D4 for months Jan to Mar already and a chart showing those data.
What we need to know: The first data row is 1 (row 0 is header row) and the current last data row is 3. The last data row will increase. The category column is 0 (A) and the series columns are 1 (B),2 (C) and 3 (D). Note, all indexes are 0 based.
Now we can run the following code using apache poi 4.1.1:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.xddf.usermodel.chart.*;
class ExcelChangeChartData {
static void updateChartData(XSSFChart chart, XSSFSheet dataSheet,
int firstDataRow, int lastDataRow, int categoryColumn, int[] seriesColumns) {
for (XDDFChartData chartData : chart.getChartSeries()) {
for (int s = 0; s < chartData.getSeriesCount() ; s++) {
XDDFChartData.Series series = chartData.getSeries(s);
if (seriesColumns.length > s) {
XDDFCategoryDataSource category = XDDFDataSourcesFactory.fromStringCellRange(
dataSheet, new CellRangeAddress(firstDataRow, lastDataRow, categoryColumn, categoryColumn));
int seriesColumn = seriesColumns[s];
XDDFNumericalDataSource<Double> values = XDDFDataSourcesFactory.fromNumericCellRange(
dataSheet, new CellRangeAddress(firstDataRow, lastDataRow, seriesColumn, seriesColumn));
series.replaceData(category, values);
series.plot();
}
}
}
}
public static void main(String[] args) throws Exception {
String[] months = new String[]{"Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
int firstDataRow = 1;
int lastDataRow = 3;
int categoryColumn = 0;
int[] seriesColumns = new int[]{1,2,3};
for (int m = 0; m < months.length - 1; m++) {
String monthSource = months[m];
String monthResult = months[m+1];
String filePath = "./ExcelWithChart" + monthSource + ".xlsx";
java.util.Random random = new java.util.Random();
XSSFWorkbook workbook = (XSSFWorkbook)WorkbookFactory.create(new FileInputStream(filePath));
XSSFSheet sheet = workbook.getSheetAt(0);
XSSFRow row = sheet.createRow(lastDataRow + 1);
XSSFCell cell = row.createCell(categoryColumn);
cell.setCellValue(monthResult);
for (int i = 0; i < seriesColumns.length; i++) {
cell = row.createCell(seriesColumns[i]);
cell.setCellValue(random.nextDouble() / 10 + 0.02);
cell.setCellStyle(sheet.getRow(lastDataRow).getCell(seriesColumns[i]).getCellStyle());
}
lastDataRow++;
XSSFDrawing drawing = sheet.createDrawingPatriarch();
XSSFChart chart = drawing.getCharts().get(0);
updateChartData(chart, sheet, firstDataRow, lastDataRow, categoryColumn, seriesColumns);
filePath = "./ExcelWithChart" + monthResult + ".xlsx";
FileOutputStream out = new FileOutputStream(filePath);
workbook.write(out);
out.close();
workbook.close();
}
}
}
This creates 9 additional Excel files ExcelWithChartApr.xlsx ... ExcelWithChartDec.xlsx where each has a new month's data added.
The method updateChartData updates the chart data using the XDDFChartData.Series.replaceData method.
I want to create AutoFilters at multiple places in an excel sheet using Apache POI. (e.g. at row 2 & at row 8).
hssfSheet.setAutoFilter(new CellRangeAddress(2, 4, 6, 3));
hssfSheet.setAutoFilter(new CellRangeAddress(8, 5, 3, 5));
I've been adding it like mentioned above but second filter is overriding the first one and when excel sheet is created I can see only one.
Can someone please help me.
Thanks.
Use should use XSSFSheet and create CTTable. Sheet may contain many tables.
Here is code for adding table to sheet. Please ensure that cells were created and exist.
private void createExcelTable(XSSFSheet sheet, AreaReference reference, int tableId,
String name) {
XSSFTable table = sheet.createTable();
table.setDisplayName(name);
CTTable cttable = table.getCTTable();
// Style configurations
// CTTableStyleInfo is available in ooxml-schemas-1.1.jar.
// Replace with it your poi-ooxml.
CTTableStyleInfo style = cttable.addNewTableStyleInfo();
style.setName("TableStyleLight1");
style.setShowColumnStripes(false);
style.setShowRowStripes(true);
// Set which area the table should be placed in
cttable.setRef(reference.formatAsString());
// id starts from 1
cttable.setId(tableId);
cttable.setName(name);
cttable.setDisplayName(name);
// first row is used for header with filter
CTAutoFilter autoFilter = cttable.addNewAutoFilter();
autoFilter.setRef(reference.formatAsString());
CTTableColumns columns = cttable.addNewTableColumns();
// sets count of columns for current table
short firstColumn = reference.getFirstCell().getCol();
short lastColumn = reference.getLastCell().getCol();
columns.setCount(lastColumn - firstColumn + 1);
for (int i = firstColumn; i <= lastColumn; i++) {
// create columns
CTTableColumn column = columns.addNewTableColumn();
column.setName("Column" + i);
// id starts from 1
column.setId(i + 1);
}
}
Example of AreaReference:
new AreaReference(new CellReference(0, 0), new CellReference(4, 5));
I have a main report which is printed horizontally. It has 5 columns.
On every column i want to put a sub report. So i created this:
And the sub-report just like this:
The problem is, when i run i get the following exception:
net.sf.jasperreports.engine.JRRuntimeException: Subreport overflowed on a band that does not support overflow.
Looks like jasper reports can't stretch the detail band vertically when there is a sub-report in it and the print order is set to horizontal.
What can I do to avoid this error and achieve what i want?
I found the solution for this problem. After a deep search i found that, sadly, there's no way to do this on Jasper Reports because, no matter what, when you have a report printed horizontally, the "Detail" band will never change it's height. So, subreports or text fields which overflows will throw an exception.
The workaround for this problem is to work without reports, with a PDF generator like iText, for example.
This is the code i did to achive what i wanted with iText if somebody needs it:
Document document = new Document();
File arquivo = new File("C:\\Users\\Mateus\\Desktop\\testezãozarãozão.pdf");
PdfWriter.getInstance(document, new FileOutputStream(arquivo));
document.open();
LinkedHashMap<Produto, LinkedHashMap<String, List<PrePedidoItem>>> produtos = createStructuredHashMap();
for (Produto produto : produtos.keySet()) {
PdfPTable table = new PdfPTable(5);
PdfPCell cellProduto = new PdfPCell();
Phrase phraseProduto = new Phrase(String.valueOf(produto));
phraseProduto.setFont(new Font(Font.FontFamily.HELVETICA, 11, Font.BOLD|Font.UNDERLINE, new BaseColor(50, 65, 200)));
cellProduto.addElement(phraseProduto);
cellProduto.setColspan(5);
cellProduto.setHorizontalAlignment(PdfPCell.ALIGN_MIDDLE);
cellProduto.setBorder(Rectangle.NO_BORDER);
cellProduto.setPaddingBottom(10);
cellProduto.setPaddingTop(20);
table.addCell(cellProduto);
LinkedHashMap<String, List<PrePedidoItem>> mapas = produtos.get(produto);
int mapasAdicionados = 0;
for (String mapa : mapas.keySet()) {
PdfPCell cellMapa = new PdfPCell();
Phrase phraseMapa = new Phrase(mapa);
phraseMapa.setFont(new Font(Font.FontFamily.HELVETICA, 9, Font.BOLD, new BaseColor(215, 100, 0)));
cellMapa.addElement(phraseMapa);
List<PrePedidoItem> itensDoMapa = mapas.get(mapa);
for (PrePedidoItem item : itensDoMapa) {
DecimalFormat df = new DecimalFormat("###,##0.00");
Phrase phraseItem = new Phrase(df.format(item.getLargura()) + " x " + df.format(item.getComprimento()));
phraseItem.setFont(new Font(Font.FontFamily.HELVETICA, 9, Font.NORMAL, BaseColor.BLACK));
cellMapa.addElement(phraseItem);
}
cellMapa.setBorder(Rectangle.NO_BORDER);
table.addCell(cellMapa);
mapasAdicionados ++;
if(mapasAdicionados == 5) {
mapasAdicionados = 0;
}
}
PdfPCell celulaPreenchimentoMapas = new PdfPCell();
celulaPreenchimentoMapas.setColspan(5 - mapasAdicionados);
celulaPreenchimentoMapas.setBorder(Rectangle.NO_BORDER);
table.addCell(celulaPreenchimentoMapas);
document.add(table);
}
document.close();
Desktop.getDesktop().open(arquivo);
I am using Java with iText in order to generate some PDFs. I need to put text in columns, so I am trying to use PdfPTable. I create it with:
myTable = new PdfPTable(n);
n being the number of columns. The problem is that PdfPTable fills the table row by row, that is, you first give the cell in column 1 of row 1, then column 2 of row 1, and so on, but I need to do it column by column, because that is how the data is being fed to me.
I would use a Table (which lets you specify the position) like in http://stderr.org/doc/libitext-java-doc/www/tutorial/ch05.html, but I get a "could not resolve to a type", and my Eclipse can't find the proper import.
Edit: in case my previous explanation was confusing, what I want is to fill the table in this order:
1 3 5
2 4 6
Instead of this:
1 2 3
4 5 6
Here is one way: Create a PdfPTable with the number of columns desired, in your case 3. For each iteration through your data create a PdfPTable with 1 column. Create 2 PdfPCell objects, one containing the data element you are currently on and the other containing the next value in your data. So now you have a PdfPTable with 1 column and two rows. Add this PdfPTable to the PdfPTable that has 3 columns. Continue that until you've printed all your data. Better explained with code:
public class Clazz {
public static final String RESULT = "result.pdf";
private String [] data = {"1", "2", "3", "4", "5", "6"};
private void go() throws Exception {
Document doc = new Document();
PdfWriter.getInstance(doc, new FileOutputStream(RESULT));
doc.open();
PdfPTable mainTable = new PdfPTable(3);
PdfPCell cell;
for (int i = 0; i < data.length; i+=2) {
cell = new PdfPCell(new Phrase(data[i]));
PdfPTable table = new PdfPTable(1);
table.addCell(cell);
if (i+1 <= data.length -1) {
cell = new PdfPCell(new Phrase(data[i + 1]));
table.addCell(cell);
} else {
cell = new PdfPCell(new Phrase(""));
table.addCell(cell);
}
mainTable.addCell(table);
}
doc.add(mainTable);
doc.close();
}
}
One way could be creating inner table of column no = 1 for each main column and add that into a main table.
private static PdfPTable writeColumnWise(String[] data, int noOfColumns, int noOfRows) {
PdfPTable table = new PdfPTable(noOfColumns);
PdfPTable columnTable = new PdfPTable(1);
columnTable.getDefaultCell().setBorderWidth(0.0f);
columnTable.getDefaultCell().setPadding(0.0f);
for(int i=0; i<data.length; i++){
if( i != 0 && i % noOfRows == 0 ){
// add columnTable into main table
table.addCell(columnTable);
//re initialize columnTable for next column
columnTable = new PdfPTable(1);
columnTable.getDefaultCell().setBorderWidth(0.0f);
columnTable.getDefaultCell().setPadding(0.0f);
}
PdfPCell cell = new PdfPCell(new Paragraph(data[i]));
columnTable.addCell(cell);
}
// add columnTable for last column into main table
table.addCell(columnTable);
return table;
}