Skip to content

Commit

Permalink
Tooltip translate tests (recharts#3872)
Browse files Browse the repository at this point in the history
  • Loading branch information
PavelVanecek authored and Gabriel Mercier committed Nov 23, 2023
1 parent 25b7a29 commit 70142fb
Show file tree
Hide file tree
Showing 4 changed files with 536 additions and 103 deletions.
144 changes: 41 additions & 103 deletions src/component/Tooltip.tsx
Expand Up @@ -4,15 +4,12 @@
import React, { PureComponent, CSSProperties, ReactNode, ReactElement, SVGProps } from 'react';
import { translateStyle } from 'react-smooth';
import _ from 'lodash';
import classNames from 'classnames';
import { DefaultTooltipContent, ValueType, NameType, Payload, Props as DefaultProps } from './DefaultTooltipContent';

import { Global } from '../util/Global';
import { isNumber } from '../util/DataUtils';
import { AnimationDuration, AnimationTiming } from '../util/types';
import { UniqueOption, getUniqPayload } from '../util/payload/getUniqPayload';

const CLS_PREFIX = 'recharts-tooltip-wrapper';
import { AllowInDimension, AnimationDuration, AnimationTiming, Coordinate } from '../util/types';
import { getTooltipTranslate } from '../util/tooltip/translate';

const EPS = 1;
export type ContentType<TValue extends ValueType, TName extends NameType> =
Expand All @@ -38,14 +35,8 @@ function renderContent<TValue extends ValueType, TName extends NameType>(
}

export type TooltipProps<TValue extends ValueType, TName extends NameType> = DefaultProps<TValue, TName> & {
allowEscapeViewBox?: {
x?: boolean;
y?: boolean;
};
reverseDirection?: {
x?: boolean;
y?: boolean;
};
allowEscapeViewBox?: AllowInDimension;
reverseDirection?: AllowInDimension;
content?: ContentType<TValue, TName>;
viewBox?: {
x?: number;
Expand All @@ -57,14 +48,8 @@ export type TooltipProps<TValue extends ValueType, TName extends NameType> = Def
offset?: number;
wrapperStyle?: CSSProperties;
cursor?: boolean | ReactElement | SVGProps<SVGElement>;
coordinate?: {
x?: number;
y?: number;
};
position?: {
x?: number;
y?: number;
};
coordinate?: Partial<Coordinate>;
position?: Partial<Coordinate>;
trigger?: 'hover' | 'click';
shared?: boolean;
payloadUniqBy?: UniqueOption<Payload<TValue, TName>>;
Expand Down Expand Up @@ -167,93 +152,55 @@ export class Tooltip<TValue extends ValueType, TName extends NameType> extends P
}
}

getTranslate = ({
key,
tooltipDimension,
viewBoxDimension,
}: {
key: 'x' | 'y';
tooltipDimension: number;
viewBoxDimension: number;
}) => {
const { allowEscapeViewBox, reverseDirection, coordinate, offset, position, viewBox } = this.props;

if (position && isNumber(position[key])) {
return position[key];
}

const negative = coordinate[key] - tooltipDimension - offset;
const positive = coordinate[key] + offset;
if (allowEscapeViewBox[key]) {
return reverseDirection[key] ? negative : positive;
}

if (reverseDirection[key]) {
const tooltipBoundary = negative;
const viewBoxBoundary = viewBox[key];
if (tooltipBoundary < viewBoxBoundary) {
return Math.max(positive, viewBox[key]);
}
return Math.max(negative, viewBox[key]);
}
const tooltipBoundary = positive + tooltipDimension;
const viewBoxBoundary = viewBox[key] + viewBoxDimension;
if (tooltipBoundary > viewBoxBoundary) {
return Math.max(negative, viewBox[key]);
}
return Math.max(positive, viewBox[key]);
};

render() {
const { payload, isAnimationActive, animationDuration, animationEasing, filterNull, payloadUniqBy } = this.props;
const {
active,
allowEscapeViewBox,
animationDuration,
animationEasing,
content,
coordinate,
filterNull,
isAnimationActive,
offset,
payload,
payloadUniqBy,
position,
reverseDirection,
useTranslate3d,
viewBox,
wrapperStyle,
} = this.props;
const finalPayload = getUniqPayload(
filterNull && payload && payload.length ? payload.filter(entry => !_.isNil(entry.value)) : payload,
payloadUniqBy,
defaultUniqBy,
);
const hasPayload = finalPayload && finalPayload.length;
const { content, viewBox, coordinate, position, active, wrapperStyle } = this.props;

const { cssClasses, cssProperties } = getTooltipTranslate({
allowEscapeViewBox,
coordinate,
offsetTopLeft: offset,
position,
reverseDirection,
tooltipBox: {
height: this.state.boxHeight,
width: this.state.boxWidth,
},
useTranslate3d,
viewBox,
});

let outerStyle: CSSProperties = {
...cssProperties,
pointerEvents: 'none',
visibility: !this.state.dismissed && active && hasPayload ? 'visible' : 'hidden',
position: 'absolute',
top: 0,
left: 0,
...wrapperStyle,
};
let translateX, translateY;

if (position && isNumber(position.x) && isNumber(position.y)) {
translateX = position.x;
translateY = position.y;
} else {
const { boxWidth, boxHeight } = this.state;

if (boxWidth > 0 && boxHeight > 0 && coordinate) {
translateX = this.getTranslate({
key: 'x',
tooltipDimension: boxWidth,
viewBoxDimension: viewBox.width,
});

translateY = this.getTranslate({
key: 'y',
tooltipDimension: boxHeight,
viewBoxDimension: viewBox.height,
});
} else {
outerStyle.visibility = 'hidden';
}
}

outerStyle = {
...translateStyle({
transform: this.props.useTranslate3d
? `translate3d(${translateX}px, ${translateY}px, 0)`
: `translate(${translateX}px, ${translateY}px)`,
}),
...outerStyle,
};

if (isAnimationActive && active) {
outerStyle = {
Expand All @@ -264,23 +211,14 @@ export class Tooltip<TValue extends ValueType, TName extends NameType> extends P
};
}

const cls = classNames(CLS_PREFIX, {
[`${CLS_PREFIX}-right`]:
isNumber(translateX) && coordinate && isNumber(coordinate.x) && translateX >= coordinate.x,
[`${CLS_PREFIX}-left`]: isNumber(translateX) && coordinate && isNumber(coordinate.x) && translateX < coordinate.x,
[`${CLS_PREFIX}-bottom`]:
isNumber(translateY) && coordinate && isNumber(coordinate.y) && translateY >= coordinate.y,
[`${CLS_PREFIX}-top`]: isNumber(translateY) && coordinate && isNumber(coordinate.y) && translateY < coordinate.y,
});

return (
// ESLint is disabled to allow listening to the `Escape` key. Refer to
// https://github.com/recharts/recharts/pull/2925
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
<div
tabIndex={-1}
role="dialog"
className={cls}
className={cssClasses}
style={outerStyle}
ref={node => {
this.wrapperNode = node;
Expand Down
157 changes: 157 additions & 0 deletions src/util/tooltip/translate.ts
@@ -0,0 +1,157 @@
import { CSSProperties } from 'react';
import classNames from 'classnames';
import { translateStyle } from 'react-smooth';
import { isNumber } from '../DataUtils';
import { Coordinate, CartesianViewBox, AllowInDimension } from '../types';

export type Dimension2D = 'x' | 'y';

const CSS_CLASS_PREFIX = 'recharts-tooltip-wrapper';

const TOOLTIP_HIDDEN: CSSProperties = { visibility: 'hidden' };

export function getTooltipCSSClassName({
coordinate,
translateX,
translateY,
}: {
translateX: number | undefined;
translateY: number | undefined;
coordinate: Partial<Coordinate> | undefined;
}): string {
return classNames(CSS_CLASS_PREFIX, {
[`${CSS_CLASS_PREFIX}-right`]:
isNumber(translateX) && coordinate && isNumber(coordinate.x) && translateX >= coordinate.x,
[`${CSS_CLASS_PREFIX}-left`]:
isNumber(translateX) && coordinate && isNumber(coordinate.x) && translateX < coordinate.x,
[`${CSS_CLASS_PREFIX}-bottom`]:
isNumber(translateY) && coordinate && isNumber(coordinate.y) && translateY >= coordinate.y,
[`${CSS_CLASS_PREFIX}-top`]:
isNumber(translateY) && coordinate && isNumber(coordinate.y) && translateY < coordinate.y,
});
}

export function getTooltipTranslateXY({
allowEscapeViewBox,
coordinate,
key,
offsetTopLeft,
position,
reverseDirection,
tooltipDimension,
viewBox,
viewBoxDimension,
}: {
allowEscapeViewBox: AllowInDimension;
coordinate: Partial<Coordinate>;
key: Dimension2D;
offsetTopLeft: number;
position: Partial<Coordinate>;
reverseDirection: AllowInDimension;
tooltipDimension: number;
viewBox: CartesianViewBox;
viewBoxDimension: number;
}): number {
if (position && isNumber(position[key])) {
return position[key];
}

const negative = coordinate[key] - tooltipDimension - offsetTopLeft;
const positive = coordinate[key] + offsetTopLeft;
if (allowEscapeViewBox[key]) {
return reverseDirection[key] ? negative : positive;
}

if (reverseDirection[key]) {
const tooltipBoundary = negative;
const viewBoxBoundary = viewBox[key];
if (tooltipBoundary < viewBoxBoundary) {
return Math.max(positive, viewBox[key]);
}
return Math.max(negative, viewBox[key]);
}
const tooltipBoundary = positive + tooltipDimension;
const viewBoxBoundary = viewBox[key] + viewBoxDimension;
if (tooltipBoundary > viewBoxBoundary) {
return Math.max(negative, viewBox[key]);
}
return Math.max(positive, viewBox[key]);
}

export function getTransformStyle({
translateX,
translateY,
useTranslate3d,
}: {
useTranslate3d: boolean;
translateX: number;
translateY: number;
}): CSSProperties {
return translateStyle({
transform: useTranslate3d
? `translate3d(${translateX}px, ${translateY}px, 0)`
: `translate(${translateX}px, ${translateY}px)`,
});
}

export function getTooltipTranslate({
allowEscapeViewBox,
coordinate,
offsetTopLeft,
position,
reverseDirection,
tooltipBox,
useTranslate3d,
viewBox,
}: {
allowEscapeViewBox: AllowInDimension;
coordinate: Partial<Coordinate> | undefined;
offsetTopLeft: number;
position: Partial<Coordinate>;
reverseDirection: AllowInDimension;
tooltipBox: { width: number; height: number };
useTranslate3d: boolean;
viewBox: CartesianViewBox;
}): { cssProperties: CSSProperties; cssClasses: string } {
let cssProperties: CSSProperties, translateX: number | undefined, translateY: number | undefined;
if (tooltipBox.height > 0 && tooltipBox.width > 0 && coordinate) {
translateX = getTooltipTranslateXY({
allowEscapeViewBox,
coordinate,
key: 'x',
offsetTopLeft,
position,
reverseDirection,
tooltipDimension: tooltipBox.width,
viewBox,
viewBoxDimension: viewBox.width,
});

translateY = getTooltipTranslateXY({
allowEscapeViewBox,
coordinate,
key: 'y',
offsetTopLeft,
position,
reverseDirection,
tooltipDimension: tooltipBox.height,
viewBox,
viewBoxDimension: viewBox.height,
});
cssProperties = getTransformStyle({
translateX,
translateY,
useTranslate3d,
});
} else {
cssProperties = TOOLTIP_HIDDEN;
}
return {
cssProperties,
cssClasses: getTooltipCSSClassName({
translateX,
translateY,
coordinate,
}),
};
}
6 changes: 6 additions & 0 deletions src/util/types.ts
Expand Up @@ -62,6 +62,12 @@ export type LegendType =
| 'wye'
| 'none';
export type TooltipType = 'none';

export type AllowInDimension = {
x?: boolean;
y?: boolean;
};

export interface Coordinate {
x: number;
y: number;
Expand Down

0 comments on commit 70142fb

Please sign in to comment.