@epilot/spark-ui 1.0.0-alpha.3 → 1.0.0-alpha.5
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/dist/accent-colors-Ch2J0EEY.js +13 -0
- package/dist/accent-colors-Ch2J0EEY.js.map +1 -0
- package/dist/accent-colors-CjHCf8fH.cjs +18 -0
- package/dist/accent-colors-CjHCf8fH.cjs.map +1 -0
- package/dist/components/accordion.cjs +1 -1
- package/dist/components/accordion.d.ts +1 -1
- package/dist/components/accordion.js +1 -1
- package/dist/components/attachment.d.cts +1 -1
- package/dist/components/attachment.d.ts +1 -1
- package/dist/components/badge.cjs +12 -7
- package/dist/components/badge.cjs.map +1 -1
- package/dist/components/badge.d.cts +5 -1
- package/dist/components/badge.d.ts +5 -1
- package/dist/components/badge.js +12 -7
- package/dist/components/badge.js.map +1 -1
- package/dist/components/bar-chart.cjs +60 -56
- package/dist/components/bar-chart.cjs.map +1 -1
- package/dist/components/bar-chart.d.cts +7 -2
- package/dist/components/bar-chart.d.ts +7 -2
- package/dist/components/bar-chart.js +60 -56
- package/dist/components/bar-chart.js.map +1 -1
- package/dist/components/battery-progress-row.cjs +10 -16
- package/dist/components/battery-progress-row.cjs.map +1 -1
- package/dist/components/battery-progress-row.d.cts +1 -1
- package/dist/components/battery-progress-row.d.ts +2 -2
- package/dist/components/battery-progress-row.js +10 -16
- package/dist/components/battery-progress-row.js.map +1 -1
- package/dist/components/bubble.d.cts +1 -1
- package/dist/components/bubble.d.ts +1 -1
- package/dist/components/button.cjs +5 -2
- package/dist/components/button.cjs.map +1 -1
- package/dist/components/button.d.cts +16 -7
- package/dist/components/button.d.ts +16 -7
- package/dist/components/button.js +5 -2
- package/dist/components/button.js.map +1 -1
- package/dist/components/callout.d.cts +1 -1
- package/dist/components/callout.d.ts +1 -1
- package/dist/components/card.d.cts +1 -1
- package/dist/components/card.d.ts +1 -1
- package/dist/components/chart.cjs +1 -1
- package/dist/components/chart.cjs.map +1 -1
- package/dist/components/chart.js +1 -1
- package/dist/components/chart.js.map +1 -1
- package/dist/components/checkbox.cjs +30 -23
- package/dist/components/checkbox.cjs.map +1 -1
- package/dist/components/checkbox.d.cts +5 -1
- package/dist/components/checkbox.d.ts +5 -1
- package/dist/components/checkbox.js +30 -23
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/circular-gauge.d.ts +1 -1
- package/dist/components/day-of-week-picker.cjs +2 -2
- package/dist/components/day-of-week-picker.cjs.map +1 -1
- package/dist/components/day-of-week-picker.js +2 -2
- package/dist/components/day-of-week-picker.js.map +1 -1
- package/dist/components/device-card.d.ts +1 -1
- package/dist/components/donut-chart.cjs +3 -3
- package/dist/components/donut-chart.cjs.map +1 -1
- package/dist/components/donut-chart.js +3 -3
- package/dist/components/donut-chart.js.map +1 -1
- package/dist/components/energy-bar-chart.cjs +2 -1
- package/dist/components/energy-bar-chart.cjs.map +1 -1
- package/dist/components/energy-bar-chart.d.ts +1 -1
- package/dist/components/energy-bar-chart.js +2 -1
- package/dist/components/energy-bar-chart.js.map +1 -1
- package/dist/components/energy-breakdown.cjs +1 -1
- package/dist/components/energy-breakdown.cjs.map +1 -1
- package/dist/components/energy-breakdown.js +1 -1
- package/dist/components/energy-breakdown.js.map +1 -1
- package/dist/components/energy-history-chart.cjs +14 -6
- package/dist/components/energy-history-chart.cjs.map +1 -1
- package/dist/components/energy-history-chart.d.cts +11 -3
- package/dist/components/energy-history-chart.d.ts +11 -3
- package/dist/components/energy-history-chart.js +14 -6
- package/dist/components/energy-history-chart.js.map +1 -1
- package/dist/components/feed-in-hero.d.ts +1 -1
- package/dist/components/field.cjs +1 -1
- package/dist/components/field.d.cts +1 -1
- package/dist/components/field.d.ts +1 -1
- package/dist/components/field.js +1 -1
- package/dist/components/info-block.d.cts +1 -1
- package/dist/components/info-block.d.ts +1 -1
- package/dist/components/input-group.cjs +1 -1
- package/dist/components/input-group.js +1 -1
- package/dist/components/input-otp.cjs +1 -1
- package/dist/components/input-otp.js +1 -1
- package/dist/components/input.cjs +1 -1
- package/dist/components/input.d.cts +1 -1
- package/dist/components/input.d.ts +1 -1
- package/dist/components/input.js +1 -1
- package/dist/components/line-chart.cjs +61 -19
- package/dist/components/line-chart.cjs.map +1 -1
- package/dist/components/line-chart.d.cts +7 -0
- package/dist/components/line-chart.d.ts +7 -0
- package/dist/components/line-chart.js +61 -19
- package/dist/components/line-chart.js.map +1 -1
- package/dist/components/metering-row.d.ts +1 -1
- package/dist/components/money-saved-block.cjs +1 -1
- package/dist/components/money-saved-block.cjs.map +1 -1
- package/dist/components/money-saved-block.js +1 -1
- package/dist/components/money-saved-block.js.map +1 -1
- package/dist/components/number-flow.d.ts +1 -1
- package/dist/components/power-history-chart.cjs +23 -44
- package/dist/components/power-history-chart.cjs.map +1 -1
- package/dist/components/power-history-chart.d.cts +10 -1
- package/dist/components/power-history-chart.d.ts +10 -1
- package/dist/components/power-history-chart.js +24 -45
- package/dist/components/power-history-chart.js.map +1 -1
- package/dist/components/power-history.cjs +1 -1
- package/dist/components/power-history.cjs.map +1 -1
- package/dist/components/power-history.js +1 -1
- package/dist/components/power-history.js.map +1 -1
- package/dist/components/power-network.cjs +36 -11
- package/dist/components/power-network.cjs.map +1 -1
- package/dist/components/power-network.js +36 -11
- package/dist/components/power-network.js.map +1 -1
- package/dist/components/progress.cjs +1 -1
- package/dist/components/progress.cjs.map +1 -1
- package/dist/components/progress.js +1 -1
- package/dist/components/progress.js.map +1 -1
- package/dist/components/radio-card-group.cjs +26 -1
- package/dist/components/radio-card-group.cjs.map +1 -1
- package/dist/components/radio-card-group.js +26 -1
- package/dist/components/radio-card-group.js.map +1 -1
- package/dist/components/radio.cjs +11 -4
- package/dist/components/radio.cjs.map +1 -1
- package/dist/components/radio.d.cts +15 -3
- package/dist/components/radio.d.ts +15 -3
- package/dist/components/radio.js +11 -4
- package/dist/components/radio.js.map +1 -1
- package/dist/components/sankey-chart.cjs +12 -45
- package/dist/components/sankey-chart.cjs.map +1 -1
- package/dist/components/sankey-chart.d.cts +8 -0
- package/dist/components/sankey-chart.d.ts +9 -1
- package/dist/components/sankey-chart.js +12 -45
- package/dist/components/sankey-chart.js.map +1 -1
- package/dist/components/schedule-list.cjs +3 -3
- package/dist/components/schedule-list.cjs.map +1 -1
- package/dist/components/schedule-list.js +3 -3
- package/dist/components/schedule-list.js.map +1 -1
- package/dist/components/segmented-control.cjs +44 -0
- package/dist/components/segmented-control.cjs.map +1 -0
- package/dist/components/segmented-control.d.cts +33 -0
- package/dist/components/segmented-control.d.ts +33 -0
- package/dist/components/segmented-control.js +40 -0
- package/dist/components/segmented-control.js.map +1 -0
- package/dist/components/select.cjs +1 -1
- package/dist/components/select.d.cts +1 -1
- package/dist/components/select.d.ts +1 -1
- package/dist/components/select.js +1 -1
- package/dist/components/separator.cjs +1 -1
- package/dist/components/separator.cjs.map +1 -1
- package/dist/components/separator.js +1 -1
- package/dist/components/separator.js.map +1 -1
- package/dist/components/series-legend.cjs +1 -1
- package/dist/components/series-legend.cjs.map +1 -1
- package/dist/components/series-legend.js +1 -1
- package/dist/components/series-legend.js.map +1 -1
- package/dist/components/slider.cjs +6 -19
- package/dist/components/slider.cjs.map +1 -1
- package/dist/components/slider.d.cts +0 -13
- package/dist/components/slider.d.ts +0 -13
- package/dist/components/slider.js +6 -19
- package/dist/components/slider.js.map +1 -1
- package/dist/components/solar-forecast-chart.cjs +4 -3
- package/dist/components/solar-forecast-chart.cjs.map +1 -1
- package/dist/components/solar-forecast-chart.d.cts +4 -1
- package/dist/components/solar-forecast-chart.d.ts +4 -1
- package/dist/components/solar-forecast-chart.js +4 -3
- package/dist/components/solar-forecast-chart.js.map +1 -1
- package/dist/components/stepper.cjs +2 -2
- package/dist/components/stepper.cjs.map +1 -1
- package/dist/components/stepper.js +2 -2
- package/dist/components/stepper.js.map +1 -1
- package/dist/components/switch.cjs +8 -3
- package/dist/components/switch.cjs.map +1 -1
- package/dist/components/switch.d.cts +9 -4
- package/dist/components/switch.d.ts +9 -4
- package/dist/components/switch.js +8 -3
- package/dist/components/switch.js.map +1 -1
- package/dist/components/tabs.cjs +4 -4
- package/dist/components/tabs.cjs.map +1 -1
- package/dist/components/tabs.js +4 -4
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/theme.cjs +1 -1
- package/dist/components/theme.d.cts +1 -1
- package/dist/components/theme.d.ts +1 -1
- package/dist/components/theme.js +1 -1
- package/dist/components/time-picker.cjs +1 -1
- package/dist/components/time-picker.cjs.map +1 -1
- package/dist/components/time-picker.js +1 -1
- package/dist/components/time-picker.js.map +1 -1
- package/dist/components/tooltip.cjs +1 -1
- package/dist/components/tooltip.cjs.map +1 -1
- package/dist/components/tooltip.js +1 -1
- package/dist/components/tooltip.js.map +1 -1
- package/dist/hooks/use-reduced-motion.cjs +22 -0
- package/dist/hooks/use-reduced-motion.cjs.map +1 -0
- package/dist/hooks/use-reduced-motion.d.cts +7 -0
- package/dist/hooks/use-reduced-motion.d.ts +7 -0
- package/dist/hooks/use-reduced-motion.js +20 -0
- package/dist/hooks/use-reduced-motion.js.map +1 -0
- package/dist/{input-box-variants-Dr1K7uYE.js → input-box-variants-DcajYXvo.js} +1 -1
- package/dist/{input-box-variants-Dr1K7uYE.js.map → input-box-variants-DcajYXvo.js.map} +1 -1
- package/dist/{input-box-variants-BMDdwvqg.cjs → input-box-variants-ZCtgs-1S.cjs} +1 -1
- package/dist/{input-box-variants-BMDdwvqg.cjs.map → input-box-variants-ZCtgs-1S.cjs.map} +1 -1
- package/dist/{number-flow-DSbucQ_K.d.ts → number-flow-D9zsxH0-.d.ts} +1 -1
- package/dist/styles/index.css +39 -0
- package/dist/styles/spark-default.css +123 -40
- package/dist/styles/theme.css +78 -0
- package/dist/{theme-B3WtiByA.js → theme-8PDO9E0S.js} +2 -2
- package/dist/theme-8PDO9E0S.js.map +1 -0
- package/dist/{theme-C9_OSe0S.cjs → theme-ClRJhexF.cjs} +2 -2
- package/dist/theme-ClRJhexF.cjs.map +1 -0
- package/dist/{theme-types-DoROIXpi.d.ts → theme-types-BDdoRxXa.d.ts} +2 -3
- package/dist/{theme-types-r20EJfEC.d.cts → theme-types-BwSJKdsB.d.cts} +2 -3
- package/dist/{tones-DyCkL71h.d.ts → tones-PXJGlSpe.d.ts} +1 -1
- package/dist/utils/build-sankey-nodes.cjs +16 -3
- package/dist/utils/build-sankey-nodes.cjs.map +1 -1
- package/dist/utils/build-sankey-nodes.js +16 -3
- package/dist/utils/build-sankey-nodes.js.map +1 -1
- package/dist/utils/chart-scale.cjs +43 -0
- package/dist/utils/chart-scale.cjs.map +1 -0
- package/dist/utils/chart-scale.d.cts +22 -0
- package/dist/utils/chart-scale.d.ts +22 -0
- package/dist/utils/chart-scale.js +41 -0
- package/dist/utils/chart-scale.js.map +1 -0
- package/package.json +1 -1
- package/dist/theme-B3WtiByA.js.map +0 -1
- package/dist/theme-C9_OSe0S.cjs.map +0 -1
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { u as SparkStyle } from "../theme-types-BwSJKdsB.cjs";
|
|
3
4
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
4
5
|
import * as React$1 from "react";
|
|
5
6
|
import { Radio as Radio$1 } from "@base-ui/react/radio";
|
|
6
7
|
import { RadioGroup as RadioGroup$1 } from "@base-ui/react/radio-group";
|
|
7
8
|
|
|
8
9
|
//#region src/components/radio.d.ts
|
|
9
|
-
type
|
|
10
|
+
type SparkRadioColor = 'accent' | 'neutral' | 'danger';
|
|
11
|
+
type RadioGroupProps = React$1.ComponentProps<typeof RadioGroup$1> & {
|
|
12
|
+
styleVariant?: SparkStyle;
|
|
13
|
+
color?: SparkRadioColor;
|
|
14
|
+
};
|
|
10
15
|
declare function RadioGroup({
|
|
11
16
|
className,
|
|
17
|
+
styleVariant,
|
|
18
|
+
color,
|
|
12
19
|
...props
|
|
13
20
|
}: RadioGroupProps): _$react_jsx_runtime0.JSX.Element;
|
|
14
|
-
type RadioProps = React$1.ComponentProps<typeof Radio$1.Root
|
|
21
|
+
type RadioProps = React$1.ComponentProps<typeof Radio$1.Root> & {
|
|
22
|
+
styleVariant?: SparkStyle;
|
|
23
|
+
color?: SparkRadioColor;
|
|
24
|
+
};
|
|
15
25
|
declare function Radio({
|
|
16
26
|
className,
|
|
27
|
+
styleVariant,
|
|
28
|
+
color,
|
|
17
29
|
...props
|
|
18
30
|
}: RadioProps): _$react_jsx_runtime0.JSX.Element;
|
|
19
31
|
//#endregion
|
|
20
|
-
export { Radio, RadioGroup, type RadioGroupProps, type RadioProps };
|
|
32
|
+
export { Radio, RadioGroup, type RadioGroupProps, type RadioProps, SparkRadioColor };
|
|
21
33
|
//# sourceMappingURL=radio.d.cts.map
|
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { u as SparkStyle } from "../theme-types-BDdoRxXa.js";
|
|
3
4
|
import * as React$1 from "react";
|
|
4
5
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
5
6
|
import { Radio as Radio$1 } from "@base-ui/react/radio";
|
|
6
7
|
import { RadioGroup as RadioGroup$1 } from "@base-ui/react/radio-group";
|
|
7
8
|
|
|
8
9
|
//#region src/components/radio.d.ts
|
|
9
|
-
type
|
|
10
|
+
type SparkRadioColor = 'accent' | 'neutral' | 'danger';
|
|
11
|
+
type RadioGroupProps = React$1.ComponentProps<typeof RadioGroup$1> & {
|
|
12
|
+
styleVariant?: SparkStyle;
|
|
13
|
+
color?: SparkRadioColor;
|
|
14
|
+
};
|
|
10
15
|
declare function RadioGroup({
|
|
11
16
|
className,
|
|
17
|
+
styleVariant,
|
|
18
|
+
color,
|
|
12
19
|
...props
|
|
13
20
|
}: RadioGroupProps): _$react_jsx_runtime0.JSX.Element;
|
|
14
|
-
type RadioProps = React$1.ComponentProps<typeof Radio$1.Root
|
|
21
|
+
type RadioProps = React$1.ComponentProps<typeof Radio$1.Root> & {
|
|
22
|
+
styleVariant?: SparkStyle;
|
|
23
|
+
color?: SparkRadioColor;
|
|
24
|
+
};
|
|
15
25
|
declare function Radio({
|
|
16
26
|
className,
|
|
27
|
+
styleVariant,
|
|
28
|
+
color,
|
|
17
29
|
...props
|
|
18
30
|
}: RadioProps): _$react_jsx_runtime0.JSX.Element;
|
|
19
31
|
//#endregion
|
|
20
|
-
export { Radio, RadioGroup, type RadioGroupProps, type RadioProps };
|
|
32
|
+
export { Radio, RadioGroup, type RadioGroupProps, type RadioProps, SparkRadioColor };
|
|
21
33
|
//# sourceMappingURL=radio.d.ts.map
|
package/dist/components/radio.js
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../utils/cn.js";
|
|
3
|
+
import { t as accentByColor } from "../accent-colors-Ch2J0EEY.js";
|
|
3
4
|
import "react";
|
|
4
5
|
import { jsx } from "react/jsx-runtime";
|
|
5
6
|
import { Radio as Radio$1 } from "@base-ui/react/radio";
|
|
6
7
|
import { RadioGroup as RadioGroup$1 } from "@base-ui/react/radio-group";
|
|
7
8
|
//#region src/components/radio.tsx
|
|
8
|
-
function RadioGroup({ className, ...props }) {
|
|
9
|
+
function RadioGroup({ className, styleVariant, color, ...props }) {
|
|
9
10
|
return /* @__PURE__ */ jsx(RadioGroup$1, {
|
|
10
11
|
"data-slot": "radio-group",
|
|
12
|
+
"data-style": styleVariant !== void 0 || color !== void 0 ? styleVariant ?? "solid" : void 0,
|
|
13
|
+
"data-accent-color": color ? accentByColor[color] : void 0,
|
|
11
14
|
className: cn("grid gap-3", className),
|
|
12
15
|
...props
|
|
13
16
|
});
|
|
14
17
|
}
|
|
15
|
-
function Radio({ className, ...props }) {
|
|
18
|
+
function Radio({ className, styleVariant, color, ...props }) {
|
|
19
|
+
const dataStyle = styleVariant !== void 0 || color !== void 0 ? styleVariant ?? "solid" : void 0;
|
|
20
|
+
const dataAccentColor = color ? accentByColor[color] : void 0;
|
|
16
21
|
return /* @__PURE__ */ jsx(Radio$1.Root, {
|
|
17
22
|
"data-slot": "radio",
|
|
18
|
-
|
|
23
|
+
"data-style": dataStyle,
|
|
24
|
+
"data-accent-color": dataAccentColor,
|
|
25
|
+
className: cn("peer relative inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-full border-2 transition-colors", "before:absolute before:top-1/2 before:left-1/2 before:size-10 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']", "focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden", "data-disabled:cursor-not-allowed data-disabled:opacity-50", "border-(--spark-style-idle-border,var(--spark-a7)) bg-base-accent-soft data-unchecked:border-(length:--spark-style-border-width,2px)", "data-checked:border-(--spark-radio-ring,var(--spark-9))", className),
|
|
19
26
|
...props,
|
|
20
27
|
children: /* @__PURE__ */ jsx(Radio$1.Indicator, {
|
|
21
28
|
"data-slot": "radio-indicator",
|
|
22
29
|
className: "flex items-center justify-center",
|
|
23
|
-
children: /* @__PURE__ */ jsx("span", { className: "size-4 rounded-full bg-
|
|
30
|
+
children: /* @__PURE__ */ jsx("span", { className: "size-4 rounded-full bg-(--spark-radio-dot,var(--spark-9)) motion-safe:animate-dot-pop" })
|
|
24
31
|
})
|
|
25
32
|
});
|
|
26
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio.js","names":["RadioGroupPrimitive","RadioPrimitive"],"sources":["../../src/components/radio.tsx"],"sourcesContent":["import { Radio as RadioPrimitive } from '@base-ui/react/radio'\nimport { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\n\ntype RadioGroupProps = React.ComponentProps<typeof RadioGroupPrimitive
|
|
1
|
+
{"version":3,"file":"radio.js","names":["RadioGroupPrimitive","RadioPrimitive"],"sources":["../../src/components/radio.tsx"],"sourcesContent":["import { Radio as RadioPrimitive } from '@base-ui/react/radio'\nimport { RadioGroup as RadioGroupPrimitive } from '@base-ui/react/radio-group'\nimport * as React from 'react'\nimport { accentByColor } from '../lib/accent-colors'\nimport type { SparkStyle } from '../lib/theme-types'\nimport { cn } from '../utils/cn'\n\nexport type SparkRadioColor = 'accent' | 'neutral' | 'danger'\n\ntype RadioGroupProps = React.ComponentProps<typeof RadioGroupPrimitive> & {\n styleVariant?: SparkStyle\n color?: SparkRadioColor\n}\n\nfunction RadioGroup({\n className,\n styleVariant,\n color,\n ...props\n}: RadioGroupProps) {\n const hasLook = styleVariant !== undefined || color !== undefined\n const dataStyle = hasLook ? (styleVariant ?? 'solid') : undefined\n const dataAccentColor = color ? accentByColor[color] : undefined\n\n return (\n <RadioGroupPrimitive\n data-slot=\"radio-group\"\n data-style={dataStyle}\n data-accent-color={dataAccentColor}\n className={cn('grid gap-3', className)}\n {...props}\n />\n )\n}\n\ntype RadioProps = React.ComponentProps<typeof RadioPrimitive.Root> & {\n styleVariant?: SparkStyle\n color?: SparkRadioColor\n}\n\nfunction Radio({ className, styleVariant, color, ...props }: RadioProps) {\n const hasLook = styleVariant !== undefined || color !== undefined\n const dataStyle = hasLook ? (styleVariant ?? 'solid') : undefined\n const dataAccentColor = color ? accentByColor[color] : undefined\n\n return (\n <RadioPrimitive.Root\n data-slot=\"radio\"\n data-style={dataStyle}\n data-accent-color={dataAccentColor}\n className={cn(\n 'peer relative inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-full border-2 transition-colors',\n \"before:absolute before:top-1/2 before:left-1/2 before:size-10 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\",\n 'focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',\n 'data-disabled:cursor-not-allowed data-disabled:opacity-50',\n 'border-(--spark-style-idle-border,var(--spark-a7)) bg-base-accent-soft data-unchecked:border-(length:--spark-style-border-width,2px)',\n 'data-checked:border-(--spark-radio-ring,var(--spark-9))',\n className,\n )}\n {...props}\n >\n <RadioPrimitive.Indicator\n data-slot=\"radio-indicator\"\n className=\"flex items-center justify-center\"\n >\n <span className=\"size-4 rounded-full bg-(--spark-radio-dot,var(--spark-9)) motion-safe:animate-dot-pop\" />\n </RadioPrimitive.Indicator>\n </RadioPrimitive.Root>\n )\n}\n\nexport { Radio, RadioGroup }\nexport type { RadioGroupProps, RadioProps }\n"],"mappings":";;;;;;;;AAcA,SAAS,WAAW,EAClB,WACA,cACA,OACA,GAAG,SACe;AAKlB,QACE,oBAACA,cAAD;EACE,aAAU;EACV,cAPY,iBAAiB,KAAA,KAAa,UAAU,KAAA,IAC3B,gBAAgB,UAAW,KAAA;EAOpD,qBANoB,QAAQ,cAAc,SAAS,KAAA;EAOnD,WAAW,GAAG,cAAc,UAAU;EACtC,GAAI;EACJ,CAAA;;AASN,SAAS,MAAM,EAAE,WAAW,cAAc,OAAO,GAAG,SAAqB;CAEvE,MAAM,YADU,iBAAiB,KAAA,KAAa,UAAU,KAAA,IAC3B,gBAAgB,UAAW,KAAA;CACxD,MAAM,kBAAkB,QAAQ,cAAc,SAAS,KAAA;AAEvD,QACE,oBAACC,QAAe,MAAhB;EACE,aAAU;EACV,cAAY;EACZ,qBAAmB;EACnB,WAAW,GACT,gIACA,qIACA,iFACA,6DACA,wIACA,2DACA,UACD;EACD,GAAI;YAEJ,oBAACA,QAAe,WAAhB;GACE,aAAU;GACV,WAAU;aAEV,oBAAC,QAAD,EAAM,WAAU,yFAA0F,CAAA;GACjF,CAAA;EACP,CAAA"}
|
|
@@ -13,9 +13,10 @@ const SANKEY_CATEGORY_COLORS = {
|
|
|
13
13
|
solar: "var(--orange-a9)",
|
|
14
14
|
battery: "var(--green-a9)",
|
|
15
15
|
grid: "var(--blue-9)",
|
|
16
|
+
gridFeedIn: "var(--blue-7)",
|
|
16
17
|
home: "var(--spark-9)",
|
|
17
|
-
floor: "var(--
|
|
18
|
-
device: "var(--
|
|
18
|
+
floor: "var(--neutral-a9)",
|
|
19
|
+
device: "var(--neutral-a8)"
|
|
19
20
|
};
|
|
20
21
|
const DEFAULT_HEIGHT = 260;
|
|
21
22
|
const DEFAULT_NODE_WIDTH = 12;
|
|
@@ -31,50 +32,13 @@ const LABEL_NAME_HEIGHT = 11;
|
|
|
31
32
|
const LABEL_VALUE_GAP = -4;
|
|
32
33
|
/** Height of the value box rendered below the node name. */
|
|
33
34
|
const VALUE_HEIGHT = 16;
|
|
34
|
-
const DEFAULT_COLOR = "var(--
|
|
35
|
-
const ANIMATION_DURATION_MS = 500;
|
|
35
|
+
const DEFAULT_COLOR = "var(--neutral-a9)";
|
|
36
36
|
function resolveNodeColor(node, categoryColors, defaultColor) {
|
|
37
37
|
if (node.color) return node.color;
|
|
38
38
|
if (node.category && categoryColors?.[node.category]) return categoryColors[node.category];
|
|
39
39
|
return defaultColor;
|
|
40
40
|
}
|
|
41
|
-
function
|
|
42
|
-
return 1 - (1 - t) ** 3;
|
|
43
|
-
}
|
|
44
|
-
function linkKey(link) {
|
|
45
|
-
return `${link.source}-${link.target}`;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Interpolates link values toward `links` over ~500ms so visx re-layouts each
|
|
49
|
-
* frame, smoothly tweening node rects and link paths.
|
|
50
|
-
*/
|
|
51
|
-
function useAnimatedLinks(links) {
|
|
52
|
-
const [animated, setAnimated] = (0, react.useState)(links);
|
|
53
|
-
const animatedRef = (0, react.useRef)(links);
|
|
54
|
-
(0, react.useEffect)(() => {
|
|
55
|
-
const fromByKey = new Map(animatedRef.current.map((l) => [linkKey(l), l]));
|
|
56
|
-
const start = performance.now();
|
|
57
|
-
let raf = 0;
|
|
58
|
-
const tick = (now) => {
|
|
59
|
-
const t = Math.min(1, (now - start) / ANIMATION_DURATION_MS);
|
|
60
|
-
const eased = easeOutCubic(t);
|
|
61
|
-
const next = links.map((toLink) => {
|
|
62
|
-
const fromValue = fromByKey.get(linkKey(toLink))?.value ?? toLink.value;
|
|
63
|
-
return {
|
|
64
|
-
...toLink,
|
|
65
|
-
value: Math.round(fromValue + (toLink.value - fromValue) * eased)
|
|
66
|
-
};
|
|
67
|
-
});
|
|
68
|
-
animatedRef.current = next;
|
|
69
|
-
setAnimated(next);
|
|
70
|
-
if (t < 1) raf = requestAnimationFrame(tick);
|
|
71
|
-
};
|
|
72
|
-
raf = requestAnimationFrame(tick);
|
|
73
|
-
return () => cancelAnimationFrame(raf);
|
|
74
|
-
}, [links]);
|
|
75
|
-
return animated;
|
|
76
|
-
}
|
|
77
|
-
function SankeyChart({ nodes, links, categoryColors = SANKEY_CATEGORY_COLORS, defaultColor = DEFAULT_COLOR, valueSuffix, valueFormat, height = DEFAULT_HEIGHT, nodeWidth = DEFAULT_NODE_WIDTH, nodePadding = DEFAULT_NODE_PADDING, labelPadding = DEFAULT_LABEL_PADDING, title, className }) {
|
|
41
|
+
function SankeyChart({ nodes, links, categoryColors = SANKEY_CATEGORY_COLORS, defaultColor = DEFAULT_COLOR, valueSuffix, valueFormat, height = DEFAULT_HEIGHT, nodeWidth = DEFAULT_NODE_WIDTH, nodePadding = DEFAULT_NODE_PADDING, labelPadding = DEFAULT_LABEL_PADDING, keepAtDepthIds, title, className }) {
|
|
78
42
|
const containerRef = (0, react.useRef)(null);
|
|
79
43
|
const [width, setWidth] = (0, react.useState)(0);
|
|
80
44
|
(0, react.useEffect)(() => {
|
|
@@ -88,9 +52,8 @@ function SankeyChart({ nodes, links, categoryColors = SANKEY_CATEGORY_COLORS, de
|
|
|
88
52
|
resizeObserver.observe(element);
|
|
89
53
|
return () => resizeObserver.disconnect();
|
|
90
54
|
}, []);
|
|
91
|
-
const animatedLinks = useAnimatedLinks(links);
|
|
92
55
|
const nodeIds = (0, react.useMemo)(() => new Set(nodes.map((node) => node.id)), [nodes]);
|
|
93
|
-
const visibleLinks = (0, react.useMemo)(() =>
|
|
56
|
+
const visibleLinks = (0, react.useMemo)(() => links.filter((link) => link.value > 0 && nodeIds.has(link.source) && nodeIds.has(link.target)), [links, nodeIds]);
|
|
94
57
|
const visibleNodes = (0, react.useMemo)(() => {
|
|
95
58
|
const referencedIds = /* @__PURE__ */ new Set();
|
|
96
59
|
for (const link of visibleLinks) {
|
|
@@ -118,6 +81,10 @@ function SankeyChart({ nodes, links, categoryColors = SANKEY_CATEGORY_COLORS, de
|
|
|
118
81
|
nodes: visibleNodes.map((node) => ({ ...node })),
|
|
119
82
|
links: visibleLinks.map((link) => ({ ...link }))
|
|
120
83
|
}), [visibleNodes, visibleLinks]);
|
|
84
|
+
const nodeAlign = (0, react.useMemo)(() => {
|
|
85
|
+
const keep = new Set(keepAtDepthIds);
|
|
86
|
+
return (node, n) => keep.has(node.id) ? node.depth ?? 0 : (0, _visx_sankey.sankeyJustify)(node, n);
|
|
87
|
+
}, [keepAtDepthIds]);
|
|
121
88
|
const innerWidth = Math.max(0, width - labelPadding - LEFT_INSET);
|
|
122
89
|
const innerHeight = Math.max(0, height - TOP_INSET * 2);
|
|
123
90
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -137,7 +104,7 @@ function SankeyChart({ nodes, links, categoryColors = SANKEY_CATEGORY_COLORS, de
|
|
|
137
104
|
size: [innerWidth, innerHeight],
|
|
138
105
|
nodeWidth,
|
|
139
106
|
nodePadding,
|
|
140
|
-
nodeAlign
|
|
107
|
+
nodeAlign,
|
|
141
108
|
nodeId: (node) => node.id,
|
|
142
109
|
nodeSort,
|
|
143
110
|
children: ({ graph: laidOut, createPath }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_visx_group.Group, {
|
|
@@ -183,7 +150,7 @@ function SankeyChart({ nodes, links, categoryColors = SANKEY_CATEGORY_COLORS, de
|
|
|
183
150
|
dominantBaseline: "alphabetic",
|
|
184
151
|
fontSize: 11,
|
|
185
152
|
fontWeight: 600,
|
|
186
|
-
fill: "var(--
|
|
153
|
+
fill: "var(--neutral-a12)",
|
|
187
154
|
children: node.label
|
|
188
155
|
}),
|
|
189
156
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("foreignObject", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sankey-chart.cjs","names":["SectionTitle","Group","Sankey","sankeyJustify","NumberFlow"],"sources":["../../src/components/sankey-chart.tsx"],"sourcesContent":["import { Group } from '@visx/group'\nimport {\n Sankey,\n sankeyJustify,\n type SankeyLink,\n type SankeyNode,\n} from '@visx/sankey'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { cn } from '../utils/cn'\nimport NumberFlow, { type Format } from './number-flow'\nimport { SectionTitle } from './typography'\n\nconst SANKEY_CATEGORY_COLORS: Record<string, string> = {\n solar: 'var(--orange-a9)',\n battery: 'var(--green-a9)',\n grid: 'var(--blue-9)',\n home: 'var(--spark-9)',\n floor: 'var(--gray-a9)',\n device: 'var(--gray-a8)',\n}\n\nexport interface SankeyChartNode {\n id: string\n label: string\n /** Optional category — used to colour the node and any outgoing links. */\n category?: string\n /** Override the colour resolved from `category`. */\n color?: string\n}\n\nexport interface SankeyChartLink {\n source: string\n target: string\n value: number\n /** Override the colour of the link. Defaults to the source node colour. */\n color?: string\n}\n\nexport interface SankeyChartProps {\n nodes: SankeyChartNode[]\n links: SankeyChartLink[]\n /** Resolves a category to a colour. */\n categoryColors?: Record<string, string>\n /** Fallback colour for nodes/links without a category. */\n defaultColor?: string\n /** Suffix appended to each animated value label (e.g. \" W\"). */\n valueSuffix?: string\n /** NumberFlow `format` for value labels. */\n valueFormat?: Format\n height?: number\n nodeWidth?: number\n nodePadding?: number\n /** Reserved horizontal space on the right for labels. */\n labelPadding?: number\n title?: string\n className?: string\n}\n\ntype SankeyDatumNode = SankeyChartNode & {\n /** Computed by d3-sankey; present after layout. */\n}\n\ntype SankeyDatumLink = SankeyChartLink\n\nconst DEFAULT_HEIGHT = 260\nconst DEFAULT_NODE_WIDTH = 12\nconst DEFAULT_NODE_PADDING = 10\nconst DEFAULT_LABEL_PADDING = 96\nconst LEFT_INSET = 4\nconst TOP_INSET = 8\nconst LABEL_GAP = 8\n/** Approx. cap/ascender height of the node name above its alphabetic baseline. */\nconst LABEL_NAME_HEIGHT = 11\n/** Vertical offset of the value box relative to the name baseline. Negative pulls\n * the value up toward the name to tighten the gap between them. */\nconst LABEL_VALUE_GAP = -4\n/** Height of the value box rendered below the node name. */\nconst VALUE_HEIGHT = 16\nconst DEFAULT_COLOR = 'var(--gray-a9)'\nconst ANIMATION_DURATION_MS = 500\n\nfunction resolveNodeColor(\n node: SankeyChartNode,\n categoryColors: Record<string, string> | undefined,\n defaultColor: string,\n) {\n if (node.color) return node.color\n if (node.category && categoryColors?.[node.category]) {\n return categoryColors[node.category] as string\n }\n return defaultColor\n}\n\nfunction easeOutCubic(t: number) {\n return 1 - (1 - t) ** 3\n}\n\nfunction linkKey(link: SankeyChartLink) {\n return `${link.source}-${link.target}`\n}\n\n/**\n * Interpolates link values toward `links` over ~500ms so visx re-layouts each\n * frame, smoothly tweening node rects and link paths.\n */\nfunction useAnimatedLinks(links: SankeyChartLink[]): SankeyChartLink[] {\n const [animated, setAnimated] = useState<SankeyChartLink[]>(links)\n const animatedRef = useRef<SankeyChartLink[]>(links)\n\n useEffect(() => {\n const fromByKey = new Map(animatedRef.current.map((l) => [linkKey(l), l]))\n const start = performance.now()\n let raf = 0\n\n const tick = (now: number) => {\n const t = Math.min(1, (now - start) / ANIMATION_DURATION_MS)\n const eased = easeOutCubic(t)\n const next = links.map((toLink) => {\n const fromValue = fromByKey.get(linkKey(toLink))?.value ?? toLink.value\n return {\n ...toLink,\n value: Math.round(fromValue + (toLink.value - fromValue) * eased),\n }\n })\n animatedRef.current = next\n setAnimated(next)\n if (t < 1) raf = requestAnimationFrame(tick)\n }\n\n raf = requestAnimationFrame(tick)\n return () => cancelAnimationFrame(raf)\n }, [links])\n\n return animated\n}\n\nfunction SankeyChart({\n nodes,\n links,\n categoryColors = SANKEY_CATEGORY_COLORS,\n defaultColor = DEFAULT_COLOR,\n valueSuffix,\n valueFormat,\n height = DEFAULT_HEIGHT,\n nodeWidth = DEFAULT_NODE_WIDTH,\n nodePadding = DEFAULT_NODE_PADDING,\n labelPadding = DEFAULT_LABEL_PADDING,\n title,\n className,\n}: SankeyChartProps): React.ReactElement {\n const containerRef = useRef<HTMLDivElement>(null)\n const [width, setWidth] = useState(0)\n\n useEffect(() => {\n const element = containerRef.current\n if (!element) return\n\n const resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry) return\n setWidth(entry.contentRect.width)\n })\n\n resizeObserver.observe(element)\n return () => resizeObserver.disconnect()\n }, [])\n\n const animatedLinks = useAnimatedLinks(links)\n\n const nodeIds = useMemo(() => new Set(nodes.map((node) => node.id)), [nodes])\n\n // Drop links whose endpoints aren't in the current node set. `animatedLinks`\n // lags `nodes` by a frame while tweening, so during a node add/remove a link\n // can momentarily reference a node that no longer exists — which makes\n // d3-sankey throw `missing: <id>`.\n const visibleLinks = useMemo(\n () =>\n animatedLinks.filter(\n (link) =>\n link.value > 0 &&\n nodeIds.has(link.source) &&\n nodeIds.has(link.target),\n ),\n [animatedLinks, nodeIds],\n )\n\n const visibleNodes = useMemo(() => {\n const referencedIds = new Set<string>()\n for (const link of visibleLinks) {\n referencedIds.add(link.source)\n referencedIds.add(link.target)\n }\n return nodes.filter((node) => referencedIds.has(node.id))\n }, [nodes, visibleLinks])\n\n const colorById = useMemo(() => {\n const map = new Map<string, string>()\n for (const node of nodes) {\n map.set(node.id, resolveNodeColor(node, categoryColors, defaultColor))\n }\n return map\n }, [categoryColors, defaultColor, nodes])\n\n const orderById = useMemo(() => {\n const map = new Map<string, number>()\n visibleNodes.forEach((node, index) => map.set(node.id, index))\n return map\n }, [visibleNodes])\n\n const nodeSort = useMemo(\n () =>\n (\n a: SankeyNode<SankeyDatumNode, SankeyDatumLink>,\n b: SankeyNode<SankeyDatumNode, SankeyDatumLink>,\n ) =>\n (orderById.get(a.id) ?? 0) - (orderById.get(b.id) ?? 0),\n [orderById],\n )\n\n const graph = useMemo(\n () => ({\n nodes: visibleNodes.map((node) => ({ ...node })),\n links: visibleLinks.map((link) => ({ ...link })),\n }),\n [visibleNodes, visibleLinks],\n )\n\n const innerWidth = Math.max(0, width - labelPadding - LEFT_INSET)\n const innerHeight = Math.max(0, height - TOP_INSET * 2)\n\n return (\n <div className={cn('flex w-full flex-col gap-2 py-2', className)}>\n {title && <SectionTitle>{title}</SectionTitle>}\n\n <div ref={containerRef} className=\"w-full\" style={{ height }}>\n {width > 0 && innerWidth > 0 && (\n <svg width={width} height={height}>\n <Group left={LEFT_INSET} top={TOP_INSET}>\n <Sankey<SankeyDatumNode, SankeyDatumLink>\n root={graph}\n size={[innerWidth, innerHeight]}\n nodeWidth={nodeWidth}\n nodePadding={nodePadding}\n nodeAlign={sankeyJustify}\n nodeId={(node) => node.id}\n nodeSort={nodeSort}\n >\n {({ graph: laidOut, createPath }) => (\n <>\n <Group className=\"visx-sankey-links\">\n {laidOut.links.map((link, index) => {\n const sourceId =\n typeof link.source === 'object'\n ? (\n link.source as SankeyNode<\n SankeyDatumNode,\n SankeyDatumLink\n >\n ).id\n : String(link.source)\n const stroke =\n link.color ?? colorById.get(sourceId) ?? defaultColor\n\n return (\n <path\n key={`sankey-link-${index}`}\n d={createPath(link) ?? ''}\n fill=\"none\"\n stroke={stroke}\n strokeOpacity={0.32}\n strokeWidth={Math.max(1, link.width ?? 0)}\n />\n )\n })}\n </Group>\n\n <Group className=\"visx-sankey-nodes\">\n {laidOut.nodes.map((node, index) => {\n if (\n node.x0 === undefined ||\n node.x1 === undefined ||\n node.y0 === undefined ||\n node.y1 === undefined\n ) {\n return null\n }\n\n const w = node.x1 - node.x0\n const h = node.y1 - node.y0\n const fill = colorById.get(node.id) ?? defaultColor\n\n const labelX = node.x1 + LABEL_GAP\n // Center the name+value label on the node, then clamp it\n // vertically so a node flush against an edge (e.g. a tiny\n // \"grid\" node at the top) doesn't push its name past the\n // chart bounds and get clipped. The name's ascenders sit\n // above labelY; the value box sits below it.\n const labelY = Math.min(\n Math.max(\n node.y0 + h / 2 - 4,\n LABEL_NAME_HEIGHT - TOP_INSET,\n ),\n innerHeight + TOP_INSET - VALUE_HEIGHT,\n )\n const labelAnchor = 'start' as const\n const primaryDy = 0\n const valueWidth = Math.max(\n 40,\n labelPadding - LABEL_GAP - 4,\n )\n const valueHeight = VALUE_HEIGHT\n const valueY = labelY + LABEL_VALUE_GAP\n\n return (\n <Group key={`sankey-node-${index}`}>\n <rect\n x={node.x0}\n y={node.y0}\n width={w}\n height={Math.max(1, h)}\n fill={fill}\n rx={2}\n />\n <text\n x={labelX}\n y={labelY + primaryDy}\n textAnchor={labelAnchor}\n dominantBaseline=\"alphabetic\"\n fontSize={11}\n fontWeight={600}\n fill=\"var(--gray-a12)\"\n >\n {node.label}\n </text>\n <foreignObject\n x={labelX}\n y={valueY}\n width={valueWidth}\n height={valueHeight}\n style={{ overflow: 'visible' }}\n >\n <div\n style={{\n lineHeight: 1.2,\n overflow: 'visible',\n }}\n >\n <NumberFlow\n value={node.value ?? 0}\n {...(valueSuffix !== undefined && {\n suffix: valueSuffix,\n })}\n {...(valueFormat !== undefined && {\n format: valueFormat,\n })}\n className=\"text-[10px] text-gray-light tabular-nums\"\n />\n </div>\n </foreignObject>\n </Group>\n )\n })}\n </Group>\n </>\n )}\n </Sankey>\n </Group>\n </svg>\n )}\n </div>\n </div>\n )\n}\n\nexport type { SankeyLink, SankeyNode }\nexport { SankeyChart, SANKEY_CATEGORY_COLORS }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sankey-chart.cjs","names":["SectionTitle","Group","Sankey","NumberFlow"],"sources":["../../src/components/sankey-chart.tsx"],"sourcesContent":["import { Group } from '@visx/group'\nimport {\n Sankey,\n sankeyJustify,\n type SankeyLink,\n type SankeyNode,\n} from '@visx/sankey'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { cn } from '../utils/cn'\nimport NumberFlow, { type Format } from './number-flow'\nimport { SectionTitle } from './typography'\n\nconst SANKEY_CATEGORY_COLORS: Record<string, string> = {\n solar: 'var(--orange-a9)',\n battery: 'var(--green-a9)',\n grid: 'var(--blue-9)',\n gridFeedIn: 'var(--blue-7)',\n home: 'var(--spark-9)',\n floor: 'var(--neutral-a9)',\n device: 'var(--neutral-a8)',\n}\n\nexport interface SankeyChartNode {\n id: string\n label: string\n /** Optional category — used to colour the node and any outgoing links. */\n category?: string\n /** Override the colour resolved from `category`. */\n color?: string\n}\n\nexport interface SankeyChartLink {\n source: string\n target: string\n value: number\n /** Override the colour of the link. Defaults to the source node colour. */\n color?: string\n}\n\nexport interface SankeyChartProps {\n nodes: SankeyChartNode[]\n links: SankeyChartLink[]\n /** Resolves a category to a colour. */\n categoryColors?: Record<string, string>\n /** Fallback colour for nodes/links without a category. */\n defaultColor?: string\n /** Suffix appended to each animated value label (e.g. \" W\"). */\n valueSuffix?: string\n /** NumberFlow `format` for value labels. */\n valueFormat?: Format\n height?: number\n nodeWidth?: number\n nodePadding?: number\n /** Reserved horizontal space on the right for labels. */\n labelPadding?: number\n /**\n * Node ids kept at their natural (computed) column instead of being pushed to\n * the last column. By default `sankeyJustify` sends every node with no\n * outgoing link to the right edge; use this to hold a terminal node (e.g. grid\n * feed-in) in a middle column alongside its siblings.\n */\n keepAtDepthIds?: readonly string[]\n title?: string\n className?: string\n}\n\ntype SankeyDatumNode = SankeyChartNode & {\n /** Computed by d3-sankey; present after layout. */\n}\n\ntype SankeyDatumLink = SankeyChartLink\n\nconst DEFAULT_HEIGHT = 260\nconst DEFAULT_NODE_WIDTH = 12\nconst DEFAULT_NODE_PADDING = 10\nconst DEFAULT_LABEL_PADDING = 96\nconst LEFT_INSET = 4\nconst TOP_INSET = 8\nconst LABEL_GAP = 8\n/** Approx. cap/ascender height of the node name above its alphabetic baseline. */\nconst LABEL_NAME_HEIGHT = 11\n/** Vertical offset of the value box relative to the name baseline. Negative pulls\n * the value up toward the name to tighten the gap between them. */\nconst LABEL_VALUE_GAP = -4\n/** Height of the value box rendered below the node name. */\nconst VALUE_HEIGHT = 16\nconst DEFAULT_COLOR = 'var(--neutral-a9)'\n\nfunction resolveNodeColor(\n node: SankeyChartNode,\n categoryColors: Record<string, string> | undefined,\n defaultColor: string,\n) {\n if (node.color) return node.color\n if (node.category && categoryColors?.[node.category]) {\n return categoryColors[node.category] as string\n }\n return defaultColor\n}\n\nfunction SankeyChart({\n nodes,\n links,\n categoryColors = SANKEY_CATEGORY_COLORS,\n defaultColor = DEFAULT_COLOR,\n valueSuffix,\n valueFormat,\n height = DEFAULT_HEIGHT,\n nodeWidth = DEFAULT_NODE_WIDTH,\n nodePadding = DEFAULT_NODE_PADDING,\n labelPadding = DEFAULT_LABEL_PADDING,\n keepAtDepthIds,\n title,\n className,\n}: SankeyChartProps): React.ReactElement {\n const containerRef = useRef<HTMLDivElement>(null)\n const [width, setWidth] = useState(0)\n\n useEffect(() => {\n const element = containerRef.current\n if (!element) return\n\n const resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry) return\n setWidth(entry.contentRect.width)\n })\n\n resizeObserver.observe(element)\n return () => resizeObserver.disconnect()\n }, [])\n\n const nodeIds = useMemo(() => new Set(nodes.map((node) => node.id)), [nodes])\n\n // Drop zero-value links and any whose endpoints aren't in the current node\n // set (a stale link referencing a removed node makes d3-sankey throw\n // `missing: <id>`).\n const visibleLinks = useMemo(\n () =>\n links.filter(\n (link) =>\n link.value > 0 &&\n nodeIds.has(link.source) &&\n nodeIds.has(link.target),\n ),\n [links, nodeIds],\n )\n\n const visibleNodes = useMemo(() => {\n const referencedIds = new Set<string>()\n for (const link of visibleLinks) {\n referencedIds.add(link.source)\n referencedIds.add(link.target)\n }\n return nodes.filter((node) => referencedIds.has(node.id))\n }, [nodes, visibleLinks])\n\n const colorById = useMemo(() => {\n const map = new Map<string, string>()\n for (const node of nodes) {\n map.set(node.id, resolveNodeColor(node, categoryColors, defaultColor))\n }\n return map\n }, [categoryColors, defaultColor, nodes])\n\n const orderById = useMemo(() => {\n const map = new Map<string, number>()\n visibleNodes.forEach((node, index) => map.set(node.id, index))\n return map\n }, [visibleNodes])\n\n const nodeSort = useMemo(\n () =>\n (\n a: SankeyNode<SankeyDatumNode, SankeyDatumLink>,\n b: SankeyNode<SankeyDatumNode, SankeyDatumLink>,\n ) =>\n (orderById.get(a.id) ?? 0) - (orderById.get(b.id) ?? 0),\n [orderById],\n )\n\n const graph = useMemo(\n () => ({\n nodes: visibleNodes.map((node) => ({ ...node })),\n links: visibleLinks.map((link) => ({ ...link })),\n }),\n [visibleNodes, visibleLinks],\n )\n\n const nodeAlign = useMemo(() => {\n const keep = new Set(keepAtDepthIds)\n return (node: SankeyNode<SankeyDatumNode, SankeyDatumLink>, n: number) =>\n keep.has(node.id) ? (node.depth ?? 0) : sankeyJustify(node, n)\n }, [keepAtDepthIds])\n\n const innerWidth = Math.max(0, width - labelPadding - LEFT_INSET)\n const innerHeight = Math.max(0, height - TOP_INSET * 2)\n\n return (\n <div className={cn('flex w-full flex-col gap-2 py-2', className)}>\n {title && <SectionTitle>{title}</SectionTitle>}\n\n <div ref={containerRef} className=\"w-full\" style={{ height }}>\n {width > 0 && innerWidth > 0 && (\n <svg width={width} height={height}>\n <Group left={LEFT_INSET} top={TOP_INSET}>\n <Sankey<SankeyDatumNode, SankeyDatumLink>\n root={graph}\n size={[innerWidth, innerHeight]}\n nodeWidth={nodeWidth}\n nodePadding={nodePadding}\n nodeAlign={nodeAlign}\n nodeId={(node) => node.id}\n nodeSort={nodeSort}\n >\n {({ graph: laidOut, createPath }) => (\n <>\n <Group className=\"visx-sankey-links\">\n {laidOut.links.map((link, index) => {\n const sourceId =\n typeof link.source === 'object'\n ? (\n link.source as SankeyNode<\n SankeyDatumNode,\n SankeyDatumLink\n >\n ).id\n : String(link.source)\n const stroke =\n link.color ?? colorById.get(sourceId) ?? defaultColor\n\n return (\n <path\n key={`sankey-link-${index}`}\n d={createPath(link) ?? ''}\n fill=\"none\"\n stroke={stroke}\n strokeOpacity={0.32}\n strokeWidth={Math.max(1, link.width ?? 0)}\n />\n )\n })}\n </Group>\n\n <Group className=\"visx-sankey-nodes\">\n {laidOut.nodes.map((node, index) => {\n if (\n node.x0 === undefined ||\n node.x1 === undefined ||\n node.y0 === undefined ||\n node.y1 === undefined\n ) {\n return null\n }\n\n const w = node.x1 - node.x0\n const h = node.y1 - node.y0\n const fill = colorById.get(node.id) ?? defaultColor\n\n const labelX = node.x1 + LABEL_GAP\n // Center the name+value label on the node, then clamp it\n // vertically so a node flush against an edge (e.g. a tiny\n // \"grid\" node at the top) doesn't push its name past the\n // chart bounds and get clipped. The name's ascenders sit\n // above labelY; the value box sits below it.\n const labelY = Math.min(\n Math.max(\n node.y0 + h / 2 - 4,\n LABEL_NAME_HEIGHT - TOP_INSET,\n ),\n innerHeight + TOP_INSET - VALUE_HEIGHT,\n )\n const labelAnchor = 'start' as const\n const primaryDy = 0\n const valueWidth = Math.max(\n 40,\n labelPadding - LABEL_GAP - 4,\n )\n const valueHeight = VALUE_HEIGHT\n const valueY = labelY + LABEL_VALUE_GAP\n\n return (\n <Group key={`sankey-node-${index}`}>\n <rect\n x={node.x0}\n y={node.y0}\n width={w}\n height={Math.max(1, h)}\n fill={fill}\n rx={2}\n />\n <text\n x={labelX}\n y={labelY + primaryDy}\n textAnchor={labelAnchor}\n dominantBaseline=\"alphabetic\"\n fontSize={11}\n fontWeight={600}\n fill=\"var(--neutral-a12)\"\n >\n {node.label}\n </text>\n <foreignObject\n x={labelX}\n y={valueY}\n width={valueWidth}\n height={valueHeight}\n style={{ overflow: 'visible' }}\n >\n <div\n style={{\n lineHeight: 1.2,\n overflow: 'visible',\n }}\n >\n <NumberFlow\n value={node.value ?? 0}\n {...(valueSuffix !== undefined && {\n suffix: valueSuffix,\n })}\n {...(valueFormat !== undefined && {\n format: valueFormat,\n })}\n className=\"text-[10px] text-gray-light tabular-nums\"\n />\n </div>\n </foreignObject>\n </Group>\n )\n })}\n </Group>\n </>\n )}\n </Sankey>\n </Group>\n </svg>\n )}\n </div>\n </div>\n )\n}\n\nexport type { SankeyLink, SankeyNode }\nexport { SankeyChart, SANKEY_CATEGORY_COLORS }\n"],"mappings":"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"}
|
|
@@ -36,6 +36,13 @@ interface SankeyChartProps {
|
|
|
36
36
|
nodePadding?: number;
|
|
37
37
|
/** Reserved horizontal space on the right for labels. */
|
|
38
38
|
labelPadding?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Node ids kept at their natural (computed) column instead of being pushed to
|
|
41
|
+
* the last column. By default `sankeyJustify` sends every node with no
|
|
42
|
+
* outgoing link to the right edge; use this to hold a terminal node (e.g. grid
|
|
43
|
+
* feed-in) in a middle column alongside its siblings.
|
|
44
|
+
*/
|
|
45
|
+
keepAtDepthIds?: readonly string[];
|
|
39
46
|
title?: string;
|
|
40
47
|
className?: string;
|
|
41
48
|
}
|
|
@@ -50,6 +57,7 @@ declare function SankeyChart({
|
|
|
50
57
|
nodeWidth,
|
|
51
58
|
nodePadding,
|
|
52
59
|
labelPadding,
|
|
60
|
+
keepAtDepthIds,
|
|
53
61
|
title,
|
|
54
62
|
className
|
|
55
63
|
}: SankeyChartProps): React.ReactElement;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { t as Format } from "../number-flow-
|
|
3
|
+
import { t as Format } from "../number-flow-D9zsxH0-.js";
|
|
4
4
|
import { SankeyLink, SankeyNode } from "@visx/sankey";
|
|
5
5
|
|
|
6
6
|
//#region src/components/sankey-chart.d.ts
|
|
@@ -36,6 +36,13 @@ interface SankeyChartProps {
|
|
|
36
36
|
nodePadding?: number;
|
|
37
37
|
/** Reserved horizontal space on the right for labels. */
|
|
38
38
|
labelPadding?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Node ids kept at their natural (computed) column instead of being pushed to
|
|
41
|
+
* the last column. By default `sankeyJustify` sends every node with no
|
|
42
|
+
* outgoing link to the right edge; use this to hold a terminal node (e.g. grid
|
|
43
|
+
* feed-in) in a middle column alongside its siblings.
|
|
44
|
+
*/
|
|
45
|
+
keepAtDepthIds?: readonly string[];
|
|
39
46
|
title?: string;
|
|
40
47
|
className?: string;
|
|
41
48
|
}
|
|
@@ -50,6 +57,7 @@ declare function SankeyChart({
|
|
|
50
57
|
nodeWidth,
|
|
51
58
|
nodePadding,
|
|
52
59
|
labelPadding,
|
|
60
|
+
keepAtDepthIds,
|
|
53
61
|
title,
|
|
54
62
|
className
|
|
55
63
|
}: SankeyChartProps): React.ReactElement;
|
|
@@ -11,9 +11,10 @@ const SANKEY_CATEGORY_COLORS = {
|
|
|
11
11
|
solar: "var(--orange-a9)",
|
|
12
12
|
battery: "var(--green-a9)",
|
|
13
13
|
grid: "var(--blue-9)",
|
|
14
|
+
gridFeedIn: "var(--blue-7)",
|
|
14
15
|
home: "var(--spark-9)",
|
|
15
|
-
floor: "var(--
|
|
16
|
-
device: "var(--
|
|
16
|
+
floor: "var(--neutral-a9)",
|
|
17
|
+
device: "var(--neutral-a8)"
|
|
17
18
|
};
|
|
18
19
|
const DEFAULT_HEIGHT = 260;
|
|
19
20
|
const DEFAULT_NODE_WIDTH = 12;
|
|
@@ -29,50 +30,13 @@ const LABEL_NAME_HEIGHT = 11;
|
|
|
29
30
|
const LABEL_VALUE_GAP = -4;
|
|
30
31
|
/** Height of the value box rendered below the node name. */
|
|
31
32
|
const VALUE_HEIGHT = 16;
|
|
32
|
-
const DEFAULT_COLOR = "var(--
|
|
33
|
-
const ANIMATION_DURATION_MS = 500;
|
|
33
|
+
const DEFAULT_COLOR = "var(--neutral-a9)";
|
|
34
34
|
function resolveNodeColor(node, categoryColors, defaultColor) {
|
|
35
35
|
if (node.color) return node.color;
|
|
36
36
|
if (node.category && categoryColors?.[node.category]) return categoryColors[node.category];
|
|
37
37
|
return defaultColor;
|
|
38
38
|
}
|
|
39
|
-
function
|
|
40
|
-
return 1 - (1 - t) ** 3;
|
|
41
|
-
}
|
|
42
|
-
function linkKey(link) {
|
|
43
|
-
return `${link.source}-${link.target}`;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Interpolates link values toward `links` over ~500ms so visx re-layouts each
|
|
47
|
-
* frame, smoothly tweening node rects and link paths.
|
|
48
|
-
*/
|
|
49
|
-
function useAnimatedLinks(links) {
|
|
50
|
-
const [animated, setAnimated] = useState(links);
|
|
51
|
-
const animatedRef = useRef(links);
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
const fromByKey = new Map(animatedRef.current.map((l) => [linkKey(l), l]));
|
|
54
|
-
const start = performance.now();
|
|
55
|
-
let raf = 0;
|
|
56
|
-
const tick = (now) => {
|
|
57
|
-
const t = Math.min(1, (now - start) / ANIMATION_DURATION_MS);
|
|
58
|
-
const eased = easeOutCubic(t);
|
|
59
|
-
const next = links.map((toLink) => {
|
|
60
|
-
const fromValue = fromByKey.get(linkKey(toLink))?.value ?? toLink.value;
|
|
61
|
-
return {
|
|
62
|
-
...toLink,
|
|
63
|
-
value: Math.round(fromValue + (toLink.value - fromValue) * eased)
|
|
64
|
-
};
|
|
65
|
-
});
|
|
66
|
-
animatedRef.current = next;
|
|
67
|
-
setAnimated(next);
|
|
68
|
-
if (t < 1) raf = requestAnimationFrame(tick);
|
|
69
|
-
};
|
|
70
|
-
raf = requestAnimationFrame(tick);
|
|
71
|
-
return () => cancelAnimationFrame(raf);
|
|
72
|
-
}, [links]);
|
|
73
|
-
return animated;
|
|
74
|
-
}
|
|
75
|
-
function SankeyChart({ nodes, links, categoryColors = SANKEY_CATEGORY_COLORS, defaultColor = DEFAULT_COLOR, valueSuffix, valueFormat, height = DEFAULT_HEIGHT, nodeWidth = DEFAULT_NODE_WIDTH, nodePadding = DEFAULT_NODE_PADDING, labelPadding = DEFAULT_LABEL_PADDING, title, className }) {
|
|
39
|
+
function SankeyChart({ nodes, links, categoryColors = SANKEY_CATEGORY_COLORS, defaultColor = DEFAULT_COLOR, valueSuffix, valueFormat, height = DEFAULT_HEIGHT, nodeWidth = DEFAULT_NODE_WIDTH, nodePadding = DEFAULT_NODE_PADDING, labelPadding = DEFAULT_LABEL_PADDING, keepAtDepthIds, title, className }) {
|
|
76
40
|
const containerRef = useRef(null);
|
|
77
41
|
const [width, setWidth] = useState(0);
|
|
78
42
|
useEffect(() => {
|
|
@@ -86,9 +50,8 @@ function SankeyChart({ nodes, links, categoryColors = SANKEY_CATEGORY_COLORS, de
|
|
|
86
50
|
resizeObserver.observe(element);
|
|
87
51
|
return () => resizeObserver.disconnect();
|
|
88
52
|
}, []);
|
|
89
|
-
const animatedLinks = useAnimatedLinks(links);
|
|
90
53
|
const nodeIds = useMemo(() => new Set(nodes.map((node) => node.id)), [nodes]);
|
|
91
|
-
const visibleLinks = useMemo(() =>
|
|
54
|
+
const visibleLinks = useMemo(() => links.filter((link) => link.value > 0 && nodeIds.has(link.source) && nodeIds.has(link.target)), [links, nodeIds]);
|
|
92
55
|
const visibleNodes = useMemo(() => {
|
|
93
56
|
const referencedIds = /* @__PURE__ */ new Set();
|
|
94
57
|
for (const link of visibleLinks) {
|
|
@@ -116,6 +79,10 @@ function SankeyChart({ nodes, links, categoryColors = SANKEY_CATEGORY_COLORS, de
|
|
|
116
79
|
nodes: visibleNodes.map((node) => ({ ...node })),
|
|
117
80
|
links: visibleLinks.map((link) => ({ ...link }))
|
|
118
81
|
}), [visibleNodes, visibleLinks]);
|
|
82
|
+
const nodeAlign = useMemo(() => {
|
|
83
|
+
const keep = new Set(keepAtDepthIds);
|
|
84
|
+
return (node, n) => keep.has(node.id) ? node.depth ?? 0 : sankeyJustify(node, n);
|
|
85
|
+
}, [keepAtDepthIds]);
|
|
119
86
|
const innerWidth = Math.max(0, width - labelPadding - LEFT_INSET);
|
|
120
87
|
const innerHeight = Math.max(0, height - TOP_INSET * 2);
|
|
121
88
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -135,7 +102,7 @@ function SankeyChart({ nodes, links, categoryColors = SANKEY_CATEGORY_COLORS, de
|
|
|
135
102
|
size: [innerWidth, innerHeight],
|
|
136
103
|
nodeWidth,
|
|
137
104
|
nodePadding,
|
|
138
|
-
nodeAlign
|
|
105
|
+
nodeAlign,
|
|
139
106
|
nodeId: (node) => node.id,
|
|
140
107
|
nodeSort,
|
|
141
108
|
children: ({ graph: laidOut, createPath }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Group, {
|
|
@@ -181,7 +148,7 @@ function SankeyChart({ nodes, links, categoryColors = SANKEY_CATEGORY_COLORS, de
|
|
|
181
148
|
dominantBaseline: "alphabetic",
|
|
182
149
|
fontSize: 11,
|
|
183
150
|
fontWeight: 600,
|
|
184
|
-
fill: "var(--
|
|
151
|
+
fill: "var(--neutral-a12)",
|
|
185
152
|
children: node.label
|
|
186
153
|
}),
|
|
187
154
|
/* @__PURE__ */ jsx("foreignObject", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sankey-chart.js","names":[],"sources":["../../src/components/sankey-chart.tsx"],"sourcesContent":["import { Group } from '@visx/group'\nimport {\n Sankey,\n sankeyJustify,\n type SankeyLink,\n type SankeyNode,\n} from '@visx/sankey'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { cn } from '../utils/cn'\nimport NumberFlow, { type Format } from './number-flow'\nimport { SectionTitle } from './typography'\n\nconst SANKEY_CATEGORY_COLORS: Record<string, string> = {\n solar: 'var(--orange-a9)',\n battery: 'var(--green-a9)',\n grid: 'var(--blue-9)',\n home: 'var(--spark-9)',\n floor: 'var(--gray-a9)',\n device: 'var(--gray-a8)',\n}\n\nexport interface SankeyChartNode {\n id: string\n label: string\n /** Optional category — used to colour the node and any outgoing links. */\n category?: string\n /** Override the colour resolved from `category`. */\n color?: string\n}\n\nexport interface SankeyChartLink {\n source: string\n target: string\n value: number\n /** Override the colour of the link. Defaults to the source node colour. */\n color?: string\n}\n\nexport interface SankeyChartProps {\n nodes: SankeyChartNode[]\n links: SankeyChartLink[]\n /** Resolves a category to a colour. */\n categoryColors?: Record<string, string>\n /** Fallback colour for nodes/links without a category. */\n defaultColor?: string\n /** Suffix appended to each animated value label (e.g. \" W\"). */\n valueSuffix?: string\n /** NumberFlow `format` for value labels. */\n valueFormat?: Format\n height?: number\n nodeWidth?: number\n nodePadding?: number\n /** Reserved horizontal space on the right for labels. */\n labelPadding?: number\n title?: string\n className?: string\n}\n\ntype SankeyDatumNode = SankeyChartNode & {\n /** Computed by d3-sankey; present after layout. */\n}\n\ntype SankeyDatumLink = SankeyChartLink\n\nconst DEFAULT_HEIGHT = 260\nconst DEFAULT_NODE_WIDTH = 12\nconst DEFAULT_NODE_PADDING = 10\nconst DEFAULT_LABEL_PADDING = 96\nconst LEFT_INSET = 4\nconst TOP_INSET = 8\nconst LABEL_GAP = 8\n/** Approx. cap/ascender height of the node name above its alphabetic baseline. */\nconst LABEL_NAME_HEIGHT = 11\n/** Vertical offset of the value box relative to the name baseline. Negative pulls\n * the value up toward the name to tighten the gap between them. */\nconst LABEL_VALUE_GAP = -4\n/** Height of the value box rendered below the node name. */\nconst VALUE_HEIGHT = 16\nconst DEFAULT_COLOR = 'var(--gray-a9)'\nconst ANIMATION_DURATION_MS = 500\n\nfunction resolveNodeColor(\n node: SankeyChartNode,\n categoryColors: Record<string, string> | undefined,\n defaultColor: string,\n) {\n if (node.color) return node.color\n if (node.category && categoryColors?.[node.category]) {\n return categoryColors[node.category] as string\n }\n return defaultColor\n}\n\nfunction easeOutCubic(t: number) {\n return 1 - (1 - t) ** 3\n}\n\nfunction linkKey(link: SankeyChartLink) {\n return `${link.source}-${link.target}`\n}\n\n/**\n * Interpolates link values toward `links` over ~500ms so visx re-layouts each\n * frame, smoothly tweening node rects and link paths.\n */\nfunction useAnimatedLinks(links: SankeyChartLink[]): SankeyChartLink[] {\n const [animated, setAnimated] = useState<SankeyChartLink[]>(links)\n const animatedRef = useRef<SankeyChartLink[]>(links)\n\n useEffect(() => {\n const fromByKey = new Map(animatedRef.current.map((l) => [linkKey(l), l]))\n const start = performance.now()\n let raf = 0\n\n const tick = (now: number) => {\n const t = Math.min(1, (now - start) / ANIMATION_DURATION_MS)\n const eased = easeOutCubic(t)\n const next = links.map((toLink) => {\n const fromValue = fromByKey.get(linkKey(toLink))?.value ?? toLink.value\n return {\n ...toLink,\n value: Math.round(fromValue + (toLink.value - fromValue) * eased),\n }\n })\n animatedRef.current = next\n setAnimated(next)\n if (t < 1) raf = requestAnimationFrame(tick)\n }\n\n raf = requestAnimationFrame(tick)\n return () => cancelAnimationFrame(raf)\n }, [links])\n\n return animated\n}\n\nfunction SankeyChart({\n nodes,\n links,\n categoryColors = SANKEY_CATEGORY_COLORS,\n defaultColor = DEFAULT_COLOR,\n valueSuffix,\n valueFormat,\n height = DEFAULT_HEIGHT,\n nodeWidth = DEFAULT_NODE_WIDTH,\n nodePadding = DEFAULT_NODE_PADDING,\n labelPadding = DEFAULT_LABEL_PADDING,\n title,\n className,\n}: SankeyChartProps): React.ReactElement {\n const containerRef = useRef<HTMLDivElement>(null)\n const [width, setWidth] = useState(0)\n\n useEffect(() => {\n const element = containerRef.current\n if (!element) return\n\n const resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry) return\n setWidth(entry.contentRect.width)\n })\n\n resizeObserver.observe(element)\n return () => resizeObserver.disconnect()\n }, [])\n\n const animatedLinks = useAnimatedLinks(links)\n\n const nodeIds = useMemo(() => new Set(nodes.map((node) => node.id)), [nodes])\n\n // Drop links whose endpoints aren't in the current node set. `animatedLinks`\n // lags `nodes` by a frame while tweening, so during a node add/remove a link\n // can momentarily reference a node that no longer exists — which makes\n // d3-sankey throw `missing: <id>`.\n const visibleLinks = useMemo(\n () =>\n animatedLinks.filter(\n (link) =>\n link.value > 0 &&\n nodeIds.has(link.source) &&\n nodeIds.has(link.target),\n ),\n [animatedLinks, nodeIds],\n )\n\n const visibleNodes = useMemo(() => {\n const referencedIds = new Set<string>()\n for (const link of visibleLinks) {\n referencedIds.add(link.source)\n referencedIds.add(link.target)\n }\n return nodes.filter((node) => referencedIds.has(node.id))\n }, [nodes, visibleLinks])\n\n const colorById = useMemo(() => {\n const map = new Map<string, string>()\n for (const node of nodes) {\n map.set(node.id, resolveNodeColor(node, categoryColors, defaultColor))\n }\n return map\n }, [categoryColors, defaultColor, nodes])\n\n const orderById = useMemo(() => {\n const map = new Map<string, number>()\n visibleNodes.forEach((node, index) => map.set(node.id, index))\n return map\n }, [visibleNodes])\n\n const nodeSort = useMemo(\n () =>\n (\n a: SankeyNode<SankeyDatumNode, SankeyDatumLink>,\n b: SankeyNode<SankeyDatumNode, SankeyDatumLink>,\n ) =>\n (orderById.get(a.id) ?? 0) - (orderById.get(b.id) ?? 0),\n [orderById],\n )\n\n const graph = useMemo(\n () => ({\n nodes: visibleNodes.map((node) => ({ ...node })),\n links: visibleLinks.map((link) => ({ ...link })),\n }),\n [visibleNodes, visibleLinks],\n )\n\n const innerWidth = Math.max(0, width - labelPadding - LEFT_INSET)\n const innerHeight = Math.max(0, height - TOP_INSET * 2)\n\n return (\n <div className={cn('flex w-full flex-col gap-2 py-2', className)}>\n {title && <SectionTitle>{title}</SectionTitle>}\n\n <div ref={containerRef} className=\"w-full\" style={{ height }}>\n {width > 0 && innerWidth > 0 && (\n <svg width={width} height={height}>\n <Group left={LEFT_INSET} top={TOP_INSET}>\n <Sankey<SankeyDatumNode, SankeyDatumLink>\n root={graph}\n size={[innerWidth, innerHeight]}\n nodeWidth={nodeWidth}\n nodePadding={nodePadding}\n nodeAlign={sankeyJustify}\n nodeId={(node) => node.id}\n nodeSort={nodeSort}\n >\n {({ graph: laidOut, createPath }) => (\n <>\n <Group className=\"visx-sankey-links\">\n {laidOut.links.map((link, index) => {\n const sourceId =\n typeof link.source === 'object'\n ? (\n link.source as SankeyNode<\n SankeyDatumNode,\n SankeyDatumLink\n >\n ).id\n : String(link.source)\n const stroke =\n link.color ?? colorById.get(sourceId) ?? defaultColor\n\n return (\n <path\n key={`sankey-link-${index}`}\n d={createPath(link) ?? ''}\n fill=\"none\"\n stroke={stroke}\n strokeOpacity={0.32}\n strokeWidth={Math.max(1, link.width ?? 0)}\n />\n )\n })}\n </Group>\n\n <Group className=\"visx-sankey-nodes\">\n {laidOut.nodes.map((node, index) => {\n if (\n node.x0 === undefined ||\n node.x1 === undefined ||\n node.y0 === undefined ||\n node.y1 === undefined\n ) {\n return null\n }\n\n const w = node.x1 - node.x0\n const h = node.y1 - node.y0\n const fill = colorById.get(node.id) ?? defaultColor\n\n const labelX = node.x1 + LABEL_GAP\n // Center the name+value label on the node, then clamp it\n // vertically so a node flush against an edge (e.g. a tiny\n // \"grid\" node at the top) doesn't push its name past the\n // chart bounds and get clipped. The name's ascenders sit\n // above labelY; the value box sits below it.\n const labelY = Math.min(\n Math.max(\n node.y0 + h / 2 - 4,\n LABEL_NAME_HEIGHT - TOP_INSET,\n ),\n innerHeight + TOP_INSET - VALUE_HEIGHT,\n )\n const labelAnchor = 'start' as const\n const primaryDy = 0\n const valueWidth = Math.max(\n 40,\n labelPadding - LABEL_GAP - 4,\n )\n const valueHeight = VALUE_HEIGHT\n const valueY = labelY + LABEL_VALUE_GAP\n\n return (\n <Group key={`sankey-node-${index}`}>\n <rect\n x={node.x0}\n y={node.y0}\n width={w}\n height={Math.max(1, h)}\n fill={fill}\n rx={2}\n />\n <text\n x={labelX}\n y={labelY + primaryDy}\n textAnchor={labelAnchor}\n dominantBaseline=\"alphabetic\"\n fontSize={11}\n fontWeight={600}\n fill=\"var(--gray-a12)\"\n >\n {node.label}\n </text>\n <foreignObject\n x={labelX}\n y={valueY}\n width={valueWidth}\n height={valueHeight}\n style={{ overflow: 'visible' }}\n >\n <div\n style={{\n lineHeight: 1.2,\n overflow: 'visible',\n }}\n >\n <NumberFlow\n value={node.value ?? 0}\n {...(valueSuffix !== undefined && {\n suffix: valueSuffix,\n })}\n {...(valueFormat !== undefined && {\n format: valueFormat,\n })}\n className=\"text-[10px] text-gray-light tabular-nums\"\n />\n </div>\n </foreignObject>\n </Group>\n )\n })}\n </Group>\n </>\n )}\n </Sankey>\n </Group>\n </svg>\n )}\n </div>\n </div>\n )\n}\n\nexport type { SankeyLink, SankeyNode }\nexport { SankeyChart, SANKEY_CATEGORY_COLORS }\n"],"mappings":";;;;;;;;;AAYA,MAAM,yBAAiD;CACrD,OAAO;CACP,SAAS;CACT,MAAM;CACN,MAAM;CACN,OAAO;CACP,QAAQ;CACT;AA6CD,MAAM,iBAAiB;AACvB,MAAM,qBAAqB;AAC3B,MAAM,uBAAuB;AAC7B,MAAM,wBAAwB;AAC9B,MAAM,aAAa;AACnB,MAAM,YAAY;AAClB,MAAM,YAAY;;AAElB,MAAM,oBAAoB;;;AAG1B,MAAM,kBAAkB;;AAExB,MAAM,eAAe;AACrB,MAAM,gBAAgB;AACtB,MAAM,wBAAwB;AAE9B,SAAS,iBACP,MACA,gBACA,cACA;AACA,KAAI,KAAK,MAAO,QAAO,KAAK;AAC5B,KAAI,KAAK,YAAY,iBAAiB,KAAK,UACzC,QAAO,eAAe,KAAK;AAE7B,QAAO;;AAGT,SAAS,aAAa,GAAW;AAC/B,QAAO,KAAK,IAAI,MAAM;;AAGxB,SAAS,QAAQ,MAAuB;AACtC,QAAO,GAAG,KAAK,OAAO,GAAG,KAAK;;;;;;AAOhC,SAAS,iBAAiB,OAA6C;CACrE,MAAM,CAAC,UAAU,eAAe,SAA4B,MAAM;CAClE,MAAM,cAAc,OAA0B,MAAM;AAEpD,iBAAgB;EACd,MAAM,YAAY,IAAI,IAAI,YAAY,QAAQ,KAAK,MAAM,CAAC,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;EAC1E,MAAM,QAAQ,YAAY,KAAK;EAC/B,IAAI,MAAM;EAEV,MAAM,QAAQ,QAAgB;GAC5B,MAAM,IAAI,KAAK,IAAI,IAAI,MAAM,SAAS,sBAAsB;GAC5D,MAAM,QAAQ,aAAa,EAAE;GAC7B,MAAM,OAAO,MAAM,KAAK,WAAW;IACjC,MAAM,YAAY,UAAU,IAAI,QAAQ,OAAO,CAAC,EAAE,SAAS,OAAO;AAClE,WAAO;KACL,GAAG;KACH,OAAO,KAAK,MAAM,aAAa,OAAO,QAAQ,aAAa,MAAM;KAClE;KACD;AACF,eAAY,UAAU;AACtB,eAAY,KAAK;AACjB,OAAI,IAAI,EAAG,OAAM,sBAAsB,KAAK;;AAG9C,QAAM,sBAAsB,KAAK;AACjC,eAAa,qBAAqB,IAAI;IACrC,CAAC,MAAM,CAAC;AAEX,QAAO;;AAGT,SAAS,YAAY,EACnB,OACA,OACA,iBAAiB,wBACjB,eAAe,eACf,aACA,aACA,SAAS,gBACT,YAAY,oBACZ,cAAc,sBACd,eAAe,uBACf,OACA,aACuC;CACvC,MAAM,eAAe,OAAuB,KAAK;CACjD,MAAM,CAAC,OAAO,YAAY,SAAS,EAAE;AAErC,iBAAgB;EACd,MAAM,UAAU,aAAa;AAC7B,MAAI,CAAC,QAAS;EAEd,MAAM,iBAAiB,IAAI,gBAAgB,YAAY;GACrD,MAAM,QAAQ,QAAQ;AACtB,OAAI,CAAC,MAAO;AACZ,YAAS,MAAM,YAAY,MAAM;IACjC;AAEF,iBAAe,QAAQ,QAAQ;AAC/B,eAAa,eAAe,YAAY;IACvC,EAAE,CAAC;CAEN,MAAM,gBAAgB,iBAAiB,MAAM;CAE7C,MAAM,UAAU,cAAc,IAAI,IAAI,MAAM,KAAK,SAAS,KAAK,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC;CAM7E,MAAM,eAAe,cAEjB,cAAc,QACX,SACC,KAAK,QAAQ,KACb,QAAQ,IAAI,KAAK,OAAO,IACxB,QAAQ,IAAI,KAAK,OAAO,CAC3B,EACH,CAAC,eAAe,QAAQ,CACzB;CAED,MAAM,eAAe,cAAc;EACjC,MAAM,gCAAgB,IAAI,KAAa;AACvC,OAAK,MAAM,QAAQ,cAAc;AAC/B,iBAAc,IAAI,KAAK,OAAO;AAC9B,iBAAc,IAAI,KAAK,OAAO;;AAEhC,SAAO,MAAM,QAAQ,SAAS,cAAc,IAAI,KAAK,GAAG,CAAC;IACxD,CAAC,OAAO,aAAa,CAAC;CAEzB,MAAM,YAAY,cAAc;EAC9B,MAAM,sBAAM,IAAI,KAAqB;AACrC,OAAK,MAAM,QAAQ,MACjB,KAAI,IAAI,KAAK,IAAI,iBAAiB,MAAM,gBAAgB,aAAa,CAAC;AAExE,SAAO;IACN;EAAC;EAAgB;EAAc;EAAM,CAAC;CAEzC,MAAM,YAAY,cAAc;EAC9B,MAAM,sBAAM,IAAI,KAAqB;AACrC,eAAa,SAAS,MAAM,UAAU,IAAI,IAAI,KAAK,IAAI,MAAM,CAAC;AAC9D,SAAO;IACN,CAAC,aAAa,CAAC;CAElB,MAAM,WAAW,eAGX,GACA,OAEC,UAAU,IAAI,EAAE,GAAG,IAAI,MAAM,UAAU,IAAI,EAAE,GAAG,IAAI,IACzD,CAAC,UAAU,CACZ;CAED,MAAM,QAAQ,eACL;EACL,OAAO,aAAa,KAAK,UAAU,EAAE,GAAG,MAAM,EAAE;EAChD,OAAO,aAAa,KAAK,UAAU,EAAE,GAAG,MAAM,EAAE;EACjD,GACD,CAAC,cAAc,aAAa,CAC7B;CAED,MAAM,aAAa,KAAK,IAAI,GAAG,QAAQ,eAAe,WAAW;CACjE,MAAM,cAAc,KAAK,IAAI,GAAG,SAAS,YAAY,EAAE;AAEvD,QACE,qBAAC,OAAD;EAAK,WAAW,GAAG,mCAAmC,UAAU;YAAhE,CACG,SAAS,oBAAC,cAAD,EAAA,UAAe,OAAqB,CAAA,EAE9C,oBAAC,OAAD;GAAK,KAAK;GAAc,WAAU;GAAS,OAAO,EAAE,QAAQ;aACzD,QAAQ,KAAK,aAAa,KACzB,oBAAC,OAAD;IAAY;IAAe;cACzB,oBAAC,OAAD;KAAO,MAAM;KAAY,KAAK;eAC5B,oBAAC,QAAD;MACE,MAAM;MACN,MAAM,CAAC,YAAY,YAAY;MACpB;MACE;MACb,WAAW;MACX,SAAS,SAAS,KAAK;MACb;iBAER,EAAE,OAAO,SAAS,iBAClB,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,OAAD;OAAO,WAAU;iBACd,QAAQ,MAAM,KAAK,MAAM,UAAU;QAClC,MAAM,WACJ,OAAO,KAAK,WAAW,WAEjB,KAAK,OAIL,KACF,OAAO,KAAK,OAAO;QACzB,MAAM,SACJ,KAAK,SAAS,UAAU,IAAI,SAAS,IAAI;AAE3C,eACE,oBAAC,QAAD;SAEE,GAAG,WAAW,KAAK,IAAI;SACvB,MAAK;SACG;SACR,eAAe;SACf,aAAa,KAAK,IAAI,GAAG,KAAK,SAAS,EAAE;SACzC,EANK,eAAe,QAMpB;SAEJ;OACI,CAAA,EAER,oBAAC,OAAD;OAAO,WAAU;iBACd,QAAQ,MAAM,KAAK,MAAM,UAAU;AAClC,YACE,KAAK,OAAO,KAAA,KACZ,KAAK,OAAO,KAAA,KACZ,KAAK,OAAO,KAAA,KACZ,KAAK,OAAO,KAAA,EAEZ,QAAO;QAGT,MAAM,IAAI,KAAK,KAAK,KAAK;QACzB,MAAM,IAAI,KAAK,KAAK,KAAK;QACzB,MAAM,OAAO,UAAU,IAAI,KAAK,GAAG,IAAI;QAEvC,MAAM,SAAS,KAAK,KAAK;QAMzB,MAAM,SAAS,KAAK,IAClB,KAAK,IACH,KAAK,KAAK,IAAI,IAAI,GAClB,oBAAoB,UACrB,EACD,cAAc,YAAY,aAC3B;QACD,MAAM,cAAc;QACpB,MAAM,YAAY;QAClB,MAAM,aAAa,KAAK,IACtB,IACA,eAAe,YAAY,EAC5B;QACD,MAAM,cAAc;QACpB,MAAM,SAAS,SAAS;AAExB,eACE,qBAAC,OAAD,EAAA,UAAA;SACE,oBAAC,QAAD;UACE,GAAG,KAAK;UACR,GAAG,KAAK;UACR,OAAO;UACP,QAAQ,KAAK,IAAI,GAAG,EAAE;UAChB;UACN,IAAI;UACJ,CAAA;SACF,oBAAC,QAAD;UACE,GAAG;UACH,GAAG,SAAS;UACZ,YAAY;UACZ,kBAAiB;UACjB,UAAU;UACV,YAAY;UACZ,MAAK;oBAEJ,KAAK;UACD,CAAA;SACP,oBAAC,iBAAD;UACE,GAAG;UACH,GAAG;UACH,OAAO;UACP,QAAQ;UACR,OAAO,EAAE,UAAU,WAAW;oBAE9B,oBAAC,OAAD;WACE,OAAO;YACL,YAAY;YACZ,UAAU;YACX;qBAED,oBAAC,YAAD;YACE,OAAO,KAAK,SAAS;YACrB,GAAK,gBAAgB,KAAA,KAAa,EAChC,QAAQ,aACT;YACD,GAAK,gBAAgB,KAAA,KAAa,EAChC,QAAQ,aACT;YACD,WAAU;YACV,CAAA;WACE,CAAA;UACQ,CAAA;SACV,EAAA,EA7CI,eAAe,QA6CnB;SAEV;OACI,CAAA,CACP,EAAA,CAAA;MAEE,CAAA;KACH,CAAA;IACJ,CAAA;GAEJ,CAAA,CACF"}
|
|
1
|
+
{"version":3,"file":"sankey-chart.js","names":[],"sources":["../../src/components/sankey-chart.tsx"],"sourcesContent":["import { Group } from '@visx/group'\nimport {\n Sankey,\n sankeyJustify,\n type SankeyLink,\n type SankeyNode,\n} from '@visx/sankey'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { cn } from '../utils/cn'\nimport NumberFlow, { type Format } from './number-flow'\nimport { SectionTitle } from './typography'\n\nconst SANKEY_CATEGORY_COLORS: Record<string, string> = {\n solar: 'var(--orange-a9)',\n battery: 'var(--green-a9)',\n grid: 'var(--blue-9)',\n gridFeedIn: 'var(--blue-7)',\n home: 'var(--spark-9)',\n floor: 'var(--neutral-a9)',\n device: 'var(--neutral-a8)',\n}\n\nexport interface SankeyChartNode {\n id: string\n label: string\n /** Optional category — used to colour the node and any outgoing links. */\n category?: string\n /** Override the colour resolved from `category`. */\n color?: string\n}\n\nexport interface SankeyChartLink {\n source: string\n target: string\n value: number\n /** Override the colour of the link. Defaults to the source node colour. */\n color?: string\n}\n\nexport interface SankeyChartProps {\n nodes: SankeyChartNode[]\n links: SankeyChartLink[]\n /** Resolves a category to a colour. */\n categoryColors?: Record<string, string>\n /** Fallback colour for nodes/links without a category. */\n defaultColor?: string\n /** Suffix appended to each animated value label (e.g. \" W\"). */\n valueSuffix?: string\n /** NumberFlow `format` for value labels. */\n valueFormat?: Format\n height?: number\n nodeWidth?: number\n nodePadding?: number\n /** Reserved horizontal space on the right for labels. */\n labelPadding?: number\n /**\n * Node ids kept at their natural (computed) column instead of being pushed to\n * the last column. By default `sankeyJustify` sends every node with no\n * outgoing link to the right edge; use this to hold a terminal node (e.g. grid\n * feed-in) in a middle column alongside its siblings.\n */\n keepAtDepthIds?: readonly string[]\n title?: string\n className?: string\n}\n\ntype SankeyDatumNode = SankeyChartNode & {\n /** Computed by d3-sankey; present after layout. */\n}\n\ntype SankeyDatumLink = SankeyChartLink\n\nconst DEFAULT_HEIGHT = 260\nconst DEFAULT_NODE_WIDTH = 12\nconst DEFAULT_NODE_PADDING = 10\nconst DEFAULT_LABEL_PADDING = 96\nconst LEFT_INSET = 4\nconst TOP_INSET = 8\nconst LABEL_GAP = 8\n/** Approx. cap/ascender height of the node name above its alphabetic baseline. */\nconst LABEL_NAME_HEIGHT = 11\n/** Vertical offset of the value box relative to the name baseline. Negative pulls\n * the value up toward the name to tighten the gap between them. */\nconst LABEL_VALUE_GAP = -4\n/** Height of the value box rendered below the node name. */\nconst VALUE_HEIGHT = 16\nconst DEFAULT_COLOR = 'var(--neutral-a9)'\n\nfunction resolveNodeColor(\n node: SankeyChartNode,\n categoryColors: Record<string, string> | undefined,\n defaultColor: string,\n) {\n if (node.color) return node.color\n if (node.category && categoryColors?.[node.category]) {\n return categoryColors[node.category] as string\n }\n return defaultColor\n}\n\nfunction SankeyChart({\n nodes,\n links,\n categoryColors = SANKEY_CATEGORY_COLORS,\n defaultColor = DEFAULT_COLOR,\n valueSuffix,\n valueFormat,\n height = DEFAULT_HEIGHT,\n nodeWidth = DEFAULT_NODE_WIDTH,\n nodePadding = DEFAULT_NODE_PADDING,\n labelPadding = DEFAULT_LABEL_PADDING,\n keepAtDepthIds,\n title,\n className,\n}: SankeyChartProps): React.ReactElement {\n const containerRef = useRef<HTMLDivElement>(null)\n const [width, setWidth] = useState(0)\n\n useEffect(() => {\n const element = containerRef.current\n if (!element) return\n\n const resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry) return\n setWidth(entry.contentRect.width)\n })\n\n resizeObserver.observe(element)\n return () => resizeObserver.disconnect()\n }, [])\n\n const nodeIds = useMemo(() => new Set(nodes.map((node) => node.id)), [nodes])\n\n // Drop zero-value links and any whose endpoints aren't in the current node\n // set (a stale link referencing a removed node makes d3-sankey throw\n // `missing: <id>`).\n const visibleLinks = useMemo(\n () =>\n links.filter(\n (link) =>\n link.value > 0 &&\n nodeIds.has(link.source) &&\n nodeIds.has(link.target),\n ),\n [links, nodeIds],\n )\n\n const visibleNodes = useMemo(() => {\n const referencedIds = new Set<string>()\n for (const link of visibleLinks) {\n referencedIds.add(link.source)\n referencedIds.add(link.target)\n }\n return nodes.filter((node) => referencedIds.has(node.id))\n }, [nodes, visibleLinks])\n\n const colorById = useMemo(() => {\n const map = new Map<string, string>()\n for (const node of nodes) {\n map.set(node.id, resolveNodeColor(node, categoryColors, defaultColor))\n }\n return map\n }, [categoryColors, defaultColor, nodes])\n\n const orderById = useMemo(() => {\n const map = new Map<string, number>()\n visibleNodes.forEach((node, index) => map.set(node.id, index))\n return map\n }, [visibleNodes])\n\n const nodeSort = useMemo(\n () =>\n (\n a: SankeyNode<SankeyDatumNode, SankeyDatumLink>,\n b: SankeyNode<SankeyDatumNode, SankeyDatumLink>,\n ) =>\n (orderById.get(a.id) ?? 0) - (orderById.get(b.id) ?? 0),\n [orderById],\n )\n\n const graph = useMemo(\n () => ({\n nodes: visibleNodes.map((node) => ({ ...node })),\n links: visibleLinks.map((link) => ({ ...link })),\n }),\n [visibleNodes, visibleLinks],\n )\n\n const nodeAlign = useMemo(() => {\n const keep = new Set(keepAtDepthIds)\n return (node: SankeyNode<SankeyDatumNode, SankeyDatumLink>, n: number) =>\n keep.has(node.id) ? (node.depth ?? 0) : sankeyJustify(node, n)\n }, [keepAtDepthIds])\n\n const innerWidth = Math.max(0, width - labelPadding - LEFT_INSET)\n const innerHeight = Math.max(0, height - TOP_INSET * 2)\n\n return (\n <div className={cn('flex w-full flex-col gap-2 py-2', className)}>\n {title && <SectionTitle>{title}</SectionTitle>}\n\n <div ref={containerRef} className=\"w-full\" style={{ height }}>\n {width > 0 && innerWidth > 0 && (\n <svg width={width} height={height}>\n <Group left={LEFT_INSET} top={TOP_INSET}>\n <Sankey<SankeyDatumNode, SankeyDatumLink>\n root={graph}\n size={[innerWidth, innerHeight]}\n nodeWidth={nodeWidth}\n nodePadding={nodePadding}\n nodeAlign={nodeAlign}\n nodeId={(node) => node.id}\n nodeSort={nodeSort}\n >\n {({ graph: laidOut, createPath }) => (\n <>\n <Group className=\"visx-sankey-links\">\n {laidOut.links.map((link, index) => {\n const sourceId =\n typeof link.source === 'object'\n ? (\n link.source as SankeyNode<\n SankeyDatumNode,\n SankeyDatumLink\n >\n ).id\n : String(link.source)\n const stroke =\n link.color ?? colorById.get(sourceId) ?? defaultColor\n\n return (\n <path\n key={`sankey-link-${index}`}\n d={createPath(link) ?? ''}\n fill=\"none\"\n stroke={stroke}\n strokeOpacity={0.32}\n strokeWidth={Math.max(1, link.width ?? 0)}\n />\n )\n })}\n </Group>\n\n <Group className=\"visx-sankey-nodes\">\n {laidOut.nodes.map((node, index) => {\n if (\n node.x0 === undefined ||\n node.x1 === undefined ||\n node.y0 === undefined ||\n node.y1 === undefined\n ) {\n return null\n }\n\n const w = node.x1 - node.x0\n const h = node.y1 - node.y0\n const fill = colorById.get(node.id) ?? defaultColor\n\n const labelX = node.x1 + LABEL_GAP\n // Center the name+value label on the node, then clamp it\n // vertically so a node flush against an edge (e.g. a tiny\n // \"grid\" node at the top) doesn't push its name past the\n // chart bounds and get clipped. The name's ascenders sit\n // above labelY; the value box sits below it.\n const labelY = Math.min(\n Math.max(\n node.y0 + h / 2 - 4,\n LABEL_NAME_HEIGHT - TOP_INSET,\n ),\n innerHeight + TOP_INSET - VALUE_HEIGHT,\n )\n const labelAnchor = 'start' as const\n const primaryDy = 0\n const valueWidth = Math.max(\n 40,\n labelPadding - LABEL_GAP - 4,\n )\n const valueHeight = VALUE_HEIGHT\n const valueY = labelY + LABEL_VALUE_GAP\n\n return (\n <Group key={`sankey-node-${index}`}>\n <rect\n x={node.x0}\n y={node.y0}\n width={w}\n height={Math.max(1, h)}\n fill={fill}\n rx={2}\n />\n <text\n x={labelX}\n y={labelY + primaryDy}\n textAnchor={labelAnchor}\n dominantBaseline=\"alphabetic\"\n fontSize={11}\n fontWeight={600}\n fill=\"var(--neutral-a12)\"\n >\n {node.label}\n </text>\n <foreignObject\n x={labelX}\n y={valueY}\n width={valueWidth}\n height={valueHeight}\n style={{ overflow: 'visible' }}\n >\n <div\n style={{\n lineHeight: 1.2,\n overflow: 'visible',\n }}\n >\n <NumberFlow\n value={node.value ?? 0}\n {...(valueSuffix !== undefined && {\n suffix: valueSuffix,\n })}\n {...(valueFormat !== undefined && {\n format: valueFormat,\n })}\n className=\"text-[10px] text-gray-light tabular-nums\"\n />\n </div>\n </foreignObject>\n </Group>\n )\n })}\n </Group>\n </>\n )}\n </Sankey>\n </Group>\n </svg>\n )}\n </div>\n </div>\n )\n}\n\nexport type { SankeyLink, SankeyNode }\nexport { SankeyChart, SANKEY_CATEGORY_COLORS }\n"],"mappings":";;;;;;;;;AAYA,MAAM,yBAAiD;CACrD,OAAO;CACP,SAAS;CACT,MAAM;CACN,YAAY;CACZ,MAAM;CACN,OAAO;CACP,QAAQ;CACT;AAoDD,MAAM,iBAAiB;AACvB,MAAM,qBAAqB;AAC3B,MAAM,uBAAuB;AAC7B,MAAM,wBAAwB;AAC9B,MAAM,aAAa;AACnB,MAAM,YAAY;AAClB,MAAM,YAAY;;AAElB,MAAM,oBAAoB;;;AAG1B,MAAM,kBAAkB;;AAExB,MAAM,eAAe;AACrB,MAAM,gBAAgB;AAEtB,SAAS,iBACP,MACA,gBACA,cACA;AACA,KAAI,KAAK,MAAO,QAAO,KAAK;AAC5B,KAAI,KAAK,YAAY,iBAAiB,KAAK,UACzC,QAAO,eAAe,KAAK;AAE7B,QAAO;;AAGT,SAAS,YAAY,EACnB,OACA,OACA,iBAAiB,wBACjB,eAAe,eACf,aACA,aACA,SAAS,gBACT,YAAY,oBACZ,cAAc,sBACd,eAAe,uBACf,gBACA,OACA,aACuC;CACvC,MAAM,eAAe,OAAuB,KAAK;CACjD,MAAM,CAAC,OAAO,YAAY,SAAS,EAAE;AAErC,iBAAgB;EACd,MAAM,UAAU,aAAa;AAC7B,MAAI,CAAC,QAAS;EAEd,MAAM,iBAAiB,IAAI,gBAAgB,YAAY;GACrD,MAAM,QAAQ,QAAQ;AACtB,OAAI,CAAC,MAAO;AACZ,YAAS,MAAM,YAAY,MAAM;IACjC;AAEF,iBAAe,QAAQ,QAAQ;AAC/B,eAAa,eAAe,YAAY;IACvC,EAAE,CAAC;CAEN,MAAM,UAAU,cAAc,IAAI,IAAI,MAAM,KAAK,SAAS,KAAK,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC;CAK7E,MAAM,eAAe,cAEjB,MAAM,QACH,SACC,KAAK,QAAQ,KACb,QAAQ,IAAI,KAAK,OAAO,IACxB,QAAQ,IAAI,KAAK,OAAO,CAC3B,EACH,CAAC,OAAO,QAAQ,CACjB;CAED,MAAM,eAAe,cAAc;EACjC,MAAM,gCAAgB,IAAI,KAAa;AACvC,OAAK,MAAM,QAAQ,cAAc;AAC/B,iBAAc,IAAI,KAAK,OAAO;AAC9B,iBAAc,IAAI,KAAK,OAAO;;AAEhC,SAAO,MAAM,QAAQ,SAAS,cAAc,IAAI,KAAK,GAAG,CAAC;IACxD,CAAC,OAAO,aAAa,CAAC;CAEzB,MAAM,YAAY,cAAc;EAC9B,MAAM,sBAAM,IAAI,KAAqB;AACrC,OAAK,MAAM,QAAQ,MACjB,KAAI,IAAI,KAAK,IAAI,iBAAiB,MAAM,gBAAgB,aAAa,CAAC;AAExE,SAAO;IACN;EAAC;EAAgB;EAAc;EAAM,CAAC;CAEzC,MAAM,YAAY,cAAc;EAC9B,MAAM,sBAAM,IAAI,KAAqB;AACrC,eAAa,SAAS,MAAM,UAAU,IAAI,IAAI,KAAK,IAAI,MAAM,CAAC;AAC9D,SAAO;IACN,CAAC,aAAa,CAAC;CAElB,MAAM,WAAW,eAGX,GACA,OAEC,UAAU,IAAI,EAAE,GAAG,IAAI,MAAM,UAAU,IAAI,EAAE,GAAG,IAAI,IACzD,CAAC,UAAU,CACZ;CAED,MAAM,QAAQ,eACL;EACL,OAAO,aAAa,KAAK,UAAU,EAAE,GAAG,MAAM,EAAE;EAChD,OAAO,aAAa,KAAK,UAAU,EAAE,GAAG,MAAM,EAAE;EACjD,GACD,CAAC,cAAc,aAAa,CAC7B;CAED,MAAM,YAAY,cAAc;EAC9B,MAAM,OAAO,IAAI,IAAI,eAAe;AACpC,UAAQ,MAAoD,MAC1D,KAAK,IAAI,KAAK,GAAG,GAAI,KAAK,SAAS,IAAK,cAAc,MAAM,EAAE;IAC/D,CAAC,eAAe,CAAC;CAEpB,MAAM,aAAa,KAAK,IAAI,GAAG,QAAQ,eAAe,WAAW;CACjE,MAAM,cAAc,KAAK,IAAI,GAAG,SAAS,YAAY,EAAE;AAEvD,QACE,qBAAC,OAAD;EAAK,WAAW,GAAG,mCAAmC,UAAU;YAAhE,CACG,SAAS,oBAAC,cAAD,EAAA,UAAe,OAAqB,CAAA,EAE9C,oBAAC,OAAD;GAAK,KAAK;GAAc,WAAU;GAAS,OAAO,EAAE,QAAQ;aACzD,QAAQ,KAAK,aAAa,KACzB,oBAAC,OAAD;IAAY;IAAe;cACzB,oBAAC,OAAD;KAAO,MAAM;KAAY,KAAK;eAC5B,oBAAC,QAAD;MACE,MAAM;MACN,MAAM,CAAC,YAAY,YAAY;MACpB;MACE;MACF;MACX,SAAS,SAAS,KAAK;MACb;iBAER,EAAE,OAAO,SAAS,iBAClB,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,OAAD;OAAO,WAAU;iBACd,QAAQ,MAAM,KAAK,MAAM,UAAU;QAClC,MAAM,WACJ,OAAO,KAAK,WAAW,WAEjB,KAAK,OAIL,KACF,OAAO,KAAK,OAAO;QACzB,MAAM,SACJ,KAAK,SAAS,UAAU,IAAI,SAAS,IAAI;AAE3C,eACE,oBAAC,QAAD;SAEE,GAAG,WAAW,KAAK,IAAI;SACvB,MAAK;SACG;SACR,eAAe;SACf,aAAa,KAAK,IAAI,GAAG,KAAK,SAAS,EAAE;SACzC,EANK,eAAe,QAMpB;SAEJ;OACI,CAAA,EAER,oBAAC,OAAD;OAAO,WAAU;iBACd,QAAQ,MAAM,KAAK,MAAM,UAAU;AAClC,YACE,KAAK,OAAO,KAAA,KACZ,KAAK,OAAO,KAAA,KACZ,KAAK,OAAO,KAAA,KACZ,KAAK,OAAO,KAAA,EAEZ,QAAO;QAGT,MAAM,IAAI,KAAK,KAAK,KAAK;QACzB,MAAM,IAAI,KAAK,KAAK,KAAK;QACzB,MAAM,OAAO,UAAU,IAAI,KAAK,GAAG,IAAI;QAEvC,MAAM,SAAS,KAAK,KAAK;QAMzB,MAAM,SAAS,KAAK,IAClB,KAAK,IACH,KAAK,KAAK,IAAI,IAAI,GAClB,oBAAoB,UACrB,EACD,cAAc,YAAY,aAC3B;QACD,MAAM,cAAc;QACpB,MAAM,YAAY;QAClB,MAAM,aAAa,KAAK,IACtB,IACA,eAAe,YAAY,EAC5B;QACD,MAAM,cAAc;QACpB,MAAM,SAAS,SAAS;AAExB,eACE,qBAAC,OAAD,EAAA,UAAA;SACE,oBAAC,QAAD;UACE,GAAG,KAAK;UACR,GAAG,KAAK;UACR,OAAO;UACP,QAAQ,KAAK,IAAI,GAAG,EAAE;UAChB;UACN,IAAI;UACJ,CAAA;SACF,oBAAC,QAAD;UACE,GAAG;UACH,GAAG,SAAS;UACZ,YAAY;UACZ,kBAAiB;UACjB,UAAU;UACV,YAAY;UACZ,MAAK;oBAEJ,KAAK;UACD,CAAA;SACP,oBAAC,iBAAD;UACE,GAAG;UACH,GAAG;UACH,OAAO;UACP,QAAQ;UACR,OAAO,EAAE,UAAU,WAAW;oBAE9B,oBAAC,OAAD;WACE,OAAO;YACL,YAAY;YACZ,UAAU;YACX;qBAED,oBAAC,YAAD;YACE,OAAO,KAAK,SAAS;YACrB,GAAK,gBAAgB,KAAA,KAAa,EAChC,QAAQ,aACT;YACD,GAAK,gBAAgB,KAAA,KAAa,EAChC,QAAQ,aACT;YACD,WAAU;YACV,CAAA;WACE,CAAA;UACQ,CAAA;SACV,EAAA,EA7CI,eAAe,QA6CnB;SAEV;OACI,CAAA,CACP,EAAA,CAAA;MAEE,CAAA;KACH,CAAA;IACJ,CAAA;GAEJ,CAAA,CACF"}
|
|
@@ -22,7 +22,7 @@ ScheduleListEmpty.displayName = "ScheduleListEmpty";
|
|
|
22
22
|
const ScheduleListAdd = react.forwardRef(({ children, className, icon, type = "button", ...props }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
23
23
|
ref,
|
|
24
24
|
type,
|
|
25
|
-
className: (0, cnfast.cn)("inline-flex h-11 w-full items-center justify-center gap-1.5 rounded-2xl border border-dashed border-gray text-sm font-medium text-gray-light transition-colors hover:border-spark-a8 hover:bg-base-accent-soft hover:text-accent focus-visible:ring focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
25
|
+
className: (0, cnfast.cn)("inline-flex h-11 w-full items-center justify-center gap-1.5 rounded-2xl border border-dashed border-gray text-sm font-medium text-gray-light transition-colors hover:border-spark-a8 hover:bg-base-accent-soft hover:text-accent focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
26
26
|
...props,
|
|
27
27
|
children: [icon ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_icons_material_symbol.MaterialSymbol, {
|
|
28
28
|
className: "text-[14px]",
|
|
@@ -44,7 +44,7 @@ const ScheduleEntry = react.forwardRef(({ children, className, disabled, onClick
|
|
|
44
44
|
}
|
|
45
45
|
} : void 0,
|
|
46
46
|
"aria-disabled": disabled || void 0,
|
|
47
|
-
className: (0, cnfast.cn)("flex items-center gap-3 rounded-2xl bg-base-gray-soft px-3 py-2.5 transition-colors", interactive && "cursor-pointer hover:bg-
|
|
47
|
+
className: (0, cnfast.cn)("flex items-center gap-3 rounded-2xl bg-base-gray-soft px-3 py-2.5 transition-colors", interactive && "cursor-pointer hover:bg-gray-a3 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden", disabled && "cursor-not-allowed opacity-50", className),
|
|
48
48
|
...props,
|
|
49
49
|
children
|
|
50
50
|
});
|
|
@@ -88,7 +88,7 @@ const ScheduleEntryDelete = react.forwardRef(({ "aria-label": ariaLabel = "Delet
|
|
|
88
88
|
event.stopPropagation();
|
|
89
89
|
onClick?.(event);
|
|
90
90
|
},
|
|
91
|
-
className: (0, cnfast.cn)("inline-flex size-8 shrink-0 items-center justify-center rounded-circle text-gray-light transition-colors hover:bg-base-error-soft hover:text-error focus-visible:ring focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
91
|
+
className: (0, cnfast.cn)("inline-flex size-8 shrink-0 items-center justify-center rounded-circle text-gray-light transition-colors hover:bg-base-error-soft hover:text-error focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50", className),
|
|
92
92
|
...props,
|
|
93
93
|
children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_icons_material_symbol.MaterialSymbol, {
|
|
94
94
|
className: "text-[14px]",
|