diff --git a/__tests__/integration/snapshots/static/titanicPointPackSharedDataPadding.svg b/__tests__/integration/snapshots/static/titanicPointPackSharedDataPadding.svg
new file mode 100644
index 0000000000..0cbc949789
--- /dev/null
+++ b/__tests__/integration/snapshots/static/titanicPointPackSharedDataPadding.svg
@@ -0,0 +1,16538 @@
+
\ No newline at end of file
diff --git a/__tests__/integration/snapshots/static/titanicPointPackSharedRowData.svg b/__tests__/integration/snapshots/static/titanicPointPackSharedRowData.svg
new file mode 100644
index 0000000000..38e691e394
--- /dev/null
+++ b/__tests__/integration/snapshots/static/titanicPointPackSharedRowData.svg
@@ -0,0 +1,16538 @@
+
\ No newline at end of file
diff --git a/__tests__/plots/static/index.ts b/__tests__/plots/static/index.ts
index e60774b7f4..55aeaeb419 100644
--- a/__tests__/plots/static/index.ts
+++ b/__tests__/plots/static/index.ts
@@ -311,3 +311,5 @@ export { premierLeagueTable } from './premier-league-table';
export { singlePointBasic } from './single-point-basic';
export { populationFlowChordDefault } from './population-flow-chord-default';
export { aaplLineRrangeXY } from './aapl-line-rangeXY';
+export { titanicPointPackSharedRowData } from './titanic-point-pack-shared-row-data';
+export { titanicPointPackSharedDataPadding } from './titanic-point-pack-shared-data-padding';
diff --git a/__tests__/plots/static/titanic-point-pack-shared-data-padding.ts b/__tests__/plots/static/titanic-point-pack-shared-data-padding.ts
new file mode 100644
index 0000000000..bb8ef58e65
--- /dev/null
+++ b/__tests__/plots/static/titanic-point-pack-shared-data-padding.ts
@@ -0,0 +1,41 @@
+import { G2Spec } from '../../../src';
+
+export function titanicPointPackSharedDataPadding(): G2Spec {
+ return {
+ type: 'facetRect',
+ data: {
+ type: 'fetch',
+ value: 'data/titanic.csv',
+ transform: [
+ {
+ type: 'sortBy',
+ fields: ['survived'],
+ },
+ {
+ type: 'map',
+ callback: ({ survived, ...d }) => ({
+ ...d,
+ survived: survived + '',
+ }),
+ },
+ ],
+ },
+ shareData: true,
+ encode: {
+ x: 'pclass',
+ },
+ children: [
+ {
+ type: 'point',
+ transform: [{ type: 'pack', padding: 2 }],
+ legend: {
+ color: { labelFormatter: (d) => (d === '1' ? 'Yes' : 'No') },
+ },
+ encode: {
+ color: 'survived',
+ shape: 'point',
+ },
+ },
+ ],
+ };
+}
diff --git a/__tests__/plots/static/titanic-point-pack-shared-row-data.ts b/__tests__/plots/static/titanic-point-pack-shared-row-data.ts
new file mode 100644
index 0000000000..bed12f4cc8
--- /dev/null
+++ b/__tests__/plots/static/titanic-point-pack-shared-row-data.ts
@@ -0,0 +1,41 @@
+import { G2Spec } from '../../../src';
+
+export function titanicPointPackSharedRowData(): G2Spec {
+ return {
+ type: 'facetRect',
+ data: {
+ type: 'fetch',
+ value: 'data/titanic.csv',
+ transform: [
+ {
+ type: 'sortBy',
+ fields: ['survived'],
+ },
+ {
+ type: 'map',
+ callback: ({ survived, ...d }) => ({
+ ...d,
+ survived: survived + '',
+ }),
+ },
+ ],
+ },
+ shareData: true,
+ encode: {
+ x: 'pclass',
+ },
+ children: [
+ {
+ type: 'point',
+ transform: [{ type: 'pack', direction: 'row' }],
+ legend: {
+ color: { labelFormatter: (d) => (d === '1' ? 'Yes' : 'No') },
+ },
+ encode: {
+ color: 'survived',
+ shape: 'point',
+ },
+ },
+ ],
+ };
+}
diff --git a/site/docs/spec/transform/pack.zh.md b/site/docs/spec/transform/pack.zh.md
index 51233a196a..d5fe07703d 100644
--- a/site/docs/spec/transform/pack.zh.md
+++ b/site/docs/spec/transform/pack.zh.md
@@ -52,3 +52,10 @@ facetRect
chart.render();
```
+
+## 选项
+
+| 属性 | 描述 | 类型 | 默认值 |
+|-------------------|------------------------------------------------|---------------------|-----------------------|
+| padding | 每个元素之间的间距,单位为px | `number` | `0` |
+| direction | 元素的堆叠方向 | `'row' \| 'col'` | `col` |
\ No newline at end of file
diff --git a/src/spec/transform.ts b/src/spec/transform.ts
index 5245ef6d62..6ea95f2911 100644
--- a/src/spec/transform.ts
+++ b/src/spec/transform.ts
@@ -223,6 +223,8 @@ export type FlexXTransform = {
export type PackTransform = {
type?: 'pack';
+ padding?: number;
+ direction?: 'row' | 'col';
};
export type Reducer =
diff --git a/src/transform/pack.ts b/src/transform/pack.ts
index 3e03c9f452..f9100fe348 100644
--- a/src/transform/pack.ts
+++ b/src/transform/pack.ts
@@ -1,58 +1,64 @@
import { deepMix } from '@antv/util';
import { TransformComponent as TC } from '../runtime';
import { calcBBox } from '../utils/vector';
+import { PackTransform } from '../spec';
-export type PackOptions = Record;
+export type PackOptions = Omit;
-function modifier(P, count, layout) {
- const pcount = P.length;
- if (pcount === 0) return [];
+function pack(options: PackOptions) {
+ const { padding = 0, direction = 'col' } = options;
+ return (P, count, layout) => {
+ const pcount = P.length;
+ if (pcount === 0) return [];
- // col * row >= count
- // row is close to col * aspect, so
- // col * (col * aspect) >= count
- const { innerWidth, innerHeight } = layout;
- const aspect = innerHeight / innerWidth;
- let col = Math.ceil(Math.sqrt(count / aspect));
+ // col * row >= count
+ // row is close to col * aspect, so
+ // col * (col * aspect) >= count
+ const { innerWidth, innerHeight } = layout;
+ const aspect = innerHeight / innerWidth;
+ let col = Math.ceil(Math.sqrt(count / aspect));
- // Increase col to avoid total height of packed shape
- // being large than height of bbox.
- let size = innerWidth / col;
- let row = Math.ceil(count / col);
- let h0 = row * size;
- while (h0 > innerHeight) {
- col = col + 1;
- size = innerWidth / col;
- row = Math.ceil(count / col);
- h0 = row * size;
- }
+ // Increase col to avoid total height of packed shape
+ // being large than height of bbox.
+ let size = innerWidth / col;
+ let row = Math.ceil(count / col);
+ let h0 = row * size;
+ while (h0 > innerHeight) {
+ col = col + 1;
+ size = innerWidth / col;
+ row = Math.ceil(count / col);
+ h0 = row * size;
+ }
- // Some offset to increase the space usage.
- const space = innerHeight - row * size;
- const intervalY = row <= 1 ? 0 : space / (row - 1);
- const [offsetX, offsetY] =
- row <= 1
- ? [(innerWidth - pcount * size) / (pcount - 1), (innerHeight - size) / 2]
- : [0, 0];
+ // Some offset to increase the space usage.
+ const space = innerHeight - row * size;
+ const intervalY = row <= 1 ? 0 : space / (row - 1);
+ const [offsetX, offsetY] =
+ row <= 1
+ ? [
+ (innerWidth - pcount * size) / (pcount - 1),
+ (innerHeight - size) / 2,
+ ]
+ : [0, 0];
- return P.map((points, m) => {
- const [x, y, width, height] = calcBBox(points);
+ return P.map((points, m) => {
+ const [x, y, width, height] = calcBBox(points);
+ const i = direction === 'col' ? m % col : Math.floor(m / row);
+ const j = direction === 'col' ? Math.floor(m / col) : m % row;
- const i = m % col;
- const j = Math.floor(m / col);
+ const newX = i * size;
+ const newY = (row - j - 1) * size + space;
- const newX = i * size;
- const newY = (row - j - 1) * size + space;
+ const sx = (size - padding) / width;
+ const sy = (size - padding) / height;
- const sx = size / width;
- const sy = size / height;
-
- // Translate the shape and mark to make sure the center of
- // shape is overlap before and after scale transformation.
- const tx = newX - x + offsetX * i;
- const ty = newY - y - intervalY * j - offsetY;
- return `translate(${tx}, ${ty}) scale(${sx}, ${sy})`;
- });
+ // Translate the shape and mark to make sure the center of
+ // shape is overlap before and after scale transformation.
+ const tx = newX - x + offsetX * i + (1 / 2) * padding;
+ const ty = newY - y - intervalY * j - offsetY + (1 / 2) * padding;
+ return `translate(${tx}, ${ty}) scale(${sx}, ${sy})`;
+ });
+ };
}
/**
@@ -60,9 +66,9 @@ function modifier(P, count, layout) {
* @todo Improve or change algorithm to increase space usage.
* @todo Take some special case into account.
*/
-export const Pack: TC = () => {
+export const Pack: TC = (options) => {
return (I, mark) => {
- return [I, deepMix({}, mark, { modifier, axis: false })];
+ return [I, deepMix({}, mark, { modifier: pack(options), axis: false })];
};
};