From 9fd5cee1d6fe98fc10d497aaaac167afb44681f1 Mon Sep 17 00:00:00 2001 From: plouc Date: Thu, 5 Nov 2020 08:19:42 +0900 Subject: [PATCH] feat(pie): init pie package migration to typescript --- packages/pie/index.d.ts | 151 -------------- packages/pie/package.json | 2 +- packages/pie/src/{Pie.js => Pie.tsx} | 104 +++++----- .../pie/src/{PieSlice.js => PieSlice.tsx} | 45 ++--- .../pie/src/{PieTooltip.js => PieTooltip.tsx} | 11 +- .../src/{RadialLabel.js => RadialLabel.tsx} | 36 +--- packages/pie/src/RadialLabels.js | 59 ------ packages/pie/src/RadialLabels.tsx | 63 ++++++ .../{ResponsivePie.js => ResponsivePie.tsx} | 7 +- packages/pie/src/SliceLabels.js | 44 ++-- packages/pie/src/definitions.ts | 171 ++++++++++++++++ packages/pie/src/{hooks.js => hooks.ts} | 191 ++++++++++++++---- packages/pie/src/{index.js => index.ts} | 1 + packages/pie/src/props.js | 7 +- packages/pie/tsconfig.json | 8 + 15 files changed, 514 insertions(+), 386 deletions(-) delete mode 100644 packages/pie/index.d.ts rename packages/pie/src/{Pie.js => Pie.tsx} (64%) rename packages/pie/src/{PieSlice.js => PieSlice.tsx} (67%) rename packages/pie/src/{PieTooltip.js => PieTooltip.tsx} (70%) rename packages/pie/src/{RadialLabel.js => RadialLabel.tsx} (55%) delete mode 100644 packages/pie/src/RadialLabels.js create mode 100644 packages/pie/src/RadialLabels.tsx rename packages/pie/src/{ResponsivePie.js => ResponsivePie.tsx} (59%) create mode 100644 packages/pie/src/definitions.ts rename packages/pie/src/{hooks.js => hooks.ts} (68%) rename packages/pie/src/{index.js => index.ts} (94%) create mode 100644 packages/pie/tsconfig.json diff --git a/packages/pie/index.d.ts b/packages/pie/index.d.ts deleted file mode 100644 index 79c94eabb3..0000000000 --- a/packages/pie/index.d.ts +++ /dev/null @@ -1,151 +0,0 @@ -import * as React from 'react' -import { Box, Dimensions, Theme, MotionProps, SvgDefsAndFill } from '@nivo/core' -import { OrdinalColorsInstruction, InheritedColorProp } from '@nivo/colors' -import { LegendProps } from '@nivo/legends' - -declare module '@nivo/pie' { - export type DatumId = string | number - export type DatumValue = number - export type DatumFormattedValue = string | number - - // Default datum to use when `id` and `value` properties - // use default values, should be redefined if using - // a different structure. - export interface DefaultRawDatum { - id: DatumId - value: DatumValue - } - - export interface PieArc { - index: number - startAngle: number - endAngle: number - // center angle - angle: number - angleDeg: number - padAngle: number - } - - export interface ComputedDatum { - id: DatumId - value: DatumValue - formattedValue: DatumFormattedValue - color: string - // only defined in case gradients or patterns are used - // and the datum matches one of the rules. - fill?: string - // contains the raw datum as passed to the chart - data: R - arc: PieArc - } - - export type DatumIdAccessorFunction = (datum: R) => DatumId - export type DatumValueAccessorFunction = (datum: R) => DatumValue - export type ValueFormatter = (value: number) => string | number - export type LabelAccessorFunction = (datum: ComputedDatum) => string | number - - export interface DataProps { - data: R[] - id?: string | DatumIdAccessorFunction - value?: string | DatumValueAccessorFunction - } - - export interface PieTooltipProps { - datum: ComputedDatum - } - - export type MouseEventHandler = ( - datum: ComputedDatum, - event: React.MouseEvent - ) => void - - export type PieArcGenerator = (arc: PieArc) => string - - export type PieLayerId = 'slices' | 'radialLabels' | 'sliceLabels' | 'legends' - - export interface PieCustomLayerProps { - dataWithArc: ComputedDatum[] - centerX: number - centerY: number - radius: number - innerRadius: number - arcGenerator: PieArcGenerator - } - - export type PieCustomLayer = React.FC> - - export type PieLayer = PieLayerId | PieCustomLayer - - export type CommonPieProps = MotionProps & - Partial<{ - margin: Box - sortByValue: boolean - innerRadius: number - padAngle: number - cornerRadius: number - startAngle: number - endAngle: number - fit: boolean - - // colors, theme and border - colors: OrdinalColorsInstruction, 'color' | 'fill'>> - theme: Theme - borderWidth: number - borderColor: InheritedColorProp> - - // radial labels - enableRadialLabels: boolean - radialLabel: string | LabelAccessorFunction - radialLabelsSkipAngle: number - radialLabelsTextXOffset: number - radialLabelsTextColor: InheritedColorProp> - radialLabelsLinkOffset: number - radialLabelsLinkDiagonalLength: number - radialLabelsLinkHorizontalLength: number - radialLabelsLinkStrokeWidth: number - radialLabelsLinkColor: InheritedColorProp> - - // slices labels - enableSlicesLabels: boolean - sliceLabel: string | LabelAccessorFunction - sliceLabelsRadiusOffset: number - slicesLabelsSkipAngle: number - slicesLabelsTextColor: InheritedColorProp> - - // interactivity - isInteractive: boolean - tooltip: React.FC> - - legends: LegendProps[] - }> - - export type PieSvgProps = DataProps & - CommonPieProps & - SvgDefsAndFill> & - Partial<{ - layers: PieLayer[] - onClick: MouseEventHandler - onMouseEnter: MouseEventHandler - onMouseLeave: MouseEventHandler - role: string - }> - - export class Pie extends React.Component & Dimensions> {} - export class ResponsivePie extends React.Component> {} - - export type PieCanvasProps = DataProps & - CommonPieProps & - Partial<{ - pixelRatio: number - onClick: MouseEventHandler - onMouseEnter: MouseEventHandler - onMouseLeave: MouseEventHandler - }> - - export class PieCanvas extends React.Component< - PieCanvasProps & Dimensions - > {} - export class ResponsivePieCanvas extends React.Component< - PieCanvasProps - > {} -} diff --git a/packages/pie/package.json b/packages/pie/package.json index 9e769df64e..661812a67b 100644 --- a/packages/pie/package.json +++ b/packages/pie/package.json @@ -16,10 +16,10 @@ ], "main": "./dist/nivo-pie.cjs.js", "module": "./dist/nivo-pie.es.js", + "typings": "./dist/index.d.ts", "files": [ "README.md", "LICENSE.md", - "index.d.ts", "dist/" ], "dependencies": { diff --git a/packages/pie/src/Pie.js b/packages/pie/src/Pie.tsx similarity index 64% rename from packages/pie/src/Pie.js rename to packages/pie/src/Pie.tsx index 383467d47e..88380acf5a 100644 --- a/packages/pie/src/Pie.js +++ b/packages/pie/src/Pie.tsx @@ -6,76 +6,80 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -import React, { Fragment, createElement } from 'react' +import React, { ReactNode, Fragment, createElement } from 'react' +// @ts-ignore import { withContainer, SvgWrapper, bindDefs, useTheme, useDimensions } from '@nivo/core' -import { useInheritedColor } from '@nivo/colors' -import { PieSvgDefaultProps, PieSvgPropTypes } from './props' +// @ts-ignore +import { useInheritedColor, OrdinalColorsInstruction, InheritedColorProp } from '@nivo/colors' import { PieSlice } from './PieSlice' import { RadialLabels } from './RadialLabels' import { SliceLabels } from './SliceLabels' import PieLegends from './PieLegends' import { useNormalizedData, usePieFromBox, usePieLayerContext } from './hooks' +import { ComputedDatum, PieLayer, PieSvgProps, PieLayerId } from './definitions' +import { defaultProps } from './props' -const Pie = ({ +// prettier-ignore +const Pie = ({ data, - id, - value, + id = defaultProps.id, + value = defaultProps.value, valueFormat, - sortByValue, + sortByValue = defaultProps.sortByValue, - layers, + layers = defaultProps.layers as PieLayer[], - startAngle, - endAngle, - padAngle, - fit, - innerRadius: innerRadiusRatio, - cornerRadius, + startAngle = defaultProps.startAngle, + endAngle = defaultProps.endAngle, + padAngle = defaultProps.padAngle, + fit = defaultProps.fit, + innerRadius: innerRadiusRatio = defaultProps.innerRadius, + cornerRadius = defaultProps.cornerRadius, width, height, margin: partialMargin, - colors, + colors = defaultProps.colors as OrdinalColorsInstruction, // border - borderWidth, - borderColor: _borderColor, + borderWidth = defaultProps.borderWidth, + borderColor: _borderColor = defaultProps.borderColor as InheritedColorProp>, // radial labels - radialLabel, - enableRadialLabels, - radialLabelsSkipAngle, - radialLabelsLinkOffset, - radialLabelsLinkDiagonalLength, - radialLabelsLinkHorizontalLength, - radialLabelsLinkStrokeWidth, - radialLabelsTextXOffset, - radialLabelsTextColor, - radialLabelsLinkColor, + radialLabel = defaultProps.radialLabel, + enableRadialLabels = defaultProps.enableRadialLabels, + radialLabelsSkipAngle = defaultProps.radialLabelsSkipAngle, + radialLabelsLinkOffset = defaultProps.radialLabelsLinkOffset, + radialLabelsLinkDiagonalLength = defaultProps.radialLabelsLinkDiagonalLength, + radialLabelsLinkHorizontalLength = defaultProps.radialLabelsLinkHorizontalLength, + radialLabelsLinkStrokeWidth = defaultProps.radialLabelsLinkStrokeWidth, + radialLabelsTextXOffset = defaultProps.radialLabelsTextXOffset, + radialLabelsTextColor = defaultProps.radialLabelsTextColor, + radialLabelsLinkColor = defaultProps.radialLabelsLinkColor, // slices labels - sliceLabel, - enableSliceLabels, - sliceLabelsSkipAngle, - sliceLabelsTextColor, - sliceLabelsRadiusOffset, + sliceLabel = defaultProps.sliceLabel, + enableSliceLabels = defaultProps.enableSliceLabels, + sliceLabelsSkipAngle = defaultProps.sliceLabelsSkipAngle, + sliceLabelsTextColor = defaultProps.sliceLabelsTextColor, + sliceLabelsRadiusOffset = defaultProps.sliceLabelsRadiusOffset, // styling - defs, - fill, + defs = defaultProps.defs, + fill = defaultProps.fill, // interactivity - isInteractive, + isInteractive = defaultProps.isInteractive, onClick, onMouseEnter, onMouseMove, onMouseLeave, - tooltip, + tooltip = defaultProps.tooltip, - legends, - role, -}) => { + legends = defaultProps.legends, + role = defaultProps.role, +}: PieSvgProps) => { const theme = useTheme() const { outerWidth, outerHeight, margin, innerWidth, innerHeight } = useDimensions( @@ -84,7 +88,7 @@ const Pie = ({ partialMargin ) - const normalizedData = useNormalizedData({ + const normalizedData = useNormalizedData({ data, id, value, @@ -109,7 +113,7 @@ const Pie = ({ const boundDefs = bindDefs(defs, dataWithArc, fill) - const layerById = { + const layerById: Record = { slices: null, radialLabels: null, sliceLabels: null, @@ -120,7 +124,7 @@ const Pie = ({ layerById.slices = ( {dataWithArc.map(datumWithArc => ( - key={datumWithArc.id} datum={datumWithArc} path={arcGenerator(datumWithArc.arc)} @@ -141,7 +145,7 @@ const Pie = ({ if (enableRadialLabels && layers.includes('radialLabels')) { layerById.radialLabels = ( - dataWithArc={dataWithArc} radius={radius} label={radialLabel} @@ -162,6 +166,7 @@ const Pie = ({ layerById.sliceLabels = ( ) } - const layerContext = usePieLayerContext({ + const layerContext = usePieLayerContext({ dataWithArc, arcGenerator, centerX, @@ -205,8 +211,8 @@ const Pie = ({ role={role} > {layers.map((layer, i) => { - if (layerById[layer] !== undefined) { - return layerById[layer] + if (layerById[layer as PieLayerId] !== undefined) { + return layerById[layer as PieLayerId] } if (typeof layer === 'function') { @@ -219,8 +225,4 @@ const Pie = ({ ) } -Pie.displayName = 'Pie' -Pie.propTypes = PieSvgPropTypes -Pie.defaultProps = PieSvgDefaultProps - -export default withContainer(Pie) +export default withContainer(Pie) as (props: PieSvgProps) => JSX.Element diff --git a/packages/pie/src/PieSlice.js b/packages/pie/src/PieSlice.tsx similarity index 67% rename from packages/pie/src/PieSlice.js rename to packages/pie/src/PieSlice.tsx index d47a13c3c7..34536b5a98 100644 --- a/packages/pie/src/PieSlice.js +++ b/packages/pie/src/PieSlice.tsx @@ -7,11 +7,25 @@ * file that was distributed with this source code. */ import React, { createElement, useCallback } from 'react' -import PropTypes from 'prop-types' +// @ts-ignore import { useTooltip } from '@nivo/tooltip' -import { PiePropTypes, datumWithArcPropType } from './props' +import { ComputedDatum, CompletePieSvgProps } from './definitions' -export const PieSlice = ({ +interface PieSliceProps { + datum: ComputedDatum + path: string + borderWidth: CompletePieSvgProps['borderWidth'] + borderColor: string + isInteractive: CompletePieSvgProps['isInteractive'] + tooltip: CompletePieSvgProps['tooltip'] + onClick: CompletePieSvgProps['onClick'] + onMouseEnter: CompletePieSvgProps['onMouseEnter'] + onMouseMove: CompletePieSvgProps['onMouseMove'] + onMouseLeave: CompletePieSvgProps['onMouseLeave'] +} + +// prettier-ignore +export const PieSlice = ({ datum, path, borderWidth, @@ -22,7 +36,7 @@ export const PieSlice = ({ onMouseMove, onMouseLeave, tooltip, -}) => { +}: PieSliceProps) => { const { showTooltipFromEvent, hideTooltip } = useTooltip() const handleTooltip = useCallback( @@ -32,7 +46,7 @@ export const PieSlice = ({ const handleMouseEnter = useCallback( event => { - onMouseEnter && onMouseEnter(datum, event) + if (onMouseEnter) onMouseEnter(datum, event) handleTooltip(event) }, [onMouseEnter, handleTooltip, datum] @@ -40,7 +54,7 @@ export const PieSlice = ({ const handleMouseMove = useCallback( event => { - onMouseMove && onMouseMove(datum, event) + if (onMouseMove) onMouseMove(datum, event) handleTooltip(event) }, [onMouseMove, handleTooltip, datum] @@ -48,7 +62,7 @@ export const PieSlice = ({ const handleMouseLeave = useCallback( event => { - onMouseLeave && onMouseLeave(datum, event) + if (onMouseLeave) onMouseLeave(datum, event) hideTooltip(event) }, [onMouseLeave, hideTooltip, datum] @@ -56,7 +70,7 @@ export const PieSlice = ({ const handleClick = useCallback( event => { - onClick && onClick(datum, event) + if (onClick) onClick(datum, event) }, [onClick, datum] ) @@ -74,18 +88,3 @@ export const PieSlice = ({ /> ) } - -PieSlice.propTypes = { - datum: datumWithArcPropType.isRequired, - - path: PropTypes.string.isRequired, - borderWidth: PiePropTypes.borderWidth, - borderColor: PropTypes.string.isRequired, - - isInteractive: PiePropTypes.isInteractive, - tooltip: PiePropTypes.tooltip, - onClick: PiePropTypes.onClick, - onMouseEnter: PiePropTypes.onMouseEnter, - onMouseMove: PiePropTypes.onMouseMove, - onMouseLeave: PiePropTypes.onMouseLeave, -} diff --git a/packages/pie/src/PieTooltip.js b/packages/pie/src/PieTooltip.tsx similarity index 70% rename from packages/pie/src/PieTooltip.js rename to packages/pie/src/PieTooltip.tsx index c84e861c3d..dd967ea0f3 100644 --- a/packages/pie/src/PieTooltip.js +++ b/packages/pie/src/PieTooltip.tsx @@ -7,20 +7,17 @@ * file that was distributed with this source code. */ import React from 'react' -import PropTypes from 'prop-types' import { BasicTooltip } from '@nivo/tooltip' +import { ComputedDatum } from './definitions' -export const PieTooltip = ({ datum }) => ( +// prettier-ignore +export const PieTooltip = ({ datum }: { datum: ComputedDatum }) => ( ) -PieTooltip.propTypes = { - datum: PropTypes.object.isRequired, -} - export default PieTooltip diff --git a/packages/pie/src/RadialLabel.js b/packages/pie/src/RadialLabel.tsx similarity index 55% rename from packages/pie/src/RadialLabel.js rename to packages/pie/src/RadialLabel.tsx index 08ffa1c070..e35bb57d18 100644 --- a/packages/pie/src/RadialLabel.js +++ b/packages/pie/src/RadialLabel.tsx @@ -7,16 +7,21 @@ * file that was distributed with this source code. */ import React from 'react' -import PropTypes from 'prop-types' +// @ts-ignore import { line } from 'd3-shape' +// @ts-ignore import { textPropsByEngine, useTheme } from '@nivo/core' -import { datumWithArcPropType, PiePropTypes } from './props' +import { RadialLabelData, Point } from './definitions' const lineGenerator = line() - .x(d => d.x) - .y(d => d.y) + .x((d: Point) => d.x) + .y((d: Point) => d.y) -export const RadialLabel = ({ label, linkStrokeWidth }) => { +// prettier-ignore +export const RadialLabel = ({ label, linkStrokeWidth }: { + label: RadialLabelData + linkStrokeWidth: number +}) => { const theme = useTheme() return ( @@ -42,24 +47,3 @@ export const RadialLabel = ({ label, linkStrokeWidth }) => { ) } - -RadialLabel.propTypes = { - label: PropTypes.shape({ - line: PropTypes.arrayOf( - PropTypes.shape({ - x: PropTypes.number, - y: PropTypes.number, - }) - ).isRequired, - position: PropTypes.shape({ - x: PropTypes.number, - y: PropTypes.number, - }).isRequired, - textColor: PropTypes.string.isRequired, - linkColor: PropTypes.string.isRequired, - align: PropTypes.string.isRequired, - text: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - datum: datumWithArcPropType.isRequired, - }).isRequired, - linkStrokeWidth: PiePropTypes.radialLabelsLinkStrokeWidth, -} diff --git a/packages/pie/src/RadialLabels.js b/packages/pie/src/RadialLabels.js deleted file mode 100644 index 083f3bdd24..0000000000 --- a/packages/pie/src/RadialLabels.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import React from 'react' -import PropTypes from 'prop-types' -import { datumWithArcPropType, PiePropTypes } from './props' -import { usePieRadialLabels } from './hooks' -import { RadialLabel } from './RadialLabel' - -export const RadialLabels = ({ - dataWithArc, - label, - radius, - skipAngle, - linkOffset, - linkDiagonalLength, - linkHorizontalLength, - linkStrokeWidth, - textXOffset, - textColor, - linkColor, -}) => { - const radialLabels = usePieRadialLabels({ - enable: true, - dataWithArc, - label, - textXOffset, - textColor, - radius, - skipAngle, - linkOffset, - linkDiagonalLength, - linkHorizontalLength, - linkColor, - }) - - return radialLabels.map(label => ( - - )) -} - -RadialLabels.propTypes = { - dataWithArc: PropTypes.arrayOf(datumWithArcPropType).isRequired, - label: PiePropTypes.radialLabel, - skipAngle: PiePropTypes.radialLabelsSkipAngle, - radius: PropTypes.number.isRequired, - linkOffset: PiePropTypes.radialLabelsLinkOffset, - linkDiagonalLength: PiePropTypes.radialLabelsLinkDiagonalLength, - linkHorizontalLength: PiePropTypes.radialLabelsLinkHorizontalLength, - linkStrokeWidth: PiePropTypes.radialLabelsLinkStrokeWidth, - textXOffset: PiePropTypes.radialLabelsTextXOffset, - textColor: PiePropTypes.radialLabelsTextColor, - linkColor: PiePropTypes.radialLabelsLinkColor, -} diff --git a/packages/pie/src/RadialLabels.tsx b/packages/pie/src/RadialLabels.tsx new file mode 100644 index 0000000000..fd8921f81d --- /dev/null +++ b/packages/pie/src/RadialLabels.tsx @@ -0,0 +1,63 @@ +/* + * This file is part of the nivo project. + * + * Copyright 2016-present, Raphaël Benitte. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +import React from 'react' +import { usePieRadialLabels } from './hooks' +import { RadialLabel } from './RadialLabel' +import { ComputedDatum, CompletePieSvgProps } from './definitions' + +interface RadialLabelsProps { + dataWithArc: ComputedDatum[] + label: CompletePieSvgProps['radialLabel'] + skipAngle: CompletePieSvgProps['radialLabelsSkipAngle'] + radius: number + linkOffset: CompletePieSvgProps['radialLabelsLinkOffset'] + linkDiagonalLength: CompletePieSvgProps['radialLabelsLinkDiagonalLength'] + linkHorizontalLength: CompletePieSvgProps['radialLabelsLinkHorizontalLength'] + linkStrokeWidth: CompletePieSvgProps['radialLabelsLinkStrokeWidth'] + textXOffset: CompletePieSvgProps['radialLabelsTextXOffset'] + textColor: CompletePieSvgProps['radialLabelsTextColor'] + linkColor: CompletePieSvgProps['radialLabelsLinkColor'] +} + +// prettier-ignore +export const RadialLabels = ({ + dataWithArc, + label, + radius, + skipAngle, + linkOffset, + linkDiagonalLength, + linkHorizontalLength, + linkStrokeWidth, + textXOffset, + textColor, + linkColor, +}: RadialLabelsProps) => { + const radialLabels = usePieRadialLabels({ + enable: true, + dataWithArc, + label, + textXOffset, + textColor, + radius, + skipAngle, + linkOffset, + linkDiagonalLength, + linkHorizontalLength, + linkColor, + }) + + return ( + <> + {radialLabels.map(labelData => ( + + ))} + + ) +} diff --git a/packages/pie/src/ResponsivePie.js b/packages/pie/src/ResponsivePie.tsx similarity index 59% rename from packages/pie/src/ResponsivePie.js rename to packages/pie/src/ResponsivePie.tsx index 96b75a8e88..f52053c90d 100644 --- a/packages/pie/src/ResponsivePie.js +++ b/packages/pie/src/ResponsivePie.tsx @@ -7,12 +7,15 @@ * file that was distributed with this source code. */ import React from 'react' +// @ts-ignore import { ResponsiveWrapper } from '@nivo/core' import Pie from './Pie' +import { PieSvgProps } from './definitions' -const ResponsivePie = props => ( +// prettier-ignore +const ResponsivePie = (props: Omit, 'width' | 'height'>) => ( - {({ width, height }) => } + {({ width, height }: { width: number, height: number }) => width={width} height={height} {...props} />} ) diff --git a/packages/pie/src/SliceLabels.js b/packages/pie/src/SliceLabels.js index 974991cd85..37527d505e 100644 --- a/packages/pie/src/SliceLabels.js +++ b/packages/pie/src/SliceLabels.js @@ -38,26 +38,30 @@ export const SliceLabels = ({ textColor, }) - return labels.map(label => { - return ( - - - {label.label} - - - ) - }) + return ( + <> + {labels.map(label => { + return ( + + + {label.label} + + + ) + })} + + ) } SliceLabels.propTypes = { diff --git a/packages/pie/src/definitions.ts b/packages/pie/src/definitions.ts new file mode 100644 index 0000000000..b8fd382f7d --- /dev/null +++ b/packages/pie/src/definitions.ts @@ -0,0 +1,171 @@ +import * as React from 'react' +import { Box, Dimensions, Theme, SvgDefsAndFill } from '@nivo/core' +import { OrdinalColorsInstruction, InheritedColorProp } from '@nivo/colors' +import { LegendProps } from '@nivo/legends' + +export type DatumId = string | number +export type DatumValue = number +export type DatumFormattedValue = string | number +export type ValueFormatter = (value: number) => DatumFormattedValue + +// Default datum to use when `id` and `value` properties +// use default values, should be redefined if using +// a different structure. +export interface DefaultRawDatum { + id: DatumId + value: DatumValue +} + +export interface PieArc { + index: number + startAngle: number + endAngle: number + // center angle + angle: number + angleDeg: number + padAngle: number +} + +export interface ComputedDatum { + id: DatumId + label: DatumId + value: DatumValue + formattedValue: DatumFormattedValue + color: string + // only defined in case gradients or patterns are used + // and the datum matches one of the rules. + fill?: string + // contains the raw datum as passed to the chart + data: R + arc: PieArc +} + +export type DatumIdAccessorFunction = (datum: R) => DatumId +export type DatumValueAccessorFunction = (datum: R) => DatumValue +export type LabelAccessorFunction = (datum: ComputedDatum) => string | number + +export interface DataProps { + data: R[] +} + +export interface PieTooltipProps { + datum: ComputedDatum +} + +export type MouseEventHandler = ( + datum: ComputedDatum, + event: React.MouseEvent +) => void + +export type PieArcGenerator = (arc: PieArc) => string + +export type PieLayerId = 'slices' | 'radialLabels' | 'sliceLabels' | 'legends' + +export interface PieCustomLayerProps { + dataWithArc: ComputedDatum[] + centerX: number + centerY: number + radius: number + innerRadius: number + arcGenerator: PieArcGenerator +} + +export type PieCustomLayer = React.FC> + +export type PieLayer = PieLayerId | PieCustomLayer + +export type CommonPieProps = Dimensions & { + id: string | DatumIdAccessorFunction + value: string | DatumValueAccessorFunction + valueFormat?: ValueFormatter + + margin: Box + sortByValue: boolean + innerRadius: number + padAngle: number + cornerRadius: number + startAngle: number + endAngle: number + fit: boolean + + // colors, theme and border + colors: OrdinalColorsInstruction, 'color' | 'fill'>> + theme: Theme + borderWidth: number + borderColor: InheritedColorProp> + + // radial labels + enableRadialLabels: boolean + radialLabel: string | LabelAccessorFunction + radialLabelsSkipAngle: number + radialLabelsTextXOffset: number + radialLabelsTextColor: InheritedColorProp> + radialLabelsLinkOffset: number + radialLabelsLinkDiagonalLength: number + radialLabelsLinkHorizontalLength: number + radialLabelsLinkStrokeWidth: number + radialLabelsLinkColor: InheritedColorProp> + + // slices labels + enableSliceLabels: boolean + sliceLabel: string | LabelAccessorFunction + sliceLabelsRadiusOffset: number + sliceLabelsSkipAngle: number + sliceLabelsTextColor: InheritedColorProp> + + // interactivity + isInteractive: boolean + tooltip: React.FC> + + legends: LegendProps[] + + role: string +} + +export type PieHandlers = { + onClick?: MouseEventHandler + onMouseEnter?: MouseEventHandler + onMouseMove?: MouseEventHandler + onMouseLeave?: MouseEventHandler +} + +export type PieSvgProps = DataProps & + Partial> & + SvgDefsAndFill> & + PieHandlers & { + layers?: PieLayer[] + } + +export type CompletePieSvgProps = DataProps & + CommonPieProps & + SvgDefsAndFill> & + PieHandlers & { + layers: PieLayer[] + } + +export type PieCanvasProps = DataProps & + Partial> & + PieHandlers & { + pixelRatio?: number + } + +export type CompletePieCanvasProps = DataProps & + CommonPieProps & + PieHandlers & { + pixelRatio: number + } + +export type Point = { + x: number + y: number +} + +export type RadialLabelData = { + text: string | number + textColor: string + position: Point + align: string + line: [Point, Point, Point] + linkColor: string + datum: ComputedDatum +} diff --git a/packages/pie/src/hooks.js b/packages/pie/src/hooks.ts similarity index 68% rename from packages/pie/src/hooks.js rename to packages/pie/src/hooks.ts index ae67932aec..303f020e75 100644 --- a/packages/pie/src/hooks.js +++ b/packages/pie/src/hooks.ts @@ -7,58 +7,94 @@ * file that was distributed with this source code. */ import { useMemo } from 'react' -import get from 'lodash/get' +import { get } from 'lodash' +// @ts-ignore import { arc as d3Arc, pie as d3Pie } from 'd3-shape' import { + // @ts-ignore degreesToRadians, + // @ts-ignore radiansToDegrees, + // @ts-ignore useValueFormatter, + // @ts-ignore computeArcBoundingBox, + // @ts-ignore useTheme, + // @ts-ignore positionFromAngle, + // @ts-ignore midAngle, + // @ts-ignore getLabelGenerator, + // @ts-ignore absoluteAngleRadians, + // @ts-ignore absoluteAngleDegrees, } from '@nivo/core' -import { getOrdinalColorScale, useInheritedColor } from '@nivo/colors' +import { + // @ts-ignore + getOrdinalColorScale, + // @ts-ignore + useInheritedColor, + OrdinalColorsInstruction, + InheritedColorProp, +} from '@nivo/colors' import { PieDefaultProps } from './props' +import { + CompletePieSvgProps, + ComputedDatum, + PieArc, + PieArcGenerator, + LabelAccessorFunction, + PieCustomLayerProps, + RadialLabelData, +} from './definitions' /** * Format data so that we get a consistent data structure. * It will also add the `formattedValue` and `color` property. */ -export const useNormalizedData = ({ +export const useNormalizedData = ({ data, id = PieDefaultProps.id, value = PieDefaultProps.value, valueFormat, - colors = PieDefaultProps.colors, -}) => { - const getId = useMemo(() => (typeof id === 'function' ? id : d => get(d, id)), [id]) - const getValue = useMemo(() => (typeof value === 'function' ? value : d => get(d, value)), [ - value, - ]) - const formatValue = useValueFormatter(valueFormat) + colors = PieDefaultProps.colors as OrdinalColorsInstruction, +}: Pick, 'id' | 'value' | 'valueFormat' | 'colors'> & { + data: R[] +}): Omit< + ComputedDatum, + 'arc' | 'fill' +>[] => { + const getId = useMemo(() => (typeof id === 'function' ? id : (d: R) => get(d, id)), [id]) + const getValue = useMemo( + () => (typeof value === 'function' ? value : (d: R) => get(d, value)), + [value] + ) + const formatValue = useValueFormatter(valueFormat as any) const getColor = useMemo(() => getOrdinalColorScale(colors, 'id'), [colors]) return useMemo( () => - data.map(datum => { + data.map((datum) => { const datumId = getId(datum) const datumValue = getValue(datum) - const normalizedDatum = { + const normalizedDatum: Omit, 'arc' | 'color' | 'fill'> = { id: datumId, + // @ts-ignore label: datum.label || datumId, value: datumValue, formattedValue: formatValue(datumValue), data: datum, } - normalizedDatum.color = getColor(normalizedDatum) - return normalizedDatum + return { + ...normalizedDatum, + color: getColor(normalizedDatum), + } }), [data, getId, getValue, formatValue, getColor] ) @@ -67,42 +103,54 @@ export const useNormalizedData = ({ /** * Compute arcs, which don't depend yet on radius. */ -export const usePieArcs = ({ +export const usePieArcs = ({ data, startAngle = PieDefaultProps.startAngle, endAngle = PieDefaultProps.endAngle, padAngle = PieDefaultProps.padAngle, sortByValue = PieDefaultProps.sortByValue, -}) => { +}: { + data: Omit, 'arc' | 'fill'>[] + startAngle: number + endAngle: number + padAngle: number + sortByValue: boolean +}): Omit, 'fill'>[] => { const pie = useMemo(() => { - const pie = d3Pie() - .value(d => d.value) + const innerPie = d3Pie() + .value((d: Omit, 'arc' | 'fill'>) => d.value) .padAngle(degreesToRadians(padAngle)) .startAngle(degreesToRadians(startAngle)) .endAngle(degreesToRadians(endAngle)) - if (sortByValue !== true) pie.sortValues(null) + if (sortByValue !== true) innerPie.sortValues(null) - return pie + return innerPie }, [startAngle, endAngle, padAngle, sortByValue]) return useMemo( () => - pie(data).map(arc => { - const angle = Math.abs(arc.endAngle - arc.startAngle) - - return { - ...arc.data, - arc: { - index: arc.index, - startAngle: arc.startAngle, - endAngle: arc.endAngle, - padAngle: arc.padAngle, - angle, - angleDeg: radiansToDegrees(angle), - }, + pie(data).map( + ( + arc: Omit & { + data: Omit, 'arc' | 'fill'> + } + ) => { + const angle = Math.abs(arc.endAngle - arc.startAngle) + + return { + ...arc.data, + arc: { + index: arc.index, + startAngle: arc.startAngle, + endAngle: arc.endAngle, + padAngle: arc.padAngle, + angle, + angleDeg: radiansToDegrees(angle), + }, + } } - }), + ), [pie, data] ) @@ -112,7 +160,11 @@ export const usePieArcGenerator = ({ radius, innerRadius, cornerRadius = PieDefaultProps.cornerRadius, -}) => +}: { + radius: number + innerRadius: number + cornerRadius: number +}): PieArcGenerator => useMemo(() => d3Arc().outerRadius(radius).innerRadius(innerRadius).cornerRadius(cornerRadius), [ radius, innerRadius, @@ -123,7 +175,7 @@ export const usePieArcGenerator = ({ * Compute pie layout using explicit radius/innerRadius, * expressed in pixels. */ -export const usePie = ({ +export const usePie = ({ data, radius, innerRadius, @@ -132,6 +184,13 @@ export const usePie = ({ padAngle = PieDefaultProps.padAngle, sortByValue = PieDefaultProps.sortByValue, cornerRadius = PieDefaultProps.cornerRadius, +}: Pick< + CompletePieSvgProps, + 'startAngle' | 'endAngle' | 'padAngle' | 'sortByValue' | 'cornerRadius' +> & { + data: Omit, 'arc'>[] + radius: number + innerRadius: number }) => { const dataWithArc = usePieArcs({ data, @@ -158,7 +217,7 @@ export const usePie = ({ * It also returns `centerX`/`centerY` as those can be altered * if `fit` is `true`. */ -export const usePieFromBox = ({ +export const usePieFromBox = ({ data, width, height, @@ -169,6 +228,19 @@ export const usePieFromBox = ({ sortByValue = PieDefaultProps.sortByValue, cornerRadius = PieDefaultProps.cornerRadius, fit = PieDefaultProps.fit, +}: Pick< + CompletePieSvgProps, + | 'width' + | 'height' + | 'innerRadius' + | 'startAngle' + | 'endAngle' + | 'padAngle' + | 'sortByValue' + | 'cornerRadius' + | 'fit' +> & { + data: Omit, 'arc'>[] }) => { const dataWithArc = usePieArcs({ data, @@ -196,7 +268,12 @@ export const usePieFromBox = ({ ) const ratio = Math.min(width / box.width, height / box.height) - const adjustedBox = { + const adjustedBox: { + width: number + height: number + x?: number + y?: number + } = { width: box.width * ratio, height: box.height * ratio, } @@ -234,7 +311,7 @@ export const usePieFromBox = ({ } } -export const usePieSliceLabels = ({ +export const usePieSliceLabels = ({ enable, dataWithArc, skipAngle, @@ -243,6 +320,15 @@ export const usePieSliceLabels = ({ radiusOffset, label, textColor, +}: { + enable: boolean + dataWithArc: ComputedDatum[] + skipAngle: number + radius: number + innerRadius: number + radiusOffset: number + label: string | LabelAccessorFunction + textColor: InheritedColorProp> }) => { const theme = useTheme() const getTextColor = useInheritedColor(textColor, theme) @@ -269,7 +355,7 @@ export const usePieSliceLabels = ({ }, [enable, dataWithArc, skipAngle, radius, innerRadius, radiusOffset, getLabel, getTextColor]) } -export const usePieRadialLabels = ({ +export const usePieRadialLabels = ({ enable, dataWithArc, label, @@ -281,7 +367,19 @@ export const usePieRadialLabels = ({ linkDiagonalLength, linkHorizontalLength, linkColor, -}) => { +}: { + enable: boolean + dataWithArc: ComputedDatum[] + label: string | LabelAccessorFunction + textXOffset: number + textColor: InheritedColorProp> + radius: number + skipAngle: number + linkOffset: number + linkDiagonalLength: number + linkHorizontalLength: number + linkColor: InheritedColorProp> +}): RadialLabelData[] => { const getLabel = useMemo(() => getLabelGenerator(label), [label]) const theme = useTheme() @@ -348,14 +446,21 @@ export const usePieRadialLabels = ({ /** * Memoize the context to pass to custom layers. */ -export const usePieLayerContext = ({ +export const usePieLayerContext = ({ dataWithArc, arcGenerator, centerX, centerY, radius, innerRadius, -}) => +}: { + dataWithArc: ComputedDatum[] + arcGenerator: PieArcGenerator + centerX: number + centerY: number + radius: number + innerRadius: number +}): PieCustomLayerProps => useMemo( () => ({ dataWithArc, diff --git a/packages/pie/src/index.js b/packages/pie/src/index.ts similarity index 94% rename from packages/pie/src/index.js rename to packages/pie/src/index.ts index 4461af06d5..dfefa04b7c 100644 --- a/packages/pie/src/index.js +++ b/packages/pie/src/index.ts @@ -12,3 +12,4 @@ export { default as PieCanvas } from './PieCanvas' export { default as ResponsivePieCanvas } from './ResponsivePieCanvas' export * from './props' export * from './hooks' +export * from './definitions' diff --git a/packages/pie/src/props.js b/packages/pie/src/props.js index 59516e60d2..b63a2f630d 100644 --- a/packages/pie/src/props.js +++ b/packages/pie/src/props.js @@ -160,9 +160,10 @@ export const PieDefaultProps = { // legends legends: [], -} -export const PieSvgDefaultProps = { - ...PieDefaultProps, role: 'img', } + +export const defaultProps = PieDefaultProps + +export const PieSvgDefaultProps = PieDefaultProps diff --git a/packages/pie/tsconfig.json b/packages/pie/tsconfig.json new file mode 100644 index 0000000000..39c997d5e7 --- /dev/null +++ b/packages/pie/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.types.json", + "compilerOptions": { + "outDir": "./dist/types", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} \ No newline at end of file