@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,696 @@
|
|
|
1
|
+
import * as React from 'octane';
|
|
2
|
+
import { MouseEvent, MutableRefObject, ReactElement, ReactNode, SVGProps, useRef } from 'octane';
|
|
3
|
+
import last from 'es-toolkit/compat/last';
|
|
4
|
+
|
|
5
|
+
import { clsx } from 'clsx';
|
|
6
|
+
import { interpolate, isNullish, noop } from '../util/DataUtils';
|
|
7
|
+
import { polarToCartesian } from '../util/PolarUtils';
|
|
8
|
+
import { getTooltipNameProp, getValueByDataKey } from '../util/ChartUtils';
|
|
9
|
+
import { Polygon } from '../shape/Polygon';
|
|
10
|
+
import { Layer } from '../container/Layer';
|
|
11
|
+
import {
|
|
12
|
+
CartesianLabelListContextProvider,
|
|
13
|
+
CartesianLabelListEntry,
|
|
14
|
+
ImplicitLabelListType,
|
|
15
|
+
LabelListFromLabelProp,
|
|
16
|
+
} from '../component/LabelList';
|
|
17
|
+
import { Dots } from '../component/Dots';
|
|
18
|
+
import {
|
|
19
|
+
ActiveDotType,
|
|
20
|
+
AnimationDuration,
|
|
21
|
+
DataConsumer,
|
|
22
|
+
DataKey,
|
|
23
|
+
DotType,
|
|
24
|
+
EasingInput,
|
|
25
|
+
LegendType,
|
|
26
|
+
PolarLayout,
|
|
27
|
+
TooltipType,
|
|
28
|
+
TrapezoidViewBox,
|
|
29
|
+
} from '../util/types';
|
|
30
|
+
import type { LegendPayload } from '../component/DefaultLegendContent';
|
|
31
|
+
import { ActivePoints } from '../component/ActivePoints';
|
|
32
|
+
import { TooltipPayloadConfiguration } from '../state/tooltipSlice';
|
|
33
|
+
import { SetTooltipEntrySettings } from '../state/SetTooltipEntrySettings';
|
|
34
|
+
import { selectRadarPoints } from '../state/selectors/radarSelectors';
|
|
35
|
+
import { useAppSelector } from '../state/hooks';
|
|
36
|
+
import { useIsPanorama } from '../context/PanoramaContext';
|
|
37
|
+
import { SetPolarLegendPayload } from '../state/SetLegendPayload';
|
|
38
|
+
import { RegisterGraphicalItemId } from '../context/RegisterGraphicalItemId';
|
|
39
|
+
import { SetPolarGraphicalItem } from '../state/SetGraphicalItem';
|
|
40
|
+
import { svgPropertiesNoEvents } from '../util/svgPropertiesNoEvents';
|
|
41
|
+
import {
|
|
42
|
+
AnimatedItems,
|
|
43
|
+
AnimationInterpolateFn,
|
|
44
|
+
useAnimationCallbacks,
|
|
45
|
+
} from '../animation/AnimatedItems';
|
|
46
|
+
import { AnimationMatchByProp, matchAnimationItems, matchByIndex } from '../animation/matchBy';
|
|
47
|
+
import { useAnimationStartSnapshot } from '../animation/useAnimationStartSnapshot';
|
|
48
|
+
import { svgPropertiesAndEvents } from '../util/svgPropertiesAndEvents';
|
|
49
|
+
import { RequiresDefaultProps, resolveDefaultProps } from '../util/resolveDefaultProps';
|
|
50
|
+
import { WithIdRequired } from '../util/useUniqueId';
|
|
51
|
+
import { ZIndexable, ZIndexLayer } from '../zIndex/ZIndexLayer';
|
|
52
|
+
import { DefaultZIndexes } from '../zIndex/DefaultZIndexes';
|
|
53
|
+
import { RechartsScale } from '../util/scale/RechartsScale';
|
|
54
|
+
import { usePolarChartLayout } from '../context/chartLayoutContext';
|
|
55
|
+
|
|
56
|
+
export interface RadarPoint {
|
|
57
|
+
x: number;
|
|
58
|
+
y: number;
|
|
59
|
+
cx?: number;
|
|
60
|
+
cy?: number;
|
|
61
|
+
angle: number;
|
|
62
|
+
radius?: number;
|
|
63
|
+
value?: number;
|
|
64
|
+
payload?: any;
|
|
65
|
+
name?: string | number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface RadarProps<DataPointType = any, DataValueType = any> extends ZIndexable, DataConsumer<
|
|
69
|
+
DataPointType,
|
|
70
|
+
DataValueType
|
|
71
|
+
> {
|
|
72
|
+
/**
|
|
73
|
+
* @defaultValue true
|
|
74
|
+
*/
|
|
75
|
+
activeDot?: ActiveDotType;
|
|
76
|
+
/**
|
|
77
|
+
* @defaultValue 0
|
|
78
|
+
*/
|
|
79
|
+
angleAxisId?: string | number;
|
|
80
|
+
/**
|
|
81
|
+
* Specifies when the animation should begin, the unit of this option is ms.
|
|
82
|
+
* @defaultValue 0
|
|
83
|
+
*/
|
|
84
|
+
animationBegin?: number;
|
|
85
|
+
/**
|
|
86
|
+
* Specifies the duration of animation, the unit of this option is ms.
|
|
87
|
+
* @defaultValue 1500
|
|
88
|
+
*/
|
|
89
|
+
animationDuration?: AnimationDuration;
|
|
90
|
+
/**
|
|
91
|
+
* The type of easing function.
|
|
92
|
+
* @defaultValue ease
|
|
93
|
+
*/
|
|
94
|
+
animationEasing?: EasingInput;
|
|
95
|
+
/**
|
|
96
|
+
* Custom animation function for interpolating data items.
|
|
97
|
+
* When provided, this replaces the default animation interpolation.
|
|
98
|
+
*
|
|
99
|
+
* @param prevItems The items from the previous animation frame, or null on first render
|
|
100
|
+
* @param nextItems The target items to animate towards
|
|
101
|
+
* @param animationElapsedTime A normalized time value (0 = start, 1 = end)
|
|
102
|
+
* @returns The interpolated items at time animationElapsedTime
|
|
103
|
+
*
|
|
104
|
+
* @since 3.9
|
|
105
|
+
* @see {@link https://recharts.github.io/en-US/guide/animations/ Animations guide}
|
|
106
|
+
*/
|
|
107
|
+
animationInterpolateFn?: AnimationInterpolateFn<RadarPoint, PolarLayout>;
|
|
108
|
+
/**
|
|
109
|
+
* Strategy for matching previous items to next items during animation.
|
|
110
|
+
* Determines how Recharts pairs old data points with new data points
|
|
111
|
+
* to create smooth transitions.
|
|
112
|
+
*
|
|
113
|
+
* - `matchByIndex` (default): match by array position with proportional stretching
|
|
114
|
+
* - `matchAppend`: match sequentially by index and treat newly appended items as new
|
|
115
|
+
* - `matchByDataKey('someKey')`: match by a data key from the payload
|
|
116
|
+
* - Custom function `(item, index) => key`: match by the returned key
|
|
117
|
+
*
|
|
118
|
+
* @defaultValue index
|
|
119
|
+
* @see matchByIndex
|
|
120
|
+
* @see matchByDataKey
|
|
121
|
+
* @see matchAppend
|
|
122
|
+
*/
|
|
123
|
+
animationMatchBy?: AnimationMatchByProp<RadarPoint>;
|
|
124
|
+
baseLinePoints?: RadarPoint[];
|
|
125
|
+
className?: string;
|
|
126
|
+
connectNulls?: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Renders a circle element at each data point. Options:
|
|
129
|
+
*
|
|
130
|
+
* - `false`: no dots are drawn;
|
|
131
|
+
* - `true`: renders the dots with default settings;
|
|
132
|
+
* - `object`: the props of the dot. This will be merged with the internal calculated props of each dot;
|
|
133
|
+
* - `ReactElement`: the custom dot element;
|
|
134
|
+
* - `function`: a render function of the custom dot.
|
|
135
|
+
*
|
|
136
|
+
* @defaultValue false
|
|
137
|
+
*/
|
|
138
|
+
dot?: DotType;
|
|
139
|
+
/**
|
|
140
|
+
* @defaultValue false
|
|
141
|
+
*/
|
|
142
|
+
hide?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* If set false, animation of polygon will be disabled.
|
|
145
|
+
* If set "auto", the animation will be disabled in SSR and will respect the user's prefers-reduced-motion system preference for accessibility.
|
|
146
|
+
* @defaultValue auto
|
|
147
|
+
*/
|
|
148
|
+
isAnimationActive?: boolean | 'auto';
|
|
149
|
+
isRange?: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Renders one label for each point. Options:
|
|
152
|
+
* - `true`: renders default labels;
|
|
153
|
+
* - `false`: no labels are rendered;
|
|
154
|
+
* - `object`: the props of LabelList component;
|
|
155
|
+
* - `ReactElement`: a custom SVG label element, such as `<text>` or `<g>`.
|
|
156
|
+
* HTML elements such as `<div>` are not valid inside the chart SVG and may trigger React DOM warnings.
|
|
157
|
+
* - `function`: a render function of custom label.
|
|
158
|
+
*
|
|
159
|
+
* @defaultValue false
|
|
160
|
+
*/
|
|
161
|
+
label?: ImplicitLabelListType;
|
|
162
|
+
/**
|
|
163
|
+
* The type of icon in legend. If set to 'none', no legend item will be rendered.
|
|
164
|
+
* @defaultValue rect
|
|
165
|
+
*/
|
|
166
|
+
legendType?: LegendType;
|
|
167
|
+
/**
|
|
168
|
+
* The customized event handler of animation end
|
|
169
|
+
*/
|
|
170
|
+
onAnimationEnd?: () => void;
|
|
171
|
+
/**
|
|
172
|
+
* The customized event handler of animation start
|
|
173
|
+
*/
|
|
174
|
+
onAnimationStart?: () => void;
|
|
175
|
+
onMouseEnter?: (props: InternalRadarProps, e: MouseEvent<SVGPolygonElement>) => void;
|
|
176
|
+
onMouseLeave?: (props: InternalRadarProps, e: MouseEvent<SVGPolygonElement>) => void;
|
|
177
|
+
/**
|
|
178
|
+
* @defaultValue 0
|
|
179
|
+
*/
|
|
180
|
+
radiusAxisId?: string | number;
|
|
181
|
+
/**
|
|
182
|
+
* If set a ReactElement, the shape of radar can be customized.
|
|
183
|
+
* If set a function, the function will be called to render customized shape.
|
|
184
|
+
*/
|
|
185
|
+
shape?: ReactElement<SVGElement> | ((props: any) => ReactElement<SVGElement>);
|
|
186
|
+
tooltipType?: TooltipType;
|
|
187
|
+
/**
|
|
188
|
+
* @defaultValue 100
|
|
189
|
+
*/
|
|
190
|
+
zIndex?: number;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export type RadiusAxisForRadar = { scale: RechartsScale };
|
|
194
|
+
export type AngleAxisForRadar = {
|
|
195
|
+
scale: RechartsScale;
|
|
196
|
+
type: 'number' | 'category';
|
|
197
|
+
dataKey: DataKey<any> | undefined;
|
|
198
|
+
cx: number;
|
|
199
|
+
cy: number;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export type Props<DataPointType = any, DataValueType = any> =
|
|
203
|
+
Omit<
|
|
204
|
+
SVGProps<SVGGraphicsElement>,
|
|
205
|
+
'onMouseEnter' | 'onMouseLeave' | 'points' | 'ref'
|
|
206
|
+
> & RadarProps<DataPointType, DataValueType>;
|
|
207
|
+
|
|
208
|
+
export type RadarComposedData = {
|
|
209
|
+
points: RadarPoint[];
|
|
210
|
+
baseLinePoints: RadarPoint[];
|
|
211
|
+
isRange: boolean;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
function getLegendItemColor(
|
|
215
|
+
stroke: string | undefined,
|
|
216
|
+
fill: string | undefined,
|
|
217
|
+
): string | undefined {
|
|
218
|
+
return stroke && stroke !== 'none' ? stroke : fill;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const computeLegendPayloadFromRadarSectors = (
|
|
222
|
+
props: PropsWithDefaults,
|
|
223
|
+
): ReadonlyArray<LegendPayload> => {
|
|
224
|
+
const { dataKey, name, stroke, fill, legendType, hide } = props;
|
|
225
|
+
return [
|
|
226
|
+
{
|
|
227
|
+
inactive: hide,
|
|
228
|
+
dataKey,
|
|
229
|
+
type: legendType,
|
|
230
|
+
color: getLegendItemColor(stroke, fill),
|
|
231
|
+
value: getTooltipNameProp(name, dataKey),
|
|
232
|
+
payload: props,
|
|
233
|
+
},
|
|
234
|
+
];
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const SetRadarTooltipEntrySettings = React.memo(({
|
|
238
|
+
dataKey,
|
|
239
|
+
stroke,
|
|
240
|
+
strokeWidth,
|
|
241
|
+
fill,
|
|
242
|
+
name,
|
|
243
|
+
hide,
|
|
244
|
+
tooltipType,
|
|
245
|
+
id,
|
|
246
|
+
}: Pick<
|
|
247
|
+
WithIdRequired<PropsWithDefaults>,
|
|
248
|
+
'dataKey' | 'stroke' | 'strokeWidth' | 'fill' | 'name' | 'hide' | 'tooltipType' | 'id'
|
|
249
|
+
>) => {
|
|
250
|
+
const tooltipEntrySettings: TooltipPayloadConfiguration = {
|
|
251
|
+
/*
|
|
252
|
+
* I suppose this here _could_ return props.points
|
|
253
|
+
* because while Radar does not support item tooltip mode, it _could_ support it.
|
|
254
|
+
* But when I actually do return the points here, a defaultIndex test starts failing.
|
|
255
|
+
* So, undefined it is.
|
|
256
|
+
*/
|
|
257
|
+
dataDefinedOnItem: undefined,
|
|
258
|
+
getPosition: noop,
|
|
259
|
+
settings: {
|
|
260
|
+
stroke,
|
|
261
|
+
strokeWidth,
|
|
262
|
+
fill,
|
|
263
|
+
nameKey: undefined, // RadarChart does not have nameKey unfortunately
|
|
264
|
+
dataKey,
|
|
265
|
+
name: getTooltipNameProp(name, dataKey),
|
|
266
|
+
hide,
|
|
267
|
+
type: tooltipType,
|
|
268
|
+
color: getLegendItemColor(stroke, fill),
|
|
269
|
+
unit: '', // why doesn't Radar support unit?
|
|
270
|
+
graphicalItemId: id,
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
return <SetTooltipEntrySettings tooltipEntrySettings={tooltipEntrySettings} />;
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
function RadarDotsWrapper({
|
|
277
|
+
points,
|
|
278
|
+
props,
|
|
279
|
+
}: {
|
|
280
|
+
points: ReadonlyArray<RadarPoint>;
|
|
281
|
+
props: PropsWithDefaults;
|
|
282
|
+
}) {
|
|
283
|
+
const { dot, dataKey } = props;
|
|
284
|
+
const { id, ...propsWithoutId } = props;
|
|
285
|
+
|
|
286
|
+
const baseProps = svgPropertiesNoEvents(propsWithoutId);
|
|
287
|
+
|
|
288
|
+
return <Dots
|
|
289
|
+
points={points}
|
|
290
|
+
dot={dot}
|
|
291
|
+
className="recharts-radar-dots"
|
|
292
|
+
dotClassName="recharts-radar-dot"
|
|
293
|
+
dataKey={dataKey}
|
|
294
|
+
baseProps={baseProps}
|
|
295
|
+
/>;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function computeRadarPoints({
|
|
299
|
+
radiusAxis,
|
|
300
|
+
angleAxis,
|
|
301
|
+
displayedData,
|
|
302
|
+
dataKey,
|
|
303
|
+
bandSize,
|
|
304
|
+
}: {
|
|
305
|
+
radiusAxis: RadiusAxisForRadar;
|
|
306
|
+
angleAxis: AngleAxisForRadar;
|
|
307
|
+
displayedData: any[];
|
|
308
|
+
dataKey: RadarProps['dataKey'];
|
|
309
|
+
bandSize: number;
|
|
310
|
+
}): RadarComposedData {
|
|
311
|
+
const { cx, cy } = angleAxis;
|
|
312
|
+
let isRange = false;
|
|
313
|
+
const points: RadarPoint[] = [];
|
|
314
|
+
const angleBandSize =
|
|
315
|
+
angleAxis.type !== 'number' ? bandSize ?? 0 : 0;
|
|
316
|
+
|
|
317
|
+
displayedData.forEach((entry, i) => {
|
|
318
|
+
const name = getValueByDataKey(entry, angleAxis.dataKey, i);
|
|
319
|
+
const value = getValueByDataKey(entry, dataKey);
|
|
320
|
+
const angle: number = (angleAxis.scale.map(name) ?? 0) + angleBandSize;
|
|
321
|
+
const pointValue =
|
|
322
|
+
Array.isArray(value) ? last(value) : value;
|
|
323
|
+
const radius: number = isNullish(pointValue) ? 0 : radiusAxis.scale.map(pointValue) ?? 0;
|
|
324
|
+
|
|
325
|
+
if (Array.isArray(value) && value.length >= 2) {
|
|
326
|
+
isRange = true;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
points.push({
|
|
330
|
+
...polarToCartesian(cx, cy, radius, angle),
|
|
331
|
+
// getValueByDataKey does not validate the output type
|
|
332
|
+
name,
|
|
333
|
+
// getValueByDataKey does not validate the output type
|
|
334
|
+
value,
|
|
335
|
+
cx,
|
|
336
|
+
cy,
|
|
337
|
+
radius,
|
|
338
|
+
angle,
|
|
339
|
+
payload: entry,
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
const baseLinePoints: RadarPoint[] = [];
|
|
343
|
+
|
|
344
|
+
if (isRange) {
|
|
345
|
+
points.forEach((point: RadarPoint) => {
|
|
346
|
+
if (Array.isArray(point.value)) {
|
|
347
|
+
const baseValue = point.value[0];
|
|
348
|
+
const radius: number = isNullish(baseValue) ? 0 : radiusAxis.scale.map(baseValue) ?? 0;
|
|
349
|
+
|
|
350
|
+
baseLinePoints.push({
|
|
351
|
+
...point,
|
|
352
|
+
radius,
|
|
353
|
+
...polarToCartesian(cx, cy, radius, point.angle),
|
|
354
|
+
});
|
|
355
|
+
} else {
|
|
356
|
+
baseLinePoints.push(point);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return { points, isRange, baseLinePoints };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function RadarLabelListProvider({
|
|
365
|
+
showLabels,
|
|
366
|
+
points,
|
|
367
|
+
children,
|
|
368
|
+
}: {
|
|
369
|
+
showLabels: boolean;
|
|
370
|
+
points: ReadonlyArray<RadarPoint>;
|
|
371
|
+
children: ReactNode;
|
|
372
|
+
}) {
|
|
373
|
+
/*
|
|
374
|
+
* Radar provides a Cartesian label list context. Do we want to also provide a polar label list context?
|
|
375
|
+
* That way, users can choose to use polar positions for the Radar labels.
|
|
376
|
+
*/
|
|
377
|
+
// const labelListEntries: ReadonlyArray<PolarLabelListEntry> = points.map(
|
|
378
|
+
// (point): PolarLabelListEntry => ({
|
|
379
|
+
// value: point.value,
|
|
380
|
+
// payload: point.payload,
|
|
381
|
+
// parentViewBox: undefined,
|
|
382
|
+
// clockWise: false,
|
|
383
|
+
// viewBox: {
|
|
384
|
+
// cx: point.cx,
|
|
385
|
+
// cy: point.cy,
|
|
386
|
+
// innerRadius: point.radius,
|
|
387
|
+
// outerRadius: point.radius,
|
|
388
|
+
// startAngle: point.angle,
|
|
389
|
+
// endAngle: point.angle,
|
|
390
|
+
// clockWise: false,
|
|
391
|
+
// },
|
|
392
|
+
// }),
|
|
393
|
+
// );
|
|
394
|
+
|
|
395
|
+
const labelListEntries: ReadonlyArray<CartesianLabelListEntry> = points.map((
|
|
396
|
+
point,
|
|
397
|
+
): CartesianLabelListEntry => {
|
|
398
|
+
const viewBox: TrapezoidViewBox = {
|
|
399
|
+
x: point.x,
|
|
400
|
+
y: point.y,
|
|
401
|
+
width: 0,
|
|
402
|
+
lowerWidth: 0,
|
|
403
|
+
upperWidth: 0,
|
|
404
|
+
height: 0,
|
|
405
|
+
};
|
|
406
|
+
return {
|
|
407
|
+
...viewBox,
|
|
408
|
+
value: point.value ?? '',
|
|
409
|
+
payload: point.payload,
|
|
410
|
+
parentViewBox: undefined,
|
|
411
|
+
viewBox,
|
|
412
|
+
fill: undefined,
|
|
413
|
+
};
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
return <CartesianLabelListContextProvider
|
|
417
|
+
value={showLabels ? labelListEntries : undefined}
|
|
418
|
+
>{children}</CartesianLabelListContextProvider>;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function StaticPolygon({
|
|
422
|
+
points,
|
|
423
|
+
baseLinePoints,
|
|
424
|
+
props,
|
|
425
|
+
}: {
|
|
426
|
+
points: ReadonlyArray<RadarPoint>;
|
|
427
|
+
baseLinePoints: ReadonlyArray<RadarPoint>;
|
|
428
|
+
props: InternalRadarProps;
|
|
429
|
+
}) {
|
|
430
|
+
if (points == null) {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const { shape, isRange, connectNulls } = props;
|
|
435
|
+
|
|
436
|
+
const handleMouseEnter = (e: MouseEvent<SVGPolygonElement>) => {
|
|
437
|
+
const { onMouseEnter } = props;
|
|
438
|
+
|
|
439
|
+
if (onMouseEnter) {
|
|
440
|
+
onMouseEnter(props, e);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const handleMouseLeave = (e: MouseEvent<SVGPolygonElement>) => {
|
|
445
|
+
const { onMouseLeave } = props;
|
|
446
|
+
|
|
447
|
+
if (onMouseLeave) {
|
|
448
|
+
onMouseLeave(props, e);
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
let radar;
|
|
453
|
+
if (React.isValidElement(shape)) {
|
|
454
|
+
radar = React.cloneElement(shape, { ...props, points } as any);
|
|
455
|
+
} else if (typeof shape === 'function') {
|
|
456
|
+
radar = shape({ ...props, points });
|
|
457
|
+
} else {
|
|
458
|
+
radar = <Polygon
|
|
459
|
+
{...svgPropertiesAndEvents(props)}
|
|
460
|
+
onMouseEnter={handleMouseEnter}
|
|
461
|
+
onMouseLeave={handleMouseLeave}
|
|
462
|
+
points={points}
|
|
463
|
+
baseLinePoints={isRange ? baseLinePoints : undefined}
|
|
464
|
+
connectNulls={connectNulls}
|
|
465
|
+
/>;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return <Layer className="recharts-radar-polygon">
|
|
469
|
+
{radar}
|
|
470
|
+
<RadarDotsWrapper props={props} points={points} />
|
|
471
|
+
</Layer>;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const defaultRadarAnimateItems: AnimationInterpolateFn<RadarPoint, PolarLayout> = (
|
|
475
|
+
items,
|
|
476
|
+
animationElapsedTime,
|
|
477
|
+
) => {
|
|
478
|
+
if (items == null) return [];
|
|
479
|
+
if (animationElapsedTime === 1) {
|
|
480
|
+
return items.flatMap((item) => (item.status === 'removed' ? [] : [item.next]));
|
|
481
|
+
}
|
|
482
|
+
return items.flatMap((item) => {
|
|
483
|
+
if (item.status === 'removed') return [];
|
|
484
|
+
if (item.status === 'matched') {
|
|
485
|
+
return [
|
|
486
|
+
{
|
|
487
|
+
...item.next,
|
|
488
|
+
x: interpolate(item.prev.x, item.next.x, animationElapsedTime),
|
|
489
|
+
y: interpolate(item.prev.y, item.next.y, animationElapsedTime),
|
|
490
|
+
},
|
|
491
|
+
];
|
|
492
|
+
}
|
|
493
|
+
// added: animate from center
|
|
494
|
+
return [
|
|
495
|
+
{
|
|
496
|
+
...item.next,
|
|
497
|
+
x: interpolate(item.next.cx, item.next.x, animationElapsedTime),
|
|
498
|
+
y: interpolate(item.next.cy, item.next.y, animationElapsedTime),
|
|
499
|
+
},
|
|
500
|
+
];
|
|
501
|
+
});
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
function PolygonWithAnimation({
|
|
505
|
+
props,
|
|
506
|
+
previousPointsRef,
|
|
507
|
+
previousBaseLinePointsRef,
|
|
508
|
+
}: {
|
|
509
|
+
props: InternalRadarProps;
|
|
510
|
+
previousPointsRef: MutableRefObject<ReadonlyArray<RadarPoint> | undefined>;
|
|
511
|
+
previousBaseLinePointsRef: MutableRefObject<ReadonlyArray<RadarPoint> | undefined>;
|
|
512
|
+
}) {
|
|
513
|
+
const {
|
|
514
|
+
points,
|
|
515
|
+
baseLinePoints,
|
|
516
|
+
isAnimationActive,
|
|
517
|
+
animationBegin,
|
|
518
|
+
animationDuration,
|
|
519
|
+
animationEasing,
|
|
520
|
+
animationMatchBy,
|
|
521
|
+
animationInterpolateFn,
|
|
522
|
+
onAnimationStart,
|
|
523
|
+
onAnimationEnd,
|
|
524
|
+
} = props;
|
|
525
|
+
const animationInput = JSON.stringify([
|
|
526
|
+
points.map(({ x, y }) => [x, y]),
|
|
527
|
+
baseLinePoints.map(({ x, y }) => [x, y]),
|
|
528
|
+
]);
|
|
529
|
+
const baseLineAnimationState = useAnimationStartSnapshot(
|
|
530
|
+
animationInput,
|
|
531
|
+
previousBaseLinePointsRef,
|
|
532
|
+
);
|
|
533
|
+
const prevBaseLinePoints = baseLineAnimationState.startValue;
|
|
534
|
+
const baseLineAnimationItems = matchAnimationItems(
|
|
535
|
+
prevBaseLinePoints ?? null,
|
|
536
|
+
baseLinePoints,
|
|
537
|
+
animationMatchBy,
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
const { isAnimating, handleAnimationStart, handleAnimationEnd } = useAnimationCallbacks(
|
|
541
|
+
onAnimationStart,
|
|
542
|
+
onAnimationEnd,
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
const layout = usePolarChartLayout();
|
|
546
|
+
|
|
547
|
+
if (layout == null) return null;
|
|
548
|
+
|
|
549
|
+
return <RadarLabelListProvider showLabels={!isAnimating} points={points}>
|
|
550
|
+
<AnimatedItems
|
|
551
|
+
animationInput={animationInput}
|
|
552
|
+
animationIdPrefix="recharts-radar-"
|
|
553
|
+
items={points}
|
|
554
|
+
previousItemsRef={previousPointsRef}
|
|
555
|
+
isAnimationActive={isAnimationActive}
|
|
556
|
+
animationBegin={animationBegin}
|
|
557
|
+
animationDuration={animationDuration}
|
|
558
|
+
animationEasing={animationEasing}
|
|
559
|
+
onAnimationStart={handleAnimationStart}
|
|
560
|
+
onAnimationEnd={handleAnimationEnd}
|
|
561
|
+
animationInterpolateFn={animationInterpolateFn}
|
|
562
|
+
animationMatchBy={animationMatchBy}
|
|
563
|
+
layout={layout}
|
|
564
|
+
>
|
|
565
|
+
{(stepData, animationElapsedTime) => {
|
|
566
|
+
const stepBaseLinePoints =
|
|
567
|
+
animationElapsedTime === 1
|
|
568
|
+
? baseLinePoints
|
|
569
|
+
: animationInterpolateFn(baseLineAnimationItems, animationElapsedTime, layout);
|
|
570
|
+
baseLineAnimationState.syncStepValue(stepBaseLinePoints, animationElapsedTime);
|
|
571
|
+
return <StaticPolygon
|
|
572
|
+
points={stepData}
|
|
573
|
+
baseLinePoints={stepBaseLinePoints}
|
|
574
|
+
props={props}
|
|
575
|
+
/>;
|
|
576
|
+
}}
|
|
577
|
+
</AnimatedItems>
|
|
578
|
+
<LabelListFromLabelProp label={props.label} />
|
|
579
|
+
{props.children}
|
|
580
|
+
</RadarLabelListProvider>;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function RenderPolygon(props: InternalRadarProps) {
|
|
584
|
+
const previousPointsRef = useRef<ReadonlyArray<RadarPoint> | undefined>(undefined);
|
|
585
|
+
const previousBaseLinePointsRef = useRef<ReadonlyArray<RadarPoint> | undefined>(undefined);
|
|
586
|
+
|
|
587
|
+
return <PolygonWithAnimation
|
|
588
|
+
props={props}
|
|
589
|
+
previousPointsRef={previousPointsRef}
|
|
590
|
+
previousBaseLinePointsRef={previousBaseLinePointsRef}
|
|
591
|
+
/>;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
export const defaultRadarProps = {
|
|
595
|
+
activeDot: true,
|
|
596
|
+
angleAxisId: 0,
|
|
597
|
+
animationBegin: 0,
|
|
598
|
+
animationDuration: 1500,
|
|
599
|
+
animationEasing: 'ease',
|
|
600
|
+
animationMatchBy: matchByIndex,
|
|
601
|
+
animationInterpolateFn: defaultRadarAnimateItems,
|
|
602
|
+
dot: false,
|
|
603
|
+
hide: false,
|
|
604
|
+
isAnimationActive: 'auto',
|
|
605
|
+
label: false,
|
|
606
|
+
legendType: 'rect',
|
|
607
|
+
radiusAxisId: 0,
|
|
608
|
+
zIndex: DefaultZIndexes.area,
|
|
609
|
+
} as const satisfies Partial<Props>;
|
|
610
|
+
|
|
611
|
+
type PropsWithDefaults = RequiresDefaultProps<Props, typeof defaultRadarProps>;
|
|
612
|
+
|
|
613
|
+
export type InternalRadarProps = WithIdRequired<PropsWithDefaults> & RadarComposedData;
|
|
614
|
+
|
|
615
|
+
function RadarWithState(props: InternalRadarProps) {
|
|
616
|
+
const { hide, className, points } = props;
|
|
617
|
+
|
|
618
|
+
if (hide) {
|
|
619
|
+
return null;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const layerClass = clsx('recharts-radar', className);
|
|
623
|
+
|
|
624
|
+
return <ZIndexLayer zIndex={props.zIndex}>
|
|
625
|
+
<Layer className={layerClass}>
|
|
626
|
+
<RenderPolygon {...props} />
|
|
627
|
+
</Layer>
|
|
628
|
+
<ActivePoints
|
|
629
|
+
points={points}
|
|
630
|
+
mainColor={getLegendItemColor(props.stroke, props.fill)}
|
|
631
|
+
itemDataKey={props.dataKey}
|
|
632
|
+
activeDot={props.activeDot}
|
|
633
|
+
/>
|
|
634
|
+
</ZIndexLayer>;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function RadarImpl(props: WithIdRequired<PropsWithDefaults>) {
|
|
638
|
+
const isPanorama = useIsPanorama();
|
|
639
|
+
const radarPoints = useAppSelector(
|
|
640
|
+
(state) => selectRadarPoints(
|
|
641
|
+
state,
|
|
642
|
+
props.radiusAxisId,
|
|
643
|
+
props.angleAxisId,
|
|
644
|
+
isPanorama,
|
|
645
|
+
props.id,
|
|
646
|
+
),
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
if (radarPoints?.points == null) {
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return <RadarWithState
|
|
654
|
+
{...props}
|
|
655
|
+
points={radarPoints?.points}
|
|
656
|
+
baseLinePoints={radarPoints?.baseLinePoints}
|
|
657
|
+
isRange={radarPoints?.isRange}
|
|
658
|
+
/>;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* @consumes PolarChartContext
|
|
663
|
+
* @provides LabelListContext
|
|
664
|
+
*/
|
|
665
|
+
export function Radar<DataPointType = any, DataValueType = any>(
|
|
666
|
+
outsideProps: Props<DataPointType, DataValueType>,
|
|
667
|
+
) {
|
|
668
|
+
const props: PropsWithDefaults = resolveDefaultProps(outsideProps, defaultRadarProps);
|
|
669
|
+
return <RegisterGraphicalItemId id={props.id} type="radar">
|
|
670
|
+
{(id) => <>
|
|
671
|
+
<SetPolarGraphicalItem
|
|
672
|
+
type="radar"
|
|
673
|
+
id={id}
|
|
674
|
+
data={undefined}
|
|
675
|
+
dataKey={props.dataKey}
|
|
676
|
+
hide={props.hide}
|
|
677
|
+
angleAxisId={props.angleAxisId}
|
|
678
|
+
radiusAxisId={props.radiusAxisId}
|
|
679
|
+
/>
|
|
680
|
+
<SetPolarLegendPayload legendPayload={computeLegendPayloadFromRadarSectors(props)} />
|
|
681
|
+
<SetRadarTooltipEntrySettings
|
|
682
|
+
dataKey={props.dataKey}
|
|
683
|
+
stroke={props.stroke}
|
|
684
|
+
strokeWidth={props.strokeWidth}
|
|
685
|
+
fill={props.fill}
|
|
686
|
+
name={props.name}
|
|
687
|
+
hide={props.hide}
|
|
688
|
+
tooltipType={props.tooltipType}
|
|
689
|
+
id={id}
|
|
690
|
+
/>
|
|
691
|
+
<RadarImpl {...props} id={id} />
|
|
692
|
+
</>}
|
|
693
|
+
</RegisterGraphicalItemId>;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
Radar.displayName = 'Radar';
|