@antv/g2
Version:
the Grammar of Graphics in Javascript
203 lines (184 loc) • 4.49 kB
text/typescript
import { deepMix } from '@antv/util';
import {
treemap as treemapLayout,
treemapBinary,
treemapDice,
treemapSlice,
treemapSliceDice,
treemapSquarify,
treemapResquarify,
} from 'd3-hierarchy';
import { subObject } from '../utils/helper';
import { CompositionComponent as CC } from '../runtime';
import { TreemapMark } from '../spec';
import { maybeTooltip } from '../utils/mark';
import { generateHierarchyRoot, field } from './utils';
export type TreemapOptions = Omit<TreemapMark, 'type'>;
type Layout = {
tile?:
| 'treemapBinary'
| 'treemapDice'
| 'treemapSlice'
| 'treemapSliceDice'
| 'treemapSquarify'
| 'treemapResquarify';
size?: [number, number];
round?: boolean;
// Ignore the value of the parent node when calculating the total value.
ignoreParentValue?: boolean;
ratio?: number;
padding?: number;
paddingInner?: number;
paddingOuter?: number;
paddingTop?: number;
paddingRight?: number;
paddingBottom?: number;
paddingLeft?: number;
sort?(a: any, b: any): number;
path?: (d: any) => any;
/** The granularity of Display layer. */
layer?: number | ((d: any) => any);
};
type TreemapData = {
name: string;
children: TreemapData[];
[key: string]: any;
}[];
function getTileMethod(tile: string, ratio: number) {
const tiles = {
treemapBinary,
treemapDice,
treemapSlice,
treemapSliceDice,
treemapSquarify,
treemapResquarify,
};
const tileMethod =
tile === 'treemapSquarify' ? tiles[tile].ratio(ratio) : tiles[tile];
if (!tileMethod) {
throw new TypeError('Invalid tile method!');
}
return tileMethod;
}
function dataTransform(data, layout: Layout, encode): TreemapData {
const { value } = encode;
const tileMethod = getTileMethod(layout.tile, layout.ratio);
const root = generateHierarchyRoot(data, layout.path);
// Calculate the value and sort.
value
? root
.sum((d) =>
layout.ignoreParentValue && d.children ? 0 : field(value)(d),
)
.sort(layout.sort)
: root.count();
treemapLayout()
.tile(tileMethod)
// @ts-ignore
.size(layout.size)
.round(layout.round)
.paddingInner(layout.paddingInner)
.paddingOuter(layout.paddingOuter)
.paddingTop(layout.paddingTop)
.paddingRight(layout.paddingRight)
.paddingBottom(layout.paddingBottom)
.paddingLeft(layout.paddingLeft)(root);
return root
.descendants()
.map((d) =>
Object.assign(d, {
x: [d.x0, d.x1],
y: [d.y0, d.y1],
}),
)
.filter(
typeof layout.layer === 'function'
? layout.layer
: (d) => d.height === layout.layer,
);
}
// Defaults
const GET_DEFAULT_LAYOUT_OPTIONS = (width, height) => ({
tile: 'treemapSquarify',
ratio: 0.5 * (1 + Math.sqrt(5)),
size: [width, height],
round: false,
ignoreParentValue: true,
padding: 0,
paddingInner: 0,
paddingOuter: 0,
paddingTop: 0,
paddingRight: 0,
paddingBottom: 0,
paddingLeft: 0,
sort: (a, b) => b.value - a.value,
layer: 0,
});
const GET_DEFAULT_OPTIONS = (width, height) => ({
type: 'rect',
axis: false,
encode: {
x: 'x',
y: 'y',
color: (d) => d.data.parent.name,
},
scale: {
x: { domain: [0, width], range: [0, 1] },
y: { domain: [0, height], range: [0, 1] },
},
style: {
stroke: '#fff',
},
});
const DEFAULT_LABEL_OPTIONS = {
fontSize: 10,
text: (d) => d.data.name,
position: 'inside',
fill: '#000',
textOverflow: 'clip',
wordWrap: true,
maxLines: 1,
wordWrapWidth: (d) => d.x1 - d.x0,
};
const DEFAULT_TOOLTIP_OPTIONS = {
title: (d) => d.data.name,
items: [{ field: 'value' }],
};
export const Treemap: CC<TreemapOptions> = (options, context) => {
const { width, height } = context;
const {
data,
encode = {},
scale,
style = {},
layout = {},
labels = [],
tooltip = {},
...resOptions
} = options;
// Data
const transformedData = dataTransform(
data,
deepMix({}, GET_DEFAULT_LAYOUT_OPTIONS(width, height), layout),
encode,
);
// Label
const labelStyle = subObject(style, 'label');
return deepMix({}, GET_DEFAULT_OPTIONS(width, height), {
data: transformedData,
encode,
scale,
style,
labels: [
{
...DEFAULT_LABEL_OPTIONS,
...labelStyle,
},
...labels,
],
...resOptions,
tooltip: maybeTooltip(tooltip, DEFAULT_TOOLTIP_OPTIONS),
axis: false,
});
};
Treemap.props = {};