From 0fdd6854cc03a221022facbce2754fdd263f51a1 Mon Sep 17 00:00:00 2001 From: plouc Date: Sun, 2 Jan 2022 23:49:51 +0900 Subject: [PATCH] feat(treemap): datum now just extends object --- packages/core/package.json | 1 - packages/core/src/props/index.js | 1 - packages/core/src/props/treeMap.js | 24 - packages/static/src/mappings/treemap.ts | 4 +- packages/treemap/src/ResponsiveTreeMap.tsx | 4 +- .../treemap/src/ResponsiveTreeMapCanvas.tsx | 4 +- .../treemap/src/ResponsiveTreeMapHtml.tsx | 4 +- packages/treemap/src/TreeMap.tsx | 24 +- packages/treemap/src/TreeMapCanvas.tsx | 27 +- packages/treemap/src/TreeMapHtml.tsx | 16 +- packages/treemap/src/TreeMapHtmlNode.tsx | 12 +- packages/treemap/src/TreeMapNode.tsx | 4 +- packages/treemap/src/TreeMapNodeTooltip.tsx | 6 +- packages/treemap/src/TreeMapNodes.tsx | 18 +- packages/treemap/src/{hooks.tsx => hooks.ts} | 54 +- packages/treemap/src/index.ts | 1 + packages/treemap/src/tiling.ts | 17 + packages/treemap/src/transitions.ts | 10 +- packages/treemap/src/types.ts | 98 ++-- ...treemap.stories.js => treemap.stories.tsx} | 1 + ...tml.stories.js => treemapHtml.stories.tsx} | 1 + packages/treemap/tests/.eslintrc.yml | 5 + packages/treemap/tests/TreeMap.test.tsx | 534 ++++++++++++++++++ website/src/data/components/treemap/props.ts | 17 +- website/src/pages/treemap/canvas.tsx | 4 +- website/src/pages/treemap/html.tsx | 4 +- website/src/pages/treemap/index.tsx | 4 +- 27 files changed, 699 insertions(+), 200 deletions(-) delete mode 100644 packages/core/src/props/treeMap.js rename packages/treemap/src/{hooks.tsx => hooks.ts} (90%) create mode 100644 packages/treemap/src/tiling.ts rename packages/treemap/stories/{treemap.stories.js => treemap.stories.tsx} (99%) rename packages/treemap/stories/{treemapHtml.stories.js => treemapHtml.stories.tsx} (98%) create mode 100644 packages/treemap/tests/.eslintrc.yml create mode 100644 packages/treemap/tests/TreeMap.test.tsx diff --git a/packages/core/package.json b/packages/core/package.json index 9eab3cad01..75635ea27d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -24,7 +24,6 @@ "@react-spring/web": "9.3.1", "d3-color": "^2.0.0", "d3-format": "^1.4.4", - "d3-hierarchy": "^1.1.8", "d3-interpolate": "^2.0.1", "d3-scale": "^3.2.3", "d3-scale-chromatic": "^2.0.0", diff --git a/packages/core/src/props/index.js b/packages/core/src/props/index.js index 9c0a2c8798..207a9de463 100644 --- a/packages/core/src/props/index.js +++ b/packages/core/src/props/index.js @@ -32,4 +32,3 @@ export * from './colors' export * from './curve' export * from './defs' export * from './stack' -export * from './treeMap' diff --git a/packages/core/src/props/treeMap.js b/packages/core/src/props/treeMap.js deleted file mode 100644 index 1d101b830e..0000000000 --- a/packages/core/src/props/treeMap.js +++ /dev/null @@ -1,24 +0,0 @@ -import PropTypes from 'prop-types' -import { - treemapBinary, - treemapDice, - treemapSlice, - treemapSliceDice, - treemapSquarify, - treemapResquarify, -} from 'd3-hierarchy' - -export const treeMapTilePropMapping = { - binary: treemapBinary, - dice: treemapDice, - slice: treemapSlice, - sliceDice: treemapSliceDice, - squarify: treemapSquarify, - resquarify: treemapResquarify, -} - -export const treeMapTilePropKeys = Object.keys(treeMapTilePropMapping) - -export const treeMapTilePropType = PropTypes.oneOf(treeMapTilePropKeys) - -export const treeMapTileFromProp = prop => treeMapTilePropMapping[prop] diff --git a/packages/static/src/mappings/treemap.ts b/packages/static/src/mappings/treemap.ts index f62d851759..3191c0b860 100644 --- a/packages/static/src/mappings/treemap.ts +++ b/packages/static/src/mappings/treemap.ts @@ -1,13 +1,13 @@ import { FunctionComponent } from 'react' import Joi from 'joi' import { Dimensions } from '@nivo/core' -import { TreeMap, TreeMapSvgProps, DefaultTreeMapDatum, TreeMapDatum } from '@nivo/treemap' +import { TreeMap, TreeMapSvgProps, DefaultTreeMapDatum } from '@nivo/treemap' import { custom } from './common' import { ordinalColors, inheritedColor } from './commons/colors' import { dimensions } from './commons/dimensions' import { OmitStrict } from '../types' -export type TreeMapApiProps = OmitStrict< +export type TreeMapApiProps = OmitStrict< TreeMapSvgProps & Dimensions, | 'isInteractive' | 'onMouseEnter' diff --git a/packages/treemap/src/ResponsiveTreeMap.tsx b/packages/treemap/src/ResponsiveTreeMap.tsx index e36afe73fd..797406cffb 100644 --- a/packages/treemap/src/ResponsiveTreeMap.tsx +++ b/packages/treemap/src/ResponsiveTreeMap.tsx @@ -1,8 +1,8 @@ import { ResponsiveWrapper } from '@nivo/core' -import { DefaultTreeMapDatum, TreeMapDatum, TreeMapSvgProps } from './types' +import { DefaultTreeMapDatum, TreeMapSvgProps } from './types' import { TreeMap } from './TreeMap' -export const ResponsiveTreeMap = ( +export const ResponsiveTreeMap = ( props: Omit, 'height' | 'width'> ) => ( diff --git a/packages/treemap/src/ResponsiveTreeMapCanvas.tsx b/packages/treemap/src/ResponsiveTreeMapCanvas.tsx index 9cad9db068..acdcda6def 100644 --- a/packages/treemap/src/ResponsiveTreeMapCanvas.tsx +++ b/packages/treemap/src/ResponsiveTreeMapCanvas.tsx @@ -1,8 +1,8 @@ import { ResponsiveWrapper } from '@nivo/core' -import { DefaultTreeMapDatum, TreeMapDatum, TreeMapCanvasProps } from './types' +import { DefaultTreeMapDatum, TreeMapCanvasProps } from './types' import { TreeMapCanvas } from './TreeMapCanvas' -export const ResponsiveTreeMapCanvas = ( +export const ResponsiveTreeMapCanvas = ( props: Omit, 'height' | 'width'> ) => ( diff --git a/packages/treemap/src/ResponsiveTreeMapHtml.tsx b/packages/treemap/src/ResponsiveTreeMapHtml.tsx index 9b5aa94c1f..1e96bdf957 100644 --- a/packages/treemap/src/ResponsiveTreeMapHtml.tsx +++ b/packages/treemap/src/ResponsiveTreeMapHtml.tsx @@ -1,8 +1,8 @@ import { ResponsiveWrapper } from '@nivo/core' -import { DefaultTreeMapDatum, TreeMapDatum, TreeMapHtmlProps } from './types' +import { DefaultTreeMapDatum, TreeMapHtmlProps } from './types' import { TreeMapHtml } from './TreeMapHtml' -export const ResponsiveTreeMapHtml = ( +export const ResponsiveTreeMapHtml = ( props: Omit, 'height' | 'width'> ) => ( diff --git a/packages/treemap/src/TreeMap.tsx b/packages/treemap/src/TreeMap.tsx index 3a3cda62ed..e02ef69acc 100644 --- a/packages/treemap/src/TreeMap.tsx +++ b/packages/treemap/src/TreeMap.tsx @@ -7,27 +7,21 @@ import { } from '@nivo/core' import { useTreeMap } from './hooks' import { TreeMapNodes } from './TreeMapNodes' -import { - DefaultTreeMapDatum, - NodeComponent, - TreeMapCommonProps, - TreeMapDatum, - TreeMapSvgProps, -} from './types' +import { DefaultTreeMapDatum, NodeComponent, TreeMapCommonProps, TreeMapSvgProps } from './types' import { svgDefaultProps } from './defaults' -type InnerTreeMapProps = Omit< +type InnerTreeMapProps = Omit< TreeMapSvgProps, 'animate' | 'motionConfig' | 'renderWrapper' | 'theme' > -const InnerTreeMap = ({ +const InnerTreeMap = ({ data, identity = svgDefaultProps.identity as TreeMapCommonProps['identity'], value = svgDefaultProps.value as TreeMapCommonProps['value'], valueFormat, tile = svgDefaultProps.tile, - nodeComponent = svgDefaultProps.nodeComponent as unknown as NodeComponent, + nodeComponent = svgDefaultProps.nodeComponent as NodeComponent, innerPadding = svgDefaultProps.innerPadding, outerPadding = svgDefaultProps.outerPadding, leavesOnly = svgDefaultProps.leavesOnly, @@ -35,7 +29,7 @@ const InnerTreeMap = ({ height, margin: partialMargin, colors = svgDefaultProps.colors as TreeMapCommonProps['colors'], - colorBy = svgDefaultProps.colorBy, + colorBy = svgDefaultProps.colorBy as TreeMapCommonProps['colorBy'], nodeOpacity = svgDefaultProps.nodeOpacity, borderWidth = svgDefaultProps.borderWidth, borderColor = svgDefaultProps.borderColor as TreeMapCommonProps['borderColor'], @@ -43,7 +37,7 @@ const InnerTreeMap = ({ fill = svgDefaultProps.fill, enableLabel = svgDefaultProps.enableLabel, label = svgDefaultProps.label as TreeMapCommonProps['label'], - labelTextColor = svgDefaultProps.labelTextColor, + labelTextColor = svgDefaultProps.labelTextColor as TreeMapCommonProps['labelTextColor'], orientLabel = svgDefaultProps.orientLabel, labelSkipSize = svgDefaultProps.labelSkipSize, enableParentLabel = svgDefaultProps.enableParentLabel, @@ -51,13 +45,13 @@ const InnerTreeMap = ({ parentLabelSize = svgDefaultProps.parentLabelSize, parentLabelPosition = svgDefaultProps.parentLabelPosition, parentLabelPadding = svgDefaultProps.parentLabelPadding, - parentLabelTextColor = svgDefaultProps.parentLabelTextColor, + parentLabelTextColor = svgDefaultProps.parentLabelTextColor as TreeMapCommonProps['parentLabelTextColor'], isInteractive = svgDefaultProps.isInteractive, onMouseEnter, onMouseMove, onMouseLeave, onClick, - tooltip = svgDefaultProps.tooltip as unknown as TreeMapCommonProps['tooltip'], + tooltip = svgDefaultProps.tooltip as TreeMapCommonProps['tooltip'], role, ariaLabel, ariaLabelledBy, @@ -126,7 +120,7 @@ const InnerTreeMap = ({ ) } -export const TreeMap = ({ +export const TreeMap = ({ isInteractive = svgDefaultProps.isInteractive, animate = svgDefaultProps.animate, motionConfig = svgDefaultProps.motionConfig, diff --git a/packages/treemap/src/TreeMapCanvas.tsx b/packages/treemap/src/TreeMapCanvas.tsx index 4f8ad722a5..a6750f2112 100644 --- a/packages/treemap/src/TreeMapCanvas.tsx +++ b/packages/treemap/src/TreeMapCanvas.tsx @@ -10,16 +10,10 @@ import { } from '@nivo/core' import { useTooltip } from '@nivo/tooltip' import { useTreeMap } from './hooks' -import { - ComputedNode, - DefaultTreeMapDatum, - TreeMapCanvasProps, - TreeMapCommonProps, - TreeMapDatum, -} from './types' +import { ComputedNode, DefaultTreeMapDatum, TreeMapCanvasProps, TreeMapCommonProps } from './types' import { canvasDefaultProps } from './defaults' -const findNodeUnderCursor = ( +const findNodeUnderCursor = ( nodes: ComputedNode[], margin: Margin, x: number, @@ -29,12 +23,12 @@ const findNodeUnderCursor = ( isCursorInRect(node.x + margin.left, node.y + margin.top, node.width, node.height, x, y) ) -type InnerTreeMapCanvasProps = Omit< +type InnerTreeMapCanvasProps = Omit< TreeMapCanvasProps, 'renderWrapper' | 'theme' > -const InnerTreeMapCanvas = ({ +const InnerTreeMapCanvas = ({ data, identity = canvasDefaultProps.identity as TreeMapCommonProps['identity'], value = canvasDefaultProps.identity as TreeMapCommonProps['value'], @@ -47,19 +41,19 @@ const InnerTreeMapCanvas = ({ height, margin: partialMargin, colors = canvasDefaultProps.colors as TreeMapCommonProps['colors'], - colorBy = canvasDefaultProps.colorBy, + colorBy = canvasDefaultProps.colorBy as TreeMapCommonProps['colorBy'], nodeOpacity = canvasDefaultProps.nodeOpacity, borderWidth = canvasDefaultProps.borderWidth, borderColor = canvasDefaultProps.borderColor as TreeMapCommonProps['borderColor'], enableLabel = canvasDefaultProps.enableLabel, label = canvasDefaultProps.label as TreeMapCommonProps['label'], - labelTextColor = canvasDefaultProps.labelTextColor, + labelTextColor = canvasDefaultProps.labelTextColor as TreeMapCommonProps['labelTextColor'], orientLabel = canvasDefaultProps.orientLabel, labelSkipSize = canvasDefaultProps.labelSkipSize, isInteractive = canvasDefaultProps.isInteractive, onMouseMove, onClick, - tooltip = canvasDefaultProps.tooltip as unknown as TreeMapCommonProps['tooltip'], + tooltip = canvasDefaultProps.tooltip as TreeMapCommonProps['tooltip'], pixelRatio = canvasDefaultProps.pixelRatio, role, ariaLabel, @@ -100,11 +94,12 @@ const InnerTreeMapCanvas = ({ useEffect(() => { if (canvasEl.current === null) return + const ctx = canvasEl.current.getContext('2d') + if (ctx === null) return + canvasEl.current.width = outerWidth * pixelRatio canvasEl.current.height = outerHeight * pixelRatio - const ctx = canvasEl.current.getContext('2d')! - ctx.scale(pixelRatio, pixelRatio) ctx.fillStyle = theme.background @@ -220,7 +215,7 @@ const InnerTreeMapCanvas = ({ ) } -export const TreeMapCanvas = ({ +export const TreeMapCanvas = ({ theme, isInteractive = canvasDefaultProps.isInteractive, animate = canvasDefaultProps.animate, diff --git a/packages/treemap/src/TreeMapHtml.tsx b/packages/treemap/src/TreeMapHtml.tsx index 55f74c7ecd..8e86e876b6 100644 --- a/packages/treemap/src/TreeMapHtml.tsx +++ b/packages/treemap/src/TreeMapHtml.tsx @@ -1,15 +1,15 @@ import { Container, useDimensions } from '@nivo/core' import { useTreeMap } from './hooks' import { TreeMapNodes } from './TreeMapNodes' -import { DefaultTreeMapDatum, TreeMapCommonProps, TreeMapDatum, TreeMapHtmlProps } from './types' +import { DefaultTreeMapDatum, TreeMapCommonProps, TreeMapHtmlProps } from './types' import { htmlDefaultProps } from './defaults' -type InnerTreeMapHtmlProps = Omit< +type InnerTreeMapHtmlProps = Omit< TreeMapHtmlProps, 'animate' | 'motionConfig' | 'renderWrapper' | 'theme' > -const InnerTreeMapHtml = ({ +const InnerTreeMapHtml = ({ data, identity = htmlDefaultProps.identity as TreeMapCommonProps['identity'], value = htmlDefaultProps.value as TreeMapCommonProps['value'], @@ -23,13 +23,13 @@ const InnerTreeMapHtml = ({ height, margin: partialMargin, colors = htmlDefaultProps.colors as TreeMapCommonProps['colors'], - colorBy = htmlDefaultProps.colorBy, + colorBy = htmlDefaultProps.colorBy as TreeMapCommonProps['colorBy'], nodeOpacity = htmlDefaultProps.nodeOpacity, borderWidth = htmlDefaultProps.borderWidth, borderColor = htmlDefaultProps.borderColor as TreeMapCommonProps['borderColor'], enableLabel = htmlDefaultProps.enableLabel, label = htmlDefaultProps.label as TreeMapCommonProps['label'], - labelTextColor = htmlDefaultProps.labelTextColor, + labelTextColor = htmlDefaultProps.labelTextColor as TreeMapCommonProps['labelTextColor'], orientLabel = htmlDefaultProps.orientLabel, labelSkipSize = htmlDefaultProps.labelSkipSize, enableParentLabel = htmlDefaultProps.enableParentLabel, @@ -37,13 +37,13 @@ const InnerTreeMapHtml = ({ parentLabelSize = htmlDefaultProps.parentLabelSize, parentLabelPosition = htmlDefaultProps.parentLabelPosition, parentLabelPadding = htmlDefaultProps.parentLabelPadding, - parentLabelTextColor = htmlDefaultProps.parentLabelTextColor, + parentLabelTextColor = htmlDefaultProps.parentLabelTextColor as TreeMapCommonProps['parentLabelTextColor'], isInteractive = htmlDefaultProps.isInteractive, onMouseEnter, onMouseMove, onMouseLeave, onClick, - tooltip = htmlDefaultProps.tooltip as unknown as TreeMapCommonProps['tooltip'], + tooltip = htmlDefaultProps.tooltip as TreeMapCommonProps['tooltip'], role, ariaLabel, ariaLabelledBy, @@ -113,7 +113,7 @@ const InnerTreeMapHtml = ({ ) } -export const TreeMapHtml = ({ +export const TreeMapHtml = ({ isInteractive = htmlDefaultProps.isInteractive, animate = htmlDefaultProps.animate, motionConfig = htmlDefaultProps.motionConfig, diff --git a/packages/treemap/src/TreeMapHtmlNode.tsx b/packages/treemap/src/TreeMapHtmlNode.tsx index d652e65555..184b862e75 100644 --- a/packages/treemap/src/TreeMapHtmlNode.tsx +++ b/packages/treemap/src/TreeMapHtmlNode.tsx @@ -1,18 +1,10 @@ import { memo } from 'react' import { animated } from '@react-spring/web' import { useTheme } from '@nivo/core' -import { NodeProps, TreeMapDatum } from './types' +import { NodeProps } from './types' import { htmlNodeTransform, htmlLabelTransform } from './transitions' -/* -parentLabelHtmlTransform: `translate(${ - node.parentLabelX - (node.parentLabelRotation === 0 ? 0 : 5) -}px,${node.parentLabelY - (node.parentLabelRotation === 0 ? 5 : 0)}px) rotate(${ - node.parentLabelRotation -}deg)`, -*/ - -const NonMemoizedTreeMapHtmlNode = ({ +const NonMemoizedTreeMapHtmlNode = ({ node, animatedProps, borderWidth, diff --git a/packages/treemap/src/TreeMapNode.tsx b/packages/treemap/src/TreeMapNode.tsx index b0b54d23e9..35a6fa8a9d 100644 --- a/packages/treemap/src/TreeMapNode.tsx +++ b/packages/treemap/src/TreeMapNode.tsx @@ -1,10 +1,10 @@ import { memo } from 'react' import { animated, to } from '@react-spring/web' import { useTheme } from '@nivo/core' -import { NodeProps, TreeMapDatum } from './types' +import { NodeProps } from './types' import { svgNodeTransform, svgLabelTransform } from './transitions' -const NonMemoizedTreeMapNode = ({ +const NonMemoizedTreeMapNode = ({ node, animatedProps, borderWidth, diff --git a/packages/treemap/src/TreeMapNodeTooltip.tsx b/packages/treemap/src/TreeMapNodeTooltip.tsx index 095cf07eb0..7d07cb3f27 100644 --- a/packages/treemap/src/TreeMapNodeTooltip.tsx +++ b/packages/treemap/src/TreeMapNodeTooltip.tsx @@ -1,10 +1,8 @@ import { memo } from 'react' import { BasicTooltip } from '@nivo/tooltip' -import { TreeMapDatum, TooltipProps } from './types' +import { TooltipProps } from './types' -const NonMemoizedTreeMapNodeTooltip = ({ - node, -}: TooltipProps) => ( +const NonMemoizedTreeMapNodeTooltip = ({ node }: TooltipProps) => ( ) diff --git a/packages/treemap/src/TreeMapNodes.tsx b/packages/treemap/src/TreeMapNodes.tsx index 8d7be12220..4664595adc 100644 --- a/packages/treemap/src/TreeMapNodes.tsx +++ b/packages/treemap/src/TreeMapNodes.tsx @@ -4,15 +4,13 @@ import { useMotionConfig } from '@nivo/core' import { useInteractiveTreeMapNodes } from './hooks' import { ComputedNode, - NodeMouseEventHandler, TreeMapCommonProps, - TreeMapDatum, NodeAnimatedProps, NodeComponent, ComputedNodeWithHandlers, } from './types' -const getAnimatedNodeProps = ( +const getAnimatedNodeProps = ( node: ComputedNodeWithHandlers ): NodeAnimatedProps => ({ x: node.x, @@ -30,7 +28,7 @@ const getAnimatedNodeProps = ( parentLabelOpacity: 1, }) -const getEndingAnimatedNodeProps = ( +const getEndingAnimatedNodeProps = ( node: ComputedNodeWithHandlers ): NodeAnimatedProps => { const x = node.x + node.width / 2 @@ -53,7 +51,7 @@ const getEndingAnimatedNodeProps = ( } } -interface TreeMapNodesProps { +interface TreeMapNodesProps { nodes: ComputedNode[] nodeComponent: NodeComponent borderWidth: TreeMapCommonProps['borderWidth'] @@ -61,14 +59,14 @@ interface TreeMapNodesProps { labelSkipSize: TreeMapCommonProps['labelSkipSize'] enableParentLabel: TreeMapCommonProps['enableParentLabel'] isInteractive: TreeMapCommonProps['isInteractive'] - onMouseEnter?: NodeMouseEventHandler - onMouseMove?: NodeMouseEventHandler - onMouseLeave?: NodeMouseEventHandler - onClick?: NodeMouseEventHandler + onMouseEnter?: TreeMapCommonProps['onMouseEnter'] + onMouseMove?: TreeMapCommonProps['onMouseMove'] + onMouseLeave?: TreeMapCommonProps['onMouseLeave'] + onClick?: TreeMapCommonProps['onClick'] tooltip: TreeMapCommonProps['tooltip'] } -const NonMemoizedTreeMapNodes = ({ +const NonMemoizedTreeMapNodes = ({ nodes, nodeComponent, borderWidth, diff --git a/packages/treemap/src/hooks.tsx b/packages/treemap/src/hooks.ts similarity index 90% rename from packages/treemap/src/hooks.tsx rename to packages/treemap/src/hooks.ts index a4055c3e42..0d0378e5e7 100644 --- a/packages/treemap/src/hooks.tsx +++ b/packages/treemap/src/hooks.ts @@ -8,29 +8,21 @@ import { HierarchyNode, HierarchyRectangularNode, } from 'd3-hierarchy' -import { - // @ts-ignore - treeMapTileFromProp, - useTheme, - useValueFormatter, - PropertyAccessor, - usePropertyAccessor, -} from '@nivo/core' +import { useTheme, useValueFormatter, PropertyAccessor, usePropertyAccessor } from '@nivo/core' import { useOrdinalColorScale, useInheritedColor } from '@nivo/colors' import { useTooltip } from '@nivo/tooltip' import { commonDefaultProps } from './defaults' import { DefaultTreeMapDatum, - NodeMouseEventHandler, TreeMapCommonProps, TreeMapDataProps, - TreeMapDatum, ComputedNode, ComputedNodeWithoutStyles, ComputedNodeWithHandlers, } from './types' +import { tileByType } from './tiling' -export const useTreeMapLayout = ({ +export const useTreeMapLayout = ({ width, height, tile, @@ -54,7 +46,7 @@ export const useTreeMapLayout = ({ useMemo(() => { const treemap = d3Treemap() .size([width, height]) - .tile(treeMapTileFromProp(tile)) + .tile(tileByType[tile]) .round(true) .paddingInner(innerPadding) .paddingOuter(outerPadding) @@ -78,7 +70,7 @@ export const useTreeMapLayout = ({ leavesOnly, ]) -export const useHierarchy = ({ +export const useHierarchy = ({ root, getValue, }: { @@ -86,7 +78,7 @@ export const useHierarchy = ({ getValue: (datum: Datum) => number }) => useMemo(() => hierarchy(root).sum(getValue), [root, getValue]) -const computeNodeIdAndPath = ( +const computeNodeIdAndPath = ( node: HierarchyNode, getIdentity: (node: Datum) => string ) => { @@ -98,7 +90,7 @@ const computeNodeIdAndPath = ( return { path: path.join('.'), pathComponents: path } } -export const useTreeMap = ({ +export const useTreeMap = ({ data, width, height, @@ -117,11 +109,11 @@ export const useTreeMap = ({ parentLabelPosition = commonDefaultProps.parentLabelPosition, parentLabelPadding = commonDefaultProps.parentLabelPadding, colors = commonDefaultProps.colors as TreeMapCommonProps['colors'], - colorBy = commonDefaultProps.colorBy, + colorBy = commonDefaultProps.colorBy as TreeMapCommonProps['colorBy'], nodeOpacity = commonDefaultProps.nodeOpacity, borderColor = commonDefaultProps.borderColor as TreeMapCommonProps['borderColor'], - labelTextColor = commonDefaultProps.labelTextColor, - parentLabelTextColor = commonDefaultProps.parentLabelTextColor, + labelTextColor = commonDefaultProps.labelTextColor as TreeMapCommonProps['labelTextColor'], + parentLabelTextColor = commonDefaultProps.parentLabelTextColor as TreeMapCommonProps['parentLabelTextColor'], }: { data: TreeMapDataProps['data'] width: number @@ -165,7 +157,7 @@ export const useTreeMap = ({ leavesOnly, }) - const hierarchy = useHierarchy({ root: data, getValue }) + const hierarchy = useHierarchy({ root: data, getValue }) const rawNodes = useMemo(() => { // d3 treemap mutates the data, so we need to copy it @@ -231,10 +223,10 @@ export const useTreeMap = ({ }), [ rawNodes, - leavesOnly, getIdentity, formatValue, getLabel, + orientLabel, getParentLabel, parentLabelSize, parentLabelPosition, @@ -264,15 +256,7 @@ export const useTreeMap = ({ return nodeWithStyles }), - [ - nodes, - getColor, - nodeOpacity, - getBorderColor, - getLabelTextColor, - getParentLabelTextColor, - orientLabel, - ] + [nodes, getColor, nodeOpacity, getBorderColor, getLabelTextColor, getParentLabelTextColor] ) return { @@ -282,7 +266,7 @@ export const useTreeMap = ({ } } -export const useInteractiveTreeMapNodes = ( +export const useInteractiveTreeMapNodes = ( nodes: ComputedNode[], { isInteractive, @@ -293,10 +277,10 @@ export const useInteractiveTreeMapNodes = ( tooltip, }: { isInteractive: TreeMapCommonProps['isInteractive'] - onMouseEnter?: NodeMouseEventHandler - onMouseMove?: NodeMouseEventHandler - onMouseLeave?: NodeMouseEventHandler - onClick?: NodeMouseEventHandler + onMouseEnter?: TreeMapCommonProps['onMouseEnter'] + onMouseMove?: TreeMapCommonProps['onMouseMove'] + onMouseLeave?: TreeMapCommonProps['onMouseLeave'] + onClick?: TreeMapCommonProps['onClick'] tooltip: TreeMapCommonProps['tooltip'] } ): ComputedNodeWithHandlers[] => { @@ -353,6 +337,6 @@ export const useInteractiveTreeMapNodes = ( onClick: (event: MouseEvent) => handleClick(node, event), } }), - [nodes, handleMouseEnter, handleMouseMove, handleMouseLeave, handleClick] + [isInteractive, nodes, handleMouseEnter, handleMouseMove, handleMouseLeave, handleClick] ) } diff --git a/packages/treemap/src/index.ts b/packages/treemap/src/index.ts index 832538de2f..8a5994ca35 100644 --- a/packages/treemap/src/index.ts +++ b/packages/treemap/src/index.ts @@ -7,3 +7,4 @@ export * from './ResponsiveTreeMapCanvas' export * from './types' export * from './defaults' export * from './transitions' +export * from './tiling' diff --git a/packages/treemap/src/tiling.ts b/packages/treemap/src/tiling.ts new file mode 100644 index 0000000000..ae089ff55c --- /dev/null +++ b/packages/treemap/src/tiling.ts @@ -0,0 +1,17 @@ +import { + treemapBinary, + treemapDice, + treemapSlice, + treemapSliceDice, + treemapSquarify, +} from 'd3-hierarchy' + +export const tileByType = { + binary: treemapBinary, + dice: treemapDice, + slice: treemapSlice, + sliceDice: treemapSliceDice, + squarify: treemapSquarify, +} as const + +export type TileType = keyof typeof tileByType diff --git a/packages/treemap/src/transitions.ts b/packages/treemap/src/transitions.ts index d375f30048..5d5077b690 100644 --- a/packages/treemap/src/transitions.ts +++ b/packages/treemap/src/transitions.ts @@ -18,8 +18,12 @@ export const htmlLabelTransform = ( rotation: SpringValue ) => to([x, y, rotation], (x, y, rotation) => `translate(${x}px,${y}px) rotate(${rotation}deg)`) -/* -export const svgParentLabelTransform = () => to() +// export const htmlParentLabelTransform = () => to() -export const htmlParentLabelTransform = () => to() +/* +parentLabelHtmlTransform: `translate(${ + node.parentLabelX - (node.parentLabelRotation === 0 ? 0 : 5) +}px,${node.parentLabelY - (node.parentLabelRotation === 0 ? 5 : 0)}px) rotate(${ + node.parentLabelRotation +}deg)`, */ diff --git a/packages/treemap/src/types.ts b/packages/treemap/src/types.ts index 86630e6883..c6dcbfd879 100644 --- a/packages/treemap/src/types.ts +++ b/packages/treemap/src/types.ts @@ -1,4 +1,5 @@ import { AriaAttributes, FunctionComponent, MouseEvent, MouseEventHandler } from 'react' +import { SpringValues } from '@react-spring/web' import { Box, Dimensions, @@ -6,21 +7,18 @@ import { ValueFormat, PropertyAccessor, ModernMotionProps, + SvgDefsAndFill, } from '@nivo/core' -import { InheritedColorConfig, OrdinalColorScaleConfig } from '@nivo/colors' -import { SpringValues } from '@react-spring/web' - -export interface TreeMapDatum { - children: TreeMapDatum[] -} +import { InheritedColorConfig, OrdinalColorScaleConfig, DatumIdentityAccessor } from '@nivo/colors' +import { TileType } from './tiling' export interface DefaultTreeMapDatum { id: string - value: string - children: DefaultTreeMapDatum[] + value?: number + children?: DefaultTreeMapDatum[] } -export interface ComputedNode { +export interface ComputedNode { id: string path: string pathComponents: string[] @@ -49,12 +47,12 @@ export interface ComputedNode { parentLabelRotation: number } -export type ComputedNodeWithoutStyles = Omit< +export type ComputedNodeWithoutStyles = Omit< ComputedNode, 'color' | 'opacity' | 'borderColor' | 'labelTextColor' | 'parentLabelTextColor' > -export interface ComputedNodeWithHandlers extends ComputedNode { +export interface ComputedNodeWithHandlers extends ComputedNode { onMouseEnter?: MouseEventHandler onMouseMove?: MouseEventHandler onMouseLeave?: MouseEventHandler @@ -77,7 +75,7 @@ export type NodeAnimatedProps = { parentLabelOpacity: number } -export interface NodeProps { +export interface NodeProps { node: ComputedNodeWithHandlers animatedProps: SpringValues borderWidth: TreeMapCommonProps['borderWidth'] @@ -85,44 +83,42 @@ export interface NodeProps { labelSkipSize: TreeMapCommonProps['labelSkipSize'] enableParentLabel: TreeMapCommonProps['enableParentLabel'] } -export type NodeComponent = FunctionComponent> +export type NodeComponent = FunctionComponent> -export type TreeMapTile = 'binary' | 'squarify' | 'slice' | 'dice' | 'sliceDice' | 'resquarify' - -export interface TooltipProps { +export interface TooltipProps { node: ComputedNode } -export type TooltipComponent = FunctionComponent> +export type TooltipComponent = FunctionComponent> export type LayerId = 'nodes' -export interface CustomLayerProps { +export interface CustomLayerProps { nodes: ComputedNode[] } -export type CustomSvgLayer = FunctionComponent> -export type CustomHtmlLayer = FunctionComponent> -export type CustomCanvasLayer = ( +export type CustomSvgLayer = FunctionComponent> +export type CustomHtmlLayer = FunctionComponent> +export type CustomCanvasLayer = ( ctx: CanvasRenderingContext2D, props: CustomLayerProps ) => void -export type NodeMouseEventHandler = ( +export type NodeMouseEventHandler = ( node: ComputedNode, event: MouseEvent ) => void -export interface TreeMapDataProps { +export interface TreeMapDataProps { data: Datum } -export type TreeMapCommonProps = { +export type TreeMapCommonProps = { margin: Box identity: PropertyAccessor value: PropertyAccessor valueFormat: ValueFormat - tile: TreeMapTile + tile: TileType leavesOnly: boolean innerPadding: number outerPadding: number @@ -131,7 +127,11 @@ export type TreeMapCommonProps = { label: PropertyAccessor, 'label' | 'parentLabel'>, string> labelFormat: ValueFormat labelSkipSize: number - labelTextColor: InheritedColorConfig + labelTextColor: InheritedColorConfig< + ComputedNodeWithoutStyles & { + color: ComputedNode['color'] + } + > orientLabel: boolean enableParentLabel: boolean @@ -139,11 +139,15 @@ export type TreeMapCommonProps = { parentLabelSize: number parentLabelPosition: 'top' | 'right' | 'bottom' | 'left' parentLabelPadding: number - parentLabelTextColor: InheritedColorConfig + parentLabelTextColor: InheritedColorConfig< + ComputedNodeWithoutStyles & { + color: ComputedNode['color'] + } + > theme: Theme colors: OrdinalColorScaleConfig> - colorBy: any //colorPropertyAccessorPropType.isRequired, + colorBy: string | DatumIdentityAccessor> nodeOpacity: number borderWidth: number borderColor: InheritedColorConfig< @@ -153,6 +157,10 @@ export type TreeMapCommonProps = { > isInteractive: boolean + onMouseEnter: NodeMouseEventHandler + onMouseMove: NodeMouseEventHandler + onMouseLeave: NodeMouseEventHandler + onClick: NodeMouseEventHandler tooltip: TooltipComponent renderWrapper: boolean @@ -163,36 +171,36 @@ export type TreeMapCommonProps = { ariaDescribedBy: AriaAttributes['aria-describedby'] } & ModernMotionProps -export type TreeMapSvgProps = Partial> & +export type TreeMapSvgProps = Partial> & TreeMapDataProps & - Dimensions & { + Dimensions & + SvgDefsAndFill> & { nodeComponent?: NodeComponent layers?: (LayerId | CustomSvgLayer)[] - defs?: any[] - fill?: any[] - onMouseEnter?: NodeMouseEventHandler - onMouseMove?: NodeMouseEventHandler - onMouseLeave?: NodeMouseEventHandler - onClick?: NodeMouseEventHandler } -export type TreeMapHtmlProps = Partial> & +export type TreeMapHtmlProps = Partial> & TreeMapDataProps & Dimensions & { nodeComponent?: NodeComponent layers?: (LayerId | CustomHtmlLayer)[] - pixelRatio?: number - onMouseEnter?: NodeMouseEventHandler - onMouseMove?: NodeMouseEventHandler - onMouseLeave?: NodeMouseEventHandler - onClick?: NodeMouseEventHandler } -export type TreeMapCanvasProps = Partial> & +export type TreeMapCanvasProps = Partial< + Omit< + TreeMapCommonProps, + | 'enableParentLabel' + | 'parentLabel' + | 'parentLabelSize' + | 'parentLabelPosition' + | 'parentLabelPadding' + | 'parentLabelTextColor' + | 'onMouseEnter' + | 'onMouseLeave' + > +> & TreeMapDataProps & Dimensions & { layers?: (LayerId | CustomCanvasLayer)[] pixelRatio?: number - onMouseMove?: NodeMouseEventHandler - onClick?: NodeMouseEventHandler } diff --git a/packages/treemap/stories/treemap.stories.js b/packages/treemap/stories/treemap.stories.tsx similarity index 99% rename from packages/treemap/stories/treemap.stories.js rename to packages/treemap/stories/treemap.stories.tsx index 08603555c8..a292a3aeb3 100644 --- a/packages/treemap/stories/treemap.stories.js +++ b/packages/treemap/stories/treemap.stories.tsx @@ -1,6 +1,7 @@ import { storiesOf } from '@storybook/react' import { generateLibTree } from '@nivo/generators' import { linearGradientDef, patternDotsDef } from '@nivo/core' +// @ts-ignore import { TreeMap } from '../src' const commonProperties = { diff --git a/packages/treemap/stories/treemapHtml.stories.js b/packages/treemap/stories/treemapHtml.stories.tsx similarity index 98% rename from packages/treemap/stories/treemapHtml.stories.js rename to packages/treemap/stories/treemapHtml.stories.tsx index 7b4a7c06eb..c669beaa21 100644 --- a/packages/treemap/stories/treemapHtml.stories.js +++ b/packages/treemap/stories/treemapHtml.stories.tsx @@ -1,5 +1,6 @@ import { storiesOf } from '@storybook/react' import { generateLibTree } from '@nivo/generators' +// @ts-ignore import { TreeMapHtml } from '../src' const commonProperties = { diff --git a/packages/treemap/tests/.eslintrc.yml b/packages/treemap/tests/.eslintrc.yml new file mode 100644 index 0000000000..4e0ff0d227 --- /dev/null +++ b/packages/treemap/tests/.eslintrc.yml @@ -0,0 +1,5 @@ +env: + jest: true + +rules: + react/prop-types: 0 \ No newline at end of file diff --git a/packages/treemap/tests/TreeMap.test.tsx b/packages/treemap/tests/TreeMap.test.tsx new file mode 100644 index 0000000000..43e29f103d --- /dev/null +++ b/packages/treemap/tests/TreeMap.test.tsx @@ -0,0 +1,534 @@ +import { mount } from 'enzyme' +import { Globals } from '@react-spring/web' +import { + TreeMap, + TreeMapSvgProps, + DefaultTreeMapDatum, + // @ts-ignore +} from '../src' + +const sampleData: DefaultTreeMapDatum = { + id: 'root', + children: [ + { + id: 'A', + value: 50, + }, + { + id: 'B', + children: [ + { + id: 'B.0', + value: 25, + }, + { + id: 'B.1', + value: 25, + }, + ], + }, + ], +} + +const baseProps: TreeMapSvgProps = { + width: 400, + height: 400, + data: sampleData, + animate: false, +} + +beforeAll(() => { + Globals.assign({ skipAnimation: true }) +}) + +afterAll(() => { + Globals.assign({ skipAnimation: false }) +}) + +/* +it('should render a basic network chart', () => { + const wrapper = mount() + + sampleData.nodes.forEach(node => { + const nodeElement = wrapper.find(`circle[data-testid='node.${node.id}']`) + expect(nodeElement.exists()).toBeTruthy() + }) + + sampleData.links.forEach(link => { + const linkElement = wrapper.find(`line[data-testid='link.${link.source}.${link.target}']`) + expect(linkElement.exists()).toBeTruthy() + }) +}) + +describe('nodes', () => { + it('static node color', () => { + const color = 'rgba(255, 0, 255, 1)' + const wrapper = mount() + + sampleData.nodes.forEach(node => { + expect(wrapper.find(`circle[data-testid='node.${node.id}']`).prop('fill')).toEqual( + color + ) + }) + }) + + it('static node size', () => { + const size = 32 + const wrapper = mount() + + sampleData.nodes.forEach(node => { + expect(wrapper.find(`circle[data-testid='node.${node.id}']`).prop('r')).toEqual( + size / 2 + ) + }) + }) + + it('dynamic node size', () => { + const computeSize = (node: { id: string; index: number }) => 10 + node.index * 2 + const nodesWithIndex = sampleData.nodes.map((node, index) => ({ + ...node, + index, + })) + const wrapper = mount( + + {...baseProps} + data={{ + nodes: nodesWithIndex, + links: sampleData.links, + }} + nodeSize={computeSize} + /> + ) + + nodesWithIndex.forEach(node => { + expect(wrapper.find(`circle[data-testid='node.${node.id}']`).prop('r')).toEqual( + computeSize(node) / 2 + ) + }) + }) + + it('custom node component', () => { + const CustomNode = ({ node }: NodeProps) => ( + + ) + + const wrapper = mount() + + sampleData.nodes.forEach(node => { + expect(wrapper.find(`g[data-testid='node.${node.id}.custom']`).exists()).toBeTruthy() + }) + }) +}) + +describe('active/inactive nodes', () => { + it('styles depending on nodes status', () => { + const wrapper = mount( + + ) + + sampleData.nodes.forEach(activeNode => { + wrapper.find(`circle[data-testid='node.${activeNode.id}']`).simulate('mouseenter') + + expect( + wrapper.find(`circle[data-testid='node.${activeNode.id}']`).parent().prop('r').get() + ).toEqual(10) + + sampleData.nodes + .filter(node => node.id !== activeNode.id) + .forEach(otherNode => { + expect( + wrapper + .find(`circle[data-testid='node.${otherNode.id}']`) + .parent() + .prop('r') + .get() + ).toEqual(0) + }) + }) + }) + + it('default active node ids', () => { + const activeIds = ['A', 'C'] + const wrapper = mount( + + ) + + sampleData.nodes.forEach(node => { + const nodeElement = wrapper.find(`circle[data-testid='node.${node.id}']`) + + if (activeIds.includes(node.id)) { + expect(nodeElement.prop('r')).toEqual(10) + } else { + expect(nodeElement.prop('r')).toEqual(0) + } + }) + }) + + it('ignored if non interactive', () => { + const wrapper = mount( + + ) + + sampleData.nodes.forEach(node => { + expect(wrapper.find(`circle[data-testid='node.${node.id}']`).prop('r')).toEqual(5) + }) + }) +}) + +describe('links', () => { + it('static link thickness', () => { + const wrapper = mount() + + sampleData.links.forEach(link => { + expect( + wrapper + .find(`line[data-testid='link.${link.source}.${link.target}']`) + .prop('strokeWidth') + ).toEqual(3) + }) + }) + + it('dynamic link thickness', () => { + const wrapper = mount( 1 + link.index} />) + + sampleData.links.forEach((link, index) => { + expect( + wrapper + .find(`line[data-testid='link.${link.source}.${link.target}']`) + .prop('strokeWidth') + ).toEqual(1 + index) + }) + }) + + it('static link color', () => { + const color = 'rgba(255, 0, 0, 1)' + const wrapper = mount() + + sampleData.links.forEach(link => { + expect( + wrapper + .find(`line[data-testid='link.${link.source}.${link.target}']`) + .prop('stroke') + ).toEqual(color) + }) + }) + + it('dynamic link color', () => { + const wrapper = mount( + `rgba(${link.index * 10}, 0, 0, 1)`} /> + ) + + sampleData.links.forEach((link, index) => { + expect( + wrapper + .find(`line[data-testid='link.${link.source}.${link.target}']`) + .prop('stroke') + ).toEqual(`rgba(${index * 10}, 0, 0, 1)`) + }) + }) + + it('link color from source node color', () => { + const color = 'rgba(125, 255, 125, 1)' + const wrapper = mount( + + ) + + sampleData.links.forEach(link => { + expect( + wrapper + .find(`line[data-testid='link.${link.source}.${link.target}']`) + .prop('stroke') + ).toEqual(color) + }) + }) + + it('link color from target node color', () => { + const color = 'rgba(125, 125, 255, 1)' + const wrapper = mount( + + ) + + sampleData.links.forEach(link => { + expect( + wrapper + .find(`line[data-testid='link.${link.source}.${link.target}']`) + .prop('stroke') + ).toEqual(color) + }) + }) + + it('link blend mode', () => { + const wrapper = mount() + + sampleData.links.forEach(link => { + expect( + wrapper.find(`line[data-testid='link.${link.source}.${link.target}']`).prop('style') + ).toEqual({ mixBlendMode: 'multiply' }) + }) + }) + + it('custom link component', () => { + const CustomLink = ({ link }: LinkProps) => ( + + ) + + const wrapper = mount() + + sampleData.links.forEach(link => { + expect( + wrapper.find(`g[data-testid='link.${link.source}.${link.target}.custom']`).exists() + ).toBeTruthy() + }) + }) +}) + +describe('tooltip', () => { + it('default node tooltip', () => { + const wrapper = mount() + + sampleData.nodes.forEach(node => { + const nodeElement = wrapper.find(`circle[data-testid='node.${node.id}']`) + + nodeElement.simulate('mouseenter') + + const tooltip = wrapper.find(svgDefaultProps.nodeTooltip).childAt(0).childAt(0) + expect(tooltip.exists()).toBeTruthy() + expect(tooltip.text()).toEqual(node.id) + + nodeElement.simulate('mouseleave') + expect(wrapper.find(svgDefaultProps.nodeTooltip).children()).toHaveLength(0) + }) + }) + + it('disabled if non interactive', () => { + const wrapper = mount() + + sampleData.nodes.forEach(node => { + const nodeElement = wrapper.find(`circle[data-testid='node.${node.id}']`) + + nodeElement.simulate('mouseenter') + + expect(wrapper.find(svgDefaultProps.nodeTooltip).children()).toHaveLength(0) + }) + }) + + it('custom node tooltip', () => { + const CustomTooltip = ({ node }: { node: ComputedNode }) => ( +
Custom: {node.id}
+ ) + const wrapper = mount() + + sampleData.nodes.forEach(node => { + const nodeElement = wrapper.find(`circle[data-testid='node.${node.id}']`) + + nodeElement.simulate('mouseenter') + + const tooltip = wrapper.find(CustomTooltip) + expect(tooltip.exists()).toBeTruthy() + expect(tooltip.text()).toEqual(`Custom: ${node.id}`) + + nodeElement.simulate('mouseleave') + expect(wrapper.find(CustomTooltip).exists()).toBeFalsy() + }) + }) +}) + +describe('interactivity', () => { + it('onClick', () => { + const onClick = jest.fn() + const wrapper = mount() + + sampleData.nodes.forEach(node => { + wrapper.find(`circle[data-testid='node.${node.id}']`).simulate('click') + + expect(onClick).toHaveBeenCalledTimes(1) + const [datum] = onClick.mock.calls[0] + expect(datum.id).toEqual(node.id) + + onClick.mockClear() + }) + }) + + it('onMouseEnter', () => { + const onMouseEnter = jest.fn() + const wrapper = mount() + + sampleData.nodes.forEach(node => { + wrapper.find(`circle[data-testid='node.${node.id}']`).simulate('mouseenter') + + expect(onMouseEnter).toHaveBeenCalledTimes(1) + const [datum] = onMouseEnter.mock.calls[0] + expect(datum.id).toEqual(node.id) + + onMouseEnter.mockClear() + }) + }) + + it('onMouseMove handler', () => { + const onMouseMove = jest.fn() + const wrapper = mount() + + sampleData.nodes.forEach(node => { + wrapper.find(`circle[data-testid='node.${node.id}']`).simulate('mousemove') + + expect(onMouseMove).toHaveBeenCalledTimes(1) + const [datum] = onMouseMove.mock.calls[0] + expect(datum.id).toEqual(node.id) + + onMouseMove.mockClear() + }) + }) + + it('onMouseLeave handler', () => { + const onMouseLeave = jest.fn() + const wrapper = mount() + + sampleData.nodes.forEach(node => { + wrapper.find(`circle[data-testid='node.${node.id}']`).simulate('mouseleave') + + expect(onMouseLeave).toHaveBeenCalledTimes(1) + const [datum] = onMouseLeave.mock.calls[0] + expect(datum.id).toEqual(node.id) + + onMouseLeave.mockClear() + }) + }) + + it('disabled if non interactive', () => { + const onClick = jest.fn() + const onMouseEnter = jest.fn() + const onMouseMove = jest.fn() + const onMouseLeave = jest.fn() + + const wrapper = mount( + + ) + + sampleData.nodes.forEach(node => { + const nodeElement = wrapper.find(`circle[data-testid='node.${node.id}']`) + + nodeElement.simulate('mouseenter') + expect(onMouseEnter).not.toHaveBeenCalled() + + nodeElement.simulate('mousemove') + expect(onMouseMove).not.toHaveBeenCalled() + + nodeElement.simulate('mouseleave') + expect(onMouseLeave).not.toHaveBeenCalled() + + nodeElement.simulate('click') + expect(onClick).not.toHaveBeenCalled() + }) + }) +}) + +describe('layers', () => { + it('custom order', () => { + const wrapper = mount() + + const layers = wrapper.find('svg > g').children() + expect(layers.at(0).is('NetworkNodes')).toBeTruthy() + expect(layers.at(1).is('NetworkLinks')).toBeTruthy() + }) + + it('custom layer', () => { + const CustomLayer = () => null + const wrapper = mount() + + const customLayer = wrapper.find(CustomLayer) + expect(customLayer.exists()).toBeTruthy() + + const customLayerProps = customLayer.props() + expect(customLayerProps).toHaveProperty('nodes') + expect(customLayerProps).toHaveProperty('links') + expect(customLayerProps).toHaveProperty('activeNodeIds') + expect(customLayerProps).toHaveProperty('setActiveNodeIds') + }) +}) + +describe('annotations', () => { + it('circle annotation using id', () => { + const annotatedNodeId = 'C' + const wrapper = mount( + + ) + + const annotation = wrapper.find(Annotation) + expect(annotation.exists()).toBeTruthy() + + const annotatedNode = wrapper.find(`circle[data-testid='node.${annotatedNodeId}']`) + const [nodeX, nodeY] = Array.from( + annotatedNode.prop('transform').match(/translate\(([0-9.]+),([0-9.]+)\)/) + ) + .slice(1) + .map(Number) + + expect(annotation.find('circle').first().prop('cx')).toEqual(nodeX) + expect(annotation.find('circle').first().prop('cy')).toEqual(nodeY) + }) +}) + +describe('accessibility', () => { + it('aria properties', () => { + 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') + }) +}) +*/ diff --git a/website/src/data/components/treemap/props.ts b/website/src/data/components/treemap/props.ts index b4fdbd8ac8..6aa1d526d7 100644 --- a/website/src/data/components/treemap/props.ts +++ b/website/src/data/components/treemap/props.ts @@ -1,4 +1,4 @@ -import { commonDefaultProps as defaults } from '@nivo/treemap' +import { commonDefaultProps as defaults, tileByType } from '@nivo/treemap' import { motionProperties, defsProperties, @@ -68,17 +68,10 @@ const props: ChartProperty[] = [ defaultValue: 'squarify', control: { type: 'choices', - choices: [ - { label: 'binary', value: 'binary' }, - { label: 'squarify', value: 'squarify' }, - { label: 'slice', value: 'slice' }, - { label: 'dice', value: 'dice' }, - { label: 'sliceDice', value: 'sliceDice' }, - { - label: 'resquarify', - value: 'resquarify', - }, - ], + choices: Object.keys(tileByType).map(tile => ({ + label: tile, + value: tile, + })), }, }, { diff --git a/website/src/pages/treemap/canvas.tsx b/website/src/pages/treemap/canvas.tsx index d67dc83d40..1089956f76 100644 --- a/website/src/pages/treemap/canvas.tsx +++ b/website/src/pages/treemap/canvas.tsx @@ -9,9 +9,9 @@ import { graphql, useStaticQuery } from 'gatsby' interface Datum { name: string - loc: number + loc?: number color: string - children: Datum[] + children?: Datum[] } const initialProperties = { diff --git a/website/src/pages/treemap/html.tsx b/website/src/pages/treemap/html.tsx index 532a8f93ab..22619b9d5c 100644 --- a/website/src/pages/treemap/html.tsx +++ b/website/src/pages/treemap/html.tsx @@ -9,9 +9,9 @@ import { generateLightDataSet } from '../../data/components/treemap/generator' interface Datum { name: string - loc: number + loc?: number color: string - children: Datum[] + children?: Datum[] } const initialProperties = { diff --git a/website/src/pages/treemap/index.tsx b/website/src/pages/treemap/index.tsx index 05bd231871..6e9b69450e 100644 --- a/website/src/pages/treemap/index.tsx +++ b/website/src/pages/treemap/index.tsx @@ -11,9 +11,9 @@ const generateData = () => generateLightDataSet().root interface Datum { name: string - loc: number + loc?: number color: string - children: Datum[] + children?: Datum[] } const initialProperties = {