Skip to content

Commit e6f4c70

Browse files
committedAug 17, 2021
feat(stream): migrate stream package to TypeScript
1 parent 53811a5 commit e6f4c70

35 files changed

+860
-920
lines changed
 

‎packages/bar/src/ResponsiveBar.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ export const ResponsiveBar = <RawDatum extends BarDatum>(
66
props: Omit<BarSvgProps<RawDatum>, 'height' | 'width'>
77
) => (
88
<ResponsiveWrapper>
9-
{({ width, height }) => <Bar width={width} height={height} {...props} />}
9+
{({ width, height }) => <Bar<RawDatum> width={width} height={height} {...props} />}
1010
</ResponsiveWrapper>
1111
)

‎packages/stream/index.d.ts

-139
This file was deleted.

‎packages/stream/package.json

+6-4
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@
2121
],
2222
"main": "./dist/nivo-stream.cjs.js",
2323
"module": "./dist/nivo-stream.es.js",
24+
"typings": "./dist/types/index.d.ts",
2425
"files": [
2526
"README.md",
2627
"LICENSE.md",
27-
"index.d.ts",
28-
"dist/"
28+
"dist/",
29+
"!dist/tsconfig.tsbuildinfo"
2930
],
3031
"dependencies": {
3132
"@nivo/axes": "0.73.0",
@@ -38,11 +39,12 @@
3839
"d3-shape": "^1.3.5"
3940
},
4041
"devDependencies": {
41-
"@nivo/core": "0.73.0"
42+
"@nivo/core": "0.73.0",
43+
"@types/d3-scale": "^3.2.2",
44+
"@types/d3-shape": "^2.0.0"
4245
},
4346
"peerDependencies": {
4447
"@nivo/core": "0.73.0",
45-
"prop-types": ">= 15.5.10 < 16.0.0",
4648
"react": ">= 16.14.0 < 18.0.0"
4749
},
4850
"publishConfig": {

‎packages/stream/src/ResponsiveStream.js

-18
This file was deleted.
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ResponsiveWrapper } from '@nivo/core'
2+
import { StreamDatum, StreamSvgProps } from './types'
3+
import { Stream } from './Stream'
4+
5+
export const ResponsiveStream = <RawDatum extends StreamDatum>(
6+
props: Omit<StreamSvgProps<RawDatum>, 'height' | 'width'>
7+
) => (
8+
<ResponsiveWrapper>
9+
{({ width, height }) => <Stream<RawDatum> width={width} height={height} {...props} />}
10+
</ResponsiveWrapper>
11+
)

‎packages/stream/src/Stream.js

-184
This file was deleted.

‎packages/stream/src/Stream.tsx

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import { createElement, Fragment, ReactNode } from 'react'
2+
import {
3+
Container,
4+
SvgWrapper,
5+
useDimensions,
6+
// @ts-ignore
7+
bindDefs,
8+
} from '@nivo/core'
9+
import { Axes, Grid } from '@nivo/axes'
10+
import { BoxLegendSvg } from '@nivo/legends'
11+
import { StreamLayers } from './StreamLayers'
12+
import { StreamDots } from './StreamDots'
13+
import { StreamSlices } from './StreamSlices'
14+
import { useStream } from './hooks'
15+
import { svgDefaultProps } from './props'
16+
import { StreamDatum, StreamLayerId, StreamSvgProps } from './types'
17+
18+
type InnerStreamProps<RawDatum extends StreamDatum> = Omit<
19+
StreamSvgProps<RawDatum>,
20+
'animate' | 'motionConfig' | 'renderWrapper' | 'theme'
21+
>
22+
23+
const InnerStream = <RawDatum extends StreamDatum>({
24+
data,
25+
keys,
26+
label,
27+
valueFormat,
28+
29+
offsetType,
30+
order,
31+
curve,
32+
33+
layers: chartLayers = svgDefaultProps.layers,
34+
35+
width,
36+
height,
37+
margin: partialMargin,
38+
39+
axisTop,
40+
axisRight,
41+
axisBottom = svgDefaultProps.axisBottom,
42+
axisLeft = svgDefaultProps.axisLeft,
43+
enableGridX = svgDefaultProps.enableGridX,
44+
enableGridY = svgDefaultProps.enableGridY,
45+
46+
colors,
47+
fillOpacity = svgDefaultProps.fillOpacity,
48+
borderWidth = svgDefaultProps.borderWidth,
49+
borderColor,
50+
defs = svgDefaultProps.defs,
51+
fill = svgDefaultProps.fill,
52+
53+
enableDots = svgDefaultProps.enableDots,
54+
dotPosition = svgDefaultProps.dotPosition,
55+
dotComponent = svgDefaultProps.dotComponent,
56+
dotSize,
57+
dotColor,
58+
dotBorderWidth,
59+
dotBorderColor,
60+
61+
isInteractive = svgDefaultProps.isInteractive,
62+
enableStackTooltip = svgDefaultProps.enableStackTooltip,
63+
64+
legends = svgDefaultProps.legends,
65+
role,
66+
}: InnerStreamProps<RawDatum>) => {
67+
const { margin, innerWidth, innerHeight, outerWidth, outerHeight } = useDimensions(
68+
width,
69+
height,
70+
partialMargin
71+
)
72+
73+
const {
74+
xScale,
75+
yScale,
76+
layers,
77+
slices,
78+
getBorderColor,
79+
getDotSize,
80+
getDotColor,
81+
getDotBorderWidth,
82+
getDotBorderColor,
83+
} = useStream<RawDatum>({
84+
width: innerWidth,
85+
height: innerHeight,
86+
data,
87+
keys,
88+
label,
89+
valueFormat,
90+
offsetType,
91+
order,
92+
curve,
93+
colors,
94+
borderColor,
95+
dotSize,
96+
dotColor,
97+
dotBorderWidth,
98+
dotBorderColor,
99+
})
100+
101+
const boundDefs = bindDefs(defs, layers, fill)
102+
103+
const layerById: Record<StreamLayerId, ReactNode> = {
104+
grid: null,
105+
axes: null,
106+
layers: null,
107+
dots: null,
108+
slices: null,
109+
legends: null,
110+
}
111+
112+
if (chartLayers.includes('grid')) {
113+
layerById.grid = (
114+
<Grid
115+
key="grid"
116+
width={innerWidth}
117+
height={innerHeight}
118+
xScale={enableGridX ? (xScale as any) : null}
119+
yScale={enableGridY ? (yScale as any) : null}
120+
/>
121+
)
122+
}
123+
124+
if (chartLayers.includes('axes')) {
125+
layerById.axes = (
126+
<Axes
127+
key="axes"
128+
xScale={xScale as any}
129+
yScale={yScale as any}
130+
width={innerWidth}
131+
height={innerHeight}
132+
top={axisTop}
133+
right={axisRight}
134+
bottom={axisBottom}
135+
left={axisLeft}
136+
/>
137+
)
138+
}
139+
140+
if (chartLayers.includes('layers')) {
141+
layerById.layers = (
142+
<StreamLayers
143+
key="layers"
144+
layers={layers}
145+
fillOpacity={fillOpacity}
146+
borderWidth={borderWidth}
147+
getBorderColor={getBorderColor}
148+
isInteractive={isInteractive}
149+
/>
150+
)
151+
}
152+
153+
if (chartLayers.includes('dots') && enableDots) {
154+
layerById.dots = (
155+
<Fragment key="dots">
156+
{layers.map(layer => (
157+
<StreamDots
158+
key={layer.id}
159+
id={layer.id}
160+
color={layer.color}
161+
data={layer.data}
162+
dotComponent={dotComponent}
163+
position={dotPosition}
164+
getSize={getDotSize}
165+
getColor={getDotColor}
166+
getBorderWidth={getDotBorderWidth}
167+
getBorderColor={getDotBorderColor}
168+
/>
169+
))}
170+
</Fragment>
171+
)
172+
}
173+
174+
if (chartLayers.includes('slices') && isInteractive && enableStackTooltip) {
175+
layerById.slices = <StreamSlices key="slices" slices={slices} height={innerHeight} />
176+
}
177+
178+
if (chartLayers.includes('legends')) {
179+
layerById.legends = (
180+
<Fragment key="legends">
181+
{legends.map((legend, i) => {
182+
const legendData = layers
183+
.map(layer => ({
184+
id: layer.id,
185+
label: layer.label,
186+
color: layer.color,
187+
fill: layer.fill,
188+
}))
189+
.reverse()
190+
191+
return (
192+
<BoxLegendSvg
193+
key={i}
194+
{...legend}
195+
containerWidth={innerWidth}
196+
containerHeight={innerHeight}
197+
data={legendData}
198+
/>
199+
)
200+
})}
201+
</Fragment>
202+
)
203+
}
204+
205+
return (
206+
<SvgWrapper
207+
width={outerWidth}
208+
height={outerHeight}
209+
margin={margin}
210+
defs={boundDefs}
211+
role={role}
212+
>
213+
{chartLayers.map((layer, i) => {
214+
if (typeof layer === 'function') {
215+
return <Fragment key={i}>{createElement(layer, {})}</Fragment>
216+
}
217+
218+
return layerById?.[layer] ?? null
219+
})}
220+
</SvgWrapper>
221+
)
222+
}
223+
224+
export const Stream = <RawDatum extends StreamDatum>({
225+
isInteractive = svgDefaultProps.isInteractive,
226+
animate = svgDefaultProps.animate,
227+
motionConfig = svgDefaultProps.motionConfig,
228+
theme,
229+
renderWrapper,
230+
...otherProps
231+
}: StreamSvgProps<RawDatum>) => (
232+
<Container
233+
{...{
234+
animate,
235+
isInteractive,
236+
motionConfig,
237+
renderWrapper,
238+
theme,
239+
}}
240+
>
241+
<InnerStream<RawDatum> isInteractive={isInteractive} {...otherProps} />
242+
</Container>
243+
)

‎packages/stream/src/StreamDots.js

-68
This file was deleted.

‎packages/stream/src/StreamDots.tsx

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { createElement } from 'react'
2+
import { StreamSvgProps, StreamLayerDatum, StreamDatum } from './types'
3+
4+
interface StreamDotsProps<RawDatum extends StreamDatum> {
5+
id: string | number
6+
color: string
7+
data: StreamLayerDatum[]
8+
dotComponent: Exclude<StreamSvgProps<RawDatum>['dotComponent'], undefined>
9+
position: 'start' | 'center' | 'end'
10+
getSize: (datum: StreamLayerDatum) => number
11+
getColor: (datum: StreamLayerDatum) => string
12+
getBorderWidth: (datum: StreamLayerDatum) => number
13+
getBorderColor: (datum: StreamLayerDatum) => string
14+
}
15+
16+
const getDotY = <RawDatum extends StreamDatum>(
17+
datum: StreamLayerDatum,
18+
position: StreamDotsProps<RawDatum>['position']
19+
) => {
20+
let y = datum.y2
21+
if (position === 'center') {
22+
y = datum.y1 + (datum.y2 - datum.y1) / 2
23+
} else if (position === 'start') {
24+
y = datum.y1
25+
}
26+
27+
return y
28+
}
29+
30+
export const StreamDots = <RawDatum extends StreamDatum>({
31+
data,
32+
dotComponent,
33+
position,
34+
getSize,
35+
getColor,
36+
getBorderWidth,
37+
getBorderColor,
38+
}: StreamDotsProps<RawDatum>) => (
39+
<>
40+
{data.map((datum, i) => {
41+
return createElement(dotComponent, {
42+
key: i,
43+
datum,
44+
x: datum.x,
45+
y: getDotY<RawDatum>(datum, position),
46+
size: getSize(datum),
47+
color: getColor(datum),
48+
borderWidth: getBorderWidth(datum),
49+
borderColor: getBorderColor(datum),
50+
})
51+
})}
52+
</>
53+
)

‎packages/stream/src/StreamDotsItem.js

-46
This file was deleted.
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { useSpring, animated } from '@react-spring/web'
2+
import { useMotionConfig } from '@nivo/core'
3+
4+
export interface StreamDotsItemProps {
5+
x: number
6+
y: number
7+
size: number
8+
color: string
9+
borderWidth: number
10+
borderColor: string
11+
}
12+
13+
export const StreamDotsItem = ({
14+
x,
15+
y,
16+
size,
17+
color,
18+
borderWidth,
19+
borderColor,
20+
}: StreamDotsItemProps) => {
21+
const { animate, config: springConfig } = useMotionConfig()
22+
const animatedProps = useSpring({
23+
x,
24+
y,
25+
radius: size * 0.5,
26+
color,
27+
config: springConfig,
28+
immediate: !animate,
29+
})
30+
31+
return (
32+
<animated.circle
33+
cx={animatedProps.x}
34+
cy={animatedProps.y}
35+
r={animatedProps.radius}
36+
fill={animatedProps.color}
37+
strokeWidth={borderWidth}
38+
stroke={borderColor}
39+
/>
40+
)
41+
}
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
9-
import { memo, useCallback } from 'react'
10-
import PropTypes from 'prop-types'
1+
import { useCallback } from 'react'
112
import { useSpring, animated } from '@react-spring/web'
123
import { useAnimatedPath, useMotionConfig } from '@nivo/core'
4+
import { InheritedColorConfigCustomFunction } from '@nivo/colors'
135
import { BasicTooltip, useTooltip } from '@nivo/tooltip'
6+
import { StreamLayerData } from './types'
147

15-
const StreamLayer = ({
8+
interface StreamLayerProps {
9+
layer: StreamLayerData
10+
fillOpacity: number
11+
borderWidth: number
12+
getBorderColor: InheritedColorConfigCustomFunction<StreamLayerData>
13+
isInteractive: boolean
14+
}
15+
16+
export const StreamLayer = ({
1617
layer,
1718
fillOpacity,
1819
borderWidth,
1920
getBorderColor,
20-
getTooltipLabel,
2121
isInteractive,
22-
}) => {
22+
}: StreamLayerProps) => {
2323
const { showTooltipFromEvent, hideTooltip } = useTooltip()
2424
const handleMouseHover = useCallback(
2525
event => {
2626
showTooltipFromEvent(
27-
<BasicTooltip id={getTooltipLabel(layer)} enableChip={true} color={layer.color} />,
27+
<BasicTooltip id={layer.label} enableChip={true} color={layer.color} />,
2828
event,
2929
'left'
3030
)
3131
},
32-
[showTooltipFromEvent, getTooltipLabel, layer]
32+
[showTooltipFromEvent, layer]
3333
)
3434

3535
const { animate, config: springConfig } = useMotionConfig()
@@ -53,14 +53,3 @@ const StreamLayer = ({
5353
/>
5454
)
5555
}
56-
57-
StreamLayer.propTypes = {
58-
layer: PropTypes.object.isRequired,
59-
fillOpacity: PropTypes.number.isRequired,
60-
borderWidth: PropTypes.number.isRequired,
61-
getBorderColor: PropTypes.func.isRequired,
62-
getTooltipLabel: PropTypes.func.isRequired,
63-
isInteractive: PropTypes.bool.isRequired,
64-
}
65-
66-
export default memo(StreamLayer)

‎packages/stream/src/StreamLayers.js

-46
This file was deleted.

‎packages/stream/src/StreamLayers.tsx

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { InheritedColorConfigCustomFunction } from '@nivo/colors'
2+
import { StreamLayer } from './StreamLayer'
3+
import { StreamLayerData } from './types'
4+
5+
interface StreamLayersProps {
6+
layers: StreamLayerData[]
7+
fillOpacity: number
8+
borderWidth: number
9+
getBorderColor: InheritedColorConfigCustomFunction<StreamLayerData>
10+
isInteractive: boolean
11+
}
12+
13+
export const StreamLayers = ({
14+
layers,
15+
fillOpacity,
16+
borderWidth,
17+
getBorderColor,
18+
isInteractive,
19+
}: StreamLayersProps) => (
20+
<g>
21+
{layers.map((layer, i) => (
22+
<StreamLayer
23+
key={i}
24+
layer={layer}
25+
getBorderColor={getBorderColor}
26+
borderWidth={borderWidth}
27+
fillOpacity={fillOpacity}
28+
isInteractive={isInteractive}
29+
/>
30+
))}
31+
</g>
32+
)

‎packages/stream/src/StreamSlices.js

-46
This file was deleted.

‎packages/stream/src/StreamSlices.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { StreamSliceData } from './types'
2+
import { StreamSlicesItem } from './StreamSlicesItem'
3+
4+
interface StreamSlicesProps {
5+
slices: StreamSliceData[]
6+
height: number
7+
}
8+
9+
export const StreamSlices = ({ slices, height }: StreamSlicesProps) => (
10+
<g>
11+
{slices.map(slice => (
12+
<StreamSlicesItem key={slice.index} slice={slice} height={height} />
13+
))}
14+
</g>
15+
)

‎packages/stream/src/StreamSlicesItem.js ‎packages/stream/src/StreamSlicesItem.tsx

+12-24
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
9-
import { memo, useCallback, useMemo, useState } from 'react'
10-
import PropTypes from 'prop-types'
1+
import { useCallback, useMemo, useState } from 'react'
112
import { TableTooltip, Chip } from '@nivo/tooltip'
123
import { useTooltip } from '@nivo/tooltip'
4+
import { StreamSliceData } from './types'
135

14-
const StreamSlicesItem = ({ slice, height, getTooltipLabel, getTooltipValue }) => {
6+
interface StreamSlicesItemProps {
7+
slice: StreamSliceData
8+
height: number
9+
}
10+
11+
export const StreamSlicesItem = ({ slice, height }: StreamSlicesItemProps) => {
1512
const [isHover, setIsHover] = useState(false)
1613
const { showTooltipFromEvent, hideTooltip } = useTooltip()
1714

1815
const rows = useMemo(
1916
() =>
2017
slice.stack.map(p => [
21-
<Chip key={p.id} color={p.color} />,
22-
getTooltipLabel(p),
23-
getTooltipValue(p.value),
18+
<Chip key={p.layerId} color={p.color} />,
19+
p.layerLabel,
20+
p.formattedValue,
2421
]),
25-
[slice, getTooltipLabel, getTooltipValue]
22+
[slice]
2623
)
2724

2825
const handleMouseHover = useCallback(
@@ -64,12 +61,3 @@ const StreamSlicesItem = ({ slice, height, getTooltipLabel, getTooltipValue }) =
6461
</g>
6562
)
6663
}
67-
68-
StreamSlicesItem.propTypes = {
69-
slice: PropTypes.object.isRequired,
70-
height: PropTypes.number.isRequired,
71-
getTooltipLabel: PropTypes.func.isRequired,
72-
getTooltipValue: PropTypes.func.isRequired,
73-
}
74-
75-
export default memo(StreamSlicesItem)

‎packages/stream/src/hooks.js

-155
This file was deleted.

‎packages/stream/src/hooks.ts

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { useMemo } from 'react'
2+
import { area, stack as d3Stack } from 'd3-shape'
3+
import { scaleLinear, scalePoint } from 'd3-scale'
4+
import {
5+
useTheme,
6+
usePropertyAccessor,
7+
useValueFormatter,
8+
// @ts-ignore
9+
curveFromProp,
10+
// @ts-ignore
11+
stackOrderFromProp,
12+
// @ts-ignore
13+
stackOffsetFromProp,
14+
} from '@nivo/core'
15+
import { useInheritedColor, useOrdinalColorScale } from '@nivo/colors'
16+
import {
17+
StreamCommonProps,
18+
StreamDataProps,
19+
StreamDatum,
20+
StreamLayerData,
21+
StreamLayerDatum,
22+
StreamSliceData,
23+
} from './types'
24+
import { defaultProps } from './props'
25+
26+
export const useStream = <RawDatum extends StreamDatum>({
27+
width,
28+
height,
29+
data,
30+
keys,
31+
label = defaultProps.label,
32+
valueFormat,
33+
offsetType = defaultProps.offsetType,
34+
order = defaultProps.order,
35+
curve = defaultProps.curve,
36+
colors = defaultProps.colors,
37+
borderColor = defaultProps.borderColor,
38+
dotSize = defaultProps.dotSize,
39+
dotColor = defaultProps.dotColor,
40+
dotBorderWidth = defaultProps.dotBorderWidth,
41+
dotBorderColor = defaultProps.dotBorderColor,
42+
}: {
43+
width: number
44+
height: number
45+
data: StreamDataProps<RawDatum>['data']
46+
keys: StreamDataProps<RawDatum>['keys']
47+
label?: StreamCommonProps<RawDatum>['label']
48+
valueFormat?: StreamCommonProps<RawDatum>['valueFormat']
49+
offsetType?: StreamCommonProps<RawDatum>['offsetType']
50+
order?: StreamCommonProps<RawDatum>['order']
51+
curve?: StreamCommonProps<RawDatum>['curve']
52+
colors?: StreamCommonProps<RawDatum>['colors']
53+
dotSize?: StreamCommonProps<RawDatum>['dotSize']
54+
dotColor?: StreamCommonProps<RawDatum>['dotColor']
55+
dotBorderWidth?: StreamCommonProps<RawDatum>['dotBorderWidth']
56+
dotBorderColor?: StreamCommonProps<RawDatum>['dotBorderColor']
57+
borderColor?: StreamCommonProps<RawDatum>['borderColor']
58+
}) => {
59+
const areaGenerator = useMemo(
60+
() =>
61+
area<StreamLayerDatum>()
62+
.x(({ x }) => x)
63+
.y0(({ y1 }) => y1)
64+
.y1(({ y2 }) => y2)
65+
.curve(curveFromProp(curve)),
66+
[curve]
67+
)
68+
69+
const stack = useMemo(
70+
() =>
71+
d3Stack<RawDatum, string | number>()
72+
.keys(keys)
73+
.offset(stackOffsetFromProp(offsetType))
74+
.order(stackOrderFromProp(order)),
75+
[keys, offsetType, order]
76+
)
77+
78+
const [layers, xScale, yScale] = useMemo(() => {
79+
const allMin: number[] = []
80+
const allMax: number[] = []
81+
82+
const layers = stack(data).map(layer => {
83+
return layer.map(point => {
84+
allMin.push(point[0])
85+
allMax.push(point[1])
86+
87+
return {
88+
...point,
89+
value: point.data[layer.key] as number,
90+
}
91+
})
92+
})
93+
94+
const minValue = Math.min(...allMin)
95+
const maxValue = Math.max(...allMax)
96+
97+
return [
98+
layers,
99+
scalePoint<number>()
100+
.domain(Array.from({ length: data.length }, (_, i) => i))
101+
.range([0, width]),
102+
scaleLinear().domain([minValue, maxValue]).range([height, 0]),
103+
]
104+
}, [stack, data, width, height])
105+
106+
const theme = useTheme()
107+
const getColor = useOrdinalColorScale<Omit<StreamLayerData, 'label' | 'color' | 'data'>>(
108+
colors,
109+
'id'
110+
)
111+
const getBorderColor = useInheritedColor<StreamLayerData>(borderColor, theme)
112+
113+
const getDotSize = useMemo(() => (typeof dotSize === 'function' ? dotSize : () => dotSize), [
114+
dotSize,
115+
])
116+
const getDotColor = useInheritedColor(dotColor, theme)
117+
const getDotBorderWidth = useMemo(
118+
() => (typeof dotBorderWidth === 'function' ? dotBorderWidth : () => dotBorderWidth),
119+
[dotBorderWidth]
120+
)
121+
const getDotBorderColor = useInheritedColor(dotBorderColor, theme)
122+
123+
const getLabel = usePropertyAccessor<
124+
Omit<StreamLayerData, 'label' | 'color' | 'data'>,
125+
string | number
126+
>(label)
127+
const formatValue = useValueFormatter(valueFormat)
128+
129+
const enhancedLayers: StreamLayerData[] = useMemo(
130+
() =>
131+
layers.map((points, layerIndex) => {
132+
const computedPoints: StreamLayerDatum[] = points.map((point, i) => ({
133+
layerId: keys[layerIndex],
134+
layerLabel: '',
135+
index: i,
136+
color: '',
137+
x: xScale(i) as number,
138+
value: point.value,
139+
formattedValue: formatValue(point.value),
140+
y1: yScale(point[0]),
141+
y2: yScale(point[1]),
142+
}))
143+
144+
const layer: Omit<StreamLayerData, 'label' | 'color' | 'data'> = {
145+
id: keys[layerIndex] as string,
146+
path: areaGenerator(computedPoints) as string,
147+
}
148+
149+
const layerWithComputedProperties: Omit<StreamLayerData, 'data'> = {
150+
...layer,
151+
label: getLabel(layer),
152+
color: getColor(layer),
153+
}
154+
155+
return {
156+
...layerWithComputedProperties,
157+
data: computedPoints.map(point => {
158+
point.layerLabel = layerWithComputedProperties.label
159+
point.color = layerWithComputedProperties.color
160+
161+
return point
162+
}),
163+
}
164+
}),
165+
[layers, keys, getLabel, areaGenerator, getColor, xScale, yScale]
166+
)
167+
168+
const slices: StreamSliceData[] = useMemo(
169+
() =>
170+
Array.from({ length: data.length }, (_, i) => {
171+
const sliceStack = enhancedLayers
172+
.map(layer => layer.data[i])
173+
.sort((a, b) => a.y2 - b.y2)
174+
175+
return {
176+
index: i,
177+
x: enhancedLayers[0].data[i].x,
178+
stack: sliceStack,
179+
}
180+
}),
181+
[data.length, enhancedLayers]
182+
)
183+
184+
return {
185+
xScale,
186+
yScale,
187+
layers: enhancedLayers,
188+
slices,
189+
getBorderColor,
190+
getDotSize,
191+
getDotColor,
192+
getDotBorderWidth,
193+
getDotBorderColor,
194+
}
195+
}

‎packages/stream/src/index.js

-11
This file was deleted.

‎packages/stream/src/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './Stream'
2+
export * from './ResponsiveStream'
3+
export * from './props'

‎packages/stream/src/props.js

-99
This file was deleted.

‎packages/stream/src/props.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { StackOrder, StackOffset, AreaCurve } from '@nivo/core'
2+
import { StreamCommonProps, StreamLayerId } from './types'
3+
import { StreamDotsItem } from './StreamDotsItem'
4+
5+
export const defaultProps = {
6+
label: 'id',
7+
8+
order: 'none' as StackOrder,
9+
offsetType: 'wiggle' as StackOffset,
10+
curve: 'catmullRom' as AreaCurve,
11+
12+
axisBottom: {},
13+
axisLeft: {},
14+
enableGridX: false,
15+
enableGridY: true,
16+
17+
colors: { scheme: 'nivo' } as StreamCommonProps<any>['colors'],
18+
fillOpacity: 1,
19+
borderWidth: 0,
20+
borderColor: { from: 'color', modifiers: [['darker', 1]] } as StreamCommonProps<
21+
any
22+
>['borderColor'],
23+
24+
enableDots: false,
25+
dotPosition: 'center' as StreamCommonProps<any>['dotPosition'],
26+
dotComponent: StreamDotsItem,
27+
dotSize: 6,
28+
dotColor: { from: 'color' },
29+
dotBorderWidth: 0,
30+
dotBorderColor: { from: 'color' },
31+
32+
isInteractive: true,
33+
tooltipLabel: 'id',
34+
enableStackTooltip: true,
35+
36+
legends: [],
37+
legendLabel: 'id',
38+
39+
role: 'application',
40+
}
41+
42+
export const svgDefaultProps = {
43+
...defaultProps,
44+
layers: ['grid', 'axes', 'layers', 'dots', 'slices', 'legends'] as StreamLayerId[],
45+
46+
defs: [],
47+
fill: [],
48+
49+
animate: true,
50+
motionConfig: 'default',
51+
52+
role: 'img',
53+
isFocusable: false,
54+
}

‎packages/stream/src/types.ts

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import {
2+
Box,
3+
Dimensions,
4+
ModernMotionProps,
5+
Theme,
6+
PropertyAccessor,
7+
StackOrder,
8+
StackOffset,
9+
AreaCurve,
10+
SvgDefsAndFill,
11+
ValueFormat,
12+
} from '@nivo/core'
13+
import { AxisProps, GridValues } from '@nivo/axes'
14+
import { InheritedColorConfig, OrdinalColorScaleConfig } from '@nivo/colors'
15+
import { LegendProps } from '@nivo/legends'
16+
17+
export type StreamLayerId = 'grid' | 'axes' | 'layers' | 'dots' | 'slices' | 'legends'
18+
19+
export interface StreamDatum {
20+
[key: string]: string | number
21+
}
22+
23+
export type TooltipFormatter<T> = (value: T) => React.ReactNode
24+
25+
export type TooltipLabel<T> = (value: T) => string
26+
27+
export type StackFunc<RawDatum extends StreamDatum> = (
28+
data: RawDatum[]
29+
) => {
30+
0: number
31+
1: number
32+
data: RawDatum
33+
}[][]
34+
35+
export interface StreamLayerData {
36+
id: string | number
37+
label: string | number
38+
color: string
39+
// populated when using patterns/gradients
40+
fill?: string
41+
path: string
42+
data: StreamLayerDatum[]
43+
}
44+
45+
export interface StreamLayerDatum {
46+
layerId: StreamLayerData['id']
47+
layerLabel: StreamLayerData['label']
48+
index: number
49+
value: number
50+
formattedValue: number | string
51+
color: string
52+
x: number
53+
y1: number
54+
y2: number
55+
}
56+
57+
export type DotComponent = React.FC<{
58+
datum: StreamLayerDatum
59+
x: number
60+
y: number
61+
size: number
62+
color: string
63+
borderWidth: number
64+
borderColor: string
65+
}>
66+
67+
export interface StreamSliceData {
68+
index: number
69+
x: number
70+
stack: StreamLayerDatum[]
71+
}
72+
73+
export interface StreamDataProps<RawDatum extends StreamDatum> {
74+
data: RawDatum[]
75+
keys: Exclude<keyof RawDatum, symbol>[]
76+
}
77+
78+
export type StreamCommonProps<RawDatum extends StreamDatum> = {
79+
label: PropertyAccessor<Omit<StreamLayerData, 'label' | 'color' | 'data'>, string | number>
80+
valueFormat: ValueFormat<number>
81+
82+
stack: StackFunc<RawDatum>
83+
order: StackOrder
84+
offsetType: StackOffset
85+
curve: AreaCurve
86+
87+
layers: StreamLayerId[]
88+
89+
margin: Box
90+
91+
axisTop: AxisProps | null
92+
axisRight: AxisProps | null
93+
axisBottom: AxisProps | null
94+
axisLeft: AxisProps | null
95+
enableGridX: boolean
96+
gridXValues: GridValues<string | number>
97+
enableGridY: boolean
98+
gridYValues: GridValues<number>
99+
100+
isInteractive: boolean
101+
tooltipLabel: PropertyAccessor<StreamLayerData, string>
102+
enableStackTooltip: boolean
103+
104+
theme: Theme
105+
colors: OrdinalColorScaleConfig<Omit<StreamLayerData, 'label' | 'color' | 'data'>>
106+
fillOpacity: number
107+
borderWidth: number
108+
borderColor: InheritedColorConfig<StreamLayerData>
109+
110+
enableDots: boolean
111+
dotComponent: DotComponent
112+
dotPosition: 'start' | 'center' | 'end'
113+
dotSize: ((datum: StreamLayerDatum) => number) | number
114+
dotColor: InheritedColorConfig<StreamLayerDatum>
115+
dotBorderWidth: ((datum: StreamLayerDatum) => number) | number
116+
dotBorderColor: InheritedColorConfig<StreamLayerDatum>
117+
118+
legends: LegendProps[]
119+
120+
renderWrapper: boolean
121+
122+
role: string
123+
}
124+
125+
export type StreamSvgProps<RawDatum extends StreamDatum> = Partial<StreamCommonProps<RawDatum>> &
126+
StreamDataProps<RawDatum> &
127+
SvgDefsAndFill<StreamLayerData> &
128+
Dimensions &
129+
ModernMotionProps

‎packages/stream/stories/stream.stories.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const labelLookup = {
3333
nv: 'Nevada',
3434
}
3535

36-
stories.add('custom legend label', () => (
36+
stories.add('custom label', () => (
3737
<Stream
3838
{...commonProperties}
3939
data={range(16).map(() =>
@@ -44,8 +44,7 @@ stories.add('custom legend label', () => (
4444
)}
4545
keys={Object.keys(labelLookup)}
4646
margin={{ top: 50, right: 110, bottom: 50, left: 60 }}
47-
legendLabel={d => labelLookup[d.id]}
48-
tooltipLabel={d => labelLookup[d.id]}
47+
label={d => labelLookup[d.id]}
4948
legends={[
5049
{
5150
anchor: 'bottom-right',
@@ -88,10 +87,10 @@ stories.add('regular stacked chart', () => (
8887

8988
stories.add('custom curve', () => <Stream {...commonProperties} curve="step" />)
9089

91-
stories.add('formatting tooltip values', () => (
90+
stories.add('formatting values', () => (
9291
<Stream
9392
{...commonProperties}
94-
tooltipFormat={value =>
93+
valueFormat={value =>
9594
`${Number(value).toLocaleString('ru-RU', {
9695
minimumFractionDigits: 2,
9796
})} ₽`

‎packages/stream/tests/.eslintrc.yml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
env:
2+
jest: true

‎packages/stream/tests/Stream.test.tsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { mount } from 'enzyme'
2+
import { Stream } from '../src'
3+
4+
type TestDatum = {
5+
A: number
6+
B: number
7+
C: number
8+
extra?: string
9+
}
10+
11+
it('should render a basic stream chart', () => {
12+
const wrapper = mount(
13+
<Stream<TestDatum>
14+
width={500}
15+
height={300}
16+
data={[
17+
{ A: 10, B: 20, C: 30 },
18+
{ A: 20, B: 10, C: 30 },
19+
{ A: 30, B: 10, C: 20 },
20+
]}
21+
keys={['A', 'B', 'C']}
22+
animate={false}
23+
/>
24+
)
25+
26+
expect(wrapper.find('StreamLayer')).toHaveLength(3)
27+
})

‎packages/stream/tsconfig.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../../tsconfig.types.json",
3+
"compilerOptions": {
4+
"outDir": "./dist/types",
5+
"rootDir": "./src"
6+
},
7+
"include": ["src/**/*"]
8+
}

‎tsconfig.monorepo.json

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
{ "path": "./packages/marimekko" },
2727
{ "path": "./packages/pie" },
2828
{ "path": "./packages/sunburst" },
29+
{ "path": "./packages/stream" },
2930
{ "path": "./packages/swarmplot" }
3031
]
3132
}

‎website/src/components/controls/Help.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ export const Container = styled.div`
77
display: inline;
88
font-size: 0.8rem;
99
color: ${({ theme }) => theme.colors.textLight};
10-
10+
1111
p {
1212
display: inline;
1313
}
14-
14+
1515
a {
1616
color: ${({ theme }) => theme.colors.text};
1717
}

‎website/src/data/components/stream/defaults.js

+5-13
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
91
import { patternDotsDef, patternSquaresDef } from '@nivo/core'
10-
import { StreamDefaultProps } from '@nivo/stream'
2+
import { defaultProps, svgDefaultProps } from '@nivo/stream'
113

124
export default {
135
margin: {
@@ -88,15 +80,15 @@ export default {
8880
{ match: { id: 'Marcel' }, id: 'squares' },
8981
],
9082

91-
enableDots: StreamDefaultProps.enableDots,
83+
enableDots: defaultProps.enableDots,
9284
dotSize: 8,
9385
dotColor: { from: 'color' },
9486
dotBorderWidth: 2,
9587
dotBorderColor: { from: 'color', modifiers: [['darker', 0.7]] },
9688

97-
animate: StreamDefaultProps.animate,
98-
motionConfig: StreamDefaultProps.motionConfig,
89+
animate: svgDefaultProps.animate,
90+
motionConfig: svgDefaultProps.motionConfig,
9991

100-
isInteractive: StreamDefaultProps.isInteractive,
92+
isInteractive: defaultProps.isInteractive,
10193
enableStackTooltip: true,
10294
}

‎website/src/data/components/stream/generator.js

-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
91
import range from 'lodash/range'
102
import random from 'lodash/random'
113

‎website/src/data/components/stream/mapper.js

-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
91
import { settingsMapper, mapAxis } from '../../../lib/settings'
102

113
export default settingsMapper(

‎website/src/data/components/stream/props.js

+1-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
91
import { areaCurvePropKeys, stackOrderPropKeys, stackOffsetPropKeys } from '@nivo/core'
10-
import { StreamDefaultProps as defaults } from '@nivo/stream'
2+
import { defaultProps as defaults } from '@nivo/stream'
113
import {
124
themeProperty,
135
axesProperties,

‎website/src/pages/stream/index.js

-8
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
/*
2-
* This file is part of the nivo project.
3-
*
4-
* Copyright 2016-present, Raphaël Benitte.
5-
*
6-
* For the full copyright and license information, please view the LICENSE
7-
* file that was distributed with this source code.
8-
*/
91
import React from 'react'
102
import { ResponsiveStream, StreamDefaultProps } from '@nivo/stream'
113
import ComponentTemplate from '../../components/components/ComponentTemplate'

0 commit comments

Comments
 (0)
Please sign in to comment.