@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,1096 @@
|
|
|
1
|
+
import * as React from 'octane';
|
|
2
|
+
import { MutableRefObject, ReactElement, ReactNode, useMemo, useRef } from 'octane';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
import { BaseLineType, CurveType, Props as CurveProps } from '../shape/Curve';
|
|
5
|
+
import { Layer } from '../container/Layer';
|
|
6
|
+
import {
|
|
7
|
+
CartesianLabelListContextProvider,
|
|
8
|
+
CartesianLabelListEntry,
|
|
9
|
+
ImplicitLabelListType,
|
|
10
|
+
LabelListFromLabelProp,
|
|
11
|
+
} from '../component/LabelList';
|
|
12
|
+
import { Dots, DotsDotProps } from '../component/Dots';
|
|
13
|
+
import { interpolate, isNan, isNullish, isNumber, noop } from '../util/DataUtils';
|
|
14
|
+
import {
|
|
15
|
+
getCateCoordinateOfLine,
|
|
16
|
+
getNormalizedStackId,
|
|
17
|
+
getTooltipNameProp,
|
|
18
|
+
getValueByDataKey,
|
|
19
|
+
StackId,
|
|
20
|
+
} from '../util/ChartUtils';
|
|
21
|
+
import {
|
|
22
|
+
ActiveDotType,
|
|
23
|
+
ActiveShape,
|
|
24
|
+
AnimationDuration,
|
|
25
|
+
CartesianLayout,
|
|
26
|
+
DataConsumer,
|
|
27
|
+
DataKey,
|
|
28
|
+
DataProvider,
|
|
29
|
+
DotType,
|
|
30
|
+
EasingInput,
|
|
31
|
+
LegendType,
|
|
32
|
+
NullableCoordinate,
|
|
33
|
+
TickItem,
|
|
34
|
+
TooltipType,
|
|
35
|
+
TrapezoidViewBox,
|
|
36
|
+
} from '../util/types';
|
|
37
|
+
import { isClipDot } from '../util/ReactUtils';
|
|
38
|
+
import type { LegendPayload } from '../component/DefaultLegendContent';
|
|
39
|
+
import { Formatter } from '../component/DefaultTooltipContent';
|
|
40
|
+
import { ActivePoints } from '../component/ActivePoints';
|
|
41
|
+
import { TooltipPayloadConfiguration } from '../state/tooltipSlice';
|
|
42
|
+
import { SetTooltipEntrySettings } from '../state/SetTooltipEntrySettings';
|
|
43
|
+
import { GraphicalItemClipPath, useNeedsClip } from './GraphicalItemClipPath';
|
|
44
|
+
import { BaseAxisWithScale } from '../state/selectors/axisSelectors';
|
|
45
|
+
import { ChartData } from '../state/chartDataSlice';
|
|
46
|
+
import { AreaPointItem, ComputedArea, selectArea } from '../state/selectors/areaSelectors';
|
|
47
|
+
import { useIsPanorama } from '../context/PanoramaContext';
|
|
48
|
+
import { useCartesianChartLayout, useChartLayout } from '../context/chartLayoutContext';
|
|
49
|
+
import { useChartName } from '../state/selectors/selectors';
|
|
50
|
+
import { SetLegendPayload } from '../state/SetLegendPayload';
|
|
51
|
+
import { useAppSelector } from '../state/hooks';
|
|
52
|
+
import {
|
|
53
|
+
AnimatedItems,
|
|
54
|
+
AnimationInterpolateFn,
|
|
55
|
+
useAnimationCallbacks,
|
|
56
|
+
} from '../animation/AnimatedItems';
|
|
57
|
+
import {
|
|
58
|
+
AnimationItem,
|
|
59
|
+
AnimationMatchByProp,
|
|
60
|
+
matchAnimationItems,
|
|
61
|
+
matchByIndex,
|
|
62
|
+
} from '../animation/matchBy';
|
|
63
|
+
import { useAnimationStartSnapshot } from '../animation/useAnimationStartSnapshot';
|
|
64
|
+
import { RequiresDefaultProps, resolveDefaultProps } from '../util/resolveDefaultProps';
|
|
65
|
+
import { usePlotArea } from '../hooks';
|
|
66
|
+
import { WithIdRequired, WithoutId } from '../util/useUniqueId';
|
|
67
|
+
import { RegisterGraphicalItemId } from '../context/RegisterGraphicalItemId';
|
|
68
|
+
import type { AreaSettings } from '../state/types/AreaSettings';
|
|
69
|
+
import { SetCartesianGraphicalItem } from '../state/SetGraphicalItem';
|
|
70
|
+
import { svgPropertiesNoEvents } from '../util/svgPropertiesNoEvents';
|
|
71
|
+
import { getRadiusAndStrokeWidthFromDot } from '../util/getRadiusAndStrokeWidthFromDot';
|
|
72
|
+
import { svgPropertiesAndEvents } from '../util/svgPropertiesAndEvents';
|
|
73
|
+
import { Shape } from '../util/ActiveShapeUtils';
|
|
74
|
+
import { ZIndexable, ZIndexLayer } from '../zIndex/ZIndexLayer';
|
|
75
|
+
import { DefaultZIndexes } from '../zIndex/DefaultZIndexes';
|
|
76
|
+
import { propsAreEqual } from '../util/propsAreEqual';
|
|
77
|
+
import { AxisId } from '../state/cartesianAxisSlice';
|
|
78
|
+
import type { StackDataPoint } from '../util/stacks/stackTypes';
|
|
79
|
+
import { AreaRevealShape, AreaRevealShapeProps } from './AreaRevealShape';
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @inline
|
|
83
|
+
*/
|
|
84
|
+
export type BaseValue = number | 'dataMin' | 'dataMax';
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Our base value array has payload in it, and we expose it externally too.
|
|
88
|
+
*/
|
|
89
|
+
type BaseValueCoordinate<DataPointType = any> =
|
|
90
|
+
NullableCoordinate & { payload: DataPointType | undefined };
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Internal props, combination of external props + defaultProps + private Recharts state
|
|
94
|
+
*/
|
|
95
|
+
interface InternalAreaProps extends ZIndexable {
|
|
96
|
+
activeDot: ActiveDotType;
|
|
97
|
+
animationBegin: number;
|
|
98
|
+
animationDuration: AnimationDuration;
|
|
99
|
+
animationEasing: EasingInput;
|
|
100
|
+
animationInterpolateFn: AnimationInterpolateFn<AreaPointItem, CartesianLayout>;
|
|
101
|
+
animationMatchBy: AnimationMatchByProp<AreaPointItem>;
|
|
102
|
+
baseLine: BaseLineType | undefined;
|
|
103
|
+
baseValue?: BaseValue;
|
|
104
|
+
className?: string;
|
|
105
|
+
connectNulls: boolean;
|
|
106
|
+
data?: ChartData;
|
|
107
|
+
dataKey: DataKey<any>;
|
|
108
|
+
dot: DotType;
|
|
109
|
+
height: number;
|
|
110
|
+
hide: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* ID is mandatory internally, but optional externally.
|
|
113
|
+
*/
|
|
114
|
+
id: string;
|
|
115
|
+
isAnimationActive: boolean | 'auto';
|
|
116
|
+
isRange?: boolean;
|
|
117
|
+
label?: ImplicitLabelListType;
|
|
118
|
+
layout: CartesianLayout;
|
|
119
|
+
left: number;
|
|
120
|
+
legendType: LegendType;
|
|
121
|
+
name?: string | number;
|
|
122
|
+
needClip: boolean;
|
|
123
|
+
onAnimationEnd?: () => void;
|
|
124
|
+
onAnimationStart?: () => void;
|
|
125
|
+
points: ReadonlyArray<AreaPointItem>;
|
|
126
|
+
shape: ActiveShape<CurveProps, SVGPathElement>;
|
|
127
|
+
stackId?: StackId;
|
|
128
|
+
formatter?: Formatter;
|
|
129
|
+
tooltipType?: TooltipType;
|
|
130
|
+
top: number;
|
|
131
|
+
type?: CurveType;
|
|
132
|
+
unit?: string | number;
|
|
133
|
+
width: number;
|
|
134
|
+
xAxisId: string | number;
|
|
135
|
+
yAxisId: string | number;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* External props, intended for end users to fill in
|
|
140
|
+
*/
|
|
141
|
+
interface AreaProps<DataPointType = any, DataValueType = any> extends DataProvider<
|
|
142
|
+
DataPointType
|
|
143
|
+
>, Required<DataConsumer<DataPointType, DataValueType>>, ZIndexable {
|
|
144
|
+
/**
|
|
145
|
+
* The active dot is rendered on the closest data point when user interacts with the chart. Options:
|
|
146
|
+
*
|
|
147
|
+
* - `false`: dots do not change on user activity; both active and inactive dots follow the `dot` prop (see below)
|
|
148
|
+
* - `true`: renders the active dot with default settings
|
|
149
|
+
* - `object`: the props of the active dot. This will be merged with the internal calculated props of the active dot
|
|
150
|
+
* - `ReactElement`: the custom active dot element
|
|
151
|
+
* - `function`: a render function of the custom active dot
|
|
152
|
+
*
|
|
153
|
+
* @defaultValue true
|
|
154
|
+
* @example <Area dataKey="value" activeDot={false} />
|
|
155
|
+
* @example <Area dataKey="value" activeDot={{ stroke: 'red', strokeWidth: 2, r: 10 }} />
|
|
156
|
+
* @example <Area dataKey="value" activeDot={CustomizedActiveDot} />
|
|
157
|
+
*
|
|
158
|
+
* @see {@link https://recharts.github.io/en-US/examples/SimpleLineChart/ A line chart with customized active dot}
|
|
159
|
+
*/
|
|
160
|
+
activeDot?: ActiveDotType;
|
|
161
|
+
/**
|
|
162
|
+
* Specifies when the animation should begin, the unit of this option is ms.
|
|
163
|
+
* @defaultValue 0
|
|
164
|
+
*/
|
|
165
|
+
animationBegin?: number;
|
|
166
|
+
/**
|
|
167
|
+
* Specifies the duration of animation, the unit of this option is ms.
|
|
168
|
+
* @defaultValue 1500
|
|
169
|
+
*/
|
|
170
|
+
animationDuration?: AnimationDuration;
|
|
171
|
+
/**
|
|
172
|
+
* The type of easing function.
|
|
173
|
+
* @defaultValue 'ease'
|
|
174
|
+
*/
|
|
175
|
+
animationEasing?: EasingInput;
|
|
176
|
+
/**
|
|
177
|
+
* Custom animation function for interpolating data items.
|
|
178
|
+
* When provided, this replaces the default animation interpolation.
|
|
179
|
+
*
|
|
180
|
+
* @param prevItems The items from the previous animation frame, or null on first render
|
|
181
|
+
* @param nextItems The target items to animate towards
|
|
182
|
+
* @param animationElapsedTime A normalized time value (0 = start, 1 = end)
|
|
183
|
+
* @returns The interpolated items at time animationElapsedTime
|
|
184
|
+
*
|
|
185
|
+
* @since 3.9
|
|
186
|
+
* @see {@link https://recharts.github.io/en-US/guide/animations/ Animations guide}
|
|
187
|
+
*/
|
|
188
|
+
animationInterpolateFn?: AnimationInterpolateFn<AreaPointItem, CartesianLayout>;
|
|
189
|
+
/**
|
|
190
|
+
* Strategy for matching previous items to next items during animation.
|
|
191
|
+
* Determines how Recharts pairs old data points with new data points
|
|
192
|
+
* to create smooth transitions.
|
|
193
|
+
*
|
|
194
|
+
* - `matchByIndex` (default): match by array position with proportional stretching
|
|
195
|
+
* - `matchAppend`: match sequentially by index and treat newly appended items as new
|
|
196
|
+
* - `matchByDataKey('someKey')`: match by a data key from the payload
|
|
197
|
+
* - Custom function `(item, index) => key`: match by the returned key
|
|
198
|
+
*
|
|
199
|
+
* @defaultValue index
|
|
200
|
+
* @see matchByIndex
|
|
201
|
+
* @see matchByDataKey
|
|
202
|
+
* @see matchAppend
|
|
203
|
+
*
|
|
204
|
+
* @since 3.9
|
|
205
|
+
* @see {@link https://recharts.github.io/en-US/guide/animations/ Animations guide}
|
|
206
|
+
*/
|
|
207
|
+
animationMatchBy?: AnimationMatchByProp<AreaPointItem>;
|
|
208
|
+
/**
|
|
209
|
+
* Configures the starting value used to build the internal baseline for non-ranged, non-stacked areas.
|
|
210
|
+
*
|
|
211
|
+
* WARNING despite the name `dataMin`|`dataMax` this actually reads the domain instead, so it should rather be
|
|
212
|
+
* `domainMin`|`domainMax`. This looks like a small detail,
|
|
213
|
+
* but it's actually important because domains are usually extended automatically.
|
|
214
|
+
* For example the default numerical domain starts from 0.
|
|
215
|
+
*
|
|
216
|
+
* - `number`: uses the corresponding axis value as a flat baseline
|
|
217
|
+
* - `dataMin`: uses the minimum of the value-axis domain
|
|
218
|
+
* - `dataMax`: uses the maximum of the value-axis domain
|
|
219
|
+
*
|
|
220
|
+
* Ignored when the area is stacked or when `dataKey` already returns `[min, max]` tuples.
|
|
221
|
+
*
|
|
222
|
+
* Note that the baseValue does not interact with `animationInterpolateFn`;
|
|
223
|
+
* baseValue is always animated by linear interpolation.
|
|
224
|
+
* If you want a custom animation then have your `dataKey` return a tuple of two values instead of a single number
|
|
225
|
+
* which will also render a ranged Area, and that does work with your custom `animationInterpolateFn`.
|
|
226
|
+
*/
|
|
227
|
+
baseValue?: BaseValue;
|
|
228
|
+
className?: string;
|
|
229
|
+
/**
|
|
230
|
+
* Whether to connect the area across null points.
|
|
231
|
+
*
|
|
232
|
+
* @defaultValue false
|
|
233
|
+
* @see {@link https://recharts.github.io/en-US/examples/AreaChartConnectNulls/ AreaChart with connectNull true and false}
|
|
234
|
+
*/
|
|
235
|
+
connectNulls?: boolean;
|
|
236
|
+
/**
|
|
237
|
+
* Renders a circle element at each data point. Options:
|
|
238
|
+
*
|
|
239
|
+
* - `false`: no dots are drawn;
|
|
240
|
+
* - `true`: renders the dots with default settings;
|
|
241
|
+
* - `object`: the props of the dot. This will be merged with the internal calculated props of each dot;
|
|
242
|
+
* - `ReactElement`: the custom dot element;
|
|
243
|
+
* - `function`: a render function of the custom dot.
|
|
244
|
+
*
|
|
245
|
+
* @defaultValue false
|
|
246
|
+
*/
|
|
247
|
+
dot?: DotType;
|
|
248
|
+
/**
|
|
249
|
+
* Hides the whole graphical element when true.
|
|
250
|
+
*
|
|
251
|
+
* Hiding an element is different from removing it from the chart:
|
|
252
|
+
* Hidden graphical elements are still visible in Legend,
|
|
253
|
+
* and can be included in axis domain calculations,
|
|
254
|
+
* depending on `includeHidden` props of your XAxis/YAxis.
|
|
255
|
+
*
|
|
256
|
+
* @defaultValue false
|
|
257
|
+
*/
|
|
258
|
+
hide?: boolean;
|
|
259
|
+
/**
|
|
260
|
+
* Unique identifier of this component.
|
|
261
|
+
* Used as an HTML attribute `id`, and also to identify this element internally.
|
|
262
|
+
*
|
|
263
|
+
* If undefined, Recharts will generate a unique ID automatically.
|
|
264
|
+
*/
|
|
265
|
+
id?: string;
|
|
266
|
+
/**
|
|
267
|
+
* If set false, animation of area will be disabled.
|
|
268
|
+
* If set "auto", will be disabled in SSR and will respect the user's prefers-reduced-motion system preference for accessibility.
|
|
269
|
+
* @defaultValue 'auto'
|
|
270
|
+
*/
|
|
271
|
+
isAnimationActive?: boolean | 'auto';
|
|
272
|
+
/**
|
|
273
|
+
* Renders one label for each data point. Options:
|
|
274
|
+
*
|
|
275
|
+
* - `true`: renders default labels
|
|
276
|
+
* - `false`: no labels are rendered
|
|
277
|
+
* - `object`: the props of LabelList component
|
|
278
|
+
* - `ReactElement`: a custom SVG label element, such as `<text>` or `<g>`.
|
|
279
|
+
* HTML elements such as `<div>` are not valid inside the chart SVG and may trigger React DOM warnings.
|
|
280
|
+
* - `function`: a render function of custom label
|
|
281
|
+
*
|
|
282
|
+
* @defaultValue false
|
|
283
|
+
*/
|
|
284
|
+
label?: ImplicitLabelListType;
|
|
285
|
+
/**
|
|
286
|
+
* The type of icon in legend.
|
|
287
|
+
* If set to 'none', no legend item will be rendered.
|
|
288
|
+
* @defaultValue 'line'
|
|
289
|
+
*/
|
|
290
|
+
legendType?: LegendType;
|
|
291
|
+
/**
|
|
292
|
+
* The name of data.
|
|
293
|
+
* This option will be used in tooltip and legend to represent this graphical item.
|
|
294
|
+
* If no value was set to this option, the value of dataKey will be used alternatively.
|
|
295
|
+
*/
|
|
296
|
+
name?: string | number;
|
|
297
|
+
/**
|
|
298
|
+
* The customized event handler of animation end
|
|
299
|
+
*/
|
|
300
|
+
onAnimationEnd?: () => void;
|
|
301
|
+
/**
|
|
302
|
+
* The customized event handler of animation start
|
|
303
|
+
*/
|
|
304
|
+
onAnimationStart?: () => void;
|
|
305
|
+
/**
|
|
306
|
+
* If set a ReactElement, the shape of area can be customized.
|
|
307
|
+
* If set a function, the function will be called to render customized shape.
|
|
308
|
+
* By default, renders a filled curve path with a clipPath entrance animation.
|
|
309
|
+
*
|
|
310
|
+
* During animations the shape receives additional props: `animationElapsedTime`, `isAnimating`, and `isEntrance`.
|
|
311
|
+
* When a custom shape is provided, the built-in clipPath entrance animation is skipped.
|
|
312
|
+
*
|
|
313
|
+
* @see AreaRevealShape — the default entrance animation, available for import from recharts
|
|
314
|
+
* @example <Area dataKey="value" shape={CustomizedShapeComponent} />
|
|
315
|
+
* @example <Area dataKey="value" shape={renderShapeFunction} />
|
|
316
|
+
*/
|
|
317
|
+
shape?: ActiveShape<AreaRevealShapeProps, SVGPathElement>;
|
|
318
|
+
/**
|
|
319
|
+
* When two Areas have the same axisId and same stackId, then the two Areas are stacked in the chart.
|
|
320
|
+
*/
|
|
321
|
+
stackId?: StackId;
|
|
322
|
+
/**
|
|
323
|
+
* The stroke color. If "none", no line will be drawn.
|
|
324
|
+
* @defaultValue '#3182bd'
|
|
325
|
+
*/
|
|
326
|
+
stroke?: string;
|
|
327
|
+
/**
|
|
328
|
+
* The width of the stroke
|
|
329
|
+
* @defaultValue 1
|
|
330
|
+
*/
|
|
331
|
+
strokeWidth?: string | number;
|
|
332
|
+
tooltipType?: TooltipType;
|
|
333
|
+
/**
|
|
334
|
+
* The interpolation type of curve. Allows custom interpolation function.
|
|
335
|
+
*
|
|
336
|
+
* @defaultValue linear
|
|
337
|
+
* @link https://d3js.org/d3-shape/curve
|
|
338
|
+
* @see {@link https://recharts.github.io/en-US/examples/CardinalAreaChart/ An AreaChart which has two area with different interpolation.}
|
|
339
|
+
*/
|
|
340
|
+
type?: CurveType;
|
|
341
|
+
/**
|
|
342
|
+
* The unit of data. This option will be used in tooltip.
|
|
343
|
+
*/
|
|
344
|
+
unit?: string | number;
|
|
345
|
+
/**
|
|
346
|
+
* Formats the value displayed in the tooltip for this Area.
|
|
347
|
+
* When set, takes precedence over the `formatter` prop on the Tooltip component.
|
|
348
|
+
*/
|
|
349
|
+
formatter?: Formatter;
|
|
350
|
+
/**
|
|
351
|
+
* The id of XAxis which is corresponding to the data. Required when there are multiple XAxes.
|
|
352
|
+
* @defaultValue 0
|
|
353
|
+
*/
|
|
354
|
+
xAxisId?: AxisId;
|
|
355
|
+
/**
|
|
356
|
+
* The id of YAxis which is corresponding to the data. Required when there are multiple YAxes.
|
|
357
|
+
* @defaultValue 0
|
|
358
|
+
*/
|
|
359
|
+
yAxisId?: AxisId;
|
|
360
|
+
/**
|
|
361
|
+
* Z-Index of this component and its children. The higher the value,
|
|
362
|
+
* the more on top it will be rendered.
|
|
363
|
+
* Components with higher zIndex will appear in front of components with lower zIndex.
|
|
364
|
+
* If undefined or 0, the content is rendered in the default layer without portals.
|
|
365
|
+
*
|
|
366
|
+
* @since 3.4
|
|
367
|
+
* @defaultValue 100
|
|
368
|
+
* @see {@link https://recharts.github.io/en-US/guide/zIndex/ Z-Index and layers guide}
|
|
369
|
+
*/
|
|
370
|
+
zIndex?: number;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const defaultAreaAnimateItems: AnimationInterpolateFn<AreaPointItem, CartesianLayout> = (
|
|
374
|
+
items,
|
|
375
|
+
animationElapsedTime,
|
|
376
|
+
) => {
|
|
377
|
+
if (items == null) {
|
|
378
|
+
// First render: return items as-is, clip-path animation handles the reveal
|
|
379
|
+
return [];
|
|
380
|
+
}
|
|
381
|
+
if (animationElapsedTime === 1) {
|
|
382
|
+
return items.flatMap((item) => (item.status === 'removed' ? [] : [item.next]));
|
|
383
|
+
}
|
|
384
|
+
return items.flatMap((item) => {
|
|
385
|
+
if (item.status === 'matched') {
|
|
386
|
+
return [
|
|
387
|
+
{
|
|
388
|
+
...item.next,
|
|
389
|
+
x: interpolate(item.prev.x, item.next.x, animationElapsedTime),
|
|
390
|
+
y: interpolate(item.prev.y, item.next.y, animationElapsedTime),
|
|
391
|
+
},
|
|
392
|
+
];
|
|
393
|
+
}
|
|
394
|
+
if (item.status === 'added') {
|
|
395
|
+
/*
|
|
396
|
+
* Here we just return the final position without interpolating
|
|
397
|
+
* so that we can allow the default initial animation that is done by clipPath in AreaRevealShape.
|
|
398
|
+
* If you want your own custom animations then you may want to interpolate this one as well.
|
|
399
|
+
*/
|
|
400
|
+
return [item.next];
|
|
401
|
+
}
|
|
402
|
+
// removed: drop
|
|
403
|
+
return [];
|
|
404
|
+
});
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
export const defaultAreaProps = {
|
|
408
|
+
activeDot: true,
|
|
409
|
+
animationBegin: 0,
|
|
410
|
+
animationDuration: 1500,
|
|
411
|
+
animationEasing: 'ease',
|
|
412
|
+
animationMatchBy: matchByIndex,
|
|
413
|
+
animationInterpolateFn: defaultAreaAnimateItems,
|
|
414
|
+
connectNulls: false,
|
|
415
|
+
dot: false,
|
|
416
|
+
fill: '#3182bd',
|
|
417
|
+
fillOpacity: 0.6,
|
|
418
|
+
hide: false,
|
|
419
|
+
isAnimationActive: 'auto',
|
|
420
|
+
legendType: 'line',
|
|
421
|
+
stroke: '#3182bd',
|
|
422
|
+
strokeWidth: 1,
|
|
423
|
+
type: 'linear',
|
|
424
|
+
label: false,
|
|
425
|
+
shape: AreaRevealShape,
|
|
426
|
+
xAxisId: 0,
|
|
427
|
+
yAxisId: 0,
|
|
428
|
+
zIndex: DefaultZIndexes.area,
|
|
429
|
+
} as const satisfies Partial<Props<never, never>>;
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Because of naming conflict, we are forced to ignore certain (valid) SVG attributes.
|
|
433
|
+
*/
|
|
434
|
+
type AreaSvgProps =
|
|
435
|
+
Omit<
|
|
436
|
+
CurveProps,
|
|
437
|
+
'points' | 'ref' | 'layout' | 'path' | 'pathRef' | 'baseLine' | 'dangerouslySetInnerHTML'
|
|
438
|
+
>;
|
|
439
|
+
|
|
440
|
+
type InternalProps = AreaSvgProps & InternalAreaProps;
|
|
441
|
+
|
|
442
|
+
export type Props<DataPointType, ValueAxisType> =
|
|
443
|
+
AreaSvgProps & AreaProps<DataPointType, ValueAxisType>;
|
|
444
|
+
|
|
445
|
+
type PropsWithDefaults<DataPointType, ValueAxisType> =
|
|
446
|
+
WithIdRequired<
|
|
447
|
+
RequiresDefaultProps<Props<DataPointType, ValueAxisType>, typeof defaultAreaProps>
|
|
448
|
+
>;
|
|
449
|
+
|
|
450
|
+
function getLegendItemColor(
|
|
451
|
+
stroke: string | undefined,
|
|
452
|
+
fill: string | undefined,
|
|
453
|
+
): string | undefined {
|
|
454
|
+
return stroke && stroke !== 'none' ? stroke : fill;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const computeLegendPayloadFromAreaData = <DataPointType, ValueAxisType>(
|
|
458
|
+
props: Props<DataPointType, ValueAxisType>,
|
|
459
|
+
): ReadonlyArray<LegendPayload> => {
|
|
460
|
+
const { dataKey, name, stroke, fill, legendType, hide } = props;
|
|
461
|
+
return [
|
|
462
|
+
{
|
|
463
|
+
inactive: hide,
|
|
464
|
+
dataKey,
|
|
465
|
+
type: legendType,
|
|
466
|
+
color: getLegendItemColor(stroke, fill),
|
|
467
|
+
value: getTooltipNameProp(name, dataKey),
|
|
468
|
+
payload: props,
|
|
469
|
+
},
|
|
470
|
+
];
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const SetAreaTooltipEntrySettings = React.memo(<DataPointType, ValueAxisType>({
|
|
474
|
+
dataKey,
|
|
475
|
+
data,
|
|
476
|
+
stroke,
|
|
477
|
+
strokeWidth,
|
|
478
|
+
fill,
|
|
479
|
+
name,
|
|
480
|
+
hide,
|
|
481
|
+
unit,
|
|
482
|
+
formatter,
|
|
483
|
+
tooltipType,
|
|
484
|
+
id,
|
|
485
|
+
}: Pick<
|
|
486
|
+
WithIdRequired<Props<DataPointType, ValueAxisType>>,
|
|
487
|
+
| 'dataKey'
|
|
488
|
+
| 'data'
|
|
489
|
+
| 'stroke'
|
|
490
|
+
| 'strokeWidth'
|
|
491
|
+
| 'fill'
|
|
492
|
+
| 'name'
|
|
493
|
+
| 'hide'
|
|
494
|
+
| 'unit'
|
|
495
|
+
| 'formatter'
|
|
496
|
+
| 'tooltipType'
|
|
497
|
+
| 'id'
|
|
498
|
+
>) => {
|
|
499
|
+
const tooltipEntrySettings: TooltipPayloadConfiguration = {
|
|
500
|
+
dataDefinedOnItem: data,
|
|
501
|
+
getPosition: noop,
|
|
502
|
+
settings: {
|
|
503
|
+
stroke,
|
|
504
|
+
strokeWidth,
|
|
505
|
+
fill,
|
|
506
|
+
dataKey,
|
|
507
|
+
nameKey: undefined,
|
|
508
|
+
name: getTooltipNameProp(name, dataKey),
|
|
509
|
+
hide,
|
|
510
|
+
type: tooltipType,
|
|
511
|
+
color: getLegendItemColor(stroke, fill),
|
|
512
|
+
unit,
|
|
513
|
+
formatter,
|
|
514
|
+
graphicalItemId: id,
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
return <SetTooltipEntrySettings tooltipEntrySettings={tooltipEntrySettings} />;
|
|
518
|
+
}) as (props: Pick<WithIdRequired<Props<DataPointType, ValueAxisType>>, 'dataKey' | 'data' | 'stroke' | 'strokeWidth' | 'fill' | 'name' | 'hide' | 'unit' | 'formatter' | 'tooltipType' | 'id'>) => ReactElement;
|
|
519
|
+
|
|
520
|
+
function AreaDotsWrapper({
|
|
521
|
+
clipPathId,
|
|
522
|
+
points,
|
|
523
|
+
props,
|
|
524
|
+
}: {
|
|
525
|
+
clipPathId: string;
|
|
526
|
+
points: ReadonlyArray<AreaPointItem>;
|
|
527
|
+
props: WithoutId<InternalProps>;
|
|
528
|
+
}) {
|
|
529
|
+
const { needClip, dot, dataKey } = props;
|
|
530
|
+
const areaProps: DotsDotProps = svgPropertiesNoEvents(props);
|
|
531
|
+
|
|
532
|
+
return <Dots
|
|
533
|
+
points={points}
|
|
534
|
+
dot={dot}
|
|
535
|
+
className="recharts-area-dots"
|
|
536
|
+
dotClassName="recharts-area-dot"
|
|
537
|
+
dataKey={dataKey}
|
|
538
|
+
baseProps={areaProps}
|
|
539
|
+
needClip={needClip}
|
|
540
|
+
clipPathId={clipPathId}
|
|
541
|
+
/>;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function AreaLabelListProvider({
|
|
545
|
+
showLabels,
|
|
546
|
+
children,
|
|
547
|
+
points,
|
|
548
|
+
}: {
|
|
549
|
+
showLabels: boolean;
|
|
550
|
+
children: ReactNode;
|
|
551
|
+
points: ReadonlyArray<AreaPointItem>;
|
|
552
|
+
}) {
|
|
553
|
+
const labelListEntries: ReadonlyArray<CartesianLabelListEntry> = points.map((
|
|
554
|
+
point,
|
|
555
|
+
): CartesianLabelListEntry => {
|
|
556
|
+
const viewBox: TrapezoidViewBox = {
|
|
557
|
+
x: point.x ?? 0,
|
|
558
|
+
y: point.y ?? 0,
|
|
559
|
+
width: 0,
|
|
560
|
+
lowerWidth: 0,
|
|
561
|
+
upperWidth: 0,
|
|
562
|
+
height: 0,
|
|
563
|
+
};
|
|
564
|
+
return {
|
|
565
|
+
...viewBox,
|
|
566
|
+
value: point.value,
|
|
567
|
+
payload: point.payload,
|
|
568
|
+
parentViewBox: undefined,
|
|
569
|
+
viewBox,
|
|
570
|
+
fill: undefined,
|
|
571
|
+
};
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
return <CartesianLabelListContextProvider
|
|
575
|
+
value={showLabels ? labelListEntries : undefined}
|
|
576
|
+
>{children}</CartesianLabelListContextProvider>;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function StaticArea({
|
|
580
|
+
points,
|
|
581
|
+
baseLine,
|
|
582
|
+
needClip,
|
|
583
|
+
clipPathId,
|
|
584
|
+
props,
|
|
585
|
+
animationElapsedTime,
|
|
586
|
+
isAnimating,
|
|
587
|
+
isEntrance,
|
|
588
|
+
}: {
|
|
589
|
+
points: ReadonlyArray<AreaPointItem>;
|
|
590
|
+
baseLine: BaseLineType | undefined;
|
|
591
|
+
needClip: boolean;
|
|
592
|
+
clipPathId: string;
|
|
593
|
+
props: InternalProps;
|
|
594
|
+
animationElapsedTime: number;
|
|
595
|
+
isAnimating: boolean;
|
|
596
|
+
isEntrance: boolean;
|
|
597
|
+
}) {
|
|
598
|
+
const { layout, type, stroke, connectNulls, isRange, shape } = props;
|
|
599
|
+
|
|
600
|
+
const { id, ...propsWithoutId } = props;
|
|
601
|
+
const propsWithEvents = svgPropertiesAndEvents(propsWithoutId);
|
|
602
|
+
|
|
603
|
+
const curveProps: AreaRevealShapeProps = {
|
|
604
|
+
...propsWithEvents,
|
|
605
|
+
id,
|
|
606
|
+
points,
|
|
607
|
+
connectNulls,
|
|
608
|
+
type,
|
|
609
|
+
baseLine,
|
|
610
|
+
layout,
|
|
611
|
+
stroke,
|
|
612
|
+
isRange,
|
|
613
|
+
animationElapsedTime,
|
|
614
|
+
isAnimating,
|
|
615
|
+
isEntrance,
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
return <>
|
|
619
|
+
{points?.length > 1 &&
|
|
620
|
+
<Layer clipPath={needClip ? `url(#clipPath-${clipPathId})` : undefined}>
|
|
621
|
+
<Shape option={shape} DefaultShape={defaultAreaProps.shape} shapeProps={curveProps} />
|
|
622
|
+
</Layer>}
|
|
623
|
+
<AreaDotsWrapper points={points} props={propsWithoutId} clipPathId={clipPathId} />
|
|
624
|
+
</>;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function interpolateScalarBaseLine(
|
|
628
|
+
baseLine: BaseLineType | undefined,
|
|
629
|
+
prevBaseLine: BaseLineType | undefined,
|
|
630
|
+
animationElapsedTime: number,
|
|
631
|
+
): BaseLineType {
|
|
632
|
+
if (isNumber(baseLine)) {
|
|
633
|
+
const previousNumberBaseLine = isNumber(prevBaseLine) ? prevBaseLine : undefined;
|
|
634
|
+
return interpolate(previousNumberBaseLine, baseLine, animationElapsedTime);
|
|
635
|
+
}
|
|
636
|
+
if (isNullish(baseLine) || isNan(baseLine)) {
|
|
637
|
+
const previousNumberBaseLine = isNumber(prevBaseLine) ? prevBaseLine : undefined;
|
|
638
|
+
return interpolate(previousNumberBaseLine, 0, animationElapsedTime);
|
|
639
|
+
}
|
|
640
|
+
return baseLine;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function AreaWithAnimation({
|
|
644
|
+
needClip,
|
|
645
|
+
clipPathId,
|
|
646
|
+
props,
|
|
647
|
+
previousPointsRef,
|
|
648
|
+
previousBaselineRef,
|
|
649
|
+
}: {
|
|
650
|
+
needClip: boolean;
|
|
651
|
+
clipPathId: string;
|
|
652
|
+
props: InternalProps;
|
|
653
|
+
previousPointsRef: MutableRefObject<ReadonlyArray<AreaPointItem> | null>;
|
|
654
|
+
previousBaselineRef: MutableRefObject<BaseLineType | undefined>;
|
|
655
|
+
}) {
|
|
656
|
+
const {
|
|
657
|
+
points,
|
|
658
|
+
baseLine,
|
|
659
|
+
isAnimationActive,
|
|
660
|
+
animationBegin,
|
|
661
|
+
animationDuration,
|
|
662
|
+
animationEasing,
|
|
663
|
+
animationMatchBy,
|
|
664
|
+
animationInterpolateFn,
|
|
665
|
+
} = props;
|
|
666
|
+
const animationInput = useMemo(() => ({ points, baseLine }), [points, baseLine]);
|
|
667
|
+
const baseLineAnimationState = useAnimationStartSnapshot(animationInput, previousBaselineRef);
|
|
668
|
+
const layout = useCartesianChartLayout();
|
|
669
|
+
|
|
670
|
+
const { isAnimating, handleAnimationStart, handleAnimationEnd } = useAnimationCallbacks(
|
|
671
|
+
props.onAnimationStart,
|
|
672
|
+
props.onAnimationEnd,
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
const prevBaseLine = baseLineAnimationState.startValue;
|
|
676
|
+
|
|
677
|
+
if (layout == null) {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
let baseLineAnimationItems: ReadonlyArray<AnimationItem<NullableCoordinate>> | null;
|
|
682
|
+
if (Array.isArray(baseLine) && Array.isArray(prevBaseLine)) {
|
|
683
|
+
baseLineAnimationItems = matchAnimationItems(prevBaseLine, baseLine, animationMatchBy);
|
|
684
|
+
} else if (Array.isArray(baseLine)) {
|
|
685
|
+
baseLineAnimationItems = matchAnimationItems(null, baseLine, animationMatchBy);
|
|
686
|
+
} else {
|
|
687
|
+
baseLineAnimationItems = null;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return <AnimatedItems
|
|
691
|
+
animationInput={animationInput}
|
|
692
|
+
animationIdPrefix="recharts-area-"
|
|
693
|
+
items={points}
|
|
694
|
+
previousItemsRef={previousPointsRef}
|
|
695
|
+
isAnimationActive={isAnimationActive}
|
|
696
|
+
animationBegin={animationBegin}
|
|
697
|
+
animationDuration={animationDuration}
|
|
698
|
+
animationEasing={animationEasing}
|
|
699
|
+
onAnimationStart={handleAnimationStart}
|
|
700
|
+
onAnimationEnd={handleAnimationEnd}
|
|
701
|
+
animationInterpolateFn={animationInterpolateFn}
|
|
702
|
+
animationMatchBy={animationMatchBy}
|
|
703
|
+
layout={layout}
|
|
704
|
+
>
|
|
705
|
+
{(stepPoints, animationElapsedTime, isEntrance) => {
|
|
706
|
+
let stepBaseLine: number | ReadonlyArray<NullableCoordinate> | undefined;
|
|
707
|
+
if (animationElapsedTime === 1) {
|
|
708
|
+
stepBaseLine = baseLine;
|
|
709
|
+
} else if (Array.isArray(baseLine)) {
|
|
710
|
+
stepBaseLine = animationInterpolateFn(baseLineAnimationItems, animationElapsedTime, layout);
|
|
711
|
+
} else {
|
|
712
|
+
stepBaseLine = isEntrance
|
|
713
|
+
? baseLine
|
|
714
|
+
: interpolateScalarBaseLine(baseLine, prevBaseLine, animationElapsedTime);
|
|
715
|
+
}
|
|
716
|
+
baseLineAnimationState.syncStepValue(stepBaseLine, animationElapsedTime);
|
|
717
|
+
return <AreaLabelListProvider showLabels={!isAnimating} points={points}>
|
|
718
|
+
{props.children}
|
|
719
|
+
<StaticArea
|
|
720
|
+
points={stepPoints}
|
|
721
|
+
baseLine={stepBaseLine}
|
|
722
|
+
needClip={needClip}
|
|
723
|
+
clipPathId={clipPathId}
|
|
724
|
+
props={props}
|
|
725
|
+
animationElapsedTime={animationElapsedTime}
|
|
726
|
+
isAnimating={isAnimating || animationElapsedTime < 1}
|
|
727
|
+
isEntrance={isEntrance}
|
|
728
|
+
/>
|
|
729
|
+
<LabelListFromLabelProp label={props.label} />
|
|
730
|
+
</AreaLabelListProvider>;
|
|
731
|
+
}}
|
|
732
|
+
</AnimatedItems>;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/*
|
|
736
|
+
* This component decides if the area should be animated or not.
|
|
737
|
+
* It also holds the state of the animation.
|
|
738
|
+
*/
|
|
739
|
+
function RenderArea({
|
|
740
|
+
needClip,
|
|
741
|
+
clipPathId,
|
|
742
|
+
props,
|
|
743
|
+
}: {
|
|
744
|
+
needClip: boolean;
|
|
745
|
+
clipPathId: string;
|
|
746
|
+
props: InternalProps;
|
|
747
|
+
}) {
|
|
748
|
+
/*
|
|
749
|
+
* These two must be refs, not state!
|
|
750
|
+
* Because we want to store the most recent shape of the animation in case we have to interrupt the animation;
|
|
751
|
+
* that happens when user initiates another animation before the current one finishes.
|
|
752
|
+
*
|
|
753
|
+
* If this was a useState, then every step in the animation would trigger a re-render.
|
|
754
|
+
* So, useRef it is.
|
|
755
|
+
*/
|
|
756
|
+
const previousPointsRef = useRef<ReadonlyArray<AreaPointItem> | null>(null);
|
|
757
|
+
const previousBaselineRef = useRef<BaseLineType | undefined>();
|
|
758
|
+
|
|
759
|
+
return <AreaWithAnimation
|
|
760
|
+
needClip={needClip}
|
|
761
|
+
clipPathId={clipPathId}
|
|
762
|
+
props={props}
|
|
763
|
+
previousPointsRef={previousPointsRef}
|
|
764
|
+
previousBaselineRef={previousBaselineRef}
|
|
765
|
+
/>;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function AreaWithState(props: InternalProps) {
|
|
769
|
+
const {
|
|
770
|
+
hide,
|
|
771
|
+
dot,
|
|
772
|
+
points,
|
|
773
|
+
className,
|
|
774
|
+
top,
|
|
775
|
+
left,
|
|
776
|
+
needClip,
|
|
777
|
+
xAxisId,
|
|
778
|
+
yAxisId,
|
|
779
|
+
width,
|
|
780
|
+
height,
|
|
781
|
+
id,
|
|
782
|
+
baseLine,
|
|
783
|
+
zIndex,
|
|
784
|
+
} = props;
|
|
785
|
+
|
|
786
|
+
if (hide) {
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const layerClass = clsx('recharts-area', className);
|
|
791
|
+
const clipPathId = id;
|
|
792
|
+
const { r, strokeWidth } = getRadiusAndStrokeWidthFromDot(dot);
|
|
793
|
+
const clipDot = isClipDot(dot);
|
|
794
|
+
const dotSize = r * 2 + strokeWidth;
|
|
795
|
+
const activePointsClipPath = needClip
|
|
796
|
+
? `url(#clipPath-${clipDot ? '' : 'dots-'}${clipPathId})`
|
|
797
|
+
: undefined;
|
|
798
|
+
|
|
799
|
+
return <ZIndexLayer zIndex={zIndex}>
|
|
800
|
+
<Layer className={layerClass}>
|
|
801
|
+
{needClip &&
|
|
802
|
+
<defs>
|
|
803
|
+
<GraphicalItemClipPath clipPathId={clipPathId} xAxisId={xAxisId} yAxisId={yAxisId} />
|
|
804
|
+
{!clipDot &&
|
|
805
|
+
<clipPath id={`clipPath-dots-${clipPathId}`}>
|
|
806
|
+
<rect
|
|
807
|
+
x={left - dotSize / 2}
|
|
808
|
+
y={top - dotSize / 2}
|
|
809
|
+
width={width + dotSize}
|
|
810
|
+
height={height + dotSize}
|
|
811
|
+
/>
|
|
812
|
+
</clipPath>}
|
|
813
|
+
</defs>}
|
|
814
|
+
<RenderArea needClip={needClip} clipPathId={clipPathId} props={props} />
|
|
815
|
+
</Layer>
|
|
816
|
+
<ActivePoints
|
|
817
|
+
points={points}
|
|
818
|
+
mainColor={getLegendItemColor(props.stroke, props.fill)}
|
|
819
|
+
itemDataKey={props.dataKey}
|
|
820
|
+
activeDot={props.activeDot}
|
|
821
|
+
clipPath={activePointsClipPath}
|
|
822
|
+
/>
|
|
823
|
+
{props.isRange && Array.isArray(baseLine) &&
|
|
824
|
+
<ActivePoints
|
|
825
|
+
points={baseLine}
|
|
826
|
+
mainColor={getLegendItemColor(props.stroke, props.fill)}
|
|
827
|
+
itemDataKey={props.dataKey}
|
|
828
|
+
activeDot={props.activeDot}
|
|
829
|
+
clipPath={activePointsClipPath}
|
|
830
|
+
/>}
|
|
831
|
+
</ZIndexLayer>;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function AreaImpl<DataPointType, ValueAxisType>(
|
|
835
|
+
props: PropsWithDefaults<DataPointType, ValueAxisType>,
|
|
836
|
+
) {
|
|
837
|
+
const {
|
|
838
|
+
activeDot,
|
|
839
|
+
animationBegin,
|
|
840
|
+
animationDuration,
|
|
841
|
+
animationEasing,
|
|
842
|
+
connectNulls,
|
|
843
|
+
dot,
|
|
844
|
+
fill,
|
|
845
|
+
fillOpacity,
|
|
846
|
+
hide,
|
|
847
|
+
isAnimationActive,
|
|
848
|
+
legendType,
|
|
849
|
+
stroke,
|
|
850
|
+
xAxisId,
|
|
851
|
+
yAxisId,
|
|
852
|
+
...everythingElse
|
|
853
|
+
} = props;
|
|
854
|
+
const layout = useChartLayout();
|
|
855
|
+
const chartName = useChartName();
|
|
856
|
+
const { needClip } = useNeedsClip(xAxisId, yAxisId);
|
|
857
|
+
const isPanorama = useIsPanorama();
|
|
858
|
+
|
|
859
|
+
const { points, isRange, baseLine } =
|
|
860
|
+
useAppSelector((state) => selectArea(state, props.id, isPanorama)) ?? {};
|
|
861
|
+
const plotArea = usePlotArea();
|
|
862
|
+
|
|
863
|
+
if (layout !== 'horizontal' && layout !== 'vertical' || plotArea == null) {
|
|
864
|
+
// Can't render Area in an unsupported layout
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
if (chartName !== 'AreaChart' && chartName !== 'ComposedChart') {
|
|
869
|
+
// There is nothing stopping us from rendering Area in other charts, except for historical reasons. Do we want to allow that?
|
|
870
|
+
return null;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const { height, width, x: left, y: top } = plotArea;
|
|
874
|
+
|
|
875
|
+
if (!points || !points.length) {
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return <AreaWithState
|
|
880
|
+
{...everythingElse}
|
|
881
|
+
activeDot={activeDot}
|
|
882
|
+
animationBegin={animationBegin}
|
|
883
|
+
animationDuration={animationDuration}
|
|
884
|
+
animationEasing={animationEasing}
|
|
885
|
+
baseLine={baseLine}
|
|
886
|
+
connectNulls={connectNulls}
|
|
887
|
+
dot={dot}
|
|
888
|
+
fill={fill}
|
|
889
|
+
fillOpacity={fillOpacity}
|
|
890
|
+
height={height}
|
|
891
|
+
hide={hide}
|
|
892
|
+
layout={layout}
|
|
893
|
+
isAnimationActive={isAnimationActive}
|
|
894
|
+
isRange={isRange}
|
|
895
|
+
legendType={legendType}
|
|
896
|
+
needClip={needClip}
|
|
897
|
+
points={points}
|
|
898
|
+
stroke={stroke}
|
|
899
|
+
width={width}
|
|
900
|
+
left={left}
|
|
901
|
+
top={top}
|
|
902
|
+
xAxisId={xAxisId}
|
|
903
|
+
yAxisId={yAxisId}
|
|
904
|
+
/>;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
export const getBaseValue = (
|
|
908
|
+
layout: 'horizontal' | 'vertical',
|
|
909
|
+
chartBaseValue: BaseValue | undefined,
|
|
910
|
+
itemBaseValue: BaseValue | undefined,
|
|
911
|
+
xAxis: BaseAxisWithScale,
|
|
912
|
+
yAxis: BaseAxisWithScale,
|
|
913
|
+
): number => {
|
|
914
|
+
// The baseValue can be defined both on the AreaChart, and on the Area.
|
|
915
|
+
// The value for the item takes precedence.
|
|
916
|
+
const baseValue: BaseValue | undefined = itemBaseValue ?? chartBaseValue;
|
|
917
|
+
|
|
918
|
+
if (isNumber(baseValue)) {
|
|
919
|
+
return baseValue;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
const numericAxis =
|
|
923
|
+
layout === 'horizontal' ? yAxis : xAxis;
|
|
924
|
+
// @ts-expect-error d3scale .domain() returns unknown, Math.max expects number
|
|
925
|
+
const domain: [number, number] = numericAxis.scale.domain();
|
|
926
|
+
|
|
927
|
+
if (numericAxis.type === 'number') {
|
|
928
|
+
const domainMax = Math.max(domain[0], domain[1]);
|
|
929
|
+
const domainMin = Math.min(domain[0], domain[1]);
|
|
930
|
+
|
|
931
|
+
if (baseValue === 'dataMin') {
|
|
932
|
+
return domainMin;
|
|
933
|
+
}
|
|
934
|
+
if (baseValue === 'dataMax') {
|
|
935
|
+
return domainMax;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
return domainMax < 0 ? domainMax : Math.max(Math.min(domain[0], domain[1]), 0);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
if (baseValue === 'dataMin') {
|
|
942
|
+
return domain[0];
|
|
943
|
+
}
|
|
944
|
+
if (baseValue === 'dataMax') {
|
|
945
|
+
return domain[1];
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
return domain[0];
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
export function computeArea({
|
|
952
|
+
areaSettings: { connectNulls, baseValue: itemBaseValue, dataKey },
|
|
953
|
+
stackedData,
|
|
954
|
+
layout,
|
|
955
|
+
chartBaseValue,
|
|
956
|
+
xAxis,
|
|
957
|
+
yAxis,
|
|
958
|
+
displayedData,
|
|
959
|
+
dataStartIndex,
|
|
960
|
+
xAxisTicks,
|
|
961
|
+
yAxisTicks,
|
|
962
|
+
bandSize,
|
|
963
|
+
}: {
|
|
964
|
+
areaSettings: AreaSettings;
|
|
965
|
+
stackedData: ReadonlyArray<StackDataPoint> | undefined;
|
|
966
|
+
layout: 'horizontal' | 'vertical';
|
|
967
|
+
chartBaseValue: BaseValue | undefined;
|
|
968
|
+
xAxis: BaseAxisWithScale;
|
|
969
|
+
yAxis: BaseAxisWithScale;
|
|
970
|
+
displayedData: ChartData;
|
|
971
|
+
dataStartIndex: number;
|
|
972
|
+
xAxisTicks: TickItem[];
|
|
973
|
+
yAxisTicks: TickItem[];
|
|
974
|
+
bandSize: number;
|
|
975
|
+
}): ComputedArea {
|
|
976
|
+
const hasStack = stackedData && stackedData.length;
|
|
977
|
+
const baseValue = getBaseValue(layout, chartBaseValue, itemBaseValue, xAxis, yAxis);
|
|
978
|
+
const isHorizontalLayout = layout === 'horizontal';
|
|
979
|
+
let isRange = false;
|
|
980
|
+
|
|
981
|
+
const points: ReadonlyArray<AreaPointItem> = displayedData.map((entry, index): AreaPointItem => {
|
|
982
|
+
let valueAsArray: ReadonlyArray<unknown> | undefined;
|
|
983
|
+
|
|
984
|
+
if (hasStack) {
|
|
985
|
+
valueAsArray = stackedData[dataStartIndex + index];
|
|
986
|
+
} else {
|
|
987
|
+
const rawValue = getValueByDataKey(entry, dataKey);
|
|
988
|
+
|
|
989
|
+
if (!Array.isArray(rawValue)) {
|
|
990
|
+
valueAsArray = [baseValue, rawValue];
|
|
991
|
+
} else {
|
|
992
|
+
valueAsArray = rawValue;
|
|
993
|
+
isRange = true;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const value1 = valueAsArray?.[1] ?? null;
|
|
998
|
+
|
|
999
|
+
const isBreakPoint =
|
|
1000
|
+
value1 == null || hasStack && !connectNulls && getValueByDataKey(entry, dataKey) == null;
|
|
1001
|
+
|
|
1002
|
+
if (isHorizontalLayout) {
|
|
1003
|
+
return {
|
|
1004
|
+
x: getCateCoordinateOfLine({ axis: xAxis, ticks: xAxisTicks, bandSize, entry, index }),
|
|
1005
|
+
y: isBreakPoint ? null : yAxis.scale.map(value1) ?? null,
|
|
1006
|
+
value: valueAsArray,
|
|
1007
|
+
payload: entry,
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
return {
|
|
1012
|
+
x: isBreakPoint ? null : xAxis.scale.map(value1) ?? null,
|
|
1013
|
+
y: getCateCoordinateOfLine({ axis: yAxis, ticks: yAxisTicks, bandSize, entry, index }),
|
|
1014
|
+
value: valueAsArray,
|
|
1015
|
+
payload: entry,
|
|
1016
|
+
};
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
let baseLine: number | NullableCoordinate[] | undefined;
|
|
1020
|
+
if (hasStack || isRange) {
|
|
1021
|
+
baseLine = points.map((entry: AreaPointItem): BaseValueCoordinate => {
|
|
1022
|
+
const x = Array.isArray(entry.value) ? entry.value[0] : null;
|
|
1023
|
+
if (isHorizontalLayout) {
|
|
1024
|
+
return {
|
|
1025
|
+
x: entry.x,
|
|
1026
|
+
y: x != null && entry.y != null ? yAxis.scale.map(x) ?? null : null,
|
|
1027
|
+
payload: entry.payload,
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
return {
|
|
1031
|
+
x: x != null ? xAxis.scale.map(x) ?? null : null,
|
|
1032
|
+
y: entry.y,
|
|
1033
|
+
payload: entry.payload,
|
|
1034
|
+
};
|
|
1035
|
+
});
|
|
1036
|
+
} else {
|
|
1037
|
+
baseLine = isHorizontalLayout ? yAxis.scale.map(baseValue) : xAxis.scale.map(baseValue);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
return {
|
|
1041
|
+
points,
|
|
1042
|
+
baseLine: baseLine ?? 0,
|
|
1043
|
+
isRange,
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function AreaFn<DataPointType, ValueAxisType>(outsideProps: Props<DataPointType, ValueAxisType>) {
|
|
1048
|
+
const props = resolveDefaultProps(outsideProps, defaultAreaProps);
|
|
1049
|
+
const isPanorama = useIsPanorama();
|
|
1050
|
+
// Report all props to Redux store first, before calling hooks, to avoid circular dependencies.
|
|
1051
|
+
return <RegisterGraphicalItemId id={props.id} type="area">
|
|
1052
|
+
{(id) => <>
|
|
1053
|
+
<SetLegendPayload legendPayload={computeLegendPayloadFromAreaData(props)} />
|
|
1054
|
+
<SetAreaTooltipEntrySettings
|
|
1055
|
+
dataKey={props.dataKey}
|
|
1056
|
+
data={props.data}
|
|
1057
|
+
stroke={props.stroke}
|
|
1058
|
+
strokeWidth={props.strokeWidth}
|
|
1059
|
+
fill={props.fill}
|
|
1060
|
+
name={props.name}
|
|
1061
|
+
hide={props.hide}
|
|
1062
|
+
unit={props.unit}
|
|
1063
|
+
formatter={props.formatter}
|
|
1064
|
+
tooltipType={props.tooltipType}
|
|
1065
|
+
id={id}
|
|
1066
|
+
/>
|
|
1067
|
+
<SetCartesianGraphicalItem
|
|
1068
|
+
type="area"
|
|
1069
|
+
id={id}
|
|
1070
|
+
data={props.data}
|
|
1071
|
+
dataKey={props.dataKey}
|
|
1072
|
+
xAxisId={props.xAxisId}
|
|
1073
|
+
yAxisId={props.yAxisId}
|
|
1074
|
+
zAxisId={0}
|
|
1075
|
+
stackId={getNormalizedStackId(props.stackId)}
|
|
1076
|
+
hide={props.hide}
|
|
1077
|
+
barSize={undefined}
|
|
1078
|
+
baseValue={props.baseValue}
|
|
1079
|
+
isPanorama={isPanorama}
|
|
1080
|
+
connectNulls={props.connectNulls}
|
|
1081
|
+
/>
|
|
1082
|
+
<AreaImpl {...props} id={id} />
|
|
1083
|
+
</>}
|
|
1084
|
+
</RegisterGraphicalItemId>;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* @provides LabelListContext
|
|
1089
|
+
* @consumes CartesianChartContext
|
|
1090
|
+
*/
|
|
1091
|
+
export const Area = React.memo(AreaFn, propsAreEqual) as (props: Props<
|
|
1092
|
+
DataPointType,
|
|
1093
|
+
ValueAxisType
|
|
1094
|
+
>) => ReactElement;
|
|
1095
|
+
// @ts-expect-error we need to set the displayName for debugging purposes
|
|
1096
|
+
Area.displayName = 'Area';
|