solid-recharts 0.2.0 → 0.2.1

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.
Files changed (82) hide show
  1. package/package.json +5 -3
  2. package/src/cartesian/Area.tsx +40 -55
  3. package/src/cartesian/Bar.tsx +66 -58
  4. package/src/cartesian/BarStack.tsx +5 -2
  5. package/src/cartesian/Brush.tsx +1 -1
  6. package/src/cartesian/Funnel.tsx +15 -19
  7. package/src/cartesian/Line.tsx +34 -51
  8. package/src/cartesian/LineDrawShape.tsx +8 -6
  9. package/src/cartesian/ReferenceArea.tsx +25 -35
  10. package/src/cartesian/ReferenceDot.tsx +24 -34
  11. package/src/cartesian/ReferenceLine.tsx +25 -34
  12. package/src/cartesian/Scatter.tsx +25 -37
  13. package/src/cartesian/XAxis.tsx +54 -67
  14. package/src/cartesian/YAxis.tsx +53 -66
  15. package/src/cartesian/ZAxis.tsx +27 -40
  16. package/src/chart/SunburstChart.tsx +1 -1
  17. package/src/component/ActivePoints.tsx +0 -9
  18. package/src/component/Dots.tsx +7 -3
  19. package/src/context/ErrorBarContext.tsx +15 -24
  20. package/src/polar/Pie.tsx +36 -48
  21. package/src/polar/PolarAngleAxis.tsx +41 -50
  22. package/src/polar/PolarRadiusAxis.tsx +40 -48
  23. package/src/polar/Radar.tsx +22 -34
  24. package/src/polar/RadialBar.tsx +21 -39
  25. package/src/shape/Curve.tsx +1 -1
  26. package/src/shape/Symbols.tsx +1 -1
  27. package/src/state/SetLegendPayload.ts +19 -27
  28. package/src/state/SetTooltipEntrySettings.tsx +9 -26
  29. package/src/state/cartesianAxisSlice.ts +40 -60
  30. package/src/state/createCollectionActions.ts +51 -0
  31. package/src/state/createSelector.ts +97 -0
  32. package/src/state/externalEventsMiddleware.ts +2 -11
  33. package/src/state/graphicalItemsSlice.ts +27 -40
  34. package/src/state/isEventThrottled.ts +12 -0
  35. package/src/state/keyboardEventsMiddleware.ts +2 -8
  36. package/src/state/layoutSlice.ts +8 -4
  37. package/src/state/legendSlice.ts +24 -26
  38. package/src/state/mouseEventsMiddleware.ts +2 -13
  39. package/src/state/patched.ts +19 -0
  40. package/src/state/referenceElementsSlice.ts +40 -31
  41. package/src/state/registerInStore.ts +68 -0
  42. package/src/state/selectors/areaSelectors.ts +13 -36
  43. package/src/state/selectors/arrayEqualityCheck.ts +32 -7
  44. package/src/state/selectors/axisSelectors.ts +215 -169
  45. package/src/state/selectors/barSelectors.ts +8 -36
  46. package/src/state/selectors/combiners/combineConfiguredScale.ts +1 -1
  47. package/src/state/selectors/combiners/combineRealScaleType.ts +1 -1
  48. package/src/state/selectors/dataSelectors.ts +35 -16
  49. package/src/state/selectors/numberDomainEqualityCheck.ts +16 -8
  50. package/src/state/selectors/selectAxisForGraphicalItem.ts +52 -0
  51. package/src/state/tooltipSlice.ts +93 -64
  52. package/src/util/ActiveShapeUtils.tsx +0 -7
  53. package/src/util/BarUtils.tsx +27 -22
  54. package/src/util/ChartUtils.ts +69 -25
  55. package/src/util/propsAreEqual.ts +3 -33
  56. package/src/util/stacks/stackTypes.ts +1 -1
  57. package/src/util/types.ts +1 -1
  58. package/test/cartesian/CartesianAxis.spec.tsx +1 -1
  59. package/test/cartesian/GraphicalItemClipPath.scale-behavior.spec.tsx +1 -1
  60. package/test/cartesian/ReferenceLine/getEndPoints.spec.ts +1 -1
  61. package/test/cartesian/XAxis/XAxis.timescale.spec.tsx +1 -1
  62. package/test/helper/mockAxes.ts +1 -1
  63. package/test/shape/Curve.spec.tsx +1 -1
  64. package/test/state/createCollectionActions.spec.ts +87 -0
  65. package/test/state/createSelector.spec.ts +158 -0
  66. package/test/state/registerInStore.spec.tsx +101 -0
  67. package/test/state/selectors/combiners/combineConfiguredScale.spec.ts +1 -1
  68. package/test/state/selectors/combiners/combineRealScaleType.spec.ts +1 -1
  69. package/test/state/selectors/selectorMemoization.spec.tsx +136 -0
  70. package/test/util/CartesianUtils/CartesianUtils.spec.ts +1 -1
  71. package/test/util/ChartUtils/ChartUtils.spec.ts +1 -1
  72. package/test/util/ChartUtils/getCateCoordinateOfLine.spec.ts +14 -0
  73. package/test/util/ChartUtils/getStackedData.spec.ts +1 -1
  74. package/test/util/ChartUtils/truncateByDomain.spec.ts +1 -1
  75. package/test/util/scale/createCategoricalInverse.spec.ts +1 -1
  76. package/test-results/.last-run.json +4 -0
  77. package/src/state/reduxDevtoolsJsonStringifyReplacer.ts +0 -15
  78. package/src/util/axisPropsAreEqual.ts +0 -42
  79. package/src/util/resolveDefaultProps.tsx +0 -8
  80. package/src/util/typedDataKey.ts +0 -8
  81. package/test/state/reduxDevtoolsJsonStringifyReplacer.spec.ts +0 -25
  82. package/test/util/axisPropsAreEqual.spec.ts +0 -93
@@ -4,14 +4,14 @@
4
4
  // settings into the cartesianAxis slice so Scatter can size its points by the z
5
5
  // value. Solid deviations: useLayoutEffect/prevRef → createEffect add/replace +
6
6
  // onCleanup remove (the XAxis/YAxis idiom); defaults via merge.
7
- import { createEffect, merge } from "solid-js";
7
+ import { merge } from "solid-js";
8
8
  import { useAppStore } from "../state/hooks";
9
9
  import { AxisId, ZAxisSettings } from "../state/cartesianAxisSlice";
10
10
  import { implicitZAxis } from "../state/selectors/axisSelectors";
11
11
  import { AxisDomain, DataKey, ScaleType } from "../util/types";
12
12
  import { AxisRange } from "../state/selectors/axisSelectors";
13
13
  import { CustomScaleDefinition } from "../util/scale/CustomScaleDefinition";
14
- import { unregisterOnCleanup } from "../state/unregisterOnCleanup";
14
+ import { registerInStore } from "../state/registerInStore";
15
15
 
16
16
  export interface ZAxisProps<DataPointType = unknown, DataValueType = unknown> {
17
17
  zAxisId?: AxisId;
@@ -37,44 +37,31 @@ export function ZAxis<DataPointType = unknown, DataValueType = unknown>(
37
37
  const store = useAppStore();
38
38
  const merged = merge(zAxisDefaultProps, props);
39
39
 
40
- let prev: ZAxisSettings | null = null;
41
-
42
- createEffect(
43
- (): ZAxisSettings => ({
44
- id: merged.zAxisId,
45
- type: merged.type,
46
- dataKey: merged.dataKey,
47
- range: merged.range,
48
- domain: merged.domain,
49
- scale: merged.scale,
50
- // recharts registers the raw prop name/unit (undefined when unset); the
51
- // implicit "" is only the fallback when NO ZAxis exists (selectZAxisSettings).
52
- name: merged.name,
53
- unit: merged.unit,
54
- allowDataOverflow: implicitZAxis.allowDataOverflow,
55
- allowDuplicatedCategory: implicitZAxis.allowDuplicatedCategory,
56
- includeHidden: implicitZAxis.includeHidden,
57
- reversed: implicitZAxis.reversed,
58
- }),
59
- (settings) => {
60
- if (store == null) {
61
- return;
62
- }
63
- if (prev === null) {
64
- store.actions.cartesianAxis.addZAxis(settings);
65
- } else if (prev !== settings) {
66
- store.actions.cartesianAxis.replaceZAxis({ prev, next: settings });
67
- }
68
- prev = settings;
69
- },
70
- );
71
-
72
- unregisterOnCleanup(() => {
73
- if (prev && store != null) {
74
- store.actions.cartesianAxis.removeZAxis(prev);
75
- prev = null;
76
- }
77
- });
40
+ if (store != null) {
41
+ registerInStore<ZAxisSettings>(
42
+ () => ({
43
+ id: merged.zAxisId,
44
+ type: merged.type,
45
+ dataKey: merged.dataKey,
46
+ range: merged.range,
47
+ domain: merged.domain,
48
+ scale: merged.scale,
49
+ // recharts registers the raw prop name/unit (undefined when unset); the
50
+ // implicit "" is only the fallback when NO ZAxis exists (selectZAxisSettings).
51
+ name: merged.name,
52
+ unit: merged.unit,
53
+ allowDataOverflow: implicitZAxis.allowDataOverflow,
54
+ allowDuplicatedCategory: implicitZAxis.allowDuplicatedCategory,
55
+ includeHidden: implicitZAxis.includeHidden,
56
+ reversed: implicitZAxis.reversed,
57
+ }),
58
+ {
59
+ add: (next) => store.actions.cartesianAxis.addZAxis(next),
60
+ replace: (prev, next) => store.actions.cartesianAxis.replaceZAxis({ prev, next }),
61
+ remove: (prev) => store.actions.cartesianAxis.removeZAxis(prev),
62
+ },
63
+ );
64
+ }
78
65
 
79
66
  return null;
80
67
  }
@@ -14,7 +14,7 @@
14
14
  // DEFERRED: event throttling nuances.
15
15
  import { createMemo, For, merge, Show } from "solid-js";
16
16
  import type { JSX } from "@solidjs/web";
17
- import { scaleLinear } from "victory-vendor/d3-scale";
17
+ import { scaleLinear } from "d3-scale";
18
18
  import { Surface } from "../container/Surface";
19
19
  import { Layer } from "../container/Layer";
20
20
  import { Sector } from "../shape/Sector";
@@ -125,12 +125,3 @@ export function renderActivePointsLayerFromState(
125
125
  </g>
126
126
  );
127
127
  }
128
-
129
- /** @deprecated Use computeActivePointState + renderActivePointsLayerFromState */
130
- export function renderActivePointsLayer(args: ActivePointsLayerArgs): JSX.Element | null {
131
- const state = computeActivePointState(args);
132
- if (state == null) {
133
- return null;
134
- }
135
- return renderActivePointsLayerFromState(state, args);
136
- }
@@ -109,11 +109,15 @@ export function Dots(props: DotsProps): JSX.Element {
109
109
  return (
110
110
  <Show when={shouldRenderDots(props.points, props.dot)}>
111
111
  <Layer class={props.class} clipPath={layerClip()}>
112
- <For each={props.points}>
112
+ {/* keyed={false} (index-keyed): during animation `points` is a fresh
113
+ array with fresh point objects every frame; reference-keyed <For>
114
+ would dispose+recreate every dot's DOM node per frame. Index-keying
115
+ reuses the rows and only updates cx/cy reactively. */}
116
+ <For each={props.points} keyed={false}>
113
117
  {(entry, i) => (
114
118
  <DotRenderer
115
- entry={entry}
116
- index={i()}
119
+ entry={entry()}
120
+ index={i}
117
121
  dot={props.dot}
118
122
  baseProps={props.baseProps}
119
123
  dataKey={props.dataKey}
@@ -7,7 +7,7 @@
7
7
  // Solid deviations: context value carries an `Accessor` (Solid context is fixed
8
8
  // at provide-time; the accessor keeps `data` reactive). useEffect add/replace +
9
9
  // cleanup remove → createEffect + onCleanup. useGraphicalItemId provides the id.
10
- import { createContext, createEffect, useContext } from "solid-js";
10
+ import { createContext, useContext } from "solid-js";
11
11
  import type { Accessor } from "solid-js";
12
12
  import type { JSX } from "@solidjs/web";
13
13
  import { AxisId } from "../state/cartesianAxisSlice";
@@ -15,7 +15,7 @@ import type { ErrorBarDataPointFormatter } from "../cartesian/ErrorBar";
15
15
  import type { ErrorBarsSettings } from "../state/errorBarSlice";
16
16
  import { useAppStore } from "../state/hooks";
17
17
  import { useGraphicalItemId } from "./RegisterGraphicalItemId";
18
- import { unregisterOnCleanup } from "../state/unregisterOnCleanup";
18
+ import { registerInStore } from "../state/registerInStore";
19
19
 
20
20
  export interface ErrorBarContextValue {
21
21
  data: ReadonlyArray<unknown> | undefined;
@@ -57,29 +57,20 @@ export const useErrorBarContext = (): Accessor<ErrorBarContextValue> => useConte
57
57
  export function ReportErrorBarSettings(props: ErrorBarsSettings): JSX.Element {
58
58
  const store = useAppStore();
59
59
  const graphicalItemId = useGraphicalItemId();
60
- let prev: ErrorBarsSettings | null = null;
61
60
 
62
- createEffect(
63
- (): ErrorBarsSettings => ({ dataKey: props.dataKey, direction: props.direction }),
64
- (settings) => {
65
- if (store == null || graphicalItemId == null) {
66
- return;
67
- }
68
- if (prev === null) {
69
- store.actions.errorBars.addErrorBar({ itemId: graphicalItemId, errorBar: settings });
70
- } else if (prev !== settings) {
71
- store.actions.errorBars.replaceErrorBar({ itemId: graphicalItemId, prev, next: settings });
72
- }
73
- prev = settings;
74
- },
75
- );
76
-
77
- unregisterOnCleanup(() => {
78
- if (prev != null && store != null && graphicalItemId != null) {
79
- store.actions.errorBars.removeErrorBar({ itemId: graphicalItemId, errorBar: prev });
80
- prev = null;
81
- }
82
- });
61
+ if (store != null && graphicalItemId != null) {
62
+ registerInStore<ErrorBarsSettings>(
63
+ () => ({ dataKey: props.dataKey, direction: props.direction }),
64
+ {
65
+ add: (next) =>
66
+ store.actions.errorBars.addErrorBar({ itemId: graphicalItemId, errorBar: next }),
67
+ replace: (prev, next) =>
68
+ store.actions.errorBars.replaceErrorBar({ itemId: graphicalItemId, prev, next }),
69
+ remove: (prev) =>
70
+ store.actions.errorBars.removeErrorBar({ itemId: graphicalItemId, errorBar: prev }),
71
+ },
72
+ );
73
+ }
83
74
 
84
75
  return null;
85
76
  }
package/src/polar/Pie.tsx CHANGED
@@ -81,7 +81,7 @@ import { ZIndexLayer } from "../zIndex/ZIndexLayer";
81
81
  import { getMaxRadius, polarToCartesian } from "../util/PolarUtils";
82
82
  import { getPercentValue, interpolate, isNumber, isNumOrStr, mathSign } from "../util/DataUtils";
83
83
  import { getValueByDataKey } from "../util/ChartUtils";
84
- import { unregisterOnCleanup } from "../state/unregisterOnCleanup";
84
+ import { registerInStore } from "../state/registerInStore";
85
85
 
86
86
  export type PieCoordinate = {
87
87
  cx: number;
@@ -583,53 +583,41 @@ export function Pie<DataPointType = Record<string, unknown>, DataValueType = unk
583
583
  const merged = merge(pieDefaultProps, props);
584
584
  const id = useUniqueId("recharts-pie", props.id);
585
585
 
586
- let prev: PieSettings | null = null;
587
-
588
- createEffect(
589
- (): PieSettings => ({
590
- id,
591
- type: "pie",
592
- data: merged.data,
593
- dataKey: merged.dataKey,
594
- hide: merged.hide,
595
- angleAxisId: merged.angleAxisId ?? 0,
596
- radiusAxisId: merged.radiusAxisId ?? 0,
597
- name: merged.name,
598
- nameKey: merged.nameKey,
599
- tooltipType: merged.tooltipType,
600
- legendType: merged.legendType,
601
- fill: merged.fill,
602
- stroke: merged.stroke,
603
- cx: merged.cx,
604
- cy: merged.cy,
605
- startAngle: merged.startAngle,
606
- endAngle: merged.endAngle,
607
- paddingAngle: merged.paddingAngle,
608
- minAngle: merged.minAngle,
609
- innerRadius: merged.innerRadius,
610
- outerRadius: merged.outerRadius,
611
- cornerRadius: merged.cornerRadius,
612
- maxRadius: merged.maxRadius,
613
- }),
614
- (settings) => {
615
- if (store == null) {
616
- return;
617
- }
618
- if (prev === null) {
619
- store.actions.graphicalItems.addPolarGraphicalItem(settings);
620
- } else if (prev !== settings) {
621
- store.actions.graphicalItems.replacePolarGraphicalItem({ prev, next: settings });
622
- }
623
- prev = settings;
624
- },
625
- );
626
-
627
- unregisterOnCleanup(() => {
628
- if (prev && store != null) {
629
- store.actions.graphicalItems.removePolarGraphicalItem(prev);
630
- prev = null;
631
- }
632
- });
586
+ if (store != null) {
587
+ registerInStore<PieSettings>(
588
+ () => ({
589
+ id,
590
+ type: "pie",
591
+ data: merged.data,
592
+ dataKey: merged.dataKey,
593
+ hide: merged.hide,
594
+ angleAxisId: merged.angleAxisId ?? 0,
595
+ radiusAxisId: merged.radiusAxisId ?? 0,
596
+ name: merged.name,
597
+ nameKey: merged.nameKey,
598
+ tooltipType: merged.tooltipType,
599
+ legendType: merged.legendType,
600
+ fill: merged.fill,
601
+ stroke: merged.stroke,
602
+ cx: merged.cx,
603
+ cy: merged.cy,
604
+ startAngle: merged.startAngle,
605
+ endAngle: merged.endAngle,
606
+ paddingAngle: merged.paddingAngle,
607
+ minAngle: merged.minAngle,
608
+ innerRadius: merged.innerRadius,
609
+ outerRadius: merged.outerRadius,
610
+ cornerRadius: merged.cornerRadius,
611
+ maxRadius: merged.maxRadius,
612
+ }),
613
+ {
614
+ add: (next) => store.actions.graphicalItems.addPolarGraphicalItem(next),
615
+ replace: (prev, next) =>
616
+ store.actions.graphicalItems.replacePolarGraphicalItem({ prev, next }),
617
+ remove: (prev) => store.actions.graphicalItems.removePolarGraphicalItem(prev),
618
+ },
619
+ );
620
+ }
633
621
 
634
622
  // Per-slice <Cell> overrides (collected from children, in data order).
635
623
  const cells = collectCells(() => props.children);
@@ -17,7 +17,7 @@
17
17
  // evaluated element to inject computed x/y/payload — use the function form
18
18
  // instead). NOTE: the reference PolarAngleAxis renders no axis `label` title
19
19
  // either (only PolarRadiusAxis does) — no gap there.
20
- import { createEffect, For, merge, Show } from "solid-js";
20
+ import { For, merge, Show } from "solid-js";
21
21
  import type { JSX } from "@solidjs/web";
22
22
  import { svgPropertiesNoEvents } from "../util/svgPropertiesNoEvents";
23
23
  import { useAppSelector, useAppStore } from "../state/hooks";
@@ -48,7 +48,7 @@ import { useIsPanorama } from "../context/PanoramaContext";
48
48
  import { usePolarChartLayout } from "../context/chartLayoutContext";
49
49
  import { getAxisTypeBasedOnLayout } from "../util/getAxisTypeBasedOnLayout";
50
50
  import { CustomScaleDefinition } from "../util/scale/CustomScaleDefinition";
51
- import { unregisterOnCleanup } from "../state/unregisterOnCleanup";
51
+ import { registerInStore } from "../state/registerInStore";
52
52
  import { DefaultZIndexes } from "../zIndex/DefaultZIndexes";
53
53
  import { ZIndexLayer } from "../zIndex/ZIndexLayer";
54
54
 
@@ -334,54 +334,45 @@ export function PolarAngleAxis<DataPointType = unknown, DataValueType = unknown>
334
334
  const merged = merge(polarAngleAxisDefaultProps, props);
335
335
  const isPanorama = useIsPanorama();
336
336
 
337
- let prev: AngleAxisSettings | null = null;
338
-
339
- createEffect(
340
- (): AngleAxisSettings | undefined => {
341
- const evaluatedType = getAxisTypeBasedOnLayout(layout(), "angleAxis", merged.type);
342
- if (evaluatedType == null) {
343
- return undefined;
344
- }
345
- return {
346
- id: merged.angleAxisId,
347
- scale: merged.scale,
348
- type: evaluatedType,
349
- dataKey: merged.dataKey,
350
- unit: undefined,
351
- name: merged.name,
352
- // Ignored on purpose: axis calc behaves as if false (recharts note).
353
- allowDuplicatedCategory: false,
354
- allowDataOverflow: false,
355
- reversed: merged.reversed,
356
- includeHidden: false,
357
- domain: merged.domain,
358
- allowDecimals: merged.allowDecimals,
359
- tickCount: merged.tickCount,
360
- ticks: merged.ticks,
361
- // DEVIATION: the settings slice shares the cartesian tick renderer type,
362
- // so a polar function-form renderer is recorded as `true` (presence only —
363
- // the renderer itself stays local to this component's render; no selector
364
- // reads the function back out of the store).
365
- tick: typeof merged.tick === "function" ? true : merged.tick,
366
- niceTicks: merged.niceTicks,
367
- };
368
- },
369
- (settings) => {
370
- if (settings == null || store == null) {
371
- return;
372
- }
373
- // addAngleAxis overwrites by id, so it doubles as replace-on-change.
374
- store.actions.polarAxis.addAngleAxis(settings);
375
- prev = settings;
376
- },
377
- );
378
-
379
- unregisterOnCleanup(() => {
380
- if (prev && store != null) {
381
- store.actions.polarAxis.removeAngleAxis(prev);
382
- prev = null;
383
- }
384
- });
337
+ if (store != null) {
338
+ registerInStore<AngleAxisSettings>(
339
+ (): AngleAxisSettings | undefined => {
340
+ const evaluatedType = getAxisTypeBasedOnLayout(layout(), "angleAxis", merged.type);
341
+ if (evaluatedType == null) {
342
+ return undefined;
343
+ }
344
+ return {
345
+ id: merged.angleAxisId,
346
+ scale: merged.scale,
347
+ type: evaluatedType,
348
+ dataKey: merged.dataKey,
349
+ unit: undefined,
350
+ name: merged.name,
351
+ // Ignored on purpose: axis calc behaves as if false (recharts note).
352
+ allowDuplicatedCategory: false,
353
+ allowDataOverflow: false,
354
+ reversed: merged.reversed,
355
+ includeHidden: false,
356
+ domain: merged.domain,
357
+ allowDecimals: merged.allowDecimals,
358
+ tickCount: merged.tickCount,
359
+ ticks: merged.ticks,
360
+ // DEVIATION: the settings slice shares the cartesian tick renderer type,
361
+ // so a polar function-form renderer is recorded as `true` (presence only —
362
+ // the renderer itself stays local to this component's render; no selector
363
+ // reads the function back out of the store).
364
+ tick: typeof merged.tick === "function" ? true : merged.tick,
365
+ niceTicks: merged.niceTicks,
366
+ };
367
+ },
368
+ {
369
+ // addAngleAxis overwrites by id, so it doubles as replace-on-change.
370
+ add: (next) => store.actions.polarAxis.addAngleAxis(next),
371
+ replace: (_prev, next) => store.actions.polarAxis.addAngleAxis(next),
372
+ remove: (prev) => store.actions.polarAxis.removeAngleAxis(prev),
373
+ },
374
+ );
375
+ }
385
376
 
386
377
  const viewBox = useAppSelector(selectPolarViewBox);
387
378
  const scale = useAppSelector((state) =>
@@ -14,7 +14,7 @@
14
14
  // DefaultZIndexes.axis).
15
15
  // DEFERRED (documented): ELEMENT-form `tick` (Solid can't clone-and-inject
16
16
  // computed props into an evaluated node — use the function form instead).
17
- import { createEffect, For, merge, Show } from "solid-js";
17
+ import { For, merge, Show } from "solid-js";
18
18
  import type { JSX } from "@solidjs/web";
19
19
  import { svgPropertiesNoEvents } from "../util/svgPropertiesNoEvents";
20
20
  import { useAppSelector, useAppStore } from "../state/hooks";
@@ -47,7 +47,7 @@ import { usePolarChartLayout } from "../context/chartLayoutContext";
47
47
  import { getAxisTypeBasedOnLayout } from "../util/getAxisTypeBasedOnLayout";
48
48
  import { CustomScaleDefinition } from "../util/scale/CustomScaleDefinition";
49
49
  import type { PolarAxisLineObjectProps, PolarTickEventHandler } from "./PolarAngleAxis";
50
- import { unregisterOnCleanup } from "../state/unregisterOnCleanup";
50
+ import { registerInStore } from "../state/registerInStore";
51
51
 
52
52
  type TickOrientation = "left" | "right" | "middle";
53
53
 
@@ -265,52 +265,44 @@ export function PolarRadiusAxis<DataPointType = unknown, DataValueType = unknown
265
265
  const layout = usePolarChartLayout();
266
266
  const merged = merge(polarRadiusAxisDefaultProps, props);
267
267
 
268
- let prev: RadiusAxisSettings | null = null;
269
-
270
- createEffect(
271
- (): RadiusAxisSettings | undefined => {
272
- const evaluatedType = getAxisTypeBasedOnLayout(layout(), "radiusAxis", merged.type);
273
- if (evaluatedType == null) {
274
- return undefined;
275
- }
276
- return {
277
- id: merged.radiusAxisId,
278
- scale: merged.scale,
279
- type: evaluatedType,
280
- dataKey: merged.dataKey,
281
- unit: undefined,
282
- name: merged.name,
283
- allowDuplicatedCategory: merged.allowDuplicatedCategory,
284
- allowDataOverflow: merged.allowDataOverflow,
285
- reversed: merged.reversed,
286
- includeHidden: merged.includeHidden,
287
- domain: merged.domain,
288
- allowDecimals: merged.allowDecimals,
289
- tickCount: merged.tickCount,
290
- ticks: merged.ticks,
291
- // DEVIATION: the settings slice shares the cartesian tick renderer type,
292
- // so a polar function-form renderer is recorded as `true` (presence only —
293
- // the renderer itself stays local to this component's render; no selector
294
- // reads the function back out of the store).
295
- tick: typeof merged.tick === "function" ? true : merged.tick,
296
- niceTicks: merged.niceTicks,
297
- };
298
- },
299
- (settings) => {
300
- if (settings == null || store == null) {
301
- return;
302
- }
303
- store.actions.polarAxis.addRadiusAxis(settings);
304
- prev = settings;
305
- },
306
- );
307
-
308
- unregisterOnCleanup(() => {
309
- if (prev && store != null) {
310
- store.actions.polarAxis.removeRadiusAxis(prev);
311
- prev = null;
312
- }
313
- });
268
+ if (store != null) {
269
+ registerInStore<RadiusAxisSettings>(
270
+ (): RadiusAxisSettings | undefined => {
271
+ const evaluatedType = getAxisTypeBasedOnLayout(layout(), "radiusAxis", merged.type);
272
+ if (evaluatedType == null) {
273
+ return undefined;
274
+ }
275
+ return {
276
+ id: merged.radiusAxisId,
277
+ scale: merged.scale,
278
+ type: evaluatedType,
279
+ dataKey: merged.dataKey,
280
+ unit: undefined,
281
+ name: merged.name,
282
+ allowDuplicatedCategory: merged.allowDuplicatedCategory,
283
+ allowDataOverflow: merged.allowDataOverflow,
284
+ reversed: merged.reversed,
285
+ includeHidden: merged.includeHidden,
286
+ domain: merged.domain,
287
+ allowDecimals: merged.allowDecimals,
288
+ tickCount: merged.tickCount,
289
+ ticks: merged.ticks,
290
+ // DEVIATION: the settings slice shares the cartesian tick renderer type,
291
+ // so a polar function-form renderer is recorded as `true` (presence only —
292
+ // the renderer itself stays local to this component's render; no selector
293
+ // reads the function back out of the store).
294
+ tick: typeof merged.tick === "function" ? true : merged.tick,
295
+ niceTicks: merged.niceTicks,
296
+ };
297
+ },
298
+ {
299
+ // addRadiusAxis overwrites by id, so it doubles as replace-on-change.
300
+ add: (next) => store.actions.polarAxis.addRadiusAxis(next),
301
+ replace: (_prev, next) => store.actions.polarAxis.addRadiusAxis(next),
302
+ remove: (prev) => store.actions.polarAxis.removeRadiusAxis(prev),
303
+ },
304
+ );
305
+ }
314
306
 
315
307
  const viewBox = useAppSelector(selectPolarViewBox);
316
308
  const scale = useAppSelector((state) =>
@@ -21,7 +21,7 @@
21
21
  // cloneElement). PERMANENT-N/A: element-form `shape`. DEFERRED: object-form
22
22
  // `shape`. zIndex relayering IS wired: the `.recharts-radar` Layer is wrapped in
23
23
  // <ZIndexLayer> (default DefaultZIndexes.area = 100), matching recharts.
24
- import { createEffect, createMemo, merge, Show } from "solid-js";
24
+ import { createMemo, merge, Show } from "solid-js";
25
25
  import type { JSX } from "@solidjs/web";
26
26
  import { useAppSelector, useAppStore } from "../state/hooks";
27
27
  import { useIsPanorama } from "../context/PanoramaContext";
@@ -74,7 +74,7 @@ import { SetTooltipEntrySettings } from "../state/SetTooltipEntrySettings";
74
74
  import { SetLegendPayload } from "../state/SetLegendPayload";
75
75
  import { LegendPayload } from "../component/DefaultLegendContent";
76
76
  import { TooltipPayloadConfiguration } from "../state/tooltipSlice";
77
- import { unregisterOnCleanup } from "../state/unregisterOnCleanup";
77
+ import { registerInStore } from "../state/registerInStore";
78
78
 
79
79
  /** recharts: stroke (if not "none") wins as the legend/tooltip color, else fill. */
80
80
  const getLegendItemColor = (
@@ -295,38 +295,26 @@ export function Radar<DataPointType = Record<string, unknown>, DataValueType = u
295
295
  const isPanorama = useIsPanorama();
296
296
  const id = useUniqueId("recharts-radar", props.id);
297
297
 
298
- let prev: RadarSettings | null = null;
299
-
300
- createEffect(
301
- (): RadarSettings => ({
302
- id,
303
- type: "radar",
304
- // Radar has no own data prop — it always reads the chart data.
305
- data: undefined,
306
- dataKey: merged.dataKey,
307
- hide: merged.hide,
308
- angleAxisId: merged.angleAxisId,
309
- radiusAxisId: merged.radiusAxisId,
310
- }),
311
- (settings) => {
312
- if (store == null) {
313
- return;
314
- }
315
- if (prev === null) {
316
- store.actions.graphicalItems.addPolarGraphicalItem(settings);
317
- } else if (prev !== settings) {
318
- store.actions.graphicalItems.replacePolarGraphicalItem({ prev, next: settings });
319
- }
320
- prev = settings;
321
- },
322
- );
323
-
324
- unregisterOnCleanup(() => {
325
- if (prev && store != null) {
326
- store.actions.graphicalItems.removePolarGraphicalItem(prev);
327
- prev = null;
328
- }
329
- });
298
+ if (store != null) {
299
+ registerInStore<RadarSettings>(
300
+ () => ({
301
+ id,
302
+ type: "radar",
303
+ // Radar has no own data prop — it always reads the chart data.
304
+ data: undefined,
305
+ dataKey: merged.dataKey,
306
+ hide: merged.hide,
307
+ angleAxisId: merged.angleAxisId,
308
+ radiusAxisId: merged.radiusAxisId,
309
+ }),
310
+ {
311
+ add: (next) => store.actions.graphicalItems.addPolarGraphicalItem(next),
312
+ replace: (prev, next) =>
313
+ store.actions.graphicalItems.replacePolarGraphicalItem({ prev, next }),
314
+ remove: (prev) => store.actions.graphicalItems.removePolarGraphicalItem(prev),
315
+ },
316
+ );
317
+ }
330
318
 
331
319
  const radar = useAppSelector((state) =>
332
320
  selectRadarPoints(state, merged.radiusAxisId, merged.angleAxisId, isPanorama, id),