From b6ceae1bfc3f49a025937360b4084f379c90effc Mon Sep 17 00:00:00 2001 From: Kenan Yusuf Date: Fri, 12 Jan 2024 13:26:22 +0000 Subject: [PATCH] Migrate victory-canvas to TypeScript (#2710) --- .changeset/beige-flowers-confess.md | 5 + .../src/{canvas-bar.js => canvas-bar.tsx} | 92 ++++++++----------- packages/victory-canvas/src/canvas-curve.js | 40 -------- packages/victory-canvas/src/canvas-curve.tsx | 46 ++++++++++ packages/victory-canvas/src/canvas-group.js | 71 -------------- packages/victory-canvas/src/canvas-group.tsx | 60 ++++++++++++ .../src/{canvas-point.js => canvas-point.tsx} | 46 ++++++---- ...anvas-context.js => use-canvas-context.ts} | 10 +- packages/victory-canvas/src/index.d.ts | 73 --------------- packages/victory-canvas/src/index.js | 5 - packages/victory-canvas/src/index.ts | 5 + 11 files changed, 190 insertions(+), 263 deletions(-) create mode 100644 .changeset/beige-flowers-confess.md rename packages/victory-canvas/src/{canvas-bar.js => canvas-bar.tsx} (51%) delete mode 100644 packages/victory-canvas/src/canvas-curve.js create mode 100644 packages/victory-canvas/src/canvas-curve.tsx delete mode 100644 packages/victory-canvas/src/canvas-group.js create mode 100644 packages/victory-canvas/src/canvas-group.tsx rename packages/victory-canvas/src/{canvas-point.js => canvas-point.tsx} (63%) rename packages/victory-canvas/src/hooks/{use-canvas-context.js => use-canvas-context.ts} (54%) delete mode 100644 packages/victory-canvas/src/index.d.ts delete mode 100644 packages/victory-canvas/src/index.js create mode 100644 packages/victory-canvas/src/index.ts diff --git a/.changeset/beige-flowers-confess.md b/.changeset/beige-flowers-confess.md new file mode 100644 index 000000000..8f935c9d7 --- /dev/null +++ b/.changeset/beige-flowers-confess.md @@ -0,0 +1,5 @@ +--- +"victory-canvas": patch +--- + +Migrate victory-canvas to TypeScript diff --git a/packages/victory-canvas/src/canvas-bar.js b/packages/victory-canvas/src/canvas-bar.tsx similarity index 51% rename from packages/victory-canvas/src/canvas-bar.js rename to packages/victory-canvas/src/canvas-bar.tsx index 238a0b563..cdb24ec18 100644 --- a/packages/victory-canvas/src/canvas-bar.js +++ b/packages/victory-canvas/src/canvas-bar.tsx @@ -1,7 +1,9 @@ import { assign } from "lodash"; -import PropTypes from "prop-types"; import React from "react"; import { + BarProps, + VictoryBarAlignmentType, + VictoryBarCornerRadiusObject, getBarPath, getBarWidth, getCornerRadius, @@ -9,30 +11,41 @@ import { getStyle, } from "victory-bar"; import { useCanvasContext } from "./hooks/use-canvas-context"; -import { CommonProps } from "victory-core"; - -const evaluateProps = (props) => { +import { NumberOrCallback, VictoryCommonPrimitiveProps } from "victory-core"; + +export interface CanvasBarProps extends VictoryCommonPrimitiveProps { + alignment?: VictoryBarAlignmentType; + barOffset?: number[]; + barRatio?: number; + barWidth?: NumberOrCallback; + cornerRadius?: NumberOrCallback | VictoryBarCornerRadiusObject; + datum?: any; + getPath?: (x: number, y: number, size: number) => string; + horizontal?: boolean; + width?: number; + x?: number; + y?: number; + y0?: number; +} + +const evaluateProps = (props: CanvasBarProps) => { /** * Potential evaluated props of following must be evaluated in this order: * 1) `style` * 2) `barWidth` * 3) `cornerRadius` */ - const style = getStyle(props.style, props); + const style = getStyle(props.style, props as BarProps); const barWidth = getBarWidth(props.barWidth, assign({}, props, { style })); const cornerRadius = getCornerRadius( props.cornerRadius, assign({}, props, { style, barWidth }), ); - - return assign({}, props, { - style, - barWidth, - cornerRadius, - }); + const modifiedProps = assign({}, props, { style, barWidth, cornerRadius }); + return modifiedProps; }; -export const usePreviousValue = (value) => { +const usePreviousValue = (value) => { const ref = React.useRef(); React.useEffect(() => { ref.current = value; @@ -40,30 +53,30 @@ export const usePreviousValue = (value) => { return ref.current; }; -const CanvasBar = (initialProps) => { +export const CanvasBar = (props: CanvasBarProps) => { const { canvasRef } = useCanvasContext(); - const props = evaluateProps(initialProps); - const { polar, style, barWidth, cornerRadius, origin } = props; + const modifiedProps = evaluateProps(props); + const { polar, style, barWidth, cornerRadius, origin } = modifiedProps; const path2d = React.useMemo(() => { const p = polar - ? getPolarBarPath(props, cornerRadius) - : getBarPath(props, barWidth, cornerRadius); + ? getPolarBarPath(modifiedProps, cornerRadius) + : getBarPath(modifiedProps, barWidth, cornerRadius); return new Path2D(p); - }, [polar, barWidth, cornerRadius, props]); + }, [polar, barWidth, cornerRadius, modifiedProps]); const previousPath = usePreviousValue(path2d); const draw = React.useCallback( - (ctx, path) => { + (ctx: CanvasRenderingContext2D, path: Path2D) => { ctx.fillStyle = style.fill; ctx.strokeStyle = style.stroke; ctx.globalAlpha = style.fillOpacity; ctx.lineWidth = style.strokeWidth; if (polar) { - ctx.translate(origin.x, origin.y); + ctx.translate(origin?.x || 0, origin?.y || 0); } ctx.fill(path); ctx.setTransform(1, 0, 0, 1, 0, 0); @@ -73,11 +86,12 @@ const CanvasBar = (initialProps) => { // This will clear the previous bar without clearing the entire canvas const clearPreviousPath = React.useCallback( - (ctx) => { + (ctx: CanvasRenderingContext2D) => { if (previousPath) { ctx.save(); // This ensures that the entire shape is erased - ctx.lineWidth = style.strokeWidth + 2; + const strokeWidth = (style.strokeWidth as number) || 0; + ctx.lineWidth = strokeWidth + 2; ctx.globalCompositeOperation = "destination-out"; draw(ctx, previousPath); @@ -90,8 +104,8 @@ const CanvasBar = (initialProps) => { ); React.useEffect(() => { - const ctx = canvasRef.current.getContext("2d"); - + const ctx = canvasRef.current?.getContext("2d"); + if (!ctx) return; clearPreviousPath(ctx); draw(ctx, path2d); }, [ @@ -100,38 +114,10 @@ const CanvasBar = (initialProps) => { polar, barWidth, cornerRadius, - props, + modifiedProps, path2d, clearPreviousPath, ]); return null; }; - -CanvasBar.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, - width: PropTypes.number, - x: PropTypes.number, - y: PropTypes.number, - y0: PropTypes.number, -}; - -export default CanvasBar; diff --git a/packages/victory-canvas/src/canvas-curve.js b/packages/victory-canvas/src/canvas-curve.js deleted file mode 100644 index 0bb4dbe1f..000000000 --- a/packages/victory-canvas/src/canvas-curve.js +++ /dev/null @@ -1,40 +0,0 @@ -import PropTypes from "prop-types"; -import React from "react"; -import { CommonProps, LineHelpers } from "victory-core"; -import { useCanvasContext } from "./hooks/use-canvas-context"; - -const CanvasCurve = (props) => { - const { canvasRef, clear, clip } = useCanvasContext(); - const { style, data } = props; - const { stroke, strokeWidth } = style; - - const draw = React.useCallback( - (ctx) => { - const line = LineHelpers.getLineFunction(props); - ctx.strokeStyle = stroke; - ctx.lineWidth = strokeWidth; - line.context(ctx)(data); - ctx.stroke(); - }, - [data, props, stroke, strokeWidth], - ); - - React.useEffect(() => { - const ctx = canvasRef.current.getContext("2d"); - clear(ctx); - draw(ctx); - clip(ctx); - }, [canvasRef, draw, clear, clip]); - - return null; -}; - -CanvasCurve.propTypes = { - ...CommonProps.primitiveProps, - interpolation: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), - openCurve: PropTypes.bool, - origin: PropTypes.object, - polar: PropTypes.bool, -}; - -export default CanvasCurve; diff --git a/packages/victory-canvas/src/canvas-curve.tsx b/packages/victory-canvas/src/canvas-curve.tsx new file mode 100644 index 000000000..7a8e19f91 --- /dev/null +++ b/packages/victory-canvas/src/canvas-curve.tsx @@ -0,0 +1,46 @@ +import React from "react"; +import { + LineHelpers, + NumberOrCallback, + StringOrCallback, + VictoryCommonPrimitiveProps, +} from "victory-core"; +import { useCanvasContext } from "./hooks/use-canvas-context"; +import { LineRadial } from "../../victory-vendor/d3-shape"; + +export interface CanvasCurveProps extends VictoryCommonPrimitiveProps { + ariaLabel?: StringOrCallback; + // eslint-disable-next-line @typescript-eslint/ban-types + interpolation?: string | Function; + openCurve?: boolean; + tabIndex?: NumberOrCallback; +} + +export const CanvasCurve = (props: CanvasCurveProps) => { + const { canvasRef, clear, clip } = useCanvasContext(); + const { style, data } = props; + const { stroke, strokeWidth } = style; + + const draw = React.useCallback( + (ctx: CanvasRenderingContext2D) => { + const line = LineHelpers.getLineFunction(props) as LineRadial< + [number, number] + >; + ctx.strokeStyle = stroke; + ctx.lineWidth = strokeWidth; + line.context(ctx)(data); + ctx.stroke(); + }, + [data, props, stroke, strokeWidth], + ); + + React.useEffect(() => { + const ctx = canvasRef.current?.getContext("2d"); + if (!ctx) return; + clear(ctx); + draw(ctx); + clip(ctx); + }, [canvasRef, draw, clear, clip]); + + return null; +}; diff --git a/packages/victory-canvas/src/canvas-group.js b/packages/victory-canvas/src/canvas-group.js deleted file mode 100644 index a89f17cd0..000000000 --- a/packages/victory-canvas/src/canvas-group.js +++ /dev/null @@ -1,71 +0,0 @@ -import React from "react"; -import { CanvasContext } from "./hooks/use-canvas-context"; -import PropTypes from "prop-types"; -import { PropTypes as CustomPropTypes } from "victory-core"; - -const CanvasGroup = (props) => { - const canvasRef = React.useRef(); - const { children, width, height, clipWidth, padding } = props; - - const clear = React.useCallback( - (ctx) => { - return ctx.clearRect(0, 0, width, height); - }, - [width, height], - ); - - // This needs to be called in the child component to ensure it is called after the - // shape is drawn - const clip = React.useCallback( - (ctx) => { - const maxClipWidth = width - padding.right - padding.left; - ctx.clearRect( - width - padding.right, - 0, - (maxClipWidth - clipWidth) * -1, - height, - ); - }, - [width, height, padding, clipWidth], - ); - - return ( - - - - - {children} - - ); -}; - -CanvasGroup.propTypes = { - "aria-label": PropTypes.string, - children: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.node), - PropTypes.node, - ]), - className: PropTypes.string, - clipWidth: CustomPropTypes.nonNegative, - height: PropTypes.number, - padding: PropTypes.oneOfType([ - PropTypes.number, - PropTypes.shape({ - top: PropTypes.number, - bottom: PropTypes.number, - left: PropTypes.number, - right: PropTypes.number, - }), - ]), - width: PropTypes.number, -}; -CanvasGroup.role = "container"; -CanvasGroup.displayName = "CanvasGroup"; - -export default CanvasGroup; diff --git a/packages/victory-canvas/src/canvas-group.tsx b/packages/victory-canvas/src/canvas-group.tsx new file mode 100644 index 000000000..c83bcb04e --- /dev/null +++ b/packages/victory-canvas/src/canvas-group.tsx @@ -0,0 +1,60 @@ +import React from "react"; +import { CanvasContext } from "./hooks/use-canvas-context"; +import { PaddingProps } from "victory-core"; + +export interface CanvasGroupProps { + children?: React.ReactNode | React.ReactNode[]; + clipWidth?: number; + height?: number; + padding?: PaddingProps; + width?: number; +} + +export const CanvasGroup = (props: CanvasGroupProps) => { + const canvasRef = React.useRef(null); + const { children, width = 0, height = 0, clipWidth, padding } = props; + + const clear = React.useCallback( + (ctx: CanvasRenderingContext2D) => { + return ctx.clearRect(0, 0, width, height); + }, + [width, height], + ); + + // This needs to be called in the child component to ensure it is called after the + // shape is drawn + const clip = React.useCallback( + (ctx: CanvasRenderingContext2D) => { + const paddingRight = + typeof padding === "number" ? padding : padding?.right || 0; + const paddingLeft = + typeof padding === "number" ? padding : padding?.left || 0; + + const maxClipWidth = width - paddingRight - paddingLeft; + ctx.clearRect( + width - paddingRight, + 0, + clipWidth ? (maxClipWidth - clipWidth) * -1 : 0, + height, + ); + }, + [width, height, padding, clipWidth], + ); + + return ( + + + + + {children} + + ); +}; + +CanvasGroup.role = "container"; diff --git a/packages/victory-canvas/src/canvas-point.js b/packages/victory-canvas/src/canvas-point.tsx similarity index 63% rename from packages/victory-canvas/src/canvas-point.js rename to packages/victory-canvas/src/canvas-point.tsx index 7b71fdbe8..41708b50b 100644 --- a/packages/victory-canvas/src/canvas-point.js +++ b/packages/victory-canvas/src/canvas-point.tsx @@ -1,9 +1,24 @@ import React from "react"; -import PropTypes from "prop-types"; import { assign } from "lodash"; -import { Helpers, CommonProps, PointPathHelpers } from "victory-core"; +import { + Helpers, + PointPathHelpers, + ScatterSymbolType, + VictoryCommonPrimitiveProps, +} from "victory-core"; import { useCanvasContext } from "./hooks/use-canvas-context"; +export interface CanvasPointProps extends VictoryCommonPrimitiveProps { + datum?: any; + getPath?: (x: number, y: number, size: number) => string; + // eslint-disable-next-line @typescript-eslint/ban-types + size?: number | Function; + // eslint-disable-next-line @typescript-eslint/ban-types + symbol?: ScatterSymbolType | Function; + x?: number; + y?: number; +} + const getPath = (props) => { const { x, y, size, symbol } = props; if (props.getPath) { @@ -27,7 +42,7 @@ const getPath = (props) => { return symbolFunction(x, y, size); }; -const evaluateProps = (props) => { +const evaluateProps = (props: CanvasPointProps) => { /** * Potential evaluated props are: * `size` @@ -45,37 +60,28 @@ const evaluateProps = (props) => { }); }; -const CanvasPoint = (initialProps) => { +export const CanvasPoint = (props: CanvasPointProps) => { const { canvasRef } = useCanvasContext(); - const props = evaluateProps(initialProps); + const modifiedProps = evaluateProps(props); const draw = React.useCallback( - (ctx) => { - const { style } = props; - const path = getPath(props); + (ctx: CanvasRenderingContext2D) => { + const { style } = modifiedProps; + const path = getPath(modifiedProps); ctx.fillStyle = style.fill; // eslint-disable-next-line no-undef const path2d = new Path2D(path); ctx.fill(path2d); }, - [props], + [modifiedProps], ); React.useEffect(() => { - const ctx = canvasRef.current.getContext("2d"); + const ctx = canvasRef.current?.getContext("2d"); + if (!ctx) return; draw(ctx); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return null; }; - -CanvasPoint.propTypes = { - ...CommonProps.primitiveProps, - datum: PropTypes.object, - size: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - x: PropTypes.number, - y: PropTypes.number, -}; - -export default CanvasPoint; diff --git a/packages/victory-canvas/src/hooks/use-canvas-context.js b/packages/victory-canvas/src/hooks/use-canvas-context.ts similarity index 54% rename from packages/victory-canvas/src/hooks/use-canvas-context.js rename to packages/victory-canvas/src/hooks/use-canvas-context.ts index e93b23ab8..93f40bd93 100644 --- a/packages/victory-canvas/src/hooks/use-canvas-context.js +++ b/packages/victory-canvas/src/hooks/use-canvas-context.ts @@ -1,6 +1,14 @@ import React from "react"; -export const CanvasContext = React.createContext(); +export type CanvasContextValue = { + canvasRef: React.RefObject; + clear: (ctx: CanvasRenderingContext2D) => void; + clip: (ctx: CanvasRenderingContext2D) => void; +}; + +export const CanvasContext = React.createContext< + CanvasContextValue | undefined +>(undefined); export const useCanvasContext = () => { const context = React.useContext(CanvasContext); diff --git a/packages/victory-canvas/src/index.d.ts b/packages/victory-canvas/src/index.d.ts deleted file mode 100644 index 7459c09ff..000000000 --- a/packages/victory-canvas/src/index.d.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { VictoryBarAlignmentType } from "victory-bar"; -import { - VictoryCommonPrimitiveProps, - NumberOrCallback, - StringOrCallback, - ScatterSymbolType, - PaddingProps, -} from "victory-core"; -import * as React from "react"; - -export interface CanvasBarProps 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; - width?: number; - x?: number; - y?: number; - y0?: number; -} - -export class CanvasBar extends React.Component {} - -export interface CanvasCurveProps extends VictoryCommonPrimitiveProps { - ariaLabel?: StringOrCallback; - interpolation?: string | Function; - openCurve?: boolean; - tabIndex?: NumberOrCallback; -} - -export class CanvasCurve extends React.Component {} - -export interface CanvasPointProps extends VictoryCommonPrimitiveProps { - datum?: any; - getPath?: (x: number, y: number, size: number) => string; - size?: number | Function; - symbol?: ScatterSymbolType | Function; - x?: number; - y?: number; -} - -export class CanvasPoint extends React.Component {} - -export interface CanvasGroupProps { - children?: React.ReactNode | React.ReactNode[]; - clipWidth?: number; - height?: number; - padding?: PaddingProps; - width?: number; -} - -export class CanvasGroup extends React.Component {} - -export interface CanvasContextValue { - canvasRef: React.RefObject; - clear(ctx: CanvasRenderingContext2D): void; - clip(ctx: CanvasRenderingContext2D): void; -} - -export const useCanvasContext: () => CanvasContextValue; diff --git a/packages/victory-canvas/src/index.js b/packages/victory-canvas/src/index.js deleted file mode 100644 index a2d4bed68..000000000 --- a/packages/victory-canvas/src/index.js +++ /dev/null @@ -1,5 +0,0 @@ -export { default as CanvasBar } from "./canvas-bar"; -export { default as CanvasGroup } from "./canvas-group"; -export { default as CanvasCurve } from "./canvas-curve"; -export { default as CanvasPoint } from "./canvas-point"; -export { useCanvasContext } from "./hooks/use-canvas-context"; diff --git a/packages/victory-canvas/src/index.ts b/packages/victory-canvas/src/index.ts new file mode 100644 index 000000000..e68329729 --- /dev/null +++ b/packages/victory-canvas/src/index.ts @@ -0,0 +1,5 @@ +export * from "./canvas-bar"; +export * from "./canvas-group"; +export * from "./canvas-curve"; +export * from "./canvas-point"; +export { useCanvasContext } from "./hooks/use-canvas-context";