# Transform 数据转换

一个数据视图（DataSet.View）通过 Transform 来进行数据转换操作，其语法如下：

```javascript
dv.transform({
  type: connectorName,
  ...otherOptions,
});
```

举个例子：

```javascript
const testCSV = `Expt,Run,Speed
 1,1,850
 1,2,740
 1,3,900
 1,4,1070`;

const dv = new DataSet.DataView().source(testCSV, {
  type: 'csv',
});
console.log(dv.rows);
/*
 * dv.rows:
 * [
 *   {Expt: " 1", Run: "1", Speed: "850"}
 *   {Expt: " 1", Run: "2", Speed: "740"}
 *   {Expt: " 1", Run: "3", Speed: "900"}
 *   {Expt: " 1", Run: "4", Speed: "1070"}
 * ]
 */

dv.transform({
  type: 'filter',
  callback(row) {
    return row.Run !== '1';
  },
});
console.log(dv.rows);
/*
 * dv.rows:
 * [
 *   {Expt: " 1", Run: "2", Speed: "740"}
 *   {Expt: " 1", Run: "3", Speed: "900"}
 *   {Expt: " 1", Run: "4", Speed: "1070"}
 * ]
 */
```

上述代码中，数据视图实例 `dv` 使用 `csv` 类型的 Connector 载入了一段 CSV 文本，之后执行 `filter` Transform，过滤了某些数据。

目前 DataSet 支持以下 Transform：

## 静态处理相关

### filter 数据过滤

具体用法见示例：

```javascript
import { DataView } from '@antv/data-set';

const data = [
  { year: 1990, sales: 200 },
  { year: 1992, sales: 100 },
  { year: 1994, sales: 120 },
  { year: 1995, sales: 85 },
];

const dv = new DataView().source(data);
dv.transform({
  type: 'filter',
  callback(row) {
    return row.sales < 100;
  },
});

console.log(dv.rows); // [ { year: 1995, sales: 85 } ]
```

### map 数据加工

具体用法见示例：

```javascript
const data = [
  { x: 'a', y: 1 },
  { x: 'b', y: 11 },
  { x: 'c', y: 21 },
];

const dv = new DataView().source(data);
dv.transform({
  type: 'map',
  callback(row) {
    row.z = 'z'; // 为每条记录新添加一个 z 字段
    return row;
  },
});

console.log(dv.rows);
/*
[
  { x: 'a', y: 1, z: 'z' },
  { x: 'b', y: 11, z: 'z' },
  { x: 'c', y: 21, z: 'z' }
]
*/
```

### pick 字段过滤

具体用法见示例：

```javascript
const data = [
  { x: 1, y: 11 },
  { x: 2, y: 12 },
  { x: 3, y: 13 },
  { x: 4, y: 14 },
  { x: 5, y: 15 },
];

const dv = new DataView().source(data);
dv.transform({
  type: 'pick',
  fields: ['x'],
});

console.log(dv.rows);
/*
[
  { x: 1 },
  { x: 2 },
  { x: 3 },
  { x: 4 },
  { x: 5 }
]
*/
```

### rename 字段重命名

> alias: rename-fields

具体用法见示例：

```javascript
const data = [{ a: 1, b: 2 }];
const dv = new DataView().source(data);
dv.transform({
  type: 'rename',
  map: {
    a: 'x',
    b: 'y',
  },
});

console.log(dv.rows); // [ { x: 1, y: 2 } ]
```

### reverse 逆序排列

具体用法见示例：

```javascript
const data = [
  { x: 1, y: 1 },
  { x: 2, y: 2 },
  { x: 3, y: 3 },
];
const dv = new DataView().source(data);
dv.transform({
  type: 'reverse',
});

console.log(dv.rows);
/*
[
  { x: 3, y: 3 },
  { x: 2, y: 2 },
  { x: 1, y: 1 }
]
*/
```

### sort 数据排序

具体用法见示例：

```javascript
dv.transform({
  type: 'sort',
  callback(a, b) {
    // 排序依据，和原生 js 的排序 callback 一致
    return a.year - b.year;
  },
});
```

### sort-by 按字段排序

> alias: sortBy

具体用法见示例：

```javascript
dv.transform({
  type: 'sort-by',
  fields: ['year'], // 根据指定的字段集进行排序，与lodash的sortBy行为一致
  order: 'DESC', // 默认为 ASC，DESC 则为逆序
});
```

### subset 获取子集

具体用法见示例：

```javascript
dv.transform({
  type: 'subset',
  startRowIndex: 1, // 保留行的起始索引
  endRowIndex: 2, // 保留行的结束索引
  fields: ['year'], // 保留字段集
});
```

### partition 数据分组

> alias: group | groups

具体用法见示例：

```javascript
dv.transform({
  type: 'partition',
  groupBy: ['year'], // 以year字段进行分组
  orderBy: ['month'], // 以month字段进行排序
});
```

## 数据形变 / 数据补全相关

### fill-rows 补全行

> alias: fillRows

先按照 `groupBy` 和 `orderBy` 进行分组，如果以分组作为补全依据（`fillBy: 'group'`），那么就对比每个分组，以 `orderBy` 序列字段值最全的分组为标准补全数据行数不足的分组。如果以序列作为补全依据（`fillBy: 'order'`），那么就取所有 `orderBy` 序列字段的组合，为每个分组都补充全量的序列。

> 注意！如果原始数据有除 groupBy 和 orderBy 指定的字段以外的字段，那么补充的数据行里会缺失这些字段。这时可以结合 impute Transform 来做字段数据补全。

具体用法见示例：

> fillBy: group

```javascript
const data = [
  { a: 0, b: 0 },
  { a: 0, b: 1 },
  { a: 0, b: 2 },
  { a: 1, b: 1 },
  { a: 1, b: 2 },
  { a: 1, b: 3 },
  { a: 2, b: 0 },
];
const dv = new DataSet.DataView().source(data).transform({
  type: 'fill-rows',
  groupBy: ['a'],
  orderBy: ['b'],
  fillBy: 'group', // 默认为 group，可选值：order
});
console.log(dv.rows);
/*
 * dv.rows 变为:
 * [
 *   { a: 0, b: 0 }, // 分组1 作为第一个序列字段最全（b）的组，被选为基准
 *   { a: 0, b: 1 },
 *   { a: 0, b: 2 },
 *   { a: 1, b: 1 }, // 分组2 序列字段个数和基准组一致，所以不补充数据
 *   { a: 1, b: 2 },
 *   { a: 1, b: 3 },
 *   { a: 2, b: 0 }, // 分组3 缺省数据，根据基准组进行数据补全
 *   { a: 2, b: 1 }, // 这行数据被补充
 *   { a: 2, b: 2 }, // 这行数据被补充
 * ]
 */
```

> fillBy: order

```javascript
// 使用和上例同样的数据
const dv = new DataSet.DataView().source(data).transform({
  type: 'fill-rows',
  groupBy: ['a'],
  orderBy: ['b'],
  fillBy: 'order', // 默认为 group，可选值：order
});
console.log(dv.rows);
/*
 * dv.rows 变为:
 * [
 *   { a: 0, b: 0 }, // 分组1
 *   { a: 0, b: 1 },
 *   { a: 0, b: 2 },
 *   { a: 0, b: 3 }, // 分组1 补充了这行数据，因为全量的序列字段（b）有这个值
 *   { a: 1, b: 0 }, // 分组2 补充了这行数据，因为全量的序列字段（b）有这个值
 *   { a: 1, b: 1 },
 *   { a: 1, b: 2 },
 *   { a: 1, b: 3 },
 *   { a: 2, b: 0 }, // 分组3 缺省数据，根据基准组进行数据补全
 *   { a: 2, b: 1 }, // 这行数据被补充
 *   { a: 2, b: 2 }, // 这行数据被补充
 *   { a: 2, b: 3 }, // 这行数据被补充
 * ]
 */
```

### impute 补全列 / 补全字段

根据配置规则为某个字段补全缺失值。

具体用法见示例：

```javascript
const data = [
  { x: 0, y: 1 },
  { x: 0, y: 2 },
  { x: 0, y: 3 },
  { x: 0 },
  { x: 1, y: 5 },
  { x: 1, y: 6 },
  { x: 1, y: 7 },
  { x: 1 },
  { x: 1, y: 9 },
  { x: 2 },
];
const dv = new DataSet.DataView().source(data).transform({
  type: 'impute',
  field: 'y', // 待补全字段
  groupBy: ['x'], // 分组字段集（传空则不分组）
  method: 'max', // 补全字段值时执行的规则
});
/*
 dv.rows 变为
[
  { x: 0, y: 1 },
  { x: 0, y: 2 },
  { x: 0, y: 3 },
  { x: 0, y: 3 },
  { x: 1, y: 5 },
  { x: 1, y: 6 },
  { x: 1, y: 7 },
  { x: 1, y: 7 },
  { x: 1, y: 9 },
  { x: 2, y: 9 }
]
 */
```

> 补全字段的规则（method）有常见的统计函数：max, min, median, mean

> 还有补充固定值的写法：method 指定为 value，然后 value 指定为填充的常量，譬如

```javascript
dv.transform({
  type: 'impute',
  field: 'y', // 待补全字段
  groupBy: ['x'], // 分组字段集（传空则不分组）
  method: 'value', // 补全常量
  value: 10, // 补全的常量为10
});
```

### fold 字段展开

以指定字段集为 key，展开数据。

具体用法见示例：

```javascript
const data = [
  { country: 'USA', gold: 10, silver: 20 },
  { country: 'Canada', gold: 7, silver: 26 },
];
const dv = ds
  .createView()
  .source(data)
  .transform({
    type: 'fold',
    fields: ['gold', 'silver'], // 展开字段集
    key: 'key', // key字段
    value: 'value', // value字段
    retains: ['country'], // 保留字段集，默认为除 fields 以外的所有字段
  });
/*
 dv.rows 变为
[
  { key: gold, value: 10, country: "USA" },
  { key: silver, value: 20, country: "USA" },
  { key: gold, value: 7, country: "Canada" },
  { key: silver, value: 26, country: "Canada" }
]
 */
```

## 数据比例（百分比）相关 Transform

### percent 总和百分比

统计某个维度下某个字段的值的和占总和的比例（可以分组）。

`field` 是统计发生的字段（求和，求百分比），`dimension` 是统计的维度字段，也就是"每个不同的 `dimension` 下，`field` 值占总和的百分比"，`groupBy` 则是分组字段，每一个分组内部独立求百分比（每一个分组内，最后的 `percent` 字段相加之和为 1）。

具体用法见示例：

```javascript
dv.transform({
  type: 'percent',
  field: 'sold', // 统计销量
  dimension: 'year', // 每年的占比
  groupBy: ['category'], // 以不同产品类别为分组，每个分组内部各自统计占比
  as: 'percent', // 结果存储在 percent 字段
});
```

### proportion 行数百分比

统计某个维度下某个字段的数据条数占总条数的比例（可以分组）。和 percent Transform 类似，但统计的是数据条目的占比，而不是数据总和的占比。

具体用法见示例：

```javascript
dv.transform({
  type: 'proportion',
  dimension: 'year', // 每年的占比
  groupBy: ['category'], // 以不同产品类别为分组，每个分组内部各自统计占比
  as: 'proportion', // 结果存储在proportion字段
});
```

## 数据统计相关

### aggregate 聚合统计

统计处理，支持并行的多种统计。

具体用法见示例：

```javascript
dv.transform({
  type: 'aggregate', // 别名summary
  fields: [], // 统计字段集
  operations: [], // 统计操作集
  as: [], // 存储字段集
  groupBy: [], // 分组字段集
});
```

> 以上 fields/operations/as 这三个数组元素一一对应。“对某个字段 field 进行某种统计操作 operation 结果存储在某个字段上 as。”

支持的 operations: [详见 simple-statistics](https://simplestatistics.org/)。

- count
- max
- min
- mean
- median
- mode
- product
- standardDeviation
- sum
- sumSimple
- variance

### regression 回归曲线

计算两个字段的回归拟合曲线。

具体用法见示例：

```javascript
dv.transform({
  type: 'regression',
  method: 'linear', // 回归算法类型
  fields: ['x', 'y'], // 统计字段
  bandwidth: 0.1, // 步长
  extent: [0, 4], // 结果集里，x的数值范围
  as: ['x', 'y'], // 结果字段
});
```

支持的回归算法类型：

- linear
- exponential
- logarithmic
- power
- polynomial

## 数据分箱相关

### bin.histogram 直方图分箱

单字段

> alias: bin.dot

具体用法见示例：

```javascript
dv.transform({
  type: 'bin.histogram',
  field: 'a', // 对应数轴上的一个点
  bins: 30, // 分箱个数
  binWidth: 10, // 分箱步长（会覆盖bins选项）
  offset: 0, // 分箱偏移量
  groupBy: [], // 分组（用于层叠直方图）
  as: ['x', 'count'], // x 为数组，存储了某个分箱的上下限 [x0, x1]
});
```

### bin.quantile 分位值分箱

单字段

具体用法见示例：

```javascript
dv.transform({
  type: 'bin.quantile',
  field: 'y', // 计算分为值的字段
  as: '_bin', // 保存分为值的数组字段
  groupBy: [], // 分组
  fraction: 4, // 可选，默认四分位
  p: [0.5, 0.3], // 可选，p参数列表，与 fraction 二选一
});
```

### bin.hexagon 六边形分箱

双字段

> alias: bin.hex | hexbin

具体用法见示例：

```javascript
dv.transform({
  type: 'bin.hexagon',
  fields: ['a', 'b'], // 对应坐标轴上的一个点
  bins: [30, 30], // 两个方向上的分箱个数
  binWidth: [10, 1000], // 两个方向上的分箱步长（会覆盖bins的配置）
  offset: [0, 0], // 两个方向上的分箱偏移量
  sizeByCount: false, // 是否根据分箱个数调整分箱大小（六边形的半径）
  as: ['x', 'y', 'count'], // 这个点落在的六边形的顶点坐标集，以及每个分箱内的数据条数
  // x: [ x0, x1, x2, x3, x4, x5 ], y: [ y0, y1, y2, y3, y4, y5 ]
  // count: Number
});
/*
 * 顶点顺序：
 *      3
 * 4          2
 *
 * 5          1
 *      0
 */
```

### bin.rectangle 矩形分箱

双字段

> alias: bin.rect

具体用法见示例：

```javascript
dv.transform({
  type: 'bin.rectangle',
  fields: ['a', 'b'], // 对应坐标轴上的一个点
  bins: [30, 30], // 两个方向上的分箱个数
  binWidth: [10, 10], // 两个方向上的分箱步长（会覆盖bins配置）
  offset: [0, 0], // 两个方向上的分箱偏移量
  sizeByCount: false, // 是否根据分箱个数调整分箱大小
  as: ['x', 'y', 'count'], // 这个点落在的六边形的顶点坐标集
  // x: [ x0, x1, x2, x3 ], y: [ y0, y1, y2, y3 ]
  // count: Number
});
/*
 * 顶点顺序：
 * 3 - 2
 * |   |
 * 0 - 1
 */
```

## 核函数相关

### kernel-smooth.regression 核函数概率密度回归

用于画核函数概率密度回归曲线，支持单字段或者双字段。

具体用法见示例：

```javascript
dv.transform({
  type: 'kernel-smooth.regression',
  fields: ['x', 'y'], // 必填，1 或 2 字段
  method: 'gaussian', // 采用的核函数类型。也可以指定为自定义的函数
  extent: [min(x), max(x)], // 数值范围，默认为 x 字段的数值范围
  bandwidth: 0.4, // 步长，默认采用 silverman 的算法计算
  as: ['x', 'y'], // 结果字段，单字段时，y 为 x 值对应的概率
});
```

支持的核函数类型：

- cosine
- epanechnikov
- gaussian (default)
- quartic
- triangular
- tricube
- triweight
- uniform

### kernel-smooth.density 核函数概率密度分布

用于画核函数概率密度分布热力图，双字段。

具体用法见示例：

```javascript
dv.transform({
  type: 'kernel-smooth.density',
  fields: ['x', 'y'], // 必填
  method: 'gaussian', // 采用的核函数类型。也可以指定为自定义的函数
  extent: [
    [min(x), max(x)],
    [min(y), max(y)],
  ], // 数值范围，默认为 x 以及 y 字段各自的数值范围
  bandwidth: 0.4, // 步长，默认采用 silverman 的算法计算
  as: ['x', 'y'], // 结果字段，单字段时，y 为 x 值对应的概率
});
```

> silverman 提出的 bandwidth 计算算法: [paper](https://ned.ipac.caltech.edu/level5/March02/Silverman/paper.pdf)

支持的核函数类型同上

## 树相关

### hierarchy.treemap 树形图

> alias: treemap

根据树形数据生成树形图 Treemap 布局。

具体用法见示例：

```javascript
dv.transform({
  type: 'hierarchy.treemap',
  field: 'value',
  tile: 'treemapSquarify', // 布局类型
  size: [1, 1], // width, height
  round: false,
  // ratio: 1.618033988749895, // golden ratio
  padding: 0, // 各种 padding 配置
  paddingInner: 0,
  paddingOuter: 0,
  paddingTop: 0,
  paddingRight: 0,
  paddingBottom: 0,
  paddingLeft: 0,
  as: ['x', 'y'], // 矩形的顶点集
  // x: [ x0, x1, x2, x3 ], y: [ y0, y1, y2, y3 ]
});
```

支持的布局类型：

- treemapBinary
- treemapDice
- treemapSlice
- treemapSliceDice
- treemapSquarify
- treemapResquarify

### hierarchy.partition 相邻层次图

> alias: adjacency

根据树形数据生成相邻层次图 Adjacency Diagram 布局，可以通过坐标变换变形为 Sunburst 图。

具体用法见示例：

```javascript
dv.transform({
  type: 'hierarchy.partition',
  field: 'value',
  size: [1, 1], // width, height
  round: false,
  // ratio: 1.618033988749895, // golden ratio
  padding: 0, // 各种 padding 配置
  as: ['x', 'y'], // 矩形的顶点集
  // x: [ x0, x1, x2, x3 ], y: [ y0, y1, y2, y3 ]
});
```

## 图相关

### diagram.arc 弧长链接图

弧长链接图（Arc Diagram）可以变形为和弦图（Chord Diagram）。

> alias: arc

具体用法见示例：

```javascript
dv.transform({
  type: 'diagram.arc',
  y: 0,
  thickness: 0.05, // 节点高度，区间 (0, 1)
  weight: false, // 是否带权重，无权重为弧长链接图，带权重为和弦图
  marginRatio: 0.1, // 空隙比例，区间[0, 1)
  id: (node) => node.id, // 获取节点id
  source: (edge) => edge.source, // 获取边起始点id
  target: (edge) => edge.target, // 获取边结束点id
  sourceWeight: (edge) => edge.value, // 获取边起始点权重
  targetWeight: (edge) => edge.value1, // 获取边结束点权重
  sortBy: null, // 排序，可以按照id，权重（'weight'）或者边数量（'frequency'）排序，也可以自定排序函数
});
```

> 注意！这个 Transform 做完之后，有两部分数据（顶点和边数据），G2 在使用是不能直接通过 chart.source(dv) 来导入数据，只能分别导入顶点和边集合，例如：

```javascript
const nodeView = chart.view();
nodeView.source(dv.nodes);

const edgeView = chart.view();
edgeView.source(dv.edges);
```

### diagram.sankey 桑基图

> alias: sankey

具体用法见示例：

```javascript
dv.transform({
  type: 'diagram.sankey',
  source: (edge) => edge.source, // 边起点id
  target: (edge) => edge.target, // 边终点id
  nodeAlign: 'sankeyJustify', // sankeyLeft / sankeyRight / sankeyCenter
  nodeWidth: 0.02, // 节点宽，范围：(0, 1)
  nodePadding: 0.02, // 节点上下间距，范围：(0, 1)
  sort: undefined | null | ((a: any, b: any) => number); // 同列节点排序  undefined 默认值 在每次迭代过程中按宽度递增、null 按照数据排序 不重新排序、function 根据指定函数进行排序，并在初始化的时候排序一次
});
```

> 注意！这个 Transform 做完后同样需要注意上述 arc transform 一样的数据导入问题

### diagram.voronoi

voronoi 图

> alias: voronoi

具体用法见示例：

```javascript
dv.transform({
  type: 'diagram.voronoi',
  fields: ['field0', 'field1'], // 对应坐标轴上的一个点
  extend: [
    [x0, y0],
    [x1, y1],
  ], // 范围
  size: [width, height], // 范围
  as: ['x', 'y'], // 每个点包围多边形的顶点集
  // x: [ x0, x1, x2, ... ], y: [ y0, y1, y2, ... ]
});
```

## Geo 地理数据相关

### geo.projection 地理映射

具体用法见示例：

```javascript
dv.transform({
  type: 'geo.projection',
  projection: 'geoAiry', // 指定映射类型
  as: ['x', 'y', 'centroid_x', 'centroid_y'], // x，y是对应多边形的顶点集
  // centroid_x是中心点的x坐标
  // centroid_y是中心点y坐标
});
```

### geo.centroid 由地名获取地理位置点

具体用法见示例：

```javascript
dv.transform({
  type: 'geo.centroid',
  field: 'name', // 标注地名的字段
  geoDataView: geoDataView, // 使用的geo数据来源，可以是DataView实例，也可以是DataView实例的name
  as: ['_centroid_x', '_centroid_y'], // _centroid_x是中心点的x坐标
  // _centroid_y是中心点y坐标
});
```

### geo.region 由地名获取地理位置区域

具体用法见示例：

```javascript
dv.transform({
  type: 'geo.region',
  field: 'name', // 标注地名的字段
  geoDataView: geoDataView, // 使用的geo数据来源，可以是DataView实例，也可以是DataView实例的name
  as: ['_x', '_y'], // 多边形的顶点集
  // _x: [ x0, x1, x2, ... ], _y: [ y0, y1, y2, ... ]
});
```

## 其他

### tag-cloud 词云布局

> alias: word-cloud

具体用法见示例：

```javascript
dv.transform({
  type: 'tag-cloud',
  fields: ['text', 'value'], // 参与标签云layout的字段集（前者为文本内容，后者为权重值）
  font: 'serif', // 标签字体
  size: [500, 500], // 画布size，[ width, height ]
  padding: 0,
  spiral: 'archimedean', // 标签螺旋排布规律函数 'archimedean' || 'rectangular' || {function}
  fontSize(d) {
    return d.value;
  }, // 计算标签字体大小的回调函数，d为一行数据
  timeInterval: Infinity, // 最大迭代时间，默认为无限大
  imageMask: { Image }, // Image的实例，必须是 loaded 状态
});
```

> 带图片形状的词云布局实例

```javascript
const imageMask = new Image();
imageMask.crossOrigin = '';
imageMask.src = 'https://zos.alipayobjects.com/rmsportal/EEFqYWuloqIHRnh.jpg';
imageMask.onload = () => {
  dv.transform({
    type: 'tag-cloud',
    imageMask,
  });
};
```
