@octanejs/recharts 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/animation/CSSTransitionAnimate.tsrx +142 -0
- package/src/cartesian/Area.tsrx +1096 -0
- package/src/cartesian/Area.tsrx.d.ts +340 -0
- package/src/cartesian/AreaRevealShape.tsrx +202 -0
- package/src/cartesian/CartesianGrid.tsrx +643 -0
- package/src/cartesian/CartesianGrid.tsrx.d.ts +219 -0
- package/src/cartesian/ErrorBar.tsrx +322 -0
- package/src/cartesian/ErrorBar.tsrx.d.ts +131 -0
- package/src/cartesian/Funnel.tsrx +833 -0
- package/src/cartesian/Funnel.tsrx.d.ts +274 -0
- package/src/cartesian/ReferenceLine.tsrx +452 -0
- package/src/cartesian/ReferenceLine.tsrx.d.ts +178 -0
- package/src/cartesian/Scatter.tsrx +1076 -0
- package/src/cartesian/Scatter.tsrx.d.ts +351 -0
- package/src/cartesian/ZAxis.tsrx +148 -0
- package/src/cartesian/ZAxis.tsrx.d.ts +102 -0
- package/src/chart/AreaChart.tsrx +22 -0
- package/src/chart/AreaChart.tsrx.d.ts +12 -0
- package/src/chart/ComposedChart.tsrx +22 -0
- package/src/chart/ComposedChart.tsrx.d.ts +12 -0
- package/src/chart/FunnelChart.tsrx +22 -0
- package/src/chart/FunnelChart.tsrx.d.ts +12 -0
- package/src/chart/PieChart.tsrx +31 -0
- package/src/chart/PieChart.tsrx.d.ts +31 -0
- package/src/chart/PolarChart.tsrx +121 -0
- package/src/chart/RadarChart.tsrx +53 -0
- package/src/chart/RadarChart.tsrx.d.ts +31 -0
- package/src/chart/RadialBarChart.tsrx +31 -0
- package/src/chart/RadialBarChart.tsrx.d.ts +31 -0
- package/src/chart/Sankey.tsrx +1542 -0
- package/src/chart/Sankey.tsrx.d.ts +253 -0
- package/src/chart/ScatterChart.tsrx +22 -0
- package/src/chart/ScatterChart.tsrx.d.ts +12 -0
- package/src/chart/SunburstChart.tsrx +495 -0
- package/src/chart/SunburstChart.tsrx.d.ts +146 -0
- package/src/chart/types.ts +19 -0
- package/src/component/Cursor.tsrx +157 -0
- package/src/component/DefaultLegendContent.tsrx +288 -0
- package/src/component/DefaultLegendContent.tsrx.d.ts +125 -0
- package/src/component/DefaultTooltipContent.tsrx +215 -0
- package/src/component/DefaultTooltipContent.tsrx.d.ts +87 -0
- package/src/component/Legend.tsrx +302 -0
- package/src/component/Legend.tsrx.d.ts +88 -0
- package/src/component/ResponsiveContainer.ts +1 -22
- package/src/component/ResponsiveContainer.tsrx +171 -0
- package/src/component/ResponsiveContainer.tsrx.d.ts +81 -0
- package/src/component/Tooltip.tsrx +425 -0
- package/src/component/Tooltip.tsrx.d.ts +246 -0
- package/src/component/TooltipBoundingBox.tsrx +135 -0
- package/src/context/CellsContext.ts +12 -0
- package/src/context/legendPayloadContext.tsrx +11 -0
- package/src/context/useTooltipAxis.ts +20 -0
- package/src/index.d.ts +236 -0
- package/src/index.ts +67 -0
- package/src/polar/Pie.tsrx +1224 -0
- package/src/polar/Pie.tsrx.d.ts +436 -0
- package/src/polar/PolarAngleAxis.tsrx +554 -0
- package/src/polar/PolarAngleAxis.tsrx.d.ts +215 -0
- package/src/polar/PolarGrid.tsrx +317 -0
- package/src/polar/PolarGrid.tsrx.d.ts +117 -0
- package/src/polar/PolarRadiusAxis.tsrx +463 -0
- package/src/polar/PolarRadiusAxis.tsrx.d.ts +209 -0
- package/src/polar/Radar.tsrx +696 -0
- package/src/polar/Radar.tsrx.d.ts +215 -0
- package/src/polar/RadialBar.tsrx +887 -0
- package/src/polar/RadialBar.tsrx.d.ts +340 -0
- package/src/state/ReportPolarOptions.tsrx +11 -0
- package/src/state/selectors/areaSelectors.ts +257 -0
- package/src/state/selectors/funnelSelectors.ts +86 -0
- package/src/state/selectors/pieSelectors.ts +134 -0
- package/src/state/selectors/polarGridSelectors.ts +43 -0
- package/src/state/selectors/polarScaleSelectors.ts +159 -0
- package/src/state/selectors/polarSelectors.ts +198 -0
- package/src/state/selectors/radarSelectors.ts +228 -0
- package/src/state/selectors/radialBarSelectors.ts +511 -0
- package/src/state/selectors/scatterSelectors.ts +168 -0
- package/src/util/FunnelUtils.tsrx +19 -0
- package/src/util/RadialBarUtils.tsrx +29 -0
- package/src/util/ScatterUtils.tsrx +34 -0
- package/src/util/cursor/getCursorPoints.ts +45 -0
- package/src/util/cursor/getCursorRectangle.ts +28 -0
- package/src/util/cursor/getRadialCursorPoints.ts +31 -0
- package/src/util/payload/getUniqPayload.ts +28 -0
- package/src/util/scale/CartesianScaleHelper.ts +71 -0
- package/src/util/tooltip/translate.ts +168 -0
- package/src/util/types.ts +3 -0
- package/src/util/useElementOffset.ts +147 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from 'octane';
|
|
2
|
+
import { RadialBarDataItem, RadialBarProps } from '../polar/RadialBar';
|
|
3
|
+
import { Sector } from '../shape/Sector';
|
|
4
|
+
import { ShapeAnimationProps } from './types';
|
|
5
|
+
import { Shape } from './ActiveShapeUtils';
|
|
6
|
+
|
|
7
|
+
export const defaultRadialBarShape = Sector;
|
|
8
|
+
|
|
9
|
+
export function parseCornerRadius(cornerRadius: string | number | undefined): number | undefined {
|
|
10
|
+
if (typeof cornerRadius === 'string') {
|
|
11
|
+
return parseInt(cornerRadius, 10);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return cornerRadius;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RadialBarSectorProps extends RadialBarDataItem, ShapeAnimationProps {
|
|
18
|
+
index: number;
|
|
19
|
+
isActive: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type RadialBarSectorComponentProps =
|
|
23
|
+
RadialBarSectorProps & {
|
|
24
|
+
option: Exclude<RadialBarProps['shape'], undefined> | RadialBarProps['activeShape'];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function RadialBarSector({ option, ...shapeProps }: RadialBarSectorComponentProps) {
|
|
28
|
+
return <Shape option={option} DefaultShape={defaultRadialBarShape} shapeProps={shapeProps} />;
|
|
29
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from 'octane';
|
|
2
|
+
import { ActiveShape, ShapeAnimationProps, SymbolType } from './types';
|
|
3
|
+
import { ScatterPointItem } from '../cartesian/Scatter';
|
|
4
|
+
import { InnerSymbolsProp, Symbols } from '../shape/Symbols';
|
|
5
|
+
import { Shape } from './ActiveShapeUtils';
|
|
6
|
+
import { DATA_ITEM_GRAPHICAL_ITEM_ID_ATTRIBUTE_NAME } from './Constants';
|
|
7
|
+
import { GraphicalItemId } from '../state/graphicalItemsSlice';
|
|
8
|
+
|
|
9
|
+
export type ScatterShapeProps =
|
|
10
|
+
ScatterPointItem & ShapeAnimationProps & {
|
|
11
|
+
isActive: boolean;
|
|
12
|
+
index: number;
|
|
13
|
+
[DATA_ITEM_GRAPHICAL_ITEM_ID_ATTRIBUTE_NAME]: GraphicalItemId;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type ScatterSymbolShapeProps = ScatterShapeProps & InnerSymbolsProp;
|
|
17
|
+
|
|
18
|
+
function renderSymbols(props: ScatterSymbolShapeProps) {
|
|
19
|
+
return <Symbols {...props} />;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ScatterSymbol({ option, ...props }: {
|
|
23
|
+
option: ActiveShape<ScatterShapeProps & InnerSymbolsProp, SVGPathElement> | SymbolType;
|
|
24
|
+
} & ScatterShapeProps) {
|
|
25
|
+
if (typeof option === 'string') {
|
|
26
|
+
return <Shape
|
|
27
|
+
option={<Symbols type={option} {...props} />}
|
|
28
|
+
DefaultShape={renderSymbols}
|
|
29
|
+
shapeProps={props}
|
|
30
|
+
/>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return <Shape option={option} DefaultShape={renderSymbols} shapeProps={props} />;
|
|
34
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { polarToCartesian } from '../PolarUtils';
|
|
2
|
+
import {
|
|
3
|
+
Coordinate,
|
|
4
|
+
ChartOffsetInternal,
|
|
5
|
+
PolarCoordinate,
|
|
6
|
+
isPolarCoordinate,
|
|
7
|
+
CartesianLayout,
|
|
8
|
+
PolarLayout,
|
|
9
|
+
} from '../types';
|
|
10
|
+
import { RadialCursorPoints, getRadialCursorPoints } from './getRadialCursorPoints';
|
|
11
|
+
|
|
12
|
+
export function getCursorPoints(
|
|
13
|
+
layout: CartesianLayout | PolarLayout,
|
|
14
|
+
activeCoordinate: Coordinate | PolarCoordinate,
|
|
15
|
+
offset: ChartOffsetInternal,
|
|
16
|
+
): [Coordinate, Coordinate] | RadialCursorPoints | undefined {
|
|
17
|
+
if (layout === 'horizontal') {
|
|
18
|
+
return [
|
|
19
|
+
{ x: activeCoordinate.x, y: offset.top },
|
|
20
|
+
{ x: activeCoordinate.x, y: offset.top + offset.height },
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (layout === 'vertical') {
|
|
25
|
+
return [
|
|
26
|
+
{ x: offset.left, y: activeCoordinate.y },
|
|
27
|
+
{ x: offset.left + offset.width, y: activeCoordinate.y },
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isPolarCoordinate(activeCoordinate)) {
|
|
32
|
+
if (layout === 'centric') {
|
|
33
|
+
const { cx, cy, innerRadius, outerRadius, angle } = activeCoordinate;
|
|
34
|
+
const innerPoint = polarToCartesian(cx, cy, innerRadius, angle);
|
|
35
|
+
const outerPoint = polarToCartesian(cx, cy, outerRadius, angle);
|
|
36
|
+
return [
|
|
37
|
+
{ x: innerPoint.x, y: innerPoint.y },
|
|
38
|
+
{ x: outerPoint.x, y: outerPoint.y },
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
return getRadialCursorPoints(activeCoordinate);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ChartCoordinate, ChartOffsetInternal, LayoutType } from '../types';
|
|
2
|
+
|
|
3
|
+
export type CursorRectangle = {
|
|
4
|
+
stroke: string;
|
|
5
|
+
fill: string;
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function getCursorRectangle(
|
|
13
|
+
layout: LayoutType,
|
|
14
|
+
activeCoordinate: ChartCoordinate,
|
|
15
|
+
offset: ChartOffsetInternal,
|
|
16
|
+
tooltipAxisBandSize: number,
|
|
17
|
+
): CursorRectangle {
|
|
18
|
+
const halfSize = tooltipAxisBandSize / 2;
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
stroke: 'none',
|
|
22
|
+
fill: '#ccc',
|
|
23
|
+
x: layout === 'horizontal' ? activeCoordinate.x - halfSize : offset.left + 0.5,
|
|
24
|
+
y: layout === 'horizontal' ? offset.top + 0.5 : activeCoordinate.y - halfSize,
|
|
25
|
+
width: layout === 'horizontal' ? tooltipAxisBandSize : offset.width - 1,
|
|
26
|
+
height: layout === 'horizontal' ? offset.height - 1 : tooltipAxisBandSize,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { polarToCartesian } from '../PolarUtils';
|
|
2
|
+
import { Coordinate, PolarCoordinate } from '../types';
|
|
3
|
+
|
|
4
|
+
export type RadialCursorPoints = {
|
|
5
|
+
points: [startPoint: Coordinate, endPoint: Coordinate];
|
|
6
|
+
cx?: number;
|
|
7
|
+
cy?: number;
|
|
8
|
+
radius?: number;
|
|
9
|
+
startAngle?: number;
|
|
10
|
+
endAngle?: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Only applicable for radial layouts
|
|
15
|
+
* @param {Object} activeCoordinate ChartCoordinate
|
|
16
|
+
* @returns {Object} RadialCursorPoints
|
|
17
|
+
*/
|
|
18
|
+
export function getRadialCursorPoints(activeCoordinate: PolarCoordinate): RadialCursorPoints {
|
|
19
|
+
const { cx, cy, radius, startAngle, endAngle } = activeCoordinate;
|
|
20
|
+
const startPoint = polarToCartesian(cx, cy, radius, startAngle);
|
|
21
|
+
const endPoint = polarToCartesian(cx, cy, radius, endAngle);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
points: [startPoint, endPoint],
|
|
25
|
+
cx,
|
|
26
|
+
cy,
|
|
27
|
+
radius,
|
|
28
|
+
startAngle,
|
|
29
|
+
endAngle,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import uniqBy from 'es-toolkit/compat/uniqBy';
|
|
2
|
+
|
|
3
|
+
type UniqueFunc<T> = (entry: T) => unknown;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This is configuration option that decides how to filter for unique values only:
|
|
7
|
+
*
|
|
8
|
+
* - `false` means "no filter"
|
|
9
|
+
* - `true` means "use recharts default filter"
|
|
10
|
+
* - function means "use return of this function as the default key"
|
|
11
|
+
*/
|
|
12
|
+
export type UniqueOption<T> = boolean | UniqueFunc<T>;
|
|
13
|
+
|
|
14
|
+
export function getUniqPayload<T>(
|
|
15
|
+
payload: ReadonlyArray<T>,
|
|
16
|
+
option: UniqueOption<T> | undefined,
|
|
17
|
+
defaultUniqBy: UniqueFunc<T>,
|
|
18
|
+
): ReadonlyArray<T> {
|
|
19
|
+
if (option === true) {
|
|
20
|
+
return uniqBy(payload, defaultUniqBy);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (typeof option === 'function') {
|
|
24
|
+
return uniqBy(payload, option);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return payload;
|
|
28
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { BandPosition, RechartsScale } from './RechartsScale';
|
|
2
|
+
import { CategoricalDomainItem, Coordinate, NullableCoordinate } from '../types';
|
|
3
|
+
|
|
4
|
+
export interface CartesianScaleHelper<
|
|
5
|
+
XDomain extends CategoricalDomainItem = CategoricalDomainItem,
|
|
6
|
+
YDomain extends CategoricalDomainItem = CategoricalDomainItem,
|
|
7
|
+
> {
|
|
8
|
+
map(value: { x: XDomain; y: YDomain }, { position }: { position: BandPosition }): Coordinate;
|
|
9
|
+
|
|
10
|
+
mapWithFallback(
|
|
11
|
+
value: { x?: XDomain | null | undefined; y?: YDomain | null | undefined },
|
|
12
|
+
{ position, fallback }: { position?: BandPosition; fallback: 'rangeMin' | 'rangeMax' },
|
|
13
|
+
): Coordinate;
|
|
14
|
+
|
|
15
|
+
isInRange({ x, y }: Partial<NullableCoordinate>): boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Groups X and Y scale functions together and provides helper methods.
|
|
20
|
+
*/
|
|
21
|
+
export class CartesianScaleHelperImpl<
|
|
22
|
+
XDomain extends CategoricalDomainItem = CategoricalDomainItem,
|
|
23
|
+
YDomain extends CategoricalDomainItem = CategoricalDomainItem,
|
|
24
|
+
> implements CartesianScaleHelper<XDomain, YDomain> {
|
|
25
|
+
private xAxisScale: RechartsScale<XDomain>;
|
|
26
|
+
|
|
27
|
+
private yAxisScale: RechartsScale<YDomain>;
|
|
28
|
+
|
|
29
|
+
constructor({ x, y }: { x: RechartsScale<XDomain>; y: RechartsScale<YDomain> }) {
|
|
30
|
+
this.xAxisScale = x;
|
|
31
|
+
this.yAxisScale = y;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
map(value: { x: XDomain; y: YDomain }, { position }: { position: BandPosition }): Coordinate {
|
|
35
|
+
return {
|
|
36
|
+
x: this.xAxisScale.map(value.x, { position }) ?? 0,
|
|
37
|
+
y: this.yAxisScale.map(value.y, { position }) ?? 0,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
mapWithFallback(
|
|
42
|
+
value: { x?: XDomain | null | undefined; y?: YDomain | null | undefined },
|
|
43
|
+
{ position, fallback }: { position?: BandPosition; fallback: 'rangeMin' | 'rangeMax' },
|
|
44
|
+
): Coordinate {
|
|
45
|
+
let fallbackY: number, fallbackX: number;
|
|
46
|
+
if (fallback === 'rangeMin') {
|
|
47
|
+
fallbackY = this.yAxisScale.rangeMin();
|
|
48
|
+
} else if (fallback === 'rangeMax') {
|
|
49
|
+
fallbackY = this.yAxisScale.rangeMax();
|
|
50
|
+
} else {
|
|
51
|
+
fallbackY = 0;
|
|
52
|
+
}
|
|
53
|
+
if (fallback === 'rangeMin') {
|
|
54
|
+
fallbackX = this.xAxisScale.rangeMin();
|
|
55
|
+
} else if (fallback === 'rangeMax') {
|
|
56
|
+
fallbackX = this.xAxisScale.rangeMax();
|
|
57
|
+
} else {
|
|
58
|
+
fallbackX = 0;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
x: this.xAxisScale.map(value.x, { position }) ?? fallbackX,
|
|
62
|
+
y: this.yAxisScale.map(value.y, { position }) ?? fallbackY,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
isInRange({ x, y }: Partial<NullableCoordinate>): boolean {
|
|
67
|
+
const xInRange = x == null || this.xAxisScale.isInRange(x);
|
|
68
|
+
const yInRange = y == null || this.yAxisScale.isInRange(y);
|
|
69
|
+
return xInRange && yInRange;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import { CSSProperties } from 'octane';
|
|
3
|
+
import { isNumber } from '../DataUtils';
|
|
4
|
+
import { Coordinate, CartesianViewBox, AllowInDimension } from '../types';
|
|
5
|
+
|
|
6
|
+
export type Dimension2D = 'x' | 'y';
|
|
7
|
+
|
|
8
|
+
const CSS_CLASS_PREFIX = 'recharts-tooltip-wrapper';
|
|
9
|
+
|
|
10
|
+
const TOOLTIP_HIDDEN: CSSProperties = { visibility: 'hidden' };
|
|
11
|
+
|
|
12
|
+
export function getTooltipCSSClassName({
|
|
13
|
+
coordinate,
|
|
14
|
+
translateX,
|
|
15
|
+
translateY,
|
|
16
|
+
}: {
|
|
17
|
+
translateX: number | undefined;
|
|
18
|
+
translateY: number | undefined;
|
|
19
|
+
coordinate: Partial<Coordinate> | undefined;
|
|
20
|
+
}): string {
|
|
21
|
+
return clsx(CSS_CLASS_PREFIX, {
|
|
22
|
+
[`${CSS_CLASS_PREFIX}-right`]:
|
|
23
|
+
isNumber(translateX) && coordinate && isNumber(coordinate.x) && translateX >= coordinate.x,
|
|
24
|
+
[`${CSS_CLASS_PREFIX}-left`]:
|
|
25
|
+
isNumber(translateX) && coordinate && isNumber(coordinate.x) && translateX < coordinate.x,
|
|
26
|
+
[`${CSS_CLASS_PREFIX}-bottom`]:
|
|
27
|
+
isNumber(translateY) && coordinate && isNumber(coordinate.y) && translateY >= coordinate.y,
|
|
28
|
+
[`${CSS_CLASS_PREFIX}-top`]:
|
|
29
|
+
isNumber(translateY) && coordinate && isNumber(coordinate.y) && translateY < coordinate.y,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getTooltipTranslateXY({
|
|
34
|
+
allowEscapeViewBox,
|
|
35
|
+
coordinate,
|
|
36
|
+
key,
|
|
37
|
+
offset,
|
|
38
|
+
position,
|
|
39
|
+
reverseDirection,
|
|
40
|
+
tooltipDimension,
|
|
41
|
+
viewBox,
|
|
42
|
+
viewBoxDimension,
|
|
43
|
+
}: {
|
|
44
|
+
allowEscapeViewBox: AllowInDimension;
|
|
45
|
+
coordinate: Coordinate;
|
|
46
|
+
key: Dimension2D;
|
|
47
|
+
offset: number;
|
|
48
|
+
position: Partial<Coordinate> | undefined;
|
|
49
|
+
reverseDirection: AllowInDimension;
|
|
50
|
+
tooltipDimension: number;
|
|
51
|
+
viewBox: CartesianViewBox;
|
|
52
|
+
viewBoxDimension: number | undefined;
|
|
53
|
+
}): number {
|
|
54
|
+
if (position && isNumber(position[key])) {
|
|
55
|
+
return position[key];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const negative = coordinate[key] - tooltipDimension - (offset > 0 ? offset : 0);
|
|
59
|
+
const positive = coordinate[key] + offset;
|
|
60
|
+
|
|
61
|
+
if (allowEscapeViewBox[key]) {
|
|
62
|
+
return reverseDirection[key] ? negative : positive;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const viewBoxKey = viewBox[key];
|
|
66
|
+
if (viewBoxKey == null) {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (reverseDirection[key]) {
|
|
71
|
+
const tooltipBoundary = negative;
|
|
72
|
+
const viewBoxBoundary = viewBoxKey;
|
|
73
|
+
if (tooltipBoundary < viewBoxBoundary) {
|
|
74
|
+
return Math.max(positive, viewBoxKey);
|
|
75
|
+
}
|
|
76
|
+
return Math.max(negative, viewBoxKey);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (viewBoxDimension == null) {
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
const tooltipBoundary = positive + tooltipDimension;
|
|
83
|
+
const viewBoxBoundary = viewBoxKey + viewBoxDimension;
|
|
84
|
+
if (tooltipBoundary > viewBoxBoundary) {
|
|
85
|
+
return Math.max(negative, viewBoxKey);
|
|
86
|
+
}
|
|
87
|
+
return Math.max(positive, viewBoxKey);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getTransformStyle({
|
|
91
|
+
translateX,
|
|
92
|
+
translateY,
|
|
93
|
+
useTranslate3d,
|
|
94
|
+
}: {
|
|
95
|
+
useTranslate3d: boolean;
|
|
96
|
+
translateX: number;
|
|
97
|
+
translateY: number;
|
|
98
|
+
}): CSSProperties {
|
|
99
|
+
return {
|
|
100
|
+
transform: useTranslate3d
|
|
101
|
+
? `translate3d(${translateX}px, ${translateY}px, 0)`
|
|
102
|
+
: `translate(${translateX}px, ${translateY}px)`,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getTooltipTranslate({
|
|
107
|
+
allowEscapeViewBox,
|
|
108
|
+
coordinate,
|
|
109
|
+
offsetTop,
|
|
110
|
+
offsetLeft,
|
|
111
|
+
position,
|
|
112
|
+
reverseDirection,
|
|
113
|
+
tooltipBox,
|
|
114
|
+
useTranslate3d,
|
|
115
|
+
viewBox,
|
|
116
|
+
}: {
|
|
117
|
+
allowEscapeViewBox: AllowInDimension;
|
|
118
|
+
coordinate: Coordinate | undefined;
|
|
119
|
+
offsetTop: number;
|
|
120
|
+
offsetLeft: number;
|
|
121
|
+
position: Partial<Coordinate> | undefined;
|
|
122
|
+
reverseDirection: AllowInDimension;
|
|
123
|
+
tooltipBox: { width: number; height: number };
|
|
124
|
+
useTranslate3d: boolean;
|
|
125
|
+
viewBox: CartesianViewBox;
|
|
126
|
+
}): { cssProperties: CSSProperties; cssClasses: string } {
|
|
127
|
+
let cssProperties: CSSProperties, translateX: number | undefined, translateY: number | undefined;
|
|
128
|
+
if (tooltipBox.height > 0 && tooltipBox.width > 0 && coordinate) {
|
|
129
|
+
translateX = getTooltipTranslateXY({
|
|
130
|
+
allowEscapeViewBox,
|
|
131
|
+
coordinate,
|
|
132
|
+
key: 'x',
|
|
133
|
+
offset: offsetLeft,
|
|
134
|
+
position,
|
|
135
|
+
reverseDirection,
|
|
136
|
+
tooltipDimension: tooltipBox.width,
|
|
137
|
+
viewBox,
|
|
138
|
+
viewBoxDimension: viewBox.width,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
translateY = getTooltipTranslateXY({
|
|
142
|
+
allowEscapeViewBox,
|
|
143
|
+
coordinate,
|
|
144
|
+
key: 'y',
|
|
145
|
+
offset: offsetTop,
|
|
146
|
+
position,
|
|
147
|
+
reverseDirection,
|
|
148
|
+
tooltipDimension: tooltipBox.height,
|
|
149
|
+
viewBox,
|
|
150
|
+
viewBoxDimension: viewBox.height,
|
|
151
|
+
});
|
|
152
|
+
cssProperties = getTransformStyle({
|
|
153
|
+
translateX,
|
|
154
|
+
translateY,
|
|
155
|
+
useTranslate3d,
|
|
156
|
+
});
|
|
157
|
+
} else {
|
|
158
|
+
cssProperties = TOOLTIP_HIDDEN;
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
cssProperties,
|
|
162
|
+
cssClasses: getTooltipCSSClassName({
|
|
163
|
+
translateX,
|
|
164
|
+
translateY,
|
|
165
|
+
coordinate,
|
|
166
|
+
}),
|
|
167
|
+
};
|
|
168
|
+
}
|
package/src/util/types.ts
CHANGED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'octane';
|
|
2
|
+
|
|
3
|
+
const EPS = 1;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Stores the dimensions and position of a DOM element as returned by `getBoundingClientRect()`.
|
|
7
|
+
*
|
|
8
|
+
* Values are viewport-relative and may be fractional (subpixel precision).
|
|
9
|
+
*
|
|
10
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect}
|
|
11
|
+
*/
|
|
12
|
+
export type ElementOffset = {
|
|
13
|
+
/**
|
|
14
|
+
* Height of an element as returned by `getBoundingClientRect()`.
|
|
15
|
+
* This is the CSS height including padding and border, and may be a fractional value.
|
|
16
|
+
*
|
|
17
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMRect/height}
|
|
18
|
+
*/
|
|
19
|
+
height: number;
|
|
20
|
+
/**
|
|
21
|
+
* Distance from the left edge of the viewport to the left edge of the element,
|
|
22
|
+
* as returned by `getBoundingClientRect()`.
|
|
23
|
+
*
|
|
24
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMRect/left}
|
|
25
|
+
*/
|
|
26
|
+
left: number;
|
|
27
|
+
/**
|
|
28
|
+
* Distance from the top edge of the viewport to the top edge of the element,
|
|
29
|
+
* as returned by `getBoundingClientRect()`.
|
|
30
|
+
*
|
|
31
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMRect/top}
|
|
32
|
+
*/
|
|
33
|
+
top: number;
|
|
34
|
+
/**
|
|
35
|
+
* Width of an element as returned by `getBoundingClientRect()`.
|
|
36
|
+
* This is the CSS width including padding and border, and may be a fractional value.
|
|
37
|
+
*
|
|
38
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/DOMRect/width}
|
|
39
|
+
*/
|
|
40
|
+
width: number;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Callback ref setter returned by {@link useElementOffset}.
|
|
45
|
+
*
|
|
46
|
+
* Pass this to a DOM element's `ref` prop to start observing its layout.
|
|
47
|
+
*
|
|
48
|
+
* @param node - the DOM element to observe, or `null` when the element unmounts
|
|
49
|
+
*/
|
|
50
|
+
export type SetElementOffset = (node: HTMLElement | null) => void;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks whether two ElementOffset values differ by more than `EPS` (1px) in any dimension.
|
|
54
|
+
*
|
|
55
|
+
* @param a - the first ElementOffset to compare
|
|
56
|
+
* @param b - the second ElementOffset to compare
|
|
57
|
+
* @returns true if any dimension differs by more than 1px
|
|
58
|
+
*/
|
|
59
|
+
function hasSignificantChange(a: ElementOffset, b: ElementOffset): boolean {
|
|
60
|
+
return (
|
|
61
|
+
Math.abs(a.height - b.height) > EPS ||
|
|
62
|
+
Math.abs(a.left - b.left) > EPS ||
|
|
63
|
+
Math.abs(a.top - b.top) > EPS ||
|
|
64
|
+
Math.abs(a.width - b.width) > EPS
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Reads the current bounding box of a DOM element using `getBoundingClientRect()`.
|
|
70
|
+
*
|
|
71
|
+
* @param node - the DOM element to measure
|
|
72
|
+
* @returns an ElementOffset with the element's current dimensions and viewport-relative position
|
|
73
|
+
*/
|
|
74
|
+
function readElementOffset(node: HTMLElement): ElementOffset {
|
|
75
|
+
const rect = node.getBoundingClientRect();
|
|
76
|
+
return {
|
|
77
|
+
height: rect.height,
|
|
78
|
+
left: rect.left,
|
|
79
|
+
top: rect.top,
|
|
80
|
+
width: rect.width,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Use this to listen to element layout changes.
|
|
86
|
+
*
|
|
87
|
+
* Very useful for reading actual sizes of DOM elements relative to the viewport.
|
|
88
|
+
*
|
|
89
|
+
* Uses ResizeObserver to automatically detect size changes of the observed element.
|
|
90
|
+
*
|
|
91
|
+
* @param extraDependencies use this to trigger new DOM dimensions read when any of these change. Good for things like payload and label, that will re-render something down in the children array, but you want to read the layout box of a parent.
|
|
92
|
+
* @returns [lastElementOffset, updateElementOffset] most recent value, and setter. Pass the setter to a DOM element ref like this: `<div ref={updateElementOffset}>`
|
|
93
|
+
*/
|
|
94
|
+
export function useElementOffset(
|
|
95
|
+
extraDependencies: ReadonlyArray<unknown> = [],
|
|
96
|
+
): [ElementOffset, SetElementOffset] {
|
|
97
|
+
const [lastBoundingBox, setLastBoundingBox] = useState<ElementOffset>({
|
|
98
|
+
height: 0,
|
|
99
|
+
left: 0,
|
|
100
|
+
top: 0,
|
|
101
|
+
width: 0,
|
|
102
|
+
});
|
|
103
|
+
const observerRef = useRef<ResizeObserver | null>(null);
|
|
104
|
+
const lastBoundingBoxRef = useRef(lastBoundingBox);
|
|
105
|
+
lastBoundingBoxRef.current = lastBoundingBox;
|
|
106
|
+
|
|
107
|
+
const updateBoundingBox = useCallback(
|
|
108
|
+
(node: HTMLElement | null) => {
|
|
109
|
+
// Disconnect any previously active ResizeObserver
|
|
110
|
+
if (observerRef.current != null) {
|
|
111
|
+
observerRef.current.disconnect();
|
|
112
|
+
observerRef.current = null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (node != null) {
|
|
116
|
+
// Measure immediately on ref attach
|
|
117
|
+
const box = readElementOffset(node);
|
|
118
|
+
if (hasSignificantChange(box, lastBoundingBoxRef.current)) {
|
|
119
|
+
setLastBoundingBox(box);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Set up ResizeObserver for future size changes
|
|
123
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
124
|
+
const observer = new ResizeObserver(() => {
|
|
125
|
+
const newBox = readElementOffset(node);
|
|
126
|
+
if (hasSignificantChange(newBox, lastBoundingBoxRef.current)) {
|
|
127
|
+
setLastBoundingBox(newBox);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
observer.observe(node);
|
|
131
|
+
observerRef.current = observer;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
136
|
+
[...extraDependencies],
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Cleanup on unmount
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
return () => {
|
|
142
|
+
observerRef.current?.disconnect();
|
|
143
|
+
};
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
146
|
+
return [lastBoundingBox, updateBoundingBox];
|
|
147
|
+
}
|