@solidports/recharts 0.1.0
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/LICENSE +22 -0
- package/NOTICE +19 -0
- package/README.md +102 -0
- package/dist/dev.js +28564 -0
- package/dist/index.d.ts +7859 -0
- package/dist/index.js +28546 -0
- package/package.json +98 -0
- package/src/animation/AnimatedItems.tsx +183 -0
- package/src/animation/AnimationController.ts +33 -0
- package/src/animation/AnimationControllerImpl.ts +36 -0
- package/src/animation/AnimationHandle.ts +307 -0
- package/src/animation/CSSTransitionAnimate.tsx +170 -0
- package/src/animation/JavascriptAnimate.tsx +119 -0
- package/src/animation/easing.ts +221 -0
- package/src/animation/matchBy.ts +164 -0
- package/src/animation/timeoutController.ts +70 -0
- package/src/animation/useAnimationController.tsx +34 -0
- package/src/animation/useAnimationStartSnapshot.ts +118 -0
- package/src/animation/util.ts +41 -0
- package/src/cartesian/Area.tsx +915 -0
- package/src/cartesian/AreaRevealShape.tsx +190 -0
- package/src/cartesian/Bar.tsx +1369 -0
- package/src/cartesian/BarStack.tsx +152 -0
- package/src/cartesian/Brush.tsx +951 -0
- package/src/cartesian/CartesianAxis.tsx +722 -0
- package/src/cartesian/CartesianGrid.tsx +711 -0
- package/src/cartesian/ErrorBar.tsx +361 -0
- package/src/cartesian/Funnel.tsx +803 -0
- package/src/cartesian/GraphicalItemClipPath.tsx +67 -0
- package/src/cartesian/Line.tsx +863 -0
- package/src/cartesian/LineDrawShape.tsx +89 -0
- package/src/cartesian/ReferenceArea.tsx +331 -0
- package/src/cartesian/ReferenceDot.tsx +304 -0
- package/src/cartesian/ReferenceLine.tsx +474 -0
- package/src/cartesian/Scatter.tsx +888 -0
- package/src/cartesian/XAxis.tsx +433 -0
- package/src/cartesian/YAxis.tsx +443 -0
- package/src/cartesian/ZAxis.tsx +155 -0
- package/src/cartesian/cartesianPositionToCSSTranslate.tsx +29 -0
- package/src/cartesian/cartesianViewBoxToTrapezoid.tsx +21 -0
- package/src/cartesian/getCartesianPosition.tsx +267 -0
- package/src/cartesian/getEquidistantTicks.ts +144 -0
- package/src/cartesian/getTicks.ts +225 -0
- package/src/cartesian/isOutsidePosition.ts +15 -0
- package/src/cartesian/useAnimatedLineLength.ts +54 -0
- package/src/chart/AreaChart.tsx +29 -0
- package/src/chart/BarChart.tsx +29 -0
- package/src/chart/CartesianChart.tsx +131 -0
- package/src/chart/CategoricalChart.tsx +94 -0
- package/src/chart/ComposedChart.tsx +29 -0
- package/src/chart/FunnelChart.tsx +29 -0
- package/src/chart/LineChart.tsx +29 -0
- package/src/chart/PieChart.tsx +50 -0
- package/src/chart/PolarChart.tsx +164 -0
- package/src/chart/RadarChart.tsx +70 -0
- package/src/chart/RadialBarChart.tsx +46 -0
- package/src/chart/RechartsWrapper.tsx +551 -0
- package/src/chart/Sankey.tsx +1534 -0
- package/src/chart/ScatterChart.tsx +29 -0
- package/src/chart/SunburstChart.tsx +504 -0
- package/src/chart/Treemap.tsx +1223 -0
- package/src/chart/createInitialRootProps.ts +24 -0
- package/src/chart/types.ts +53 -0
- package/src/component/ActivePoints.tsx +144 -0
- package/src/component/Cell.tsx +37 -0
- package/src/component/Cursor.tsx +185 -0
- package/src/component/Customized.tsx +50 -0
- package/src/component/DefaultLegendContent.tsx +348 -0
- package/src/component/DefaultTooltipContent.tsx +224 -0
- package/src/component/Dots.tsx +157 -0
- package/src/component/Label.tsx +636 -0
- package/src/component/LabelList.tsx +375 -0
- package/src/component/Legend.tsx +512 -0
- package/src/component/ResponsiveContainer.tsx +348 -0
- package/src/component/Text.tsx +541 -0
- package/src/component/Tooltip.tsx +470 -0
- package/src/component/TooltipBoundingBox.tsx +146 -0
- package/src/component/responsiveContainerUtils.ts +134 -0
- package/src/container/ClipPathProvider.tsx +51 -0
- package/src/container/Layer.tsx +38 -0
- package/src/container/RootSurface.tsx +120 -0
- package/src/container/Surface.tsx +61 -0
- package/src/context/CellsContext.tsx +54 -0
- package/src/context/ErrorBarContext.tsx +146 -0
- package/src/context/GraphicalItemChildrenScope.tsx +18 -0
- package/src/context/PanoramaContext.tsx +9 -0
- package/src/context/RegisterGraphicalItemId.tsx +30 -0
- package/src/context/accessibilityContext.tsx +6 -0
- package/src/context/brushUpdateContext.tsx +9 -0
- package/src/context/chartDataContext.tsx +97 -0
- package/src/context/chartLayoutContext.tsx +201 -0
- package/src/context/legendPayloadContext.tsx +20 -0
- package/src/context/legendPortalContext.tsx +12 -0
- package/src/context/tooltipContext.tsx +89 -0
- package/src/context/tooltipPortalContext.tsx +12 -0
- package/src/context/useTooltipAxis.ts +22 -0
- package/src/hooks.ts +476 -0
- package/src/index.ts +280 -0
- package/src/polar/Pie.tsx +1360 -0
- package/src/polar/PolarAngleAxis.tsx +616 -0
- package/src/polar/PolarGrid.tsx +312 -0
- package/src/polar/PolarRadiusAxis.tsx +547 -0
- package/src/polar/Radar.tsx +734 -0
- package/src/polar/RadialBar.tsx +917 -0
- package/src/polar/defaultPolarAngleAxisProps.tsx +27 -0
- package/src/polar/defaultPolarRadiusAxisProps.tsx +25 -0
- package/src/shape/Cross.tsx +89 -0
- package/src/shape/Curve.tsx +343 -0
- package/src/shape/Dot.tsx +70 -0
- package/src/shape/Polygon.tsx +195 -0
- package/src/shape/Rectangle.tsx +470 -0
- package/src/shape/Sector.tsx +347 -0
- package/src/shape/Symbols.tsx +139 -0
- package/src/shape/Trapezoid.tsx +280 -0
- package/src/state/RechartsStateContext.tsx +12 -0
- package/src/state/RechartsStateProvider.tsx +87 -0
- package/src/state/RechartsStoreContext.tsx +25 -0
- package/src/state/RechartsStoreProvider.tsx +35 -0
- package/src/state/ReportChartProps.tsx +40 -0
- package/src/state/ReportEventSettings.tsx +30 -0
- package/src/state/ReportMainChartProps.tsx +61 -0
- package/src/state/ReportPolarOptions.tsx +35 -0
- package/src/state/SetGraphicalItem.ts +79 -0
- package/src/state/SetLegendPayload.ts +134 -0
- package/src/state/SetTooltipEntrySettings.tsx +72 -0
- package/src/state/actions.ts +464 -0
- package/src/state/brushSlice.ts +13 -0
- package/src/state/cartesianAxisSlice.ts +141 -0
- package/src/state/chartDataSlice.ts +50 -0
- package/src/state/chartState.ts +297 -0
- package/src/state/errorBarSlice.ts +29 -0
- package/src/state/eventSettingsSlice.ts +8 -0
- package/src/state/events.ts +76 -0
- package/src/state/externalEventsMiddleware.ts +255 -0
- package/src/state/graphicalItemsSlice.ts +74 -0
- package/src/state/hooks/useChartSelectors.tsx +45 -0
- package/src/state/keyboardEventsMiddleware.ts +228 -0
- package/src/state/layoutSlice.ts +21 -0
- package/src/state/legendSlice.ts +57 -0
- package/src/state/mouseEventsMiddleware.ts +185 -0
- package/src/state/optionsSlice.ts +47 -0
- package/src/state/polarAxisSlice.ts +10 -0
- package/src/state/polarOptionsSlice.ts +12 -0
- package/src/state/rawData.ts +26 -0
- package/src/state/referenceElementsSlice.ts +40 -0
- package/src/state/renderedTicksSlice.ts +15 -0
- package/src/state/rootPropsSlice.ts +47 -0
- package/src/state/selectors/areaSelectors.ts +240 -0
- package/src/state/selectors/arrayEqualityCheck.ts +29 -0
- package/src/state/selectors/axisSelectors.ts +2157 -0
- package/src/state/selectors/barSelectors.ts +310 -0
- package/src/state/selectors/barStackSelectors.ts +77 -0
- package/src/state/selectors/brushSelectors.ts +33 -0
- package/src/state/selectors/chartSelector.ts +185 -0
- package/src/state/selectors/combiners/combineActiveLabel.ts +16 -0
- package/src/state/selectors/combiners/combineActiveTooltipIndex.ts +95 -0
- package/src/state/selectors/combiners/combineAllBarPositions.ts +122 -0
- package/src/state/selectors/combiners/combineAxisRangeWithReverse.ts +16 -0
- package/src/state/selectors/combiners/combineBarPosition.ts +23 -0
- package/src/state/selectors/combiners/combineBarSizeList.ts +80 -0
- package/src/state/selectors/combiners/combineCheckedDomain.ts +48 -0
- package/src/state/selectors/combiners/combineConfiguredScale.ts +111 -0
- package/src/state/selectors/combiners/combineCoordinateForDefaultIndex.ts +45 -0
- package/src/state/selectors/combiners/combineDisplayedStackedData.ts +50 -0
- package/src/state/selectors/combiners/combineInverseScaleFunction.ts +16 -0
- package/src/state/selectors/combiners/combineRealScaleType.ts +44 -0
- package/src/state/selectors/combiners/combineStackedData.ts +27 -0
- package/src/state/selectors/combiners/combineTooltipInteractionState.ts +86 -0
- package/src/state/selectors/combiners/combineTooltipPayload.ts +205 -0
- package/src/state/selectors/combiners/combineTooltipPayloadConfigurations.ts +58 -0
- package/src/state/selectors/containerSelectors.ts +12 -0
- package/src/state/selectors/dataSelectors.ts +88 -0
- package/src/state/selectors/funnelSelectors.ts +75 -0
- package/src/state/selectors/graphicalItemSelectors.ts +30 -0
- package/src/state/selectors/legendSelectors.ts +29 -0
- package/src/state/selectors/lineSelectors.ts +134 -0
- package/src/state/selectors/numberDomainEqualityCheck.ts +14 -0
- package/src/state/selectors/pickAxisId.ts +5 -0
- package/src/state/selectors/pickAxisType.ts +3 -0
- package/src/state/selectors/pieSelectors.ts +97 -0
- package/src/state/selectors/polarAxisSelectors.ts +201 -0
- package/src/state/selectors/polarGridSelectors.ts +28 -0
- package/src/state/selectors/polarScaleSelectors.ts +133 -0
- package/src/state/selectors/polarSelectors.ts +219 -0
- package/src/state/selectors/radarSelectors.ts +191 -0
- package/src/state/selectors/radialBarSelectors.ts +435 -0
- package/src/state/selectors/rootPropsSelectors.ts +25 -0
- package/src/state/selectors/scatterSelectors.ts +96 -0
- package/src/state/selectors/selectActivePropsFromChartPointer.ts +26 -0
- package/src/state/selectors/selectAllAxes.ts +17 -0
- package/src/state/selectors/selectChartOffset.ts +16 -0
- package/src/state/selectors/selectChartOffsetInternal.ts +136 -0
- package/src/state/selectors/selectLegendArea.ts +20 -0
- package/src/state/selectors/selectPlotArea.ts +22 -0
- package/src/state/selectors/selectTooltipAxisId.ts +7 -0
- package/src/state/selectors/selectTooltipAxisType.ts +32 -0
- package/src/state/selectors/selectTooltipEventType.ts +48 -0
- package/src/state/selectors/selectTooltipPayloadSearcher.ts +6 -0
- package/src/state/selectors/selectTooltipSettings.ts +39 -0
- package/src/state/selectors/selectTooltipState.ts +8 -0
- package/src/state/selectors/selectors.ts +271 -0
- package/src/state/selectors/tooltipSelectors.ts +578 -0
- package/src/state/selectors/touchSelectors.ts +31 -0
- package/src/state/store.ts +29 -0
- package/src/state/storeIdentity.ts +9 -0
- package/src/state/teardownWrite.ts +10 -0
- package/src/state/tooltipInteraction.ts +23 -0
- package/src/state/tooltipSlice.ts +334 -0
- package/src/state/touchEventsMiddleware.ts +214 -0
- package/src/state/types/AnimationSettings.ts +20 -0
- package/src/state/types/AreaSettings.ts +14 -0
- package/src/state/types/BarSettings.ts +15 -0
- package/src/state/types/LineSettings.ts +9 -0
- package/src/state/types/PieSettings.ts +34 -0
- package/src/state/types/RadarSettings.ts +5 -0
- package/src/state/types/RadialBarSettings.ts +10 -0
- package/src/state/types/ScatterSettings.ts +12 -0
- package/src/state/types/StackedGraphicalItem.ts +34 -0
- package/src/state/useChartState.ts +17 -0
- package/src/state/zIndexSlice.ts +39 -0
- package/src/synchronisation/syncSelectors.ts +7 -0
- package/src/synchronisation/types.ts +44 -0
- package/src/synchronisation/useChartSynchronisation.tsx +404 -0
- package/src/theme/RechartsTheme.ts +16 -0
- package/src/theme/RechartsThemeContext.ts +19 -0
- package/src/theme/darkTheme.ts +13 -0
- package/src/theme/graphicalItemIdentity.ts +70 -0
- package/src/theme/legacyTheme.ts +17 -0
- package/src/theme/lightTheme.ts +13 -0
- package/src/types.ts +54 -0
- package/src/util/ActiveShapeUtils.tsx +162 -0
- package/src/util/BarUtils.tsx +62 -0
- package/src/util/BarePortal.tsx +50 -0
- package/src/util/CamelCaseSVGAttrs.ts +34 -0
- package/src/util/CartesianUtils.ts +48 -0
- package/src/util/ChartUtils.ts +802 -0
- package/src/util/Constants.ts +42 -0
- package/src/util/CssPrefixUtils.ts +20 -0
- package/src/util/DOMUtils.ts +129 -0
- package/src/util/DataUtils.ts +216 -0
- package/src/util/Events.ts +16 -0
- package/src/util/FunnelUtils.tsx +15 -0
- package/src/util/Global.ts +12 -0
- package/src/util/IfOverflow.ts +15 -0
- package/src/util/LRUCache.ts +41 -0
- package/src/util/LogUtils.ts +22 -0
- package/src/util/PolarUtils.ts +132 -0
- package/src/util/RadialBarUtils.tsx +24 -0
- package/src/util/ReactUtils.ts +95 -0
- package/src/util/ReduceCSSCalc.ts +187 -0
- package/src/util/ScatterUtils.tsx +52 -0
- package/src/util/ShapeElementProps.tsx +87 -0
- package/src/util/TickUtils.ts +60 -0
- package/src/util/XAxisUtils.ts +48 -0
- package/src/util/YAxisUtils.ts +48 -0
- package/src/util/axisPropsAreEqual.ts +48 -0
- package/src/util/createCartesianCharts.tsx +150 -0
- package/src/util/createEventProxy.ts +20 -0
- package/src/util/createPolarCharts.tsx +112 -0
- package/src/util/cursor/getCursorPoints.ts +46 -0
- package/src/util/cursor/getCursorRectangle.ts +28 -0
- package/src/util/cursor/getRadialCursorPoints.ts +32 -0
- package/src/util/excludeEventProps.ts +171 -0
- package/src/util/getActiveCoordinate.ts +213 -0
- package/src/util/getAxisTypeBasedOnLayout.ts +21 -0
- package/src/util/getClassNameFromUnknown.ts +6 -0
- package/src/util/getEveryNth.ts +27 -0
- package/src/util/getRadiusAndStrokeWidthFromDot.ts +35 -0
- package/src/util/getRelativeCoordinate.ts +80 -0
- package/src/util/getSliced.ts +13 -0
- package/src/util/hoverDedupe.ts +41 -0
- package/src/util/isDomainSpecifiedByUser.ts +219 -0
- package/src/util/isWellBehavedNumber.ts +7 -0
- package/src/util/pathTotalLength.ts +25 -0
- package/src/util/payload/getUniqPayload.ts +28 -0
- package/src/util/propsAreEqual.ts +121 -0
- package/src/util/resolveDefaultProps.ts +161 -0
- package/src/util/round.ts +32 -0
- package/src/util/scale/CartesianScaleHelper.ts +72 -0
- package/src/util/scale/CustomScaleDefinition.ts +81 -0
- package/src/util/scale/RechartsScale.ts +130 -0
- package/src/util/scale/createCategoricalInverse.ts +72 -0
- package/src/util/scale/getNiceTickValues.ts +311 -0
- package/src/util/scale/index.ts +2 -0
- package/src/util/scale/util/arithmetic.ts +54 -0
- package/src/util/solid-1-compat.ts +310 -0
- package/src/util/solidJsxCamelAugment.d.ts +84 -0
- package/src/util/stacks/getStackSeriesIdentifier.ts +21 -0
- package/src/util/stacks/stackTypes.ts +60 -0
- package/src/util/structurallyEqual.ts +31 -0
- package/src/util/svgPropertiesAndEvents.ts +53 -0
- package/src/util/svgPropertiesNoEvents.ts +571 -0
- package/src/util/tooltip/translate.ts +171 -0
- package/src/util/typedDataKey.ts +48 -0
- package/src/util/types.ts +1560 -0
- package/src/util/useAnimationId.ts +31 -0
- package/src/util/useElementOffset.ts +110 -0
- package/src/util/useId.ts +8 -0
- package/src/util/usePrefersReducedMotion.ts +48 -0
- package/src/util/useReportScale.ts +24 -0
- package/src/util/useUniqueId.ts +30 -0
- package/src/zIndex/DefaultZIndexes.ts +84 -0
- package/src/zIndex/ZIndexLayer.tsx +121 -0
- package/src/zIndex/ZIndexPortal.tsx +79 -0
- package/src/zIndex/getZIndexFromUnknown.ts +14 -0
- package/src/zIndex/zIndexSelectors.ts +48 -0
|
@@ -0,0 +1,1223 @@
|
|
|
1
|
+
/* eslint-disable import/no-cycle, sort-keys */
|
|
2
|
+
import { createMemo, createSignal, For, Show, untrack } from 'solid-js';
|
|
3
|
+
import type { JSX } from '@solidjs/web';
|
|
4
|
+
import omit from "es-toolkit/compat/omit"
|
|
5
|
+
import get from "es-toolkit/compat/get"
|
|
6
|
+
|
|
7
|
+
import { Layer } from "../container/Layer"
|
|
8
|
+
import { Surface } from "../container/Surface"
|
|
9
|
+
import { Polygon } from "../shape/Polygon"
|
|
10
|
+
import { Rectangle } from "../shape/Rectangle"
|
|
11
|
+
import { getValueByDataKey } from "../util/ChartUtils"
|
|
12
|
+
import { COLOR_PANEL } from "../util/Constants"
|
|
13
|
+
import { isNan, noop } from "../util/DataUtils"
|
|
14
|
+
import { getStringSize } from "../util/DOMUtils"
|
|
15
|
+
import type {
|
|
16
|
+
AnimationDuration,
|
|
17
|
+
AnimationTiming,
|
|
18
|
+
Coordinate,
|
|
19
|
+
DataConsumer,
|
|
20
|
+
DataKey,
|
|
21
|
+
EventThrottlingProps,
|
|
22
|
+
Margin,
|
|
23
|
+
Percent,
|
|
24
|
+
RectanglePosition,
|
|
25
|
+
} from "../util/types"
|
|
26
|
+
import { ReportChartMargin, useChartHeight, useChartWidth } from "../context/chartLayoutContext"
|
|
27
|
+
import { RechartsWrapper } from "./RechartsWrapper"
|
|
28
|
+
import type {
|
|
29
|
+
TooltipIndex,
|
|
30
|
+
TooltipPayloadConfiguration,
|
|
31
|
+
TooltipPayloadSearcher,
|
|
32
|
+
} from "../state/tooltipSlice"
|
|
33
|
+
import { SetTooltipEntrySettings } from "../state/SetTooltipEntrySettings"
|
|
34
|
+
import type { ChartOptions } from "../state/optionsSlice"
|
|
35
|
+
import { RechartsStateProvider } from "../state/RechartsStateProvider"
|
|
36
|
+
import { createInitialLayoutState } from "../state/chartState"
|
|
37
|
+
import { ReportEventSettings } from "../state/ReportEventSettings"
|
|
38
|
+
import { useOptionalChartState } from "../state/useChartState"
|
|
39
|
+
import { isPositiveNumber } from "../util/isWellBehavedNumber"
|
|
40
|
+
import { svgPropertiesNoEvents } from "../util/svgPropertiesNoEvents"
|
|
41
|
+
import { CSSTransitionAnimate, extractCssEasing } from "../animation/CSSTransitionAnimate"
|
|
42
|
+
import type { RequiresDefaultProps } from "../util/resolveDefaultProps"
|
|
43
|
+
import { resolveDefaultProps } from "../util/resolveDefaultProps"
|
|
44
|
+
import { RegisterGraphicalItemId } from "../context/RegisterGraphicalItemId"
|
|
45
|
+
import type { GraphicalItemId } from "../state/graphicalItemsSlice"
|
|
46
|
+
import { initialEventSettingsState } from "../state/eventSettingsSlice"
|
|
47
|
+
|
|
48
|
+
import { splitProps } from '../util/solid-1-compat';
|
|
49
|
+
import { setTooltipInteraction } from "../state/tooltipInteraction"
|
|
50
|
+
const NODE_VALUE_KEY = "value"
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* This is what end users defines as `data` on Treemap.
|
|
54
|
+
*/
|
|
55
|
+
export interface TreemapDataType {
|
|
56
|
+
children?: ReadonlyArray<TreemapDataType>
|
|
57
|
+
|
|
58
|
+
[key: string]: unknown
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* This is what is returned from `squarify`, the final treemap data structure
|
|
63
|
+
* that gets rendered and is stored in
|
|
64
|
+
*/
|
|
65
|
+
export interface TreemapNode {
|
|
66
|
+
children: ReadonlyArray<TreemapNode> | null
|
|
67
|
+
value: number
|
|
68
|
+
depth: number
|
|
69
|
+
index: number
|
|
70
|
+
x: number
|
|
71
|
+
y: number
|
|
72
|
+
width: number
|
|
73
|
+
height: number
|
|
74
|
+
name: string
|
|
75
|
+
tooltipIndex: TooltipIndex
|
|
76
|
+
root?: TreemapNode
|
|
77
|
+
|
|
78
|
+
[k: string]: unknown
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isTreemapNode(value: unknown): value is TreemapNode {
|
|
82
|
+
return (
|
|
83
|
+
value != null &&
|
|
84
|
+
typeof value === "object" &&
|
|
85
|
+
"x" in value &&
|
|
86
|
+
"y" in value &&
|
|
87
|
+
"width" in value &&
|
|
88
|
+
"height" in value &&
|
|
89
|
+
typeof value.x === "number" &&
|
|
90
|
+
typeof value.y === "number" &&
|
|
91
|
+
typeof value.width === "number" &&
|
|
92
|
+
typeof value.height === "number"
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const treemapPayloadSearcher: TooltipPayloadSearcher = (
|
|
97
|
+
data: unknown,
|
|
98
|
+
activeIndex: TooltipIndex,
|
|
99
|
+
): TreemapNode | undefined => {
|
|
100
|
+
if (data == null || activeIndex == null) {
|
|
101
|
+
return undefined
|
|
102
|
+
}
|
|
103
|
+
return get(data, activeIndex)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const addToTreemapNodeIndex = (
|
|
107
|
+
indexInChildrenArr: number,
|
|
108
|
+
activeTooltipIndexSoFar: TooltipIndex | undefined = "",
|
|
109
|
+
): TooltipIndex => {
|
|
110
|
+
return `${activeTooltipIndexSoFar}children[${indexInChildrenArr}]`
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const chartOptions: ChartOptions = {
|
|
114
|
+
chartName: "Treemap",
|
|
115
|
+
defaultTooltipEventType: "item",
|
|
116
|
+
eventEmitter: undefined,
|
|
117
|
+
tooltipPayloadSearcher: treemapPayloadSearcher,
|
|
118
|
+
validateTooltipEventTypes: ["item"],
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const computeNode = <DataPointType extends TreemapDataType, DataValueType>({
|
|
122
|
+
depth,
|
|
123
|
+
node,
|
|
124
|
+
index,
|
|
125
|
+
dataKey,
|
|
126
|
+
nameKey,
|
|
127
|
+
nestedActiveTooltipIndex,
|
|
128
|
+
}: {
|
|
129
|
+
depth: number
|
|
130
|
+
node: TreemapNode
|
|
131
|
+
index: number
|
|
132
|
+
dataKey: DataKey<DataPointType, DataValueType>
|
|
133
|
+
nameKey: DataKey<DataPointType, DataValueType>
|
|
134
|
+
nestedActiveTooltipIndex?: TooltipIndex | undefined
|
|
135
|
+
}): TreemapNode => {
|
|
136
|
+
const currentTooltipIndex =
|
|
137
|
+
depth === 0 ? "" : addToTreemapNodeIndex(index, nestedActiveTooltipIndex)
|
|
138
|
+
const { children } = node
|
|
139
|
+
const childDepth = depth + 1
|
|
140
|
+
const computedChildren =
|
|
141
|
+
children && children.length
|
|
142
|
+
? children.map((child: TreemapNode, i: number) =>
|
|
143
|
+
computeNode({
|
|
144
|
+
dataKey,
|
|
145
|
+
depth: childDepth,
|
|
146
|
+
index: i,
|
|
147
|
+
nameKey,
|
|
148
|
+
nestedActiveTooltipIndex: currentTooltipIndex,
|
|
149
|
+
node: child,
|
|
150
|
+
}),
|
|
151
|
+
)
|
|
152
|
+
: null
|
|
153
|
+
let nodeValue: number
|
|
154
|
+
|
|
155
|
+
if (computedChildren && computedChildren.length) {
|
|
156
|
+
nodeValue = computedChildren.reduce(
|
|
157
|
+
(result: number, child: TreemapNode) => result + child.value,
|
|
158
|
+
0,
|
|
159
|
+
)
|
|
160
|
+
} else {
|
|
161
|
+
/* TODO need to verify dataKey */
|
|
162
|
+
const rawNodeValue = node[dataKey as string]
|
|
163
|
+
const numericValue = typeof rawNodeValue === "number" ? rawNodeValue : 0
|
|
164
|
+
nodeValue = isNan(numericValue) || numericValue <= 0 ? 0 : numericValue
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
...node,
|
|
169
|
+
children: computedChildren,
|
|
170
|
+
name: getValueByDataKey(
|
|
171
|
+
node as Record<string, unknown>,
|
|
172
|
+
nameKey as DataKey<unknown>,
|
|
173
|
+
"",
|
|
174
|
+
) as string,
|
|
175
|
+
[NODE_VALUE_KEY]: nodeValue,
|
|
176
|
+
depth,
|
|
177
|
+
index,
|
|
178
|
+
tooltipIndex: currentTooltipIndex,
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const filterRect = (node: TreemapNode): RectanglePosition => ({
|
|
183
|
+
height: node.height,
|
|
184
|
+
width: node.width,
|
|
185
|
+
x: node.x,
|
|
186
|
+
y: node.y,
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
const NEST_INDEX_HEIGHT = 30
|
|
190
|
+
|
|
191
|
+
const getTreemapRenderHeight = (height: number, type: Props["type"]) => {
|
|
192
|
+
if (type === "nest") {
|
|
193
|
+
return height - NEST_INDEX_HEIGHT
|
|
194
|
+
}
|
|
195
|
+
return height
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const insetRect = (rect: RectanglePosition, nodeInset: number): RectanglePosition => {
|
|
199
|
+
if (!Number.isFinite(nodeInset) || nodeInset <= 0) {
|
|
200
|
+
return rect
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const clampedPadding = Math.min(nodeInset, rect.width / 2, rect.height / 2)
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
x: rect.x + clampedPadding,
|
|
207
|
+
y: rect.y + clampedPadding,
|
|
208
|
+
width: Math.max(rect.width - clampedPadding * 2, 0),
|
|
209
|
+
height: Math.max(rect.height - clampedPadding * 2, 0),
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const applyGapToChildren = (
|
|
214
|
+
children: ReadonlyArray<TreemapNodeWithArea>,
|
|
215
|
+
parentRect: RectanglePosition,
|
|
216
|
+
nodeGap: number,
|
|
217
|
+
): ReadonlyArray<TreemapNodeWithArea> => {
|
|
218
|
+
if (!Number.isFinite(nodeGap) || nodeGap <= 0) {
|
|
219
|
+
return children
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const halfGap = nodeGap / 2
|
|
223
|
+
const parentRight = parentRect.x + parentRect.width
|
|
224
|
+
const parentBottom = parentRect.y + parentRect.height
|
|
225
|
+
|
|
226
|
+
return children.map((child) => {
|
|
227
|
+
const childRight = child.x + child.width
|
|
228
|
+
const childBottom = child.y + child.height
|
|
229
|
+
const leftInset = child.x > parentRect.x ? halfGap : 0
|
|
230
|
+
const rightInset = childRight < parentRight ? halfGap : 0
|
|
231
|
+
const topInset = child.y > parentRect.y ? halfGap : 0
|
|
232
|
+
const bottomInset = childBottom < parentBottom ? halfGap : 0
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
...child,
|
|
236
|
+
x: child.x + leftInset,
|
|
237
|
+
y: child.y + topInset,
|
|
238
|
+
width: Math.max(child.width - leftInset - rightInset, 0),
|
|
239
|
+
height: Math.max(child.height - topInset - bottomInset, 0),
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
type TreemapNodeWithArea = TreemapNode & { area: number }
|
|
245
|
+
|
|
246
|
+
/* Compute the area for each child based on value & scale. */
|
|
247
|
+
const getAreaOfChildren = (
|
|
248
|
+
children: ReadonlyArray<TreemapNode>,
|
|
249
|
+
areaValueRatio: number,
|
|
250
|
+
): ReadonlyArray<TreemapNodeWithArea> => {
|
|
251
|
+
const ratio = areaValueRatio < 0 ? 0 : areaValueRatio
|
|
252
|
+
|
|
253
|
+
return children.map((child: TreemapNode) => {
|
|
254
|
+
const area = child[NODE_VALUE_KEY] * ratio
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
...child,
|
|
258
|
+
area: isNan(area) || area <= 0 ? 0 : area,
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* Computes the score for the specified row, as the worst aspect ratio. */
|
|
264
|
+
const getWorstScore = (
|
|
265
|
+
row: AreaArray<TreemapNodeWithArea>,
|
|
266
|
+
parentSize: number,
|
|
267
|
+
aspectRatio: number,
|
|
268
|
+
): number => {
|
|
269
|
+
const parentArea = parentSize * parentSize
|
|
270
|
+
const rowArea = row.area * row.area
|
|
271
|
+
const { min, max } = row.reduce(
|
|
272
|
+
(result: { min: number; max: number }, child: TreemapNodeWithArea) => ({
|
|
273
|
+
max: Math.max(result.max, child.area),
|
|
274
|
+
min: Math.min(result.min, child.area),
|
|
275
|
+
}),
|
|
276
|
+
{ max: 0, min: Infinity },
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
return rowArea
|
|
280
|
+
? Math.max(
|
|
281
|
+
(parentArea * max * aspectRatio) / rowArea,
|
|
282
|
+
rowArea / (parentArea * min * aspectRatio),
|
|
283
|
+
)
|
|
284
|
+
: Infinity
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const horizontalPosition = (
|
|
288
|
+
row: AreaArray<TreemapNodeWithArea>,
|
|
289
|
+
parentSize: number,
|
|
290
|
+
parentRect: RectanglePosition,
|
|
291
|
+
isFlush: boolean,
|
|
292
|
+
): RectanglePosition => {
|
|
293
|
+
let rowHeight = parentSize ? Math.round(row.area / parentSize) : 0
|
|
294
|
+
|
|
295
|
+
if (isFlush || rowHeight > parentRect.height) {
|
|
296
|
+
rowHeight = parentRect.height
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let curX = parentRect.x
|
|
300
|
+
let child
|
|
301
|
+
for (let i = 0, len = row.length; i < len; i++) {
|
|
302
|
+
child = row[i]
|
|
303
|
+
if (child == null) {
|
|
304
|
+
continue
|
|
305
|
+
}
|
|
306
|
+
child.x = curX
|
|
307
|
+
child.y = parentRect.y
|
|
308
|
+
child.height = rowHeight
|
|
309
|
+
child.width = Math.min(
|
|
310
|
+
rowHeight ? Math.round(child.area / rowHeight) : 0,
|
|
311
|
+
parentRect.x + parentRect.width - curX,
|
|
312
|
+
)
|
|
313
|
+
curX += child.width
|
|
314
|
+
}
|
|
315
|
+
/* add the remain x to the last one of row */
|
|
316
|
+
if (child != null) {
|
|
317
|
+
child.width += parentRect.x + parentRect.width - curX
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
...parentRect,
|
|
322
|
+
height: parentRect.height - rowHeight,
|
|
323
|
+
y: parentRect.y + rowHeight,
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const verticalPosition = (
|
|
328
|
+
row: AreaArray<TreemapNodeWithArea>,
|
|
329
|
+
parentSize: number,
|
|
330
|
+
parentRect: RectanglePosition,
|
|
331
|
+
isFlush: boolean,
|
|
332
|
+
): RectanglePosition => {
|
|
333
|
+
let rowWidth = parentSize ? Math.round(row.area / parentSize) : 0
|
|
334
|
+
|
|
335
|
+
if (isFlush || rowWidth > parentRect.width) {
|
|
336
|
+
rowWidth = parentRect.width
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let curY = parentRect.y
|
|
340
|
+
let child
|
|
341
|
+
for (let i = 0, len = row.length; i < len; i++) {
|
|
342
|
+
child = row[i]
|
|
343
|
+
if (child == null) {
|
|
344
|
+
continue
|
|
345
|
+
}
|
|
346
|
+
child.x = parentRect.x
|
|
347
|
+
child.y = curY
|
|
348
|
+
child.width = rowWidth
|
|
349
|
+
child.height = Math.min(
|
|
350
|
+
rowWidth ? Math.round(child.area / rowWidth) : 0,
|
|
351
|
+
parentRect.y + parentRect.height - curY,
|
|
352
|
+
)
|
|
353
|
+
curY += child.height
|
|
354
|
+
}
|
|
355
|
+
if (child) {
|
|
356
|
+
child.height += parentRect.y + parentRect.height - curY
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
...parentRect,
|
|
361
|
+
width: parentRect.width - rowWidth,
|
|
362
|
+
x: parentRect.x + rowWidth,
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const position = (
|
|
367
|
+
row: AreaArray<TreemapNodeWithArea>,
|
|
368
|
+
parentSize: number,
|
|
369
|
+
parentRect: RectanglePosition,
|
|
370
|
+
isFlush: boolean,
|
|
371
|
+
): RectanglePosition => {
|
|
372
|
+
if (parentSize === parentRect.width) {
|
|
373
|
+
return horizontalPosition(row, parentSize, parentRect, isFlush)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return verticalPosition(row, parentSize, parentRect, isFlush)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
type AreaArray<T> = Array<T> & { area: number }
|
|
380
|
+
|
|
381
|
+
/* Recursively arranges the specified node's children into squarified rows. */
|
|
382
|
+
const squarify = (
|
|
383
|
+
node: TreemapNode,
|
|
384
|
+
aspectRatio: number,
|
|
385
|
+
nodeInset: number,
|
|
386
|
+
nodeGap: number,
|
|
387
|
+
): TreemapNode => {
|
|
388
|
+
const { children } = node
|
|
389
|
+
|
|
390
|
+
if (children && children.length) {
|
|
391
|
+
const layoutRect: RectanglePosition = insetRect(filterRect(node), nodeInset)
|
|
392
|
+
let rect: RectanglePosition = layoutRect
|
|
393
|
+
const row = [] as unknown as AreaArray<TreemapNodeWithArea>
|
|
394
|
+
let best = Infinity
|
|
395
|
+
let child, score
|
|
396
|
+
let size = Math.min(rect.width, rect.height)
|
|
397
|
+
const scaleChildren = getAreaOfChildren(
|
|
398
|
+
children,
|
|
399
|
+
(rect.width * rect.height) / node[NODE_VALUE_KEY],
|
|
400
|
+
)
|
|
401
|
+
const tempChildren = scaleChildren.slice()
|
|
402
|
+
|
|
403
|
+
/* why are we setting static properties on an array? */
|
|
404
|
+
row.area = 0
|
|
405
|
+
|
|
406
|
+
while (tempChildren.length > 0) {
|
|
407
|
+
;[child] = tempChildren
|
|
408
|
+
if (child == null) {
|
|
409
|
+
continue
|
|
410
|
+
}
|
|
411
|
+
/* row first */
|
|
412
|
+
row.push(child)
|
|
413
|
+
row.area += child.area
|
|
414
|
+
|
|
415
|
+
score = getWorstScore(row, size, aspectRatio)
|
|
416
|
+
if (score <= best) {
|
|
417
|
+
/* continue with this orientation */
|
|
418
|
+
tempChildren.shift()
|
|
419
|
+
best = score
|
|
420
|
+
} else {
|
|
421
|
+
/* abort, and try a different orientation */
|
|
422
|
+
row.area -= row.pop()?.area ?? 0
|
|
423
|
+
rect = position(row, size, rect, false)
|
|
424
|
+
size = Math.min(rect.width, rect.height)
|
|
425
|
+
row.length = row.area = 0
|
|
426
|
+
best = Infinity
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (row.length) {
|
|
431
|
+
rect = position(row, size, rect, true)
|
|
432
|
+
row.length = row.area = 0
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const childrenWithGaps = applyGapToChildren(scaleChildren, layoutRect, nodeGap)
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
...node,
|
|
439
|
+
children: childrenWithGaps.map((c) => squarify(c, aspectRatio, nodeInset, nodeGap)),
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return node
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export type TreemapContentType = JSX.Element | ((props: TreemapNode) => JSX.Element)
|
|
447
|
+
|
|
448
|
+
export interface Props<
|
|
449
|
+
DataPointType extends TreemapDataType = TreemapDataType,
|
|
450
|
+
DataValueType = unknown,
|
|
451
|
+
>
|
|
452
|
+
extends DataConsumer<DataPointType, DataValueType>, EventThrottlingProps {
|
|
453
|
+
/**
|
|
454
|
+
* The width of chart container.
|
|
455
|
+
* Can be a number or a percent string like "100%".
|
|
456
|
+
*/
|
|
457
|
+
width?: number | Percent
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* The height of chart container.
|
|
461
|
+
* Can be a number or a percent string like "100%".
|
|
462
|
+
*/
|
|
463
|
+
height?: number | Percent
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* The source data. Each element should be an object.
|
|
467
|
+
* The properties of each object represent the values of different data dimensions.
|
|
468
|
+
*
|
|
469
|
+
* Use the `dataKey` prop to specify which properties to use.
|
|
470
|
+
*
|
|
471
|
+
* If the `children` property is present on an element, it will be treated as a nested treemap.
|
|
472
|
+
*/
|
|
473
|
+
data?: ReadonlyArray<DataPointType>
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* @deprecated unused prop, doesn't do anything, use `key` instead
|
|
477
|
+
*/
|
|
478
|
+
animationId?: number
|
|
479
|
+
|
|
480
|
+
style?: JSX.CSSProperties
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* The treemap will try to keep every single rectangle's aspect ratio near the aspectRatio given.
|
|
484
|
+
* @default 1.618033988749895
|
|
485
|
+
*/
|
|
486
|
+
aspectRatio?: number
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* The inset between a parent node and its child nodes.
|
|
490
|
+
*
|
|
491
|
+
* Insets a parent node's available area before laying out its children.
|
|
492
|
+
* This creates space between a parent boundary and its child nodes.
|
|
493
|
+
* @default 0
|
|
494
|
+
*/
|
|
495
|
+
nodeInset?: number
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* The gap between the nodes.
|
|
499
|
+
*
|
|
500
|
+
* Adds spacing between sibling nodes at the same depth level.
|
|
501
|
+
* This does not inset children from their parent boundary.
|
|
502
|
+
* @default 0
|
|
503
|
+
*/
|
|
504
|
+
nodeGap?: number
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* If set to a function, the function will be called to render the content.
|
|
508
|
+
*/
|
|
509
|
+
content?: TreemapContentType
|
|
510
|
+
|
|
511
|
+
fill?: string
|
|
512
|
+
|
|
513
|
+
stroke?: string
|
|
514
|
+
|
|
515
|
+
class?: string
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Name represents each sector in the tooltip.
|
|
519
|
+
* This allows you to extract the name from the data:
|
|
520
|
+
*
|
|
521
|
+
* - `string`: the name of the field in the data object;
|
|
522
|
+
* - `number`: the index of the field in the data;
|
|
523
|
+
* - `function`: a function that receives the data object and returns the name.
|
|
524
|
+
*
|
|
525
|
+
* @defaultValue 'name'
|
|
526
|
+
*/
|
|
527
|
+
nameKey?: DataKey<DataPointType, DataValueType>
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Decides how to extract the value of this Treemap from the data:
|
|
531
|
+
* - `string`: the name of the field in the data object;
|
|
532
|
+
* - `number`: the index of the field in the data;
|
|
533
|
+
* - `function`: a function that receives the data object and returns the value of this Treemap.
|
|
534
|
+
*
|
|
535
|
+
* @defaultValue 'value'
|
|
536
|
+
*/
|
|
537
|
+
dataKey?: DataKey<DataPointType, DataValueType>
|
|
538
|
+
|
|
539
|
+
children?: JSX.Element
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* The type of treemap to render.
|
|
543
|
+
*
|
|
544
|
+
* - 'flat': Renders the entire treemap at once, with all leaf nodes visible.
|
|
545
|
+
* - 'nest': Renders an interactive, nested treemap. Clicking on a parent node will "zoom in" to show its children,
|
|
546
|
+
* and a breadcrumb navigation will be displayed to allow navigating back up the hierarchy.
|
|
547
|
+
*
|
|
548
|
+
* @default 'flat'
|
|
549
|
+
*/
|
|
550
|
+
type?: "flat" | "nest"
|
|
551
|
+
|
|
552
|
+
colorPanel?: ReadonlyArray<string>
|
|
553
|
+
|
|
554
|
+
/* customize nest index content */
|
|
555
|
+
nestIndexContent?: (item: TreemapNode, i: number) => JSX.Element
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* The customized event handler of animation start
|
|
559
|
+
*/
|
|
560
|
+
onAnimationStart?: () => void
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* The customized event handler of animation end
|
|
564
|
+
*/
|
|
565
|
+
onAnimationEnd?: () => void
|
|
566
|
+
|
|
567
|
+
onMouseEnter?: (node: TreemapNode, e: MouseEvent & { currentTarget: SVGGraphicsElement }) => void
|
|
568
|
+
|
|
569
|
+
onMouseLeave?: (node: TreemapNode, e: MouseEvent & { currentTarget: SVGGraphicsElement }) => void
|
|
570
|
+
|
|
571
|
+
onClick?: (node: TreemapNode) => void
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* If set false, animation of treemap will be disabled.
|
|
575
|
+
* If set "auto", animation is disabled during SSR and when the user prefers reduced motion.
|
|
576
|
+
* @default 'auto'
|
|
577
|
+
*/
|
|
578
|
+
isAnimationActive?: boolean | "auto"
|
|
579
|
+
|
|
580
|
+
isUpdateAnimationActive?: boolean | "auto"
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Specifies when the animation should begin, the unit of this option is ms.
|
|
584
|
+
* @default 0
|
|
585
|
+
*/
|
|
586
|
+
animationBegin?: number
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Specifies the duration of animation, the unit of this option is ms.
|
|
590
|
+
* @default 1500
|
|
591
|
+
*/
|
|
592
|
+
animationDuration?: AnimationDuration
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* The type of easing function.
|
|
596
|
+
* @default 'linear'
|
|
597
|
+
*/
|
|
598
|
+
animationEasing?: AnimationTiming
|
|
599
|
+
|
|
600
|
+
id?: string
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
type ContentItemProps = {
|
|
604
|
+
id: GraphicalItemId
|
|
605
|
+
content: TreemapContentType | undefined
|
|
606
|
+
nodeProps: TreemapNode
|
|
607
|
+
type: string
|
|
608
|
+
colorPanel: ReadonlyArray<string> | undefined
|
|
609
|
+
dataKey: DataKey<unknown>
|
|
610
|
+
onClick?: (e: MouseEvent & { currentTarget: SVGGraphicsElement }) => void
|
|
611
|
+
onMouseEnter?: (e: MouseEvent & { currentTarget: SVGGraphicsElement }) => void
|
|
612
|
+
onMouseLeave?: (e: MouseEvent & { currentTarget: SVGGraphicsElement }) => void
|
|
613
|
+
onMouseOver?: (e: MouseEvent & { currentTarget: SVGGraphicsElement }) => void
|
|
614
|
+
onMouseOut?: (e: MouseEvent & { currentTarget: SVGGraphicsElement }) => void
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/* eslint-disable solid/reactivity -- ContentItem reads are structural checks at setup; props are stable per render since component is created fresh per node */
|
|
618
|
+
function ContentItem(props: ContentItemProps): JSX.Element {
|
|
619
|
+
if (typeof props.content === "function") {
|
|
620
|
+
return (
|
|
621
|
+
<Layer
|
|
622
|
+
onMouseEnter={props.onMouseEnter}
|
|
623
|
+
onMouseOver={props.onMouseOver}
|
|
624
|
+
onMouseLeave={props.onMouseLeave}
|
|
625
|
+
onMouseOut={props.onMouseOut}
|
|
626
|
+
onClick={props.onClick}
|
|
627
|
+
>
|
|
628
|
+
{(props.content as (p: TreemapNode) => JSX.Element)(props.nodeProps)}
|
|
629
|
+
</Layer>
|
|
630
|
+
)
|
|
631
|
+
}
|
|
632
|
+
/* optimize default shape */
|
|
633
|
+
const { x, y, width, height, index } = untrack(() => ({
|
|
634
|
+
height: props.nodeProps.height,
|
|
635
|
+
index: props.nodeProps.index,
|
|
636
|
+
width: props.nodeProps.width,
|
|
637
|
+
x: props.nodeProps.x,
|
|
638
|
+
y: props.nodeProps.y,
|
|
639
|
+
}))
|
|
640
|
+
let arrow: JSX.Element | null = null
|
|
641
|
+
if (
|
|
642
|
+
width > 10 &&
|
|
643
|
+
height > 10 &&
|
|
644
|
+
props.nodeProps.children &&
|
|
645
|
+
props.type === "nest" &&
|
|
646
|
+
props.nodeProps.depth > 0
|
|
647
|
+
) {
|
|
648
|
+
arrow = (
|
|
649
|
+
<Polygon
|
|
650
|
+
points={[
|
|
651
|
+
{ x: x + 2, y: y + height / 2 },
|
|
652
|
+
{ x: x + 6, y: y + height / 2 + 3 },
|
|
653
|
+
{ x: x + 2, y: y + height / 2 + 6 },
|
|
654
|
+
]}
|
|
655
|
+
/>
|
|
656
|
+
)
|
|
657
|
+
}
|
|
658
|
+
let text: JSX.Element | null = null
|
|
659
|
+
const nameSize = getStringSize(props.nodeProps.name)
|
|
660
|
+
if (width > 20 && height > 20 && nameSize.width < width && nameSize.height < height) {
|
|
661
|
+
text = (
|
|
662
|
+
<text x={x + 8} y={y + height / 2 + 7} font-size={String(14)}>
|
|
663
|
+
{props.nodeProps.name}
|
|
664
|
+
</text>
|
|
665
|
+
)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const colors = props.colorPanel || COLOR_PANEL
|
|
669
|
+
const rectProps = untrack(() => omit(props.nodeProps, ["children"]))
|
|
670
|
+
return (
|
|
671
|
+
<g>
|
|
672
|
+
<Rectangle
|
|
673
|
+
fill={untrack(() =>
|
|
674
|
+
props.nodeProps.depth < 2 ? colors[index % colors.length] : "rgba(255,255,255,0)",
|
|
675
|
+
)}
|
|
676
|
+
stroke="#fff"
|
|
677
|
+
{...rectProps}
|
|
678
|
+
onMouseEnter={props.onMouseEnter}
|
|
679
|
+
onMouseOver={props.onMouseOver}
|
|
680
|
+
onMouseLeave={props.onMouseLeave}
|
|
681
|
+
onMouseOut={props.onMouseOut}
|
|
682
|
+
onClick={props.onClick}
|
|
683
|
+
data-recharts-item-index={props.nodeProps.tooltipIndex}
|
|
684
|
+
/>
|
|
685
|
+
{arrow}
|
|
686
|
+
{text}
|
|
687
|
+
</g>
|
|
688
|
+
)
|
|
689
|
+
}
|
|
690
|
+
/* eslint-enable solid/reactivity */
|
|
691
|
+
|
|
692
|
+
function ContentItemWithEvents(props: ContentItemProps): JSX.Element {
|
|
693
|
+
const newCtx = useOptionalChartState()
|
|
694
|
+
/* GOTCHA-005: createMemo so reactive nodeProps flow into coordinate. */
|
|
695
|
+
const activeCoordinate = createMemo<Coordinate>(() => ({
|
|
696
|
+
x: props.nodeProps.x + props.nodeProps.width / 2,
|
|
697
|
+
y: props.nodeProps.y + props.nodeProps.height / 2,
|
|
698
|
+
}))
|
|
699
|
+
|
|
700
|
+
/* GOTCHA-016-C: dedupe enter/over so user.hover only fires dispatch once. */
|
|
701
|
+
let entered = false
|
|
702
|
+
const fireEnter = () => {
|
|
703
|
+
if (entered) return
|
|
704
|
+
entered = true
|
|
705
|
+
const hoverPayload = {
|
|
706
|
+
active: true,
|
|
707
|
+
coordinate: activeCoordinate(),
|
|
708
|
+
dataKey: props.dataKey,
|
|
709
|
+
graphicalItemId: props.id,
|
|
710
|
+
index: props.nodeProps.tooltipIndex,
|
|
711
|
+
}
|
|
712
|
+
if (newCtx != null) setTooltipInteraction(newCtx.setState, "itemInteraction", "hover", hoverPayload)
|
|
713
|
+
}
|
|
714
|
+
const fireLeave = () => {
|
|
715
|
+
if (!entered) return
|
|
716
|
+
entered = false
|
|
717
|
+
/*
|
|
718
|
+
* clearing state on mouseLeaveItem causes re-rendering issues
|
|
719
|
+
* we don't actually want to do this for TreeMap - we clear state when we leave the entire chart instead
|
|
720
|
+
*/
|
|
721
|
+
}
|
|
722
|
+
const onClick = () => {
|
|
723
|
+
const clickPayload = {
|
|
724
|
+
active: true,
|
|
725
|
+
coordinate: activeCoordinate(),
|
|
726
|
+
dataKey: props.dataKey,
|
|
727
|
+
graphicalItemId: props.id,
|
|
728
|
+
index: props.nodeProps.tooltipIndex,
|
|
729
|
+
}
|
|
730
|
+
if (newCtx != null) setTooltipInteraction(newCtx.setState, "itemInteraction", "click", clickPayload)
|
|
731
|
+
}
|
|
732
|
+
return (
|
|
733
|
+
<ContentItem
|
|
734
|
+
{...props}
|
|
735
|
+
onMouseEnter={fireEnter}
|
|
736
|
+
onMouseOver={fireEnter}
|
|
737
|
+
onMouseLeave={fireLeave}
|
|
738
|
+
onMouseOut={fireLeave}
|
|
739
|
+
onClick={onClick}
|
|
740
|
+
/>
|
|
741
|
+
)
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function SetTreemapTooltipEntrySettings(props: {
|
|
745
|
+
dataKey: DataKey<unknown>
|
|
746
|
+
nameKey: DataKey<unknown>
|
|
747
|
+
stroke: string | undefined
|
|
748
|
+
fill: string | undefined
|
|
749
|
+
currentRoot: TreemapNode | undefined
|
|
750
|
+
id: GraphicalItemId
|
|
751
|
+
}): JSX.Element {
|
|
752
|
+
/* GOTCHA-005: createMemo so reactive props flow into the settings object. */
|
|
753
|
+
const tooltipEntrySettings = createMemo<TooltipPayloadConfiguration>(() => ({
|
|
754
|
+
dataDefinedOnItem: props.currentRoot,
|
|
755
|
+
getPosition: noop,
|
|
756
|
+
settings: {
|
|
757
|
+
color: props.fill,
|
|
758
|
+
dataKey: props.dataKey,
|
|
759
|
+
fill: props.fill,
|
|
760
|
+
graphicalItemId: props.id,
|
|
761
|
+
hide: false,
|
|
762
|
+
name: undefined,
|
|
763
|
+
nameKey: props.nameKey,
|
|
764
|
+
stroke: props.stroke,
|
|
765
|
+
strokeWidth: undefined,
|
|
766
|
+
type: undefined,
|
|
767
|
+
unit: "",
|
|
768
|
+
},
|
|
769
|
+
}))
|
|
770
|
+
return <SetTooltipEntrySettings tooltipEntrySettings={tooltipEntrySettings()} />
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/* Why is margin not a treemap prop? No clue. Probably it should be */
|
|
774
|
+
const defaultTreemapMargin: Margin = {
|
|
775
|
+
bottom: 0,
|
|
776
|
+
left: 0,
|
|
777
|
+
right: 0,
|
|
778
|
+
top: 0,
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
export const defaultTreeMapProps = {
|
|
782
|
+
animationBegin: 0,
|
|
783
|
+
animationDuration: 1500,
|
|
784
|
+
animationEasing: "linear",
|
|
785
|
+
aspectRatio: 0.5 * (1 + Math.sqrt(5)),
|
|
786
|
+
dataKey: "value",
|
|
787
|
+
isAnimationActive: "auto",
|
|
788
|
+
isUpdateAnimationActive: "auto",
|
|
789
|
+
nameKey: "name",
|
|
790
|
+
nodeGap: 0,
|
|
791
|
+
nodeInset: 0,
|
|
792
|
+
type: "flat",
|
|
793
|
+
...initialEventSettingsState,
|
|
794
|
+
} as const satisfies Partial<Props>
|
|
795
|
+
|
|
796
|
+
type InternalTreemapProps = RequiresDefaultProps<Props, typeof defaultTreeMapProps> & {
|
|
797
|
+
width: number
|
|
798
|
+
height: number
|
|
799
|
+
id: GraphicalItemId
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/* eslint-disable solid/reactivity -- itemProps.nodeProps destructure and treemapProps reads are stable at component setup; TreemapItem is created fresh per node */
|
|
803
|
+
function TreemapItem(itemProps: {
|
|
804
|
+
content: TreemapContentType | undefined
|
|
805
|
+
nodeProps: TreemapNode
|
|
806
|
+
isLeaf: boolean
|
|
807
|
+
treemapProps: InternalTreemapProps
|
|
808
|
+
onNestClick: (node: TreemapNode) => void
|
|
809
|
+
}): JSX.Element {
|
|
810
|
+
const { width, height, x, y } = itemProps.nodeProps
|
|
811
|
+
const translateX = -x - width
|
|
812
|
+
const translateY = 0
|
|
813
|
+
|
|
814
|
+
const onMouseEnter = (e: MouseEvent & { currentTarget: SVGGraphicsElement }) => {
|
|
815
|
+
if (
|
|
816
|
+
(itemProps.isLeaf || itemProps.treemapProps.type === "nest") &&
|
|
817
|
+
typeof itemProps.treemapProps.onMouseEnter === "function"
|
|
818
|
+
) {
|
|
819
|
+
itemProps.treemapProps.onMouseEnter(itemProps.nodeProps, e)
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const onMouseLeave = (e: MouseEvent & { currentTarget: SVGGraphicsElement }) => {
|
|
824
|
+
if (
|
|
825
|
+
(itemProps.isLeaf || itemProps.treemapProps.type === "nest") &&
|
|
826
|
+
typeof itemProps.treemapProps.onMouseLeave === "function"
|
|
827
|
+
) {
|
|
828
|
+
itemProps.treemapProps.onMouseLeave(itemProps.nodeProps, e)
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
const onClick = () => {
|
|
833
|
+
if (itemProps.treemapProps.type === "nest" && itemProps.nodeProps.depth > 0) {
|
|
834
|
+
itemProps.onNestClick(itemProps.nodeProps)
|
|
835
|
+
}
|
|
836
|
+
if (
|
|
837
|
+
(itemProps.isLeaf || itemProps.treemapProps.type === "nest") &&
|
|
838
|
+
typeof itemProps.treemapProps.onClick === "function"
|
|
839
|
+
) {
|
|
840
|
+
itemProps.treemapProps.onClick(itemProps.nodeProps)
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/* One object per item: an inline JSX object literal is a getter that re-spreads on every
|
|
845
|
+
`props.nodeProps` read downstream. ContentItem snapshots node props at setup, and
|
|
846
|
+
x/y/width/height above are setup constants, so a setup snapshot reads the same. */
|
|
847
|
+
const contentNodeProps = untrack(() => ({
|
|
848
|
+
...itemProps.nodeProps,
|
|
849
|
+
height,
|
|
850
|
+
isAnimationActive: itemProps.treemapProps.isAnimationActive,
|
|
851
|
+
isUpdateAnimationActive: !itemProps.treemapProps.isUpdateAnimationActive,
|
|
852
|
+
width,
|
|
853
|
+
x,
|
|
854
|
+
y,
|
|
855
|
+
}))
|
|
856
|
+
|
|
857
|
+
function handleAnimationEnd() {
|
|
858
|
+
if (typeof itemProps.treemapProps.onAnimationEnd === "function") {
|
|
859
|
+
itemProps.treemapProps.onAnimationEnd()
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function handleAnimationStart() {
|
|
864
|
+
if (typeof itemProps.treemapProps.onAnimationStart === "function") {
|
|
865
|
+
itemProps.treemapProps.onAnimationStart()
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
return (
|
|
870
|
+
<CSSTransitionAnimate
|
|
871
|
+
animationId={`treemap-${itemProps.nodeProps.tooltipIndex}`}
|
|
872
|
+
from={`translate(${translateX}px, ${translateY}px)`}
|
|
873
|
+
to="translate(0, 0)"
|
|
874
|
+
attributeName="transform"
|
|
875
|
+
begin={itemProps.treemapProps.animationBegin}
|
|
876
|
+
easing={extractCssEasing(itemProps.treemapProps.animationEasing)}
|
|
877
|
+
isActive={itemProps.treemapProps.isAnimationActive}
|
|
878
|
+
duration={itemProps.treemapProps.animationDuration}
|
|
879
|
+
onAnimationStart={handleAnimationStart}
|
|
880
|
+
onAnimationEnd={handleAnimationEnd}
|
|
881
|
+
>
|
|
882
|
+
{(style) => (
|
|
883
|
+
<Layer
|
|
884
|
+
onMouseEnter={onMouseEnter}
|
|
885
|
+
onMouseLeave={onMouseLeave}
|
|
886
|
+
onClick={onClick}
|
|
887
|
+
style={{ ...style(), "transform-origin": `${x} ${y}` }}
|
|
888
|
+
>
|
|
889
|
+
<ContentItemWithEvents
|
|
890
|
+
id={itemProps.treemapProps.id}
|
|
891
|
+
content={itemProps.content}
|
|
892
|
+
dataKey={itemProps.treemapProps.dataKey as DataKey<unknown>}
|
|
893
|
+
nodeProps={contentNodeProps}
|
|
894
|
+
type={itemProps.treemapProps.type}
|
|
895
|
+
colorPanel={itemProps.treemapProps.colorPanel}
|
|
896
|
+
/>
|
|
897
|
+
</Layer>
|
|
898
|
+
)}
|
|
899
|
+
</CSSTransitionAnimate>
|
|
900
|
+
)
|
|
901
|
+
}
|
|
902
|
+
/* eslint-enable solid/reactivity */
|
|
903
|
+
|
|
904
|
+
function TreemapWithState(props: InternalTreemapProps): JSX.Element {
|
|
905
|
+
const newCtx = useOptionalChartState()
|
|
906
|
+
|
|
907
|
+
const derivedTree = createMemo(() => {
|
|
908
|
+
const root: TreemapNode = computeNode({
|
|
909
|
+
dataKey: props.dataKey,
|
|
910
|
+
depth: 0,
|
|
911
|
+
index: 0,
|
|
912
|
+
nameKey: props.nameKey,
|
|
913
|
+
node: {
|
|
914
|
+
children: props.data,
|
|
915
|
+
height: getTreemapRenderHeight(props.height, props.type),
|
|
916
|
+
width: props.width,
|
|
917
|
+
x: 0,
|
|
918
|
+
y: 0,
|
|
919
|
+
} as unknown as TreemapNode,
|
|
920
|
+
})
|
|
921
|
+
const fRoot: TreemapNode = squarify(
|
|
922
|
+
root,
|
|
923
|
+
props.aspectRatio,
|
|
924
|
+
props.nodeInset,
|
|
925
|
+
props.nodeGap,
|
|
926
|
+
)
|
|
927
|
+
return { currentRoot: root, formatRoot: fRoot }
|
|
928
|
+
})
|
|
929
|
+
|
|
930
|
+
const [nest, setNest] = createSignal<{
|
|
931
|
+
currentRoot: TreemapNode
|
|
932
|
+
formatRoot: TreemapNode
|
|
933
|
+
nestIndex: Array<TreemapNode>
|
|
934
|
+
} | null>(null)
|
|
935
|
+
|
|
936
|
+
const formatRoot = createMemo(
|
|
937
|
+
() => nest()?.formatRoot ?? derivedTree().formatRoot,
|
|
938
|
+
)
|
|
939
|
+
const currentRoot = createMemo(
|
|
940
|
+
() => nest()?.currentRoot ?? derivedTree().currentRoot,
|
|
941
|
+
)
|
|
942
|
+
const nestIndex = createMemo(
|
|
943
|
+
() => nest()?.nestIndex ?? [derivedTree().currentRoot],
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
function handleClick(node: TreemapNode) {
|
|
947
|
+
if (props.type === "nest" && node.children) {
|
|
948
|
+
const root = computeNode({
|
|
949
|
+
depth: 0,
|
|
950
|
+
node: {
|
|
951
|
+
...node,
|
|
952
|
+
height: getTreemapRenderHeight(props.height, props.type),
|
|
953
|
+
width: props.width,
|
|
954
|
+
x: 0,
|
|
955
|
+
y: 0,
|
|
956
|
+
},
|
|
957
|
+
index: 0,
|
|
958
|
+
dataKey: props.dataKey,
|
|
959
|
+
nameKey: props.nameKey,
|
|
960
|
+
/* with Treemap nesting, should this continue nesting the index or start from empty string? */
|
|
961
|
+
nestedActiveTooltipIndex: node.tooltipIndex,
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
const fRoot = squarify(root, props.aspectRatio, props.nodeInset, props.nodeGap)
|
|
965
|
+
setNest({
|
|
966
|
+
currentRoot: root,
|
|
967
|
+
formatRoot: fRoot,
|
|
968
|
+
nestIndex: [...nestIndex(), node],
|
|
969
|
+
})
|
|
970
|
+
}
|
|
971
|
+
if (props.onClick) {
|
|
972
|
+
props.onClick(node)
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function handleNestIndex(node: TreemapNode, i: number) {
|
|
977
|
+
const root = computeNode({
|
|
978
|
+
depth: 0,
|
|
979
|
+
node: {
|
|
980
|
+
...node,
|
|
981
|
+
height: getTreemapRenderHeight(props.height, props.type),
|
|
982
|
+
width: props.width,
|
|
983
|
+
x: 0,
|
|
984
|
+
y: 0,
|
|
985
|
+
},
|
|
986
|
+
index: 0,
|
|
987
|
+
dataKey: props.dataKey,
|
|
988
|
+
nameKey: props.nameKey,
|
|
989
|
+
/* with Treemap nesting, should this continue nesting the index or start from empty string? */
|
|
990
|
+
nestedActiveTooltipIndex: node.tooltipIndex,
|
|
991
|
+
})
|
|
992
|
+
|
|
993
|
+
const fRoot = squarify(root, props.aspectRatio, props.nodeInset, props.nodeGap)
|
|
994
|
+
setNest({
|
|
995
|
+
currentRoot: node,
|
|
996
|
+
formatRoot: fRoot,
|
|
997
|
+
nestIndex: nestIndex().slice(0, i + 1),
|
|
998
|
+
})
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
/* The chart's SVG props are the same for every node; filter the props view once. */
|
|
1002
|
+
const chartSvgProps = createMemo(() => svgPropertiesNoEvents(props))
|
|
1003
|
+
|
|
1004
|
+
function renderNode(root: TreemapNode, node: TreemapNode): JSX.Element | null {
|
|
1005
|
+
const nodeProps = untrack(() => ({ ...chartSvgProps(), ...node, root }))
|
|
1006
|
+
const isLeaf = node.children == null || node.children.length === 0
|
|
1007
|
+
|
|
1008
|
+
const curRoot = untrack(() => currentRoot())
|
|
1009
|
+
const isCurrentRootChild = (curRoot?.children || []).filter(
|
|
1010
|
+
(item: TreemapNode) => item.depth === node.depth && item.name === node.name,
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
if (isCurrentRootChild.length === 0 && root.depth && props.type === "nest") {
|
|
1014
|
+
return null
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
return (
|
|
1018
|
+
<Layer class={`recharts-treemap-depth-${node.depth}`}>
|
|
1019
|
+
<TreemapItem
|
|
1020
|
+
isLeaf={isLeaf}
|
|
1021
|
+
content={props.content}
|
|
1022
|
+
nodeProps={nodeProps}
|
|
1023
|
+
treemapProps={props}
|
|
1024
|
+
onNestClick={handleClick}
|
|
1025
|
+
/>
|
|
1026
|
+
<Show when={node.children && node.children.length}>
|
|
1027
|
+
<For each={node.children as TreemapNode[]}>
|
|
1028
|
+
{(child: TreemapNode) => renderNode(node, child)}
|
|
1029
|
+
</For>
|
|
1030
|
+
</Show>
|
|
1031
|
+
</Layer>
|
|
1032
|
+
)
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function renderAllNodes(): JSX.Element | null {
|
|
1036
|
+
const fRoot = formatRoot()
|
|
1037
|
+
|
|
1038
|
+
if (fRoot == null) {
|
|
1039
|
+
return null
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
return renderNode(fRoot, fRoot)
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function renderNestIndexContent(item: TreemapNode, i: number): JSX.Element | string {
|
|
1046
|
+
if (typeof props.nestIndexContent === "function") {
|
|
1047
|
+
return props.nestIndexContent(item, i)
|
|
1048
|
+
}
|
|
1049
|
+
/* TODO need to verify nameKey type */
|
|
1050
|
+
const rawName = get(item, props.nameKey as string, "root")
|
|
1051
|
+
return typeof rawName === "string" ? rawName : "root"
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/* render nest treemap */
|
|
1055
|
+
function renderNestIndex(): JSX.Element {
|
|
1056
|
+
return (
|
|
1057
|
+
<div
|
|
1058
|
+
class="recharts-treemap-nest-index-wrapper"
|
|
1059
|
+
style={{ "margin-top": "8px", "text-align": "center" }}
|
|
1060
|
+
>
|
|
1061
|
+
<For each={nestIndex()}>
|
|
1062
|
+
{(item: TreemapNode, i) => (
|
|
1063
|
+
<div
|
|
1064
|
+
onClick={() => handleNestIndex(item, i())}
|
|
1065
|
+
class="recharts-treemap-nest-index-box"
|
|
1066
|
+
style={{
|
|
1067
|
+
background: "#000",
|
|
1068
|
+
color: "#fff",
|
|
1069
|
+
cursor: "pointer",
|
|
1070
|
+
display: "inline-block",
|
|
1071
|
+
"margin-right": "3px",
|
|
1072
|
+
padding: "0 7px",
|
|
1073
|
+
}}
|
|
1074
|
+
>
|
|
1075
|
+
{renderNestIndexContent(item, i())}
|
|
1076
|
+
</div>
|
|
1077
|
+
)}
|
|
1078
|
+
</For>
|
|
1079
|
+
</div>
|
|
1080
|
+
)
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
function handleTouchMove(e: TouchEvent) {
|
|
1084
|
+
const touchEvent = e.touches[0]
|
|
1085
|
+
if (touchEvent == null) {
|
|
1086
|
+
return
|
|
1087
|
+
}
|
|
1088
|
+
const target = document.elementFromPoint(touchEvent.clientX, touchEvent.clientY)
|
|
1089
|
+
if (target == null || typeof target.getAttribute !== "function" || formatRoot() == null) {
|
|
1090
|
+
return
|
|
1091
|
+
}
|
|
1092
|
+
const itemIndex = target.getAttribute("data-recharts-item-index")
|
|
1093
|
+
const activeNode: unknown = treemapPayloadSearcher(formatRoot(), itemIndex)
|
|
1094
|
+
if (isTreemapNode(activeNode) === false) {
|
|
1095
|
+
return
|
|
1096
|
+
}
|
|
1097
|
+
const treemapNode = activeNode as TreemapNode
|
|
1098
|
+
|
|
1099
|
+
const activeCoordinate = {
|
|
1100
|
+
x: treemapNode.x + treemapNode.width / 2,
|
|
1101
|
+
y: treemapNode.y + treemapNode.height / 2,
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const itemPayload = {
|
|
1105
|
+
active: true,
|
|
1106
|
+
coordinate: activeCoordinate,
|
|
1107
|
+
dataKey: props.dataKey as DataKey<unknown>,
|
|
1108
|
+
graphicalItemId: props.id,
|
|
1109
|
+
index: itemIndex,
|
|
1110
|
+
}
|
|
1111
|
+
if (newCtx != null) setTooltipInteraction(newCtx.setState, "itemInteraction", "hover", itemPayload)
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
const attrs = () => svgPropertiesNoEvents(props)
|
|
1115
|
+
|
|
1116
|
+
return (
|
|
1117
|
+
<>
|
|
1118
|
+
<SetTreemapTooltipEntrySettings
|
|
1119
|
+
dataKey={props.dataKey as DataKey<unknown>}
|
|
1120
|
+
nameKey={props.nameKey as DataKey<unknown>}
|
|
1121
|
+
stroke={props.stroke}
|
|
1122
|
+
fill={props.fill}
|
|
1123
|
+
currentRoot={currentRoot()}
|
|
1124
|
+
id={props.id}
|
|
1125
|
+
/>
|
|
1126
|
+
<Surface
|
|
1127
|
+
{...attrs()}
|
|
1128
|
+
width={props.width}
|
|
1129
|
+
height={getTreemapRenderHeight(props.height, props.type)}
|
|
1130
|
+
onTouchMove={handleTouchMove}
|
|
1131
|
+
>
|
|
1132
|
+
{renderAllNodes()}
|
|
1133
|
+
{props.children}
|
|
1134
|
+
</Surface>
|
|
1135
|
+
<Show when={props.type === "nest"}>{renderNestIndex()}</Show>
|
|
1136
|
+
</>
|
|
1137
|
+
)
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
function TreemapDispatchInject(
|
|
1141
|
+
props: RequiresDefaultProps<Props, typeof defaultTreeMapProps> & { children?: JSX.Element },
|
|
1142
|
+
): JSX.Element | null {
|
|
1143
|
+
/* width/height arrive after layout dispatch; gate on Show so the body
|
|
1144
|
+
re-evaluates once dimensions populate. See GOTCHA-011. */
|
|
1145
|
+
const width = createMemo(() => useChartWidth())
|
|
1146
|
+
const height = createMemo(() => useChartHeight())
|
|
1147
|
+
const hasSize = createMemo(
|
|
1148
|
+
() => isPositiveNumber(width()) && isPositiveNumber(height()),
|
|
1149
|
+
)
|
|
1150
|
+
const [childrenSplit, restProps] = splitProps(props, ["children"])
|
|
1151
|
+
return (
|
|
1152
|
+
<Show when={hasSize()}>
|
|
1153
|
+
<RegisterGraphicalItemId id={restProps.id} type="treemap">
|
|
1154
|
+
{(id) => (
|
|
1155
|
+
<TreemapWithState
|
|
1156
|
+
{...restProps}
|
|
1157
|
+
id={id}
|
|
1158
|
+
width={width() as number}
|
|
1159
|
+
height={height() as number}
|
|
1160
|
+
>
|
|
1161
|
+
{childrenSplit.children}
|
|
1162
|
+
</TreemapWithState>
|
|
1163
|
+
)}
|
|
1164
|
+
</RegisterGraphicalItemId>
|
|
1165
|
+
</Show>
|
|
1166
|
+
)
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
/**
|
|
1170
|
+
* The Treemap chart is used to visualize hierarchical data using nested rectangles.
|
|
1171
|
+
*
|
|
1172
|
+
* @consumes ResponsiveContainerContext
|
|
1173
|
+
* @provides TooltipEntrySettings
|
|
1174
|
+
*/
|
|
1175
|
+
export function Treemap(outsideProps: Props): JSX.Element {
|
|
1176
|
+
/* GOTCHA-005-B: split children to avoid spread-induced enumeration that would
|
|
1177
|
+
instantiate user JSX (Tooltip, etc.) under outer owner. */
|
|
1178
|
+
const [childrenSplit, restProps] = splitProps(outsideProps, ["children"])
|
|
1179
|
+
const props = resolveDefaultProps(restProps, defaultTreeMapProps)
|
|
1180
|
+
|
|
1181
|
+
return (
|
|
1182
|
+
<RechartsStateProvider
|
|
1183
|
+
preloadedState={{
|
|
1184
|
+
layout: untrack(() =>
|
|
1185
|
+
createInitialLayoutState({ height: props.height, margin: defaultTreemapMargin, width: props.width }),
|
|
1186
|
+
),
|
|
1187
|
+
options: chartOptions,
|
|
1188
|
+
}}
|
|
1189
|
+
>
|
|
1190
|
+
<ReportChartMargin margin={defaultTreemapMargin} />
|
|
1191
|
+
<ReportEventSettings
|
|
1192
|
+
throttleDelay={props.throttleDelay}
|
|
1193
|
+
throttledEvents={props.throttledEvents}
|
|
1194
|
+
/>
|
|
1195
|
+
<RechartsWrapper
|
|
1196
|
+
dispatchTouchEvents={false}
|
|
1197
|
+
class={props.class}
|
|
1198
|
+
style={props.style}
|
|
1199
|
+
width={props.width}
|
|
1200
|
+
height={props.height}
|
|
1201
|
+
/*
|
|
1202
|
+
* Treemap has a bug where it doesn't include strokeWidth in its dimension calculation
|
|
1203
|
+
* which makes the actual chart exactly {strokeWidth} larger than asked for.
|
|
1204
|
+
* It's not a huge deal usually, but it makes the responsive option cycle infinitely.
|
|
1205
|
+
*/
|
|
1206
|
+
responsive={false}
|
|
1207
|
+
onMouseEnter={undefined}
|
|
1208
|
+
onMouseLeave={undefined}
|
|
1209
|
+
onClick={undefined}
|
|
1210
|
+
onMouseMove={undefined}
|
|
1211
|
+
onMouseDown={undefined}
|
|
1212
|
+
onMouseUp={undefined}
|
|
1213
|
+
onContextMenu={undefined}
|
|
1214
|
+
onDoubleClick={undefined}
|
|
1215
|
+
onTouchStart={undefined}
|
|
1216
|
+
onTouchMove={undefined}
|
|
1217
|
+
onTouchEnd={undefined}
|
|
1218
|
+
>
|
|
1219
|
+
<TreemapDispatchInject {...props}>{childrenSplit.children}</TreemapDispatchInject>
|
|
1220
|
+
</RechartsWrapper>
|
|
1221
|
+
</RechartsStateProvider>
|
|
1222
|
+
)
|
|
1223
|
+
}
|