diff --git a/packages/arcs/src/centers.ts b/packages/arcs/src/centers.ts index 03d170f0b..33c1a8d5c 100644 --- a/packages/arcs/src/centers.ts +++ b/packages/arcs/src/centers.ts @@ -47,6 +47,11 @@ export const useArcCentersTransition = ) => { + // center root node label + const dataWithCenteredRoot = data.map(d => + d.arc.innerRadius === 0 ? { ...d, arc: { ...d.arc, outerRadius: 0 } } : d + ) + const { animate, config: springConfig } = useMotionConfig() const phases = useArcTransitionMode(mode, extra) @@ -60,7 +65,7 @@ export const useArcCentersTransition = (data, { + >(dataWithCenteredRoot, { keys: datum => datum.id, initial: phases.update, from: phases.enter, diff --git a/packages/sunburst/package.json b/packages/sunburst/package.json index 26546c477..b3ab0c46f 100644 --- a/packages/sunburst/package.json +++ b/packages/sunburst/package.json @@ -33,11 +33,13 @@ "@nivo/colors": "0.80.0", "@nivo/tooltip": "0.80.0", "d3-hierarchy": "^1.1.8", + "d3-scale": "^3.2.3", "lodash": "^4.17.21" }, "devDependencies": { "@nivo/core": "0.80.0", - "@types/d3-hierarchy": "^1.1.7" + "@types/d3-hierarchy": "^1.1.7", + "d3-scale": "^3.2.3" }, "peerDependencies": { "@nivo/core": "0.80.0", diff --git a/packages/sunburst/src/Sunburst.tsx b/packages/sunburst/src/Sunburst.tsx index c7f4a9e71..e306f9b09 100644 --- a/packages/sunburst/src/Sunburst.tsx +++ b/packages/sunburst/src/Sunburst.tsx @@ -25,6 +25,8 @@ const InnerSunburst = ({ data, id = defaultProps.id, value = defaultProps.value, + innerRadius = defaultProps.innerRadius, + renderRootNode = defaultProps.renderRootNode, valueFormat, cornerRadius = defaultProps.cornerRadius, layers = defaultProps.layers as SunburstLayer[], @@ -73,6 +75,8 @@ const InnerSunburst = ({ valueFormat, radius, cornerRadius, + innerRadius, + renderRootNode, colors, colorBy, inheritColorFromParent, diff --git a/packages/sunburst/src/hooks.ts b/packages/sunburst/src/hooks.ts index f0318c9dc..435bcee00 100644 --- a/packages/sunburst/src/hooks.ts +++ b/packages/sunburst/src/hooks.ts @@ -1,5 +1,6 @@ import { useMemo } from 'react' import { partition as d3Partition, hierarchy as d3Hierarchy } from 'd3-hierarchy' +import { scaleRadial as d3ScaleRadial } from 'd3-scale' import cloneDeep from 'lodash/cloneDeep' import sortBy from 'lodash/sortBy' import { usePropertyAccessor, useTheme, useValueFormatter } from '@nivo/core' @@ -21,6 +22,8 @@ export const useSunburst = ({ valueFormat, radius, cornerRadius = defaultProps.cornerRadius, + innerRadius = defaultProps.innerRadius, + renderRootNode = defaultProps.renderRootNode, colors = defaultProps.colors, colorBy = defaultProps.colorBy, inheritColorFromParent = defaultProps.inheritColorFromParent, @@ -32,6 +35,8 @@ export const useSunburst = ({ valueFormat?: DataProps['valueFormat'] radius: number cornerRadius?: SunburstCommonProps['cornerRadius'] + innerRadius?: SunburstCommonProps['innerRadius'] + renderRootNode?: SunburstCommonProps['renderRootNode'] colors?: SunburstCommonProps['colors'] colorBy?: SunburstCommonProps['colorBy'] inheritColorFromParent?: SunburstCommonProps['inheritColorFromParent'] @@ -58,7 +63,9 @@ export const useSunburst = ({ const partition = d3Partition().size([2 * Math.PI, radius * radius]) // exclude root node - const descendants = partition(hierarchy).descendants().slice(1) + const descendants = renderRootNode + ? partition(hierarchy).descendants() + : partition(hierarchy).descendants().slice(1) const total = hierarchy.value ?? 0 @@ -68,6 +75,12 @@ export const useSunburst = ({ // are going to be computed first const sortedNodes = sortBy(descendants, 'depth') + const innerRadiusOffset = radius * Math.min(innerRadius, 1) + + const maxDepth = Math.max(...sortedNodes.map(n => n.depth)) + + const scale = d3ScaleRadial().domain([0, maxDepth]).range([innerRadiusOffset, radius]) + return sortedNodes.reduce[]>((acc, descendant) => { const id = getId(descendant.data) // d3 hierarchy node value is optional by default as it depends on @@ -82,8 +95,12 @@ export const useSunburst = ({ const arc: Arc = { startAngle: descendant.x0, endAngle: descendant.x1, - innerRadius: Math.sqrt(descendant.y0), - outerRadius: Math.sqrt(descendant.y1), + innerRadius: + renderRootNode && descendant.depth === 0 ? 0 : scale(descendant.depth - 1), + outerRadius: + renderRootNode && descendant.depth === 0 + ? innerRadius + : scale(descendant.depth), } let parent: ComputedDatum | undefined @@ -125,6 +142,8 @@ export const useSunburst = ({ getColor, inheritColorFromParent, getChildColor, + innerRadius, + renderRootNode, ]) const arcGenerator = useArcGenerator({ cornerRadius }) diff --git a/packages/sunburst/src/props.ts b/packages/sunburst/src/props.ts index aa59533eb..41b33617e 100644 --- a/packages/sunburst/src/props.ts +++ b/packages/sunburst/src/props.ts @@ -7,6 +7,8 @@ export const defaultProps = { id: 'id', value: 'value', cornerRadius: 0, + innerRadius: 0.4, + renderRootNode: false, layers: ['arcs', 'arcLabels'] as SunburstLayerId[], colors: { scheme: 'nivo' } as unknown as OrdinalColorScaleConfig, colorBy: 'id' as const, diff --git a/packages/sunburst/src/types.ts b/packages/sunburst/src/types.ts index 159b25186..0bb6ff39b 100644 --- a/packages/sunburst/src/types.ts +++ b/packages/sunburst/src/types.ts @@ -63,6 +63,8 @@ export type SunburstCommonProps = { height: number margin?: Box cornerRadius: number + innerRadius: number + renderRootNode: boolean theme: Theme colors: OrdinalColorScaleConfig, 'color' | 'fill'>> colorBy: 'id' | 'depth' diff --git a/packages/sunburst/stories/sunburst.stories.tsx b/packages/sunburst/stories/sunburst.stories.tsx index 1c5736bad..bbd36cc2a 100644 --- a/packages/sunburst/stories/sunburst.stories.tsx +++ b/packages/sunburst/stories/sunburst.stories.tsx @@ -1,7 +1,7 @@ import { useState } from 'react' import { storiesOf } from '@storybook/react' import { action } from '@storybook/addon-actions' -import { withKnobs, boolean, select } from '@storybook/addon-knobs' +import { withKnobs, boolean, select, number } from '@storybook/addon-knobs' // @ts-ignore import { linearGradientDef, patternDotsDef, useTheme } from '@nivo/core' // @ts-ignore @@ -223,3 +223,16 @@ const CenteredMetric = ({ nodes, centerX, centerY }: SunburstCustomLayerProps ( {...commonProperties} layers={['arcs', 'arcLabels', CenteredMetric]} /> )) + +stories.add('with root node', () => ( + + {...commonProperties} + innerRadius={number('innerRadius', 0.25, { + range: true, + min: 0.0, + max: 0.95, + step: 0.05, + })} + renderRootNode={boolean('renderRootNode', true)} + /> +)) diff --git a/website/src/data/components/sunburst/props.ts b/website/src/data/components/sunburst/props.ts index afd39aed9..cd5c5c1f0 100644 --- a/website/src/data/components/sunburst/props.ts +++ b/website/src/data/components/sunburst/props.ts @@ -111,6 +111,29 @@ const props: ChartProperty[] = [ step: 1, }, }, + { + key: 'innerRadius', + help: `Size of the center circle. Value should be between 0~1 as it's a ratio from original radius.`, + type: 'number', + required: false, + defaultValue: defaultProps.innerRadiusRatio, + group: 'Base', + control: { + type: 'range', + min: 0, + max: 0.95, + step: 0.05 + }, + }, + { + key: 'renderRootNode', + help: `Render the root node. By default, the root node is omitted.`, + type: 'boolean', + required: false, + defaultValue: defaultProps.renderRootNode, + control: { type: 'switch' }, + group: 'Base', + }, ...chartDimensions(allFlavors), themeProperty(['svg', 'api']), ordinalColors({ diff --git a/website/src/pages/sunburst/api.tsx b/website/src/pages/sunburst/api.tsx index 7a31b8090..5664d9daa 100644 --- a/website/src/pages/sunburst/api.tsx +++ b/website/src/pages/sunburst/api.tsx @@ -53,6 +53,8 @@ const SunburstApi = () => { value: 'loc', valueFormat: { format: '', enabled: false }, cornerRadius: 2, + innerRadius: 0.4, + renderRootNode: false, borderWidth: 1, borderColor: 'white', colors: { scheme: 'nivo' }, diff --git a/website/src/pages/sunburst/index.js b/website/src/pages/sunburst/index.js index ec61e19e1..e93c79ca4 100644 --- a/website/src/pages/sunburst/index.js +++ b/website/src/pages/sunburst/index.js @@ -25,6 +25,8 @@ const initialProperties = { value: 'loc', valueFormat: { format: '', enabled: false }, cornerRadius: 2, + innerRadius: 0.4, + renderRootNode: false, borderWidth: 1, borderColor: { theme: 'background' }, colors: { scheme: 'nivo' },