@antv/g2
Version:
the Grammar of Graphics in Javascript
161 lines (145 loc) • 3.96 kB
text/typescript
import { deepMix } from '@antv/util';
import { CompositeMarkComponent as CC, MarkOptions } from '../runtime';
import { ChordMark } from '../spec';
import { subObject } from '../utils/helper';
import { subTooltip, maybeAnimation } from '../utils/mark';
import { Arc } from '../data/arc';
import { ArcOptions } from '../data/utils/arc/types';
import { field, initializeData } from './utils';
const DEFAULT_LAYOUT_OPTIONS: ArcOptions = {
y: 0,
thickness: 0.05, // width of the node, (0, 1)
marginRatio: 0.1, // margin ratio, [0, 1)
id: (node) => node.key,
source: (edge) => edge.source,
target: (edge) => edge.target,
sourceWeight: (edge) => edge.value || 1,
targetWeight: (edge) => edge.value || 1,
sortBy: null, // optional, id | weight | frequency | {function}
};
const DEFAULT_NODE_OPTIONS = {
type: 'polygon',
axis: false,
legend: false,
encode: {
shape: 'polygon',
x: 'x',
y: 'y',
},
scale: {
x: { type: 'identity' },
y: { type: 'identity' },
},
style: {
opacity: 1,
fillOpacity: 1,
lineWidth: 1,
},
};
const DEFAULT_LINK_OPTIONS = {
type: 'polygon',
axis: false,
legend: false,
encode: {
shape: 'ribbon',
x: 'x',
y: 'y',
},
style: {
opacity: 0.5,
lineWidth: 1,
},
};
const DEFAULT_LABEL_OPTIONS = {
position: 'outside',
fontSize: 10,
};
export type ChordOptions = Omit<ChordMark, 'type'>;
export const Chord: CC<ChordOptions> = (options, context) => {
const {
data,
encode = {},
scale,
style = {},
layout = {},
nodeLabels = [],
linkLabels = [],
animate = {},
tooltip = {},
} = options;
// Initialize data, generating nodes by link if is not specified.
const { nodes, links } = initializeData(data, encode);
// Extract encode for node and link.
const nodeEncode = subObject(encode, 'node');
const linkEncode = subObject(encode, 'link');
const { key: nodeKey = (d) => d.key, color = nodeKey } = nodeEncode;
const { linkEncodeColor = (d) => d.source } = linkEncode;
const {
nodeWidthRatio = DEFAULT_LAYOUT_OPTIONS.thickness,
nodePaddingRatio = DEFAULT_LAYOUT_OPTIONS.marginRatio,
...restLayout
} = layout;
const { nodes: nodeData, edges: linkData } = Arc({
...DEFAULT_LAYOUT_OPTIONS,
id: field(nodeKey),
thickness: nodeWidthRatio,
marginRatio: nodePaddingRatio,
...restLayout,
weight: true,
})({ nodes, edges: links });
// Extract label style and apply defaults.
const { text = nodeKey, ...labelStyle } = subObject(style, 'label');
const nodeTooltip = subTooltip(
tooltip,
'node',
{
title: '',
items: [(d) => ({ name: d.key, value: d.value })],
},
true,
);
const linkTooltip = subTooltip(tooltip, 'link', {
title: '',
items: [(d) => ({ name: `${d.source} -> ${d.target}`, value: d.value })],
});
const { height, width } = context;
const minimumLen = Math.min(height, width);
return [
deepMix({}, DEFAULT_LINK_OPTIONS, {
data: linkData,
encode: { ...linkEncode, color: linkEncodeColor },
labels: linkLabels,
style: {
fill: linkEncodeColor ? undefined : '#aaa',
...subObject(style, 'link'),
},
tooltip: linkTooltip,
animate: maybeAnimation(animate, 'link'),
}),
deepMix({}, DEFAULT_NODE_OPTIONS, {
data: nodeData,
encode: { ...nodeEncode, color },
scale,
style: subObject(style, 'node'),
coordinate: {
type: 'polar',
// Leave enough rendering space for the label.
outerRadius: (minimumLen - 20) / minimumLen,
startAngle: -Math.PI * 2,
endAngle: 0,
},
labels: [
{
...DEFAULT_LABEL_OPTIONS,
text,
...labelStyle,
},
...nodeLabels,
],
tooltip: nodeTooltip,
animate: maybeAnimation(animate, 'node'),
axis: false,
}),
] as MarkOptions[];
};
Chord.props = {};