victory-core
Version:
161 lines (147 loc) • 4.52 kB
text/typescript
import React from "react";
import * as Collection from "./collection";
import { VictoryCommonProps } from "./common-props";
import type { DomainPropType, ScaleXYPropType } from "../types/prop-types";
// Private Functions
function transformTarget(
target: number,
matrix: DOMMatrix,
dimension: "x" | "y",
) {
const { a, d, e, f } = matrix;
return dimension === "y" ? d * target + f : a * target + e;
}
function getTransformationMatrix(svg: SVGGraphicsElement): DOMMatrix {
return svg.getScreenCTM()!.inverse();
}
interface ReactNativeTouchEvent extends Event {
identifier?: number;
locationX: number;
locationY: number;
}
function isNativeTouchEvent(
nativeEvent: Event | undefined,
): nativeEvent is ReactNativeTouchEvent {
return !!(
nativeEvent &&
(nativeEvent as ReactNativeTouchEvent).identifier !== undefined
);
}
function isReactTouchEvent(evt: React.SyntheticEvent): evt is React.TouchEvent {
return (
(evt as React.TouchEvent).changedTouches &&
(evt as React.TouchEvent).changedTouches.length > 0
);
}
// Exported Functions
export function getParentSVG(
evt: Pick<React.SyntheticEvent, "target"> &
Partial<Pick<React.SyntheticEvent, "nativeEvent">>,
): SVGElement {
if (isNativeTouchEvent(evt.nativeEvent)) {
// @ts-expect-error Seems like a superfluous check.
return undefined;
}
const getParent = (target) => {
if (target.nodeName === "svg") {
return target;
}
return target.parentNode ? getParent(target.parentNode) : target;
};
return getParent(evt.target);
}
export function getSVGEventCoordinates(
evt: React.SyntheticEvent,
svg?: SVGElement,
): SVGCoordinateType {
if (isNativeTouchEvent(evt.nativeEvent)) {
// react-native override. relies on the RN.View being the _exact_ same size as its child SVG.
// this should be fine: the svg is the only child of View and the View shirks to its children
return {
x: evt.nativeEvent.locationX,
y: evt.nativeEvent.locationY,
};
}
const location = isReactTouchEvent(evt)
? evt.changedTouches[0]
: (evt as React.MouseEvent);
const matrix = getTransformationMatrix(
(svg as SVGGraphicsElement) || getParentSVG(location),
);
return {
x: transformTarget(location.clientX, matrix, "x"),
y: transformTarget(location.clientY, matrix, "y"),
};
}
export function getDomainCoordinates(
props: Pick<VictoryCommonProps, "scale" | "horizontal">,
domain?: DomainPropType,
) {
const { horizontal } = props;
const scale = props.scale as ScaleXYPropType;
// FIXME: add support for DomainTuple: [number, number]
const domainObj: any = domain || {
x: scale.x.domain(),
y: scale.y.domain(),
};
return {
x: horizontal
? [scale.y(domainObj.y[0]), scale.y(domainObj.y[1])]
: [scale.x(domainObj.x[0]), scale.x(domainObj.x[1])],
y: horizontal
? [scale.x(domainObj.x[0]), scale.x(domainObj.x[1])]
: [scale.y(domainObj.y[0]), scale.y(domainObj.y[1])],
};
}
// eslint-disable-next-line max-params
export function getDataCoordinates(
props: any,
scale: ScaleXYPropType,
x: number,
y: number,
): SVGCoordinateType {
const { polar, horizontal } = props;
if (!polar) {
return {
x: horizontal ? scale.x.invert(y) : scale.x.invert(x),
y: horizontal ? scale.y.invert(x) : scale.y.invert(y),
};
}
const origin = props.origin || { x: 0, y: 0 };
const baseX = x - origin.x;
const baseY = y - origin.y;
const radius = Math.abs(baseX * Math.sqrt(1 + Math.pow(-baseY / baseX, 2)));
const angle = (-Math.atan2(baseY, baseX) + Math.PI * 2) % (Math.PI * 2);
return {
x: scale.x.invert(angle),
y: scale.y.invert(radius),
};
}
export function getBounds(props: ComputedCommonProps): SVGCoordinateBounds {
const { x1, x2, y1, y2, scale } = props;
const point1 = getDataCoordinates(props, scale, x1, y1);
const point2 = getDataCoordinates(props, scale, x2, y2);
const makeBound = (a: number, b: number) => {
return [
Collection.getMinValue([a, b]) as number,
Collection.getMaxValue([a, b]) as number,
] as const;
};
return {
x: makeBound(point1.x, point2.x),
y: makeBound(point1.y, point2.y),
};
}
export type SVGCoordinateType = { x: number; y: number };
export type SVGCoordinateBounds = {
x: readonly [number, number];
y: readonly [number, number];
};
type ComputedCommonProps = {
scale: ScaleXYPropType;
x1: number;
x2: number;
y1: number;
y2: number;
horizontal?: boolean;
};