Skip to content

Commit af9dcae

Browse files
Copilotkdinev
andcommitted
Add support for hierarchical column groups in PDF export
Implemented multi-level header support for column groups: - Detects when columns have multi-level headers (column groups) - Draws headers level by level from parent to child groups - Properly handles columnSpan for grouped headers - Respects startIndex for correct positioning - Redraws multi-level headers on page breaks - Maintains compatibility with simple single-level headers Co-authored-by: kdinev <[email protected]>
1 parent 200bf5d commit af9dcae

File tree

1 file changed

+97
-9
lines changed

1 file changed

+97
-9
lines changed

projects/igniteui-angular/src/lib/services/pdf/pdf-exporter.ts

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,24 @@ export class IgxPdfExporterService extends IgxBaseExporter {
4949

5050
protected exportDataImplementation(data: IExportRecord[], options: IgxPdfExporterOptions, done: () => void): void {
5151
const defaultOwner = this._ownersMap.get(DEFAULT_OWNER);
52-
const columns = defaultOwner?.columns.filter(col => col.field && !col.skip && col.headerType === ExportHeaderType.ColumnHeader) || [];
52+
53+
// Get all columns (including multi-column headers)
54+
const allColumns = defaultOwner?.columns.filter(col => !col.skip) || [];
55+
56+
// Get leaf columns (actual data columns)
57+
const leafColumns = allColumns.filter(col => col.field && col.headerType === ExportHeaderType.ColumnHeader);
58+
59+
// Check if we have multi-level headers
60+
const maxLevel = defaultOwner?.maxLevel || 0;
61+
const hasMultiColumnHeaders = maxLevel > 0 && allColumns.some(col => col.headerType === ExportHeaderType.MultiColumnHeader);
5362

54-
if (columns.length === 0 && data.length > 0) {
63+
if (leafColumns.length === 0 && data.length > 0) {
5564
// If no columns are defined, use the keys from the first data record
5665
const firstDataElement = data[0];
5766
const keys = Object.keys(firstDataElement.data);
5867

5968
keys.forEach((key) => {
60-
columns.push({
69+
leafColumns.push({
6170
header: key,
6271
field: key,
6372
skip: false,
@@ -80,8 +89,8 @@ export class IgxPdfExporterService extends IgxBaseExporter {
8089
const margin = 40;
8190
const usableWidth = pageWidth - (2 * margin);
8291

83-
// Calculate column widths
84-
const columnWidth = usableWidth / columns.length;
92+
// Calculate column widths based on leaf columns
93+
const columnWidth = usableWidth / leafColumns.length;
8594
const rowHeight = 20;
8695
const headerHeight = 25;
8796
const indentSize = 15; // Indentation per level for hierarchical data
@@ -92,9 +101,14 @@ export class IgxPdfExporterService extends IgxBaseExporter {
92101
// Set font
93102
pdf.setFontSize(options.fontSize);
94103

95-
// Draw main table headers
96-
this.drawTableHeaders(pdf, columns, margin, yPosition, columnWidth, headerHeight, usableWidth, options);
97-
yPosition += headerHeight;
104+
// Draw multi-level headers if present
105+
if (hasMultiColumnHeaders) {
106+
yPosition = this.drawMultiLevelHeaders(pdf, allColumns, maxLevel, margin, yPosition, columnWidth, headerHeight, usableWidth, options);
107+
} else {
108+
// Draw simple single-level headers
109+
this.drawTableHeaders(pdf, leafColumns, margin, yPosition, columnWidth, headerHeight, usableWidth, options);
110+
yPosition += headerHeight;
111+
}
98112

99113
// Draw data rows
100114
pdf.setFont('helvetica', 'normal');
@@ -113,6 +127,14 @@ export class IgxPdfExporterService extends IgxBaseExporter {
113127
if (yPosition + rowHeight > pageHeight - margin) {
114128
pdf.addPage();
115129
yPosition = margin;
130+
131+
// Redraw headers on new page
132+
if (hasMultiColumnHeaders) {
133+
yPosition = this.drawMultiLevelHeaders(pdf, allColumns, maxLevel, margin, yPosition, columnWidth, headerHeight, usableWidth, options);
134+
} else {
135+
this.drawTableHeaders(pdf, leafColumns, margin, yPosition, columnWidth, headerHeight, usableWidth, options);
136+
yPosition += headerHeight;
137+
}
116138
}
117139

118140
// Calculate indentation for hierarchical records
@@ -124,7 +146,7 @@ export class IgxPdfExporterService extends IgxBaseExporter {
124146
const indent = indentLevel * indentSize;
125147

126148
// Draw parent row
127-
this.drawDataRow(pdf, record, columns, margin, yPosition, columnWidth, rowHeight, indent, options);
149+
this.drawDataRow(pdf, record, leafColumns, margin, yPosition, columnWidth, rowHeight, indent, options);
128150
yPosition += rowHeight;
129151

130152
// For hierarchical grids, check if this record has child records
@@ -203,6 +225,72 @@ export class IgxPdfExporterService extends IgxBaseExporter {
203225
done();
204226
}
205227

228+
private drawMultiLevelHeaders(
229+
pdf: jsPDF,
230+
columns: any[],
231+
maxLevel: number,
232+
xStart: number,
233+
yStart: number,
234+
baseColumnWidth: number,
235+
headerHeight: number,
236+
tableWidth: number,
237+
options: IgxPdfExporterOptions
238+
): number {
239+
let yPosition = yStart;
240+
pdf.setFont('helvetica', 'bold');
241+
242+
// Draw headers level by level (from top/parent to bottom/children)
243+
for (let level = 0; level <= maxLevel; level++) {
244+
// Get headers for this level
245+
const headersForLevel = columns.filter(col => {
246+
if (level === 0) {
247+
// For level 0, include multi-column headers at level 0 and leaf columns that span multiple levels
248+
return (col.level === 0 && col.headerType === ExportHeaderType.MultiColumnHeader) ||
249+
(col.level === 0 && col.headerType === ExportHeaderType.ColumnHeader);
250+
} else {
251+
// For other levels, include headers at this level or leaf columns that need to span down
252+
return (col.level === level && col.headerType === ExportHeaderType.MultiColumnHeader) ||
253+
(col.level < level && col.headerType === ExportHeaderType.ColumnHeader);
254+
}
255+
}).filter(col => col.columnSpan > 0);
256+
257+
if (headersForLevel.length === 0) continue;
258+
259+
// Sort by startIndex to maintain order
260+
headersForLevel.sort((a, b) => a.startIndex - b.startIndex);
261+
262+
// Draw background
263+
pdf.setFillColor(240, 240, 240);
264+
if (options.showTableBorders) {
265+
pdf.rect(xStart, yPosition, tableWidth, headerHeight, 'F');
266+
}
267+
268+
// Draw each header in this level
269+
headersForLevel.forEach((col) => {
270+
const colSpan = col.columnSpan || 1;
271+
const width = baseColumnWidth * colSpan;
272+
const xPosition = xStart + (col.startIndex * baseColumnWidth);
273+
274+
if (options.showTableBorders) {
275+
pdf.rect(xPosition, yPosition, width, headerHeight);
276+
}
277+
278+
// Center text in cell
279+
const headerText = col.header || col.field || '';
280+
const textWidth = pdf.getTextWidth(headerText);
281+
const textX = xPosition + (width - textWidth) / 2;
282+
const textY = yPosition + headerHeight / 2 + options.fontSize / 3;
283+
284+
pdf.text(headerText, textX, textY);
285+
});
286+
287+
yPosition += headerHeight;
288+
}
289+
290+
pdf.setFont('helvetica', 'normal');
291+
return yPosition;
292+
}
293+
206294
private drawTableHeaders(
207295
pdf: jsPDF,
208296
columns: any[],

0 commit comments

Comments
 (0)