From b45f0d116c06dbc4b8129f639fdd25e40ab15a39 Mon Sep 17 00:00:00 2001 From: plouc Date: Mon, 3 Jan 2022 01:00:13 +0900 Subject: [PATCH] feat(treemap): add support for custom layers to SVG and canvas implementations --- packages/treemap/src/TreeMap.tsx | 55 ++++++++++++++----- packages/treemap/src/TreeMapHtml.tsx | 55 +++++++++++++------ packages/treemap/src/hooks.ts | 11 ++++ .../components/api-client/ApiClient.tsx | 3 +- .../components/api-client/ApiTabs.tsx | 3 +- website/src/data/components/treemap/props.ts | 15 +++++ 6 files changed, 107 insertions(+), 35 deletions(-) diff --git a/packages/treemap/src/TreeMap.tsx b/packages/treemap/src/TreeMap.tsx index e02ef69acc..b33bced018 100644 --- a/packages/treemap/src/TreeMap.tsx +++ b/packages/treemap/src/TreeMap.tsx @@ -1,3 +1,4 @@ +import { createElement, Fragment, ReactNode } from 'react' import { SvgWrapper, Container, @@ -5,9 +6,15 @@ import { // @ts-ignore bindDefs, } from '@nivo/core' -import { useTreeMap } from './hooks' +import { useTreeMap, useCustomLayerProps } from './hooks' import { TreeMapNodes } from './TreeMapNodes' -import { DefaultTreeMapDatum, NodeComponent, TreeMapCommonProps, TreeMapSvgProps } from './types' +import { + DefaultTreeMapDatum, + NodeComponent, + TreeMapCommonProps, + TreeMapSvgProps, + LayerId, +} from './types' import { svgDefaultProps } from './defaults' type InnerTreeMapProps = Omit< @@ -28,6 +35,7 @@ const InnerTreeMap = ({ width, height, margin: partialMargin, + layers = svgDefaultProps.layers as NonNullable['layers']>, colors = svgDefaultProps.colors as TreeMapCommonProps['colors'], colorBy = svgDefaultProps.colorBy as TreeMapCommonProps['colorBy'], nodeOpacity = svgDefaultProps.nodeOpacity, @@ -89,20 +97,14 @@ const InnerTreeMap = ({ parentLabelTextColor, }) - const boundDefs = bindDefs(defs, nodes, fill) + const layerById: Record = { + nodes: null, + } - return ( - + if (layers.includes('nodes')) { + layerById.nodes = ( + key="nodes" nodes={nodes} nodeComponent={nodeComponent} borderWidth={borderWidth} @@ -116,6 +118,31 @@ const InnerTreeMap = ({ onClick={onClick} tooltip={tooltip} /> + ) + } + + const customLayerProps = useCustomLayerProps({ nodes }) + + const boundDefs = bindDefs(defs, nodes, fill) + + return ( + + {layers.map((layer, i) => { + if (typeof layer === 'function') { + return {createElement(layer, customLayerProps)} + } + + return layerById?.[layer] ?? null + })} ) } diff --git a/packages/treemap/src/TreeMapHtml.tsx b/packages/treemap/src/TreeMapHtml.tsx index 8e86e876b6..90f9d1d359 100644 --- a/packages/treemap/src/TreeMapHtml.tsx +++ b/packages/treemap/src/TreeMapHtml.tsx @@ -1,8 +1,9 @@ +import { createElement, Fragment, ReactNode } from 'react' import { Container, useDimensions } from '@nivo/core' -import { useTreeMap } from './hooks' +import { useCustomLayerProps, useTreeMap } from './hooks' import { TreeMapNodes } from './TreeMapNodes' -import { DefaultTreeMapDatum, TreeMapCommonProps, TreeMapHtmlProps } from './types' -import { htmlDefaultProps } from './defaults' +import { DefaultTreeMapDatum, TreeMapCommonProps, TreeMapHtmlProps, LayerId } from './types' +import { htmlDefaultProps, svgDefaultProps } from './defaults' type InnerTreeMapHtmlProps = Omit< TreeMapHtmlProps, @@ -22,6 +23,7 @@ const InnerTreeMapHtml = ({ width, height, margin: partialMargin, + layers = svgDefaultProps.layers as NonNullable['layers']>, colors = htmlDefaultProps.colors as TreeMapCommonProps['colors'], colorBy = htmlDefaultProps.colorBy as TreeMapCommonProps['colorBy'], nodeOpacity = htmlDefaultProps.nodeOpacity, @@ -81,6 +83,32 @@ const InnerTreeMapHtml = ({ parentLabelTextColor, }) + const layerById: Record = { + nodes: null, + } + + if (layers.includes('nodes')) { + layerById.nodes = ( + + key="nodes" + nodes={nodes} + nodeComponent={nodeComponent} + borderWidth={borderWidth} + enableLabel={enableLabel} + labelSkipSize={labelSkipSize} + enableParentLabel={enableParentLabel} + isInteractive={isInteractive} + onMouseEnter={onMouseEnter} + onMouseMove={onMouseMove} + onMouseLeave={onMouseLeave} + onClick={onClick} + tooltip={tooltip} + /> + ) + } + + const customLayerProps = useCustomLayerProps({ nodes }) + return (
({ }} >
- - nodes={nodes} - nodeComponent={nodeComponent} - borderWidth={borderWidth} - enableLabel={enableLabel} - labelSkipSize={labelSkipSize} - enableParentLabel={enableParentLabel} - isInteractive={isInteractive} - onMouseEnter={onMouseEnter} - onMouseMove={onMouseMove} - onMouseLeave={onMouseLeave} - onClick={onClick} - tooltip={tooltip} - /> + {layers.map((layer, i) => { + if (typeof layer === 'function') { + return {createElement(layer, customLayerProps)} + } + + return layerById?.[layer] ?? null + })}
) diff --git a/packages/treemap/src/hooks.ts b/packages/treemap/src/hooks.ts index 0d0378e5e7..501790842e 100644 --- a/packages/treemap/src/hooks.ts +++ b/packages/treemap/src/hooks.ts @@ -19,6 +19,7 @@ import { ComputedNode, ComputedNodeWithoutStyles, ComputedNodeWithHandlers, + CustomLayerProps, } from './types' import { tileByType } from './tiling' @@ -340,3 +341,13 @@ export const useInteractiveTreeMapNodes = ( [isInteractive, nodes, handleMouseEnter, handleMouseMove, handleMouseLeave, handleClick] ) } + +export const useCustomLayerProps = ({ + nodes, +}: CustomLayerProps): CustomLayerProps => + useMemo( + () => ({ + nodes, + }), + [nodes] + ) diff --git a/website/src/components/components/api-client/ApiClient.tsx b/website/src/components/components/api-client/ApiClient.tsx index ae6c5f8648..33d7f7c89a 100644 --- a/website/src/components/components/api-client/ApiClient.tsx +++ b/website/src/components/components/api-client/ApiClient.tsx @@ -128,8 +128,7 @@ const ControlPanel = styled.div` right: 0; bottom: 0; --innerWidth: calc(100% - ${({ theme }) => theme.dimensions.miniNavWidth}px); - --partialWidth: calc(var(--innerWidth) * 0.6); - width: var(--partialWidth); + width: calc(var(--innerWidth) * 0.55); background: ${({ theme }) => theme.colors.cardAltBackground}; --innerHeight: calc(100% - ${({ theme }) => theme.dimensions.headerHeight}px); height: calc(var(--innerHeight) * 0.45); diff --git a/website/src/components/components/api-client/ApiTabs.tsx b/website/src/components/components/api-client/ApiTabs.tsx index fcb9c10b7e..2281bf86d3 100644 --- a/website/src/components/components/api-client/ApiTabs.tsx +++ b/website/src/components/components/api-client/ApiTabs.tsx @@ -69,9 +69,8 @@ const Wrapper = styled.div` position: fixed; top: ${({ theme }) => theme.dimensions.headerHeight}px; right: 0; - width: 60%; --innerWidth: calc(100% - ${({ theme }) => theme.dimensions.miniNavWidth}px); - width: calc(var(--innerWidth) * 0.6); + width: calc(var(--innerWidth) * 0.55); --innerHeight: calc(100% - ${({ theme }) => theme.dimensions.headerHeight}px); height: calc(var(--innerHeight) * 0.55); z-index: 10; diff --git a/website/src/data/components/treemap/props.ts b/website/src/data/components/treemap/props.ts index 6aa1d526d7..7d4d3b155c 100644 --- a/website/src/data/components/treemap/props.ts +++ b/website/src/data/components/treemap/props.ts @@ -333,6 +333,21 @@ const props: ChartProperty[] = [ help: 'onClick handler.', required: false, }, + { + key: 'nodeComponent', + type: 'NodeComponent', + group: 'Customization', + help: 'Override the default node component.', + flavors: ['svg', 'html'], + }, + { + key: 'layers', + type: `('nodes' | CustomSvgLayer | CustomHtmlLayer | CustomCanvasLayer)[]`, + group: 'Customization', + help: 'Define layers, please use the appropriate variant for custom layers.', + defaultValue: defaults.layers, + flavors: ['svg', 'html', 'canvas'], + }, ...commonAccessibilityProps(allFlavors), ...motionProperties(['svg', 'html', 'canvas'], defaults, 'react-spring'), ]