diff --git a/.changeset/poor-crabs-hear.md b/.changeset/poor-crabs-hear.md new file mode 100644 index 000000000..61cfba9a8 --- /dev/null +++ b/.changeset/poor-crabs-hear.md @@ -0,0 +1,16 @@ +--- +"victory-axis": patch +"victory-chart": patch +"victory-core": patch +"victory-cursor-container": patch +"victory-group": patch +"victory-legend": patch +"victory-pie": patch +"victory-polar-axis": patch +"victory-scatter": patch +"victory-stack": patch +"victory-tooltip": patch +"victory-voronoi-container": patch +--- + +Improve types in victory-core helpers diff --git a/packages/victory-axis/src/helper-methods.tsx b/packages/victory-axis/src/helper-methods.tsx index 7b02492f3..1f21c643f 100644 --- a/packages/victory-axis/src/helper-methods.tsx +++ b/packages/victory-axis/src/helper-methods.tsx @@ -467,7 +467,7 @@ const getOrientation = (props) => { const getCalculatedValues = (props) => { const defaultStyles = getStyleObject(props); const style = getStyles(props, defaultStyles); - const padding = Helpers.getPadding(props); + const padding = Helpers.getPadding(props.padding); const labelPadding = getLabelPadding(props, style); const stringTicks = Axis.stringTicks(props) ? props.tickValues : undefined; const axis = Axis.getAxis(props); diff --git a/packages/victory-chart/src/helper-methods.tsx b/packages/victory-chart/src/helper-methods.tsx index c269a62ef..855dff833 100644 --- a/packages/victory-chart/src/helper-methods.tsx +++ b/packages/victory-chart/src/helper-methods.tsx @@ -115,7 +115,7 @@ export function getCalculatedProps(initialProps, childComponents) { const origin = polar ? Helpers.getPolarOrigin(props) : Axis.getOrigin(domain); - const padding = Helpers.getPadding(props); + const padding = Helpers.getPadding(props.padding); return { categories, diff --git a/packages/victory-core/src/victory-clip-container/victory-clip-container.tsx b/packages/victory-core/src/victory-clip-container/victory-clip-container.tsx index c6e38297f..4321afc3e 100644 --- a/packages/victory-core/src/victory-clip-container/victory-clip-container.tsx +++ b/packages/victory-core/src/victory-clip-container/victory-clip-container.tsx @@ -75,7 +75,7 @@ export class VictoryClipContainer extends React.Component< translateX = 0, translateY = 0, } = props; - const clipPadding = Helpers.getPadding({ padding: props.clipPadding }); + const clipPadding = Helpers.getPadding(props.clipPadding); const radius = props.radius || Helpers.getRadius(props); return { x: (polar ? origin.x : translateX) - clipPadding.left, @@ -158,9 +158,7 @@ export class VictoryClipContainer extends React.Component< rectComponent, clipPathComponent, } = props; - const { top, bottom, left, right } = Helpers.getPadding({ - padding: props.clipPadding, - }); + const { top, bottom, left, right } = Helpers.getPadding(props.clipPadding); let child; if (polar) { const radius = props.radius || Helpers.getRadius(props); diff --git a/packages/victory-core/src/victory-label/victory-label.tsx b/packages/victory-core/src/victory-label/victory-label.tsx index 0d0a590b7..ed31de2c1 100644 --- a/packages/victory-core/src/victory-label/victory-label.tsx +++ b/packages/victory-core/src/victory-label/victory-label.tsx @@ -146,11 +146,11 @@ const getBackgroundPadding = (props) => { if (props.backgroundPadding && Array.isArray(props.backgroundPadding)) { return props.backgroundPadding.map((backgroundPadding) => { const padding = Helpers.evaluateProp(backgroundPadding, props); - return Helpers.getPadding({ padding }); + return Helpers.getPadding(padding); }); } const padding = Helpers.evaluateProp(props.backgroundPadding, props); - return Helpers.getPadding({ padding }); + return Helpers.getPadding(padding); }; const getLineHeight = (props) => { diff --git a/packages/victory-core/src/victory-util/helpers.test.ts b/packages/victory-core/src/victory-util/helpers.test.ts index a2f0937af..7230bd448 100644 --- a/packages/victory-core/src/victory-util/helpers.test.ts +++ b/packages/victory-core/src/victory-util/helpers.test.ts @@ -165,7 +165,7 @@ describe("victory-util/helpers", () => { describe("getPadding", () => { it("sets padding from a single number", () => { const props = { padding: 40 }; - expect(Helpers.getPadding(props)).toEqual({ + expect(Helpers.getPadding(props.padding)).toEqual({ top: 40, bottom: 40, left: 40, @@ -177,14 +177,14 @@ describe("victory-util/helpers", () => { const props = { padding: { top: 20, bottom: 40, left: 60, right: 80 }, }; - expect(Helpers.getPadding(props)).toEqual(props.padding); + expect(Helpers.getPadding(props.padding)).toEqual(props.padding); }); it("fills missing values with 0", () => { const props = { padding: { top: 40, bottom: 40 }, }; - expect(Helpers.getPadding(props)).toEqual({ + expect(Helpers.getPadding(props.padding)).toEqual({ top: 40, bottom: 40, left: 0, diff --git a/packages/victory-core/src/victory-util/helpers.ts b/packages/victory-core/src/victory-util/helpers.ts index 57a6d83d6..29517a6f4 100644 --- a/packages/victory-core/src/victory-util/helpers.ts +++ b/packages/victory-core/src/victory-util/helpers.ts @@ -5,29 +5,73 @@ import pick from "lodash/pick"; import { ValueOrAccessor } from "../types/prop-types"; -// Private Functions +export type ElementPadding = { + top: number; + bottom: number; + left: number; + right: number; +}; + +export type MaybePointData = { + x?: number; + x0?: number; + x1?: number; + y?: number; + y0?: number; + y1?: number; + _x?: number; + _x0?: number; + _x1?: number; + _y?: number; + _y0?: number; + _y1?: number; + _voronoiX?: number; + _voronoiY?: number; +}; -function getCartesianRange(props, axis) { - // determine how to lay the axis and what direction positive and negative are - const vertical = axis !== "x"; - const padding = getPadding(props); +/** + * Determine the range of a cartesian axis + */ +function getCartesianRange(options: { + axis: "x" | "y"; + height: number; + width: number; + padding: ElementPadding; +}): [number, number] { + const vertical = options.axis !== "x"; if (vertical) { - return [props.height - padding.bottom, padding.top]; + return [options.height - options.padding.bottom, options.padding.top]; } - return [padding.left, props.width - padding.right]; + return [options.padding.left, options.width - options.padding.right]; } -function getPolarRange(props, axis) { - if (axis === "x") { - const startAngle = degreesToRadians(props.startAngle || 0); - const endAngle = degreesToRadians(props.endAngle || 360); +/** + * Determine the range of a polar axis in radians + */ +function getPolarRange(options: { + axis: "x" | "y"; + innerRadius?: number; + startAngle?: number; + endAngle?: number; + padding: ElementPadding; + height: number; + width: number; +}): [number, number] { + if (options.axis === "x") { + const startAngle = degreesToRadians(options.startAngle || 0); + const endAngle = degreesToRadians(options.endAngle || 360); return [startAngle, endAngle]; } - return [props.innerRadius || 0, getRadius(props)]; + return [ + options.innerRadius || 0, + getRadius({ + height: options.height, + width: options.width, + padding: options.padding, + }), + ]; } -// Exported Functions - /** * Creates an object composed of the inverted keys and values of object. * If object contains duplicate values, subsequent values overwrite property assignments of previous values. @@ -65,21 +109,36 @@ export function omit( return newObject; } -export function getPoint(datum) { - const exists = (val) => val !== undefined; +/** + * Coalesce the x and y values from a data point + */ +export function getPoint(datum: MaybePointData): MaybePointData { const { _x, _x1, _x0, _voronoiX, _y, _y1, _y0, _voronoiY } = datum; - const defaultX = exists(_x1) ? _x1 : _x; - const defaultY = exists(_y1) ? _y1 : _y; + const defaultX = _x1 ?? _x; + const defaultY = _y1 ?? _y; + const point = { - x: exists(_voronoiX) ? _voronoiX : defaultX, - x0: exists(_x0) ? _x0 : _x, - y: exists(_voronoiY) ? _voronoiY : defaultY, - y0: exists(_y0) ? _y0 : _y, + x: _voronoiX ?? defaultX, + x0: _x0 ?? _x, + y: _voronoiY ?? defaultY, + y0: _y0 ?? _y, }; + return defaults({}, point, datum); } -export function scalePoint(props, datum) { +/** + * Scale a point based on the origin, direction, and given scale function + */ +export function scalePoint( + props: { + scale: { x: (x?: number) => number; y: (y?: number) => number }; + polar?: boolean; + horizontal?: boolean; + origin?: { x: number; y: number }; + }, + datum: MaybePointData, +) { const { scale, polar, horizontal } = props; const d = getPoint(datum); const origin = props.origin || { x: 0, y: 0 }; @@ -95,8 +154,12 @@ export function scalePoint(props, datum) { }; } -export function getPadding(props, name = "padding") { - const padding = props[name]; +/** + * Returns a padding value from a number or partial padding values + */ +export function getPadding( + padding?: number | Partial, +): ElementPadding { const paddingVal = typeof padding === "number" ? padding : 0; const paddingObj = typeof padding === "object" ? padding : {}; return { @@ -107,7 +170,10 @@ export function getPadding(props, name = "padding") { }; } -export function isTooltip(component) { +/** + * Returns true if the component is defined as a tooltip + */ +export function isTooltip(component?: { type?: { role?: string } }) { const labelRole = component && component.type && component.type.role; return labelRole === "tooltip"; } @@ -140,6 +206,9 @@ export function getStyles(style, defaultStyles) { }; } +/** + * Returns the value of a prop or accessor function with the given props + */ export function evaluateProp( prop: ValueOrAccessor>, props: Record, @@ -168,15 +237,29 @@ export function radiansToDegrees(radians) { return typeof radians === "number" ? radians / (Math.PI / 180) : radians; } -export function getRadius(props) { - const { left, right, top, bottom } = getPadding(props); - const { width, height } = props; +/** + * Get the maximum radius that will fit in the container + */ +export function getRadius(options: { + height: number; + width: number; + padding: ElementPadding; +}) { + const { width, height, padding } = options; + const { left, right, top, bottom } = padding; return Math.min(width - left - right, height - top - bottom) / 2; } -export function getPolarOrigin(props) { +/** + * Returns the origin for a polar chart within the padded area + */ +export function getPolarOrigin(props: { + height: number; + width: number; + padding: ElementPadding; +}): { x: number; y: number } { const { width, height } = props; - const { top, bottom, left, right } = getPadding(props); + const { top, bottom, left, right } = getPadding(props.padding); const radius = Math.min(width - left - right, height - top - bottom) / 2; const offsetWidth = width / 2 + left - right; const offsetHeight = height / 2 + top - bottom; @@ -186,15 +269,43 @@ export function getPolarOrigin(props) { }; } -export function getRange(props, axis) { +/** + * Determine the range of an axis based on the given props + */ +export function getRange( + props: { + range?: [number, number]; + polar?: boolean; + innerRadius?: number; + startAngle?: number; + endAngle?: number; + height: number; + width: number; + padding: number | Partial; + }, + axis: "x" | "y", +) { if (props.range && props.range[axis]) { return props.range[axis]; } else if (props.range && Array.isArray(props.range)) { return props.range; } return props.polar - ? getPolarRange(props, axis) - : getCartesianRange(props, axis); + ? getPolarRange({ + axis, + innerRadius: props.innerRadius, + startAngle: props.startAngle, + endAngle: props.endAngle, + height: props.height, + width: props.width, + padding: getPadding(props.padding), + }) + : getCartesianRange({ + axis, + height: props.height, + width: props.width, + padding: getPadding(props.padding), + }); } /** @@ -392,7 +503,10 @@ export function reduceChildren< * @returns {Boolean} returns true if the props object contains `horizontal: true` of if any * children or nested children are horizontal */ -export function isHorizontal(props) { +export function isHorizontal(props: { + horizontal?: boolean; + children?: React.ReactNode; +}) { if (props.horizontal !== undefined || !props.children) { return props.horizontal; } diff --git a/packages/victory-core/src/victory-util/type-helpers.ts b/packages/victory-core/src/victory-util/type-helpers.ts index b160edc05..24f14137e 100644 --- a/packages/victory-core/src/victory-util/type-helpers.ts +++ b/packages/victory-core/src/victory-util/type-helpers.ts @@ -30,11 +30,6 @@ export function getValueForAxis( return value; } -// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -export function isFunction(func?: unknown): func is T { - return typeof func === "function"; -} - export function isDate(value: unknown): value is Date { return value instanceof Date; } diff --git a/packages/victory-cursor-container/src/victory-cursor-container.tsx b/packages/victory-cursor-container/src/victory-cursor-container.tsx index 8fcb05c7f..4f69ac75f 100644 --- a/packages/victory-cursor-container/src/victory-cursor-container.tsx +++ b/packages/victory-cursor-container/src/victory-cursor-container.tsx @@ -97,7 +97,7 @@ export const useVictoryCursorContainer = ( : props.children; return Helpers.getPadding(child?.props); } - return Helpers.getPadding(props); + return Helpers.getPadding(props.padding); }; const getCursorElements = () => { diff --git a/packages/victory-group/src/helper-methods.tsx b/packages/victory-group/src/helper-methods.tsx index 189ccf365..6dc1d3340 100644 --- a/packages/victory-group/src/helper-methods.tsx +++ b/packages/victory-group/src/helper-methods.tsx @@ -43,7 +43,7 @@ export function getCalculatedProps(initialProps, childComponents) { }; const origin = polar ? props.origin : Helpers.getPolarOrigin(props); - const padding = Helpers.getPadding(props); + const padding = Helpers.getPadding(props.padding); return { datasets, categories, diff --git a/packages/victory-legend/src/helper-methods.ts b/packages/victory-legend/src/helper-methods.ts index b8828c677..3c8e52aa4 100644 --- a/packages/victory-legend/src/helper-methods.ts +++ b/packages/victory-legend/src/helper-methods.ts @@ -38,7 +38,7 @@ const getCalculatedValues = (props) => { const style = getStyles(props, defaultStyles); const colorScale = getColorScale(props); const isHorizontal = orientation === "horizontal"; - const borderPadding = Helpers.getPadding({ padding: props.borderPadding }); + const borderPadding = Helpers.getPadding(props.borderPadding); return Object.assign({}, props, { style, isHorizontal, diff --git a/packages/victory-pie/src/helper-methods.ts b/packages/victory-pie/src/helper-methods.ts index 4256a474f..62fd6b30a 100644 --- a/packages/victory-pie/src/helper-methods.ts +++ b/packages/victory-pie/src/helper-methods.ts @@ -91,7 +91,7 @@ const getCalculatedValues = (props) => { const colors = Array.isArray(colorScale) ? colorScale : Style.getColorScale(colorScale, theme); - const padding = Helpers.getPadding(props); + const padding = Helpers.getPadding(props.padding); const defaultRadius = getRadius(props, padding); const origin = getOrigin(props, padding); const data = getDataSortedByCategories(props, Data.getData(props)); diff --git a/packages/victory-polar-axis/src/helper-methods.ts b/packages/victory-polar-axis/src/helper-methods.ts index aef399a06..2ac805281 100644 --- a/packages/victory-polar-axis/src/helper-methods.ts +++ b/packages/victory-polar-axis/src/helper-methods.ts @@ -63,7 +63,7 @@ const getStyleObject = (props: VictoryPolarAxisProps) => { }; const getRadius = (props: VictoryPolarAxisProps) => { - const { left, right, top, bottom } = Helpers.getPadding(props); + const { left, right, top, bottom } = Helpers.getPadding(props.padding); const { width, height } = props; if (width === undefined || height === undefined) { @@ -439,7 +439,7 @@ const getCalculatedValues = (initialProps: VictoryPolarAxisProps) => { const props = Object.assign({ polar: true }, initialProps); const defaultStyles = getStyleObject(props); const style = getStyles(props, defaultStyles); - const padding = Helpers.getPadding(props); + const padding = Helpers.getPadding(props.padding); const axis = Axis.getAxis(props); const axisType = getAxisType(props); const stringTicks = Axis.stringTicks(props) ? props.tickValues : undefined; @@ -451,7 +451,10 @@ const getCalculatedValues = (initialProps: VictoryPolarAxisProps) => { axisType === "angular" ? filterTicks(initialTicks, scale) : initialTicks; const tickFormat = Axis.getTickFormat(props, scale); const radius = getRadius(props); + + // @ts-expect-error props is not typed but contains width and height const origin = Helpers.getPolarOrigin(props); + return { axis, style, diff --git a/packages/victory-scatter/src/helper-methods.tsx b/packages/victory-scatter/src/helper-methods.tsx index 6518f7ad7..7dcfc3d34 100644 --- a/packages/victory-scatter/src/helper-methods.tsx +++ b/packages/victory-scatter/src/helper-methods.tsx @@ -13,7 +13,9 @@ export const getBubbleSize = (datum, props) => { const zMin = Math.min(...zData); const zMax = Math.max(...zData); const getMaxRadius = () => { - const minPadding = Math.min(...Object.values(Helpers.getPadding(props))); + const minPadding = Math.min( + ...Object.values(Helpers.getPadding(props.padding)), + ); return Math.max(minPadding, 5); // eslint-disable-line no-magic-numbers }; const maxRadius = maxBubbleSize || getMaxRadius(); diff --git a/packages/victory-stack/src/helper-methods.tsx b/packages/victory-stack/src/helper-methods.tsx index be32d34ee..54f7ca1f7 100644 --- a/packages/victory-stack/src/helper-methods.tsx +++ b/packages/victory-stack/src/helper-methods.tsx @@ -211,7 +211,7 @@ export function getChildProps(props, calculatedProps) { return { height: props.height, width: props.width, - padding: Helpers.getPadding(props), + padding: Helpers.getPadding(props.padding), standalone: false, theme: props.theme, categories, diff --git a/packages/victory-tooltip/src/victory-tooltip.tsx b/packages/victory-tooltip/src/victory-tooltip.tsx index e493e71e6..3479d9137 100644 --- a/packages/victory-tooltip/src/victory-tooltip.tsx +++ b/packages/victory-tooltip/src/victory-tooltip.tsx @@ -244,7 +244,7 @@ export class VictoryTooltip extends React.Component { }), ) || this.getLabelPadding(style); - const flyoutPadding = Helpers.getPadding({ padding }); + const flyoutPadding = Helpers.getPadding(padding); const pointerWidth = Helpers.evaluateProp( props.pointerWidth, diff --git a/packages/victory-voronoi-container/src/victory-voronoi-container.tsx b/packages/victory-voronoi-container/src/victory-voronoi-container.tsx index b1a919d25..bb717d5e8 100644 --- a/packages/victory-voronoi-container/src/victory-voronoi-container.tsx +++ b/packages/victory-voronoi-container/src/victory-voronoi-container.tsx @@ -66,6 +66,8 @@ export const useVictoryVoronoiContainer = ( const { mousePosition, mouseFollowTooltips } = props; const voronoiDimension = getDimension(); const point = getPoint(points[0]); + + // @ts-expect-error scale is defined but the types do not reflect that const basePosition = Helpers.scalePoint(props, point); let center = mouseFollowTooltips ? mousePosition : undefined; diff --git a/packages/victory-voronoi-container/src/voronoi-helpers.ts b/packages/victory-voronoi-container/src/voronoi-helpers.ts index b8f349d0c..170b39ecc 100644 --- a/packages/victory-voronoi-container/src/voronoi-helpers.ts +++ b/packages/victory-voronoi-container/src/voronoi-helpers.ts @@ -11,7 +11,7 @@ const ON_MOUSE_MOVE_THROTTLE_MS = 32; class VoronoiHelpersClass { withinBounds(props, point) { const { width, height, polar, origin, scale } = props; - const padding = Helpers.getPadding(props, "voronoiPadding"); + const padding = Helpers.getPadding(props.voronoiPadding); const { x, y } = point; if (polar) { const distanceSquared =