From 9dff1f3ee5ab3ba78bc6e627d20c78d1069a7285 Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Thu, 11 Jan 2024 13:33:46 +0000 Subject: [PATCH] Migrate victory-bar to TypeScript (#2709) --- .changeset/young-squids-lay.md | 5 + ...elper-methods.js => bar-helper-methods.ts} | 47 ++++++-- .../src/{bar.test.js => bar.test.tsx} | 0 packages/victory-bar/src/{bar.js => bar.tsx} | 74 ++++++------ .../src/geometry-helper-methods.js | 113 ------------------ ...est.js => geometry-helper-methods.test.ts} | 0 .../src/geometry-helper-methods.ts | 103 ++++++++++++++++ .../{helper-methods.js => helper-methods.ts} | 4 +- packages/victory-bar/src/index.d.ts | 73 ----------- .../victory-bar/src/{index.js => index.ts} | 4 +- ...lper-methods.js => path-helper-methods.ts} | 19 +-- ...ctory-bar.test.js => victory-bar.test.tsx} | 13 +- .../src/{victory-bar.js => victory-bar.tsx} | 89 +++++++++----- 13 files changed, 262 insertions(+), 282 deletions(-) create mode 100644 .changeset/young-squids-lay.md rename packages/victory-bar/src/{bar-helper-methods.js => bar-helper-methods.ts} (58%) rename packages/victory-bar/src/{bar.test.js => bar.test.tsx} (100%) rename packages/victory-bar/src/{bar.js => bar.tsx} (61%) delete mode 100644 packages/victory-bar/src/geometry-helper-methods.js rename packages/victory-bar/src/{geometry-helper-methods.test.js => geometry-helper-methods.test.ts} (100%) create mode 100644 packages/victory-bar/src/geometry-helper-methods.ts rename packages/victory-bar/src/{helper-methods.js => helper-methods.ts} (96%) delete mode 100644 packages/victory-bar/src/index.d.ts rename packages/victory-bar/src/{index.js => index.ts} (75%) rename packages/victory-bar/src/{path-helper-methods.js => path-helper-methods.ts} (97%) rename packages/victory-bar/src/{victory-bar.test.js => victory-bar.test.tsx} (95%) rename packages/victory-bar/src/{victory-bar.js => victory-bar.tsx} (52%) diff --git a/.changeset/young-squids-lay.md b/.changeset/young-squids-lay.md new file mode 100644 index 000000000..22c0c3a63 --- /dev/null +++ b/.changeset/young-squids-lay.md @@ -0,0 +1,5 @@ +--- +"victory-bar": patch +--- + +Migrate victory-bar to TypeScript diff --git a/packages/victory-bar/src/bar-helper-methods.js b/packages/victory-bar/src/bar-helper-methods.ts similarity index 58% rename from packages/victory-bar/src/bar-helper-methods.js rename to packages/victory-bar/src/bar-helper-methods.ts index 9a349b6b2..c4ffc81c5 100644 --- a/packages/victory-bar/src/bar-helper-methods.js +++ b/packages/victory-bar/src/bar-helper-methods.ts @@ -1,8 +1,18 @@ import { assign, isNil, isPlainObject } from "lodash"; -import { Helpers } from "victory-core"; +import { Helpers, VictoryStyleObject } from "victory-core"; +import { BarProps } from "./bar"; +import { + VictoryBarCornerRadiusObject, + VictoryBarCornerRadiusKey, +} from "./victory-bar"; -export const getBarWidth = (barWidth, props) => { - const { scale, data, defaultBarWidth, style } = props; +const DEFAULT_BAR_WIDTH = 8; + +export const getBarWidth = ( + barWidth: BarProps["barWidth"], + props: BarProps, +) => { + const { scale, data, style } = props; if (barWidth) { return Helpers.evaluateProp(barWidth, props); } else if (style.width) { @@ -13,18 +23,24 @@ export const getBarWidth = (barWidth, props) => { const bars = data.length + 2; const barRatio = props.barRatio || 0.5; const defaultWidth = - barRatio * (data.length < 2 ? defaultBarWidth : extent / bars); + barRatio * (data.length < 2 ? DEFAULT_BAR_WIDTH : extent / bars); return Math.max(1, defaultWidth); }; -const getCornerRadiusFromObject = (cornerRadius, props) => { - const realCornerRadius = { +const getCornerRadiusFromObject = ( + cornerRadius: VictoryBarCornerRadiusObject, + props: BarProps, +) => { + const realCornerRadius: VictoryBarCornerRadiusObject = { topLeft: 0, topRight: 0, bottomLeft: 0, bottomRight: 0, }; - const updateCornerRadius = (corner, fallback) => { + const updateCornerRadius = ( + corner: VictoryBarCornerRadiusKey, + fallback: "top" | "bottom", + ) => { if (!isNil(cornerRadius[corner])) { realCornerRadius[corner] = Helpers.evaluateProp( cornerRadius[corner], @@ -44,8 +60,17 @@ const getCornerRadiusFromObject = (cornerRadius, props) => { return realCornerRadius; }; -export const getCornerRadius = (cornerRadius, props) => { - const realCornerRadius = { +function isCornerRadiusObject( + cornerRadius: BarProps["cornerRadius"], +): cornerRadius is VictoryBarCornerRadiusObject { + return isPlainObject(cornerRadius); +} + +export const getCornerRadius = ( + cornerRadius: BarProps["cornerRadius"], + props: BarProps, +) => { + const realCornerRadius: VictoryBarCornerRadiusObject = { topLeft: 0, topRight: 0, bottomLeft: 0, @@ -54,7 +79,7 @@ export const getCornerRadius = (cornerRadius, props) => { if (!cornerRadius) { return realCornerRadius; } - if (isPlainObject(cornerRadius)) { + if (isCornerRadiusObject(cornerRadius)) { return getCornerRadiusFromObject(cornerRadius, props); } realCornerRadius.topLeft = Helpers.evaluateProp(cornerRadius, props); @@ -62,7 +87,7 @@ export const getCornerRadius = (cornerRadius, props) => { return realCornerRadius; }; -export const getStyle = (style = {}, props) => { +export const getStyle = (style: VictoryStyleObject = {}, props: BarProps) => { if (props.disableInlineStyles) { return {}; } diff --git a/packages/victory-bar/src/bar.test.js b/packages/victory-bar/src/bar.test.tsx similarity index 100% rename from packages/victory-bar/src/bar.test.js rename to packages/victory-bar/src/bar.test.tsx diff --git a/packages/victory-bar/src/bar.js b/packages/victory-bar/src/bar.tsx similarity index 61% rename from packages/victory-bar/src/bar.js rename to packages/victory-bar/src/bar.tsx index 20024ad83..fb81248ed 100644 --- a/packages/victory-bar/src/bar.js +++ b/packages/victory-bar/src/bar.tsx @@ -1,11 +1,36 @@ +/* eslint-disable react/prop-types */ import { assign } from "lodash"; -import PropTypes from "prop-types"; import React, { forwardRef } from "react"; -import { CommonProps, Helpers, Path } from "victory-core"; +import { + Helpers, + NumberOrCallback, + Path, + VictoryCommonPrimitiveProps, +} from "victory-core"; import { getStyle, getBarWidth, getCornerRadius } from "./bar-helper-methods"; import { getPolarBarPath, getBarPath } from "./path-helper-methods"; +import { + VictoryBarAlignmentType, + VictoryBarCornerRadiusObject, +} from "./victory-bar"; -const evaluateProps = (props) => { +export interface BarProps extends VictoryCommonPrimitiveProps { + alignment?: VictoryBarAlignmentType; + barOffset?: number[]; + barRatio?: number; + barWidth?: NumberOrCallback; + cornerRadius?: NumberOrCallback | VictoryBarCornerRadiusObject; + datum?: any; + getPath?: (x: number, y: number, props: any) => string; + horizontal?: boolean; + pathComponent?: React.ReactElement; + width?: number; + x?: number; + y?: number; + y0?: number; +} + +const evaluateProps = (props: BarProps) => { /** * Potential evaluated props of following must be evaluated in this order: * 1) `style` @@ -41,14 +66,17 @@ const evaluateProps = (props) => { }); }; -const defaultProps = { - defaultBarWidth: 8, +const defaultProps: Partial = { pathComponent: , role: "presentation", shapeRendering: "auto", }; + // eslint-disable-next-line prefer-arrow-callback -const Bar = forwardRef(function Bar(props, ref) { +export const Bar = forwardRef(function Bar( + props, + ref, +) { props = evaluateProps({ ...defaultProps, ...props }); const { polar, origin, style, barWidth, cornerRadius } = props; @@ -57,6 +85,11 @@ const Bar = forwardRef(function Bar(props, ref) { : getBarPath(props, barWidth, cornerRadius); const defaultTransform = polar && origin ? `translate(${origin.x}, ${origin.y})` : undefined; + + if (!props.pathComponent) { + return null; + } + return React.cloneElement(props.pathComponent, { ...props.events, "aria-label": props.ariaLabel, @@ -73,32 +106,3 @@ const Bar = forwardRef(function Bar(props, ref) { ref, }); }); - -Bar.propTypes = { - ...CommonProps.primitiveProps, - alignment: PropTypes.oneOf(["start", "middle", "end"]), - barRatio: PropTypes.number, - barWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - cornerRadius: PropTypes.oneOfType([ - PropTypes.number, - PropTypes.func, - PropTypes.shape({ - top: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - topLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - topRight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - bottom: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - bottomLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - bottomRight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - }), - ]), - datum: PropTypes.object, - getPath: PropTypes.func, - horizontal: PropTypes.bool, - pathComponent: PropTypes.element, - width: PropTypes.number, - x: PropTypes.number, - y: PropTypes.number, - y0: PropTypes.number, -}; - -export default Bar; diff --git a/packages/victory-bar/src/geometry-helper-methods.js b/packages/victory-bar/src/geometry-helper-methods.js deleted file mode 100644 index 03a42b856..000000000 --- a/packages/victory-bar/src/geometry-helper-methods.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * A point in the 2d plane - * @param {number} x - x coordinate - * @param {number} y - y coordinate - * @returns {object} - point object - */ -const point = function (x, y) { - return { - x, - y, - distance(p1) { - return Math.sqrt(Math.pow(this.x - p1.x, 2) + Math.pow(this.y - p1.y, 2)); - }, - // vector addition in 2d plane - add(p1) { - return point(this.x + p1.x, this.y + p1.y); - }, - // vector subtraction in 2d - // returns p0 - p1 - subtract(p1) { - return point(this.x - p1.x, this.y - p1.y); - }, - // multiply a 2d point by a scalar - scalarMult(n) { - return point(this.x * n, this.y * n); - }, - scalarDivide(n) { - if (n === 0) { - throw new Error("Division by 0 error"); - } - return point(this.x / n, this.y / n); - }, - equals(p1) { - return this.x === p1.x && this.y === p1.y; - }, - }; -}; - -/** - * A circle in the 2d plane - * @param {point} center - center of circle - * @param {number} radius - radius of circle - * @returns {object} - point object - */ -const circle = function (center, radius) { - return { - center, - radius, - hasIntersection(circle1) { - const P0 = this.center; - const P1 = circle1.center; - const r0 = this.radius; - const r1 = circle1.radius; - const d = P0.distance(P1); - - if (d > r0 + r1) { - return false; // separate circles - } - if (d < Math.abs(r0 - r1)) { - return false; // one circle contains another - } - return true; - }, - equals(circle1) { - const P0 = this.center; - const P1 = circle1.center; - const r0 = this.radius; - const r1 = circle1.radius; - return r0 === r1 && P0.equals(P1); - }, - // Source: http://paulbourke.net/geometry/circlesphere/ - // "Intersection of two circles" by Paul Bourke - // Left-most point is returned as 0th element of array - // Right-most point is returned as 1st elemennt of array - intersection(circle1) { - // eslint-disable-line max-statements - const P0 = this.center; - const P1 = circle1.center; - const r0 = this.radius; - const r1 = circle1.radius; - const d = P0.distance(P1); - if (!this.hasIntersection(circle1) || this.equals(circle1)) { - return []; - } - const a = (Math.pow(r0, 2) - Math.pow(r1, 2) + Math.pow(d, 2)) / (2 * d); - const h = Math.sqrt(Math.pow(r0, 2) - Math.pow(a, 2)); - const P2 = P0.add(P1.subtract(P0).scalarMult(a).scalarDivide(d)); - const { x: x0, y: y0 } = P0; - const { x: x1, y: y1 } = P1; - const { x: x2, y: y2 } = P2; - const P3s = [ - point(x2 - (h * (y1 - y0)) / d, y2 + (h * (x1 - x0)) / d), - point(x2 + (h * (y1 - y0)) / d, y2 - (h * (x1 - x0)) / d), - ]; - P3s.sort((Point1, Point2) => Point1.x - Point2.x); - return P3s; - }, - solveX(y) { - const sqrt = Math.sqrt( - Math.pow(this.radius, 2) - Math.pow(y - this.center.y, 2), - ); - return [this.center.x - sqrt, this.center.x + sqrt]; - }, - solveY(x) { - const sqrt = Math.sqrt( - Math.pow(this.radius, 2) - Math.pow(x - this.center.x, 2), - ); - return [this.center.y - sqrt, this.center.y + sqrt]; - }, - }; -}; - -export { circle, point }; diff --git a/packages/victory-bar/src/geometry-helper-methods.test.js b/packages/victory-bar/src/geometry-helper-methods.test.ts similarity index 100% rename from packages/victory-bar/src/geometry-helper-methods.test.js rename to packages/victory-bar/src/geometry-helper-methods.test.ts diff --git a/packages/victory-bar/src/geometry-helper-methods.ts b/packages/victory-bar/src/geometry-helper-methods.ts new file mode 100644 index 000000000..a56af403f --- /dev/null +++ b/packages/victory-bar/src/geometry-helper-methods.ts @@ -0,0 +1,103 @@ +/** + * A point in the 2d plane + */ +export const point = (x: number, y: number) => ({ + x, + y, + distance(p1: { x: number; y: number }) { + return Math.sqrt(Math.pow(this.x - p1.x, 2) + Math.pow(this.y - p1.y, 2)); + }, + // vector addition in 2d plane + add(p1: { x: number; y: number }) { + return point(this.x + p1.x, this.y + p1.y); + }, + // vector subtraction in 2d + // returns p0 - p1 + subtract(p1: { x: number; y: number }) { + return point(this.x - p1.x, this.y - p1.y); + }, + // multiply a 2d point by a scalar + scalarMult(n: number) { + return point(this.x * n, this.y * n); + }, + scalarDivide(n: number) { + if (n === 0) { + throw new Error("Division by 0 error"); + } + return point(this.x / n, this.y / n); + }, + equals(p1: { x: number; y: number }) { + return this.x === p1.x && this.y === p1.y; + }, +}); + +type Center = ReturnType; + +/** + * A circle in the 2d plane + */ +export const circle = (center: Center, radius: number) => ({ + center, + radius, + hasIntersection(circle1: { center: Center; radius: number }) { + const P0 = this.center; + const P1 = circle1.center; + const r0 = this.radius; + const r1 = circle1.radius; + const d = P0.distance(P1); + + if (d > r0 + r1) { + return false; // separate circles + } + if (d < Math.abs(r0 - r1)) { + return false; // one circle contains another + } + return true; + }, + equals(circle1: { center: Center; radius: number }) { + const P0 = this.center; + const P1 = circle1.center; + const r0 = this.radius; + const r1 = circle1.radius; + return r0 === r1 && P0.equals(P1); + }, + // Source: http://paulbourke.net/geometry/circlesphere/ + // "Intersection of two circles" by Paul Bourke + // Left-most point is returned as 0th element of array + // Right-most point is returned as 1st elemennt of array + intersection(circle1: { center: Center; radius: number }) { + // eslint-disable-line max-statements + const P0 = this.center; + const P1 = circle1.center; + const r0 = this.radius; + const r1 = circle1.radius; + const d = P0.distance(P1); + if (!this.hasIntersection(circle1) || this.equals(circle1)) { + return []; + } + const a = (Math.pow(r0, 2) - Math.pow(r1, 2) + Math.pow(d, 2)) / (2 * d); + const h = Math.sqrt(Math.pow(r0, 2) - Math.pow(a, 2)); + const P2 = P0.add(P1.subtract(P0).scalarMult(a).scalarDivide(d)); + const { x: x0, y: y0 } = P0; + const { x: x1, y: y1 } = P1; + const { x: x2, y: y2 } = P2; + const P3s = [ + point(x2 - (h * (y1 - y0)) / d, y2 + (h * (x1 - x0)) / d), + point(x2 + (h * (y1 - y0)) / d, y2 - (h * (x1 - x0)) / d), + ]; + P3s.sort((Point1, Point2) => Point1.x - Point2.x); + return P3s; + }, + solveX(y: number) { + const sqrt = Math.sqrt( + Math.pow(this.radius, 2) - Math.pow(y - this.center.y, 2), + ); + return [this.center.x - sqrt, this.center.x + sqrt]; + }, + solveY(x: number) { + const sqrt = Math.sqrt( + Math.pow(this.radius, 2) - Math.pow(x - this.center.x, 2), + ); + return [this.center.y - sqrt, this.center.y + sqrt]; + }, +}); diff --git a/packages/victory-bar/src/helper-methods.js b/packages/victory-bar/src/helper-methods.ts similarity index 96% rename from packages/victory-bar/src/helper-methods.js rename to packages/victory-bar/src/helper-methods.ts index 5c1fe1ff5..70ba0eea7 100644 --- a/packages/victory-bar/src/helper-methods.js +++ b/packages/victory-bar/src/helper-methods.ts @@ -15,8 +15,8 @@ export const getBarPosition = (props, datum) => { ? 1 / Number.MAX_SAFE_INTEGER : 0; let defaultMin = defaultZero; - const minY = Collection.getMinValue(props.domain[axis]); - const maxY = Collection.getMaxValue(props.domain[axis]); + const minY = Collection.getMinValue(props.domain[axis]) as number; + const maxY = Collection.getMaxValue(props.domain[axis]) as number; if (minY < 0 && maxY <= 0) { defaultMin = maxY; diff --git a/packages/victory-bar/src/index.d.ts b/packages/victory-bar/src/index.d.ts deleted file mode 100644 index d730a4beb..000000000 --- a/packages/victory-bar/src/index.d.ts +++ /dev/null @@ -1,73 +0,0 @@ -import * as React from "react"; -import { - EventPropTypeInterface, - NumberOrCallback, - StringOrNumberOrCallback, - VictoryCommonProps, - VictoryCommonPrimitiveProps, - VictoryDatableProps, - VictoryMultiLabelableProps, - VictoryStyleInterface, -} from "victory-core"; - -export type VictoryBarTTargetType = "data" | "labels" | "parent"; -export type VictoryBarAlignmentType = "start" | "middle" | "end"; - -export interface VictoryBarProps - extends VictoryCommonProps, - VictoryDatableProps, - VictoryMultiLabelableProps { - alignment?: VictoryBarAlignmentType; - barRatio?: number; - barWidth?: NumberOrCallback; - cornerRadius?: - | NumberOrCallback - | { - top?: NumberOrCallback; - topLeft?: NumberOrCallback; - topRight?: NumberOrCallback; - bottom?: NumberOrCallback; - bottomLeft?: NumberOrCallback; - bottomRight?: NumberOrCallback; - }; - events?: EventPropTypeInterface< - VictoryBarTTargetType, - number | string | number[] | string[] - >[]; - eventKey?: StringOrNumberOrCallback; - horizontal?: boolean; - style?: VictoryStyleInterface; -} - -/** - * Draw SVG bar charts with React. VictoryBar is a composable component, so it doesn"t include axes - * Check out VictoryChart for complete bar charts and more. - */ -export class VictoryBar extends React.Component {} - -export interface BarProps extends VictoryCommonPrimitiveProps { - alignment?: VictoryBarAlignmentType; - barOffset?: number[]; - barRatio?: number; - barWidth?: NumberOrCallback; - cornerRadius?: - | NumberOrCallback - | { - top?: NumberOrCallback; - topLeft?: NumberOrCallback; - topRight?: NumberOrCallback; - bottom?: NumberOrCallback; - bottomLeft?: NumberOrCallback; - bottomRight?: NumberOrCallback; - }; - datum?: any; - getPath?: Function; - horizontal?: boolean; - pathComponent?: React.ReactElement; - width?: number; - x?: number; - y?: number; - y0?: number; -} - -export class Bar extends React.Component {} diff --git a/packages/victory-bar/src/index.js b/packages/victory-bar/src/index.ts similarity index 75% rename from packages/victory-bar/src/index.js rename to packages/victory-bar/src/index.ts index 5aef82765..203f42079 100644 --- a/packages/victory-bar/src/index.js +++ b/packages/victory-bar/src/index.ts @@ -1,5 +1,5 @@ -export { default as VictoryBar } from "./victory-bar"; -export { default as Bar } from "./bar"; +export * from "./victory-bar"; +export * from "./bar"; export { getBarPosition } from "./helper-methods"; export { getVerticalBarPath, diff --git a/packages/victory-bar/src/path-helper-methods.js b/packages/victory-bar/src/path-helper-methods.ts similarity index 97% rename from packages/victory-bar/src/path-helper-methods.js rename to packages/victory-bar/src/path-helper-methods.ts index 79d8106a0..bf9169230 100644 --- a/packages/victory-bar/src/path-helper-methods.js +++ b/packages/victory-bar/src/path-helper-methods.ts @@ -24,7 +24,7 @@ const getPosition = (props, width) => { }; }; -const getAngle = (props, index) => { +const getAngle = (props, index: number) => { const { data, scale } = props; const x = data[index]._x1 === undefined ? "_x" : "_x1"; return scale.x(data[index][x]); @@ -38,7 +38,7 @@ const getAngularWidth = (props, width) => { return (width / (2 * Math.PI * r)) * angularRange; }; -const transformAngle = (angle) => { +const transformAngle = (angle: number) => { return -1 * angle + Math.PI / 2; }; @@ -48,7 +48,7 @@ export const getCustomBarPath = (props, width) => { return getPath(propsWithCalculatedValues); }; -const getStartAngle = (props, index) => { +const getStartAngle = (props, index: number) => { const { data, scale, alignment } = props; const currentAngle = getAngle(props, index); const angularRange = Math.abs(scale.x.range()[1] - scale.x.range()[0]); @@ -64,7 +64,7 @@ const getStartAngle = (props, index) => { return (currentAngle + previousAngle) / 2; }; -const getEndAngle = (props, index) => { +const getEndAngle = (props, index: number) => { const { data, scale, alignment } = props; const currentAngle = getAngle(props, index); const angularRange = Math.abs(scale.x.range()[1] - scale.x.range()[0]); @@ -268,26 +268,29 @@ export const getVerticalPolarBarPath = (props, cornerRadius) => { end = getEndAngle(props, index); } - const getPath = (edge) => { - const pathFunction = d3Shape + const getPath = (edge): string => { + const pathFunction: any = d3Shape .arc() .innerRadius(r1) .outerRadius(r2) .startAngle(transformAngle(start)) .endAngle(transformAngle(end)) .cornerRadius(cornerRadius[edge]); + return pathFunction(); }; const getPathData = (edge) => { const rightPath = getPath(`${edge}Right`); - const rightMoves = rightPath.match(/[A-Z]/g); + const rightMoves: string[] = rightPath.match(/[A-Z]/g) || []; const rightCoords = rightPath.split(/[A-Z]/).slice(1); const rightMiddle = rightMoves.indexOf("L"); + const leftPath = getPath(`${edge}Left`); - const leftMoves = leftPath.match(/[A-Z]/g); + const leftMoves: string[] = leftPath.match(/[A-Z]/g) || []; const leftCoords = leftPath.split(/[A-Z]/).slice(1); const leftMiddle = leftMoves.indexOf("L"); + return { rightMoves, rightCoords, diff --git a/packages/victory-bar/src/victory-bar.test.js b/packages/victory-bar/src/victory-bar.test.tsx similarity index 95% rename from packages/victory-bar/src/victory-bar.test.js rename to packages/victory-bar/src/victory-bar.test.tsx index ef7f971b0..beb0369d2 100644 --- a/packages/victory-bar/src/victory-bar.test.js +++ b/packages/victory-bar/src/victory-bar.test.tsx @@ -41,14 +41,14 @@ describe("components/victory-bar", () => { it("renders an svg with the correct width and height", () => { 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"); const viewBoxValue = `0 0 ${450} ${300}`; - expect(svg.getAttribute("viewBox")).toEqual(viewBoxValue); + expect(svg?.getAttribute("viewBox")).toEqual(viewBoxValue); }); it("renders 4 bars", () => { @@ -130,6 +130,7 @@ describe("components/victory-bar", () => { it("renders bars values with null accessor", () => { const data = range(8); const { container } = render( + // @ts-expect-error "'null' is not assignable to 'x'" , ); const bars = container.querySelectorAll("path"); @@ -152,7 +153,7 @@ describe("components/victory-bar", () => { return getBarHeight(commandString); }); - expect(Math.trunc(heights[1] / 2)).toEqual(Math.trunc(heights[0], 0.5)); + expect(Math.trunc(heights[1] / 2)).toEqual(Math.trunc(heights[0])); expect(((heights[2] / 3) * 2).toFixed(3)).toEqual(heights[1].toFixed(3)); }); @@ -186,7 +187,7 @@ describe("components/victory-bar", () => { />, ); const bar = container.querySelector("path"); - fireEvent.click(bar); + fireEvent.click(bar!); expect(clickHandler).toHaveBeenCalled(); }); @@ -262,14 +263,14 @@ describe("components/victory-bar", () => { dataComponent={ `x: ${datum.x}`} - tabIndex={({ index }) => index + 1} + tabIndex={({ index }) => (index as number) + 1} /> } />, ); container.querySelectorAll("path").forEach((bar, index) => { - expect(parseInt(bar.getAttribute("tabindex"))).toEqual(index + 1); + expect(parseInt(bar.getAttribute("tabindex")!)).toEqual(index + 1); expect(bar.getAttribute("aria-label")).toEqual(`x: ${data[index].x}`); }); }); diff --git a/packages/victory-bar/src/victory-bar.js b/packages/victory-bar/src/victory-bar.tsx similarity index 52% rename from packages/victory-bar/src/victory-bar.js rename to packages/victory-bar/src/victory-bar.tsx index a42531a32..f2638bba0 100644 --- a/packages/victory-bar/src/victory-bar.js +++ b/packages/victory-bar/src/victory-bar.tsx @@ -1,19 +1,58 @@ -import PropTypes from "prop-types"; import React from "react"; import { getBaseProps } from "./helper-methods"; -import Bar from "./bar"; +import { Bar } from "./bar"; import { Helpers, VictoryLabel, VictoryContainer, VictoryTheme, - CommonProps, addEvents, Data, Domain, UserProps, + EventPropTypeInterface, + NumberOrCallback, + StringOrNumberOrCallback, + VictoryCommonProps, + VictoryDatableProps, + VictoryMultiLabelableProps, + VictoryStyleInterface, + EventsMixinClass, } from "victory-core"; +export type VictoryBarCornerRadiusKey = + | "top" + | "bottom" + | "topLeft" + | "topRight" + | "bottomLeft" + | "bottomRight"; + +export type VictoryBarCornerRadiusObject = Partial< + Record +>; + +export type VictoryBarTTargetType = "data" | "labels" | "parent"; + +export type VictoryBarAlignmentType = "start" | "middle" | "end"; + +export interface VictoryBarProps + extends VictoryCommonProps, + VictoryDatableProps, + VictoryMultiLabelableProps { + alignment?: VictoryBarAlignmentType; + barRatio?: number; + barWidth?: NumberOrCallback; + cornerRadius?: NumberOrCallback | VictoryBarCornerRadiusObject; + events?: EventPropTypeInterface< + VictoryBarTTargetType, + number | string | number[] | string[] + >[]; + eventKey?: StringOrNumberOrCallback; + horizontal?: boolean; + style?: VictoryStyleInterface; +} + const fallbackProps = { width: 450, height: 300, @@ -27,8 +66,15 @@ const defaultData = [ { x: 4, y: 4 }, ]; -class VictoryBar extends React.Component { - static animationWhitelist = [ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface VictoryBarBase extends EventsMixinClass {} + +/** + * Draw SVG bar charts with React. VictoryBar is a composable component, so it doesn"t include axes + * Check out VictoryChart for complete bar charts and more. + */ +class VictoryBarBase extends React.Component { + static animationWhitelist: (keyof VictoryBarProps)[] = [ "data", "domain", "height", @@ -58,29 +104,7 @@ class VictoryBar extends React.Component { }, }; - static propTypes = { - ...CommonProps.baseProps, - ...CommonProps.dataProps, - alignment: PropTypes.oneOf(["start", "middle", "end"]), - barRatio: PropTypes.number, - barWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - cornerRadius: PropTypes.oneOfType([ - PropTypes.number, - PropTypes.func, - PropTypes.shape({ - top: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - topLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - topRight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - bottom: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - bottomLeft: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - bottomRight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - }), - ]), - getPath: PropTypes.func, - horizontal: PropTypes.bool, - }; - - static defaultProps = { + static defaultProps: VictoryBarProps = { containerComponent: , data: defaultData, dataComponent: , @@ -94,8 +118,9 @@ class VictoryBar extends React.Component { static getDomain = Domain.getDomainWithZero; static getData = Data.getData; - static getBaseProps = (props) => getBaseProps(props, fallbackProps); - static expectedComponents = [ + static getBaseProps = (props: VictoryBarProps) => + getBaseProps(props, fallbackProps); + static expectedComponents: (keyof VictoryBarProps)[] = [ "dataComponent", "labelComponent", "groupComponent", @@ -107,7 +132,7 @@ class VictoryBar extends React.Component { return !!this.props.animate; } - render() { + render(): React.ReactElement { const { animationWhitelist, role } = VictoryBar; const props = Helpers.modifyProps(this.props, fallbackProps, role); @@ -125,4 +150,4 @@ class VictoryBar extends React.Component { } } -export default addEvents(VictoryBar); +export const VictoryBar = addEvents(VictoryBarBase);