From e6bab88aec5cf66aa09fbf1bbe25e73e2bbdb015 Mon Sep 17 00:00:00 2001 From: plouc Date: Thu, 9 Sep 2021 00:40:55 +0900 Subject: [PATCH] feat(radial-bar): add support for labels --- packages/radial-bar/src/RadialBar.tsx | 24 +++++- packages/radial-bar/src/hooks.ts | 17 ++-- packages/radial-bar/src/props.ts | 8 +- packages/radial-bar/src/types.ts | 14 +++- website/src/data/components/pie/props.ts | 4 +- .../src/data/components/radial-bar/meta.yml | 3 +- .../src/data/components/radial-bar/props.ts | 78 ++++++++++++++++++- website/src/pages/radial-bar/index.tsx | 6 ++ 8 files changed, 130 insertions(+), 24 deletions(-) diff --git a/packages/radial-bar/src/RadialBar.tsx b/packages/radial-bar/src/RadialBar.tsx index 241fbffa92..2764136bec 100644 --- a/packages/radial-bar/src/RadialBar.tsx +++ b/packages/radial-bar/src/RadialBar.tsx @@ -1,6 +1,7 @@ import { createElement, Fragment, ReactNode } from 'react' import { Container, useDimensions, SvgWrapper } from '@nivo/core' -import { RadialBarLayerId, RadialBarSvgProps } from './types' +import { ArcLabelsLayer } from '@nivo/arcs' +import { RadialBarLayerId, RadialBarSvgProps, ComputedBar } from './types' import { svgDefaultProps } from './props' import { useRadialBar } from './hooks' import { RadialBarArcs } from './RadialBarArcs' @@ -21,6 +22,11 @@ const InnerRadialBar = ({ layers = svgDefaultProps.layers, colors = svgDefaultProps.colors, cornerRadius = svgDefaultProps.cornerRadius, + enableLabels = svgDefaultProps.enableLabels, + label = svgDefaultProps.label, + labelsSkipAngle = svgDefaultProps.labelsSkipAngle, + labelsRadiusOffset = svgDefaultProps.labelsRadiusOffset, + labelsTextColor = svgDefaultProps.labelsTextColor, isInteractive = svgDefaultProps.isInteractive, tooltip = svgDefaultProps.tooltip, onClick, @@ -52,6 +58,7 @@ const InnerRadialBar = ({ const layerById: Record = { grid: null, bars: null, + labels: null, legends: null, } @@ -84,6 +91,21 @@ const InnerRadialBar = ({ ) } + if (layers.includes('labels') && enableLabels) { + layerById.labels = ( + + key="labels" + center={center} + data={bars} + label={label} + radiusOffset={labelsRadiusOffset} + skipAngle={labelsSkipAngle} + textColor={labelsTextColor} + transitionMode={transitionMode} + /> + ) + } + return ( d3Arc() - .startAngle(d => degreesToRadians(d.startAngle)) - .endAngle(d => degreesToRadians(d.endAngle)) + .startAngle(d => d.startAngle) + .endAngle(d => d.endAngle) .innerRadius(d => d.innerRadius) .outerRadius(d => d.outerRadius) .cornerRadius(cornerRadius), @@ -121,8 +116,8 @@ export const useRadialBar = ({ color: '', stackedValue, arc: { - startAngle: valueScale(currentValue), - endAngle: valueScale(stackedValue), + startAngle: degreesToRadians(valueScale(currentValue)), + endAngle: degreesToRadians(valueScale(stackedValue)), innerRadius, outerRadius, }, diff --git a/packages/radial-bar/src/props.ts b/packages/radial-bar/src/props.ts index ee4f91a0f0..b2eac26ad3 100644 --- a/packages/radial-bar/src/props.ts +++ b/packages/radial-bar/src/props.ts @@ -2,7 +2,7 @@ import { RadialBarLayerId } from './types' import { RadialBarTooltip } from './RadialBarTooltip' export const svgDefaultProps = { - layers: ['grid', 'bars', 'legends'] as RadialBarLayerId[], + layers: ['grid', 'bars', 'labels', 'legends'] as RadialBarLayerId[], startAngle: 0, endAngle: 270, @@ -10,6 +10,12 @@ export const svgDefaultProps = { colors: { scheme: 'nivo' as const }, cornerRadius: 0, + enableLabels: true, + label: 'value', + labelsSkipAngle: 10, + labelsRadiusOffset: 0.5, + labelsTextColor: { theme: 'labels.text.fill' }, + isInteractive: true, tooltip: RadialBarTooltip, diff --git a/packages/radial-bar/src/types.ts b/packages/radial-bar/src/types.ts index 220c16c638..49efe332db 100644 --- a/packages/radial-bar/src/types.ts +++ b/packages/radial-bar/src/types.ts @@ -1,6 +1,6 @@ import { AriaAttributes, FunctionComponent, MouseEvent } from 'react' -import { Theme, Box, Dimensions, ModernMotionProps } from '@nivo/core' -import { Arc, ArcTransitionMode } from '@nivo/arcs' +import { Theme, Box, Dimensions, ModernMotionProps, PropertyAccessor } from '@nivo/core' +import { Arc, ArcLabelsProps, ArcTransitionMode } from '@nivo/arcs' import { OrdinalColorScaleConfig } from '@nivo/colors' export interface RadialBarDatum { @@ -28,14 +28,14 @@ export interface RadialBarDataProps { data: RadialBarSerie[] } -export type RadialBarLayerId = 'grid' | 'bars' | 'legends' +export type RadialBarLayerId = 'grid' | 'bars' | 'labels' | 'legends' export interface RadialBarTooltipProps { bar: ComputedBar } export type RadialBarTooltipComponent = FunctionComponent -export interface RadialBarCommonProps { +export type RadialBarCommonProps = { margin: Box theme: Theme @@ -47,6 +47,12 @@ export interface RadialBarCommonProps { startAngle: number endAngle: number + enableLabels: boolean + label: PropertyAccessor + labelsSkipAngle: ArcLabelsProps['arcLabelsSkipAngle'] + labelsRadiusOffset: ArcLabelsProps['arcLabelsRadiusOffset'] + labelsTextColor: ArcLabelsProps['arcLabelsTextColor'] + isInteractive: boolean tooltip: RadialBarTooltipComponent onClick: (bar: ComputedBar, event: MouseEvent) => void diff --git a/website/src/data/components/pie/props.ts b/website/src/data/components/pie/props.ts index 64f7b5bc47..bc0cd14603 100644 --- a/website/src/data/components/pie/props.ts +++ b/website/src/data/components/pie/props.ts @@ -521,7 +521,7 @@ const props: ChartProperty[] = [ { key: 'activeInnerRadiusOffset', flavors: ['svg', 'canvas'], - help: `Skip label if corresponding slice's angle is lower than provided value.`, + help: `Extends active slice inner radius.`, type: 'number', required: false, defaultValue: defaultProps.activeInnerRadiusOffset, @@ -536,7 +536,7 @@ const props: ChartProperty[] = [ { key: 'activeOuterRadiusOffset', flavors: ['svg', 'canvas'], - help: `Skip label if corresponding slice's angle is lower than provided value.`, + help: `Extends active slice outer radius.`, type: 'number', required: false, defaultValue: defaultProps.activeOuterRadiusOffset, diff --git a/website/src/data/components/radial-bar/meta.yml b/website/src/data/components/radial-bar/meta.yml index 2e010cc274..1763d4d819 100644 --- a/website/src/data/components/radial-bar/meta.yml +++ b/website/src/data/components/radial-bar/meta.yml @@ -11,7 +11,8 @@ RadialBar: - isomorphic stories: [] description: | - Generates a radial bar chart from an array of data. + A radial bar chart. + Note that margin object does not take grid labels into account, so you should adjust it to leave enough room for it. diff --git a/website/src/data/components/radial-bar/props.ts b/website/src/data/components/radial-bar/props.ts index 181a335000..fad7361164 100644 --- a/website/src/data/components/radial-bar/props.ts +++ b/website/src/data/components/radial-bar/props.ts @@ -1,5 +1,4 @@ import { svgDefaultProps } from '@nivo/radial-bar' -import { defaultProps } from '@nivo/pie' import { arcTransitionModes } from '@nivo/arcs' import { themeProperty, motionProperties, groupProperties } from '../../../lib/componentProperties' import { ChartProperty } from '../../../types' @@ -188,9 +187,82 @@ const props: ChartProperty[] = [ step: 1, }, }, + { + key: 'enableLabels', + group: 'Labels', + type: 'boolean', + required: false, + help: 'Enable/disable labels.', + flavors: ['svg'], + defaultValue: svgDefaultProps.enableLabels, + controlType: 'switch', + }, + { + key: 'label', + group: 'Labels', + type: 'string | (bar: ComputedBar) => string', + required: false, + help: + 'Defines how to get label text, can be a string (used to access current bar property) or a function which will receive the actual bar data.', + flavors: ['svg'], + defaultValue: svgDefaultProps.label, + controlType: 'choices', + controlOptions: { + choices: ['category', 'groupId', 'value', 'formattedValue'].map(choice => ({ + label: choice, + value: choice, + })), + }, + }, + { + key: 'labelsSkipAngle', + group: 'Labels', + type: 'number', + required: false, + help: `Skip label if corresponding arc's angle is lower than provided value.`, + flavors: ['svg'], + defaultValue: svgDefaultProps.labelsSkipAngle, + controlType: 'range', + controlOptions: { + unit: '°', + min: 0, + max: 45, + step: 1, + }, + }, + { + key: 'labelsRadiusOffset', + group: 'Labels', + type: 'number', + required: false, + help: ` + Define the radius to use to determine the label position, starting from inner radius, + this is expressed as a ratio. Centered at 0.5 by default. + `, + flavors: ['svg'], + defaultValue: svgDefaultProps.labelsRadiusOffset, + controlType: 'range', + controlOptions: { + min: 0, + max: 2, + step: 0.05, + }, + }, + { + key: 'labelsTextColor', + group: 'Labels', + help: 'Defines how to compute label text color.', + type: 'string | object | Function', + required: false, + flavors: ['svg'], + defaultValue: svgDefaultProps.labelsTextColor, + controlType: 'inheritedColor', + }, { key: 'layers', group: 'Customization', + type: '(RadialBarLayerId | RadialBarCustomLayer)[]', + required: false, help: 'Defines the order of layers and add custom layers.', description: ` You can also use this to insert extra layers @@ -199,8 +271,6 @@ const props: ChartProperty[] = [ The layer function which will receive the chart's context & computed data and must return a valid SVG element. `, - required: false, - type: '(RadarLayerId | FunctionComponent)[]', flavors: ['svg'], defaultValue: svgDefaultProps.layers, }, @@ -293,7 +363,7 @@ const props: ChartProperty[] = [ help: 'Define how transitions behave.', type: 'string', required: false, - defaultValue: defaultProps.transitionMode, + defaultValue: svgDefaultProps.transitionMode, controlType: 'choices', group: 'Motion', controlOptions: { diff --git a/website/src/pages/radial-bar/index.tsx b/website/src/pages/radial-bar/index.tsx index 94d7723e11..2affde6f31 100644 --- a/website/src/pages/radial-bar/index.tsx +++ b/website/src/pages/radial-bar/index.tsx @@ -29,6 +29,12 @@ const initialProperties: UnmappedRadarProps = { colors: svgDefaultProps.colors, cornerRadius: 2, + enableLabels: svgDefaultProps.enableLabels, + label: svgDefaultProps.label, + labelsSkipAngle: svgDefaultProps.labelsSkipAngle, + labelsRadiusOffset: svgDefaultProps.labelsRadiusOffset, + labelsTextColor: svgDefaultProps.labelsTextColor, + animate: true, motionConfig: 'gentle' as const, transitionMode: 'pushOut' as const,