diff --git a/packages/bar/src/Bar.tsx b/packages/bar/src/Bar.tsx index 3a1540b9b8..b3620dd52a 100644 --- a/packages/bar/src/Bar.tsx +++ b/packages/bar/src/Bar.tsx @@ -1,13 +1,6 @@ import { Axes, Grid } from '@nivo/axes' import { BarAnnotations } from './BarAnnotations' -import { - BarDatum, - BarLayer, - BarLayerId, - BarSvgProps, - ComputedBarDatumWithValue, - LegendData, -} from './types' +import { BarDatum, BarLayer, BarLayerId, BarSvgProps, ComputedBarDatumWithValue } from './types' import { BarLegends } from './BarLegends' import { // @ts-ignore @@ -18,15 +11,12 @@ import { bindDefs, useDimensions, useMotionConfig, - usePropertyAccessor, useTheme, - useValueFormatter, } from '@nivo/core' -import { Fragment, ReactNode, createElement, useCallback, useMemo, useState } from 'react' -import { generateGroupedBars, generateStackedBars, getLegendData } from './compute' +import { Fragment, ReactNode, createElement, useMemo } from 'react' import { svgDefaultProps } from './props' -import { useInheritedColor, useOrdinalColorScale } from '@nivo/colors' import { useTransition } from '@react-spring/web' +import { useBar } from './hooks' type InnerBarProps = Omit< BarSvgProps, @@ -35,24 +25,24 @@ type InnerBarProps = Omit< const InnerBar = ({ data, - indexBy = svgDefaultProps.indexBy, - keys = svgDefaultProps.keys, + indexBy, + keys, margin: partialMargin, width, height, - groupMode = svgDefaultProps.groupMode, - layout = svgDefaultProps.layout, - reverse = svgDefaultProps.reverse, - minValue = svgDefaultProps.minValue, - maxValue = svgDefaultProps.maxValue, + groupMode, + layout, + reverse, + minValue, + maxValue, - valueScale = svgDefaultProps.valueScale, - indexScale = svgDefaultProps.indexScale, + valueScale, + indexScale, - padding = svgDefaultProps.padding, - innerPadding = svgDefaultProps.innerPadding, + padding, + innerPadding, axisTop, axisRight, @@ -66,26 +56,26 @@ const InnerBar = ({ layers = svgDefaultProps.layers as BarLayer[], barComponent = svgDefaultProps.barComponent, - enableLabel = svgDefaultProps.enableLabel, - label = svgDefaultProps.label, - labelSkipWidth = svgDefaultProps.labelSkipWidth, - labelSkipHeight = svgDefaultProps.labelSkipHeight, - labelTextColor = svgDefaultProps.labelTextColor, + enableLabel, + label, + labelSkipWidth, + labelSkipHeight, + labelTextColor, markers, - colorBy = svgDefaultProps.colorBy, - colors = svgDefaultProps.colors, + colorBy, + colors, defs = svgDefaultProps.defs, fill = svgDefaultProps.fill, borderRadius = svgDefaultProps.borderRadius, borderWidth = svgDefaultProps.borderWidth, - borderColor = svgDefaultProps.borderColor, + borderColor, annotations = svgDefaultProps.annotations, legendLabel, - tooltipLabel = svgDefaultProps.tooltipLabel, + tooltipLabel, valueFormat, @@ -95,19 +85,19 @@ const InnerBar = ({ onMouseEnter, onMouseLeave, - legends = svgDefaultProps.legends, + legends, role = svgDefaultProps.role, + ariaLabel, + ariaLabelledBy, + ariaDescribedBy, + isFocusable = svgDefaultProps.isFocusable, + barAriaLabel, + barAriaLabelledBy, + barAriaDescribedBy, initialHiddenIds, }: InnerBarProps) => { - const [hiddenIds, setHiddenIds] = useState(initialHiddenIds ?? []) - const toggleSerie = useCallback(id => { - setHiddenIds(state => - state.indexOf(id) > -1 ? state.filter(item => item !== id) : [...state, id] - ) - }, []) - const theme = useTheme() const { animate, config: springConfig } = useMotionConfig() const { outerWidth, outerHeight, margin, innerWidth, innerHeight } = useDimensions( @@ -116,59 +106,49 @@ const InnerBar = ({ partialMargin ) - const formatValue = useValueFormatter(valueFormat) - const getBorderColor = useInheritedColor>( + const { + bars, + barsWithValue, + xScale, + yScale, + getLabel, + getTooltipLabel, + getBorderColor, + getLabelColor, + shouldRenderBarLabel, + toggleSerie, + legendsWithData, + } = useBar({ + indexBy, + label, + tooltipLabel, + valueFormat, + colors, + colorBy, borderColor, - theme - ) - const getColor = useOrdinalColorScale(colors, colorBy) - const getIndex = usePropertyAccessor(indexBy) - const getLabel = usePropertyAccessor(label) - const getLabelColor = useInheritedColor>( labelTextColor, - theme - ) - const getTooltipLabel = usePropertyAccessor(tooltipLabel) - - const generateBars = groupMode === 'grouped' ? generateGroupedBars : generateStackedBars - const result = generateBars({ + groupMode, layout, reverse, data, - getIndex, keys, minValue, maxValue, + margin, width: innerWidth, height: innerHeight, - getColor, padding, innerPadding, valueScale, indexScale, - hiddenIds, - formatValue, - getTooltipLabel, + enableLabel, + labelSkipWidth, + labelSkipHeight, + legends, + legendLabel, + initialHiddenIds, }) - const legendData = useMemo( - () => - keys.map(key => { - const bar = result.bars.find(bar => bar.data.id === key) - - return { ...bar, data: { id: key, ...bar?.data, hidden: hiddenIds.includes(key) } } - }), - [hiddenIds, keys, result.bars] - ) - - const barsWithValue = useMemo( - () => - result.bars.filter( - (bar): bar is ComputedBarDatumWithValue => bar.data.value !== null - ), - [result.bars] - ) - const transition = useTransition< ComputedBarDatumWithValue, { @@ -249,16 +229,6 @@ const InnerBar = ({ immediate: !animate, }) - const shouldRenderLabel = useCallback( - ({ width, height }: { height: number; width: number }) => { - if (!enableLabel) return false - if (labelSkipWidth > 0 && width < labelSkipWidth) return false - if (labelSkipHeight > 0 && height < labelSkipHeight) return false - return true - }, - [enableLabel, labelSkipHeight, labelSkipWidth] - ) - const commonProps = useMemo( () => ({ borderRadius, @@ -272,6 +242,10 @@ const InnerBar = ({ onMouseLeave, getTooltipLabel, tooltip, + isFocusable, + ariaLabel: barAriaLabel, + ariaLabelledBy: barAriaLabelledBy, + ariaDescribedBy: barAriaDescribedBy, }), [ borderRadius, @@ -285,10 +259,14 @@ const InnerBar = ({ onMouseEnter, onMouseLeave, tooltip, + isFocusable, + barAriaLabel, + barAriaLabelledBy, + barAriaDescribedBy, ] ) - const boundDefs = bindDefs(defs, result.bars, fill, { + const boundDefs = bindDefs(defs, bars, fill, { dataKey: 'data', targetKey: 'data.fill', }) @@ -304,7 +282,7 @@ const InnerBar = ({ if (layers.includes('annotations')) { layerById.annotations = ( - + ) } @@ -312,8 +290,8 @@ const InnerBar = ({ layerById.axes = ( ({ ...commonProps, bar, style, - shouldRenderLabel: shouldRenderLabel(bar), + shouldRenderLabel: shouldRenderBarLabel(bar), label: getLabel(bar.data), }) )} @@ -346,8 +324,8 @@ const InnerBar = ({ key="grid" width={innerWidth} height={innerHeight} - xScale={enableGridX ? (result.xScale as any) : null} - yScale={enableGridY ? (result.yScale as any) : null} + xScale={enableGridX ? (xScale as any) : null} + yScale={enableGridY ? (yScale as any) : null} xValues={gridXValues} yValues={gridYValues} /> @@ -355,27 +333,12 @@ const InnerBar = ({ } if (layers.includes('legends')) { - const data = ([] as LegendData[]).concat( - ...legends.map(legend => - getLegendData({ - bars: legend.dataFrom === 'keys' ? legendData : result.bars, - direction: legend.direction, - from: legend.dataFrom, - groupMode, - layout, - legendLabel, - reverse, - }) - ) - ) - layerById.legends = ( ) @@ -388,8 +351,8 @@ const InnerBar = ({ markers={markers} width={innerWidth} height={innerHeight} - xScale={result.xScale} - yScale={result.yScale} + xScale={xScale} + yScale={yScale} theme={theme} /> ) @@ -404,9 +367,11 @@ const InnerBar = ({ innerHeight, width, height, - ...result, + bars, + xScale, + yScale, }), - [commonProps, height, innerHeight, innerWidth, margin, result, width] + [commonProps, margin, innerWidth, innerHeight, width, height, bars, xScale, yScale] ) return ( @@ -416,6 +381,10 @@ const InnerBar = ({ margin={margin} defs={boundDefs} role={role} + ariaLabel={ariaLabel} + ariaLabelledBy={ariaLabelledBy} + ariaDescribedBy={ariaDescribedBy} + isFocusable={isFocusable} > {layers.map((layer, i) => { if (typeof layer === 'function') { diff --git a/packages/bar/src/BarCanvas.tsx b/packages/bar/src/BarCanvas.tsx index adb44cb654..a6e677ef1c 100644 --- a/packages/bar/src/BarCanvas.tsx +++ b/packages/bar/src/BarCanvas.tsx @@ -1,19 +1,11 @@ -import { - BarCanvasLayer, - BarCanvasProps, - BarDatum, - ComputedBarDatum, - ComputedBarDatumWithValue, -} from './types' +import { BarCanvasLayer, BarCanvasProps, BarDatum, ComputedBarDatum } from './types' import { Container, Margin, getRelativeCursor, isCursorInRect, useDimensions, - usePropertyAccessor, useTheme, - useValueFormatter, } from '@nivo/core' import { ForwardedRef, @@ -25,7 +17,6 @@ import { useRef, } from 'react' import { canvasDefaultProps } from './props' -import { generateGroupedBars, generateStackedBars, getLegendData } from './compute' import { renderAnnotationsToCanvas, useAnnotations, @@ -33,8 +24,8 @@ import { } from '@nivo/annotations' import { renderAxesToCanvas, renderGridLinesToCanvas } from '@nivo/axes' import { renderLegendToCanvas } from '@nivo/legends' -import { useInheritedColor, useOrdinalColorScale } from '@nivo/colors' import { useTooltip } from '@nivo/tooltip' +import { useBar } from './hooks' declare module 'react' { // eslint-disable-next-line @typescript-eslint/ban-types @@ -64,24 +55,24 @@ const isNumber = (value: unknown): value is number => typeof value === 'number' const InnerBarCanvas = ({ data, - indexBy = canvasDefaultProps.indexBy, - keys = canvasDefaultProps.keys, + indexBy, + keys, margin: partialMargin, width, height, - groupMode = canvasDefaultProps.groupMode, - layout = canvasDefaultProps.layout, - reverse = canvasDefaultProps.reverse, - minValue = canvasDefaultProps.minValue, - maxValue = canvasDefaultProps.maxValue, + groupMode, + layout, + reverse, + minValue, + maxValue, - valueScale = canvasDefaultProps.valueScale, - indexScale = canvasDefaultProps.indexScale, + valueScale, + indexScale, - padding = canvasDefaultProps.padding, - innerPadding = canvasDefaultProps.innerPadding, + padding, + innerPadding, axisTop, axisRight, @@ -146,22 +137,22 @@ const InnerBarCanvas = ({ } }, - enableLabel = canvasDefaultProps.enableLabel, - label = canvasDefaultProps.label, - labelSkipWidth = canvasDefaultProps.labelSkipWidth, - labelSkipHeight = canvasDefaultProps.labelSkipHeight, - labelTextColor = canvasDefaultProps.labelTextColor, + enableLabel, + label, + labelSkipWidth, + labelSkipHeight, + labelTextColor, - colorBy = canvasDefaultProps.colorBy, - colors = canvasDefaultProps.colors, + colorBy, + colors, borderRadius = canvasDefaultProps.borderRadius, borderWidth = canvasDefaultProps.borderWidth, - borderColor = canvasDefaultProps.borderColor, + borderColor, annotations = canvasDefaultProps.annotations, legendLabel, - tooltipLabel = canvasDefaultProps.tooltipLabel, + tooltipLabel, valueFormat, @@ -171,7 +162,7 @@ const InnerBarCanvas = ({ onMouseEnter, onMouseLeave, - legends = canvasDefaultProps.legends, + legends, pixelRatio = canvasDefaultProps.pixelRatio, @@ -186,76 +177,53 @@ const InnerBarCanvas = ({ partialMargin ) - const { showTooltipFromEvent, hideTooltip } = useTooltip() - - const formatValue = useValueFormatter(valueFormat) - const getBorderColor = useInheritedColor>( + const { + bars, + barsWithValue, + xScale, + yScale, + getLabel, + getTooltipLabel, + getBorderColor, + getLabelColor, + shouldRenderBarLabel, + legendsWithData, + } = useBar({ + indexBy, + label, + tooltipLabel, + valueFormat, + colors, + colorBy, borderColor, - theme - ) - const getColor = useOrdinalColorScale(colors, colorBy) - const getIndex = usePropertyAccessor(indexBy) - const getLabel = usePropertyAccessor(label) - const getLabelColor = useInheritedColor>( labelTextColor, - theme - ) - const getTooltipLabel = usePropertyAccessor(tooltipLabel) - - const options = { + groupMode, layout, reverse, data, - getIndex, keys, minValue, maxValue, + margin, width: innerWidth, height: innerHeight, - getColor, padding, innerPadding, valueScale, indexScale, - formatValue, - getTooltipLabel, - } - - const result = - groupMode === 'grouped' ? generateGroupedBars(options) : generateStackedBars(options) - - const legendData = useMemo( - () => - keys.map(key => { - const bar = result.bars.find(bar => bar.data.id === key) - - return { ...bar, data: { id: key, ...bar?.data, hidden: false } } - }), - [keys, result.bars] - ) - - const barsWithValue = useMemo( - () => - result.bars.filter( - (bar): bar is ComputedBarDatumWithValue => bar.data.value !== null - ), - [result.bars] - ) + enableLabel, + labelSkipWidth, + labelSkipHeight, + legends, + legendLabel, + }) - const shouldRenderLabel = useCallback( - ({ width, height }: { height: number; width: number }) => { - if (!enableLabel) return false - if (labelSkipWidth > 0 && width < labelSkipWidth) return false - if (labelSkipHeight > 0 && height < labelSkipHeight) return false - return true - }, - [enableLabel, labelSkipHeight, labelSkipWidth] - ) + const { showTooltipFromEvent, hideTooltip } = useTooltip() // Using any because return type isn't correct const boundAnnotations: any = useComputedAnnotations({ annotations: useAnnotations({ - data: result.bars, + data: bars, annotations, getPosition: node => ({ x: node.x, @@ -288,7 +256,9 @@ const InnerBarCanvas = ({ innerHeight, width, height, - ...result, + bars, + xScale, + yScale, }), [ borderRadius, @@ -305,7 +275,9 @@ const InnerBarCanvas = ({ onClick, onMouseEnter, onMouseLeave, - result, + bars, + xScale, + yScale, tooltip, width, ] @@ -336,7 +308,7 @@ const InnerBarCanvas = ({ renderGridLinesToCanvas(ctx, { width, height, - scale: result.xScale as any, + scale: xScale as any, axis: 'x', values: gridXValues, }) @@ -346,7 +318,7 @@ const InnerBarCanvas = ({ renderGridLinesToCanvas(ctx, { width, height, - scale: result.yScale as any, + scale: yScale as any, axis: 'y', values: gridYValues, }) @@ -354,8 +326,8 @@ const InnerBarCanvas = ({ } } else if (layer === 'axes') { renderAxesToCanvas(ctx, { - xScale: result.xScale as any, - yScale: result.yScale as any, + xScale: xScale as any, + yScale: yScale as any, width: innerWidth, height: innerHeight, top: axisTop, @@ -373,21 +345,11 @@ const InnerBarCanvas = ({ borderWidth, label: getLabel(bar.data), labelColor: getLabelColor(bar) as string, - shouldRenderLabel: shouldRenderLabel(bar), + shouldRenderLabel: shouldRenderBarLabel(bar), }) }) } else if (layer === 'legends') { - legends.forEach(legend => { - const data = getLegendData({ - bars: legendData, - direction: legend.direction, - from: legend.dataFrom, - groupMode, - layout, - legendLabel, - reverse, - }) - + legendsWithData.forEach(([legend, data]) => { renderLegendToCanvas(ctx, { ...legend, data, @@ -427,30 +389,28 @@ const InnerBarCanvas = ({ layerContext, layers, layout, - legendData, - legendLabel, - legends, + legendsWithData, margin.left, margin.top, outerHeight, outerWidth, pixelRatio, renderBar, - result.xScale, - result.yScale, + xScale, + yScale, reverse, - shouldRenderLabel, + shouldRenderBarLabel, theme, width, ]) const handleMouseHover = useCallback( (event: React.MouseEvent) => { - if (!result.bars) return + if (!bars) return if (!canvasEl.current) return const [x, y] = getRelativeCursor(canvasEl.current, event) - const bar = findBarUnderCursor(result.bars, margin, x, y) + const bar = findBarUnderCursor(bars, margin, x, y) if (bar !== undefined) { showTooltipFromEvent( @@ -470,39 +430,39 @@ const InnerBarCanvas = ({ hideTooltip() } }, - [hideTooltip, margin, onMouseEnter, result.bars, showTooltipFromEvent, tooltip] + [hideTooltip, margin, onMouseEnter, bars, showTooltipFromEvent, tooltip] ) const handleMouseLeave = useCallback( (event: React.MouseEvent) => { - if (!result.bars) return + if (!bars) return if (!canvasEl.current) return hideTooltip() const [x, y] = getRelativeCursor(canvasEl.current, event) - const bar = findBarUnderCursor(result.bars, margin, x, y) + const bar = findBarUnderCursor(bars, margin, x, y) if (bar) { onMouseLeave?.(bar.data, event) } }, - [hideTooltip, margin, onMouseLeave, result.bars] + [hideTooltip, margin, onMouseLeave, bars] ) const handleClick = useCallback( (event: React.MouseEvent) => { - if (!result.bars) return + if (!bars) return if (!canvasEl.current) return const [x, y] = getRelativeCursor(canvasEl.current, event) - const bar = findBarUnderCursor(result.bars, margin, x, y) + const bar = findBarUnderCursor(bars, margin, x, y) if (bar !== undefined) { onClick?.({ ...bar.data, color: bar.color }, event) } }, - [margin, onClick, result.bars] + [margin, onClick, bars] ) return ( diff --git a/packages/bar/src/BarItem.tsx b/packages/bar/src/BarItem.tsx index 6c4221c396..a84fec3930 100644 --- a/packages/bar/src/BarItem.tsx +++ b/packages/bar/src/BarItem.tsx @@ -1,8 +1,8 @@ -import { BarDatum, BarItemProps } from './types' +import { createElement, MouseEvent, useCallback, useMemo } from 'react' import { animated, to } from '@react-spring/web' -import { createElement, useCallback } from 'react' import { useTheme } from '@nivo/core' import { useTooltip } from '@nivo/tooltip' +import { BarDatum, BarItemProps } from './types' export const BarItem = ({ bar: { data, ...bar }, @@ -31,36 +31,54 @@ export const BarItem = ({ onMouseLeave, tooltip, + + isFocusable, + ariaLabel, + ariaLabelledBy, + ariaDescribedBy, }: BarItemProps) => { const theme = useTheme() - const { showTooltipFromEvent, hideTooltip } = useTooltip() + const { showTooltipFromEvent, showTooltipAt, hideTooltip } = useTooltip() + + const renderTooltip = useMemo(() => () => createElement(tooltip, { ...bar, ...data }), [ + tooltip, + bar, + data, + ]) const handleClick = useCallback( - (event: React.MouseEvent) => { + (event: MouseEvent) => { onClick?.({ color: bar.color, ...data }, event) }, [bar, data, onClick] ) const handleTooltip = useCallback( - (event: React.MouseEvent) => - showTooltipFromEvent(createElement(tooltip, { ...bar, ...data }), event), - [bar, data, showTooltipFromEvent, tooltip] + (event: MouseEvent) => showTooltipFromEvent(renderTooltip(), event), + [showTooltipFromEvent, renderTooltip] ) const handleMouseEnter = useCallback( - (event: React.MouseEvent) => { + (event: MouseEvent) => { onMouseEnter?.(data, event) - showTooltipFromEvent(createElement(tooltip, { ...bar, ...data }), event) + showTooltipFromEvent(renderTooltip(), event) }, - [bar, data, onMouseEnter, showTooltipFromEvent, tooltip] + [data, onMouseEnter, showTooltipFromEvent, renderTooltip] ) const handleMouseLeave = useCallback( - (event: React.MouseEvent) => { + (event: MouseEvent) => { onMouseLeave?.(data, event) hideTooltip() }, [data, hideTooltip, onMouseLeave] ) + // extra handlers to allow keyboard navigation + const handleFocus = useCallback(() => { + showTooltipAt(renderTooltip(), [bar.absX + bar.width / 2, bar.absY]) + }, [showTooltipAt, renderTooltip, bar]) + const handleBlur = useCallback(() => { + hideTooltip() + }, [hideTooltip]) + return ( ({ fill={data.fill ?? color} strokeWidth={borderWidth} stroke={borderColor} + focusable={isFocusable} + tabIndex={isFocusable ? 0 : undefined} + aria-label={ariaLabel ? ariaLabel(data) : undefined} + aria-labelledby={ariaLabelledBy ? ariaLabelledBy(data) : undefined} + aria-describedby={ariaDescribedBy ? ariaDescribedBy(data) : undefined} onMouseEnter={isInteractive ? handleMouseEnter : undefined} onMouseMove={isInteractive ? handleTooltip : undefined} onMouseLeave={isInteractive ? handleMouseLeave : undefined} onClick={isInteractive ? handleClick : undefined} + onFocus={isInteractive && isFocusable ? handleFocus : undefined} + onBlur={isInteractive && isFocusable ? handleBlur : undefined} /> {shouldRenderLabel && ( { +interface BarLegendsProps { width: number height: number - legends: BarCommonProps['legends'] - data: LegendData[] + legends: [BarLegendProps, LegendData[]][] toggleSerie: (id: string | number) => void } -export const BarLegends = ({ - width, - height, - legends, - data, - toggleSerie, -}: BarLegendsProps) => { - return ( - <> - {legends.map((legend, i) => ( - - ))} - - ) -} +export const BarLegends = ({ width, height, legends, toggleSerie }: BarLegendsProps) => ( + <> + {legends.map(([legend, data], i) => ( + + ))} + +) diff --git a/packages/bar/src/compute/grouped.ts b/packages/bar/src/compute/grouped.ts index 9d3d125959..ce7cf216eb 100644 --- a/packages/bar/src/compute/grouped.ts +++ b/packages/bar/src/compute/grouped.ts @@ -1,8 +1,8 @@ -import { BarDatum, BarSvgProps, ComputedBarDatum, ComputedDatum } from '../types' +import { Margin } from '@nivo/core' import { OrdinalColorScale } from '@nivo/colors' -import { Scale, ScaleBand } from '@nivo/scales' +import { Scale, ScaleBand, computeScale } from '@nivo/scales' +import { BarDatum, BarSvgProps, ComputedBarDatum, ComputedDatum } from '../types' import { coerceValue, filterNullValues, getIndexScale, normalizeData } from './common' -import { computeScale } from '@nivo/scales' type Params = { data: RawDatum[] @@ -14,12 +14,12 @@ type Params = { keys: string[] xScale: XScaleInput extends string ? ScaleBand : Scale yScale: YScaleInput extends string ? ScaleBand : Scale + margin: Margin } const gt = (value: number, other: number) => value > other const lt = (value: number, other: number) => value < other -const flatten = (array: T[][]) => ([] as T[]).concat(...array) const range = (start: number, end: number) => Array.from(' '.repeat(end - start), (_, index) => start + index) @@ -40,46 +40,49 @@ const generateVerticalGroupedBars = >( keys, xScale, yScale, + margin, }: Params, barWidth: number, reverse: boolean, yRef: number -) => { +): ComputedBarDatum[] => { const compare = reverse ? lt : gt const getY = (d: number) => (compare(d, 0) ? yScale(d) ?? 0 : yRef) const getHeight = (d: number, y: number) => (compare(d, 0) ? yRef - y : (yScale(d) ?? 0) - yRef) const cleanedData = data.map(filterNullValues) - const bars = flatten( - keys.map((key, i) => - range(0, xScale.domain().length).map(index => { - const [rawValue, value] = coerceValue(data[index][key]) - const indexValue = getIndex(data[index]) - const x = (xScale(indexValue) ?? 0) + barWidth * i + innerPadding * i - const y = getY(value) - const barHeight = getHeight(value, y) - const barData = { - id: key, - value: rawValue === null ? rawValue : value, - formattedValue: formatValue(value), - hidden: false, - index, - indexValue, - data: cleanedData[index], - } - - return { - key: `${key}.${barData.indexValue}`, - data: barData, - x, - y, - width: barWidth, - height: barHeight, - color: getColor(barData), - label: getTooltipLabel(barData), - } + const bars: ComputedBarDatum[] = [] + keys.forEach((key, i) => + range(0, xScale.domain().length).forEach(index => { + const [rawValue, value] = coerceValue(data[index][key]) + const indexValue = getIndex(data[index]) + const x = (xScale(indexValue) ?? 0) + barWidth * i + innerPadding * i + const y = getY(value) + const barHeight = getHeight(value, y) + const barData: ComputedDatum = { + id: key, + value: rawValue === null ? rawValue : value, + formattedValue: formatValue(value), + hidden: false, + index, + indexValue, + data: cleanedData[index], + } + + bars.push({ + key: `${key}.${barData.indexValue}`, + index: bars.length, + data: barData, + x, + y, + absX: margin.left + x, + absY: margin.top + y, + width: barWidth, + height: barHeight, + color: getColor(barData), + label: getTooltipLabel(barData), }) - ) + }) ) return bars @@ -99,46 +102,49 @@ const generateHorizontalGroupedBars = > innerPadding = 0, xScale, yScale, + margin, }: Params, barHeight: number, reverse: boolean, xRef: number -) => { +): ComputedBarDatum[] => { const compare = reverse ? lt : gt const getX = (d: number) => (compare(d, 0) ? xRef : xScale(d) ?? 0) const getWidth = (d: number, x: number) => (compare(d, 0) ? (xScale(d) ?? 0) - xRef : xRef - x) const cleanedData = data.map(filterNullValues) - const bars = flatten( - keys.map((key, i) => - range(0, yScale.domain().length).map(index => { - const [rawValue, value] = coerceValue(data[index][key]) - const indexValue = getIndex(data[index]) - const x = getX(value) - const y = (yScale(indexValue) ?? 0) + barHeight * i + innerPadding * i - const barWidth = getWidth(value, x) - const barData = { - id: key, - value: rawValue === null ? rawValue : value, - formattedValue: formatValue(value), - hidden: false, - index, - indexValue, - data: cleanedData[index], - } - - return { - key: `${key}.${barData.indexValue}`, - data: barData, - x, - y, - width: barWidth, - height: barHeight, - color: getColor(barData), - label: getTooltipLabel(barData), - } + const bars: ComputedBarDatum[] = [] + keys.forEach((key, i) => + range(0, yScale.domain().length).forEach(index => { + const [rawValue, value] = coerceValue(data[index][key]) + const indexValue = getIndex(data[index]) + const x = getX(value) + const y = (yScale(indexValue) ?? 0) + barHeight * i + innerPadding * i + const barWidth = getWidth(value, x) + const barData: ComputedDatum = { + id: key, + value: rawValue === null ? rawValue : value, + formattedValue: formatValue(value), + hidden: false, + index, + indexValue, + data: cleanedData[index], + } + + bars.push({ + key: `${key}.${barData.indexValue}`, + index: bars.length, + data: barData, + x, + y, + absX: margin.left + x, + absY: margin.top + y, + width: barWidth, + height: barHeight, + color: getColor(barData), + label: getTooltipLabel(barData), }) - ) + }) ) return bars @@ -179,6 +185,7 @@ export const generateGroupedBars = ({ getColor: OrdinalColorScale> getIndex: (datum: RawDatum) => string getTooltipLabel: (datum: ComputedDatum) => string + margin: Margin hiddenIds?: string[] }) => { const keys = props.keys.filter(key => !hiddenIds.includes(key)) diff --git a/packages/bar/src/compute/legends.ts b/packages/bar/src/compute/legends.ts index 93d5925d1d..0a4fade185 100644 --- a/packages/bar/src/compute/legends.ts +++ b/packages/bar/src/compute/legends.ts @@ -1,4 +1,11 @@ -import { BarDatum, BarLegendProps, BarSvgProps, BarsWithHidden, LegendLabelDatum } from '../types' +import { + BarDatum, + BarLegendProps, + BarSvgProps, + BarsWithHidden, + LegendData, + LegendLabelDatum, +} from '../types' import { getPropertyAccessor } from '@nivo/core' import { uniqBy } from 'lodash' @@ -9,7 +16,7 @@ export const getLegendDataForKeys = ( groupMode: NonNullable['groupMode']>, reverse: boolean, getLegendLabel: (datum: LegendLabelDatum) => string -) => { +): LegendData[] => { const data = uniqBy( bars.map(bar => ({ id: bar.data.id, @@ -37,7 +44,7 @@ export const getLegendDataForIndexes = ( bars: BarsWithHidden, layout: NonNullable['layout']>, getLegendLabel: (datum: LegendLabelDatum) => string -) => { +): LegendData[] => { const data = uniqBy( bars.map(bar => ({ id: bar.data.indexValue ?? '', diff --git a/packages/bar/src/compute/stacked.ts b/packages/bar/src/compute/stacked.ts index b8aa438b0f..cbf14f1469 100644 --- a/packages/bar/src/compute/stacked.ts +++ b/packages/bar/src/compute/stacked.ts @@ -1,7 +1,8 @@ -import { BarDatum, BarSvgProps, ComputedBarDatum, ComputedDatum } from '../types' +import { Margin } from '@nivo/core' import { OrdinalColorScale } from '@nivo/colors' import { Scale, ScaleBand, computeScale } from '@nivo/scales' import { Series, SeriesPoint, stack, stackOffsetDiverging } from 'd3-shape' +import { BarDatum, BarSvgProps, ComputedBarDatum, ComputedDatum } from '../types' import { coerceValue, filterNullValues, getIndexScale, normalizeData } from './common' type StackDatum = SeriesPoint @@ -15,6 +16,7 @@ type Params = { stackedData: Series[] xScale: XScaleInput extends string ? ScaleBand : Scale yScale: YScaleInput extends string ? ScaleBand : Scale + margin: Margin } const flattenDeep = (arr: T[]): T => @@ -36,44 +38,47 @@ const generateVerticalStackedBars = >( stackedData, xScale, yScale, + margin, }: Params, barWidth: number, reverse: boolean -) => { +): ComputedBarDatum[] => { const getY = (d: StackDatum) => yScale(d[reverse ? 0 : 1]) const getHeight = (d: StackDatum, y: number) => (yScale(d[reverse ? 1 : 0]) ?? 0) - y - const bars = flattenDeep( - stackedData.map(stackedDataItem => - xScale.domain().map((index, i) => { - const d = stackedDataItem[i] - const x = xScale(getIndex(d.data)) ?? 0 - const y = (getY(d) ?? 0) + innerPadding * 0.5 - const barHeight = getHeight(d, y) - innerPadding - const [rawValue, value] = coerceValue(d.data[stackedDataItem.key]) - - const barData = { - id: stackedDataItem.key, - value: rawValue === null ? rawValue : value, - formattedValue: formatValue(value), - hidden: false, - index: i, - indexValue: index, - data: filterNullValues(d.data), - } - - return { - key: `${stackedDataItem.key}.${index}`, - data: barData, - x, - y, - width: barWidth, - height: barHeight, - color: getColor(barData), - label: getTooltipLabel(barData), - } + const bars: ComputedBarDatum[] = [] + stackedData.forEach(stackedDataItem => + xScale.domain().forEach((index, i) => { + const d = stackedDataItem[i] + const x = xScale(getIndex(d.data)) ?? 0 + const y = (getY(d) ?? 0) + innerPadding * 0.5 + const barHeight = getHeight(d, y) - innerPadding + const [rawValue, value] = coerceValue(d.data[stackedDataItem.key]) + + const barData: ComputedDatum = { + id: stackedDataItem.key, + value: rawValue === null ? rawValue : value, + formattedValue: formatValue(value), + hidden: false, + index: i, + indexValue: index, + data: filterNullValues(d.data), + } + + bars.push({ + key: `${stackedDataItem.key}.${index}`, + index: bars.length, + data: barData, + x, + y, + absX: margin.left + x, + absY: margin.top + y, + width: barWidth, + height: barHeight, + color: getColor(barData), + label: getTooltipLabel(barData), }) - ) + }) ) return bars @@ -92,44 +97,47 @@ const generateHorizontalStackedBars = > stackedData, xScale, yScale, + margin, }: Params, barHeight: number, reverse: boolean -) => { +): ComputedBarDatum[] => { const getX = (d: StackDatum) => xScale(d[reverse ? 1 : 0]) const getWidth = (d: StackDatum, x: number) => (xScale(d[reverse ? 0 : 1]) ?? 0) - x - const bars = flattenDeep( - stackedData.map(stackedDataItem => - yScale.domain().map((index, i) => { - const d = stackedDataItem[i] - const y = yScale(getIndex(d.data)) ?? 0 - const x = (getX(d) ?? 0) + innerPadding * 0.5 - const barWidth = getWidth(d, x) - innerPadding - const [rawValue, value] = coerceValue(d.data[stackedDataItem.key]) - - const barData = { - id: stackedDataItem.key, - value: rawValue === null ? rawValue : value, - formattedValue: formatValue(value), - hidden: false, - index: i, - indexValue: index, - data: filterNullValues(d.data), - } - - return { - key: `${stackedDataItem.key}.${index}`, - data: barData, - x, - y, - width: barWidth, - height: barHeight, - color: getColor(barData), - label: getTooltipLabel(barData), - } + const bars: ComputedBarDatum[] = [] + stackedData.forEach(stackedDataItem => + yScale.domain().forEach((index, i) => { + const d = stackedDataItem[i] + const y = yScale(getIndex(d.data)) ?? 0 + const x = (getX(d) ?? 0) + innerPadding * 0.5 + const barWidth = getWidth(d, x) - innerPadding + const [rawValue, value] = coerceValue(d.data[stackedDataItem.key]) + + const barData: ComputedDatum = { + id: stackedDataItem.key, + value: rawValue === null ? rawValue : value, + formattedValue: formatValue(value), + hidden: false, + index: i, + indexValue: index, + data: filterNullValues(d.data), + } + + bars.push({ + key: `${stackedDataItem.key}.${index}`, + index: bars.length, + data: barData, + x, + y, + absX: margin.left + x, + absY: margin.top + y, + width: barWidth, + height: barHeight, + color: getColor(barData), + label: getTooltipLabel(barData), }) - ) + }) ) return bars @@ -170,6 +178,7 @@ export const generateStackedBars = ({ getColor: OrdinalColorScale> getIndex: (datum: RawDatum) => string getTooltipLabel: (datum: ComputedDatum) => string + margin: Margin hiddenIds?: string[] }) => { const keys = props.keys.filter(key => !hiddenIds.includes(key)) diff --git a/packages/bar/src/hooks.ts b/packages/bar/src/hooks.ts new file mode 100644 index 0000000000..2dba5cef8c --- /dev/null +++ b/packages/bar/src/hooks.ts @@ -0,0 +1,187 @@ +import { useCallback, useMemo, useState } from 'react' +import { useInheritedColor, useOrdinalColorScale } from '@nivo/colors' +import { usePropertyAccessor, useTheme, useValueFormatter, Margin } from '@nivo/core' +import { + DataProps, + BarCommonProps, + BarDatum, + ComputedBarDatumWithValue, + LegendData, + BarLegendProps, +} from './types' +import { defaultProps } from './props' +import { generateGroupedBars, generateStackedBars, getLegendData } from './compute' + +export const useBar = ({ + indexBy = defaultProps.indexBy, + keys = defaultProps.keys, + label = defaultProps.label, + tooltipLabel = defaultProps.tooltipLabel, + valueFormat, + colors = defaultProps.colors, + colorBy = defaultProps.colorBy, + borderColor = defaultProps.borderColor, + labelTextColor = defaultProps.labelTextColor, + groupMode = defaultProps.groupMode, + layout = defaultProps.layout, + reverse = defaultProps.reverse, + data, + minValue = defaultProps.minValue, + maxValue = defaultProps.maxValue, + margin, + width, + height, + padding = defaultProps.padding, + innerPadding = defaultProps.innerPadding, + valueScale = defaultProps.valueScale, + indexScale = defaultProps.indexScale, + initialHiddenIds = defaultProps.initialHiddenIds, + enableLabel = defaultProps.enableLabel, + labelSkipWidth = defaultProps.labelSkipWidth, + labelSkipHeight = defaultProps.labelSkipHeight, + legends = defaultProps.legends, + legendLabel, +}: { + indexBy?: BarCommonProps['indexBy'] + label?: BarCommonProps['label'] + tooltipLabel?: BarCommonProps['tooltipLabel'] + valueFormat?: BarCommonProps['valueFormat'] + colors?: BarCommonProps['colors'] + colorBy?: BarCommonProps['colorBy'] + borderColor?: BarCommonProps['borderColor'] + labelTextColor?: BarCommonProps['labelTextColor'] + groupMode?: BarCommonProps['groupMode'] + layout?: BarCommonProps['layout'] + reverse?: BarCommonProps['reverse'] + data: DataProps['data'] + keys?: BarCommonProps['keys'] + minValue?: BarCommonProps['minValue'] + maxValue?: BarCommonProps['maxValue'] + margin: Margin + width: number + height: number + padding?: BarCommonProps['padding'] + innerPadding?: BarCommonProps['innerPadding'] + valueScale?: BarCommonProps['valueScale'] + indexScale?: BarCommonProps['indexScale'] + initialHiddenIds?: BarCommonProps['initialHiddenIds'] + enableLabel?: BarCommonProps['enableLabel'] + labelSkipWidth?: BarCommonProps['labelSkipWidth'] + labelSkipHeight?: BarCommonProps['labelSkipHeight'] + legends?: BarCommonProps['legends'] + legendLabel?: BarCommonProps['legendLabel'] +}) => { + const [hiddenIds, setHiddenIds] = useState(initialHiddenIds ?? []) + const toggleSerie = useCallback(id => { + setHiddenIds(state => + state.indexOf(id) > -1 ? state.filter(item => item !== id) : [...state, id] + ) + }, []) + + const getIndex = usePropertyAccessor(indexBy) + const getLabel = usePropertyAccessor(label) + const getTooltipLabel = usePropertyAccessor(tooltipLabel) + const formatValue = useValueFormatter(valueFormat) + + const theme = useTheme() + const getColor = useOrdinalColorScale(colors, colorBy) + const getBorderColor = useInheritedColor>( + borderColor, + theme + ) + const getLabelColor = useInheritedColor>( + labelTextColor, + theme + ) + + const generateBars = groupMode === 'grouped' ? generateGroupedBars : generateStackedBars + const { bars, xScale, yScale } = generateBars({ + layout, + reverse, + data, + getIndex, + keys, + minValue, + maxValue, + width, + height, + getColor, + padding, + innerPadding, + valueScale, + indexScale, + hiddenIds, + formatValue, + getTooltipLabel, + margin, + }) + + const barsWithValue = useMemo( + () => + bars + .filter( + (bar): bar is ComputedBarDatumWithValue => bar.data.value !== null + ) + .map((bar, index) => ({ + ...bar, + index, + })), + [bars] + ) + + const shouldRenderBarLabel = useCallback( + ({ width, height }: { height: number; width: number }) => { + if (!enableLabel) return false + if (labelSkipWidth > 0 && width < labelSkipWidth) return false + if (labelSkipHeight > 0 && height < labelSkipHeight) return false + return true + }, + [enableLabel, labelSkipWidth, labelSkipHeight] + ) + + const legendData = useMemo( + () => + keys.map(key => { + const bar = bars.find(bar => bar.data.id === key) + + return { ...bar, data: { id: key, ...bar?.data, hidden: hiddenIds.includes(key) } } + }), + [hiddenIds, keys, bars] + ) + + const legendsWithData: [BarLegendProps, LegendData[]][] = useMemo( + () => + legends.map(legend => { + const data = getLegendData({ + bars: legend.dataFrom === 'keys' ? legendData : bars, + direction: legend.direction, + from: legend.dataFrom, + groupMode, + layout, + legendLabel, + reverse, + }) + + return [legend, data] + }), + [legends, legendData, bars, groupMode, layout, legendLabel, reverse] + ) + + return { + bars, + barsWithValue, + xScale, + yScale, + getIndex, + getLabel, + getTooltipLabel, + formatValue, + getColor, + getBorderColor, + getLabelColor, + shouldRenderBarLabel, + hiddenIds, + toggleSerie, + legendsWithData, + } +} diff --git a/packages/bar/src/props.ts b/packages/bar/src/props.ts index a0176968fb..265b9cf077 100644 --- a/packages/bar/src/props.ts +++ b/packages/bar/src/props.ts @@ -44,7 +44,7 @@ export const defaultProps = { tooltipLabel: (datum: ComputedDatum) => `${datum.id} - ${datum.indexValue}`, legends: [], - + initialHiddenIds: [], annotations: [], } @@ -60,6 +60,7 @@ export const svgDefaultProps = { motionConfig: 'default', role: 'img', + isFocusable: false, } export const canvasDefaultProps = { diff --git a/packages/bar/src/types.ts b/packages/bar/src/types.ts index f08888dbbc..05c7459766 100644 --- a/packages/bar/src/types.ts +++ b/packages/bar/src/types.ts @@ -48,9 +48,12 @@ export type ComputedBarDatumWithValue = ComputedBarDatum & { export type ComputedBarDatum = { key: string + index: number data: ComputedDatum x: number y: number + absX: number + absY: number width: number height: number color: string @@ -131,7 +134,7 @@ export type BarCanvasLayer = | BarCanvasCustomLayer export type BarLayer = BarLayerId | BarCustomLayer -export interface BarItemProps +export interface BarItemProps extends Pick< BarCommonProps, 'borderRadius' | 'borderWidth' | 'isInteractive' | 'tooltip' @@ -158,11 +161,22 @@ export interface BarItemProps label: string shouldRenderLabel: boolean + + isFocusable: boolean + ariaLabel?: BarSvgProps['barAriaLabel'] + ariaLabelledBy?: BarSvgProps['barAriaLabelledBy'] + ariaDescribedBy?: BarSvgProps['barAriaDescribedBy'] } -export type RenderBarProps = Omit< +export type RenderBarProps = Omit< BarItemProps, - 'isInteractive' | 'style' | 'tooltip' + | 'isInteractive' + | 'style' + | 'tooltip' + | 'isFocusable' + | 'ariaLabel' + | 'ariaLabelledBy' + | 'ariaDescribedBy' > & { borderColor: string labelColor: string @@ -234,6 +248,8 @@ export type BarCommonProps = { legends: BarLegendProps[] renderWrapper?: boolean + + initialHiddenIds: string[] } export type BarSvgProps = Partial> & @@ -252,9 +268,20 @@ export type BarSvgProps = Partial[] + role: string + ariaLabel?: React.AriaAttributes['aria-label'] + ariaLabelledBy?: React.AriaAttributes['aria-labelledby'] + ariaDescribedBy?: React.AriaAttributes['aria-describedby'] + isFocusable?: boolean + barAriaLabel?: (data: ComputedDatum) => React.AriaAttributes['aria-label'] + barAriaLabelledBy?: ( + data: ComputedDatum + ) => React.AriaAttributes['aria-labelledby'] + barAriaDescribedBy?: ( + data: ComputedDatum + ) => React.AriaAttributes['aria-describedby'] }> export type BarCanvasProps = Partial> & diff --git a/packages/bar/tests/Bar.test.tsx b/packages/bar/tests/Bar.test.tsx index 59a6000c1a..e7efa96fb3 100644 --- a/packages/bar/tests/Bar.test.tsx +++ b/packages/bar/tests/Bar.test.tsx @@ -393,10 +393,13 @@ it(`should generate grouped bars correctly when keys are mismatched`, () => { }, height: 300, key: 'A.one', + index: 0, label: 'A - one', width: 71.33333333333333, x: 24, y: 0, + absX: 24, + absY: 0, }) expect(bars.at(1).prop('bar')).toEqual({ @@ -412,10 +415,13 @@ it(`should generate grouped bars correctly when keys are mismatched`, () => { }, height: 270, key: 'B.two', + index: 1, label: 'B - two', width: 71.33333333333333, x: 333.3333333333333, y: 30, + absX: 333.3333333333333, + absY: 30, }) expect(bars.at(2).prop('bar')).toEqual({ @@ -431,10 +437,13 @@ it(`should generate grouped bars correctly when keys are mismatched`, () => { }, height: 90, key: 'C.one', + index: 2, label: 'C - one', width: 71.33333333333333, x: 166.66666666666666, y: 210, + absX: 166.66666666666666, + absY: 210, }) }) @@ -469,10 +478,13 @@ it(`should generate stacked bars correctly when keys are mismatched`, () => { }, height: 231, key: 'A.one', + index: 0, label: 'A - one', width: 214, x: 24, y: 69, + absX: 24, + absY: 69, }) expect(bars.at(1).prop('bar')).toEqual({ @@ -488,10 +500,13 @@ it(`should generate stacked bars correctly when keys are mismatched`, () => { }, height: 208, key: 'B.two', + index: 1, label: 'B - two', width: 214, x: 262, y: 92, + absX: 262, + absY: 92, }) expect(bars.at(2).prop('bar')).toEqual({ @@ -507,10 +522,13 @@ it(`should generate stacked bars correctly when keys are mismatched`, () => { }, height: 69, key: 'C.one', + index: 2, label: 'C - one', width: 214, x: 24, y: 0, + absX: 24, + absY: 0, }) }) @@ -630,3 +648,56 @@ describe('tooltip', () => { expect(wrapper.find(CustomTooltip).exists()).toBeTruthy() }) }) + +describe('accessibility', () => { + it('should forward root aria properties to the SVG element', () => { + const wrapper = mount( + + ) + + const svg = wrapper.find('svg') + + expect(svg.prop('aria-label')).toBe('AriaLabel') + expect(svg.prop('aria-labelledby')).toBe('AriaLabelledBy') + expect(svg.prop('aria-describedby')).toBe('AriaDescribedBy') + }) + + it('should add an aria attributes to bars', () => { + const wrapper = mount( + 'BarAriaLabel'} + barAriaLabelledBy={() => 'BarAriaLabelledBy'} + barAriaDescribedBy={() => 'BarAriaDescribedBy'} + /> + ) + + wrapper + .find('BarItem') + .find('rect') + .forEach(bar => { + expect(bar.prop('aria-label')).toBe('BarAriaLabel') + expect(bar.prop('aria-labelledby')).toBe('BarAriaLabelledBy') + expect(bar.prop('aria-describedby')).toBe('BarAriaDescribedBy') + }) + }) +}) diff --git a/packages/core/index.d.ts b/packages/core/index.d.ts index 32ac58cdbe..4eedf61065 100644 --- a/packages/core/index.d.ts +++ b/packages/core/index.d.ts @@ -348,6 +348,10 @@ declare module '@nivo/core' { margin: Margin defs?: any role?: string + ariaLabel?: React.AriaAttributes['aria-label'] + ariaLabelledBy?: React.AriaAttributes['aria-labelledby'] + ariaDescribedBy?: React.AriaAttributes['aria-describedby'] + isFocusable?: boolean }> ) => JSX.Element export const SvgWrapper: SvgWrapperType diff --git a/packages/core/src/components/SvgWrapper.js b/packages/core/src/components/SvgWrapper.js index a5683bb333..99e9e12b5a 100644 --- a/packages/core/src/components/SvgWrapper.js +++ b/packages/core/src/components/SvgWrapper.js @@ -1,20 +1,33 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import PropTypes from 'prop-types' import { Defs } from './defs' import { useTheme } from '../theming' -const SvgWrapper = ({ width, height, margin, defs, children, role }) => { +const SvgWrapper = ({ + width, + height, + margin, + defs, + children, + role, + ariaLabel, + ariaLabelledBy, + ariaDescribedBy, + isFocusable, +}) => { const theme = useTheme() return ( - + {children} @@ -32,6 +45,10 @@ SvgWrapper.propTypes = { defs: PropTypes.array, children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired, role: PropTypes.string, + isFocusable: PropTypes.bool, + ariaLabel: PropTypes.string, + ariaLabelledBy: PropTypes.string, + ariaDescribedBy: PropTypes.string, } export default SvgWrapper diff --git a/packages/core/src/components/defs/Defs.js b/packages/core/src/components/defs/Defs.js index b1e8810ca3..40f677c22b 100644 --- a/packages/core/src/components/defs/Defs.js +++ b/packages/core/src/components/defs/Defs.js @@ -20,7 +20,7 @@ const Defs = ({ defs: definitions }) => { if (!definitions || definitions.length < 1) return null return ( - + {definitions.map(({ type, ...def }) => { if (defsMapping[type]) return createElement(defsMapping[type], { key: def.id, ...def }) diff --git a/website/src/components/Markdown.js b/website/src/components/Markdown.js index 07f4f66d20..a148077d28 100644 --- a/website/src/components/Markdown.js +++ b/website/src/components/Markdown.js @@ -1,11 +1,3 @@ -/* - * This file is part of the nivo project. - * - * (c) 2016 Raphaël Benitte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import React, { memo } from 'react' import PropTypes from 'prop-types' import { Link } from 'gatsby' @@ -26,7 +18,7 @@ const linkRenderer = ({ href, children }) => { } return ( - + {children} ) diff --git a/website/src/components/components/ComponentTabs.js b/website/src/components/components/ComponentTabs.js index 43b30e72f4..42a30bcd0e 100644 --- a/website/src/components/components/ComponentTabs.js +++ b/website/src/components/components/ComponentTabs.js @@ -1,11 +1,3 @@ -/* - * This file is part of the nivo project. - * - * (c) 2016 Raphaël Benitte - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ import React, { useState } from 'react' import PropTypes from 'prop-types' import styled from 'styled-components' @@ -38,10 +30,14 @@ const ComponentTabs = ({ let content if (currentTab === 'chart') { - content = {children} + content = ( + + {children} + + ) } else if (currentTab === 'code') { content = ( - + ) @@ -55,8 +51,8 @@ const ComponentTabs = ({ return ( -