Skip to content

Commit

Permalink
feat(treemap): add support for custom layers to SVG and canvas implem…
Browse files Browse the repository at this point in the history
…entations
  • Loading branch information
plouc committed Jan 2, 2022
1 parent 8946b02 commit b45f0d1
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 35 deletions.
55 changes: 41 additions & 14 deletions packages/treemap/src/TreeMap.tsx
@@ -1,13 +1,20 @@
import { createElement, Fragment, ReactNode } from 'react'
import {
SvgWrapper,
Container,
useDimensions,
// @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<Datum extends object> = Omit<
Expand All @@ -28,6 +35,7 @@ const InnerTreeMap = <Datum extends object>({
width,
height,
margin: partialMargin,
layers = svgDefaultProps.layers as NonNullable<TreeMapSvgProps<Datum>['layers']>,
colors = svgDefaultProps.colors as TreeMapCommonProps<Datum>['colors'],
colorBy = svgDefaultProps.colorBy as TreeMapCommonProps<Datum>['colorBy'],
nodeOpacity = svgDefaultProps.nodeOpacity,
Expand Down Expand Up @@ -89,20 +97,14 @@ const InnerTreeMap = <Datum extends object>({
parentLabelTextColor,
})

const boundDefs = bindDefs(defs, nodes, fill)
const layerById: Record<LayerId, ReactNode> = {
nodes: null,
}

return (
<SvgWrapper
width={outerWidth}
height={outerHeight}
margin={margin}
defs={boundDefs}
role={role}
ariaLabel={ariaLabel}
ariaLabelledBy={ariaLabelledBy}
ariaDescribedBy={ariaDescribedBy}
>
if (layers.includes('nodes')) {
layerById.nodes = (
<TreeMapNodes<Datum>
key="nodes"
nodes={nodes}
nodeComponent={nodeComponent}
borderWidth={borderWidth}
Expand All @@ -116,6 +118,31 @@ const InnerTreeMap = <Datum extends object>({
onClick={onClick}
tooltip={tooltip}
/>
)
}

const customLayerProps = useCustomLayerProps<Datum>({ nodes })

const boundDefs = bindDefs(defs, nodes, fill)

return (
<SvgWrapper
width={outerWidth}
height={outerHeight}
margin={margin}
defs={boundDefs}
role={role}
ariaLabel={ariaLabel}
ariaLabelledBy={ariaLabelledBy}
ariaDescribedBy={ariaDescribedBy}
>
{layers.map((layer, i) => {
if (typeof layer === 'function') {
return <Fragment key={i}>{createElement(layer, customLayerProps)}</Fragment>
}

return layerById?.[layer] ?? null
})}
</SvgWrapper>
)
}
Expand Down
55 changes: 38 additions & 17 deletions 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<Datum extends object> = Omit<
TreeMapHtmlProps<Datum>,
Expand All @@ -22,6 +23,7 @@ const InnerTreeMapHtml = <Datum extends object>({
width,
height,
margin: partialMargin,
layers = svgDefaultProps.layers as NonNullable<TreeMapHtmlProps<Datum>['layers']>,
colors = htmlDefaultProps.colors as TreeMapCommonProps<Datum>['colors'],
colorBy = htmlDefaultProps.colorBy as TreeMapCommonProps<Datum>['colorBy'],
nodeOpacity = htmlDefaultProps.nodeOpacity,
Expand Down Expand Up @@ -81,6 +83,32 @@ const InnerTreeMapHtml = <Datum extends object>({
parentLabelTextColor,
})

const layerById: Record<LayerId, ReactNode> = {
nodes: null,
}

if (layers.includes('nodes')) {
layerById.nodes = (
<TreeMapNodes<Datum>
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<Datum>({ nodes })

return (
<div
role={role}
Expand All @@ -94,20 +122,13 @@ const InnerTreeMapHtml = <Datum extends object>({
}}
>
<div style={{ position: 'absolute', top: margin.top, left: margin.left }}>
<TreeMapNodes<Datum>
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 <Fragment key={i}>{createElement(layer, customLayerProps)}</Fragment>
}

return layerById?.[layer] ?? null
})}
</div>
</div>
)
Expand Down
11 changes: 11 additions & 0 deletions packages/treemap/src/hooks.ts
Expand Up @@ -19,6 +19,7 @@ import {
ComputedNode,
ComputedNodeWithoutStyles,
ComputedNodeWithHandlers,
CustomLayerProps,
} from './types'
import { tileByType } from './tiling'

Expand Down Expand Up @@ -340,3 +341,13 @@ export const useInteractiveTreeMapNodes = <Datum extends object>(
[isInteractive, nodes, handleMouseEnter, handleMouseMove, handleMouseLeave, handleClick]
)
}

export const useCustomLayerProps = <Datum extends object>({
nodes,
}: CustomLayerProps<Datum>): CustomLayerProps<Datum> =>
useMemo(
() => ({
nodes,
}),
[nodes]
)
3 changes: 1 addition & 2 deletions website/src/components/components/api-client/ApiClient.tsx
Expand Up @@ -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);
Expand Down
3 changes: 1 addition & 2 deletions website/src/components/components/api-client/ApiTabs.tsx
Expand Up @@ -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;
Expand Down
15 changes: 15 additions & 0 deletions website/src/data/components/treemap/props.ts
Expand Up @@ -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'),
]
Expand Down

0 comments on commit b45f0d1

Please sign in to comment.