Skip to content

Commit 75481c6

Browse files
committedAug 14, 2021
feat(bar): add a generic hook to handle both SVG and canvas implementations
1 parent 8a32e50 commit 75481c6

File tree

8 files changed

+355
-254
lines changed

8 files changed

+355
-254
lines changed
 

‎packages/bar/src/Bar.tsx

+64-108
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
BarLayerId,
77
BarSvgProps,
88
ComputedBarDatumWithValue,
9-
LegendData,
109
} from './types'
1110
import { BarLegends } from './BarLegends'
1211
import {
@@ -18,15 +17,12 @@ import {
1817
bindDefs,
1918
useDimensions,
2019
useMotionConfig,
21-
usePropertyAccessor,
2220
useTheme,
23-
useValueFormatter,
2421
} from '@nivo/core'
25-
import { Fragment, ReactNode, createElement, useCallback, useMemo, useState } from 'react'
26-
import { generateGroupedBars, generateStackedBars, getLegendData } from './compute'
22+
import { Fragment, ReactNode, createElement, useMemo } from 'react'
2723
import { svgDefaultProps } from './props'
28-
import { useInheritedColor, useOrdinalColorScale } from '@nivo/colors'
2924
import { useTransition } from '@react-spring/web'
25+
import { useBar } from './hooks'
3026

3127
type InnerBarProps<RawDatum extends BarDatum> = Omit<
3228
BarSvgProps<RawDatum>,
@@ -35,24 +31,24 @@ type InnerBarProps<RawDatum extends BarDatum> = Omit<
3531

3632
const InnerBar = <RawDatum extends BarDatum>({
3733
data,
38-
indexBy = svgDefaultProps.indexBy,
39-
keys = svgDefaultProps.keys,
34+
indexBy,
35+
keys,
4036

4137
margin: partialMargin,
4238
width,
4339
height,
4440

45-
groupMode = svgDefaultProps.groupMode,
46-
layout = svgDefaultProps.layout,
47-
reverse = svgDefaultProps.reverse,
48-
minValue = svgDefaultProps.minValue,
49-
maxValue = svgDefaultProps.maxValue,
41+
groupMode,
42+
layout,
43+
reverse,
44+
minValue,
45+
maxValue,
5046

51-
valueScale = svgDefaultProps.valueScale,
52-
indexScale = svgDefaultProps.indexScale,
47+
valueScale,
48+
indexScale,
5349

54-
padding = svgDefaultProps.padding,
55-
innerPadding = svgDefaultProps.innerPadding,
50+
padding,
51+
innerPadding,
5652

5753
axisTop,
5854
axisRight,
@@ -66,26 +62,26 @@ const InnerBar = <RawDatum extends BarDatum>({
6662
layers = svgDefaultProps.layers as BarLayer<RawDatum>[],
6763
barComponent = svgDefaultProps.barComponent,
6864

69-
enableLabel = svgDefaultProps.enableLabel,
70-
label = svgDefaultProps.label,
71-
labelSkipWidth = svgDefaultProps.labelSkipWidth,
72-
labelSkipHeight = svgDefaultProps.labelSkipHeight,
73-
labelTextColor = svgDefaultProps.labelTextColor,
65+
enableLabel,
66+
label,
67+
labelSkipWidth,
68+
labelSkipHeight,
69+
labelTextColor,
7470

7571
markers,
7672

77-
colorBy = svgDefaultProps.colorBy,
78-
colors = svgDefaultProps.colors,
73+
colorBy,
74+
colors,
7975
defs = svgDefaultProps.defs,
8076
fill = svgDefaultProps.fill,
8177
borderRadius = svgDefaultProps.borderRadius,
8278
borderWidth = svgDefaultProps.borderWidth,
83-
borderColor = svgDefaultProps.borderColor,
79+
borderColor,
8480

8581
annotations = svgDefaultProps.annotations,
8682

8783
legendLabel,
88-
tooltipLabel = svgDefaultProps.tooltipLabel,
84+
tooltipLabel,
8985

9086
valueFormat,
9187

@@ -95,7 +91,7 @@ const InnerBar = <RawDatum extends BarDatum>({
9591
onMouseEnter,
9692
onMouseLeave,
9793

98-
legends = svgDefaultProps.legends,
94+
legends,
9995

10096
role = svgDefaultProps.role,
10197
ariaLabel,
@@ -108,13 +104,6 @@ const InnerBar = <RawDatum extends BarDatum>({
108104

109105
initialHiddenIds,
110106
}: InnerBarProps<RawDatum>) => {
111-
const [hiddenIds, setHiddenIds] = useState(initialHiddenIds ?? [])
112-
const toggleSerie = useCallback(id => {
113-
setHiddenIds(state =>
114-
state.indexOf(id) > -1 ? state.filter(item => item !== id) : [...state, id]
115-
)
116-
}, [])
117-
118107
const theme = useTheme()
119108
const { animate, config: springConfig } = useMotionConfig()
120109
const { outerWidth, outerHeight, margin, innerWidth, innerHeight } = useDimensions(
@@ -123,59 +112,49 @@ const InnerBar = <RawDatum extends BarDatum>({
123112
partialMargin
124113
)
125114

126-
const formatValue = useValueFormatter(valueFormat)
127-
const getBorderColor = useInheritedColor<ComputedBarDatumWithValue<RawDatum>>(
115+
const {
116+
bars,
117+
barsWithValue,
118+
xScale,
119+
yScale,
120+
getLabel,
121+
getTooltipLabel,
122+
getBorderColor,
123+
getLabelColor,
124+
shouldRenderBarLabel,
125+
toggleSerie,
126+
legendsWithData,
127+
} = useBar<RawDatum>({
128+
indexBy,
129+
label,
130+
tooltipLabel,
131+
valueFormat,
132+
colors,
133+
colorBy,
128134
borderColor,
129-
theme
130-
)
131-
const getColor = useOrdinalColorScale(colors, colorBy)
132-
const getIndex = usePropertyAccessor(indexBy)
133-
const getLabel = usePropertyAccessor(label)
134-
const getLabelColor = useInheritedColor<ComputedBarDatumWithValue<RawDatum>>(
135135
labelTextColor,
136-
theme
137-
)
138-
const getTooltipLabel = usePropertyAccessor(tooltipLabel)
139-
140-
const generateBars = groupMode === 'grouped' ? generateGroupedBars : generateStackedBars
141-
const result = generateBars({
136+
groupMode,
142137
layout,
143138
reverse,
144139
data,
145-
getIndex,
146140
keys,
147141
minValue,
148142
maxValue,
143+
margin,
149144
width: innerWidth,
150145
height: innerHeight,
151-
getColor,
152146
padding,
153147
innerPadding,
154148
valueScale,
155149
indexScale,
156-
hiddenIds,
157-
formatValue,
158-
getTooltipLabel,
150+
enableLabel,
151+
labelSkipWidth,
152+
labelSkipHeight,
153+
legends,
154+
legendLabel,
155+
initialHiddenIds,
159156
})
160157

161-
const legendData = useMemo(
162-
() =>
163-
keys.map(key => {
164-
const bar = result.bars.find(bar => bar.data.id === key)
165-
166-
return { ...bar, data: { id: key, ...bar?.data, hidden: hiddenIds.includes(key) } }
167-
}),
168-
[hiddenIds, keys, result.bars]
169-
)
170-
171-
const barsWithValue = useMemo(
172-
() =>
173-
result.bars.filter(
174-
(bar): bar is ComputedBarDatumWithValue<RawDatum> => bar.data.value !== null
175-
),
176-
[result.bars]
177-
)
178-
179158
const transition = useTransition<
180159
ComputedBarDatumWithValue<RawDatum>,
181160
{
@@ -256,16 +235,6 @@ const InnerBar = <RawDatum extends BarDatum>({
256235
immediate: !animate,
257236
})
258237

259-
const shouldRenderLabel = useCallback(
260-
({ width, height }: { height: number; width: number }) => {
261-
if (!enableLabel) return false
262-
if (labelSkipWidth > 0 && width < labelSkipWidth) return false
263-
if (labelSkipHeight > 0 && height < labelSkipHeight) return false
264-
return true
265-
},
266-
[enableLabel, labelSkipHeight, labelSkipWidth]
267-
)
268-
269238
const commonProps = useMemo(
270239
() => ({
271240
borderRadius,
@@ -303,7 +272,7 @@ const InnerBar = <RawDatum extends BarDatum>({
303272
]
304273
)
305274

306-
const boundDefs = bindDefs(defs, result.bars, fill, {
275+
const boundDefs = bindDefs(defs, bars, fill, {
307276
dataKey: 'data',
308277
targetKey: 'data.fill',
309278
})
@@ -319,16 +288,16 @@ const InnerBar = <RawDatum extends BarDatum>({
319288

320289
if (layers.includes('annotations')) {
321290
layerById.annotations = (
322-
<BarAnnotations key="annotations" bars={result.bars} annotations={annotations} />
291+
<BarAnnotations key="annotations" bars={bars} annotations={annotations} />
323292
)
324293
}
325294

326295
if (layers.includes('axes')) {
327296
layerById.axes = (
328297
<Axes
329298
key="axes"
330-
xScale={result.xScale as any}
331-
yScale={result.yScale as any}
299+
xScale={xScale as any}
300+
yScale={yScale as any}
332301
width={innerWidth}
333302
height={innerHeight}
334303
top={axisTop}
@@ -347,7 +316,7 @@ const InnerBar = <RawDatum extends BarDatum>({
347316
...commonProps,
348317
bar,
349318
style,
350-
shouldRenderLabel: shouldRenderLabel(bar),
319+
shouldRenderLabel: shouldRenderBarLabel(bar),
351320
label: getLabel(bar.data),
352321
})
353322
)}
@@ -361,36 +330,21 @@ const InnerBar = <RawDatum extends BarDatum>({
361330
key="grid"
362331
width={innerWidth}
363332
height={innerHeight}
364-
xScale={enableGridX ? (result.xScale as any) : null}
365-
yScale={enableGridY ? (result.yScale as any) : null}
333+
xScale={enableGridX ? (xScale as any) : null}
334+
yScale={enableGridY ? (yScale as any) : null}
366335
xValues={gridXValues}
367336
yValues={gridYValues}
368337
/>
369338
)
370339
}
371340

372341
if (layers.includes('legends')) {
373-
const data = ([] as LegendData[]).concat(
374-
...legends.map(legend =>
375-
getLegendData({
376-
bars: legend.dataFrom === 'keys' ? legendData : result.bars,
377-
direction: legend.direction,
378-
from: legend.dataFrom,
379-
groupMode,
380-
layout,
381-
legendLabel,
382-
reverse,
383-
})
384-
)
385-
)
386-
387342
layerById.legends = (
388343
<BarLegends
389344
key="legends"
390345
width={innerWidth}
391346
height={innerHeight}
392-
data={data}
393-
legends={legends}
347+
legends={legendsWithData}
394348
toggleSerie={toggleSerie}
395349
/>
396350
)
@@ -403,8 +357,8 @@ const InnerBar = <RawDatum extends BarDatum>({
403357
markers={markers}
404358
width={innerWidth}
405359
height={innerHeight}
406-
xScale={result.xScale}
407-
yScale={result.yScale}
360+
xScale={xScale}
361+
yScale={yScale}
408362
theme={theme}
409363
/>
410364
)
@@ -419,9 +373,11 @@ const InnerBar = <RawDatum extends BarDatum>({
419373
innerHeight,
420374
width,
421375
height,
422-
...result,
376+
bars,
377+
xScale,
378+
yScale,
423379
}),
424-
[commonProps, height, innerHeight, innerWidth, margin, result, width]
380+
[commonProps, margin, innerWidth, innerHeight, width, height, bars, xScale, yScale]
425381
)
426382

427383
return (

‎packages/bar/src/BarCanvas.tsx

+76-116
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
1-
import {
2-
BarCanvasLayer,
3-
BarCanvasProps,
4-
BarDatum,
5-
ComputedBarDatum,
6-
ComputedBarDatumWithValue,
7-
} from './types'
1+
import { BarCanvasLayer, BarCanvasProps, BarDatum, ComputedBarDatum } from './types'
82
import {
93
Container,
104
Margin,
115
getRelativeCursor,
126
isCursorInRect,
137
useDimensions,
14-
usePropertyAccessor,
158
useTheme,
16-
useValueFormatter,
179
} from '@nivo/core'
1810
import {
1911
ForwardedRef,
@@ -25,16 +17,15 @@ import {
2517
useRef,
2618
} from 'react'
2719
import { canvasDefaultProps } from './props'
28-
import { generateGroupedBars, generateStackedBars, getLegendData } from './compute'
2920
import {
3021
renderAnnotationsToCanvas,
3122
useAnnotations,
3223
useComputedAnnotations,
3324
} from '@nivo/annotations'
3425
import { renderAxesToCanvas, renderGridLinesToCanvas } from '@nivo/axes'
3526
import { renderLegendToCanvas } from '@nivo/legends'
36-
import { useInheritedColor, useOrdinalColorScale } from '@nivo/colors'
3727
import { useTooltip } from '@nivo/tooltip'
28+
import { useBar } from './hooks'
3829

3930
declare module 'react' {
4031
// eslint-disable-next-line @typescript-eslint/ban-types
@@ -64,24 +55,24 @@ const isNumber = (value: unknown): value is number => typeof value === 'number'
6455

6556
const InnerBarCanvas = <RawDatum extends BarDatum>({
6657
data,
67-
indexBy = canvasDefaultProps.indexBy,
68-
keys = canvasDefaultProps.keys,
58+
indexBy,
59+
keys,
6960

7061
margin: partialMargin,
7162
width,
7263
height,
7364

74-
groupMode = canvasDefaultProps.groupMode,
75-
layout = canvasDefaultProps.layout,
76-
reverse = canvasDefaultProps.reverse,
77-
minValue = canvasDefaultProps.minValue,
78-
maxValue = canvasDefaultProps.maxValue,
65+
groupMode,
66+
layout,
67+
reverse,
68+
minValue,
69+
maxValue,
7970

80-
valueScale = canvasDefaultProps.valueScale,
81-
indexScale = canvasDefaultProps.indexScale,
71+
valueScale,
72+
indexScale,
8273

83-
padding = canvasDefaultProps.padding,
84-
innerPadding = canvasDefaultProps.innerPadding,
74+
padding,
75+
innerPadding,
8576

8677
axisTop,
8778
axisRight,
@@ -146,22 +137,22 @@ const InnerBarCanvas = <RawDatum extends BarDatum>({
146137
}
147138
},
148139

149-
enableLabel = canvasDefaultProps.enableLabel,
150-
label = canvasDefaultProps.label,
151-
labelSkipWidth = canvasDefaultProps.labelSkipWidth,
152-
labelSkipHeight = canvasDefaultProps.labelSkipHeight,
153-
labelTextColor = canvasDefaultProps.labelTextColor,
140+
enableLabel,
141+
label,
142+
labelSkipWidth,
143+
labelSkipHeight,
144+
labelTextColor,
154145

155-
colorBy = canvasDefaultProps.colorBy,
156-
colors = canvasDefaultProps.colors,
146+
colorBy,
147+
colors,
157148
borderRadius = canvasDefaultProps.borderRadius,
158149
borderWidth = canvasDefaultProps.borderWidth,
159-
borderColor = canvasDefaultProps.borderColor,
150+
borderColor,
160151

161152
annotations = canvasDefaultProps.annotations,
162153

163154
legendLabel,
164-
tooltipLabel = canvasDefaultProps.tooltipLabel,
155+
tooltipLabel,
165156

166157
valueFormat,
167158

@@ -171,7 +162,7 @@ const InnerBarCanvas = <RawDatum extends BarDatum>({
171162
onMouseEnter,
172163
onMouseLeave,
173164

174-
legends = canvasDefaultProps.legends,
165+
legends,
175166

176167
pixelRatio = canvasDefaultProps.pixelRatio,
177168

@@ -186,76 +177,53 @@ const InnerBarCanvas = <RawDatum extends BarDatum>({
186177
partialMargin
187178
)
188179

189-
const { showTooltipFromEvent, hideTooltip } = useTooltip()
190-
191-
const formatValue = useValueFormatter(valueFormat)
192-
const getBorderColor = useInheritedColor<ComputedBarDatumWithValue<RawDatum>>(
180+
const {
181+
bars,
182+
barsWithValue,
183+
xScale,
184+
yScale,
185+
getLabel,
186+
getTooltipLabel,
187+
getBorderColor,
188+
getLabelColor,
189+
shouldRenderBarLabel,
190+
legendsWithData,
191+
} = useBar<RawDatum>({
192+
indexBy,
193+
label,
194+
tooltipLabel,
195+
valueFormat,
196+
colors,
197+
colorBy,
193198
borderColor,
194-
theme
195-
)
196-
const getColor = useOrdinalColorScale(colors, colorBy)
197-
const getIndex = usePropertyAccessor(indexBy)
198-
const getLabel = usePropertyAccessor(label)
199-
const getLabelColor = useInheritedColor<ComputedBarDatumWithValue<RawDatum>>(
200199
labelTextColor,
201-
theme
202-
)
203-
const getTooltipLabel = usePropertyAccessor(tooltipLabel)
204-
205-
const options = {
200+
groupMode,
206201
layout,
207202
reverse,
208203
data,
209-
getIndex,
210204
keys,
211205
minValue,
212206
maxValue,
207+
margin,
213208
width: innerWidth,
214209
height: innerHeight,
215-
getColor,
216210
padding,
217211
innerPadding,
218212
valueScale,
219213
indexScale,
220-
formatValue,
221-
getTooltipLabel,
222-
}
223-
224-
const result =
225-
groupMode === 'grouped' ? generateGroupedBars(options) : generateStackedBars(options)
226-
227-
const legendData = useMemo(
228-
() =>
229-
keys.map(key => {
230-
const bar = result.bars.find(bar => bar.data.id === key)
231-
232-
return { ...bar, data: { id: key, ...bar?.data, hidden: false } }
233-
}),
234-
[keys, result.bars]
235-
)
236-
237-
const barsWithValue = useMemo(
238-
() =>
239-
result.bars.filter(
240-
(bar): bar is ComputedBarDatumWithValue<RawDatum> => bar.data.value !== null
241-
),
242-
[result.bars]
243-
)
214+
enableLabel,
215+
labelSkipWidth,
216+
labelSkipHeight,
217+
legends,
218+
legendLabel,
219+
})
244220

245-
const shouldRenderLabel = useCallback(
246-
({ width, height }: { height: number; width: number }) => {
247-
if (!enableLabel) return false
248-
if (labelSkipWidth > 0 && width < labelSkipWidth) return false
249-
if (labelSkipHeight > 0 && height < labelSkipHeight) return false
250-
return true
251-
},
252-
[enableLabel, labelSkipHeight, labelSkipWidth]
253-
)
221+
const { showTooltipFromEvent, hideTooltip } = useTooltip()
254222

255223
// Using any because return type isn't correct
256224
const boundAnnotations: any = useComputedAnnotations({
257225
annotations: useAnnotations({
258-
data: result.bars,
226+
data: bars,
259227
annotations,
260228
getPosition: node => ({
261229
x: node.x,
@@ -288,7 +256,9 @@ const InnerBarCanvas = <RawDatum extends BarDatum>({
288256
innerHeight,
289257
width,
290258
height,
291-
...result,
259+
bars,
260+
xScale,
261+
yScale,
292262
}),
293263
[
294264
borderRadius,
@@ -305,7 +275,9 @@ const InnerBarCanvas = <RawDatum extends BarDatum>({
305275
onClick,
306276
onMouseEnter,
307277
onMouseLeave,
308-
result,
278+
bars,
279+
xScale,
280+
yScale,
309281
tooltip,
310282
width,
311283
]
@@ -336,7 +308,7 @@ const InnerBarCanvas = <RawDatum extends BarDatum>({
336308
renderGridLinesToCanvas<string | number>(ctx, {
337309
width,
338310
height,
339-
scale: result.xScale as any,
311+
scale: xScale as any,
340312
axis: 'x',
341313
values: gridXValues,
342314
})
@@ -346,16 +318,16 @@ const InnerBarCanvas = <RawDatum extends BarDatum>({
346318
renderGridLinesToCanvas<string | number>(ctx, {
347319
width,
348320
height,
349-
scale: result.yScale as any,
321+
scale: yScale as any,
350322
axis: 'y',
351323
values: gridYValues,
352324
})
353325
}
354326
}
355327
} else if (layer === 'axes') {
356328
renderAxesToCanvas(ctx, {
357-
xScale: result.xScale as any,
358-
yScale: result.yScale as any,
329+
xScale: xScale as any,
330+
yScale: yScale as any,
359331
width: innerWidth,
360332
height: innerHeight,
361333
top: axisTop,
@@ -373,21 +345,11 @@ const InnerBarCanvas = <RawDatum extends BarDatum>({
373345
borderWidth,
374346
label: getLabel(bar.data),
375347
labelColor: getLabelColor(bar) as string,
376-
shouldRenderLabel: shouldRenderLabel(bar),
348+
shouldRenderLabel: shouldRenderBarLabel(bar),
377349
})
378350
})
379351
} else if (layer === 'legends') {
380-
legends.forEach(legend => {
381-
const data = getLegendData({
382-
bars: legendData,
383-
direction: legend.direction,
384-
from: legend.dataFrom,
385-
groupMode,
386-
layout,
387-
legendLabel,
388-
reverse,
389-
})
390-
352+
legendsWithData.forEach(([legend, data]) => {
391353
renderLegendToCanvas(ctx, {
392354
...legend,
393355
data,
@@ -427,30 +389,28 @@ const InnerBarCanvas = <RawDatum extends BarDatum>({
427389
layerContext,
428390
layers,
429391
layout,
430-
legendData,
431-
legendLabel,
432-
legends,
392+
legendsWithData,
433393
margin.left,
434394
margin.top,
435395
outerHeight,
436396
outerWidth,
437397
pixelRatio,
438398
renderBar,
439-
result.xScale,
440-
result.yScale,
399+
xScale,
400+
yScale,
441401
reverse,
442-
shouldRenderLabel,
402+
shouldRenderBarLabel,
443403
theme,
444404
width,
445405
])
446406

447407
const handleMouseHover = useCallback(
448408
(event: React.MouseEvent<HTMLCanvasElement>) => {
449-
if (!result.bars) return
409+
if (!bars) return
450410
if (!canvasEl.current) return
451411

452412
const [x, y] = getRelativeCursor(canvasEl.current, event)
453-
const bar = findBarUnderCursor(result.bars, margin, x, y)
413+
const bar = findBarUnderCursor(bars, margin, x, y)
454414

455415
if (bar !== undefined) {
456416
showTooltipFromEvent(
@@ -470,39 +430,39 @@ const InnerBarCanvas = <RawDatum extends BarDatum>({
470430
hideTooltip()
471431
}
472432
},
473-
[hideTooltip, margin, onMouseEnter, result.bars, showTooltipFromEvent, tooltip]
433+
[hideTooltip, margin, onMouseEnter, bars, showTooltipFromEvent, tooltip]
474434
)
475435

476436
const handleMouseLeave = useCallback(
477437
(event: React.MouseEvent<HTMLCanvasElement>) => {
478-
if (!result.bars) return
438+
if (!bars) return
479439
if (!canvasEl.current) return
480440

481441
hideTooltip()
482442

483443
const [x, y] = getRelativeCursor(canvasEl.current, event)
484-
const bar = findBarUnderCursor(result.bars, margin, x, y)
444+
const bar = findBarUnderCursor(bars, margin, x, y)
485445

486446
if (bar) {
487447
onMouseLeave?.(bar.data, event)
488448
}
489449
},
490-
[hideTooltip, margin, onMouseLeave, result.bars]
450+
[hideTooltip, margin, onMouseLeave, bars]
491451
)
492452

493453
const handleClick = useCallback(
494454
(event: React.MouseEvent<HTMLCanvasElement>) => {
495-
if (!result.bars) return
455+
if (!bars) return
496456
if (!canvasEl.current) return
497457

498458
const [x, y] = getRelativeCursor(canvasEl.current, event)
499-
const bar = findBarUnderCursor(result.bars, margin, x, y)
459+
const bar = findBarUnderCursor(bars, margin, x, y)
500460

501461
if (bar !== undefined) {
502462
onClick?.({ ...bar.data, color: bar.color }, event)
503463
}
504464
},
505-
[margin, onClick, result.bars]
465+
[margin, onClick, bars]
506466
)
507467

508468
return (

‎packages/bar/src/BarLegends.tsx

+20-24
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,31 @@
11
import { BoxLegendSvg } from '@nivo/legends'
2-
import { BarCommonProps, LegendData } from './types'
2+
import { BarLegendProps, LegendData } from './types'
33

4-
interface BarLegendsProps<RawDatum> {
4+
interface BarLegendsProps {
55
width: number
66
height: number
7-
legends: BarCommonProps<RawDatum>['legends']
8-
data: LegendData[]
7+
legends: [BarLegendProps, LegendData[]][]
98
toggleSerie: (id: string | number) => void
109
}
1110

12-
export const BarLegends = <RawDatum,>({
11+
export const BarLegends = ({
1312
width,
1413
height,
1514
legends,
16-
data,
1715
toggleSerie,
18-
}: BarLegendsProps<RawDatum>) => {
19-
return (
20-
<>
21-
{legends.map((legend, i) => (
22-
<BoxLegendSvg
23-
key={i}
24-
{...legend}
25-
containerWidth={width}
26-
containerHeight={height}
27-
data={legend.data ?? data}
28-
toggleSerie={
29-
legend.toggleSerie && legend.dataFrom === 'keys' ? toggleSerie : undefined
30-
}
31-
/>
32-
))}
33-
</>
34-
)
35-
}
16+
}: BarLegendsProps) => (
17+
<>
18+
{legends.map(([legend, data], i) => (
19+
<BoxLegendSvg
20+
key={i}
21+
{...legend}
22+
containerWidth={width}
23+
containerHeight={height}
24+
data={legend.data ?? data}
25+
toggleSerie={
26+
legend.toggleSerie && legend.dataFrom === 'keys' ? toggleSerie : undefined
27+
}
28+
/>
29+
))}
30+
</>
31+
)

‎packages/bar/src/compute/legends.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { BarDatum, BarLegendProps, BarSvgProps, BarsWithHidden, LegendLabelDatum } from '../types'
1+
import {
2+
BarDatum,
3+
BarLegendProps,
4+
BarSvgProps,
5+
BarsWithHidden,
6+
LegendData,
7+
LegendLabelDatum,
8+
} from '../types'
29
import { getPropertyAccessor } from '@nivo/core'
310
import { uniqBy } from 'lodash'
411

@@ -9,7 +16,7 @@ export const getLegendDataForKeys = <RawDatum extends BarDatum>(
916
groupMode: NonNullable<BarSvgProps<RawDatum>['groupMode']>,
1017
reverse: boolean,
1118
getLegendLabel: (datum: LegendLabelDatum<RawDatum>) => string
12-
) => {
19+
): LegendData[] => {
1320
const data = uniqBy(
1421
bars.map(bar => ({
1522
id: bar.data.id,
@@ -37,7 +44,7 @@ export const getLegendDataForIndexes = <RawDatum extends BarDatum>(
3744
bars: BarsWithHidden<RawDatum>,
3845
layout: NonNullable<BarSvgProps<RawDatum>['layout']>,
3946
getLegendLabel: (datum: LegendLabelDatum<RawDatum>) => string
40-
) => {
47+
): LegendData[] => {
4148
const data = uniqBy(
4249
bars.map(bar => ({
4350
id: bar.data.indexValue ?? '',

‎packages/bar/src/hooks.ts

+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { useCallback, useMemo, useState } from 'react'
2+
import { useInheritedColor, useOrdinalColorScale } from '@nivo/colors'
3+
import { usePropertyAccessor, useTheme, useValueFormatter, Margin } from '@nivo/core'
4+
import {
5+
DataProps,
6+
BarCommonProps,
7+
BarDatum,
8+
ComputedBarDatumWithValue,
9+
LegendData,
10+
BarLegendProps,
11+
} from './types'
12+
import { defaultProps } from './props'
13+
import { generateGroupedBars, generateStackedBars, getLegendData } from './compute'
14+
15+
export const useBar = <RawDatum extends BarDatum>({
16+
indexBy = defaultProps.indexBy,
17+
keys = defaultProps.keys,
18+
label = defaultProps.label,
19+
tooltipLabel = defaultProps.tooltipLabel,
20+
valueFormat,
21+
colors = defaultProps.colors,
22+
colorBy = defaultProps.colorBy,
23+
borderColor = defaultProps.borderColor,
24+
labelTextColor = defaultProps.labelTextColor,
25+
groupMode = defaultProps.groupMode,
26+
layout = defaultProps.layout,
27+
reverse = defaultProps.reverse,
28+
data,
29+
minValue = defaultProps.minValue,
30+
maxValue = defaultProps.maxValue,
31+
margin,
32+
width,
33+
height,
34+
padding = defaultProps.padding,
35+
innerPadding = defaultProps.innerPadding,
36+
valueScale = defaultProps.valueScale,
37+
indexScale = defaultProps.indexScale,
38+
initialHiddenIds = defaultProps.initialHiddenIds,
39+
enableLabel = defaultProps.enableLabel,
40+
labelSkipWidth = defaultProps.labelSkipWidth,
41+
labelSkipHeight = defaultProps.labelSkipHeight,
42+
legends = defaultProps.legends,
43+
legendLabel,
44+
}: {
45+
indexBy?: BarCommonProps<RawDatum>['indexBy']
46+
label?: BarCommonProps<RawDatum>['label']
47+
tooltipLabel?: BarCommonProps<RawDatum>['tooltipLabel']
48+
valueFormat?: BarCommonProps<RawDatum>['valueFormat']
49+
colors?: BarCommonProps<RawDatum>['colors']
50+
colorBy?: BarCommonProps<RawDatum>['colorBy']
51+
borderColor?: BarCommonProps<RawDatum>['borderColor']
52+
labelTextColor?: BarCommonProps<RawDatum>['labelTextColor']
53+
groupMode?: BarCommonProps<RawDatum>['groupMode']
54+
layout?: BarCommonProps<RawDatum>['layout']
55+
reverse?: BarCommonProps<RawDatum>['reverse']
56+
data: DataProps<RawDatum>['data']
57+
keys?: BarCommonProps<RawDatum>['keys']
58+
minValue?: BarCommonProps<RawDatum>['minValue']
59+
maxValue?: BarCommonProps<RawDatum>['maxValue']
60+
margin: Margin
61+
width: number
62+
height: number
63+
padding?: BarCommonProps<RawDatum>['padding']
64+
innerPadding?: BarCommonProps<RawDatum>['innerPadding']
65+
valueScale?: BarCommonProps<RawDatum>['valueScale']
66+
indexScale?: BarCommonProps<RawDatum>['indexScale']
67+
initialHiddenIds?: BarCommonProps<RawDatum>['initialHiddenIds']
68+
enableLabel?: BarCommonProps<RawDatum>['enableLabel']
69+
labelSkipWidth?: BarCommonProps<RawDatum>['labelSkipWidth']
70+
labelSkipHeight?: BarCommonProps<RawDatum>['labelSkipHeight']
71+
legends?: BarCommonProps<RawDatum>['legends']
72+
legendLabel?: BarCommonProps<RawDatum>['legendLabel']
73+
}) => {
74+
const [hiddenIds, setHiddenIds] = useState(initialHiddenIds ?? [])
75+
const toggleSerie = useCallback(id => {
76+
setHiddenIds(state =>
77+
state.indexOf(id) > -1 ? state.filter(item => item !== id) : [...state, id]
78+
)
79+
}, [])
80+
81+
const getIndex = usePropertyAccessor(indexBy)
82+
const getLabel = usePropertyAccessor(label)
83+
const getTooltipLabel = usePropertyAccessor(tooltipLabel)
84+
const formatValue = useValueFormatter(valueFormat)
85+
86+
const theme = useTheme()
87+
const getColor = useOrdinalColorScale(colors, colorBy)
88+
const getBorderColor = useInheritedColor<ComputedBarDatumWithValue<RawDatum>>(
89+
borderColor,
90+
theme
91+
)
92+
const getLabelColor = useInheritedColor<ComputedBarDatumWithValue<RawDatum>>(
93+
labelTextColor,
94+
theme
95+
)
96+
97+
const generateBars = groupMode === 'grouped' ? generateGroupedBars : generateStackedBars
98+
const { bars, xScale, yScale } = generateBars({
99+
layout,
100+
reverse,
101+
data,
102+
getIndex,
103+
keys,
104+
minValue,
105+
maxValue,
106+
width,
107+
height,
108+
getColor,
109+
padding,
110+
innerPadding,
111+
valueScale,
112+
indexScale,
113+
hiddenIds,
114+
formatValue,
115+
getTooltipLabel,
116+
})
117+
118+
const barsWithValue = useMemo(
119+
() =>
120+
bars.filter(
121+
(bar): bar is ComputedBarDatumWithValue<RawDatum> => bar.data.value !== null
122+
),
123+
[bars]
124+
)
125+
126+
const shouldRenderBarLabel = useCallback(
127+
({ width, height }: { height: number; width: number }) => {
128+
if (!enableLabel) return false
129+
if (labelSkipWidth > 0 && width < labelSkipWidth) return false
130+
if (labelSkipHeight > 0 && height < labelSkipHeight) return false
131+
return true
132+
},
133+
[enableLabel, labelSkipWidth, labelSkipHeight]
134+
)
135+
136+
const legendData = useMemo(
137+
() =>
138+
keys.map(key => {
139+
const bar = bars.find(bar => bar.data.id === key)
140+
141+
return { ...bar, data: { id: key, ...bar?.data, hidden: hiddenIds.includes(key) } }
142+
}),
143+
[hiddenIds, keys, bars]
144+
)
145+
146+
const legendsWithData: [BarLegendProps, LegendData[]][] = useMemo(
147+
() =>
148+
legends.map(legend => {
149+
const data = getLegendData({
150+
bars: legend.dataFrom === 'keys' ? legendData : bars,
151+
direction: legend.direction,
152+
from: legend.dataFrom,
153+
groupMode,
154+
layout,
155+
legendLabel,
156+
reverse,
157+
})
158+
159+
return [legend, data]
160+
}),
161+
[legends, legendData, bars, groupMode, layout, legendLabel, reverse]
162+
)
163+
164+
return {
165+
bars,
166+
barsWithValue,
167+
xScale,
168+
yScale,
169+
getIndex,
170+
getLabel,
171+
getTooltipLabel,
172+
formatValue,
173+
getColor,
174+
getBorderColor,
175+
getLabelColor,
176+
shouldRenderBarLabel,
177+
hiddenIds,
178+
toggleSerie,
179+
legendsWithData,
180+
}
181+
}

‎packages/bar/src/props.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const defaultProps = {
4444
tooltipLabel: <RawDatum>(datum: ComputedDatum<RawDatum>) => `${datum.id} - ${datum.indexValue}`,
4545

4646
legends: [],
47-
47+
initialHiddenIds: [],
4848
annotations: [],
4949
}
5050

‎packages/bar/src/types.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ export type BarCommonProps<RawDatum> = {
239239
legends: BarLegendProps[]
240240

241241
renderWrapper?: boolean
242+
243+
initialHiddenIds: string[]
242244
}
243245

244246
export type BarSvgProps<RawDatum extends BarDatum> = Partial<BarCommonProps<RawDatum>> &
@@ -257,7 +259,6 @@ export type BarSvgProps<RawDatum extends BarDatum> = Partial<BarCommonProps<RawD
257259

258260
markers: CartesianMarkerProps[]
259261

260-
initialHiddenIds: string[]
261262
layers: BarLayer<RawDatum>[]
262263

263264
role: string

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ const initialProperties = {
156156
isFocusable: true,
157157
ariaLabel: 'Nivo bar chart demo',
158158
barAriaLabel: d => {
159-
console.log({ d })
159+
// console.log({ d })
160160
return `aria label`
161161
},
162162
}

0 commit comments

Comments
 (0)
Please sign in to comment.