数据透视表
@univerjs/preset-sheets-advanced
数据透视表是一种强大的数据分析工具,允许用户快速汇总、分析和可视化大量数据。它支持多种数据操作和格式设置选项,帮助用户更直观地理解数据。
简介
数据透视表通常用于以下场景:
- 汇总数据:数据透视表可以帮助您快速汇总数据,以便更好地了解数据的整体情况。
- 分析数据:数据透视表可以帮助您分析数据,找出数据中的模式和关系。
- 探索数据:数据透视表可以帮助您探索数据,发现数据中的规律和趋势。
- 可视化数据:数据透视表可以帮助您将数据可视化,以便更好地理解数据。
数据透视表通常包含以下几个要素:
- 行标签:数据透视表中的行标签用于对数据进行分组。
- 列标签:数据透视表中的列标签用于对数据进行分组。
- 值:数据透视表中的值用于对数据进行汇总。
在数据透视表中,您可以根据需要对行标签、列标签和值进行调整,以便更好地分析数据。
数据源
在 Univer Sheets 中,数据透视表的数据源可以是一个工作表(Worksheet),也可以是一个数据区域。您可以根据需要选择不同的数据源,以便更好地分析数据。 我们会以数据源的第一行作为默认的列标签,其他部分作为数据源。因此我们要求数据源尽可能的归整,尽可能避免空行,空列。 在同一列的数据中,我们会自动识别数据类型,以便更好的支持数据透视表的功能。假设在某一列中,数据类型不一致,我们会默认将该列的数据类型设置为字符串。
在 Univer Sheets 中,数据透视表会自动响应数据的变化,当数据源发生变化时,数据透视表会自动更新,以便更好地分析数据。
筛选
数据透视表支持对行标签和列标签进行筛选,同时提供筛选字段,该字段不会参与数据透视表主要数据分类汇总部分的排版,但是可以用于筛选数据。
数据透视表支持两种筛选方式:
- 标签筛选:标签筛选是直接作用于当前标签的维度的项(item), 例如在省份维度下,我们可以过滤掉部分省份。
- 值筛选:值筛选需要指定一个值维度,然后会对值字段的汇总结果进行筛选,例如我们可以过滤掉销售额小于 1000 的数据。
排序
数据透视表的排序只会在行/列字段生效,排序方式支持升序和降序,排序方式使用 localCompare 方式。后续我们会支持更多的排序方式,例如拼音排序。
数据透视表的排序支持两种方式:
- 标签排序:标签排序是直接作用于当前标签的维度的项(item), 例如在省份维度下,我们可以按照省份名称排序。
- 值排序:值排序需要指定一个值维度,然后会对值字段的汇总结果进行排序,例如我们可以按照销售额排序。
汇总
数据透视表支持 11 种 excel 支持的汇总方式,包括:求和、计数、数据计数、平均值、最大值、最小值、阶乘、标准差、方差、总体标准差、总体方差。
数据透视表支持多个值维度,同时可以定制多值维度所在区域(value position)和位置(value index)。
这是很复杂的排版逻辑, 因此我们仅支持了表格视图,后续我们会根据情况支持更多的视图。
下钻
下钻是指在数据透视表的值区域中,用户可以通过双击某个单元格,查看该单元格对应的详细数据。例如,用户可以在数据透视表中点击某个单元格,查看该单元格的详细数据,以便更好地追溯原数据。
分组
数据透视表支持分组功能,目前 Univer Sheets 尚未支持。
分组主要有以下三种方式:
- 日期分组:日期分组是指将日期数据按照年、月、日等单位进行分组。
- 数字分组:数字分组是指将数字数据按照一定的区间进行分组。
- 元素分组:元素分组是指将元素数据按照一定的规则进行分组。
预设模式
打印功能被包含在 @univerjs/preset-sheets-advanced
预设中。
安装
@univerjs/preset-sheets-advanced 的 UniverSheetsAdvancedPreset
预设在运行时依赖 UniverSheetsDrawingPreset
预设,请先安装 @univerjs/preset-sheets-drawing。
npm install @univerjs/preset-sheets-drawing @univerjs/preset-sheets-advanced
使用
import { UniverSheetsAdvancedPreset } from '@univerjs/preset-sheets-advanced'
import UniverPresetSheetsAdvancedZhCN from '@univerjs/preset-sheets-advanced/locales/zh-CN'
import { UniverSheetsCorePreset } from '@univerjs/preset-sheets-core'
import UniverPresetSheetsCoreZhCN from '@univerjs/preset-sheets-core/locales/zh-CN'
import { UniverSheetsDrawingPreset } from '@univerjs/preset-sheets-drawing'
import UniverPresetSheetsDrawingZhCN from '@univerjs/preset-sheets-drawing/locales/zh-CN'
import { createUniver, LocaleType, merge } from '@univerjs/presets'
import '@univerjs/preset-sheets-core/lib/index.css'
import '@univerjs/preset-sheets-drawing/lib/index.css'
import '@univerjs/preset-sheets-advanced/lib/index.css'
const { univerAPI } = createUniver({
locale: LocaleType.ZH_CN,
locales: {
[LocaleType.ZH_CN]: merge(
{},
UniverPresetSheetsCoreZhCN,
UniverPresetSheetsDrawingZhCN,
UniverPresetSheetsAdvancedZhCN,
),
},
presets: [
UniverSheetsCorePreset(),
UniverSheetsDrawingPreset(),
UniverSheetsAdvancedPreset(),
],
})
如果你拥有 Univer 的商业许可证,请参考在客户端使用许可证进行配置。
插件模式
安装
npm install @univerjs-pro/sheets-pivot @univerjs-pro/sheets-pivot-ui
使用
import { UniverSheetsPivotTablePlugin } from '@univerjs-pro/sheets-pivot'
import { UniverSheetsPivotTableUIPlugin } from '@univerjs-pro/sheets-pivot-ui'
import SheetsPivotTableUIZhCN from '@univerjs-pro/sheets-pivot-ui/locale/zh-CN'
import SheetsPivotTableZhCN from '@univerjs-pro/sheets-pivot/locale/zh-CN'
import { LocaleType, merge, Univer } from '@univerjs/core'
import '@univerjs-pro/sheets-pivot-ui/lib/index.css'
import '@univerjs-pro/sheets-pivot/facade' // [!code
const univer = new Univer({
locale: LocaleType.ZH_CN,
locales: {
[LocaleType.ZH_CN]: merge(
{},
SheetsPivotTableZhCN,
SheetsPivotTableUIZhCN,
),
},
})
插件注册
透视表插件支持针对不同的使用场景灵活注册,提供两种模式以适配不同的计算需求。
单独注册在主进程
透视表插件可以单独在主进程中注册。这种模式适用于轻量级任务,操作简单,无需额外配置插件 config。开发者只需引入 UniverSheetsPivotTablePlugin
即可。
univer.registerPlugin(UniverSheetsPivotTablePlugin)
univer.registerPlugin(UniverSheetsPivotTableUIPlugin)
主进程和 worker 进程同时注册
对于需要高性能的场景,透视表插件支持同时注册在主进程和 worker 进程中。通过这种模式,插件可以利用多线程架构实现高效的数据分发和计算处理,显著减轻主进程的计算负担,并提升整体性能。
在这种模式下,主进程主要负责数据的分发、同步和渲染,worker 进程专注于复杂的计算任务。为实现这一模式,需要在注册时通过 config 进行精细化配置。
// 浏览器主进程插件注册文件
// notExecuteFormula 为 true 表示主进程不执行计算,只负责数据的分发、同步和渲染
univer.registerPlugin(UniverSheetsPivotTablePlugin, { notExecuteFormula: true })
univer.registerPlugin(UniverSheetsPivotTableUIPlugin)
// web worker 进程插件注册文件
// notExecuteFormula 为 false 表示 worker 进程执行计算
univer.registerPlugin(UniverSheetsPivotTablePlugin, { notExecuteFormula: false })
如果你拥有 Univer 的商业许可证,请参考在客户端使用许可证进行配置。
Facade API
完整 Facade API 类型定义,请查看 FacadeAPI
添加透视表
FWorkbook.addPivotTable
方法用于添加透视表,成功会返回 FPivotTable
实例。
// 需要确保工作表范围 A1:G9 是非空的
const fWorkbook = univerAPI.getActiveWorkbook()
const unitId = fWorkbook.getId()
const fSheet = fWorkbook.getActiveSheet()
const subUnitId = fSheet.getSheetId()
const sheetName = fSheet.getSheetName()
const sourceInfo = {
unitId,
subUnitId,
sheetName,
range: {
startRow: 0,
startColumn: 0,
endRow: 8,
endColumn: 6,
},
}
const anchorCellInfo = {
unitId,
subUnitId,
row: 20,
col: 8,
}
const fPivotTable = await fWorkbook.addPivotTable(sourceInfo, univerAPI.Enum.PositionTypeEnum.Existing, anchorCellInfo)
const pivotTableId = fPivotTable.getPivotTableId()
// 设置状态,避免重复添加维度
let hasAdded = false
// 透视表的创建是异步的,因此需要等待透视表渲染完成后再添加维度
univerAPI.addEvent(univerAPI.Event.PivotTableRendered, (params) => {
if (!hasAdded && params.pivotTableId === pivotTableId) {
fPivotTable.addField(1, univerAPI.Enum.PivotTableFiledAreaEnum.Row, 0)
fPivotTable.addField(1, univerAPI.Enum.PivotTableFiledAreaEnum.Value, 0)
hasAdded = true
}
})
获取透视表
FWorkbook.getPivotTableByCell
方法通过锚定单元格获取透视表实例。如果目标单元格在透视表中,它将返回 FPivotTable
实例。
const fWorkbook = univerAPI.getActiveWorkbook()
const unitId = fWorkbook.getId()
const fSheet = fWorkbook.getActiveSheet()
const subUnitId = fSheet.getSheetId()
// 从 FWorkbook 中获取透视表实例
const fPivotTable = fWorkbook.getPivotTableByCell(unitId, subUnitId, 0, 8)
// 从 FSheet 中获取透视表实例
const pivotTable2 = fSheet.getPivotTableByCell(0, 8)
FPivotTable
实例中有一些方法,可以用来操作透视表。
方法 | 描述 |
---|---|
remove | 删除透视表实例 |
getConfig | 获取当前透视表的配置信息,其中包括数据源范围,锚定单元格, 维度配置信息 |
addField | 添加一个维度 |
removeField | 删除一个维度 |
updateFieldPosition | 更新维度的所在区域以及顺序 |
updateValuePosition | 更新 ∑Value 的位置 |
setSubtotalType | 设置汇总方式 |
setLabelSort | 设置标签排序 |
setLabelManualFilter | 设置标签筛选 |
renameField | 重命名维度 |
setValueFilter | 设置值维度 |
reset | 重置透视表的维度配置 |
setFieldsConfig | 设置透视表的维度配置 |
getValueFilters | 获取透视表的值维度配置 |
class FPivotTable {
/**
* @description Get the pivot table config by the pivot table id.
* @typedef PivotTableConfig
* @property {TargetInfo} targetCellInfo The target cell info of the pivot table.
* @property {SourceInfo} sourceRangeInfo The source data range info of the pivot table.
* @property {boolean} isEmpty The pivot table is empty or not.
* @property {object} fieldsConfig The snapshot of the pivot table fields config.
* @returns {PivotTableConfig|undefined} The pivot table config or undefined.
*/
getConfig(): IPivotTableConfig
/**
* @description Remove a pivot table from the workbook by pivot table id
*/
async remove(): void
/**
*@description Add a pivot field to the pivot table.
* @param {string|number} dataFieldIdOrIndex The data field id.
* @param {PivotTableFiledAreaEnum} fieldArea The area of the field.
* @param {number} index The index of the field in the target area.
* @returns {boolean} Whether the pivot field is added successfully.
*/
async addField(dataFieldIdOrIndex: string | number, fieldArea: PivotTableFiledAreaEnum, index: number): Promise<boolean>
/**
* @description Remove a pivot field from the pivot table
* @param {string[]} fieldIds The deleted field ids.
* @returns {boolean} Whether the pivot field is removed successfully.
*/
async removeField(fieldIds: string[]): Promise<boolean>
/**
* @description Update the pivot table field position.
* @param {string} fieldId - The moved field id.
* @param {PivotTableFiledAreaEnum} area - The target area of the field.
* @param {number} index - The target index of the field, if the index is bigger than the field count in the target area, the field will be moved to the last, if the index is smaller than 0, the field will be moved to the first.
* @returns {boolean} Whether the pivot field is moved successfully.
*/
async updateFieldPosition(fieldId: string, area: PivotTableFiledAreaEnum, index: number): Promise<boolean>
/**
* @description If there are multiple value fields in the pivot table, you can update the position of the value field, which only can be position in row or column.
* @param {PivotTableValuePositionEnum} position - The position of the value field.
* @param {number} index - The index of the value field.
* @returns {boolean} Whether the pivot value field is moved successfully.
*/
async updateValuePosition(position: PivotTableValuePositionEnum, index: number): Promise<boolean>
/**
* @description Set the pivot table subtotal type for value field, it only works for the value field.
* @param {string} fieldId - The field id.
* @param {PivotSubtotalTypeEnum} subtotalType - The subtotal type of the field.
* @returns {boolean} Whether the pivot table subtotal type is set successfully.
*/
async setSubtotalType(fieldId: string, subtotalType: PivotSubtotalTypeEnum): Promise<boolean>
/**
* @description Set the pivot table sort info.
* @param {string} tableFieldId - The field id of the sort.
* @param {PivotTableSortInfo} info - The sort info.
* @typedef PivotTableSortInfo
* @property {PivotDataFieldSortOperatorEnum} type The sort operator of the field items.
* @returns {boolean} Whether the pivot table sort info is set successfully.
*/
async setLabelSort(tableFieldId: string, info: IPivotTableSortInfo): Promise<boolean>
/**
* @description Set the pivot table filter.
* @param {string} tableFieldId - The field id of the filter.
* @param {string[]} items - The items of the filter.
* @returns {boolean} Whether the pivot table filter is set successfully.
*/
async setLabelFilter(tableFieldId: string, items: string[], isAll?: boolean): Promise<boolean>
/**
* @description Rename the pivot table field.
* @param {string} fieldId - The field id.
* @param {string} name - The new name of the field.
* @returns {boolean} Whether the pivot table field is renamed successfully.
*/
async renameField(fieldId: string, name: string): Promise<boolean>
/**
* @description Set the pivot table value filter. A value filter is used to filter the data based on the value of a field.
* @param {string} fieldId - The field id of the filter. Only one value filer can be set for a field.
* @param {Omit<IPivotTableValueFilter, 'type'>} filterInfo - The filter info. The undefined value will be removed from the old filter.
* @typedef filterInfo
* @property {valueGreaterThan} operator - The filter operator is used to compare the value of the field with the expected value.Currently, The following operators are supported: valueBetween, valueEqual, valueGreaterThan, valueGreaterThanOrEqual, valueLessThan, valueLessThanOrEqual,valueNotBetween,valueNotEqual.
* @property {number} expected - The expected value.
* @property {string} valueFieldId - The value field id.
* @returns {boolean} Whether the pivot table value filter is set successfully.
*/
async setValueFilter(fieldId: string, filterInfo: Omit<IPivotTableValueFilter, 'type'>): Promise<boolean>
/**
* @description Clear the fields by provided field area or clear all fields.
* @param {PivotTableFiledAreaEnum} [resetArea] The area of the field to reset or undefined to reset all fields.
* @returns {Promise<boolean>} Whether the pivot table fields are reset successfully.
*/
reset(resetArea?: PivotTableFiledAreaEnum): Promise<boolean>
/**
* @description Set the pivot table fields config.It will add fields to the pivot table from provided config.Before setting the fields config, you should ensure the pivot table is empty to avoid the conflict.
* @param {IPivotTableConfig['fieldsConfig']} config The pivot table fields config.
* @returns {Promise<boolean>} Whether the pivot table fields config is set successfully.
*/
setFieldsConfig(config: IPivotTableConfig['fieldsConfig']): Promise<boolean>
/**
* Get all value filters of the pivot table. In pivot table, the value filter must be applied in order.So the order of the value filter is important.
* @returns {IValueFilterInfoItem[]} The value filter info list.
*/
getValueFilters(): IValueFilterInfoItem[]
}
事件监听
完整事件类型定义,请查看 Events。
univerAPI.Event.BeforePivotTableAdd
事件在添加透视表之前触发。
const disposable = univerAPI.addEvent(univerAPI.Event.BeforePivotTableAdd, (params) => {
const { positionType, targetCellInfo } = params
if (positionType === univerAPI.Enum.PositionTypeEnum.Existing && targetCellInfo.sheetName === 'Sheet 1') {
// 取消添加透视表操作
params.cancel = true
console.log(`无法将透视表添加到 ${targetCellInfo.sheetName} 工作表`)
}
})
// 移除事件监听器,使用 `disposable.dispose()`
univerAPI.Event.PivotTableAdded
事件在添加透视表之后触发。
const disposable = univerAPI.addEvent(univerAPI.Event.PivotTableAdded, (params) => {
const { positionType, targetCellInfo } = params
if (positionType === univerAPI.Enum.PositionTypeEnum.Existing) {
console.log('在现有工作表中创建了一个透视表')
console.log(`
目标工作表名称为 ${targetCellInfo.sheetName},
目标行为 ${targetCellInfo.row}, 目标列为 ${targetCellInfo.column}
`)
} else {
console.log('在新工作表中创建了一个透视表')
}
})
// 移除事件监听器,使用 `disposable.dispose()`
univerAPI.Event.BeforePivotTableMove
事件在移动透视表之前触发。
const disposable = univerAPI.addEvent(univerAPI.Event.BeforePivotTableMove, (params) => {
const { pivotTableId, targetCellInfo } = params
if (pivotTableId === 'pivotTable1' && targetCellInfo.sheetName === 'Sheet 1') {
// 取消移动透视表操作
params.cancel = true
console.log(`无法将透视表移动到 ${targetCellInfo.sheetName} 工作表`)
}
})
// 移除事件监听器,使用 `disposable.dispose()`
univerAPI.Event.PivotTableMoved
事件在移动透视表之后触发。
const disposable = univerAPI.addEvent(univerAPI.Event.PivotTableMoved, (params) => {
const { pivotTableId, targetCellInfo, originTargetInfo } = params
if (pivotTableId === 'pivotTable1') {
console.log(`
透视表从行: ${originTargetInfo.row} & 列: ${originTargetInfo.column}
移动到工作表 ${targetCellInfo.sheetName} 行: ${targetCellInfo.row} & 列: ${targetCellInfo.column}
`)
}
})
// 移除事件监听器,使用 `disposable.dispose()`
univerAPI.Event.PivotTableRendered
事件在透视表渲染完成后触发。
// import { unionPivotViewRange } from '@univerjs-pro/sheets-pivot';
const disposable = univerAPI.addEvent(univerAPI.Event.PivotTableRendered, (params) => {
const { pivotTableId, rangeInfo } = params
console.log(`透视表 ${pivotTableId} 已渲染`)
if (rangeInfo) {
console.log(unionPivotViewRange(rangeInfo))
}
})
// 移除事件监听器,使用 `disposable.dispose()`
univerAPI.Event.PivotTableRemoved
事件在透视表被删除后触发。
const disposable = univerAPI.addEvent(univerAPI.Event.PivotTableRemoved, (params) => {
const { pivotTableId } = params
console.log(`透视表 ${pivotTableId} 已被删除`)
})
// 移除事件监听器,使用 `disposable.dispose()`
univerAPI.Event.PivotTableFieldAdded
事件在透视表添加维度后触发。
const fWorkbook = univerAPI.getActiveWorkbook()
const disposable = univerAPI.addEvent(univerAPI.Event.PivotTableFieldAdded, (params) => {
const { pivotTableId, fieldId, fieldArea, fieldIndex } = params
const pivotTable = fWorkbook.getPivotTableById(pivotTableId)
const fieldSetting = pivotTable.getFieldSetting('fieldId')
console.log(fieldSetting)
})
// 移除事件监听器,使用 `disposable.dispose()`
univerAPI.Event.PivotTableFieldRemoved
事件在透视表删除维度后触发。
const fWorkbook = univerAPI.getActiveWorkbook()
const disposable = univerAPI.addEvent(univerAPI.Event.PivotTableFieldRemoved, (params) => {
const { pivotTableId, fieldId } = params
const pivotTable = fWorkbook.getPivotTableById(pivotTableId)
const fieldSetting = pivotTable.getFieldSetting(fieldId)
console.log(fieldSetting)
})
// 移除事件监听器,使用 `disposable.dispose()`
univerAPI.Event.PivotTableFieldMoved
事件在透视表移动维度后触发。
const fWorkbook = univerAPI.getActiveWorkbook()
const disposable = univerAPI.addEvent(univerAPI.Event.PivotTableFieldMoved, (params) => {
const { pivotTableId, fieldId, fieldArea, fieldIndex } = params
const pivotTable = fWorkbook.getPivotTableById(pivotTableId)
const fieldSetting = pivotTable.getFieldSetting(fieldId)
console.log(`源名称 ${fieldSetting.sourceName} 显示名称 ${fieldSetting.displayName}`)
})
// 移除事件监听器,使用 `disposable.dispose()`
univerAPI.Event.PivotTableFieldCollapseChanged
事件在透视表展开/折叠维度后触发。
const fWorkbook = univerAPI.getActiveWorkbook()
const disposable = univerAPI.addEvent(univerAPI.Event.PivotTableFieldCollapseChanged, (params) => {
const { pivotTableId, fieldId, isCollapsed } = params
const pivotTable = fWorkbook.getPivotTableById(pivotTableId)
const fieldSetting = pivotTable.getFieldSetting(fieldId)
console.log(`源名称 ${fieldSetting.sourceName} 显示名称 ${fieldSetting.displayName}`)
})
// 移除事件监听器,使用 `disposable.dispose()`
univerAPI.Event.PivotTableFieldFilterChanged
事件在透视表维度筛选后触发。
const fWorkbook = univerAPI.getActiveWorkbook()
const disposable = univerAPI.addEvent(univerAPI.Event.PivotTableFieldFilterChanged, (params) => {
const { pivotTableId, fieldId, filter } = params
const pivotTable = fWorkbook.getPivotTableById(pivotTableId)
const fieldSetting = pivotTable.getFieldSetting(fieldId)
console.log(`检查列表筛选值为 ${fieldSetting.filterInfo.checklist}`)
})
// 移除事件监听器,使用 `disposable.dispose()`
univerAPI.Event.PivotTableFieldSortChanged
事件在透视表维度排序后触发。
const fWorkbook = univerAPI.getActiveWorkbook()
const disposable = univerAPI.addEvent(univerAPI.Event.PivotTableFieldSortChanged, (params) => {
const { pivotTableId, fieldId, sort } = params
const pivotTable = fWorkbook.getPivotTableById(pivotTableId)
const fieldSetting = pivotTable.getFieldSetting(fieldId)
console.log(`排序信息为 ${fieldSetting.sortInfo}`)
})
// 移除事件监听器,使用 `disposable.dispose()`
univerAPI.Event.PivotTableFieldSettingChanged
事件在透视表维度设置变更后触发。
const fWorkbook = univerAPI.getActiveWorkbook()
const disposable = univerAPI.addEvent(univerAPI.Event.PivotTableFieldSettingChanged, (params) => {
const { pivotTableId, fieldId, setting } = params
const pivotTable = fWorkbook.getPivotTableById(pivotTableId)
const fieldSetting = pivotTable.getFieldSetting(fieldId)
console.log(`源名称 ${fieldSetting.sourceName} 显示名称 ${fieldSetting.displayName}`)
})
// 移除事件监听器,使用 `disposable.dispose()`
univerAPI.Event.PivotTableValuePositionChanged
事件在透视表值位置变更后触发。
const fWorkbook = univerAPI.getActiveWorkbook()
const disposable = univerAPI.addEvent(univerAPI.Event.PivotTableValuePositionChanged, (params) => {
const { pivotTableId, valueId, position } = params
const pivotTable = fWorkbook.getPivotTableById(pivotTableId)
const valueSetting = pivotTable.getValueSetting(valueId)
console.log(`源名称 ${valueSetting.sourceName} 显示名称 ${valueSetting.displayName}`)
})
// 移除事件监听器,使用 `disposable.dispose()`