From 492675fdb7d700bd8163bdc5966cd64091998cb2 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Thu, 25 Jan 2024 15:00:45 +0000 Subject: [PATCH] Migrate victory-pie to TypeScript (#2740) --- .changeset/kind-rivers-kick.md | 5 + .../{helper-methods.js => helper-methods.ts} | 4 +- packages/victory-pie/src/index.d.ts | 86 ----- packages/victory-pie/src/index.js | 2 - packages/victory-pie/src/index.ts | 2 + .../victory-pie/src/{slice.js => slice.tsx} | 50 ++- packages/victory-pie/src/victory-pie.js | 342 ------------------ ...ctory-pie.test.js => victory-pie.test.tsx} | 28 +- packages/victory-pie/src/victory-pie.tsx | 269 ++++++++++++++ 9 files changed, 338 insertions(+), 450 deletions(-) create mode 100644 .changeset/kind-rivers-kick.md rename packages/victory-pie/src/{helper-methods.js => helper-methods.ts} (99%) delete mode 100644 packages/victory-pie/src/index.d.ts delete mode 100644 packages/victory-pie/src/index.js create mode 100644 packages/victory-pie/src/index.ts rename packages/victory-pie/src/{slice.js => slice.tsx} (71%) delete mode 100644 packages/victory-pie/src/victory-pie.js rename packages/victory-pie/src/{victory-pie.test.js => victory-pie.test.tsx} (93%) create mode 100644 packages/victory-pie/src/victory-pie.tsx diff --git a/.changeset/kind-rivers-kick.md b/.changeset/kind-rivers-kick.md new file mode 100644 index 000000000..baa57d5f8 --- /dev/null +++ b/.changeset/kind-rivers-kick.md @@ -0,0 +1,5 @@ +--- +"victory-pie": patch +--- + +Migrate victory-pie to TypeScript diff --git a/packages/victory-pie/src/helper-methods.js b/packages/victory-pie/src/helper-methods.ts similarity index 99% rename from packages/victory-pie/src/helper-methods.js rename to packages/victory-pie/src/helper-methods.ts index 9e107460d..25fedbb3d 100644 --- a/packages/victory-pie/src/helper-methods.js +++ b/packages/victory-pie/src/helper-methods.ts @@ -53,7 +53,7 @@ const getSlices = (props, data) => { .startAngle(Helpers.degreesToRadians(props.startAngle)) .endAngle(Helpers.degreesToRadians(props.endAngle)) .padAngle(Helpers.degreesToRadians(padAngle)) - .value((datum) => { + .value((datum: any) => { return datum._y; }); return layoutFunction(data); @@ -62,7 +62,7 @@ const getSlices = (props, data) => { const getCalculatedValues = (props) => { const { colorScale } = props; const styleObject = Helpers.getDefaultStyles(props, "pie"); - const style = Helpers.getStyles(props.style, styleObject, "auto", "100%"); + const style = Helpers.getStyles(props.style, styleObject); const colors = Array.isArray(colorScale) ? colorScale : Style.getColorScale(colorScale); diff --git a/packages/victory-pie/src/index.d.ts b/packages/victory-pie/src/index.d.ts deleted file mode 100644 index cf1e1bbfc..000000000 --- a/packages/victory-pie/src/index.d.ts +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from "react"; -import { - CategoryPropType, - ColorScalePropType, - DataGetterPropType, - EventPropTypeInterface, - StringOrCallback, - NumberOrCallback, - OriginType, - SliceNumberOrCallback, - StringOrNumberOrCallback, - VictoryCommonProps, - VictoryLabelableProps, - VictoryMultiLabelableProps, - VictoryStyleInterface, -} from "victory-core"; - -export type VictorySliceLabelPositionType = - | "startAngle" - | "centroid" - | "endAngle"; -export type VictorySliceLabelPlacementType = - | "vertical" - | "parallel" - | "perpendicular"; -export type VictorySliceTTargetType = "data" | "labels" | "parent"; - -export interface SliceProps extends VictoryCommonProps { - ariaLabel?: StringOrCallback; - cornerRadius?: SliceNumberOrCallback; - datum?: any; - innerRadius?: NumberOrCallback; - padAngle?: SliceNumberOrCallback; - pathComponent?: React.ReactElement; - pathFunction?: (props: SliceProps) => string; - radius?: SliceNumberOrCallback; - slice?: { - startAngle?: number; - endAngle?: number; - padAngle?: number; - data?: any[]; - }; - sliceEndAngle?: SliceNumberOrCallback; - sliceStartAngle?: SliceNumberOrCallback; - style?: VictoryStyleInterface; - tabIndex?: NumberOrCallback; -} - -export class Slice extends React.Component {} - -export interface VictoryPieProps - extends Omit, - VictoryLabelableProps, - VictoryMultiLabelableProps { - categories?: CategoryPropType; - colorScale?: ColorScalePropType; - cornerRadius?: SliceNumberOrCallback; - data?: any[]; - dataComponent?: React.ReactElement; - endAngle?: number; - events?: EventPropTypeInterface< - VictorySliceTTargetType, - StringOrNumberOrCallback | string[] | number[] - >[]; - eventKey?: StringOrNumberOrCallback; - innerRadius?: NumberOrCallback; - labelIndicator?: boolean | React.ReactElement; - labelIndicatorInnerOffset: number; - labelIndicatorOuterOffset: number; - labelPlacement?: - | VictorySliceLabelPlacementType - | ((props: SliceProps) => VictorySliceLabelPlacementType); - labelPosition?: - | VictorySliceLabelPositionType - | ((props: SliceProps) => VictorySliceLabelPositionType); - labelRadius?: number | ((props: SliceProps) => number); - origin?: OriginType; - padAngle?: NumberOrCallback; - radius?: NumberOrCallback; - startAngle?: number; - style?: VictoryStyleInterface; - x?: DataGetterPropType; - y?: DataGetterPropType; -} - -export class VictoryPie extends React.Component {} diff --git a/packages/victory-pie/src/index.js b/packages/victory-pie/src/index.js deleted file mode 100644 index 1f33bc760..000000000 --- a/packages/victory-pie/src/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default as VictoryPie } from "./victory-pie"; -export { default as Slice } from "./slice"; diff --git a/packages/victory-pie/src/index.ts b/packages/victory-pie/src/index.ts new file mode 100644 index 000000000..fa122b247 --- /dev/null +++ b/packages/victory-pie/src/index.ts @@ -0,0 +1,2 @@ +export * from "./victory-pie"; +export * from "./slice"; diff --git a/packages/victory-pie/src/slice.js b/packages/victory-pie/src/slice.tsx similarity index 71% rename from packages/victory-pie/src/slice.js rename to packages/victory-pie/src/slice.tsx index 1e1680bf9..287d76440 100644 --- a/packages/victory-pie/src/slice.js +++ b/packages/victory-pie/src/slice.tsx @@ -1,9 +1,51 @@ import React from "react"; import PropTypes from "prop-types"; -import { Helpers, CommonProps, Path } from "victory-core"; +import { + Helpers, + CommonProps, + Path, + NumberOrCallback, + SliceNumberOrCallback, + StringOrCallback, + VictoryCommonProps, + VictoryStyleInterface, +} from "victory-core"; import { defaults, isFunction, assign } from "lodash"; import * as d3Shape from "victory-vendor/d3-shape"; +export type VictorySliceLabelPositionType = + | "startAngle" + | "centroid" + | "endAngle"; +export type VictorySliceLabelPlacementType = + | "vertical" + | "parallel" + | "perpendicular"; +export type VictorySliceTTargetType = "data" | "labels" | "parent"; + +export interface SliceProps extends VictoryCommonProps { + ariaLabel?: StringOrCallback; + cornerRadius?: SliceNumberOrCallback; + datum?: any; + innerRadius?: NumberOrCallback; + padAngle?: SliceNumberOrCallback; + pathComponent?: React.ReactElement; + pathFunction?: (props: SliceProps) => string; + radius?: SliceNumberOrCallback; + slice?: { + startAngle?: number; + endAngle?: number; + padAngle?: number; + data?: any[]; + }; + sliceEndAngle?: SliceNumberOrCallback; + sliceStartAngle?: SliceNumberOrCallback; + style?: VictoryStyleInterface; + tabIndex?: NumberOrCallback; + role?: string; + shapeRendering?: string; +} + const getPath = (props) => { const { slice, radius, innerRadius, cornerRadius } = props; if (isFunction(props.pathFunction)) { @@ -68,13 +110,13 @@ const evaluateProps = (props) => { }); }; -const defaultProps = { +const defaultProps: SliceProps = { pathComponent: , role: "presentation", shapeRendering: "auto", }; -const Slice = (initialProps) => { +export const Slice = (initialProps: SliceProps) => { const props = evaluateProps({ ...defaultProps, ...initialProps }); const defaultTransform = props.origin ? `translate(${props.origin.x}, ${props.origin.y})` @@ -107,5 +149,3 @@ Slice.propTypes = { sliceEndAngle: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), sliceStartAngle: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), }; - -export default Slice; diff --git a/packages/victory-pie/src/victory-pie.js b/packages/victory-pie/src/victory-pie.js deleted file mode 100644 index c2641681a..000000000 --- a/packages/victory-pie/src/victory-pie.js +++ /dev/null @@ -1,342 +0,0 @@ -/* eslint no-magic-numbers: ["error", { "ignore": [-1, 0, 1, 2] }]*/ -import React from "react"; -import PropTypes from "prop-types"; -import { - addEvents, - Helpers, - Data, - LineSegment, - PropTypes as CustomPropTypes, - VictoryContainer, - VictoryLabel, - VictoryTheme, - UserProps, -} from "victory-core"; -import Slice from "./slice"; -import { isNil } from "lodash"; -import { getBaseProps } from "./helper-methods"; - -const fallbackProps = { - endAngle: 360, - height: 400, - innerRadius: 0, - cornerRadius: 0, - padAngle: 0, - padding: 30, - width: 400, - startAngle: 0, - colorScale: [ - "#ffffff", - "#f0f0f0", - "#d9d9d9", - "#bdbdbd", - "#969696", - "#737373", - "#525252", - "#252525", - "#000000", - ], - labelPosition: "centroid", - labelIndicatorInnerOffset: 15, - labelIndicatorOuterOffset: 5, -}; - -const datumHasXandY = (datum) => { - return !isNil(datum._x) && !isNil(datum._y); -}; - -class VictoryPie extends React.Component { - static animationWhitelist = [ - "data", - "endAngle", - "height", - "innerRadius", - "cornerRadius", - "padAngle", - "padding", - "colorScale", - "startAngle", - "style", - "width", - ]; - - static displayName = "VictoryPie"; - - static role = "pie"; - - static defaultTransitions = { - onExit: { - duration: 500, - before: () => ({ _y: 0, label: " " }), - }, - onEnter: { - duration: 500, - before: () => ({ _y: 0, label: " " }), - after: (datum) => ({ - y_: datum._y, - label: datum.label, - }), - }, - }; - - static propTypes = { - animate: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]), - colorScale: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.string), - PropTypes.oneOf([ - "grayscale", - "qualitative", - "heatmap", - "warm", - "cool", - "red", - "green", - "blue", - ]), - ]), - containerComponent: PropTypes.element, - cornerRadius: PropTypes.oneOfType([ - CustomPropTypes.nonNegative, - PropTypes.func, - ]), - data: PropTypes.array, - dataComponent: PropTypes.element, - disableInlineStyes: PropTypes.bool, - endAngle: PropTypes.number, - eventKey: PropTypes.oneOfType([ - PropTypes.func, - CustomPropTypes.allOfType([ - CustomPropTypes.integer, - CustomPropTypes.nonNegative, - ]), - PropTypes.string, - ]), - events: PropTypes.arrayOf( - PropTypes.shape({ - target: PropTypes.oneOf(["data", "labels", "parent"]), - eventKey: PropTypes.oneOfType([ - PropTypes.array, - PropTypes.func, - CustomPropTypes.allOfType([ - CustomPropTypes.integer, - CustomPropTypes.nonNegative, - ]), - PropTypes.string, - ]), - eventHandlers: PropTypes.object, - }), - ), - externalEventMutations: PropTypes.arrayOf( - PropTypes.shape({ - callback: PropTypes.func, - childName: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), - eventKey: PropTypes.oneOfType([ - PropTypes.array, - CustomPropTypes.allOfType([ - CustomPropTypes.integer, - CustomPropTypes.nonNegative, - ]), - PropTypes.string, - ]), - mutation: PropTypes.func, - target: PropTypes.oneOfType([PropTypes.string, PropTypes.array]), - }), - ), - groupComponent: PropTypes.element, - height: CustomPropTypes.nonNegative, - innerRadius: PropTypes.oneOfType([ - CustomPropTypes.nonNegative, - PropTypes.func, - ]), - labelComponent: PropTypes.element, - labelIndicator: PropTypes.oneOfType([PropTypes.element, PropTypes.bool]), - labelIndicatorInnerOffset: PropTypes.number, - labelIndicatorMiddleOffset: PropTypes.number, - labelIndicatorOuterOffset: PropTypes.number, - labelPlacement: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.oneOf(["parallel", "perpendicular", "vertical"]), - ]), - labelPosition: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.oneOf(["startAngle", "centroid", "endAngle"]), - ]), - labelRadius: PropTypes.oneOfType([ - CustomPropTypes.nonNegative, - PropTypes.func, - ]), - labels: PropTypes.oneOfType([PropTypes.func, PropTypes.array]), - name: PropTypes.string, - origin: PropTypes.shape({ - x: CustomPropTypes.nonNegative, - y: CustomPropTypes.nonNegative, - }), - padAngle: PropTypes.oneOfType([ - CustomPropTypes.nonNegative, - PropTypes.func, - ]), - padding: PropTypes.oneOfType([ - PropTypes.number, - PropTypes.shape({ - top: PropTypes.number, - bottom: PropTypes.number, - left: PropTypes.number, - right: PropTypes.number, - }), - ]), - radius: PropTypes.oneOfType([CustomPropTypes.nonNegative, PropTypes.func]), - sharedEvents: PropTypes.shape({ - events: PropTypes.array, - getEventState: PropTypes.func, - }), - sortKey: PropTypes.oneOfType([ - PropTypes.func, - CustomPropTypes.allOfType([ - CustomPropTypes.integer, - CustomPropTypes.nonNegative, - ]), - PropTypes.string, - PropTypes.arrayOf(PropTypes.string), - ]), - sortOrder: PropTypes.oneOf(["ascending", "descending"]), - standalone: PropTypes.bool, - startAngle: PropTypes.number, - style: PropTypes.shape({ - parent: PropTypes.object, - data: PropTypes.object, - labels: PropTypes.object, - }), - theme: PropTypes.object, - width: CustomPropTypes.nonNegative, - x: PropTypes.oneOfType([ - PropTypes.func, - CustomPropTypes.allOfType([ - CustomPropTypes.integer, - CustomPropTypes.nonNegative, - ]), - PropTypes.string, - PropTypes.arrayOf(PropTypes.string), - ]), - y: PropTypes.oneOfType([ - PropTypes.func, - CustomPropTypes.allOfType([ - CustomPropTypes.integer, - CustomPropTypes.nonNegative, - ]), - PropTypes.string, - PropTypes.arrayOf(PropTypes.string), - ]), - }; - - static defaultProps = { - data: [ - { x: "A", y: 1 }, - { x: "B", y: 2 }, - { x: "C", y: 3 }, - { x: "D", y: 1 }, - { x: "E", y: 2 }, - ], - standalone: true, - dataComponent: , - labelComponent: , - containerComponent: , - groupComponent: , - sortOrder: "ascending", - theme: VictoryTheme.grayscale, - }; - - static getBaseProps = (props) => getBaseProps(props, fallbackProps); - static getData = Data.getData; - static expectedComponents = [ - "dataComponent", - "labelComponent", - "groupComponent", - "containerComponent", - "labelIndicatorComponent", - ]; - - // Overridden in victory-native - shouldAnimate() { - return Boolean(this.props.animate); - } - - renderComponents(props, shouldRenderDatum = datumHasXandY) { - const { - dataComponent, - labelComponent, - groupComponent, - labelIndicator, - labelPosition, - } = props; - const showIndicator = labelIndicator && labelPosition === "centroid"; - - let labelIndicatorComponents = null; - - const dataComponents = this.dataKeys.reduce( - (validDataComponents, _dataKey, index) => { - const dataProps = this.getComponentProps(dataComponent, "data", index); - if (shouldRenderDatum(dataProps.datum)) { - validDataComponents.push( - React.cloneElement(dataComponent, dataProps), - ); - } - return validDataComponents; - }, - [], - ); - - const labelComponents = this.dataKeys - .map((_dataKey, index) => { - const labelProps = this.getComponentProps( - labelComponent, - "labels", - index, - ); - if (labelProps.text !== undefined && labelProps.text !== null) { - return React.cloneElement(labelComponent, labelProps); - } - return undefined; - }) - .filter(Boolean); - - if (showIndicator) { - let labelIndicatorComponent = ; - if (typeof labelIndicator === "object") { - // pass user provided react component - labelIndicatorComponent = labelIndicator; - } - - labelIndicatorComponents = this.dataKeys.map((_dataKey, index) => { - const labelIndicatorProps = this.getComponentProps( - labelIndicatorComponent, - "labelIndicators", - index, - ); - return React.cloneElement(labelIndicatorComponent, labelIndicatorProps); - }); - } - const children = showIndicator - ? [...dataComponents, ...labelComponents, ...labelIndicatorComponents] - : [...dataComponents, ...labelComponents]; - return this.renderContainer(groupComponent, children); - } - - render() { - const { animationWhitelist, role } = VictoryPie; - const props = Helpers.modifyProps(this.props, fallbackProps, role); - - if (this.shouldAnimate()) { - return this.animateComponent(props, animationWhitelist); - } - - const children = this.renderComponents(props); - - const component = props.standalone - ? this.renderContainer(props.containerComponent, children) - : children; - - return UserProps.withSafeUserProps(component, props); - } -} - -export default addEvents(VictoryPie); diff --git a/packages/victory-pie/src/victory-pie.test.js b/packages/victory-pie/src/victory-pie.test.tsx similarity index 93% rename from packages/victory-pie/src/victory-pie.test.js rename to packages/victory-pie/src/victory-pie.test.tsx index c2e733c35..399b90e03 100644 --- a/packages/victory-pie/src/victory-pie.test.js +++ b/packages/victory-pie/src/victory-pie.test.tsx @@ -13,8 +13,8 @@ import { } from "../../../test/helpers"; const pizzaSliceInnerText = "Pizza Slice"; -const PizzaSlice = ({ datum: { x } }) => ( -

+const PizzaSlice = ({ datum }: { datum?: { x: number } }) => ( +

{pizzaSliceInnerText}

); @@ -39,13 +39,13 @@ describe("components/victory-pie", () => { const { container } = render(); const svg = container.querySelector("svg"); - expect(svg.getAttribute("style")).toContain("width: 100%; height: 100%"); + expect(svg?.getAttribute("style")).toContain("width: 100%; height: 100%"); }); it("renders an svg with the correct viewBox", () => { const { container } = render(); const svg = container.querySelector("svg"); - expect(svg.getAttribute("viewBox")).toEqual("0 0 400 400"); + expect(svg?.getAttribute("viewBox")).toEqual("0 0 400 400"); }); it("renders 5 slices", () => { @@ -81,6 +81,7 @@ describe("components/victory-pie", () => { }); it("renders 0 slice labels for label function returning undefined", () => { + // @ts-expect-error "undefined" is not assignable to "string" const { container } = render( undefined} />); const labels = container.querySelectorAll("text"); @@ -125,6 +126,7 @@ describe("components/victory-pie", () => { it("renders data values with null accessor", () => { const data = range(8); const { container } = render( + // @ts-expect-error "'null' is not assignable to 'x'" , ); const slices = container.querySelectorAll("path"); @@ -138,7 +140,7 @@ describe("components/victory-pie", () => { render(} />); const xValues = Array.from(screen.getAllByText(pizzaSliceInnerText)).map( (slice) => { - return parseInt(slice.getAttribute("xvalue")); + return parseInt(slice.getAttribute("data-xvalue") || ""); }, ); @@ -153,11 +155,11 @@ describe("components/victory-pie", () => { .reverse(); render( - } />, + } />, ); const xValues = Array.from(screen.getAllByText(pizzaSliceInnerText)).map( (slice) => { - return parseInt(slice.getAttribute("xvalue")); + return parseInt(slice.getAttribute("data-xvalue") || ""); }, ); @@ -174,14 +176,14 @@ describe("components/victory-pie", () => { render( } />, ); const xValues = Array.from(screen.getAllByText(pizzaSliceInnerText)).map( (slice) => { - return parseInt(slice.getAttribute("xvalue")); + return parseInt(slice.getAttribute("data-xvalue") || ""); }, ); @@ -286,7 +288,7 @@ describe("components/victory-pie", () => { const width = 200; const { container } = render(); const svg = container.querySelector("svg"); - expect(svg.getAttribute("viewBox")).toEqual(`0 0 ${width} 400`); + expect(svg?.getAttribute("viewBox")).toEqual(`0 0 ${width} 400`); }); }); @@ -295,7 +297,7 @@ describe("components/victory-pie", () => { const height = 200; const { container } = render(); const svg = container.querySelector("svg"); - expect(svg.getAttribute("viewBox")).toEqual(`0 0 400 ${height}`); + expect(svg?.getAttribute("viewBox")).toEqual(`0 0 400 ${height}`); }); }); @@ -373,7 +375,7 @@ describe("components/victory-pie", () => { />, ); const svg = container.querySelector("svg"); - fireEvent.click(svg); + fireEvent.click(svg!); expect(clickHandler).toBeCalled(); const contextualArg = clickHandler.mock.calls[0][1]; @@ -433,7 +435,7 @@ describe("components/victory-pie", () => { dataComponent={ `${datum.x}`} - tabIndex={({ index }) => index + 5} + tabIndex={({ index }) => Number(index) + 5} /> } />, diff --git a/packages/victory-pie/src/victory-pie.tsx b/packages/victory-pie/src/victory-pie.tsx new file mode 100644 index 000000000..55a4596b7 --- /dev/null +++ b/packages/victory-pie/src/victory-pie.tsx @@ -0,0 +1,269 @@ +import React from "react"; +import { + addEvents, + Helpers, + Data, + LineSegment, + VictoryContainer, + VictoryLabel, + VictoryTheme, + UserProps, + ColorScalePropType, + EventPropTypeInterface, + NumberOrCallback, + OriginType, + SliceNumberOrCallback, + StringOrNumberOrCallback, + VictoryCommonProps, + VictoryLabelableProps, + VictoryMultiLabelableProps, + VictoryStyleInterface, + EventsMixinClass, + VictoryDatableProps, +} from "victory-core"; +import { isNil } from "lodash"; +import { getBaseProps } from "./helper-methods"; +import { + Slice, + SliceProps, + VictorySliceTTargetType, + VictorySliceLabelPlacementType, + VictorySliceLabelPositionType, +} from "./slice"; + +export interface VictoryPieProps + extends Omit, + VictoryDatableProps, + VictoryLabelableProps, + VictoryMultiLabelableProps { + colorScale?: ColorScalePropType; + cornerRadius?: SliceNumberOrCallback; + endAngle?: number; + events?: EventPropTypeInterface< + VictorySliceTTargetType, + StringOrNumberOrCallback | string[] | number[] + >[]; + eventKey?: StringOrNumberOrCallback; + innerRadius?: NumberOrCallback; + labelIndicator?: boolean | React.ReactElement; + labelIndicatorInnerOffset?: number; + labelIndicatorOuterOffset?: number; + labelPlacement?: + | VictorySliceLabelPlacementType + | ((props: SliceProps) => VictorySliceLabelPlacementType); + labelPosition?: + | VictorySliceLabelPositionType + | ((props: SliceProps) => VictorySliceLabelPositionType); + + labelIndicatorComponent?: React.ReactElement; + labelRadius?: number | ((props: SliceProps) => number); + origin?: OriginType; + padAngle?: NumberOrCallback; + radius?: NumberOrCallback; + startAngle?: number; + style?: VictoryStyleInterface; +} + +const fallbackProps = { + endAngle: 360, + height: 400, + innerRadius: 0, + cornerRadius: 0, + padAngle: 0, + padding: 30, + width: 400, + startAngle: 0, + colorScale: [ + "#ffffff", + "#f0f0f0", + "#d9d9d9", + "#bdbdbd", + "#969696", + "#737373", + "#525252", + "#252525", + "#000000", + ], + labelPosition: "centroid", + labelIndicatorInnerOffset: 15, + labelIndicatorOuterOffset: 5, +}; + +const datumHasXandY = (datum) => { + return !isNil(datum._x) && !isNil(datum._y); +}; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface VictoryPieBase extends EventsMixinClass {} + +class VictoryPieBase extends React.Component { + static animationWhitelist: (keyof VictoryPieProps)[] = [ + "data", + "endAngle", + "height", + "innerRadius", + "cornerRadius", + "padAngle", + "padding", + "colorScale", + "startAngle", + "style", + "width", + ]; + + static displayName = "VictoryPie"; + + static role = "pie"; + + static defaultTransitions = { + onExit: { + duration: 500, + before: () => ({ _y: 0, label: " " }), + }, + onEnter: { + duration: 500, + before: () => ({ _y: 0, label: " " }), + after: (datum) => ({ + y_: datum._y, + label: datum.label, + }), + }, + }; + + static defaultProps: VictoryPieProps = { + data: [ + { x: "A", y: 1 }, + { x: "B", y: 2 }, + { x: "C", y: 3 }, + { x: "D", y: 1 }, + { x: "E", y: 2 }, + ], + standalone: true, + dataComponent: , + labelComponent: , + containerComponent: , + groupComponent: , + sortOrder: "ascending", + theme: VictoryTheme.grayscale, + }; + + static getBaseProps = (props: VictoryPieProps) => + getBaseProps(props, fallbackProps); + static getData = Data.getData; + static expectedComponents: (keyof VictoryPieProps)[] = [ + "dataComponent", + "labelComponent", + "groupComponent", + "containerComponent", + "labelIndicatorComponent", + ]; + + // Overridden in victory-native + shouldAnimate() { + return Boolean(this.props.animate); + } + + renderComponents(props: VictoryPieProps, shouldRenderDatum = datumHasXandY) { + const { + dataComponent, + labelComponent, + groupComponent, + labelIndicator, + labelPosition, + } = props; + + if (!groupComponent) { + throw new Error("VictoryPie expects a groupComponent prop"); + } + + const showIndicator = labelIndicator && labelPosition === "centroid"; + + const children: React.ReactElement[] = []; + + if (dataComponent) { + const dataComponents = this.dataKeys.reduce( + (validDataComponents, _dataKey, index) => { + const dataProps = this.getComponentProps( + dataComponent, + "data", + index, + ); + if (shouldRenderDatum((dataProps as any).datum)) { + validDataComponents.push( + React.cloneElement(dataComponent, dataProps), + ); + } + return validDataComponents; + }, + [], + ); + + children.push(...dataComponents); + } + + if (labelComponent) { + const labelComponents = this.dataKeys + .map((_dataKey, index) => { + const labelProps = this.getComponentProps( + labelComponent, + "labels", + index, + ); + if ( + (labelProps as any).text !== undefined && + (labelProps as any).text !== null + ) { + return React.cloneElement(labelComponent, labelProps); + } + return undefined; + }) + .filter( + (comp: React.ReactElement | undefined): comp is React.ReactElement => + comp !== undefined, + ); + + children.push(...labelComponents); + } + + if (showIndicator && labelIndicator) { + let labelIndicatorComponent: React.ReactElement = ; + + if (typeof labelIndicator === "object") { + // pass user provided react component + labelIndicatorComponent = labelIndicator; + } + + const labelIndicatorComponents = this.dataKeys.map((_dataKey, index) => { + const labelIndicatorProps = this.getComponentProps( + labelIndicatorComponent, + "labelIndicators", + index, + ); + return React.cloneElement(labelIndicatorComponent, labelIndicatorProps); + }); + + children.push(...labelIndicatorComponents); + } + + return this.renderContainer(groupComponent, children); + } + + render(): React.ReactElement { + const { animationWhitelist, role } = VictoryPie; + const props = Helpers.modifyProps(this.props, fallbackProps, role); + + if (this.shouldAnimate()) { + return this.animateComponent(props, animationWhitelist); + } + + const children = this.renderComponents(props); + + const component = props.standalone + ? this.renderContainer(props.containerComponent, children) + : children; + + return UserProps.withSafeUserProps(component, props); + } +} + +export const VictoryPie = addEvents(VictoryPieBase);