@acronis-platform/ui-react 5.2.0 → 5.3.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.
- package/dist/components/ui/area-chart/area-chart.js +107 -107
- package/dist/components/ui/area-chart/area-chart.js.map +1 -1
- package/dist/components/ui/bar-chart/bar-chart.js +162 -162
- package/dist/components/ui/bar-chart/bar-chart.js.map +1 -1
- package/dist/components/ui/category-bar/category-bar.js +50 -50
- package/dist/components/ui/category-bar/category-bar.js.map +1 -1
- package/dist/components/ui/chart/chart.js +53 -40
- package/dist/components/ui/chart/chart.js.map +1 -1
- package/dist/components/ui/composed-chart/composed-chart.js +146 -146
- package/dist/components/ui/composed-chart/composed-chart.js.map +1 -1
- package/dist/components/ui/confidence-cone/confidence-cone.js +97 -97
- package/dist/components/ui/confidence-cone/confidence-cone.js.map +1 -1
- package/dist/components/ui/data-table/data-table-bulk-actions-bar.js +1 -1
- package/dist/components/ui/data-table/data-table-bulk-actions-bar.js.map +1 -1
- package/dist/components/ui/data-table/data-table-view-options.js +3 -2
- package/dist/components/ui/data-table/data-table-view-options.js.map +1 -1
- package/dist/components/ui/data-table/data-table.js +136 -132
- package/dist/components/ui/data-table/data-table.js.map +1 -1
- package/dist/components/ui/funnel-chart/funnel-chart.js +120 -120
- package/dist/components/ui/funnel-chart/funnel-chart.js.map +1 -1
- package/dist/components/ui/histogram/histogram.js +58 -58
- package/dist/components/ui/histogram/histogram.js.map +1 -1
- package/dist/components/ui/line-chart/line-chart.js +91 -91
- package/dist/components/ui/line-chart/line-chart.js.map +1 -1
- package/dist/components/ui/pie-chart/pie-chart.js +92 -92
- package/dist/components/ui/pie-chart/pie-chart.js.map +1 -1
- package/dist/components/ui/radar-chart/radar-chart.js +37 -37
- package/dist/components/ui/radar-chart/radar-chart.js.map +1 -1
- package/dist/components/ui/radial-bar-chart/radial-bar-chart.js +112 -112
- package/dist/components/ui/radial-bar-chart/radial-bar-chart.js.map +1 -1
- package/dist/components/ui/sankey-chart/sankey-chart.js +78 -78
- package/dist/components/ui/sankey-chart/sankey-chart.js.map +1 -1
- package/dist/components/ui/scatter-chart/scatter-chart.js +57 -57
- package/dist/components/ui/scatter-chart/scatter-chart.js.map +1 -1
- package/dist/components/ui/table/table-view-options.js +88 -33
- package/dist/components/ui/table/table-view-options.js.map +1 -1
- package/dist/components/ui/table/table.js.map +1 -1
- package/dist/components/ui/treemap/treemap.js +87 -87
- package/dist/components/ui/treemap/treemap.js.map +1 -1
- package/dist/index.js +111 -111
- package/dist/llms/data-table.md +33 -7
- package/dist/llms/table.md +56 -23
- package/dist/llms.txt +1 -1
- package/dist/react.js +111 -111
- package/dist/src/components/ui/chart/chart.d.ts +1 -0
- package/dist/src/components/ui/chart/index.d.ts +1 -1
- package/dist/src/components/ui/data-table/data-table-view-options.d.ts +1 -1
- package/dist/src/components/ui/data-table/data-table.d.ts +11 -1
- package/dist/src/components/ui/data-table/data-table.docs.d.ts +6 -0
- package/dist/src/components/ui/radial-bar-chart/radial-bar-chart.d.ts +3 -2
- package/dist/src/components/ui/sankey-chart/sankey-chart.d.ts +1 -1
- package/dist/src/components/ui/table/table-view-options.d.ts +9 -1
- package/dist/src/components/ui/treemap/treemap.d.ts +3 -2
- package/dist/ui-react.css +1 -1
- package/package.json +3 -3
|
@@ -1,119 +1,119 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn as e } from "../../../lib/utils.js";
|
|
3
3
|
import { CHART_DEFAULT_PALETTE as t } from "../chart/chart-palette.js";
|
|
4
|
-
import { ChartContainer as n, ChartStyle as r, ChartTooltip as i, resolveChartColors as a } from "../chart/chart.js";
|
|
5
|
-
import * as
|
|
6
|
-
import { jsx as
|
|
7
|
-
import { Rectangle as
|
|
4
|
+
import { ChartContainer as n, ChartStyle as r, ChartTooltip as i, resolveChartColors as a, toCssKey as o } from "../chart/chart.js";
|
|
5
|
+
import * as s from "react";
|
|
6
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
7
|
+
import { Rectangle as u, Sankey as d } from "recharts";
|
|
8
8
|
//#region src/components/ui/sankey-chart/sankey-chart.tsx
|
|
9
|
-
function
|
|
10
|
-
return function({ x: r, y: i, width: a, height:
|
|
11
|
-
let
|
|
12
|
-
return /* @__PURE__ */
|
|
9
|
+
function f(e, t, n) {
|
|
10
|
+
return function({ x: r, y: i, width: a, height: s, index: d, payload: f }) {
|
|
11
|
+
let p = String(f.name), m = e[p]?.label ?? p, h = t.has(d);
|
|
12
|
+
return /* @__PURE__ */ l("g", { children: [/* @__PURE__ */ c(u, {
|
|
13
13
|
x: r,
|
|
14
14
|
y: i,
|
|
15
15
|
width: a,
|
|
16
|
-
height:
|
|
17
|
-
fill: `var(--color-${
|
|
16
|
+
height: s,
|
|
17
|
+
fill: `var(--color-${o(p)})`,
|
|
18
18
|
radius: 2
|
|
19
|
-
}), n && /* @__PURE__ */
|
|
20
|
-
x:
|
|
21
|
-
y: i +
|
|
22
|
-
textAnchor:
|
|
19
|
+
}), n && /* @__PURE__ */ c("text", {
|
|
20
|
+
x: h ? r + a + 6 : r - 6,
|
|
21
|
+
y: i + s / 2,
|
|
22
|
+
textAnchor: h ? "start" : "end",
|
|
23
23
|
dominantBaseline: "middle",
|
|
24
24
|
className: "fill-foreground text-xs",
|
|
25
|
-
children:
|
|
25
|
+
children: m
|
|
26
26
|
})] });
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
-
function
|
|
30
|
-
let
|
|
31
|
-
return /* @__PURE__ */
|
|
29
|
+
function p({ sourceX: e, targetX: t, sourceY: n, targetY: r, sourceControlX: i, targetControlX: a, linkWidth: s, payload: l }) {
|
|
30
|
+
let u = String(l.target.name), d = l.color ?? `var(--color-${o(u)})`;
|
|
31
|
+
return /* @__PURE__ */ c("path", {
|
|
32
32
|
d: `M${e},${n}C${i},${n} ${a},${r} ${t},${r}`,
|
|
33
33
|
fill: "none",
|
|
34
|
-
stroke:
|
|
35
|
-
strokeWidth: Math.max(1,
|
|
36
|
-
strokeOpacity:
|
|
34
|
+
stroke: d,
|
|
35
|
+
strokeWidth: Math.max(1, s),
|
|
36
|
+
strokeOpacity: l.color ? 1 : .35
|
|
37
37
|
});
|
|
38
38
|
}
|
|
39
|
-
function
|
|
39
|
+
function m(e, t, n) {
|
|
40
40
|
let r = (t) => e[t]?.label ?? t, i = /* @__PURE__ */ new Map();
|
|
41
41
|
for (let e of t) {
|
|
42
42
|
let t = n[e.source]?.name ?? "", r = n[e.target]?.name ?? "";
|
|
43
43
|
i.set(t + " - " + r, {
|
|
44
|
-
color: e.color ?? `var(--color-${r})`,
|
|
44
|
+
color: e.color ?? `var(--color-${o(r)})`,
|
|
45
45
|
opacity: e.color ? 1 : .35
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
return function({ active: e, payload: t }) {
|
|
49
49
|
if (!e || !t?.length) return null;
|
|
50
|
-
let n = t[0], a = String(n?.name ?? "").split(" - "),
|
|
51
|
-
return /* @__PURE__ */
|
|
50
|
+
let n = t[0], a = String(n?.name ?? "").split(" - "), u = a[a.length - 1], d = i.get(String(n?.name ?? "")), f = d?.color ?? `var(--color-${o(u)})`, p = d?.opacity ?? 1, m = n?.value;
|
|
51
|
+
return /* @__PURE__ */ l("div", {
|
|
52
52
|
className: "flex items-center gap-2 rounded-[var(--ui-tooltip-container-border-radius)] border border-border bg-background px-[var(--ui-tooltip-container-padding-x)] py-[var(--ui-tooltip-container-padding-y)] text-xs text-foreground shadow-md",
|
|
53
53
|
children: [
|
|
54
|
-
/* @__PURE__ */
|
|
54
|
+
/* @__PURE__ */ c("span", {
|
|
55
55
|
className: "h-2.5 w-2.5 shrink-0 rounded-[2px]",
|
|
56
56
|
style: {
|
|
57
|
-
backgroundColor:
|
|
58
|
-
opacity:
|
|
57
|
+
backgroundColor: f,
|
|
58
|
+
opacity: p
|
|
59
59
|
}
|
|
60
60
|
}),
|
|
61
|
-
/* @__PURE__ */
|
|
61
|
+
/* @__PURE__ */ c("span", {
|
|
62
62
|
className: "text-muted-foreground",
|
|
63
|
-
children: a.map((e, t) => /* @__PURE__ */
|
|
63
|
+
children: a.map((e, t) => /* @__PURE__ */ l(s.Fragment, { children: [t > 0 && " → ", r(e)] }, `${e}-${t}`))
|
|
64
64
|
}),
|
|
65
|
-
|
|
65
|
+
m != null && /* @__PURE__ */ c("span", {
|
|
66
66
|
className: "ms-2 font-medium tabular-nums",
|
|
67
|
-
children: m
|
|
67
|
+
children: h(m)
|
|
68
68
|
})
|
|
69
69
|
]
|
|
70
70
|
});
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
-
function
|
|
74
|
-
return Array.isArray(e) ? e.map((e) =>
|
|
73
|
+
function h(e) {
|
|
74
|
+
return Array.isArray(e) ? e.map((e) => h(e)).join(" – ") : typeof e == "number" ? e.toLocaleString() : String(e);
|
|
75
75
|
}
|
|
76
|
-
var
|
|
77
|
-
let
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
]),
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
]),
|
|
76
|
+
var g = s.forwardRef(({ className: u, config: h, palette: g, data: _, nodePadding: v = 24, nodeWidth: y = 12, linkCurvature: b = .5, sort: x = !1, showLabels: S = !0, showLegend: C = !1, showTooltip: w = !0, tooltipContent: T, ...E }, D) => {
|
|
77
|
+
let O = `chart-${s.useId().replace(/:/g, "")}`, k = s.useMemo(() => new Set(_.links.map((e) => e.source)), [_.links]), A = s.useMemo(() => f(h, k, S), [
|
|
78
|
+
h,
|
|
79
|
+
k,
|
|
80
|
+
S
|
|
81
|
+
]), j = s.useMemo(() => m(h, _.links, _.nodes), [
|
|
82
|
+
h,
|
|
83
|
+
_.links,
|
|
84
|
+
_.nodes
|
|
85
|
+
]), M = s.useMemo(() => {
|
|
86
86
|
let e = /* @__PURE__ */ new Map(), t = /* @__PURE__ */ new Map();
|
|
87
|
-
for (let n of
|
|
88
|
-
let n =
|
|
89
|
-
return
|
|
87
|
+
for (let n of _.links) t.set(n.source, (t.get(n.source) ?? 0) + n.value), e.set(n.target, (e.get(n.target) ?? 0) + n.value);
|
|
88
|
+
let n = _.nodes.map((n, r) => e.get(r) ?? t.get(r) ?? 0), r = n.length ? Math.max(...n) : 0;
|
|
89
|
+
return _.nodes.map((e, t) => ({
|
|
90
90
|
name: e.name,
|
|
91
91
|
value: n[t],
|
|
92
92
|
pct: r > 0 ? Math.round(n[t] / r * 100) : 0
|
|
93
93
|
}));
|
|
94
|
-
}, [
|
|
95
|
-
return /* @__PURE__ */
|
|
96
|
-
ref:
|
|
97
|
-
"data-chart":
|
|
98
|
-
className: e("flex flex-col",
|
|
99
|
-
...
|
|
94
|
+
}, [_]);
|
|
95
|
+
return /* @__PURE__ */ l("div", {
|
|
96
|
+
ref: D,
|
|
97
|
+
"data-chart": O,
|
|
98
|
+
className: e("flex flex-col", u),
|
|
99
|
+
...E,
|
|
100
100
|
children: [
|
|
101
|
-
/* @__PURE__ */
|
|
102
|
-
id:
|
|
103
|
-
config: a(
|
|
101
|
+
/* @__PURE__ */ c(r, {
|
|
102
|
+
id: O,
|
|
103
|
+
config: a(h, g ?? t)
|
|
104
104
|
}),
|
|
105
|
-
/* @__PURE__ */
|
|
106
|
-
config:
|
|
107
|
-
palette:
|
|
105
|
+
/* @__PURE__ */ c(n, {
|
|
106
|
+
config: h,
|
|
107
|
+
palette: g,
|
|
108
108
|
className: "min-h-0 flex-1",
|
|
109
|
-
children: /* @__PURE__ */
|
|
110
|
-
data:
|
|
111
|
-
node:
|
|
112
|
-
link:
|
|
113
|
-
nodePadding:
|
|
114
|
-
nodeWidth:
|
|
115
|
-
linkCurvature:
|
|
116
|
-
sort:
|
|
109
|
+
children: /* @__PURE__ */ c(d, {
|
|
110
|
+
data: _,
|
|
111
|
+
node: A,
|
|
112
|
+
link: p,
|
|
113
|
+
nodePadding: v,
|
|
114
|
+
nodeWidth: y,
|
|
115
|
+
linkCurvature: b,
|
|
116
|
+
sort: x,
|
|
117
117
|
align: "left",
|
|
118
118
|
margin: {
|
|
119
119
|
top: 8,
|
|
@@ -121,28 +121,28 @@ var h = o.forwardRef(({ className: l, config: m, palette: h, data: g, nodePaddin
|
|
|
121
121
|
bottom: 8,
|
|
122
122
|
left: 24
|
|
123
123
|
},
|
|
124
|
-
children:
|
|
124
|
+
children: w && /* @__PURE__ */ c(i, { content: T ?? j })
|
|
125
125
|
})
|
|
126
126
|
}),
|
|
127
|
-
|
|
127
|
+
C && /* @__PURE__ */ c("div", {
|
|
128
128
|
"data-slot": "sankey-chart-legend",
|
|
129
129
|
className: "mt-3 grid grid-cols-2 gap-x-6 gap-y-1.5 text-xs",
|
|
130
|
-
children:
|
|
130
|
+
children: M.map(({ name: e, value: t, pct: n }) => /* @__PURE__ */ l("div", {
|
|
131
131
|
className: "flex items-center gap-2",
|
|
132
132
|
children: [
|
|
133
|
-
/* @__PURE__ */
|
|
133
|
+
/* @__PURE__ */ c("span", {
|
|
134
134
|
className: "size-2.5 shrink-0 rounded-sm",
|
|
135
|
-
style: { backgroundColor: `var(--color-${e})` }
|
|
135
|
+
style: { backgroundColor: `var(--color-${o(e)})` }
|
|
136
136
|
}),
|
|
137
|
-
/* @__PURE__ */
|
|
137
|
+
/* @__PURE__ */ c("span", {
|
|
138
138
|
className: "truncate text-muted-foreground",
|
|
139
|
-
children:
|
|
139
|
+
children: h[e]?.label ?? e
|
|
140
140
|
}),
|
|
141
|
-
/* @__PURE__ */
|
|
141
|
+
/* @__PURE__ */ c("span", {
|
|
142
142
|
className: "ms-auto font-semibold tabular-nums",
|
|
143
143
|
children: t.toLocaleString()
|
|
144
144
|
}),
|
|
145
|
-
/* @__PURE__ */
|
|
145
|
+
/* @__PURE__ */ l("span", {
|
|
146
146
|
className: "w-9 shrink-0 text-end tabular-nums text-muted-foreground",
|
|
147
147
|
children: [n, "%"]
|
|
148
148
|
})
|
|
@@ -152,8 +152,8 @@ var h = o.forwardRef(({ className: l, config: m, palette: h, data: g, nodePaddin
|
|
|
152
152
|
]
|
|
153
153
|
});
|
|
154
154
|
});
|
|
155
|
-
|
|
155
|
+
g.displayName = "SankeyChart";
|
|
156
156
|
//#endregion
|
|
157
|
-
export {
|
|
157
|
+
export { g as SankeyChart, m as makeSankeyTooltip };
|
|
158
158
|
|
|
159
159
|
//# sourceMappingURL=sankey-chart.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sankey-chart.js","names":[],"sources":["../../../../src/components/ui/sankey-chart/sankey-chart.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Sankey as RechartsSankey, Rectangle } from 'recharts';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartStyle,\n resolveChartColors,\n CHART_DEFAULT_PALETTE,\n ChartTooltip,\n type ChartConfig,\n type ChartPalette,\n} from '../chart';\n\n// A typed recharts composition over the shared `Chart` primitives — a flow\n// diagram where link width is proportional to the value flowing between nodes.\n// Like Treemap/Funnel it's a single recharts element (`<Sankey>`) themed through\n// custom `node` / `link` renderers (recharts' defaults paint one flat grey and\n// carry no token hooks). No CVA variant: a Sankey's expressiveness is the\n// nodes/links graph + geometry (plain props), not a visual \"mode\".\n//\n// Node `name`s are the color keys: each must match a `config` entry, so they\n// must be CSS-safe (they become part of a `--color-<name>` custom property).\n// The human-readable text comes from `config[name].label`.\n\n/** A node in the flow — `name` is its color/config key (CSS-safe, unique). */\nexport interface SankeyChartNode {\n name: string;\n}\n\n/** A directed flow between two nodes, referenced by their index in `nodes`. */\nexport interface SankeyChartLink {\n /** Index of the source node in `data.nodes`. */\n source: number;\n /** Index of the target node in `data.nodes`. */\n target: number;\n /** Flow magnitude — drives the link's width. */\n value: number;\n /**\n * Ribbon color (any CSS color, e.g. a `var(--ui-*)` token), rendered at **full\n * opacity** so it matches its node bar. Omit to use the target node's color at\n * 35% (the default tinted ribbon).\n */\n color?: string;\n}\n\nexport interface SankeyChartProps extends Omit<\n React.ComponentProps<'div'>,\n 'children'\n> {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /** The graph: `nodes` (color keys) + `links` (source/target indices + value). */\n data: {\n nodes: ReadonlyArray<SankeyChartNode>;\n links: ReadonlyArray<SankeyChartLink>;\n };\n /**\n * Per-node map of `label` / `icon` / `tone`, keyed by the node's `name`\n * (imported from the shared `Chart` primitives). Turned into `--color-<name>`\n * custom properties. Series take their colour from the container's `palette`;\n * each entry maps a key to a `label` and an optional `tone`.\n */\n config: ChartConfig;\n /** Vertical gap between nodes in the same column. */\n nodePadding?: number;\n /** Thickness of each node bar. */\n nodeWidth?: number;\n /** Curviness of the links (0 = straight, 1 = fully curved). */\n linkCurvature?: number;\n /**\n * Sort nodes vertically to minimize link crossings (recharts' relaxation).\n * Defaults to `false` — nodes render in the order given by `data.nodes` within\n * each column, which keeps curated flows tidy; set `true` to auto-order.\n */\n sort?: boolean;\n /** Render each node's label (from `config[name].label`) beside its bar. */\n showLabels?: boolean;\n /** Render a legend (color dot + label + value + % per node) below the chart. */\n showLegend?: boolean;\n /** Show a hover tooltip on nodes and links (dot + `source → target` + value). */\n showTooltip?: boolean;\n /**\n * Replace the default tooltip. Pass a configured `ChartTooltipContent`\n * (imported from this library) — e.g. with a `formatter` / `labelFormatter` —\n * to customize formatting without composing recharts yourself. Ignored when\n * `showTooltip` is false.\n */\n tooltipContent?: React.ComponentProps<typeof ChartTooltip>['content'];\n}\n\n// recharts injects these into the `node` / `link` render functions but doesn't\n// export their prop types from the package root, so we type the fields we use\n// (structurally compatible with recharts' internal NodeProps / LinkProps).\ninterface SankeyNodeShapeProps {\n x: number;\n y: number;\n width: number;\n height: number;\n index: number;\n payload: { name: string; value: number };\n}\n\ninterface SankeyLinkShapeProps {\n sourceX: number;\n targetX: number;\n sourceY: number;\n targetY: number;\n sourceControlX: number;\n targetControlX: number;\n linkWidth: number;\n // recharts spreads the original link onto `payload` (then overrides\n // source/target with node objects), so a custom `color` survives here.\n payload: {\n source: { name: string };\n target: { name: string };\n value: number;\n color?: string;\n };\n}\n\n// Node bar: a rect filled from `--color-<name>`, with the config label placed on\n// whichever side keeps it inside the plot — to the right for nodes that have\n// outgoing links (sources), to the left for terminal nodes (sinks).\nfunction makeNodeRenderer(\n config: ChartConfig,\n sourceIndices: ReadonlySet<number>,\n showLabels: boolean\n) {\n return function SankeyNodeShape({\n x,\n y,\n width,\n height,\n index,\n payload,\n }: SankeyNodeShapeProps) {\n const name = String(payload.name);\n const label = config[name]?.label ?? name;\n const isSource = sourceIndices.has(index);\n return (\n <g>\n <Rectangle\n x={x}\n y={y}\n width={width}\n height={height}\n fill={`var(--color-${name})`}\n radius={2}\n />\n {showLabels && (\n <text\n x={isSource ? x + width + 6 : x - 6}\n y={y + height / 2}\n textAnchor={isSource ? 'start' : 'end'}\n dominantBaseline=\"middle\"\n className=\"fill-foreground text-xs\"\n >\n {label}\n </text>\n )}\n </g>\n );\n };\n}\n\n// Link ribbon: a bezier stroked at 35% opacity in the link's own `color` if\n// given (recharts preserves custom link fields on the render `payload`), else\n// the **target** node's color (so a flow reads as \"where it goes\"). Drawn as a\n// stroke, not a fill, so the ribbon width comes from `linkWidth`.\nfunction SankeyLinkShape({\n sourceX,\n targetX,\n sourceY,\n targetY,\n sourceControlX,\n targetControlX,\n linkWidth,\n payload,\n}: SankeyLinkShapeProps) {\n const targetName = String(payload.target.name);\n const stroke = payload.color ?? `var(--color-${targetName})`;\n return (\n <path\n d={`M${sourceX},${sourceY}C${sourceControlX},${sourceY} ${targetControlX},${targetY} ${targetX},${targetY}`}\n fill=\"none\"\n stroke={stroke}\n strokeWidth={Math.max(1, linkWidth)}\n // An explicit per-link color is the caller's exact choice — render it at\n // full opacity (so it matches its node bar); the default target-tint is\n // dimmed to 35% so ribbons don't overwhelm the nodes.\n strokeOpacity={payload.color ? 1 : 0.35}\n />\n );\n}\n\n// Default tooltip: recharts' Sankey payload carries no config color, and labels\n// a hovered link \"source - target\" using the node keys (a node is a single key).\n// Parse that name to a color dot (the target node's color) + the mapped config\n// labels joined with an arrow + the value — driven by `config`, so it's reliable\n// where the shared ChartTooltipContent can't resolve the Sankey payload.\n//\n// Exported (module-only, not from the package barrel) so its branches can be\n// unit-tested directly: recharts can't lay out a Sankey in happy-dom, and its\n// hover tooltip has no statically-open form the component exposes, so rendering\n// `<SankeyChart>` never reaches this code.\nexport function makeSankeyTooltip(\n config: ChartConfig,\n links: ReadonlyArray<SankeyChartLink>,\n nodes: ReadonlyArray<SankeyChartNode>\n) {\n const labelFor = (key: string): React.ReactNode => config[key]?.label ?? key;\n // Resolve each link's dot color by its \"source - target\" name (which is the\n // tooltip item's name): the link's own color if set, else the target's, via\n // the `--color-<name>` bridge.\n const styleByName = new Map<string, { color: string; opacity: number }>();\n for (const link of links) {\n const source = nodes[link.source]?.name ?? '';\n const target = nodes[link.target]?.name ?? '';\n // Mirror the ribbon: an explicit color renders full-opacity; the default\n // target tint renders at 35%.\n styleByName.set(source + ' - ' + target, {\n color: link.color ?? `var(--color-${target})`,\n opacity: link.color ? 1 : 0.35,\n });\n }\n\n return function SankeyTooltipContent({\n active,\n payload,\n }: {\n active?: boolean;\n payload?: ReadonlyArray<{\n name?: string | number;\n value?: number | string | ReadonlyArray<number | string>;\n }>;\n }) {\n if (!active || !payload?.length) return null;\n const item = payload[0];\n const parts = String(item?.name ?? '').split(' - ');\n const targetKey = parts[parts.length - 1];\n const linkStyle = styleByName.get(String(item?.name ?? ''));\n const dotColor = linkStyle?.color ?? `var(--color-${targetKey})`;\n const dotOpacity = linkStyle?.opacity ?? 1;\n const value = item?.value;\n return (\n <div className=\"flex items-center gap-2 rounded-[var(--ui-tooltip-container-border-radius)] border border-border bg-background px-[var(--ui-tooltip-container-padding-x)] py-[var(--ui-tooltip-container-padding-y)] text-xs text-foreground shadow-md\">\n <span\n className=\"h-2.5 w-2.5 shrink-0 rounded-[2px]\"\n style={{ backgroundColor: dotColor, opacity: dotOpacity }}\n />\n <span className=\"text-muted-foreground\">\n {/* A config label can be any ReactNode, so the arrow-joined label is\n built as nodes rather than stringified. */}\n {parts.map((key, index) => (\n <React.Fragment key={`${key}-${index}`}>\n {index > 0 && ' → '}\n {labelFor(key)}\n </React.Fragment>\n ))}\n </span>\n {value != null && (\n <span className=\"ms-2 font-medium tabular-nums\">\n {formatTooltipValue(value)}\n </span>\n )}\n </div>\n );\n };\n}\n\n// recharts types a tooltip item's value as a scalar *or* a range tuple; a plain\n// `String()` would print \"1,2\" for the tuple, so format each part and join.\nfunction formatTooltipValue(\n value: number | string | ReadonlyArray<number | string>\n): string {\n if (Array.isArray(value)) {\n return value.map((part) => formatTooltipValue(part)).join(' – ');\n }\n return typeof value === 'number'\n ? value.toLocaleString()\n : String(value as string);\n}\n\nconst SankeyChart = React.forwardRef<HTMLDivElement, SankeyChartProps>(\n (\n {\n className,\n config,\n palette,\n data,\n nodePadding = 24,\n nodeWidth = 12,\n linkCurvature = 0.5,\n sort = false,\n showLabels = true,\n showLegend = false,\n showTooltip = true,\n tooltipContent,\n ...props\n },\n ref\n ) => {\n const uniqueId = React.useId();\n const chartId = `chart-${uniqueId.replace(/:/g, '')}`;\n\n // Node indices that appear as a link `source` have outgoing flow — used by\n // the node renderer to place the label on the readable side.\n const sourceIndices = React.useMemo(\n () => new Set(data.links.map((link) => link.source)),\n [data.links]\n );\n\n const nodeRenderer = React.useMemo(\n () => makeNodeRenderer(config, sourceIndices, showLabels),\n [config, sourceIndices, showLabels]\n );\n\n const defaultTooltip = React.useMemo(\n () => makeSankeyTooltip(config, data.links, data.nodes),\n [config, data.links, data.nodes]\n );\n\n // Per-node value (incoming flow, or outgoing for a source with no incoming)\n // and its share of the largest node — drives the legend's count + %.\n const nodeStats = React.useMemo(() => {\n const incoming = new Map<number, number>();\n const outgoing = new Map<number, number>();\n for (const link of data.links) {\n outgoing.set(\n link.source,\n (outgoing.get(link.source) ?? 0) + link.value\n );\n incoming.set(\n link.target,\n (incoming.get(link.target) ?? 0) + link.value\n );\n }\n const values = data.nodes.map(\n (_, i) => incoming.get(i) ?? outgoing.get(i) ?? 0\n );\n const total = values.length ? Math.max(...values) : 0;\n return data.nodes.map((node, i) => ({\n name: node.name,\n value: values[i],\n pct: total > 0 ? Math.round((values[i] / total) * 100) : 0,\n }));\n }, [data]);\n\n return (\n <div\n ref={ref}\n // The legend sits outside `ChartContainer`, so the root carries its own\n // `--color-<name>` scope for it (the plot and the recharts-portaled\n // tooltip resolve theirs from ChartContainer's).\n data-chart={chartId}\n className={cn('flex flex-col', className)}\n {...props}\n >\n <ChartStyle\n id={chartId}\n config={resolveChartColors(config, palette ?? CHART_DEFAULT_PALETTE)}\n />\n <ChartContainer\n config={config}\n palette={palette}\n className=\"min-h-0 flex-1\"\n >\n <RechartsSankey\n data={\n data as { nodes: SankeyChartNode[]; links: SankeyChartLink[] }\n }\n node={nodeRenderer}\n link={SankeyLinkShape}\n nodePadding={nodePadding}\n nodeWidth={nodeWidth}\n linkCurvature={linkCurvature}\n sort={sort}\n // recharts defaults to `justify`, which right-aligns sink nodes to\n // the last column — so a terminal node like \"No certification\" would\n // jump to the far right instead of staying at its real depth. `left`\n // positions every node by its distance from the source, keeping the\n // flow's columns intact.\n align=\"left\"\n // Node labels never extend past the plot's right edge: a node in the\n // last column has no outgoing links, so its label is drawn to the\n // LEFT of its bar (see the node renderer). Only a small gutter is\n // needed here — a wide reserve would just shrink the diagram.\n margin={{ top: 8, right: 24, bottom: 8, left: 24 }}\n >\n {showTooltip && (\n <ChartTooltip content={tooltipContent ?? defaultTooltip} />\n )}\n </RechartsSankey>\n </ChartContainer>\n {showLegend && (\n <div\n data-slot=\"sankey-chart-legend\"\n className=\"mt-3 grid grid-cols-2 gap-x-6 gap-y-1.5 text-xs\"\n >\n {nodeStats.map(({ name, value, pct }) => (\n <div key={name} className=\"flex items-center gap-2\">\n <span\n className=\"size-2.5 shrink-0 rounded-sm\"\n style={{ backgroundColor: `var(--color-${name})` }}\n />\n <span className=\"truncate text-muted-foreground\">\n {config[name]?.label ?? name}\n </span>\n <span className=\"ms-auto font-semibold tabular-nums\">\n {value.toLocaleString()}\n </span>\n <span className=\"w-9 shrink-0 text-end tabular-nums text-muted-foreground\">\n {pct}%\n </span>\n </div>\n ))}\n </div>\n )}\n </div>\n );\n }\n);\nSankeyChart.displayName = 'SankeyChart';\n\nexport { SankeyChart };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sankey-chart.js","names":[],"sources":["../../../../src/components/ui/sankey-chart/sankey-chart.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { Sankey as RechartsSankey, Rectangle } from 'recharts';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartStyle,\n resolveChartColors,\n CHART_DEFAULT_PALETTE,\n ChartTooltip,\n toCssKey,\n type ChartConfig,\n type ChartPalette,\n} from '../chart';\n\n// A typed recharts composition over the shared `Chart` primitives — a flow\n// diagram where link width is proportional to the value flowing between nodes.\n// Like Treemap/Funnel it's a single recharts element (`<Sankey>`) themed through\n// custom `node` / `link` renderers (recharts' defaults paint one flat grey and\n// carry no token hooks). No CVA variant: a Sankey's expressiveness is the\n// nodes/links graph + geometry (plain props), not a visual \"mode\".\n//\n// Node `name`s are the color keys: each must match a `config` entry. They are\n// sanitized automatically to a valid CSS custom-property fragment; CSS-safe\n// names are preferred to avoid dev-mode warnings.\n// The human-readable text comes from `config[name].label`.\n\n/** A node in the flow — `name` is its color/config key (unique; sanitized automatically to a CSS-safe fragment). */\nexport interface SankeyChartNode {\n name: string;\n}\n\n/** A directed flow between two nodes, referenced by their index in `nodes`. */\nexport interface SankeyChartLink {\n /** Index of the source node in `data.nodes`. */\n source: number;\n /** Index of the target node in `data.nodes`. */\n target: number;\n /** Flow magnitude — drives the link's width. */\n value: number;\n /**\n * Ribbon color (any CSS color, e.g. a `var(--ui-*)` token), rendered at **full\n * opacity** so it matches its node bar. Omit to use the target node's color at\n * 35% (the default tinted ribbon).\n */\n color?: string;\n}\n\nexport interface SankeyChartProps extends Omit<\n React.ComponentProps<'div'>,\n 'children'\n> {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /** The graph: `nodes` (color keys) + `links` (source/target indices + value). */\n data: {\n nodes: ReadonlyArray<SankeyChartNode>;\n links: ReadonlyArray<SankeyChartLink>;\n };\n /**\n * Per-node map of `label` / `icon` / `tone`, keyed by the node's `name`\n * (imported from the shared `Chart` primitives). Turned into `--color-<name>`\n * custom properties. Series take their colour from the container's `palette`;\n * each entry maps a key to a `label` and an optional `tone`.\n */\n config: ChartConfig;\n /** Vertical gap between nodes in the same column. */\n nodePadding?: number;\n /** Thickness of each node bar. */\n nodeWidth?: number;\n /** Curviness of the links (0 = straight, 1 = fully curved). */\n linkCurvature?: number;\n /**\n * Sort nodes vertically to minimize link crossings (recharts' relaxation).\n * Defaults to `false` — nodes render in the order given by `data.nodes` within\n * each column, which keeps curated flows tidy; set `true` to auto-order.\n */\n sort?: boolean;\n /** Render each node's label (from `config[name].label`) beside its bar. */\n showLabels?: boolean;\n /** Render a legend (color dot + label + value + % per node) below the chart. */\n showLegend?: boolean;\n /** Show a hover tooltip on nodes and links (dot + `source → target` + value). */\n showTooltip?: boolean;\n /**\n * Replace the default tooltip. Pass a configured `ChartTooltipContent`\n * (imported from this library) — e.g. with a `formatter` / `labelFormatter` —\n * to customize formatting without composing recharts yourself. Ignored when\n * `showTooltip` is false.\n */\n tooltipContent?: React.ComponentProps<typeof ChartTooltip>['content'];\n}\n\n// recharts injects these into the `node` / `link` render functions but doesn't\n// export their prop types from the package root, so we type the fields we use\n// (structurally compatible with recharts' internal NodeProps / LinkProps).\ninterface SankeyNodeShapeProps {\n x: number;\n y: number;\n width: number;\n height: number;\n index: number;\n payload: { name: string; value: number };\n}\n\ninterface SankeyLinkShapeProps {\n sourceX: number;\n targetX: number;\n sourceY: number;\n targetY: number;\n sourceControlX: number;\n targetControlX: number;\n linkWidth: number;\n // recharts spreads the original link onto `payload` (then overrides\n // source/target with node objects), so a custom `color` survives here.\n payload: {\n source: { name: string };\n target: { name: string };\n value: number;\n color?: string;\n };\n}\n\n// Node bar: a rect filled from `--color-<name>`, with the config label placed on\n// whichever side keeps it inside the plot — to the right for nodes that have\n// outgoing links (sources), to the left for terminal nodes (sinks).\nfunction makeNodeRenderer(\n config: ChartConfig,\n sourceIndices: ReadonlySet<number>,\n showLabels: boolean\n) {\n return function SankeyNodeShape({\n x,\n y,\n width,\n height,\n index,\n payload,\n }: SankeyNodeShapeProps) {\n const name = String(payload.name);\n const label = config[name]?.label ?? name;\n const isSource = sourceIndices.has(index);\n return (\n <g>\n <Rectangle\n x={x}\n y={y}\n width={width}\n height={height}\n fill={`var(--color-${toCssKey(name)})`}\n radius={2}\n />\n {showLabels && (\n <text\n x={isSource ? x + width + 6 : x - 6}\n y={y + height / 2}\n textAnchor={isSource ? 'start' : 'end'}\n dominantBaseline=\"middle\"\n className=\"fill-foreground text-xs\"\n >\n {label}\n </text>\n )}\n </g>\n );\n };\n}\n\n// Link ribbon: a bezier stroked at 35% opacity in the link's own `color` if\n// given (recharts preserves custom link fields on the render `payload`), else\n// the **target** node's color (so a flow reads as \"where it goes\"). Drawn as a\n// stroke, not a fill, so the ribbon width comes from `linkWidth`.\nfunction SankeyLinkShape({\n sourceX,\n targetX,\n sourceY,\n targetY,\n sourceControlX,\n targetControlX,\n linkWidth,\n payload,\n}: SankeyLinkShapeProps) {\n const targetName = String(payload.target.name);\n const stroke = payload.color ?? `var(--color-${toCssKey(targetName)})`;\n return (\n <path\n d={`M${sourceX},${sourceY}C${sourceControlX},${sourceY} ${targetControlX},${targetY} ${targetX},${targetY}`}\n fill=\"none\"\n stroke={stroke}\n strokeWidth={Math.max(1, linkWidth)}\n // An explicit per-link color is the caller's exact choice — render it at\n // full opacity (so it matches its node bar); the default target-tint is\n // dimmed to 35% so ribbons don't overwhelm the nodes.\n strokeOpacity={payload.color ? 1 : 0.35}\n />\n );\n}\n\n// Default tooltip: recharts' Sankey payload carries no config color, and labels\n// a hovered link \"source - target\" using the node keys (a node is a single key).\n// Parse that name to a color dot (the target node's color) + the mapped config\n// labels joined with an arrow + the value — driven by `config`, so it's reliable\n// where the shared ChartTooltipContent can't resolve the Sankey payload.\n//\n// Exported (module-only, not from the package barrel) so its branches can be\n// unit-tested directly: recharts can't lay out a Sankey in happy-dom, and its\n// hover tooltip has no statically-open form the component exposes, so rendering\n// `<SankeyChart>` never reaches this code.\nexport function makeSankeyTooltip(\n config: ChartConfig,\n links: ReadonlyArray<SankeyChartLink>,\n nodes: ReadonlyArray<SankeyChartNode>\n) {\n const labelFor = (key: string): React.ReactNode => config[key]?.label ?? key;\n // Resolve each link's dot color by its \"source - target\" name (which is the\n // tooltip item's name): the link's own color if set, else the target's, via\n // the `--color-<name>` bridge.\n const styleByName = new Map<string, { color: string; opacity: number }>();\n for (const link of links) {\n const source = nodes[link.source]?.name ?? '';\n const target = nodes[link.target]?.name ?? '';\n // Mirror the ribbon: an explicit color renders full-opacity; the default\n // target tint renders at 35%.\n styleByName.set(source + ' - ' + target, {\n color: link.color ?? `var(--color-${toCssKey(target)})`,\n opacity: link.color ? 1 : 0.35,\n });\n }\n\n return function SankeyTooltipContent({\n active,\n payload,\n }: {\n active?: boolean;\n payload?: ReadonlyArray<{\n name?: string | number;\n value?: number | string | ReadonlyArray<number | string>;\n }>;\n }) {\n if (!active || !payload?.length) return null;\n const item = payload[0];\n const parts = String(item?.name ?? '').split(' - ');\n const targetKey = parts[parts.length - 1];\n const linkStyle = styleByName.get(String(item?.name ?? ''));\n const dotColor = linkStyle?.color ?? `var(--color-${toCssKey(targetKey)})`;\n const dotOpacity = linkStyle?.opacity ?? 1;\n const value = item?.value;\n return (\n <div className=\"flex items-center gap-2 rounded-[var(--ui-tooltip-container-border-radius)] border border-border bg-background px-[var(--ui-tooltip-container-padding-x)] py-[var(--ui-tooltip-container-padding-y)] text-xs text-foreground shadow-md\">\n <span\n className=\"h-2.5 w-2.5 shrink-0 rounded-[2px]\"\n style={{ backgroundColor: dotColor, opacity: dotOpacity }}\n />\n <span className=\"text-muted-foreground\">\n {/* A config label can be any ReactNode, so the arrow-joined label is\n built as nodes rather than stringified. */}\n {parts.map((key, index) => (\n <React.Fragment key={`${key}-${index}`}>\n {index > 0 && ' → '}\n {labelFor(key)}\n </React.Fragment>\n ))}\n </span>\n {value != null && (\n <span className=\"ms-2 font-medium tabular-nums\">\n {formatTooltipValue(value)}\n </span>\n )}\n </div>\n );\n };\n}\n\n// recharts types a tooltip item's value as a scalar *or* a range tuple; a plain\n// `String()` would print \"1,2\" for the tuple, so format each part and join.\nfunction formatTooltipValue(\n value: number | string | ReadonlyArray<number | string>\n): string {\n if (Array.isArray(value)) {\n return value.map((part) => formatTooltipValue(part)).join(' – ');\n }\n return typeof value === 'number'\n ? value.toLocaleString()\n : String(value as string);\n}\n\nconst SankeyChart = React.forwardRef<HTMLDivElement, SankeyChartProps>(\n (\n {\n className,\n config,\n palette,\n data,\n nodePadding = 24,\n nodeWidth = 12,\n linkCurvature = 0.5,\n sort = false,\n showLabels = true,\n showLegend = false,\n showTooltip = true,\n tooltipContent,\n ...props\n },\n ref\n ) => {\n const uniqueId = React.useId();\n const chartId = `chart-${uniqueId.replace(/:/g, '')}`;\n\n // Node indices that appear as a link `source` have outgoing flow — used by\n // the node renderer to place the label on the readable side.\n const sourceIndices = React.useMemo(\n () => new Set(data.links.map((link) => link.source)),\n [data.links]\n );\n\n const nodeRenderer = React.useMemo(\n () => makeNodeRenderer(config, sourceIndices, showLabels),\n [config, sourceIndices, showLabels]\n );\n\n const defaultTooltip = React.useMemo(\n () => makeSankeyTooltip(config, data.links, data.nodes),\n [config, data.links, data.nodes]\n );\n\n // Per-node value (incoming flow, or outgoing for a source with no incoming)\n // and its share of the largest node — drives the legend's count + %.\n const nodeStats = React.useMemo(() => {\n const incoming = new Map<number, number>();\n const outgoing = new Map<number, number>();\n for (const link of data.links) {\n outgoing.set(\n link.source,\n (outgoing.get(link.source) ?? 0) + link.value\n );\n incoming.set(\n link.target,\n (incoming.get(link.target) ?? 0) + link.value\n );\n }\n const values = data.nodes.map(\n (_, i) => incoming.get(i) ?? outgoing.get(i) ?? 0\n );\n const total = values.length ? Math.max(...values) : 0;\n return data.nodes.map((node, i) => ({\n name: node.name,\n value: values[i],\n pct: total > 0 ? Math.round((values[i] / total) * 100) : 0,\n }));\n }, [data]);\n\n return (\n <div\n ref={ref}\n // The legend sits outside `ChartContainer`, so the root carries its own\n // `--color-<name>` scope for it (the plot and the recharts-portaled\n // tooltip resolve theirs from ChartContainer's).\n data-chart={chartId}\n className={cn('flex flex-col', className)}\n {...props}\n >\n <ChartStyle\n id={chartId}\n config={resolveChartColors(config, palette ?? CHART_DEFAULT_PALETTE)}\n />\n <ChartContainer\n config={config}\n palette={palette}\n className=\"min-h-0 flex-1\"\n >\n <RechartsSankey\n data={\n data as { nodes: SankeyChartNode[]; links: SankeyChartLink[] }\n }\n node={nodeRenderer}\n link={SankeyLinkShape}\n nodePadding={nodePadding}\n nodeWidth={nodeWidth}\n linkCurvature={linkCurvature}\n sort={sort}\n // recharts defaults to `justify`, which right-aligns sink nodes to\n // the last column — so a terminal node like \"No certification\" would\n // jump to the far right instead of staying at its real depth. `left`\n // positions every node by its distance from the source, keeping the\n // flow's columns intact.\n align=\"left\"\n // Node labels never extend past the plot's right edge: a node in the\n // last column has no outgoing links, so its label is drawn to the\n // LEFT of its bar (see the node renderer). Only a small gutter is\n // needed here — a wide reserve would just shrink the diagram.\n margin={{ top: 8, right: 24, bottom: 8, left: 24 }}\n >\n {showTooltip && (\n <ChartTooltip content={tooltipContent ?? defaultTooltip} />\n )}\n </RechartsSankey>\n </ChartContainer>\n {showLegend && (\n <div\n data-slot=\"sankey-chart-legend\"\n className=\"mt-3 grid grid-cols-2 gap-x-6 gap-y-1.5 text-xs\"\n >\n {nodeStats.map(({ name, value, pct }) => (\n <div key={name} className=\"flex items-center gap-2\">\n <span\n className=\"size-2.5 shrink-0 rounded-sm\"\n style={{ backgroundColor: `var(--color-${toCssKey(name)})` }}\n />\n <span className=\"truncate text-muted-foreground\">\n {config[name]?.label ?? name}\n </span>\n <span className=\"ms-auto font-semibold tabular-nums\">\n {value.toLocaleString()}\n </span>\n <span className=\"w-9 shrink-0 text-end tabular-nums text-muted-foreground\">\n {pct}%\n </span>\n </div>\n ))}\n </div>\n )}\n </div>\n );\n }\n);\nSankeyChart.displayName = 'SankeyChart';\n\nexport { SankeyChart };\n"],"mappings":"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"}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn as e } from "../../../lib/utils.js";
|
|
3
|
-
import { ChartContainer as t, ChartLegend as n, ChartLegendContent as r, ChartTooltip as i, ChartTooltipContent as a } from "../chart/chart.js";
|
|
4
|
-
import { resolveAnimation as
|
|
5
|
-
import * as
|
|
6
|
-
import { jsx as
|
|
7
|
-
import { CartesianGrid as
|
|
3
|
+
import { ChartContainer as t, ChartLegend as n, ChartLegendContent as r, ChartTooltip as i, ChartTooltipContent as a, toCssKey as o } from "../chart/chart.js";
|
|
4
|
+
import { resolveAnimation as s, resolveAxisDomain as c, resolveRotatedTickAnchor as l, resolveXAxisHeight as u, resolveXAxisTitle as d, resolveYAxisTitle as f } from "../chart/chart-format.js";
|
|
5
|
+
import * as p from "react";
|
|
6
|
+
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
7
|
+
import { CartesianGrid as g, Scatter as _, ScatterChart as v, XAxis as y, YAxis as b, ZAxis as x } from "recharts";
|
|
8
8
|
//#region src/components/ui/scatter-chart/scatter-chart.tsx
|
|
9
|
-
var
|
|
9
|
+
var S = p.forwardRef(({ className: p, config: S, palette: C = {
|
|
10
10
|
type: "diverging",
|
|
11
11
|
pair: "teal-violet"
|
|
12
|
-
}, series:
|
|
13
|
-
let
|
|
14
|
-
animate:
|
|
15
|
-
animationDuration:
|
|
16
|
-
animationBegin:
|
|
17
|
-
animationEasing:
|
|
18
|
-
}),
|
|
19
|
-
return /* @__PURE__ */
|
|
20
|
-
ref:
|
|
21
|
-
className: e(
|
|
22
|
-
...
|
|
23
|
-
children: /* @__PURE__ */
|
|
24
|
-
config:
|
|
25
|
-
palette:
|
|
12
|
+
}, series: w, xKey: T, yKey: E, zKey: D, zRange: O = [60, 400], shape: k = "circle", xAxisLabel: A, yAxisLabel: j, xUnit: M, yUnit: N, showGrid: P = !0, showTooltip: ee = !0, showLegend: F = !0, showXAxis: I = !0, showYAxis: L = !0, xTickFormatter: R, yTickFormatter: z, xAxisAngle: B, xAxisInterval: V, yAxisTickCount: H, yAxisDomain: U, gridDashed: W = !0, gridHorizontal: G, gridVertical: K, tooltipContent: q, animate: J, animationDuration: Y, animationBegin: X, animationEasing: Z, ...Q }, $) => {
|
|
13
|
+
let te = s({
|
|
14
|
+
animate: J,
|
|
15
|
+
animationDuration: Y,
|
|
16
|
+
animationBegin: X,
|
|
17
|
+
animationEasing: Z
|
|
18
|
+
}), ne = d(A, "insideBottom", -8), re = f(j), ie = c(U), ae = u(A, B);
|
|
19
|
+
return /* @__PURE__ */ m("div", {
|
|
20
|
+
ref: $,
|
|
21
|
+
className: e(p),
|
|
22
|
+
...Q,
|
|
23
|
+
children: /* @__PURE__ */ m(t, {
|
|
24
|
+
config: S,
|
|
25
|
+
palette: C,
|
|
26
26
|
className: "size-full [&_.recharts-label]:fill-foreground",
|
|
27
|
-
children: /* @__PURE__ */
|
|
27
|
+
children: /* @__PURE__ */ h(v, {
|
|
28
28
|
margin: {
|
|
29
29
|
top: 16,
|
|
30
30
|
right: 16,
|
|
@@ -32,28 +32,12 @@ var x = f.forwardRef(({ className: f, config: x, palette: S = {
|
|
|
32
32
|
left: 16
|
|
33
33
|
},
|
|
34
34
|
children: [
|
|
35
|
-
|
|
36
|
-
horizontal:
|
|
37
|
-
vertical:
|
|
38
|
-
strokeDasharray:
|
|
35
|
+
P && /* @__PURE__ */ m(g, {
|
|
36
|
+
horizontal: G ?? !0,
|
|
37
|
+
vertical: K ?? !1,
|
|
38
|
+
strokeDasharray: W ? "3 3" : void 0
|
|
39
39
|
}),
|
|
40
|
-
/* @__PURE__ */
|
|
41
|
-
type: "number",
|
|
42
|
-
dataKey: w,
|
|
43
|
-
name: w,
|
|
44
|
-
unit: j,
|
|
45
|
-
hide: !F,
|
|
46
|
-
tickLine: !1,
|
|
47
|
-
axisLine: !1,
|
|
48
|
-
tickMargin: 8,
|
|
49
|
-
tickFormatter: L,
|
|
50
|
-
angle: z,
|
|
51
|
-
interval: B,
|
|
52
|
-
textAnchor: c(z),
|
|
53
|
-
height: ie,
|
|
54
|
-
label: te
|
|
55
|
-
}),
|
|
56
|
-
/* @__PURE__ */ p(y, {
|
|
40
|
+
/* @__PURE__ */ m(y, {
|
|
57
41
|
type: "number",
|
|
58
42
|
dataKey: T,
|
|
59
43
|
name: T,
|
|
@@ -63,36 +47,52 @@ var x = f.forwardRef(({ className: f, config: x, palette: S = {
|
|
|
63
47
|
axisLine: !1,
|
|
64
48
|
tickMargin: 8,
|
|
65
49
|
tickFormatter: R,
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
50
|
+
angle: B,
|
|
51
|
+
interval: V,
|
|
52
|
+
textAnchor: l(B),
|
|
53
|
+
height: ae,
|
|
69
54
|
label: ne
|
|
70
55
|
}),
|
|
71
|
-
|
|
56
|
+
/* @__PURE__ */ m(b, {
|
|
72
57
|
type: "number",
|
|
73
58
|
dataKey: E,
|
|
74
|
-
|
|
75
|
-
|
|
59
|
+
name: E,
|
|
60
|
+
unit: N,
|
|
61
|
+
hide: !L,
|
|
62
|
+
tickLine: !1,
|
|
63
|
+
axisLine: !1,
|
|
64
|
+
tickMargin: 8,
|
|
65
|
+
tickFormatter: z,
|
|
66
|
+
tickCount: H,
|
|
67
|
+
domain: ie,
|
|
68
|
+
width: j ? 72 : void 0,
|
|
69
|
+
label: re
|
|
70
|
+
}),
|
|
71
|
+
D && /* @__PURE__ */ m(x, {
|
|
72
|
+
type: "number",
|
|
73
|
+
dataKey: D,
|
|
74
|
+
range: O,
|
|
75
|
+
name: D
|
|
76
76
|
}),
|
|
77
|
-
|
|
77
|
+
ee && /* @__PURE__ */ m(i, {
|
|
78
78
|
cursor: { strokeDasharray: "3 3" },
|
|
79
|
-
content:
|
|
79
|
+
content: q ?? /* @__PURE__ */ m(a, {})
|
|
80
80
|
}),
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
F && /* @__PURE__ */ m(n, { content: /* @__PURE__ */ m(r, {}) }),
|
|
82
|
+
w.map((e) => /* @__PURE__ */ m(_, {
|
|
83
83
|
name: e.key,
|
|
84
84
|
data: e.data,
|
|
85
|
-
fill: `var(--color-${e.key})`,
|
|
86
|
-
shape:
|
|
87
|
-
|
|
85
|
+
fill: `var(--color-${o(e.key)})`,
|
|
86
|
+
shape: k,
|
|
87
|
+
...te
|
|
88
88
|
}, e.key))
|
|
89
89
|
]
|
|
90
90
|
})
|
|
91
91
|
})
|
|
92
92
|
});
|
|
93
93
|
});
|
|
94
|
-
|
|
94
|
+
S.displayName = "ScatterChart";
|
|
95
95
|
//#endregion
|
|
96
|
-
export {
|
|
96
|
+
export { S as ScatterChart };
|
|
97
97
|
|
|
98
98
|
//# sourceMappingURL=scatter-chart.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scatter-chart.js","names":[],"sources":["../../../../src/components/ui/scatter-chart/scatter-chart.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport {\n CartesianGrid,\n Scatter,\n ScatterChart as RechartsScatterChart,\n XAxis,\n YAxis,\n ZAxis,\n} from 'recharts';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartLegend,\n ChartLegendContent,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n resolveAxisDomain,\n resolveRotatedTickAnchor,\n resolveXAxisHeight,\n resolveXAxisTitle,\n resolveYAxisTitle,\n type ChartConfig,\n type ChartPalette,\n type CartesianChartProps,\n type ChartAnimationProps,\n} from '../chart';\n\n// A typed recharts composition over the shared `Chart` primitives. Unlike the\n// other chart types, a scatter has no visual \"mode\" to model as a CVA variant —\n// its shape is fixed (x/y points, optionally sized by z) and its expressiveness\n// comes from the data mapping. So there is no `cva` axis here (matches the\n// skill's \"usually none\"); marker `shape` and bubble sizing are plain props.\n// Each series carries its own point array (points aren't columns of a shared\n// row the way bar/line/area series are), so the API takes a `series` list rather\n// than `dataKeys` over one `data` array.\nexport type ScatterMarkerShape =\n | 'circle'\n | 'cross'\n | 'diamond'\n | 'square'\n | 'star'\n | 'triangle'\n | 'wye';\n\nexport interface ScatterSeries {\n /** Series key — must match a `config` entry; drives its `--color-<key>` fill and legend label. */\n key: string;\n /** This series' points — each row holds at least `xKey` and `yKey` (and `zKey` when used). */\n data: ReadonlyArray<Record<string, number>>;\n}\n\nexport interface ScatterChartProps\n extends Omit<React.ComponentProps<'div'>, 'children'>,\n CartesianChartProps,\n ChartAnimationProps {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /** One `<Scatter>` per entry — each with its own point array. Use a single entry for an ungrouped scatter. */\n series: ScatterSeries[];\n /**\n * Per-series map of `label` / `icon` / `tone`, keyed by `series[].key`\n * (imported from the shared `Chart` primitives). Turned into `--color-<key>`\n * custom properties. Series take their colour from the container's `palette`;\n * each entry maps a key to a `label` and an optional `tone`.\n */\n config: ChartConfig;\n /** Numeric field for the horizontal axis. */\n xKey: string;\n /** Numeric field for the vertical axis. */\n yKey: string;\n /** Unit suffix appended to X-axis tick values (recharts `unit`). */\n xUnit?: string;\n /** Optional numeric field mapped to point size (a bubble chart), via recharts `ZAxis`. */\n zKey?: string;\n /** Point-size range `[min, max]` the `zKey` maps into. Ignored when `zKey` is unset (points use recharts' default size). */\n zRange?: [number, number];\n /** Marker shape for every point. */\n shape?: ScatterMarkerShape;\n showLegend?: boolean;\n}\n\nconst ScatterChart = React.forwardRef<HTMLDivElement, ScatterChartProps>(\n (\n {\n className,\n config,\n palette = { type: 'diverging', pair: 'teal-violet' },\n series,\n xKey,\n yKey,\n zKey,\n zRange = [60, 400],\n shape = 'circle',\n xAxisLabel,\n yAxisLabel,\n xUnit,\n yUnit,\n showGrid = true,\n showTooltip = true,\n showLegend = true,\n showXAxis = true,\n showYAxis = true,\n xTickFormatter,\n yTickFormatter,\n xAxisAngle,\n xAxisInterval,\n yAxisTickCount,\n yAxisDomain,\n gridDashed = true,\n gridHorizontal,\n gridVertical,\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n // Axis titles: the X title sits below the ticks; the Y title is rotated in\n // the left gutter. Passed to recharts' native `label` (themed via the\n // `.recharts-label` fill selector on the container).\n const xAxisTitle = resolveXAxisTitle(\n xAxisLabel,\n 'insideBottom',\n -8\n );\n const yAxisTitle = resolveYAxisTitle(yAxisLabel);\n\n const yDomain = resolveAxisDomain(yAxisDomain);\n\n const xAxisHeight = resolveXAxisHeight(xAxisLabel, xAxisAngle);\n\n return (\n <div ref={ref} className={cn(className)} {...props}>\n <ChartContainer\n config={config}\n palette={palette}\n className=\"size-full [&_.recharts-label]:fill-foreground\"\n >\n <RechartsScatterChart\n margin={{ top: 16, right: 16, bottom: 16, left: 16 }}\n >\n {showGrid && (\n <CartesianGrid\n horizontal={gridHorizontal ?? true}\n vertical={gridVertical ?? false}\n strokeDasharray={gridDashed ? '3 3' : undefined}\n />\n )}\n <XAxis\n type=\"number\"\n dataKey={xKey}\n name={xKey}\n unit={xUnit}\n hide={!showXAxis}\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n tickFormatter={xTickFormatter}\n angle={xAxisAngle}\n interval={xAxisInterval}\n textAnchor={resolveRotatedTickAnchor(xAxisAngle)}\n height={xAxisHeight}\n label={xAxisTitle}\n />\n <YAxis\n type=\"number\"\n dataKey={yKey}\n name={yKey}\n unit={yUnit}\n hide={!showYAxis}\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n tickFormatter={yTickFormatter}\n tickCount={yAxisTickCount}\n domain={yDomain}\n width={yAxisLabel ? 72 : undefined}\n label={yAxisTitle}\n />\n {zKey && (\n <ZAxis type=\"number\" dataKey={zKey} range={zRange} name={zKey} />\n )}\n {showTooltip && (\n <ChartTooltip\n cursor={{ strokeDasharray: '3 3' }}\n content={tooltipContent ?? <ChartTooltipContent />}\n />\n )}\n {showLegend && <ChartLegend content={<ChartLegendContent />} />}\n {series.map((s) => (\n <Scatter\n key={s.key}\n name={s.key}\n data={s.data as Record<string, number>[]}\n fill={`var(--color-${s.key})`}\n shape={shape}\n {...animation}\n />\n ))}\n </RechartsScatterChart>\n </ChartContainer>\n </div>\n );\n }\n);\nScatterChart.displayName = 'ScatterChart';\n\nexport { ScatterChart };\n"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"scatter-chart.js","names":[],"sources":["../../../../src/components/ui/scatter-chart/scatter-chart.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport {\n CartesianGrid,\n Scatter,\n ScatterChart as RechartsScatterChart,\n XAxis,\n YAxis,\n ZAxis,\n} from 'recharts';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartLegend,\n ChartLegendContent,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n resolveAxisDomain,\n resolveRotatedTickAnchor,\n resolveXAxisHeight,\n resolveXAxisTitle,\n resolveYAxisTitle,\n type ChartConfig,\n type ChartPalette,\n toCssKey,\n type CartesianChartProps,\n type ChartAnimationProps,\n} from '../chart';\n\n// A typed recharts composition over the shared `Chart` primitives. Unlike the\n// other chart types, a scatter has no visual \"mode\" to model as a CVA variant —\n// its shape is fixed (x/y points, optionally sized by z) and its expressiveness\n// comes from the data mapping. So there is no `cva` axis here (matches the\n// skill's \"usually none\"); marker `shape` and bubble sizing are plain props.\n// Each series carries its own point array (points aren't columns of a shared\n// row the way bar/line/area series are), so the API takes a `series` list rather\n// than `dataKeys` over one `data` array.\nexport type ScatterMarkerShape =\n | 'circle'\n | 'cross'\n | 'diamond'\n | 'square'\n | 'star'\n | 'triangle'\n | 'wye';\n\nexport interface ScatterSeries {\n /** Series key — must match a `config` entry; drives its `--color-<key>` fill and legend label. */\n key: string;\n /** This series' points — each row holds at least `xKey` and `yKey` (and `zKey` when used). */\n data: ReadonlyArray<Record<string, number>>;\n}\n\nexport interface ScatterChartProps\n extends Omit<React.ComponentProps<'div'>, 'children'>,\n CartesianChartProps,\n ChartAnimationProps {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /** One `<Scatter>` per entry — each with its own point array. Use a single entry for an ungrouped scatter. */\n series: ScatterSeries[];\n /**\n * Per-series map of `label` / `icon` / `tone`, keyed by `series[].key`\n * (imported from the shared `Chart` primitives). Turned into `--color-<key>`\n * custom properties. Series take their colour from the container's `palette`;\n * each entry maps a key to a `label` and an optional `tone`.\n */\n config: ChartConfig;\n /** Numeric field for the horizontal axis. */\n xKey: string;\n /** Numeric field for the vertical axis. */\n yKey: string;\n /** Unit suffix appended to X-axis tick values (recharts `unit`). */\n xUnit?: string;\n /** Optional numeric field mapped to point size (a bubble chart), via recharts `ZAxis`. */\n zKey?: string;\n /** Point-size range `[min, max]` the `zKey` maps into. Ignored when `zKey` is unset (points use recharts' default size). */\n zRange?: [number, number];\n /** Marker shape for every point. */\n shape?: ScatterMarkerShape;\n showLegend?: boolean;\n}\n\nconst ScatterChart = React.forwardRef<HTMLDivElement, ScatterChartProps>(\n (\n {\n className,\n config,\n palette = { type: 'diverging', pair: 'teal-violet' },\n series,\n xKey,\n yKey,\n zKey,\n zRange = [60, 400],\n shape = 'circle',\n xAxisLabel,\n yAxisLabel,\n xUnit,\n yUnit,\n showGrid = true,\n showTooltip = true,\n showLegend = true,\n showXAxis = true,\n showYAxis = true,\n xTickFormatter,\n yTickFormatter,\n xAxisAngle,\n xAxisInterval,\n yAxisTickCount,\n yAxisDomain,\n gridDashed = true,\n gridHorizontal,\n gridVertical,\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n // Axis titles: the X title sits below the ticks; the Y title is rotated in\n // the left gutter. Passed to recharts' native `label` (themed via the\n // `.recharts-label` fill selector on the container).\n const xAxisTitle = resolveXAxisTitle(\n xAxisLabel,\n 'insideBottom',\n -8\n );\n const yAxisTitle = resolveYAxisTitle(yAxisLabel);\n\n const yDomain = resolveAxisDomain(yAxisDomain);\n\n const xAxisHeight = resolveXAxisHeight(xAxisLabel, xAxisAngle);\n\n return (\n <div ref={ref} className={cn(className)} {...props}>\n <ChartContainer\n config={config}\n palette={palette}\n className=\"size-full [&_.recharts-label]:fill-foreground\"\n >\n <RechartsScatterChart\n margin={{ top: 16, right: 16, bottom: 16, left: 16 }}\n >\n {showGrid && (\n <CartesianGrid\n horizontal={gridHorizontal ?? true}\n vertical={gridVertical ?? false}\n strokeDasharray={gridDashed ? '3 3' : undefined}\n />\n )}\n <XAxis\n type=\"number\"\n dataKey={xKey}\n name={xKey}\n unit={xUnit}\n hide={!showXAxis}\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n tickFormatter={xTickFormatter}\n angle={xAxisAngle}\n interval={xAxisInterval}\n textAnchor={resolveRotatedTickAnchor(xAxisAngle)}\n height={xAxisHeight}\n label={xAxisTitle}\n />\n <YAxis\n type=\"number\"\n dataKey={yKey}\n name={yKey}\n unit={yUnit}\n hide={!showYAxis}\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n tickFormatter={yTickFormatter}\n tickCount={yAxisTickCount}\n domain={yDomain}\n width={yAxisLabel ? 72 : undefined}\n label={yAxisTitle}\n />\n {zKey && (\n <ZAxis type=\"number\" dataKey={zKey} range={zRange} name={zKey} />\n )}\n {showTooltip && (\n <ChartTooltip\n cursor={{ strokeDasharray: '3 3' }}\n content={tooltipContent ?? <ChartTooltipContent />}\n />\n )}\n {showLegend && <ChartLegend content={<ChartLegendContent />} />}\n {series.map((s) => (\n <Scatter\n key={s.key}\n name={s.key}\n data={s.data as Record<string, number>[]}\n fill={`var(--color-${toCssKey(s.key)})`}\n shape={shape}\n {...animation}\n />\n ))}\n </RechartsScatterChart>\n </ChartContainer>\n </div>\n );\n }\n);\nScatterChart.displayName = 'ScatterChart';\n\nexport { ScatterChart };\n"],"mappings":";;;;;;;;AAyFA,IAAM,IAAe,EAAM,YAEvB,EACE,cACA,WACA,aAAU;CAAE,MAAM;CAAa,MAAM;AAAc,GACnD,WACA,SACA,SACA,SACA,YAAS,CAAC,IAAI,GAAG,GACjB,WAAQ,UACR,eACA,eACA,UACA,UACA,cAAW,IACX,kBAAc,IACd,gBAAa,IACb,eAAY,IACZ,eAAY,IACZ,mBACA,mBACA,eACA,kBACA,mBACA,gBACA,gBAAa,IACb,mBACA,iBACA,mBACA,YACA,sBACA,mBACA,oBACA,GAAG,KAEL,MACG;CACH,IAAM,KAAY,EAAiB;EACjC;EACA;EACA;EACA;CACF,CAAC,GAIK,KAAa,EACjB,GACA,gBACA,EACF,GACM,KAAa,EAAkB,CAAU,GAEzC,KAAU,EAAkB,CAAW,GAEvC,KAAc,EAAmB,GAAY,CAAU;CAE7D,OACE,kBAAC,OAAD;EAAU;EAAK,WAAW,EAAG,CAAS;EAAG,GAAI;EAC3C,UAAA,kBAAC,GAAD;GACU;GACC;GACT,WAAU;GAEV,UAAA,kBAAC,GAAD;IACE,QAAQ;KAAE,KAAK;KAAI,OAAO;KAAI,QAAQ;KAAI,MAAM;IAAG;IADrD,UAAA;KAGG,KACC,kBAAC,GAAD;MACE,YAAY,KAAkB;MAC9B,UAAU,KAAgB;MAC1B,iBAAiB,IAAa,QAAQ,KAAA;KACvC,CAAA;KAEH,kBAAC,GAAD;MACE,MAAK;MACL,SAAS;MACT,MAAM;MACN,MAAM;MACN,MAAM,CAAC;MACP,UAAU;MACV,UAAU;MACV,YAAY;MACZ,eAAe;MACf,OAAO;MACP,UAAU;MACV,YAAY,EAAyB,CAAU;MAC/C,QAAQ;MACR,OAAO;KACR,CAAA;KACD,kBAAC,GAAD;MACE,MAAK;MACL,SAAS;MACT,MAAM;MACN,MAAM;MACN,MAAM,CAAC;MACP,UAAU;MACV,UAAU;MACV,YAAY;MACZ,eAAe;MACf,WAAW;MACX,QAAQ;MACR,OAAO,IAAa,KAAK,KAAA;MACzB,OAAO;KACR,CAAA;KACA,KACC,kBAAC,GAAD;MAAO,MAAK;MAAS,SAAS;MAAM,OAAO;MAAQ,MAAM;KAAO,CAAA;KAEjE,MACC,kBAAC,GAAD;MACE,QAAQ,EAAE,iBAAiB,MAAM;MACjC,SAAS,KAAkB,kBAAC,GAAD,CAAsB,CAAA;KAClD,CAAA;KAEF,KAAc,kBAAC,GAAD,EAAa,SAAS,kBAAC,GAAD,CAAqB,CAAA,EAAI,CAAA;KAC7D,EAAO,KAAK,MACX,kBAAC,GAAD;MAEE,MAAM,EAAE;MACR,MAAM,EAAE;MACR,MAAM,eAAe,EAAS,EAAE,GAAG,EAAE;MAC9B;MACP,GAAI;KACL,GANM,EAAE,GAMR,CACF;IACmB;;EACR,CAAA;CACb,CAAA;AAET,CACF;AACA,EAAa,cAAc"}
|