Skip to content

Commit e59add9

Browse files
committedSep 1, 2021
feat(sankey): use generics for nodes and links
1 parent 5b90727 commit e59add9

16 files changed

+267
-226
lines changed
 

‎packages/generators/src/sankey.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const generateSankeyData = ({
6262
} = {}) => {
6363
const nodes = availableNodes.slice(0, nodeCount).map(node =>
6464
Object.assign({}, node, {
65-
color: randColor(),
65+
nodeColor: randColor(),
6666
})
6767
)
6868

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { ResponsiveWrapper } from '@nivo/core'
2-
import { SankeyId, SankeySvgProps } from './types'
2+
import { DefaultLink, DefaultNode, SankeySvgProps } from './types'
33
import { Sankey } from './Sankey'
44

5-
export const ResponsiveSankey = <Id extends SankeyId = string>(
6-
props: Omit<SankeySvgProps<Id>, 'height' | 'width'>
5+
export const ResponsiveSankey = <
6+
N extends DefaultNode = DefaultNode,
7+
L extends DefaultLink = DefaultLink
8+
>(
9+
props: Omit<SankeySvgProps<N, L>, 'height' | 'width'>
710
) => (
811
<ResponsiveWrapper>
9-
{({ width, height }) => <Sankey<Id> width={width} height={height} {...props} />}
12+
{({ width, height }) => <Sankey<N, L> width={width} height={height} {...props} />}
1013
</ResponsiveWrapper>
1114
)

‎packages/sankey/src/Sankey.tsx

+23-16
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,21 @@ import { useSankey } from './hooks'
77
import { SankeyNodes } from './SankeyNodes'
88
import { SankeyLinks } from './SankeyLinks'
99
import { SankeyLabels } from './SankeyLabels'
10-
import { SankeyId, SankeyLayerId, SankeyLinkDatum, SankeyNodeDatum, SankeySvgProps } from './types'
10+
import {
11+
DefaultLink,
12+
DefaultNode,
13+
SankeyLayerId,
14+
SankeyLinkDatum,
15+
SankeyNodeDatum,
16+
SankeySvgProps,
17+
} from './types'
1118

12-
type InnerSankeyProps<Id extends SankeyId> = Omit<
13-
SankeySvgProps<Id>,
19+
type InnerSankeyProps<N extends DefaultNode, L extends DefaultLink> = Omit<
20+
SankeySvgProps<N, L>,
1421
'animate' | 'motionConfig' | 'renderWrapper' | 'theme'
1522
>
1623

17-
const InnerSankey = <Id extends SankeyId>({
24+
const InnerSankey = <N extends DefaultNode, L extends DefaultLink>({
1825
data,
1926
valueFormat,
2027
layout = svgDefaultProps.layout,
@@ -55,7 +62,7 @@ const InnerSankey = <Id extends SankeyId>({
5562
ariaLabel,
5663
ariaLabelledBy,
5764
ariaDescribedBy,
58-
}: InnerSankeyProps<Id>) => {
65+
}: InnerSankeyProps<N, L>) => {
5966
const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions(
6067
width,
6168
height,
@@ -72,7 +79,7 @@ const InnerSankey = <Id extends SankeyId>({
7279
currentLink,
7380
setCurrentLink,
7481
getLabelTextColor,
75-
} = useSankey<Id>({
82+
} = useSankey<N, L>({
7683
data,
7784
valueFormat,
7885
layout,
@@ -89,13 +96,13 @@ const InnerSankey = <Id extends SankeyId>({
8996
labelTextColor,
9097
})
9198

92-
let isCurrentNode: (node: SankeyNodeDatum<Id>) => boolean = () => false
93-
let isCurrentLink: (link: SankeyLinkDatum<Id>) => boolean = () => false
99+
let isCurrentNode: (node: SankeyNodeDatum<N, L>) => boolean = () => false
100+
let isCurrentLink: (link: SankeyLinkDatum<N, L>) => boolean = () => false
94101

95102
if (currentLink) {
96-
isCurrentNode = ({ id }: SankeyNodeDatum<Id>) =>
103+
isCurrentNode = ({ id }: SankeyNodeDatum<N, L>) =>
97104
id === currentLink.source.id || id === currentLink.target.id
98-
isCurrentLink = ({ source, target }: SankeyLinkDatum<Id>) =>
105+
isCurrentLink = ({ source, target }: SankeyLinkDatum<N, L>) =>
99106
source.id === currentLink.source.id && target.id === currentLink.target.id
100107
}
101108

@@ -135,7 +142,7 @@ const InnerSankey = <Id extends SankeyId>({
135142

136143
if (layers.includes('links')) {
137144
layerById.links = (
138-
<SankeyLinks<Id>
145+
<SankeyLinks<N, L>
139146
key="links"
140147
links={links}
141148
layout={layout}
@@ -158,7 +165,7 @@ const InnerSankey = <Id extends SankeyId>({
158165

159166
if (layers.includes('nodes')) {
160167
layerById.nodes = (
161-
<SankeyNodes<Id>
168+
<SankeyNodes<N, L>
162169
key="nodes"
163170
nodes={nodes}
164171
nodeOpacity={nodeOpacity}
@@ -180,7 +187,7 @@ const InnerSankey = <Id extends SankeyId>({
180187

181188
if (layers.includes('labels') && enableLabels) {
182189
layerById.labels = (
183-
<SankeyLabels<Id>
190+
<SankeyLabels<N, L>
184191
key="labels"
185192
nodes={nodes}
186193
layout={layout}
@@ -231,14 +238,14 @@ const InnerSankey = <Id extends SankeyId>({
231238
)
232239
}
233240

234-
export const Sankey = <Id extends SankeyId = string>({
241+
export const Sankey = <N extends DefaultNode = DefaultNode, L extends DefaultLink = DefaultLink>({
235242
isInteractive = svgDefaultProps.isInteractive,
236243
animate = svgDefaultProps.animate,
237244
motionConfig = svgDefaultProps.motionConfig,
238245
theme,
239246
renderWrapper,
240247
...otherProps
241-
}: SankeySvgProps<Id>) => (
248+
}: SankeySvgProps<N, L>) => (
242249
<Container
243250
{...{
244251
animate,
@@ -248,6 +255,6 @@ export const Sankey = <Id extends SankeyId = string>({
248255
theme,
249256
}}
250257
>
251-
<InnerSankey<Id> isInteractive={isInteractive} {...otherProps} />
258+
<InnerSankey<N, L> isInteractive={isInteractive} {...otherProps} />
252259
</Container>
253260
)

‎packages/sankey/src/SankeyLabels.tsx

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import { useSprings, animated } from '@react-spring/web'
22
import { useTheme, useMotionConfig } from '@nivo/core'
3-
import { SankeyCommonProps, SankeyId, SankeyNodeDatum } from './types'
3+
import { DefaultLink, DefaultNode, SankeyCommonProps, SankeyNodeDatum } from './types'
44

5-
interface SankeyLabelsProps<Id extends SankeyId> {
6-
nodes: SankeyNodeDatum<Id>[]
7-
layout: SankeyCommonProps<Id>['layout']
5+
interface SankeyLabelsProps<N extends DefaultNode, L extends DefaultLink> {
6+
nodes: SankeyNodeDatum<N, L>[]
7+
layout: SankeyCommonProps<N, L>['layout']
88
width: number
99
height: number
10-
labelPosition: SankeyCommonProps<Id>['labelPosition']
11-
labelPadding: SankeyCommonProps<Id>['labelPadding']
12-
labelOrientation: SankeyCommonProps<Id>['labelOrientation']
13-
getLabelTextColor: (node: SankeyNodeDatum<Id>) => string
10+
labelPosition: SankeyCommonProps<N, L>['labelPosition']
11+
labelPadding: SankeyCommonProps<N, L>['labelPadding']
12+
labelOrientation: SankeyCommonProps<N, L>['labelOrientation']
13+
getLabelTextColor: (node: SankeyNodeDatum<N, L>) => string
1414
}
1515

16-
export const SankeyLabels = <Id extends SankeyId>({
16+
export const SankeyLabels = <N extends DefaultNode, L extends DefaultLink>({
1717
nodes,
1818
layout,
1919
width,
@@ -22,7 +22,7 @@ export const SankeyLabels = <Id extends SankeyId>({
2222
labelPadding,
2323
labelOrientation,
2424
getLabelTextColor,
25-
}: SankeyLabelsProps<Id>) => {
25+
}: SankeyLabelsProps<N, L>) => {
2626
const theme = useTheme()
2727

2828
const labelRotation = labelOrientation === 'vertical' ? -90 : 0

‎packages/sankey/src/SankeyLinkGradient.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { SankeyCommonProps } from './types'
22

33
interface SankeyLinkGradientProps {
44
id: string
5-
layout: SankeyCommonProps<any>['layout']
5+
layout: SankeyCommonProps<any, any>['layout']
66
startColor: string
77
endColor: string
88
}

‎packages/sankey/src/SankeyLinkTooltip.tsx

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BasicTooltip, Chip } from '@nivo/tooltip'
2-
import { SankeyId, SankeyLinkDatum } from './types'
2+
import { DefaultLink, DefaultNode, SankeyLinkDatum } from './types'
33

44
const tooltipStyles = {
55
container: {
@@ -15,11 +15,13 @@ const tooltipStyles = {
1515
},
1616
}
1717

18-
export interface SankeyLinkTooltipProps<Id extends SankeyId> {
19-
link: SankeyLinkDatum<Id>
18+
export interface SankeyLinkTooltipProps<N extends DefaultNode, L extends DefaultLink> {
19+
link: SankeyLinkDatum<N, L>
2020
}
2121

22-
export const SankeyLinkTooltip = <Id extends SankeyId>({ link }: SankeyLinkTooltipProps<Id>) => (
22+
export const SankeyLinkTooltip = <N extends DefaultNode, L extends DefaultLink>({
23+
link,
24+
}: SankeyLinkTooltipProps<N, L>) => (
2325
<BasicTooltip
2426
id={
2527
<span style={tooltipStyles.container}>

‎packages/sankey/src/SankeyLinks.tsx

+27-21
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
11
import { sankeyLinkHorizontal, sankeyLinkVertical } from './links'
2-
import { SankeyCommonProps, SankeyId, SankeyLinkDatum, SankeyNodeDatum } from './types'
2+
import {
3+
DefaultLink,
4+
DefaultNode,
5+
SankeyCommonProps,
6+
SankeyLinkDatum,
7+
SankeyNodeDatum,
8+
} from './types'
39
import { SankeyLinksItem } from './SankeyLinksItem'
410
import { useMemo } from 'react'
511

6-
interface SankeyLinksProps<Id extends SankeyId> {
7-
layout: SankeyCommonProps<Id>['layout']
8-
links: SankeyLinkDatum<Id>[]
9-
linkOpacity: SankeyCommonProps<Id>['linkOpacity']
10-
linkHoverOpacity: SankeyCommonProps<Id>['linkHoverOpacity']
11-
linkHoverOthersOpacity: SankeyCommonProps<Id>['linkHoverOthersOpacity']
12-
linkContract: SankeyCommonProps<Id>['linkContract']
13-
linkBlendMode: SankeyCommonProps<Id>['linkBlendMode']
14-
enableLinkGradient: SankeyCommonProps<Id>['enableLinkGradient']
15-
tooltip: SankeyCommonProps<Id>['linkTooltip']
16-
setCurrentLink: (link: SankeyLinkDatum<Id> | null) => void
17-
currentLink: SankeyLinkDatum<Id> | null
18-
currentNode: SankeyNodeDatum<Id> | null
19-
isCurrentLink: (link: SankeyLinkDatum<Id>) => boolean
20-
isInteractive: SankeyCommonProps<Id>['isInteractive']
21-
onClick?: SankeyCommonProps<Id>['onClick']
12+
interface SankeyLinksProps<N extends DefaultNode, L extends DefaultLink> {
13+
layout: SankeyCommonProps<N, L>['layout']
14+
links: SankeyLinkDatum<N, L>[]
15+
linkOpacity: SankeyCommonProps<N, L>['linkOpacity']
16+
linkHoverOpacity: SankeyCommonProps<N, L>['linkHoverOpacity']
17+
linkHoverOthersOpacity: SankeyCommonProps<N, L>['linkHoverOthersOpacity']
18+
linkContract: SankeyCommonProps<N, L>['linkContract']
19+
linkBlendMode: SankeyCommonProps<N, L>['linkBlendMode']
20+
enableLinkGradient: SankeyCommonProps<N, L>['enableLinkGradient']
21+
tooltip: SankeyCommonProps<N, L>['linkTooltip']
22+
setCurrentLink: (link: SankeyLinkDatum<N, L> | null) => void
23+
currentLink: SankeyLinkDatum<N, L> | null
24+
currentNode: SankeyNodeDatum<N, L> | null
25+
isCurrentLink: (link: SankeyLinkDatum<N, L>) => boolean
26+
isInteractive: SankeyCommonProps<N, L>['isInteractive']
27+
onClick?: SankeyCommonProps<N, L>['onClick']
2228
}
2329

24-
export const SankeyLinks = <Id extends SankeyId>({
30+
export const SankeyLinks = <N extends DefaultNode, L extends DefaultLink>({
2531
links,
2632
layout,
2733
linkOpacity,
@@ -37,8 +43,8 @@ export const SankeyLinks = <Id extends SankeyId>({
3743
isInteractive,
3844
onClick,
3945
tooltip,
40-
}: SankeyLinksProps<Id>) => {
41-
const getOpacity = (link: SankeyLinkDatum<Id>) => {
46+
}: SankeyLinksProps<N, L>) => {
47+
const getOpacity = (link: SankeyLinkDatum<N, L>) => {
4248
if (!currentNode && !currentLink) return linkOpacity
4349
if (isCurrentLink(link)) return linkHoverOpacity
4450
return linkHoverOthersOpacity
@@ -52,7 +58,7 @@ export const SankeyLinks = <Id extends SankeyId>({
5258
return (
5359
<>
5460
{links.map(link => (
55-
<SankeyLinksItem<Id>
61+
<SankeyLinksItem<N, L>
5662
key={`${link.source.id}.${link.target.id}`}
5763
link={link}
5864
layout={layout}

‎packages/sankey/src/SankeyLinksItem.tsx

+12-12
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,23 @@ import { useSpring, animated } from '@react-spring/web'
33
import { useAnimatedPath, useMotionConfig } from '@nivo/core'
44
import { useTooltip } from '@nivo/tooltip'
55
import { SankeyLinkGradient } from './SankeyLinkGradient'
6-
import { SankeyCommonProps, SankeyId, SankeyLinkDatum } from './types'
6+
import { DefaultLink, DefaultNode, SankeyCommonProps, SankeyLinkDatum } from './types'
77

8-
interface SankeyLinksItemProps<Id extends SankeyId> {
9-
link: SankeyLinkDatum<Id>
10-
layout: SankeyCommonProps<Id>['layout']
8+
interface SankeyLinksItemProps<N extends DefaultNode, L extends DefaultLink> {
9+
link: SankeyLinkDatum<N, L>
10+
layout: SankeyCommonProps<N, L>['layout']
1111
path: string
1212
color: string
1313
opacity: number
14-
blendMode: SankeyCommonProps<Id>['linkBlendMode']
15-
enableGradient: SankeyCommonProps<Id>['enableLinkGradient']
16-
setCurrent: (link: SankeyLinkDatum<Id> | null) => void
17-
isInteractive: SankeyCommonProps<Id>['isInteractive']
18-
onClick?: SankeyCommonProps<Id>['onClick']
19-
tooltip: SankeyCommonProps<Id>['linkTooltip']
14+
blendMode: SankeyCommonProps<N, L>['linkBlendMode']
15+
enableGradient: SankeyCommonProps<N, L>['enableLinkGradient']
16+
setCurrent: (link: SankeyLinkDatum<N, L> | null) => void
17+
isInteractive: SankeyCommonProps<N, L>['isInteractive']
18+
onClick?: SankeyCommonProps<N, L>['onClick']
19+
tooltip: SankeyCommonProps<N, L>['linkTooltip']
2020
}
2121

22-
export const SankeyLinksItem = <Id extends SankeyId>({
22+
export const SankeyLinksItem = <N extends DefaultNode, L extends DefaultLink>({
2323
link,
2424
layout,
2525
path,
@@ -31,7 +31,7 @@ export const SankeyLinksItem = <Id extends SankeyId>({
3131
tooltip,
3232
isInteractive,
3333
onClick,
34-
}: SankeyLinksItemProps<Id>) => {
34+
}: SankeyLinksItemProps<N, L>) => {
3535
const linkId = `${link.source.id}.${link.target.id}`
3636

3737
const { animate, config: springConfig } = useMotionConfig()
+6-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { BasicTooltip } from '@nivo/tooltip'
2-
import { SankeyId, SankeyNodeDatum } from './types'
2+
import { DefaultLink, DefaultNode, SankeyNodeDatum } from './types'
33

4-
export interface SankeyNodeTooltipProps<Id extends SankeyId> {
5-
node: SankeyNodeDatum<Id>
4+
export interface SankeyNodeTooltipProps<N extends DefaultNode, L extends DefaultLink> {
5+
node: SankeyNodeDatum<N, L>
66
}
77

8-
export const SankeyNodeTooltip = <Id extends SankeyId>({ node }: SankeyNodeTooltipProps<Id>) => (
8+
export const SankeyNodeTooltip = <N extends DefaultNode, L extends DefaultLink>({
9+
node,
10+
}: SankeyNodeTooltipProps<N, L>) => (
911
<BasicTooltip id={node.label} enableChip={true} color={node.color} />
1012
)

‎packages/sankey/src/SankeyNodes.tsx

+26-20
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
1-
import { SankeyCommonProps, SankeyId, SankeyLinkDatum, SankeyNodeDatum } from './types'
1+
import {
2+
DefaultLink,
3+
DefaultNode,
4+
SankeyCommonProps,
5+
SankeyLinkDatum,
6+
SankeyNodeDatum,
7+
} from './types'
28
import { SankeyNodesItem } from './SankeyNodesItem'
39

4-
interface SankeyNodesProps<Id extends SankeyId> {
5-
nodes: SankeyNodeDatum<Id>[]
6-
nodeOpacity: SankeyCommonProps<Id>['nodeOpacity']
7-
nodeHoverOpacity: SankeyCommonProps<Id>['nodeHoverOpacity']
8-
nodeHoverOthersOpacity: SankeyCommonProps<Id>['nodeHoverOthersOpacity']
9-
borderWidth: SankeyCommonProps<Id>['nodeBorderWidth']
10-
getBorderColor: (node: SankeyNodeDatum<Id>) => string
11-
borderRadius: SankeyCommonProps<Id>['nodeBorderRadius']
12-
setCurrentNode: (node: SankeyNodeDatum<Id> | null) => void
13-
currentNode: SankeyNodeDatum<Id> | null
14-
currentLink: SankeyLinkDatum<Id> | null
15-
isCurrentNode: (node: SankeyNodeDatum<Id>) => boolean
16-
isInteractive: SankeyCommonProps<Id>['isInteractive']
17-
onClick?: SankeyCommonProps<Id>['onClick']
18-
tooltip: SankeyCommonProps<Id>['nodeTooltip']
10+
interface SankeyNodesProps<N extends DefaultNode, L extends DefaultLink> {
11+
nodes: SankeyNodeDatum<N, L>[]
12+
nodeOpacity: SankeyCommonProps<N, L>['nodeOpacity']
13+
nodeHoverOpacity: SankeyCommonProps<N, L>['nodeHoverOpacity']
14+
nodeHoverOthersOpacity: SankeyCommonProps<N, L>['nodeHoverOthersOpacity']
15+
borderWidth: SankeyCommonProps<N, L>['nodeBorderWidth']
16+
getBorderColor: (node: SankeyNodeDatum<N, L>) => string
17+
borderRadius: SankeyCommonProps<N, L>['nodeBorderRadius']
18+
setCurrentNode: (node: SankeyNodeDatum<N, L> | null) => void
19+
currentNode: SankeyNodeDatum<N, L> | null
20+
currentLink: SankeyLinkDatum<N, L> | null
21+
isCurrentNode: (node: SankeyNodeDatum<N, L>) => boolean
22+
isInteractive: SankeyCommonProps<N, L>['isInteractive']
23+
onClick?: SankeyCommonProps<N, L>['onClick']
24+
tooltip: SankeyCommonProps<N, L>['nodeTooltip']
1925
}
2026

21-
export const SankeyNodes = <Id extends SankeyId>({
27+
export const SankeyNodes = <N extends DefaultNode, L extends DefaultLink>({
2228
nodes,
2329
nodeOpacity,
2430
nodeHoverOpacity,
@@ -33,8 +39,8 @@ export const SankeyNodes = <Id extends SankeyId>({
3339
isInteractive,
3440
onClick,
3541
tooltip,
36-
}: SankeyNodesProps<Id>) => {
37-
const getOpacity = (node: SankeyNodeDatum<Id>) => {
42+
}: SankeyNodesProps<N, L>) => {
43+
const getOpacity = (node: SankeyNodeDatum<N, L>) => {
3844
if (!currentNode && !currentLink) return nodeOpacity
3945
if (isCurrentNode(node)) return nodeHoverOpacity
4046
return nodeHoverOthersOpacity
@@ -43,7 +49,7 @@ export const SankeyNodes = <Id extends SankeyId>({
4349
return (
4450
<>
4551
{nodes.map(node => (
46-
<SankeyNodesItem<Id>
52+
<SankeyNodesItem<N, L>
4753
key={node.id}
4854
node={node}
4955
x={node.x}

‎packages/sankey/src/SankeyNodesItem.tsx

+11-11
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,26 @@ import { createElement, useCallback } from 'react'
22
import { useSpring, animated } from '@react-spring/web'
33
import { useMotionConfig } from '@nivo/core'
44
import { useTooltip } from '@nivo/tooltip'
5-
import { SankeyCommonProps, SankeyId, SankeyNodeDatum } from './types'
5+
import { DefaultLink, DefaultNode, SankeyCommonProps, SankeyNodeDatum } from './types'
66

7-
interface SankeyNodesItemProps<Id extends SankeyId> {
8-
node: SankeyNodeDatum<Id>
7+
interface SankeyNodesItemProps<N extends DefaultNode, L extends DefaultLink> {
8+
node: SankeyNodeDatum<N, L>
99
x: number
1010
y: number
1111
width: number
1212
height: number
1313
color: string
1414
opacity: number
15-
borderWidth: SankeyCommonProps<Id>['nodeBorderWidth']
15+
borderWidth: SankeyCommonProps<N, L>['nodeBorderWidth']
1616
borderColor: string
17-
borderRadius: SankeyCommonProps<Id>['nodeBorderRadius']
18-
setCurrent: (node: SankeyNodeDatum<Id> | null) => void
19-
isInteractive: SankeyCommonProps<Id>['isInteractive']
20-
onClick?: SankeyCommonProps<Id>['onClick']
21-
tooltip: SankeyCommonProps<Id>['nodeTooltip']
17+
borderRadius: SankeyCommonProps<N, L>['nodeBorderRadius']
18+
setCurrent: (node: SankeyNodeDatum<N, L> | null) => void
19+
isInteractive: SankeyCommonProps<N, L>['isInteractive']
20+
onClick?: SankeyCommonProps<N, L>['onClick']
21+
tooltip: SankeyCommonProps<N, L>['nodeTooltip']
2222
}
2323

24-
export const SankeyNodesItem = <Id extends SankeyId>({
24+
export const SankeyNodesItem = <N extends DefaultNode, L extends DefaultLink>({
2525
node,
2626
x,
2727
y,
@@ -36,7 +36,7 @@ export const SankeyNodesItem = <Id extends SankeyId>({
3636
isInteractive,
3737
onClick,
3838
tooltip,
39-
}: SankeyNodesItemProps<Id>) => {
39+
}: SankeyNodesItemProps<N, L>) => {
4040
const { animate, config: springConfig } = useMotionConfig()
4141
const animatedProps = useSpring({
4242
x,

‎packages/sankey/src/hooks.ts

+33-33
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@ import { useTheme, usePropertyAccessor, useValueFormatter } from '@nivo/core'
55
import { useOrdinalColorScale, useInheritedColor } from '@nivo/colors'
66
import { sankeyAlignmentFromProp } from './props'
77
import {
8+
DefaultLink,
9+
DefaultNode,
810
SankeyAlignFunction,
911
SankeyCommonProps,
1012
SankeyDataProps,
11-
SankeyId,
1213
SankeyLinkDatum,
1314
SankeyNodeDatum,
1415
SankeySortFunction,
1516
} from './types'
1617

17-
const getId = (d: { id: string | number }) => d.id
18+
const getId = (d: { id: string }) => d.id
1819

19-
export const computeNodeAndLinks = <Id extends SankeyId>({
20+
export const computeNodeAndLinks = <N extends DefaultNode, L extends DefaultLink>({
2021
data: _data,
2122
formatValue,
2223
layout,
@@ -31,19 +32,19 @@ export const computeNodeAndLinks = <Id extends SankeyId>({
3132
getColor,
3233
getLabel,
3334
}: {
34-
data: SankeyDataProps<Id>['data']
35-
formatValue: (value: number) => string | number
36-
layout: SankeyCommonProps<Id>['layout']
35+
data: SankeyDataProps<N, L>['data']
36+
formatValue: (value: number) => string
37+
layout: SankeyCommonProps<N, L>['layout']
3738
alignFunction: SankeyAlignFunction
3839
sortFunction: SankeySortFunction
3940
linkSortMode: any
40-
nodeThickness: SankeyCommonProps<Id>['nodeThickness']
41-
nodeSpacing: SankeyCommonProps<Id>['nodeSpacing']
42-
nodeInnerPadding: SankeyCommonProps<Id>['nodeInnerPadding']
41+
nodeThickness: SankeyCommonProps<N, L>['nodeThickness']
42+
nodeSpacing: SankeyCommonProps<N, L>['nodeSpacing']
43+
nodeInnerPadding: SankeyCommonProps<N, L>['nodeInnerPadding']
4344
width: number
4445
height: number
45-
getColor: (node: Omit<SankeyNodeDatum<Id>, 'color' | 'label'>) => string
46-
getLabel: (node: Omit<SankeyNodeDatum<Id>, 'color' | 'label'>) => string | number
46+
getColor: (node: Omit<SankeyNodeDatum<N, L>, 'color' | 'label'>) => string
47+
getLabel: (node: Omit<SankeyNodeDatum<N, L>, 'color' | 'label'>) => string
4748
}) => {
4849
const sankey = d3Sankey()
4950
.nodeAlign(alignFunction)
@@ -58,8 +59,8 @@ export const computeNodeAndLinks = <Id extends SankeyId>({
5859
// deep clone is required as the sankey diagram mutates data
5960
// we need a different identity for correct updates
6061
const data = (cloneDeep(_data) as unknown) as {
61-
nodes: SankeyNodeDatum<Id>[]
62-
links: SankeyLinkDatum<Id>[]
62+
nodes: SankeyNodeDatum<N, L>[]
63+
links: SankeyLinkDatum<N, L>[]
6364
}
6465
sankey(data)
6566

@@ -109,7 +110,7 @@ export const computeNodeAndLinks = <Id extends SankeyId>({
109110
return data
110111
}
111112

112-
export const useSankey = <Id extends SankeyId>({
113+
export const useSankey = <N extends DefaultNode, L extends DefaultLink>({
113114
data,
114115
valueFormat,
115116
layout,
@@ -125,23 +126,23 @@ export const useSankey = <Id extends SankeyId>({
125126
label,
126127
labelTextColor,
127128
}: {
128-
data: SankeyDataProps<Id>['data']
129-
valueFormat?: SankeyCommonProps<Id>['valueFormat']
130-
layout: SankeyCommonProps<Id>['layout']
129+
data: SankeyDataProps<N, L>['data']
130+
valueFormat?: SankeyCommonProps<N, L>['valueFormat']
131+
layout: SankeyCommonProps<N, L>['layout']
131132
width: number
132133
height: number
133-
sort: SankeyCommonProps<Id>['sort']
134-
align: SankeyCommonProps<Id>['align']
135-
colors: SankeyCommonProps<Id>['colors']
136-
nodeThickness: SankeyCommonProps<Id>['nodeThickness']
137-
nodeSpacing: SankeyCommonProps<Id>['nodeSpacing']
138-
nodeInnerPadding: SankeyCommonProps<Id>['nodeInnerPadding']
139-
nodeBorderColor: SankeyCommonProps<Id>['nodeBorderColor']
140-
label: SankeyCommonProps<Id>['label']
141-
labelTextColor: SankeyCommonProps<Id>['labelTextColor']
134+
sort: SankeyCommonProps<N, L>['sort']
135+
align: SankeyCommonProps<N, L>['align']
136+
colors: SankeyCommonProps<N, L>['colors']
137+
nodeThickness: SankeyCommonProps<N, L>['nodeThickness']
138+
nodeSpacing: SankeyCommonProps<N, L>['nodeSpacing']
139+
nodeInnerPadding: SankeyCommonProps<N, L>['nodeInnerPadding']
140+
nodeBorderColor: SankeyCommonProps<N, L>['nodeBorderColor']
141+
label: SankeyCommonProps<N, L>['label']
142+
labelTextColor: SankeyCommonProps<N, L>['labelTextColor']
142143
}) => {
143-
const [currentNode, setCurrentNode] = useState<SankeyNodeDatum<Id> | null>(null)
144-
const [currentLink, setCurrentLink] = useState<SankeyLinkDatum<Id> | null>(null)
144+
const [currentNode, setCurrentNode] = useState<SankeyNodeDatum<N, L> | null>(null)
145+
const [currentLink, setCurrentLink] = useState<SankeyLinkDatum<N, L> | null>(null)
145146

146147
const sortFunction = useMemo(() => {
147148
if (sort === 'auto') return undefined
@@ -163,16 +164,15 @@ export const useSankey = <Id extends SankeyId>({
163164
const getColor = useOrdinalColorScale(colors, 'id')
164165
const getNodeBorderColor = useInheritedColor(nodeBorderColor, theme)
165166

166-
const getLabel = usePropertyAccessor<
167-
Omit<SankeyNodeDatum<Id>, 'color' | 'label'>,
168-
string | number
169-
>(label)
167+
const getLabel = usePropertyAccessor<Omit<SankeyNodeDatum<N, L>, 'color' | 'label'>, string>(
168+
label
169+
)
170170
const getLabelTextColor = useInheritedColor(labelTextColor, theme)
171171
const formatValue = useValueFormatter<number>(valueFormat)
172172

173173
const { nodes, links } = useMemo(
174174
() =>
175-
computeNodeAndLinks<Id>({
175+
computeNodeAndLinks<N, L>({
176176
data,
177177
formatValue,
178178
layout,

‎packages/sankey/src/links.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { line, curveMonotoneX, curveMonotoneY } from 'd3-shape'
2-
import { SankeyId, SankeyLinkDatum } from './types'
2+
import { DefaultLink, DefaultNode, SankeyLinkDatum } from './types'
33

4-
export const sankeyLinkHorizontal = <Id extends SankeyId>() => {
4+
export const sankeyLinkHorizontal = <N extends DefaultNode, L extends DefaultLink>() => {
55
const lineGenerator = line().curve(curveMonotoneX)
66

7-
return (link: SankeyLinkDatum<Id>, contract: number) => {
7+
return (link: SankeyLinkDatum<N, L>, contract: number) => {
88
const thickness = Math.max(1, link.thickness - contract * 2)
99
const halfThickness = thickness / 2
1010
const linkLength = link.target.x0 - link.source.x1
@@ -26,10 +26,10 @@ export const sankeyLinkHorizontal = <Id extends SankeyId>() => {
2626
}
2727
}
2828

29-
export const sankeyLinkVertical = <Id extends SankeyId>() => {
29+
export const sankeyLinkVertical = <N extends DefaultNode, L extends DefaultLink>() => {
3030
const lineGenerator = line().curve(curveMonotoneY)
3131

32-
return (link: SankeyLinkDatum<Id>, contract: number) => {
32+
return (link: SankeyLinkDatum<N, L>, contract: number) => {
3333
const thickness = Math.max(1, link.thickness - contract * 2)
3434
const halfThickness = thickness / 2
3535
const linkLength = link.target.y0 - link.source.y1

‎packages/sankey/src/props.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { sankeyCenter, sankeyJustify, sankeyLeft, sankeyRight } from 'd3-sankey'
2-
import { SankeyLayerId, SankeyNodeDatum } from './types'
2+
import { SankeyLayerId, SankeyNodeDatum, SankeyAlignType } from './types'
33
import { InheritedColorConfig } from '@nivo/colors'
44
import { SankeyNodeTooltip } from './SankeyNodeTooltip'
55
import { SankeyLinkTooltip } from './SankeyLinkTooltip'
@@ -11,11 +11,11 @@ export const sankeyAlignmentPropMapping = {
1111
end: sankeyRight,
1212
}
1313

14-
export const sankeyAlignmentPropKeys = Object.keys(sankeyAlignmentPropMapping)
14+
export const sankeyAlignmentPropKeys = Object.keys(sankeyAlignmentPropMapping) as SankeyAlignType[]
1515

16-
export const sankeyAlignmentFromProp = prop => sankeyAlignmentPropMapping[prop]
16+
export const sankeyAlignmentFromProp = (prop: SankeyAlignType) => sankeyAlignmentPropMapping[prop]
1717

18-
export const SankeyDefaultProps = {
18+
export const svgDefaultProps = {
1919
layout: 'horizontal' as const,
2020
align: 'center' as const,
2121
sort: 'auto' as const,
@@ -30,7 +30,7 @@ export const SankeyDefaultProps = {
3030
nodeInnerPadding: 0,
3131
nodeBorderWidth: 1,
3232
nodeBorderColor: { from: 'color', modifiers: [['darker', 0.5]] } as InheritedColorConfig<
33-
SankeyNodeDatum<any>
33+
SankeyNodeDatum<any, any>
3434
>,
3535
nodeBorderRadius: 0,
3636

@@ -47,7 +47,7 @@ export const SankeyDefaultProps = {
4747
labelPadding: 9,
4848
labelOrientation: 'horizontal' as const,
4949
labelTextColor: { from: 'color', modifiers: [['darker', 0.8]] } as InheritedColorConfig<
50-
SankeyNodeDatum<any>
50+
SankeyNodeDatum<any, any>
5151
>,
5252

5353
isInteractive: true,
@@ -63,5 +63,3 @@ export const SankeyDefaultProps = {
6363
animate: true,
6464
motionConfig: 'gentle',
6565
}
66-
67-
export const svgDefaultProps = SankeyDefaultProps

‎packages/sankey/src/types.ts

+58-40
Original file line numberDiff line numberDiff line change
@@ -12,68 +12,84 @@ import {
1212
import { InheritedColorConfig, OrdinalColorScaleConfig } from '@nivo/colors'
1313
import { LegendProps } from '@nivo/legends'
1414

15-
export type SankeyId = string | number
15+
export interface SankeyRawNode {
16+
id: string
17+
}
1618

17-
export interface SankeyRawNode<Id extends SankeyId> {
18-
id: Id
19+
export interface DefaultNode {
20+
id: string
1921
}
2022

21-
export interface SankeyRawLink<Id extends SankeyId> {
22-
source: Id
23-
target: Id
23+
export interface DefaultLink {
24+
source: string
25+
target: string
2426
value: number
2527
// start/end color can optionally be used to have custom gradients
2628
startColor?: string
2729
endColor?: string
2830
}
2931

30-
export interface SankeyLinkDatum<Id extends SankeyId> {
31-
color: string
32+
export interface SankeyRawLink {
33+
source: string
34+
target: string
35+
value: number
36+
// start/end color can optionally be used to have custom gradients
37+
startColor?: string
38+
endColor?: string
39+
}
40+
41+
type ExtraLinkProps<L extends DefaultLink> = Omit<L, 'source' | 'target' | 'value'>
42+
43+
export type SankeyLinkDatum<N extends DefaultNode, L extends DefaultLink> = ExtraLinkProps<L> & {
44+
// base properties generated by d3-sankey
45+
value: number
3246
index: number
47+
source: SankeyNodeDatum<N, L>
48+
target: SankeyNodeDatum<N, L>
49+
// custom nivo properties
3350
pos0: number
3451
pos1: number
35-
source: SankeyNodeDatum<Id>
36-
target: SankeyNodeDatum<Id>
3752
thickness: number
38-
value: number
39-
formattedValue: string | number
53+
color: string
54+
formattedValue: string
4055
// start/end color can optionally be used to have custom gradients
4156
startColor?: string
4257
endColor?: string
4358
}
4459

45-
export interface SankeyNodeDatum<Id extends SankeyId> {
46-
color: string
60+
export type SankeyNodeDatum<N extends DefaultNode, L extends DefaultLink> = N & {
61+
// base properties generated by d3-sankey
4762
depth: number
48-
height: number
49-
id: Id
5063
index: number
51-
label: string | number
52-
layer: number
53-
sourceLinks: SankeyLinkDatum<Id>[]
54-
targetLinks: SankeyLinkDatum<Id>[]
55-
value: number
56-
formattedValue: string | number
57-
width: number
58-
x: number
5964
x0: number
6065
x1: number
61-
y: number
6266
y0: number
6367
y1: number
68+
value: number
69+
// custom nivo properties
70+
color: string
71+
label: string
72+
formattedValue: string
73+
layer: number
74+
sourceLinks: SankeyLinkDatum<N, L>[]
75+
targetLinks: SankeyLinkDatum<N, L>[]
76+
x: number
77+
y: number
78+
width: number
79+
height: number
6480
}
6581

66-
export interface SankeyDataProps<Id extends SankeyId> {
82+
export interface SankeyDataProps<N extends DefaultNode, L extends DefaultLink> {
6783
data: {
68-
nodes: SankeyRawNode<Id>[]
69-
keys: SankeyRawLink<Id>[]
84+
nodes: N[]
85+
links: L[]
7086
}
7187
}
7288

7389
export type SankeyLayerId = 'links' | 'nodes' | 'labels' | 'legends'
7490

75-
export type SankeyMouseHandler<Id extends SankeyId> = (
76-
data: SankeyNodeDatum<Id> | SankeyLinkDatum<Id>,
91+
export type SankeyMouseHandler = <N extends DefaultNode, L extends DefaultLink>(
92+
data: SankeyNodeDatum<N, L> | SankeyLinkDatum<N, L>,
7793
event: MouseEvent
7894
) => void
7995

@@ -86,7 +102,7 @@ export type SankeySortFunction = (
86102
b: SankeyNodeMinimal<any, any>
87103
) => number
88104

89-
export interface SankeyCommonProps<Id extends SankeyId> {
105+
export interface SankeyCommonProps<N extends DefaultNode, L extends DefaultLink> {
90106
// formatting for link value
91107
valueFormat: ValueFormat<number>
92108

@@ -98,7 +114,7 @@ export interface SankeyCommonProps<Id extends SankeyId> {
98114

99115
margin: Box
100116

101-
colors: OrdinalColorScaleConfig
117+
colors: OrdinalColorScaleConfig<Omit<SankeyNodeDatum<N, L>, 'color' | 'label'>>
102118
theme: Theme
103119

104120
nodeOpacity: number
@@ -108,7 +124,7 @@ export interface SankeyCommonProps<Id extends SankeyId> {
108124
nodeSpacing: number
109125
nodeInnerPadding: number
110126
nodeBorderWidth: number
111-
nodeBorderColor: InheritedColorConfig<SankeyNodeDatum<Id>>
127+
nodeBorderColor: InheritedColorConfig<SankeyNodeDatum<N, L>>
112128
nodeBorderRadius: number
113129

114130
linkOpacity: number
@@ -119,16 +135,16 @@ export interface SankeyCommonProps<Id extends SankeyId> {
119135
enableLinkGradient: boolean
120136

121137
enableLabels: boolean
122-
label: PropertyAccessor<Omit<SankeyNodeDatum<Id>, 'color' | 'label'>, string | number>
138+
label: PropertyAccessor<Omit<SankeyNodeDatum<N, L>, 'color' | 'label'>, string>
123139
labelPosition: 'inside' | 'outside'
124140
labelPadding: number
125141
labelOrientation: 'horizontal' | 'vertical'
126-
labelTextColor: InheritedColorConfig<SankeyNodeDatum<Id>>
142+
labelTextColor: InheritedColorConfig<SankeyNodeDatum<N, L>>
127143

128144
isInteractive: boolean
129-
onClick: SankeyMouseHandler<Id>
130-
nodeTooltip: FunctionComponent<{ node: SankeyNodeDatum<Id> }>
131-
linkTooltip: FunctionComponent<{ link: SankeyLinkDatum<Id> }>
145+
onClick: SankeyMouseHandler
146+
nodeTooltip: FunctionComponent<{ node: SankeyNodeDatum<N, L> }>
147+
linkTooltip: FunctionComponent<{ link: SankeyLinkDatum<N, L> }>
132148

133149
legends: LegendProps[]
134150

@@ -140,7 +156,9 @@ export interface SankeyCommonProps<Id extends SankeyId> {
140156
ariaDescribedBy: AriaAttributes['aria-describedby']
141157
}
142158

143-
export type SankeySvgProps<Id extends SankeyId> = Partial<SankeyCommonProps<Id>> &
144-
SankeyDataProps<Id> &
159+
export type SankeySvgProps<N extends DefaultNode, L extends DefaultLink> = Partial<
160+
SankeyCommonProps<N, L>
161+
> &
162+
SankeyDataProps<N, L> &
145163
Dimensions &
146164
ModernMotionProps

‎packages/sankey/stories/sankey.stories.tsx

+35-36
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { storiesOf } from '@storybook/react'
22
import { generateSankeyData, randColor } from '@nivo/generators'
3+
import { SankeyNodeMinimal } from 'd3-sankey'
34
// @ts-ignore
45
import { Sankey } from '../src'
56

@@ -37,10 +38,10 @@ stories.add('label formatter', () => (
3738
stories.add('custom tooltip', () => (
3839
<Sankey
3940
{...commonProperties}
40-
nodeTooltip={node => <span>Custom tooltip for node: {node.label}</span>}
41-
linkTooltip={node => (
41+
nodeTooltip={({ node }) => <span>Custom tooltip for node: {node.label}</span>}
42+
linkTooltip={({ link }) => (
4243
<span>
43-
Custom tooltip for link: {node.source.label} to {node.target.label}
44+
Custom tooltip for link: {link.source.label} to {link.target.label}
4445
</span>
4546
)}
4647
/>
@@ -49,43 +50,41 @@ stories.add('custom tooltip', () => (
4950
stories.add('with formatted values', () => (
5051
<Sankey
5152
{...commonProperties}
52-
tooltipFormat={value =>
53+
valueFormat={value =>
5354
`${Number(value).toLocaleString('ru-RU', {
5455
minimumFractionDigits: 2,
5556
})} ₽`
5657
}
5758
/>
5859
))
5960

60-
const dataWithRandLinkColors = data => {
61-
return {
62-
nodes: data.nodes.map(node => ({
63-
...node,
64-
nodeColor: 'blue',
65-
})),
66-
links: data.links.map(link => ({
67-
...link,
68-
startColor: randColor(),
69-
endColor: randColor(),
70-
})),
71-
}
72-
}
73-
74-
const randColorProperties = {
75-
width: commonProperties.width,
76-
height: commonProperties.height,
77-
margin: commonProperties.margin,
78-
data: dataWithRandLinkColors(sankeyData),
79-
colors: commonProperties.colors,
80-
}
61+
const dataWithRandLinkColors = (data: typeof sankeyData) => ({
62+
nodes: data.nodes.map(node => ({
63+
...node,
64+
nodeColor: 'blue',
65+
})),
66+
links: data.links.map(link => ({
67+
...link,
68+
startColor: randColor(),
69+
endColor: randColor(),
70+
})),
71+
})
8172

8273
stories.add('with custom node & link coloring', () => (
83-
<Sankey {...randColorProperties} enableLinkGradient={true} colorBy={node => node.nodeColor} />
74+
<Sankey
75+
{...commonProperties}
76+
data={dataWithRandLinkColors(sankeyData)}
77+
enableLinkGradient={true}
78+
colors={node => node.nodeColor}
79+
/>
8480
))
8581

86-
const minNodeValueOnTop = (nodeA, nodeB) => {
87-
if (nodeA.value < nodeB.value) return -1
88-
if (nodeA.value > nodeB.value) return 1
82+
const minNodeValueOnTop = (
83+
nodeA: SankeyNodeMinimal<any, any>,
84+
nodeB: SankeyNodeMinimal<any, any>
85+
) => {
86+
if (nodeA.value! < nodeB.value!) return -1
87+
if (nodeA.value! > nodeB.value!) return 1
8988
return 0
9089
}
9190

@@ -98,12 +97,12 @@ stories.add('sort links by input', () => (
9897
{...commonProperties}
9998
data={{
10099
nodes: [
101-
{ id: 'foo_left', color: '#ff0000' },
102-
{ id: 'bar_left', color: '#0000ff' },
103-
{ id: 'baz_left', color: '#00ff00' },
104-
{ id: 'foo_right', color: '#ff0000' },
105-
{ id: 'bar_right', color: '#0000ff' },
106-
{ id: 'baz_right', color: '#00ff00' },
100+
{ id: 'foo_left', nodeColor: '#ff0000' },
101+
{ id: 'bar_left', nodeColor: '#0000ff' },
102+
{ id: 'baz_left', nodeColor: '#00ff00' },
103+
{ id: 'foo_right', nodeColor: '#ff0000' },
104+
{ id: 'bar_right', nodeColor: '#0000ff' },
105+
{ id: 'baz_right', nodeColor: '#00ff00' },
107106
],
108107
links: [
109108
{ source: 'foo_left', target: 'bar_right', value: 5 },
@@ -114,7 +113,7 @@ stories.add('sort links by input', () => (
114113
{ source: 'baz_left', target: 'bar_right', value: 5 },
115114
],
116115
}}
117-
colors={d => d.color ?? ''}
116+
colors={node => node.nodeColor}
118117
sort="input"
119118
enableLinkGradient
120119
/>

0 commit comments

Comments
 (0)
Please sign in to comment.