forked from exceljs/exceljs
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add pivot table with limitations (exceljs#2551)
* Add pivot table with limitations ```js worksheet.addPivotTable(configuration); ``` **Note:** Pivot table support is in its early stages with certain limitations, including: - Xlsx files with existing pivot tables can't be read (writing is supported). - Pivot table configurations must have one "value"-item and use the "sum" metric. - Only one pivot table can be added for the entire document. * Update README
- Loading branch information
Showing
18 changed files
with
893 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
const {objectFromProps, range, toSortedArray} = require('../utils/utils'); | ||
|
||
// TK(2023-10-10): turn this into a class constructor. | ||
|
||
function makePivotTable(worksheet, model) { | ||
// Example `model`: | ||
// { | ||
// // Source of data: the entire sheet range is taken, | ||
// // akin to `worksheet1.getSheetValues()`. | ||
// sourceSheet: worksheet1, | ||
// | ||
// // Pivot table fields: values indicate field names; | ||
// // they come from the first row in `worksheet1`. | ||
// rows: ['A', 'B'], | ||
// columns: ['C'], | ||
// values: ['E'], // only 1 item possible for now | ||
// metric: 'sum', // only 'sum' possible for now | ||
// } | ||
|
||
validate(worksheet, model); | ||
|
||
const {sourceSheet} = model; | ||
let {rows, columns, values} = model; | ||
|
||
const cacheFields = makeCacheFields(sourceSheet, [...rows, ...columns]); | ||
|
||
// let {rows, columns, values} use indices instead of names; | ||
// names can then be accessed via `pivotTable.cacheFields[index].name`. | ||
// *Note*: Using `reduce` as `Object.fromEntries` requires Node 12+; | ||
// ExcelJS is >=8.3.0 (as of 2023-10-08). | ||
const nameToIndex = cacheFields.reduce((result, cacheField, index) => { | ||
result[cacheField.name] = index; | ||
return result; | ||
}, {}); | ||
rows = rows.map(row => nameToIndex[row]); | ||
columns = columns.map(column => nameToIndex[column]); | ||
values = values.map(value => nameToIndex[value]); | ||
|
||
// form pivot table object | ||
return { | ||
sourceSheet, | ||
rows, | ||
columns, | ||
values, | ||
metric: 'sum', | ||
cacheFields, | ||
// defined in <pivotTableDefinition> of xl/pivotTables/pivotTable1.xml; | ||
// also used in xl/workbook.xml | ||
cacheId: '10', | ||
}; | ||
} | ||
|
||
function validate(worksheet, model) { | ||
if (worksheet.workbook.pivotTables.length === 1) { | ||
throw new Error( | ||
'A pivot table was already added. At this time, ExcelJS supports at most one pivot table per file.' | ||
); | ||
} | ||
|
||
if (model.metric && model.metric !== 'sum') { | ||
throw new Error('Only the "sum" metric is supported at this time.'); | ||
} | ||
|
||
const headerNames = model.sourceSheet.getRow(1).values.slice(1); | ||
const isInHeaderNames = objectFromProps(headerNames, true); | ||
for (const name of [...model.rows, ...model.columns, ...model.values]) { | ||
if (!isInHeaderNames[name]) { | ||
throw new Error(`The header name "${name}" was not found in ${model.sourceSheet.name}.`); | ||
} | ||
} | ||
|
||
if (!model.rows.length) { | ||
throw new Error('No pivot table rows specified.'); | ||
} | ||
|
||
if (!model.columns.length) { | ||
throw new Error('No pivot table columns specified.'); | ||
} | ||
|
||
if (model.values.length !== 1) { | ||
throw new Error('Exactly 1 value needs to be specified at this time.'); | ||
} | ||
} | ||
|
||
function makeCacheFields(worksheet, fieldNamesWithSharedItems) { | ||
// Cache fields are used in pivot tables to reference source data. | ||
// | ||
// Example | ||
// ------- | ||
// Turn | ||
// | ||
// `worksheet` sheet values [ | ||
// ['A', 'B', 'C', 'D', 'E'], | ||
// ['a1', 'b1', 'c1', 4, 5], | ||
// ['a1', 'b2', 'c1', 4, 5], | ||
// ['a2', 'b1', 'c2', 14, 24], | ||
// ['a2', 'b2', 'c2', 24, 35], | ||
// ['a3', 'b1', 'c3', 34, 45], | ||
// ['a3', 'b2', 'c3', 44, 45] | ||
// ]; | ||
// fieldNamesWithSharedItems = ['A', 'B', 'C']; | ||
// | ||
// into | ||
// | ||
// [ | ||
// { name: 'A', sharedItems: ['a1', 'a2', 'a3'] }, | ||
// { name: 'B', sharedItems: ['b1', 'b2'] }, | ||
// { name: 'C', sharedItems: ['c1', 'c2', 'c3'] }, | ||
// { name: 'D', sharedItems: null }, | ||
// { name: 'E', sharedItems: null } | ||
// ] | ||
|
||
const names = worksheet.getRow(1).values; | ||
const nameToHasSharedItems = objectFromProps(fieldNamesWithSharedItems, true); | ||
|
||
const aggregate = columnIndex => { | ||
const columnValues = worksheet.getColumn(columnIndex).values.splice(2); | ||
const columnValuesAsSet = new Set(columnValues); | ||
return toSortedArray(columnValuesAsSet); | ||
}; | ||
|
||
// make result | ||
const result = []; | ||
for (const columnIndex of range(1, names.length)) { | ||
const name = names[columnIndex]; | ||
const sharedItems = nameToHasSharedItems[name] ? aggregate(columnIndex) : null; | ||
result.push({name, sharedItems}); | ||
} | ||
return result; | ||
} | ||
|
||
module.exports = {makePivotTable}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,20 @@ | ||
'use strict'; | ||
|
||
module.exports = { | ||
OfficeDocument: | ||
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument', | ||
OfficeDocument: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument', | ||
Worksheet: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet', | ||
CalcChain: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/calcChain', | ||
SharedStrings: | ||
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings', | ||
SharedStrings: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings', | ||
Styles: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles', | ||
Theme: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme', | ||
Hyperlink: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', | ||
Image: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', | ||
CoreProperties: | ||
'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties', | ||
ExtenderProperties: | ||
'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties', | ||
CoreProperties: 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties', | ||
ExtenderProperties: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties', | ||
Comments: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments', | ||
VmlDrawing: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing', | ||
Table: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/table', | ||
PivotCacheDefinition: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition', | ||
PivotCacheRecords: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords', | ||
PivotTable: 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable', | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
const BaseXform = require('../base-xform'); | ||
|
||
class WorkbookPivotCacheXform extends BaseXform { | ||
render(xmlStream, model) { | ||
xmlStream.leafNode('pivotCache', { | ||
cacheId: model.cacheId, | ||
'r:id': model.rId, | ||
}); | ||
} | ||
|
||
parseOpen(node) { | ||
if (node.name === 'pivotCache') { | ||
this.model = { | ||
cacheId: node.attributes.cacheId, | ||
rId: node.attributes['r:id'], | ||
}; | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
parseText() {} | ||
|
||
parseClose() { | ||
return false; | ||
} | ||
} | ||
|
||
module.exports = WorkbookPivotCacheXform; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.