Skip to main content

透视工具

透视能力增强相关工具。包括了以下方法和相关的 TypeScript 类型定义,通过这些工具你可以快速地从明细数据中构建并定制交叉表/透视表。

  • buildDrillTree: 从明细数据中构建下钻树
  • buildRecordMatrix: 从明细数据中构建 RecordMatrix
  • convertDrillTreeToCrossTree: 将下钻树转换为 CrossTable 的输入

数据结构——下钻树#

下钻树用来表示数据按照一定的维度序列被不断分组后形成的树结构。ali-react-table 提供了 buildDrillTree 方法来从数据中构建下钻树。

下钻树构建示例#

预览点击展开或收拢
1. 输入数据(输入格式为一个对象的数组)
2. 处理过程
buildDrillTree(data, ['subs', 'shop', 'month'])
3. 输出一个 DrillNode 的数组
源码点击展开或收拢

下钻树节点结构#

下钻树中的树节点 DrillNode 结构如下:

interface DrillNode {  key: string  value: string  path: string[]  children?: DrillNode[]  hasChild?: boolean}
  • key 用于唯一标记该节点
  • value 字段表示该节点的值
  • path 表示下钻树中到当前节点路径上的值的序列
  • children 记录了当前节点的子节点数组
  • hasChild 用于标记当前是否有子节点

buildDrillTree#

在构建下钻树时,buildDrillTree(data, codes, options?) 的 options 参数可以调整默认的构建配置:

  • options.encode
    • 默认情况下,buildDrillTree 会使用默认的 simpleEncode 来编码 path 来生成 key 字段
    • 你也可以通过 options.encode 提供一个自定义的编码函数用于自定义 key 的生成方式。
  • options.includeTopWrapper
    • 默认情况下,buildDrillTree 不会生成「总计」节点。
    • options.includeTopWrapper = true 时,buildDrillTree 返回的结果的第一层将为「总计」节点
    • 注意 buildDrillTree 的返回结果总是一个数组,即使数组中只有一个「总计」节点
  • options.topValue:用于指定「总计」节点的 value,默认为 "总计"
  • options.isExpand:根据节点的 key 判断一个节点是否展开,没有展开的节点将不进行下钻;默认为 (key: string) => true

options.isExpand 与剪枝优化#

当数据量较大且维度序列较深时,buildDrillTree 会构建出一棵非常庞大的树,但后续使用时我们往往并不需要用到所有的节点,大部分节点在使用时都是被「折叠」的。isExpand 回调可以告诉 buildDrillTree 哪些节点被展开了,哪些节点是收拢的。当 isExpand(key) 返回值为 false 时,buildDrillTree 将跳过当前节点的子节点的生成过程。

在当前节点的子节点被跳过的时候,buildDrillTree 会为当前节点设置 node.hasChild = true

交叉表与下钻树#

交叉表左侧/上方的数据结构与下钻树非常类似:

  • 节点的主要字段均为 key / value / children
  • 简单情况下,下钻树可以直接作为 CrossTable 的 leftTree / topTree

但两者描述的结构是不同的:

  • LeftCrossTreeNode / TopCrossTreeNode 描述了表格结构,是展现层面的结构;
  • DrillNode 描述数据被不断分组后的结果,是数据层面的结构。

很多时候,下钻树与交叉表的 leftTree/topTree 并不是对应的,ali-react-table 提供了一些其他方法来在两者之间进行转换。

数据结构——数据立方#

下钻树具有一个下钻维度序列;而透视表(不要过分纠结这里透视表的含义,这里的透视表指的只是简单的多维度行列交叉表)具有两个下钻维度序列:左侧维度序列(也称行维度),以及上方维度序列(也成列维度)。在实践中,我们可以使用「左侧的值序列」+「上方的值序列」来唯一确定透视表中的每一个单元格。对这两个值序列进行编码(分别记为 leftKey 和 topKey),我们可以用两个字符串来表示一个单元格。

RecordMatrix 是透视表的数据源容器,存放了一个透视表所有单元格的数据。RecordMatrix 的数据结构是一个二维的 Map,类型为 type RecordMatrix = Map<string, Map<string, any>>。根据 leftKey 和 topKey,从 matrix 中可以快速找到对应单元格的数据,对应的使用方式为 matrix.get(leftKey).get(topKey).

buildRecordMatrix 从明细数据中构建数据立方#

RecordMatrix 的用法非常简单,但其构建过程则相对麻烦一些。ali-react-table/pivot 提供了 buildRecordMatrix 方法,该方法用于从明细数据中根据两个下钻维度序列生成相应的数据立方,具体接口如下:

function buildRecordMatrix(buildConfig: BuildRecordMatrixConfig): RecordMatrix

BuildRecordMatrixConfig 结构:

字段类型必传/可选说明
leftCodesstring[]必传表格左侧 下钻维度(行维度)
topCodesstring[]必传表格上方 下钻维度(列维度)
dataany[]必传明细数据,格式为 对象数组
aggregate(slice:any[]) => any可选聚合函数,若不提供则不对数据进行聚合
encode(valuePath: string[]) => string可选参见上方 buildDrillTree 文档中的 encode
isLeftExpand(key: string) => boolean可选参见上方 buildDrillTree 文档中的 isExpand
isTopExpand(key: string) => boolean可选参见上方 buildDrillTree 文档中的 isExpand
prebuiltLeftTreeDrillNode[]可选预先构建好的 左侧下钻树
prebuiltTopTreeDrillNode[]可选预先构建好的 上方下钻树

注意事项:

  • 如果提供了 prebuiltLeftTree,预构建的左侧下钻树必须使用相同的 leftCodes 和 data 进行构建,且构建时必须包含总计节点。(同理于 prebuiltTopTree)

buildRecordMatrix 示例#

下面的例子描述了 buildDrillTree,buildRecordMatrix 以及交叉表是如何配套使用的。在下面的示例中,我们直接将 下钻树作为了交叉表组件的 leftTree 和 topTree,故 leftTree/topTree 中节点的 key 与 matrix 中的索引是一致的。在交叉表的 getValue 回调中,我们直接使用 leftNode.key 与 topNode.key 就能从 matrix 取出对应单元格的数据。

预览点击展开或收拢
源码点击展开或收拢

convertDrillTreeToCrossTree#

「下钻树直接作为 leftTree 和 topTree」虽然方便,但有一些明显的不足:

  • 缺少总计/小计节点,需要手动添加
  • 无法满足多个指标同时展示的需求
  • 不支持收拢展开功能
  • 小计节点与父节点共用同一份数据,但在表格结构上两者又必须使用不同的 key,两者发生冲突

这些限制的原因在于表格展现层和数据层使用了相同的 key,导致两者必须具有相同的结构。 convertDrillTreeToCrossTree 用于将下钻树转换为交叉表的 leftTree/topTree,在转换过程中为展现层和数据层分配不同的 key 从而解除了上述限制。并提供以下特性:

  • 支持单指标或多指标,多指标情况下支持指标放在交叉表左侧或上方
  • 支持生成小计节点
  • 支持收拢展开

转换过程中,该函数会执行以下处理:

  • 该函数会将下钻树中的 key 复制到 crossTreeNode.data.dataKey 上(crossTreeNode 指生成的交叉树节点)
  • 该函数会将下钻树中的 path 复制到 crossTreeNode.data.dataPath
  • 根据多指标或小计节点相关配置,生成对应的 CrossTreeNode,并为新的节点生成 key 值
    • 多指标的情况下,指标配置会被复制到 crossTreeNode.data.indicator
    • 新增的节点中,data.dataKey 和 data.dataPath 保存了对应下钻树节点的 key 和 path
  • 开启展开/收拢功能后,为部分 CrossTreeNode 生成 title 用于渲染交互按钮

转换函数具体 api:

function convertDrillTreeToCrossTree<T extends CrossTreeNode = CrossTreeNode>(  drillTree: DrillNode[],  options: ConvertOptions<T> = {},): T[]
type ConvertOptions<T extends CrossTreeNode = CrossTreeNode> = {  /** 需要在子节点处附加的 指标节点 */  indicators?: CrossTableIndicator[]
  /** 自定义的编码函数,用于根据下钻的值序列生成唯一的字符串.   * 该参数留空 表示使用默认的编码方式 */  encode?(valuePath: string[]): string
  /** 为一个值序列生成小计(sub-total)节点.   * 针对每一个父节点,该函数都将被调用一次;   * * 函数返回 null, 表示对应父节点不需要小计节点;   * * 返回 `{ position: 'start' | 'end', value: string; data?: any }`   *  表明所要生成的小计节点的摆放位置、文本、附加的数据   *   * 该参数留空 表示所有节点均不需要生成小计节点 */  generateSubtotalNode?(    drillNode: DrillNode,  ): null | {    position: 'start' | 'end'    value: string  }
  /** 是否支持节点的展开与收拢,默认为 false。   * 当该选项为 true 时,展开/收拢才会开启,相关的按钮也才会被渲染 */  supportsExpand?: boolean
  /** 展开的节点的 key 数组 */  expandKeys?: string[]
  /** 展开节点发生变化时的回调 */  onChangeExpandKeys?(nextKeys: string[], targetNode: DrillNode, action: 'collapse' | 'expand'): void
  /** 是否强制展开总计节点,默认为 true */  enforceExpandTotalNode?: boolean}

基于交叉表和透视工具构建透视表#

有了这些透视工具和合适的渲染层,接下来我们就可以从明细数据中构建透视表了。下面这个例子中引入了一个简单的透视表设计器,提供了最基本的维度选择和表格结构调整功能。根据设计器的状态,调整构建下钻树或转换时的参数,就能够方便地实现透视表的各项功能了。

预览点击展开或收拢
表格结构:
清空维度:
左侧的维度序列:子公司>门店
左侧的可添加的维度:月份
上方维度序列:季度
上方的可添加的维度:月份
源码点击展开或收拢