Skip to content

Commit

Permalink
Migrate victory-canvas to TypeScript (#2710)
Browse files Browse the repository at this point in the history
  • Loading branch information
KenanYusuf authored Jan 12, 2024
1 parent 9dff1f3 commit b6ceae1
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 263 deletions.
5 changes: 5 additions & 0 deletions .changeset/beige-flowers-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"victory-canvas": patch
---

Migrate victory-canvas to TypeScript
Original file line number Diff line number Diff line change
@@ -1,69 +1,82 @@
import { assign } from "lodash";
import PropTypes from "prop-types";
import React from "react";
import {
BarProps,
VictoryBarAlignmentType,
VictoryBarCornerRadiusObject,
getBarPath,
getBarWidth,
getCornerRadius,
getPolarBarPath,
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;
});
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);
Expand All @@ -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);
Expand All @@ -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);
}, [
Expand All @@ -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;
40 changes: 0 additions & 40 deletions packages/victory-canvas/src/canvas-curve.js

This file was deleted.

46 changes: 46 additions & 0 deletions packages/victory-canvas/src/canvas-curve.tsx
Original file line number Diff line number Diff line change
@@ -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;
};
71 changes: 0 additions & 71 deletions packages/victory-canvas/src/canvas-group.js

This file was deleted.

60 changes: 60 additions & 0 deletions packages/victory-canvas/src/canvas-group.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLCanvasElement>(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 (
<CanvasContext.Provider
value={{
canvasRef,
clear,
clip,
}}
>
<foreignObject width={width} height={height} x={0} y={0}>
<canvas width={width} height={height} ref={canvasRef} />
</foreignObject>
{children}
</CanvasContext.Provider>
);
};

CanvasGroup.role = "container";
Loading

1 comment on commit b6ceae1

@vercel
Copy link

@vercel vercel bot commented on b6ceae1 Jan 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.