@altertable/data-app 0.67.0 → 0.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{index-8qfxv0h5.js → index-gvers8jw.js} +173 -3
- package/dist/chunks/{index-8qfxv0h5.js.map → index-gvers8jw.js.map} +4 -3
- package/dist/react/index.js +24 -12
- package/dist/react/index.js.map +3 -4
- package/dist/react/ui/index.js +1105 -238
- package/dist/react/ui/index.js.map +15 -10
- package/dist/types/react/style-contract.d.ts +12 -0
- package/dist/types/react/ui/ChartLegend.d.ts +1 -1
- package/dist/types/react/ui/ComposedChart.d.ts +1 -1
- package/dist/types/react/ui/FunnelChart.d.ts +20 -0
- package/dist/types/react/ui/JourneyChart.d.ts +10 -0
- package/dist/types/react/ui/RetentionChart.d.ts +27 -0
- package/dist/types/react/ui/chart-data.d.ts +9 -0
- package/dist/types/react/ui/chart-primitives.d.ts +4 -2
- package/dist/types/react/ui/chartColor.d.ts +1 -0
- package/dist/types/react/ui/index.d.ts +7 -0
- package/dist/types/react/ui/journey-flow.d.ts +49 -0
- package/dist/types/react/ui/useSvgId.d.ts +2 -0
- package/dist/worker.js +1 -1
- package/docs/contract.md +2 -1
- package/docs/ui.md +20 -2
- package/docs/widgets.md +15 -12
- package/docs/worker.md +2 -1
- package/package.json +1 -1
package/dist/react/ui/index.js
CHANGED
|
@@ -60,9 +60,13 @@ import {
|
|
|
60
60
|
TableWidget2,
|
|
61
61
|
TextWidget2,
|
|
62
62
|
DataValue2,
|
|
63
|
+
chartBarFill,
|
|
63
64
|
createDataContext2
|
|
64
|
-
} from "../../chunks/index-
|
|
65
|
-
import
|
|
65
|
+
} from "../../chunks/index-gvers8jw.js";
|
|
66
|
+
import {
|
|
67
|
+
formatNumber2,
|
|
68
|
+
formatPercent2
|
|
69
|
+
} from "../../chunks/index-mev09s5v.js";
|
|
66
70
|
import"../../chunks/index-ag0cgnvq.js";
|
|
67
71
|
import"../../chunks/index-mt93ff2b.js";
|
|
68
72
|
import"../../chunks/index-m6n8ctfc.js";
|
|
@@ -1080,7 +1084,7 @@ function GlossaryDefinition({
|
|
|
1080
1084
|
});
|
|
1081
1085
|
}
|
|
1082
1086
|
// src/react/ui/TrendChart.tsx
|
|
1083
|
-
import { useLayoutEffect, useRef, useState } from "react";
|
|
1087
|
+
import { useLayoutEffect, useRef, useState as useState2 } from "react";
|
|
1084
1088
|
import {
|
|
1085
1089
|
AreaChart as RechartsAreaChart,
|
|
1086
1090
|
LineChart as RechartsLineChart,
|
|
@@ -1100,8 +1104,167 @@ import {
|
|
|
1100
1104
|
Tooltip,
|
|
1101
1105
|
DefaultTooltipContent
|
|
1102
1106
|
} from "recharts";
|
|
1103
|
-
|
|
1104
|
-
|
|
1107
|
+
|
|
1108
|
+
// src/react/ui/ChartLegend.tsx
|
|
1109
|
+
import {
|
|
1110
|
+
Children,
|
|
1111
|
+
useId,
|
|
1112
|
+
useState
|
|
1113
|
+
} from "react";
|
|
1114
|
+
import { jsx as jsx6, jsxs as jsxs4, Fragment } from "react/jsx-runtime";
|
|
1115
|
+
function ChartLegendRoot({
|
|
1116
|
+
children,
|
|
1117
|
+
layout = "horizontal",
|
|
1118
|
+
align = "start",
|
|
1119
|
+
maxVisibleItems = 6,
|
|
1120
|
+
className,
|
|
1121
|
+
"aria-label": ariaLabel = "Chart legend",
|
|
1122
|
+
...props
|
|
1123
|
+
}) {
|
|
1124
|
+
const [expanded, setExpanded] = useState(false);
|
|
1125
|
+
const listId = useId();
|
|
1126
|
+
const items = Children.toArray(children);
|
|
1127
|
+
const limit = Number.isFinite(maxVisibleItems) ? Math.max(1, Math.floor(maxVisibleItems)) : 6;
|
|
1128
|
+
const visibleCount = items.length > limit ? limit - 1 : limit;
|
|
1129
|
+
const hiddenCount = Math.max(0, items.length - visibleCount);
|
|
1130
|
+
return /* @__PURE__ */ jsx6("div", {
|
|
1131
|
+
className: "altertable-chart-legend-container",
|
|
1132
|
+
children: /* @__PURE__ */ jsxs4("ul", {
|
|
1133
|
+
id: listId,
|
|
1134
|
+
...props,
|
|
1135
|
+
"aria-label": ariaLabel,
|
|
1136
|
+
"data-layout": layout,
|
|
1137
|
+
"data-align": align,
|
|
1138
|
+
className: classNames("altertable-chart-legend", className),
|
|
1139
|
+
children: [
|
|
1140
|
+
expanded ? items : items.slice(0, visibleCount),
|
|
1141
|
+
hiddenCount > 0 && /* @__PURE__ */ jsx6("li", {
|
|
1142
|
+
className: "altertable-chart-legend-overflow",
|
|
1143
|
+
children: /* @__PURE__ */ jsx6("button", {
|
|
1144
|
+
type: "button",
|
|
1145
|
+
"data-atbl-control": "action",
|
|
1146
|
+
"data-atbl-focus": "ring",
|
|
1147
|
+
className: "altertable-chart-legend-action",
|
|
1148
|
+
"aria-expanded": expanded,
|
|
1149
|
+
"aria-controls": props.id ?? listId,
|
|
1150
|
+
onClick: () => setExpanded((value) => !value),
|
|
1151
|
+
children: expanded ? "Show fewer" : `+${hiddenCount} more`
|
|
1152
|
+
})
|
|
1153
|
+
})
|
|
1154
|
+
]
|
|
1155
|
+
})
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
function ChartLegendItem({
|
|
1159
|
+
inactive,
|
|
1160
|
+
className,
|
|
1161
|
+
...props
|
|
1162
|
+
}) {
|
|
1163
|
+
return /* @__PURE__ */ jsx6("li", {
|
|
1164
|
+
...props,
|
|
1165
|
+
"data-inactive": inactive || undefined,
|
|
1166
|
+
className: classNames("altertable-chart-legend-item", className)
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
function ChartLegendMarker({
|
|
1170
|
+
kind,
|
|
1171
|
+
color = kind === "bar" ? chartBarFill : "var(--atbl-accent)",
|
|
1172
|
+
className,
|
|
1173
|
+
style,
|
|
1174
|
+
...props
|
|
1175
|
+
}) {
|
|
1176
|
+
return /* @__PURE__ */ jsxs4("svg", {
|
|
1177
|
+
...props,
|
|
1178
|
+
"aria-hidden": "true",
|
|
1179
|
+
focusable: "false",
|
|
1180
|
+
viewBox: kind === "square" ? "0 0 8 8" : "0 0 20 12",
|
|
1181
|
+
"data-kind": kind,
|
|
1182
|
+
className: classNames("altertable-chart-legend-marker", className),
|
|
1183
|
+
style: { color, ...style },
|
|
1184
|
+
children: [
|
|
1185
|
+
kind === "square" && /* @__PURE__ */ jsx6("rect", {
|
|
1186
|
+
width: "8",
|
|
1187
|
+
height: "8",
|
|
1188
|
+
fill: "currentColor"
|
|
1189
|
+
}),
|
|
1190
|
+
kind === "bar" && /* @__PURE__ */ jsx6("path", {
|
|
1191
|
+
d: "M6 11V3Q6 1 8 1H12Q14 1 14 3V11Z",
|
|
1192
|
+
fill: "currentColor"
|
|
1193
|
+
}),
|
|
1194
|
+
kind === "slice" && /* @__PURE__ */ jsx6("rect", {
|
|
1195
|
+
x: "5",
|
|
1196
|
+
y: "1",
|
|
1197
|
+
width: "10",
|
|
1198
|
+
height: "10",
|
|
1199
|
+
rx: "2",
|
|
1200
|
+
fill: "currentColor"
|
|
1201
|
+
}),
|
|
1202
|
+
kind === "point" && /* @__PURE__ */ jsx6("circle", {
|
|
1203
|
+
cx: "10",
|
|
1204
|
+
cy: "6",
|
|
1205
|
+
r: "3",
|
|
1206
|
+
fill: "currentColor"
|
|
1207
|
+
}),
|
|
1208
|
+
kind === "line" && /* @__PURE__ */ jsxs4(Fragment, {
|
|
1209
|
+
children: [
|
|
1210
|
+
/* @__PURE__ */ jsx6("path", {
|
|
1211
|
+
d: "M1 6H19",
|
|
1212
|
+
fill: "none",
|
|
1213
|
+
stroke: "currentColor",
|
|
1214
|
+
strokeWidth: "2",
|
|
1215
|
+
strokeLinecap: "round"
|
|
1216
|
+
}),
|
|
1217
|
+
/* @__PURE__ */ jsx6("circle", {
|
|
1218
|
+
cx: "10",
|
|
1219
|
+
cy: "6",
|
|
1220
|
+
r: "2.5",
|
|
1221
|
+
fill: "var(--atbl-surface)",
|
|
1222
|
+
stroke: "currentColor",
|
|
1223
|
+
strokeWidth: "2"
|
|
1224
|
+
})
|
|
1225
|
+
]
|
|
1226
|
+
}),
|
|
1227
|
+
kind === "area" && /* @__PURE__ */ jsxs4(Fragment, {
|
|
1228
|
+
children: [
|
|
1229
|
+
/* @__PURE__ */ jsx6("path", {
|
|
1230
|
+
d: "M1 10L7 5L13 7L19 2V11H1Z",
|
|
1231
|
+
fill: "currentColor",
|
|
1232
|
+
fillOpacity: "0.14"
|
|
1233
|
+
}),
|
|
1234
|
+
/* @__PURE__ */ jsx6("path", {
|
|
1235
|
+
d: "M1 10L7 5L13 7L19 2",
|
|
1236
|
+
fill: "none",
|
|
1237
|
+
stroke: "currentColor",
|
|
1238
|
+
strokeWidth: "2",
|
|
1239
|
+
strokeLinejoin: "round",
|
|
1240
|
+
strokeLinecap: "round"
|
|
1241
|
+
})
|
|
1242
|
+
]
|
|
1243
|
+
})
|
|
1244
|
+
]
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
function ChartLegendLabel({
|
|
1248
|
+
className,
|
|
1249
|
+
children,
|
|
1250
|
+
title,
|
|
1251
|
+
...props
|
|
1252
|
+
}) {
|
|
1253
|
+
return /* @__PURE__ */ jsx6("span", {
|
|
1254
|
+
...props,
|
|
1255
|
+
title: title ?? (typeof children === "string" ? children : undefined),
|
|
1256
|
+
className: classNames("altertable-chart-legend-label", className),
|
|
1257
|
+
children
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
var ChartLegend = Object.assign(ChartLegendRoot, {
|
|
1261
|
+
Item: ChartLegendItem,
|
|
1262
|
+
Marker: ChartLegendMarker,
|
|
1263
|
+
Label: ChartLegendLabel
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
// src/react/ui/chart-primitives.tsx
|
|
1267
|
+
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1105
1268
|
var accent = "var(--atbl-accent)";
|
|
1106
1269
|
function ChartDot({
|
|
1107
1270
|
active = false,
|
|
@@ -1110,7 +1273,7 @@ function ChartDot({
|
|
|
1110
1273
|
r = active ? 4.5 : 3,
|
|
1111
1274
|
...props
|
|
1112
1275
|
}) {
|
|
1113
|
-
return /* @__PURE__ */
|
|
1276
|
+
return /* @__PURE__ */ jsx7(Dot, {
|
|
1114
1277
|
stroke,
|
|
1115
1278
|
fill,
|
|
1116
1279
|
r,
|
|
@@ -1119,7 +1282,7 @@ function ChartDot({
|
|
|
1119
1282
|
});
|
|
1120
1283
|
}
|
|
1121
1284
|
function ChartBar(props) {
|
|
1122
|
-
return /* @__PURE__ */
|
|
1285
|
+
return /* @__PURE__ */ jsx7(Bar, {
|
|
1123
1286
|
fill: chartBarFill,
|
|
1124
1287
|
radius: [5, 5, 0, 0],
|
|
1125
1288
|
maxBarSize: 30,
|
|
@@ -1134,18 +1297,18 @@ function ChartLine({
|
|
|
1134
1297
|
activeDot,
|
|
1135
1298
|
...props
|
|
1136
1299
|
}) {
|
|
1137
|
-
return /* @__PURE__ */
|
|
1300
|
+
return /* @__PURE__ */ jsx7(Line, {
|
|
1138
1301
|
type: "linear",
|
|
1139
1302
|
stroke,
|
|
1140
1303
|
strokeWidth: 2,
|
|
1141
1304
|
strokeLinecap: "round",
|
|
1142
1305
|
strokeLinejoin: "round",
|
|
1143
|
-
dot: dot === undefined || dot === true ? ({ cx, cy }) => /* @__PURE__ */
|
|
1306
|
+
dot: dot === undefined || dot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx7(ChartDot, {
|
|
1144
1307
|
cx,
|
|
1145
1308
|
cy,
|
|
1146
1309
|
stroke
|
|
1147
1310
|
}) : dot,
|
|
1148
|
-
activeDot: activeDot === undefined || activeDot === true ? ({ cx, cy }) => /* @__PURE__ */
|
|
1311
|
+
activeDot: activeDot === undefined || activeDot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx7(ChartDot, {
|
|
1149
1312
|
cx,
|
|
1150
1313
|
cy,
|
|
1151
1314
|
stroke,
|
|
@@ -1161,7 +1324,7 @@ function ChartArea({
|
|
|
1161
1324
|
activeDot,
|
|
1162
1325
|
...props
|
|
1163
1326
|
}) {
|
|
1164
|
-
return /* @__PURE__ */
|
|
1327
|
+
return /* @__PURE__ */ jsx7(Area, {
|
|
1165
1328
|
legendType: "square",
|
|
1166
1329
|
type: "linear",
|
|
1167
1330
|
stroke,
|
|
@@ -1170,12 +1333,12 @@ function ChartArea({
|
|
|
1170
1333
|
strokeLinejoin: "round",
|
|
1171
1334
|
fill: stroke,
|
|
1172
1335
|
fillOpacity: 0.14,
|
|
1173
|
-
dot: dot === undefined || dot === true ? ({ cx, cy }) => /* @__PURE__ */
|
|
1336
|
+
dot: dot === undefined || dot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx7(ChartDot, {
|
|
1174
1337
|
cx,
|
|
1175
1338
|
cy,
|
|
1176
1339
|
stroke
|
|
1177
1340
|
}) : dot,
|
|
1178
|
-
activeDot: activeDot === undefined || activeDot === true ? ({ cx, cy }) => /* @__PURE__ */
|
|
1341
|
+
activeDot: activeDot === undefined || activeDot === true ? ({ cx, cy }) => /* @__PURE__ */ jsx7(ChartDot, {
|
|
1179
1342
|
cx,
|
|
1180
1343
|
cy,
|
|
1181
1344
|
stroke,
|
|
@@ -1186,14 +1349,14 @@ function ChartArea({
|
|
|
1186
1349
|
});
|
|
1187
1350
|
}
|
|
1188
1351
|
function ChartScatter(props) {
|
|
1189
|
-
return /* @__PURE__ */
|
|
1352
|
+
return /* @__PURE__ */ jsx7(Scatter, {
|
|
1190
1353
|
fill: accent,
|
|
1191
1354
|
isAnimationActive: false,
|
|
1192
1355
|
...props
|
|
1193
1356
|
});
|
|
1194
1357
|
}
|
|
1195
1358
|
function ChartXAxis(props) {
|
|
1196
|
-
return /* @__PURE__ */
|
|
1359
|
+
return /* @__PURE__ */ jsx7(XAxis, {
|
|
1197
1360
|
stroke: "var(--atbl-muted)",
|
|
1198
1361
|
tickLine: false,
|
|
1199
1362
|
axisLine: { stroke: "var(--atbl-border)" },
|
|
@@ -1202,7 +1365,7 @@ function ChartXAxis(props) {
|
|
|
1202
1365
|
});
|
|
1203
1366
|
}
|
|
1204
1367
|
function ChartYAxis(props) {
|
|
1205
|
-
return /* @__PURE__ */
|
|
1368
|
+
return /* @__PURE__ */ jsx7(YAxis, {
|
|
1206
1369
|
stroke: "var(--atbl-muted)",
|
|
1207
1370
|
tickLine: false,
|
|
1208
1371
|
axisLine: false,
|
|
@@ -1211,7 +1374,7 @@ function ChartYAxis(props) {
|
|
|
1211
1374
|
});
|
|
1212
1375
|
}
|
|
1213
1376
|
function ChartGrid(props) {
|
|
1214
|
-
return /* @__PURE__ */
|
|
1377
|
+
return /* @__PURE__ */ jsx7(CartesianGrid, {
|
|
1215
1378
|
stroke: "var(--atbl-border)",
|
|
1216
1379
|
vertical: false,
|
|
1217
1380
|
...props
|
|
@@ -1220,11 +1383,45 @@ function ChartGrid(props) {
|
|
|
1220
1383
|
function ChartTooltipContent(props) {
|
|
1221
1384
|
if (!props.active)
|
|
1222
1385
|
return null;
|
|
1223
|
-
return /* @__PURE__ */
|
|
1386
|
+
return /* @__PURE__ */ jsx7(TooltipSurface, {
|
|
1224
1387
|
"data-variant": "chart",
|
|
1225
1388
|
role: "tooltip",
|
|
1226
|
-
children: /* @__PURE__ */
|
|
1389
|
+
children: /* @__PURE__ */ jsx7(DefaultTooltipContent, {
|
|
1227
1390
|
...props,
|
|
1391
|
+
separator: "",
|
|
1392
|
+
payload: props.payload?.map((entry) => ({
|
|
1393
|
+
...entry,
|
|
1394
|
+
unit: undefined,
|
|
1395
|
+
formatter: (value, name, _item, index, payload) => {
|
|
1396
|
+
const formatter = entry.formatter ?? props.formatter;
|
|
1397
|
+
const formatted = formatter ? formatter(value, name, entry, index, props.payload ?? payload) : Array.isArray(value) ? value.join(" ~ ") : value;
|
|
1398
|
+
if (formatted == null)
|
|
1399
|
+
return null;
|
|
1400
|
+
const [displayValue, displayName] = Array.isArray(formatted) ? formatted : [formatted, name];
|
|
1401
|
+
return [
|
|
1402
|
+
/* @__PURE__ */ jsxs5("span", {
|
|
1403
|
+
className: "altertable-chart-tooltip-row",
|
|
1404
|
+
children: [
|
|
1405
|
+
/* @__PURE__ */ jsx7(ChartLegend.Marker, {
|
|
1406
|
+
kind: "square",
|
|
1407
|
+
className: "altertable-chart-tooltip-marker",
|
|
1408
|
+
color: entry.color ?? entry.stroke ?? entry.fill ?? "var(--atbl-accent)"
|
|
1409
|
+
}),
|
|
1410
|
+
/* @__PURE__ */ jsxs5("span", {
|
|
1411
|
+
className: "altertable-chart-tooltip-reading",
|
|
1412
|
+
children: [
|
|
1413
|
+
displayName,
|
|
1414
|
+
displayName !== undefined && displayName !== "" ? props.separator ?? " : " : "",
|
|
1415
|
+
displayValue,
|
|
1416
|
+
entry.unit
|
|
1417
|
+
]
|
|
1418
|
+
})
|
|
1419
|
+
]
|
|
1420
|
+
}, entry.graphicalItemId),
|
|
1421
|
+
""
|
|
1422
|
+
];
|
|
1423
|
+
}
|
|
1424
|
+
})),
|
|
1228
1425
|
contentStyle: {
|
|
1229
1426
|
margin: 0,
|
|
1230
1427
|
padding: 0,
|
|
@@ -1240,13 +1437,18 @@ function ChartTooltipContent(props) {
|
|
|
1240
1437
|
lineHeight: 1.2,
|
|
1241
1438
|
...props.labelStyle
|
|
1242
1439
|
},
|
|
1243
|
-
itemStyle: {
|
|
1440
|
+
itemStyle: {
|
|
1441
|
+
color: "var(--atbl-text)",
|
|
1442
|
+
paddingTop: 2,
|
|
1443
|
+
paddingBottom: 0,
|
|
1444
|
+
...props.itemStyle
|
|
1445
|
+
}
|
|
1244
1446
|
})
|
|
1245
1447
|
});
|
|
1246
1448
|
}
|
|
1247
1449
|
function ChartTooltip(props) {
|
|
1248
|
-
return /* @__PURE__ */
|
|
1249
|
-
content: /* @__PURE__ */
|
|
1450
|
+
return /* @__PURE__ */ jsx7(Tooltip, {
|
|
1451
|
+
content: /* @__PURE__ */ jsx7(ChartTooltipContent, {}),
|
|
1250
1452
|
cursor: false,
|
|
1251
1453
|
isAnimationActive: false,
|
|
1252
1454
|
...props
|
|
@@ -1272,7 +1474,7 @@ function validateChartItems(kind, items) {
|
|
|
1272
1474
|
}
|
|
1273
1475
|
|
|
1274
1476
|
// src/react/ui/TrendChart.tsx
|
|
1275
|
-
import { jsx as
|
|
1477
|
+
import { jsx as jsx8, jsxs as jsxs6, Fragment as Fragment2 } from "react/jsx-runtime";
|
|
1276
1478
|
function TrendChart({
|
|
1277
1479
|
items,
|
|
1278
1480
|
unit,
|
|
@@ -1282,7 +1484,7 @@ function TrendChart({
|
|
|
1282
1484
|
}) {
|
|
1283
1485
|
validateChartItems(area ? "area" : "line", items);
|
|
1284
1486
|
const Plot = area ? RechartsAreaChart : RechartsLineChart;
|
|
1285
|
-
const [activeIndex, setActiveIndex] =
|
|
1487
|
+
const [activeIndex, setActiveIndex] = useState2();
|
|
1286
1488
|
const scrollport = useRef(null);
|
|
1287
1489
|
useLayoutEffect(() => {
|
|
1288
1490
|
const element = scrollport.current;
|
|
@@ -1292,25 +1494,25 @@ function TrendChart({
|
|
|
1292
1494
|
const magnitude = items.reduce((max, item) => Math.max(max, Math.abs(item.value)), 1);
|
|
1293
1495
|
const low = items.reduce((min, item) => Math.min(min, item.value / magnitude), 0);
|
|
1294
1496
|
const high = items.reduce((max, item) => Math.max(max, item.value / magnitude), 0);
|
|
1295
|
-
return /* @__PURE__ */
|
|
1497
|
+
return /* @__PURE__ */ jsxs6("section", {
|
|
1296
1498
|
className: "altertable-trend-chart",
|
|
1297
1499
|
"aria-label": ariaLabel,
|
|
1298
1500
|
children: [
|
|
1299
|
-
items.length === 0 && /* @__PURE__ */
|
|
1501
|
+
items.length === 0 && /* @__PURE__ */ jsx8("span", {
|
|
1300
1502
|
children: "No data"
|
|
1301
1503
|
}),
|
|
1302
|
-
items.length > 0 && /* @__PURE__ */
|
|
1303
|
-
children: /* @__PURE__ */
|
|
1504
|
+
items.length > 0 && /* @__PURE__ */ jsx8(Fragment2, {
|
|
1505
|
+
children: /* @__PURE__ */ jsx8("div", {
|
|
1304
1506
|
className: "altertable-trend-scroll",
|
|
1305
1507
|
ref: scrollport,
|
|
1306
|
-
children: /* @__PURE__ */
|
|
1508
|
+
children: /* @__PURE__ */ jsxs6("div", {
|
|
1307
1509
|
className: "altertable-trend-plot",
|
|
1308
1510
|
style: { minWidth: items.length * 40 },
|
|
1309
1511
|
children: [
|
|
1310
|
-
/* @__PURE__ */
|
|
1512
|
+
/* @__PURE__ */ jsx8("div", {
|
|
1311
1513
|
className: "altertable-chart-canvas",
|
|
1312
1514
|
"aria-hidden": "true",
|
|
1313
|
-
children: /* @__PURE__ */
|
|
1515
|
+
children: /* @__PURE__ */ jsxs6(Plot, {
|
|
1314
1516
|
data: items.map((item) => ({
|
|
1315
1517
|
...item,
|
|
1316
1518
|
plotValue: item.value / magnitude
|
|
@@ -1323,29 +1525,29 @@ function TrendChart({
|
|
|
1323
1525
|
accessibilityLayer: false,
|
|
1324
1526
|
tabIndex: -1,
|
|
1325
1527
|
children: [
|
|
1326
|
-
/* @__PURE__ */
|
|
1528
|
+
/* @__PURE__ */ jsx8(ChartXAxis, {
|
|
1327
1529
|
hide: true,
|
|
1328
1530
|
dataKey: "id",
|
|
1329
1531
|
scale: "band"
|
|
1330
1532
|
}),
|
|
1331
|
-
/* @__PURE__ */
|
|
1533
|
+
/* @__PURE__ */ jsx8(ChartYAxis, {
|
|
1332
1534
|
hide: true,
|
|
1333
1535
|
domain: [low, high === low ? low + 1 : high]
|
|
1334
1536
|
}),
|
|
1335
|
-
/* @__PURE__ */
|
|
1537
|
+
/* @__PURE__ */ jsx8(ReferenceLine, {
|
|
1336
1538
|
y: 0,
|
|
1337
1539
|
stroke: "var(--atbl-border)"
|
|
1338
1540
|
}),
|
|
1339
|
-
area && /* @__PURE__ */
|
|
1541
|
+
area && /* @__PURE__ */ jsx8(ChartArea, {
|
|
1340
1542
|
dataKey: "plotValue",
|
|
1341
1543
|
stroke: "none",
|
|
1342
1544
|
fill: "var(--atbl-accent)",
|
|
1343
1545
|
dot: false,
|
|
1344
1546
|
activeDot: false
|
|
1345
1547
|
}),
|
|
1346
|
-
/* @__PURE__ */
|
|
1548
|
+
/* @__PURE__ */ jsx8(ChartLine, {
|
|
1347
1549
|
dataKey: "plotValue",
|
|
1348
|
-
dot: ({ cx, cy, index }) => /* @__PURE__ */
|
|
1550
|
+
dot: ({ cx, cy, index }) => /* @__PURE__ */ jsx8(ChartDot, {
|
|
1349
1551
|
cx,
|
|
1350
1552
|
cy,
|
|
1351
1553
|
active: index === activeIndex
|
|
@@ -1355,17 +1557,17 @@ function TrendChart({
|
|
|
1355
1557
|
]
|
|
1356
1558
|
})
|
|
1357
1559
|
}),
|
|
1358
|
-
/* @__PURE__ */
|
|
1560
|
+
/* @__PURE__ */ jsx8("div", {
|
|
1359
1561
|
className: "altertable-trend-points",
|
|
1360
|
-
children: items.map((item, index) => /* @__PURE__ */
|
|
1562
|
+
children: items.map((item, index) => /* @__PURE__ */ jsx8(Tooltip2, {
|
|
1361
1563
|
variant: "chart",
|
|
1362
1564
|
onOpenChange: (open) => setActiveIndex((current) => open ? index : current === index ? undefined : current),
|
|
1363
|
-
content: /* @__PURE__ */
|
|
1565
|
+
content: /* @__PURE__ */ jsxs6(Fragment2, {
|
|
1364
1566
|
children: [
|
|
1365
|
-
/* @__PURE__ */
|
|
1567
|
+
/* @__PURE__ */ jsx8("strong", {
|
|
1366
1568
|
children: item.label
|
|
1367
1569
|
}),
|
|
1368
|
-
/* @__PURE__ */
|
|
1570
|
+
/* @__PURE__ */ jsxs6("span", {
|
|
1369
1571
|
children: [
|
|
1370
1572
|
formatValue(item.value),
|
|
1371
1573
|
" ",
|
|
@@ -1375,7 +1577,7 @@ function TrendChart({
|
|
|
1375
1577
|
]
|
|
1376
1578
|
}),
|
|
1377
1579
|
className: "altertable-chart-column",
|
|
1378
|
-
children: /* @__PURE__ */
|
|
1580
|
+
children: /* @__PURE__ */ jsxs6("button", {
|
|
1379
1581
|
"data-atbl-focus": "ring",
|
|
1380
1582
|
"data-atbl-control": "default",
|
|
1381
1583
|
type: "button",
|
|
@@ -1383,10 +1585,10 @@ function TrendChart({
|
|
|
1383
1585
|
className: "altertable-trend-point",
|
|
1384
1586
|
"aria-label": `${item.label}: ${formatValue(item.value)} ${unit}`,
|
|
1385
1587
|
children: [
|
|
1386
|
-
/* @__PURE__ */
|
|
1588
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1387
1589
|
className: "altertable-trend-hit"
|
|
1388
1590
|
}),
|
|
1389
|
-
/* @__PURE__ */
|
|
1591
|
+
/* @__PURE__ */ jsx8("span", {
|
|
1390
1592
|
className: "altertable-trend-label",
|
|
1391
1593
|
children: item.label
|
|
1392
1594
|
})
|
|
@@ -1403,176 +1605,23 @@ function TrendChart({
|
|
|
1403
1605
|
}
|
|
1404
1606
|
|
|
1405
1607
|
// src/react/ui/LineChart.tsx
|
|
1406
|
-
import { jsx as
|
|
1608
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
1407
1609
|
function LineChart(props) {
|
|
1408
|
-
return /* @__PURE__ */
|
|
1610
|
+
return /* @__PURE__ */ jsx9(TrendChart, {
|
|
1409
1611
|
...props
|
|
1410
1612
|
});
|
|
1411
1613
|
}
|
|
1412
1614
|
// src/react/ui/AreaChart.tsx
|
|
1413
|
-
import { jsx as
|
|
1615
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1414
1616
|
function AreaChart(props) {
|
|
1415
|
-
return /* @__PURE__ */
|
|
1617
|
+
return /* @__PURE__ */ jsx10(TrendChart, {
|
|
1416
1618
|
...props,
|
|
1417
1619
|
area: true
|
|
1418
1620
|
});
|
|
1419
1621
|
}
|
|
1420
|
-
// src/react/ui/ChartLegend.tsx
|
|
1421
|
-
import {
|
|
1422
|
-
Children,
|
|
1423
|
-
useId,
|
|
1424
|
-
useState as useState2
|
|
1425
|
-
} from "react";
|
|
1426
|
-
import { jsx as jsx10, jsxs as jsxs5, Fragment as Fragment2 } from "react/jsx-runtime";
|
|
1427
|
-
function ChartLegendRoot({
|
|
1428
|
-
children,
|
|
1429
|
-
layout = "horizontal",
|
|
1430
|
-
align = "start",
|
|
1431
|
-
maxVisibleItems = 6,
|
|
1432
|
-
className,
|
|
1433
|
-
"aria-label": ariaLabel = "Chart legend",
|
|
1434
|
-
...props
|
|
1435
|
-
}) {
|
|
1436
|
-
const [expanded, setExpanded] = useState2(false);
|
|
1437
|
-
const listId = useId();
|
|
1438
|
-
const items = Children.toArray(children);
|
|
1439
|
-
const limit = Number.isFinite(maxVisibleItems) ? Math.max(1, Math.floor(maxVisibleItems)) : 6;
|
|
1440
|
-
const visibleCount = items.length > limit ? limit - 1 : limit;
|
|
1441
|
-
const hiddenCount = Math.max(0, items.length - visibleCount);
|
|
1442
|
-
return /* @__PURE__ */ jsx10("div", {
|
|
1443
|
-
className: "altertable-chart-legend-container",
|
|
1444
|
-
children: /* @__PURE__ */ jsxs5("ul", {
|
|
1445
|
-
id: listId,
|
|
1446
|
-
...props,
|
|
1447
|
-
"aria-label": ariaLabel,
|
|
1448
|
-
"data-layout": layout,
|
|
1449
|
-
"data-align": align,
|
|
1450
|
-
className: classNames("altertable-chart-legend", className),
|
|
1451
|
-
children: [
|
|
1452
|
-
expanded ? items : items.slice(0, visibleCount),
|
|
1453
|
-
hiddenCount > 0 && /* @__PURE__ */ jsx10("li", {
|
|
1454
|
-
className: "altertable-chart-legend-overflow",
|
|
1455
|
-
children: /* @__PURE__ */ jsx10("button", {
|
|
1456
|
-
type: "button",
|
|
1457
|
-
"data-atbl-control": "action",
|
|
1458
|
-
"data-atbl-focus": "ring",
|
|
1459
|
-
className: "altertable-chart-legend-action",
|
|
1460
|
-
"aria-expanded": expanded,
|
|
1461
|
-
"aria-controls": props.id ?? listId,
|
|
1462
|
-
onClick: () => setExpanded((value) => !value),
|
|
1463
|
-
children: expanded ? "Show fewer" : `+${hiddenCount} more`
|
|
1464
|
-
})
|
|
1465
|
-
})
|
|
1466
|
-
]
|
|
1467
|
-
})
|
|
1468
|
-
});
|
|
1469
|
-
}
|
|
1470
|
-
function ChartLegendItem({
|
|
1471
|
-
inactive,
|
|
1472
|
-
className,
|
|
1473
|
-
...props
|
|
1474
|
-
}) {
|
|
1475
|
-
return /* @__PURE__ */ jsx10("li", {
|
|
1476
|
-
...props,
|
|
1477
|
-
"data-inactive": inactive || undefined,
|
|
1478
|
-
className: classNames("altertable-chart-legend-item", className)
|
|
1479
|
-
});
|
|
1480
|
-
}
|
|
1481
|
-
function ChartLegendMarker({
|
|
1482
|
-
kind,
|
|
1483
|
-
color = kind === "bar" ? chartBarFill : "var(--atbl-accent)",
|
|
1484
|
-
className,
|
|
1485
|
-
style,
|
|
1486
|
-
...props
|
|
1487
|
-
}) {
|
|
1488
|
-
return /* @__PURE__ */ jsxs5("svg", {
|
|
1489
|
-
...props,
|
|
1490
|
-
"aria-hidden": "true",
|
|
1491
|
-
focusable: "false",
|
|
1492
|
-
viewBox: "0 0 20 12",
|
|
1493
|
-
"data-kind": kind,
|
|
1494
|
-
className: classNames("altertable-chart-legend-marker", className),
|
|
1495
|
-
style: { color, ...style },
|
|
1496
|
-
children: [
|
|
1497
|
-
kind === "bar" && /* @__PURE__ */ jsx10("path", {
|
|
1498
|
-
d: "M6 11V3Q6 1 8 1H12Q14 1 14 3V11Z",
|
|
1499
|
-
fill: "currentColor"
|
|
1500
|
-
}),
|
|
1501
|
-
kind === "slice" && /* @__PURE__ */ jsx10("rect", {
|
|
1502
|
-
x: "5",
|
|
1503
|
-
y: "1",
|
|
1504
|
-
width: "10",
|
|
1505
|
-
height: "10",
|
|
1506
|
-
rx: "2",
|
|
1507
|
-
fill: "currentColor"
|
|
1508
|
-
}),
|
|
1509
|
-
kind === "point" && /* @__PURE__ */ jsx10("circle", {
|
|
1510
|
-
cx: "10",
|
|
1511
|
-
cy: "6",
|
|
1512
|
-
r: "3",
|
|
1513
|
-
fill: "currentColor"
|
|
1514
|
-
}),
|
|
1515
|
-
kind === "line" && /* @__PURE__ */ jsxs5(Fragment2, {
|
|
1516
|
-
children: [
|
|
1517
|
-
/* @__PURE__ */ jsx10("path", {
|
|
1518
|
-
d: "M1 6H19",
|
|
1519
|
-
fill: "none",
|
|
1520
|
-
stroke: "currentColor",
|
|
1521
|
-
strokeWidth: "2",
|
|
1522
|
-
strokeLinecap: "round"
|
|
1523
|
-
}),
|
|
1524
|
-
/* @__PURE__ */ jsx10("circle", {
|
|
1525
|
-
cx: "10",
|
|
1526
|
-
cy: "6",
|
|
1527
|
-
r: "2.5",
|
|
1528
|
-
fill: "var(--atbl-surface)",
|
|
1529
|
-
stroke: "currentColor",
|
|
1530
|
-
strokeWidth: "2"
|
|
1531
|
-
})
|
|
1532
|
-
]
|
|
1533
|
-
}),
|
|
1534
|
-
kind === "area" && /* @__PURE__ */ jsxs5(Fragment2, {
|
|
1535
|
-
children: [
|
|
1536
|
-
/* @__PURE__ */ jsx10("path", {
|
|
1537
|
-
d: "M1 10L7 5L13 7L19 2V11H1Z",
|
|
1538
|
-
fill: "currentColor",
|
|
1539
|
-
fillOpacity: "0.14"
|
|
1540
|
-
}),
|
|
1541
|
-
/* @__PURE__ */ jsx10("path", {
|
|
1542
|
-
d: "M1 10L7 5L13 7L19 2",
|
|
1543
|
-
fill: "none",
|
|
1544
|
-
stroke: "currentColor",
|
|
1545
|
-
strokeWidth: "2",
|
|
1546
|
-
strokeLinejoin: "round",
|
|
1547
|
-
strokeLinecap: "round"
|
|
1548
|
-
})
|
|
1549
|
-
]
|
|
1550
|
-
})
|
|
1551
|
-
]
|
|
1552
|
-
});
|
|
1553
|
-
}
|
|
1554
|
-
function ChartLegendLabel({
|
|
1555
|
-
className,
|
|
1556
|
-
children,
|
|
1557
|
-
title,
|
|
1558
|
-
...props
|
|
1559
|
-
}) {
|
|
1560
|
-
return /* @__PURE__ */ jsx10("span", {
|
|
1561
|
-
...props,
|
|
1562
|
-
title: title ?? (typeof children === "string" ? children : undefined),
|
|
1563
|
-
className: classNames("altertable-chart-legend-label", className),
|
|
1564
|
-
children
|
|
1565
|
-
});
|
|
1566
|
-
}
|
|
1567
|
-
var ChartLegend = Object.assign(ChartLegendRoot, {
|
|
1568
|
-
Item: ChartLegendItem,
|
|
1569
|
-
Marker: ChartLegendMarker,
|
|
1570
|
-
Label: ChartLegendLabel
|
|
1571
|
-
});
|
|
1572
|
-
|
|
1573
1622
|
// src/react/ui/PieChart.tsx
|
|
1574
1623
|
import { useState as useState3 } from "react";
|
|
1575
|
-
import { jsx as jsx11, jsxs as
|
|
1624
|
+
import { jsx as jsx11, jsxs as jsxs7, Fragment as Fragment3 } from "react/jsx-runtime";
|
|
1576
1625
|
function PieChart({
|
|
1577
1626
|
items,
|
|
1578
1627
|
unit,
|
|
@@ -1610,12 +1659,12 @@ function PieChart({
|
|
|
1610
1659
|
}).format(share);
|
|
1611
1660
|
}
|
|
1612
1661
|
function detail(item) {
|
|
1613
|
-
return /* @__PURE__ */
|
|
1662
|
+
return /* @__PURE__ */ jsxs7(Fragment3, {
|
|
1614
1663
|
children: [
|
|
1615
1664
|
/* @__PURE__ */ jsx11("strong", {
|
|
1616
1665
|
children: item.label
|
|
1617
1666
|
}),
|
|
1618
|
-
/* @__PURE__ */
|
|
1667
|
+
/* @__PURE__ */ jsxs7("span", {
|
|
1619
1668
|
children: [
|
|
1620
1669
|
formatValue(item.value),
|
|
1621
1670
|
" ",
|
|
@@ -1628,16 +1677,16 @@ function PieChart({
|
|
|
1628
1677
|
});
|
|
1629
1678
|
}
|
|
1630
1679
|
const preview = slices.find((item) => item.id === previewId);
|
|
1631
|
-
return /* @__PURE__ */
|
|
1680
|
+
return /* @__PURE__ */ jsxs7("section", {
|
|
1632
1681
|
className: "altertable-pie-chart",
|
|
1633
1682
|
"aria-label": ariaLabel,
|
|
1634
1683
|
children: [
|
|
1635
1684
|
items.length === 0 && /* @__PURE__ */ jsx11("span", {
|
|
1636
1685
|
children: "No data"
|
|
1637
1686
|
}),
|
|
1638
|
-
items.length > 0 && /* @__PURE__ */
|
|
1687
|
+
items.length > 0 && /* @__PURE__ */ jsxs7(Fragment3, {
|
|
1639
1688
|
children: [
|
|
1640
|
-
/* @__PURE__ */
|
|
1689
|
+
/* @__PURE__ */ jsxs7("div", {
|
|
1641
1690
|
className: "altertable-pie-plot",
|
|
1642
1691
|
children: [
|
|
1643
1692
|
/* @__PURE__ */ jsx11(Tooltip2, {
|
|
@@ -1690,7 +1739,7 @@ function PieChart({
|
|
|
1690
1739
|
variant: "chart",
|
|
1691
1740
|
content: detail(item),
|
|
1692
1741
|
onOpenChange: (open) => setLegendId((current) => open ? item.id : current === item.id ? null : current),
|
|
1693
|
-
children: /* @__PURE__ */
|
|
1742
|
+
children: /* @__PURE__ */ jsxs7("button", {
|
|
1694
1743
|
"data-atbl-focus": "ring",
|
|
1695
1744
|
"data-atbl-control": "default",
|
|
1696
1745
|
type: "button",
|
|
@@ -1698,7 +1747,7 @@ function PieChart({
|
|
|
1698
1747
|
"aria-label": `${item.label}: ${formatValue(item.value)} ${unit}, ${percent(item.share)}`,
|
|
1699
1748
|
children: [
|
|
1700
1749
|
/* @__PURE__ */ jsx11(ChartLegend.Marker, {
|
|
1701
|
-
kind: "
|
|
1750
|
+
kind: "square",
|
|
1702
1751
|
color: item.color
|
|
1703
1752
|
}),
|
|
1704
1753
|
/* @__PURE__ */ jsx11(ChartLegend.Label, {
|
|
@@ -1722,7 +1771,7 @@ function PieChart({
|
|
|
1722
1771
|
});
|
|
1723
1772
|
}
|
|
1724
1773
|
// src/react/ui/ScatterChart.tsx
|
|
1725
|
-
import { jsx as jsx12, jsxs as
|
|
1774
|
+
import { jsx as jsx12, jsxs as jsxs8, Fragment as Fragment4 } from "react/jsx-runtime";
|
|
1726
1775
|
function formatNumber(value) {
|
|
1727
1776
|
return new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(value);
|
|
1728
1777
|
}
|
|
@@ -1751,16 +1800,16 @@ function ScatterChart({
|
|
|
1751
1800
|
validateChartItems("scatter", items);
|
|
1752
1801
|
const xScale = scale(items.map((item) => item.x));
|
|
1753
1802
|
const yScale = scale(items.map((item) => item.y));
|
|
1754
|
-
return /* @__PURE__ */
|
|
1803
|
+
return /* @__PURE__ */ jsxs8("section", {
|
|
1755
1804
|
className: "altertable-scatter-chart",
|
|
1756
1805
|
"aria-label": ariaLabel,
|
|
1757
1806
|
children: [
|
|
1758
1807
|
items.length === 0 && /* @__PURE__ */ jsx12("span", {
|
|
1759
1808
|
children: "No data"
|
|
1760
1809
|
}),
|
|
1761
|
-
items.length > 0 && /* @__PURE__ */
|
|
1810
|
+
items.length > 0 && /* @__PURE__ */ jsxs8(Fragment4, {
|
|
1762
1811
|
children: [
|
|
1763
|
-
/* @__PURE__ */
|
|
1812
|
+
/* @__PURE__ */ jsxs8("p", {
|
|
1764
1813
|
className: "altertable-scatter-axis-title",
|
|
1765
1814
|
children: [
|
|
1766
1815
|
yLabel,
|
|
@@ -1769,7 +1818,7 @@ function ScatterChart({
|
|
|
1769
1818
|
}),
|
|
1770
1819
|
/* @__PURE__ */ jsx12("div", {
|
|
1771
1820
|
className: "altertable-scatter-axes",
|
|
1772
|
-
children: /* @__PURE__ */
|
|
1821
|
+
children: /* @__PURE__ */ jsxs8("div", {
|
|
1773
1822
|
className: "altertable-scatter-plot",
|
|
1774
1823
|
children: [
|
|
1775
1824
|
yScale.ticks.map((value, index) => /* @__PURE__ */ jsx12("div", {
|
|
@@ -1791,12 +1840,12 @@ function ScatterChart({
|
|
|
1791
1840
|
}, index)),
|
|
1792
1841
|
items.map((item) => /* @__PURE__ */ jsx12(Tooltip2, {
|
|
1793
1842
|
variant: "chart",
|
|
1794
|
-
content: /* @__PURE__ */
|
|
1843
|
+
content: /* @__PURE__ */ jsxs8(Fragment4, {
|
|
1795
1844
|
children: [
|
|
1796
1845
|
/* @__PURE__ */ jsx12("strong", {
|
|
1797
1846
|
children: item.label
|
|
1798
1847
|
}),
|
|
1799
|
-
/* @__PURE__ */
|
|
1848
|
+
/* @__PURE__ */ jsxs8("span", {
|
|
1800
1849
|
children: [
|
|
1801
1850
|
xLabel,
|
|
1802
1851
|
": ",
|
|
@@ -1805,7 +1854,7 @@ function ScatterChart({
|
|
|
1805
1854
|
xUnit
|
|
1806
1855
|
]
|
|
1807
1856
|
}),
|
|
1808
|
-
/* @__PURE__ */
|
|
1857
|
+
/* @__PURE__ */ jsxs8("span", {
|
|
1809
1858
|
children: [
|
|
1810
1859
|
yLabel,
|
|
1811
1860
|
": ",
|
|
@@ -1834,7 +1883,7 @@ function ScatterChart({
|
|
|
1834
1883
|
]
|
|
1835
1884
|
})
|
|
1836
1885
|
}),
|
|
1837
|
-
/* @__PURE__ */
|
|
1886
|
+
/* @__PURE__ */ jsxs8("p", {
|
|
1838
1887
|
className: "altertable-scatter-axis-title altertable-scatter-x-title",
|
|
1839
1888
|
children: [
|
|
1840
1889
|
xLabel,
|
|
@@ -1849,7 +1898,7 @@ function ScatterChart({
|
|
|
1849
1898
|
// src/react/ui/BarChart.tsx
|
|
1850
1899
|
import { useLayoutEffect as useLayoutEffect2, useRef as useRef2, useState as useState4 } from "react";
|
|
1851
1900
|
import { BarChart as RechartsBarChart, Cell } from "recharts";
|
|
1852
|
-
import { jsx as jsx13, jsxs as
|
|
1901
|
+
import { jsx as jsx13, jsxs as jsxs9, Fragment as Fragment5 } from "react/jsx-runtime";
|
|
1853
1902
|
function BarChart({
|
|
1854
1903
|
items,
|
|
1855
1904
|
unit,
|
|
@@ -1865,7 +1914,7 @@ function BarChart({
|
|
|
1865
1914
|
element.scrollLeft = element.scrollWidth - element.clientWidth;
|
|
1866
1915
|
}, [items]);
|
|
1867
1916
|
const maximum = items.reduce((max, item) => Math.max(max, item.value), 1);
|
|
1868
|
-
return /* @__PURE__ */
|
|
1917
|
+
return /* @__PURE__ */ jsxs9("section", {
|
|
1869
1918
|
className: "altertable-selectable-bars",
|
|
1870
1919
|
"aria-label": ariaLabel,
|
|
1871
1920
|
children: [
|
|
@@ -1875,14 +1924,14 @@ function BarChart({
|
|
|
1875
1924
|
/* @__PURE__ */ jsx13("div", {
|
|
1876
1925
|
className: "altertable-selectable-bars-scroll",
|
|
1877
1926
|
ref: scrollport,
|
|
1878
|
-
children: /* @__PURE__ */
|
|
1927
|
+
children: /* @__PURE__ */ jsxs9("div", {
|
|
1879
1928
|
className: "altertable-selectable-bars-plot",
|
|
1880
1929
|
style: { minWidth: items.length * 45 },
|
|
1881
1930
|
children: [
|
|
1882
1931
|
/* @__PURE__ */ jsx13("div", {
|
|
1883
1932
|
className: "altertable-chart-canvas",
|
|
1884
1933
|
"aria-hidden": "true",
|
|
1885
|
-
children: /* @__PURE__ */
|
|
1934
|
+
children: /* @__PURE__ */ jsxs9(RechartsBarChart, {
|
|
1886
1935
|
data: items.map((item) => ({
|
|
1887
1936
|
...item,
|
|
1888
1937
|
plotValue: item.value / maximum
|
|
@@ -1921,12 +1970,12 @@ function BarChart({
|
|
|
1921
1970
|
children: items.map((item) => /* @__PURE__ */ jsx13(Tooltip2, {
|
|
1922
1971
|
variant: "chart",
|
|
1923
1972
|
onOpenChange: (open) => setActiveId((current) => open ? item.id : current === item.id ? undefined : current),
|
|
1924
|
-
content: /* @__PURE__ */
|
|
1973
|
+
content: /* @__PURE__ */ jsxs9(Fragment5, {
|
|
1925
1974
|
children: [
|
|
1926
1975
|
/* @__PURE__ */ jsx13("strong", {
|
|
1927
1976
|
children: item.label
|
|
1928
1977
|
}),
|
|
1929
|
-
/* @__PURE__ */
|
|
1978
|
+
/* @__PURE__ */ jsxs9("span", {
|
|
1930
1979
|
children: [
|
|
1931
1980
|
formatValue(item.value),
|
|
1932
1981
|
" ",
|
|
@@ -1936,7 +1985,7 @@ function BarChart({
|
|
|
1936
1985
|
]
|
|
1937
1986
|
}),
|
|
1938
1987
|
className: "altertable-chart-column",
|
|
1939
|
-
children: /* @__PURE__ */
|
|
1988
|
+
children: /* @__PURE__ */ jsxs9("button", {
|
|
1940
1989
|
"data-atbl-focus": "ring",
|
|
1941
1990
|
"data-atbl-control": "default",
|
|
1942
1991
|
type: "button",
|
|
@@ -1967,14 +2016,14 @@ import {
|
|
|
1967
2016
|
Radio as AriaRadio,
|
|
1968
2017
|
Label
|
|
1969
2018
|
} from "react-aria-components";
|
|
1970
|
-
import { jsx as jsx14, jsxs as
|
|
2019
|
+
import { jsx as jsx14, jsxs as jsxs10, Fragment as Fragment6 } from "react/jsx-runtime";
|
|
1971
2020
|
function RadioGroup({
|
|
1972
2021
|
label,
|
|
1973
2022
|
className,
|
|
1974
2023
|
children,
|
|
1975
2024
|
...props
|
|
1976
2025
|
}) {
|
|
1977
|
-
return /* @__PURE__ */
|
|
2026
|
+
return /* @__PURE__ */ jsxs10(AriaRadioGroup, {
|
|
1978
2027
|
...props,
|
|
1979
2028
|
className: classNames("altertable-radio-group", className),
|
|
1980
2029
|
children: [
|
|
@@ -1992,7 +2041,7 @@ function Radio({ className, children, ...props }) {
|
|
|
1992
2041
|
"data-atbl-control": "action",
|
|
1993
2042
|
"data-atbl-focus": "ring",
|
|
1994
2043
|
className: classNames("altertable-radio", className),
|
|
1995
|
-
children: (state) => /* @__PURE__ */
|
|
2044
|
+
children: (state) => /* @__PURE__ */ jsxs10(Fragment6, {
|
|
1996
2045
|
children: [
|
|
1997
2046
|
/* @__PURE__ */ jsx14("span", {
|
|
1998
2047
|
className: "altertable-radio-mark",
|
|
@@ -2024,7 +2073,7 @@ function SegmentedControl({
|
|
|
2024
2073
|
});
|
|
2025
2074
|
}
|
|
2026
2075
|
// src/react/ui/CheckboxGroup.tsx
|
|
2027
|
-
import { jsx as jsx15, jsxs as
|
|
2076
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2028
2077
|
function CheckboxGroup({
|
|
2029
2078
|
label,
|
|
2030
2079
|
values,
|
|
@@ -2035,7 +2084,7 @@ function CheckboxGroup({
|
|
|
2035
2084
|
...props
|
|
2036
2085
|
}) {
|
|
2037
2086
|
invariant(new Set(values).size === values.length, "Checkbox group values must be unique.");
|
|
2038
|
-
return /* @__PURE__ */
|
|
2087
|
+
return /* @__PURE__ */ jsxs11("fieldset", {
|
|
2039
2088
|
...props,
|
|
2040
2089
|
disabled,
|
|
2041
2090
|
className: classNames("altertable-checkbox-group", className),
|
|
@@ -2056,9 +2105,12 @@ function CheckboxGroup({
|
|
|
2056
2105
|
]
|
|
2057
2106
|
});
|
|
2058
2107
|
}
|
|
2108
|
+
// src/react/ui/ComposedChart.tsx
|
|
2109
|
+
import { useState as useState5 } from "react";
|
|
2110
|
+
|
|
2059
2111
|
// src/react/ui/ComposedChartLegend.tsx
|
|
2060
2112
|
import { Legend } from "recharts";
|
|
2061
|
-
import { jsx as jsx16, jsxs as
|
|
2113
|
+
import { jsx as jsx16, jsxs as jsxs12, Fragment as Fragment7 } from "react/jsx-runtime";
|
|
2062
2114
|
var legendMarkerKinds = {
|
|
2063
2115
|
square: "area",
|
|
2064
2116
|
rect: "bar",
|
|
@@ -2073,7 +2125,7 @@ function LegendContent({
|
|
|
2073
2125
|
align,
|
|
2074
2126
|
labelStyle,
|
|
2075
2127
|
iconType,
|
|
2076
|
-
iconSize =
|
|
2128
|
+
iconSize = 8,
|
|
2077
2129
|
inactiveColor = "var(--atbl-muted)",
|
|
2078
2130
|
position,
|
|
2079
2131
|
verticalAlign,
|
|
@@ -2095,15 +2147,18 @@ function LegendContent({
|
|
|
2095
2147
|
if (entry.type === "none")
|
|
2096
2148
|
return null;
|
|
2097
2149
|
const type = iconType ?? entry.type;
|
|
2098
|
-
const kind = legendMarkerKinds[type ?? "none"] ?? "point";
|
|
2150
|
+
const kind = iconType ? legendMarkerKinds[type ?? "none"] ?? "point" : "square";
|
|
2099
2151
|
const formatLabel = entry.formatter ?? formatter;
|
|
2100
2152
|
const label = formatLabel ? formatLabel(entry.value, entry, index) : entry.value;
|
|
2101
|
-
const content = /* @__PURE__ */
|
|
2153
|
+
const content = /* @__PURE__ */ jsxs12(Fragment7, {
|
|
2102
2154
|
children: [
|
|
2103
2155
|
type !== "none" && (entry.legendIcon ?? /* @__PURE__ */ jsx16(ChartLegend.Marker, {
|
|
2104
2156
|
kind,
|
|
2105
2157
|
color: entry.inactive ? inactiveColor : entry.color,
|
|
2106
|
-
style: {
|
|
2158
|
+
style: {
|
|
2159
|
+
width: iconSize,
|
|
2160
|
+
height: kind === "square" ? iconSize : iconSize * 0.6
|
|
2161
|
+
}
|
|
2107
2162
|
})),
|
|
2108
2163
|
/* @__PURE__ */ jsx16(ChartLegend.Label, {
|
|
2109
2164
|
style: labelStyle,
|
|
@@ -2132,7 +2187,7 @@ function ComposedChartLegend({
|
|
|
2132
2187
|
...props
|
|
2133
2188
|
}) {
|
|
2134
2189
|
return /* @__PURE__ */ jsx16(Legend, {
|
|
2135
|
-
iconSize:
|
|
2190
|
+
iconSize: 8,
|
|
2136
2191
|
inactiveColor: "var(--atbl-muted)",
|
|
2137
2192
|
content: /* @__PURE__ */ jsx16(LegendContent, {
|
|
2138
2193
|
maxVisibleItems
|
|
@@ -2164,9 +2219,17 @@ function ComposedChartRoot({
|
|
|
2164
2219
|
className,
|
|
2165
2220
|
...props
|
|
2166
2221
|
}) {
|
|
2222
|
+
const [pointerFocus, setPointerFocus] = useState5(false);
|
|
2167
2223
|
return /* @__PURE__ */ jsx17("section", {
|
|
2168
2224
|
className: ["altertable-composed-chart", className].filter(Boolean).join(" "),
|
|
2169
2225
|
"aria-label": ariaLabel,
|
|
2226
|
+
"data-atbl-internal-pointer-focus": pointerFocus || undefined,
|
|
2227
|
+
onPointerDownCapture: () => setPointerFocus(true),
|
|
2228
|
+
onKeyDownCapture: () => setPointerFocus(false),
|
|
2229
|
+
onBlurCapture: (event) => {
|
|
2230
|
+
if (!event.currentTarget.contains(event.relatedTarget))
|
|
2231
|
+
setPointerFocus(false);
|
|
2232
|
+
},
|
|
2170
2233
|
children: /* @__PURE__ */ jsx17(RechartsComposedChart, {
|
|
2171
2234
|
"data-atbl-focus": "inset",
|
|
2172
2235
|
...props,
|
|
@@ -2199,6 +2262,806 @@ var ComposedChart = Object.assign(ComposedChartRoot, {
|
|
|
2199
2262
|
LabelList,
|
|
2200
2263
|
ErrorBar
|
|
2201
2264
|
});
|
|
2265
|
+
// src/react/ui/FunnelChart.tsx
|
|
2266
|
+
import { Fragment as Fragment8, useEffect, useRef as useRef3, useState as useState6 } from "react";
|
|
2267
|
+
|
|
2268
|
+
// src/react/ui/useSvgId.ts
|
|
2269
|
+
import { useId as useId2 } from "react";
|
|
2270
|
+
function useSvgId() {
|
|
2271
|
+
const id = useId2();
|
|
2272
|
+
return `svg-${Array.from(id, (character) => /[a-zA-Z0-9-]/.test(character) ? character : `_${character.codePointAt(0).toString(16)}_`).join("")}`;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
// src/react/ui/FunnelChart.tsx
|
|
2276
|
+
import { jsx as jsx18, jsxs as jsxs13, Fragment as Fragment9 } from "react/jsx-runtime";
|
|
2277
|
+
function FunnelChart({
|
|
2278
|
+
steps,
|
|
2279
|
+
series,
|
|
2280
|
+
unit,
|
|
2281
|
+
ariaLabel,
|
|
2282
|
+
className,
|
|
2283
|
+
formatValue = formatNumber2
|
|
2284
|
+
}) {
|
|
2285
|
+
const chartId = useSvgId();
|
|
2286
|
+
const [highlighted, setHighlighted] = useState6(null);
|
|
2287
|
+
const clearTimer = useRef3(null);
|
|
2288
|
+
function cancelClear() {
|
|
2289
|
+
if (clearTimer.current !== null)
|
|
2290
|
+
clearTimeout(clearTimer.current);
|
|
2291
|
+
clearTimer.current = null;
|
|
2292
|
+
}
|
|
2293
|
+
useEffect(() => cancelClear, []);
|
|
2294
|
+
function highlight(id, conversion, step) {
|
|
2295
|
+
cancelClear();
|
|
2296
|
+
setHighlighted({ id, conversion, step });
|
|
2297
|
+
}
|
|
2298
|
+
function clearHighlight() {
|
|
2299
|
+
cancelClear();
|
|
2300
|
+
setHighlighted(null);
|
|
2301
|
+
}
|
|
2302
|
+
function scheduleClear() {
|
|
2303
|
+
cancelClear();
|
|
2304
|
+
clearTimer.current = setTimeout(() => setHighlighted(null), 100);
|
|
2305
|
+
}
|
|
2306
|
+
for (const [kind, items] of [
|
|
2307
|
+
["funnel step", steps],
|
|
2308
|
+
["funnel series", series]
|
|
2309
|
+
]) {
|
|
2310
|
+
const ids = new Set;
|
|
2311
|
+
for (const item of items) {
|
|
2312
|
+
invariant(item.id.trim().length > 0 && !ids.has(item.id), `${kind} chart IDs must be nonblank and unique.`);
|
|
2313
|
+
ids.add(item.id);
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2316
|
+
for (const item of series) {
|
|
2317
|
+
invariant(item.values.length === steps.length, "funnel chart counts must align with steps.");
|
|
2318
|
+
item.values.forEach((value, index) => {
|
|
2319
|
+
invariant(Number.isFinite(value) && value >= 0, "funnel chart counts must be finite and nonnegative.");
|
|
2320
|
+
invariant(index === 0 || value <= item.values[index - 1], "funnel chart counts must be nonincreasing.");
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
2323
|
+
function reading(item, index) {
|
|
2324
|
+
const first = item.values[0] ?? 0;
|
|
2325
|
+
const current = item.values[index];
|
|
2326
|
+
const previous = item.values[Math.max(0, index - 1)];
|
|
2327
|
+
return {
|
|
2328
|
+
current,
|
|
2329
|
+
previous,
|
|
2330
|
+
dropped: previous - current,
|
|
2331
|
+
conversion: first > 0 ? current / first : null,
|
|
2332
|
+
dropOff: first > 0 ? (previous - current) / first : null
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
const data = steps.map((step, index) => ({
|
|
2336
|
+
label: step.label,
|
|
2337
|
+
index,
|
|
2338
|
+
...Object.fromEntries(series.flatMap((item, seriesIndex) => {
|
|
2339
|
+
const value = reading(item, index);
|
|
2340
|
+
return [
|
|
2341
|
+
[`conversion${seriesIndex}`, (value.conversion ?? 0) * 100],
|
|
2342
|
+
[
|
|
2343
|
+
`drop${seriesIndex}`,
|
|
2344
|
+
(value.dropOff ?? (index === 0 ? 1 : 0)) * 100
|
|
2345
|
+
]
|
|
2346
|
+
];
|
|
2347
|
+
}))
|
|
2348
|
+
}));
|
|
2349
|
+
return /* @__PURE__ */ jsx18("section", {
|
|
2350
|
+
className: classNames("altertable-funnel-chart", className),
|
|
2351
|
+
"aria-label": ariaLabel,
|
|
2352
|
+
onKeyDownCapture: clearHighlight,
|
|
2353
|
+
children: steps.length === 0 || series.length === 0 ? /* @__PURE__ */ jsx18("span", {
|
|
2354
|
+
children: "No data"
|
|
2355
|
+
}) : /* @__PURE__ */ jsxs13(Fragment9, {
|
|
2356
|
+
children: [
|
|
2357
|
+
/* @__PURE__ */ jsx18("div", {
|
|
2358
|
+
className: "altertable-funnel-summary",
|
|
2359
|
+
children: series.map((item, index) => /* @__PURE__ */ jsxs13("div", {
|
|
2360
|
+
children: [
|
|
2361
|
+
/* @__PURE__ */ jsxs13("span", {
|
|
2362
|
+
children: [
|
|
2363
|
+
/* @__PURE__ */ jsx18(ChartLegend.Marker, {
|
|
2364
|
+
kind: "square",
|
|
2365
|
+
color: `var(--atbl-chart-${index % 8 + 1})`
|
|
2366
|
+
}),
|
|
2367
|
+
item.label
|
|
2368
|
+
]
|
|
2369
|
+
}),
|
|
2370
|
+
/* @__PURE__ */ jsx18("strong", {
|
|
2371
|
+
children: formatPercent2(reading(item, steps.length - 1).conversion)
|
|
2372
|
+
})
|
|
2373
|
+
]
|
|
2374
|
+
}, item.id))
|
|
2375
|
+
}),
|
|
2376
|
+
/* @__PURE__ */ jsxs13("p", {
|
|
2377
|
+
className: "altertable-analytics-note",
|
|
2378
|
+
children: [
|
|
2379
|
+
steps[0].label,
|
|
2380
|
+
" → ",
|
|
2381
|
+
steps.at(-1).label
|
|
2382
|
+
]
|
|
2383
|
+
}),
|
|
2384
|
+
/* @__PURE__ */ jsx18("div", {
|
|
2385
|
+
className: "altertable-funnel-scroll",
|
|
2386
|
+
children: /* @__PURE__ */ jsx18("div", {
|
|
2387
|
+
style: {
|
|
2388
|
+
minWidth: steps.length * Math.max(96, series.length * 40)
|
|
2389
|
+
},
|
|
2390
|
+
children: /* @__PURE__ */ jsxs13(ComposedChart, {
|
|
2391
|
+
data,
|
|
2392
|
+
ariaLabel: `${ariaLabel} plot`,
|
|
2393
|
+
height: 300,
|
|
2394
|
+
barCategoryGap: "4%",
|
|
2395
|
+
barGap: 1,
|
|
2396
|
+
onMouseLeave: clearHighlight,
|
|
2397
|
+
children: [
|
|
2398
|
+
/* @__PURE__ */ jsx18(ComposedChart.CartesianGrid, {}),
|
|
2399
|
+
/* @__PURE__ */ jsx18(ComposedChart.XAxis, {
|
|
2400
|
+
dataKey: "label",
|
|
2401
|
+
interval: 0,
|
|
2402
|
+
height: 48
|
|
2403
|
+
}),
|
|
2404
|
+
/* @__PURE__ */ jsx18(ComposedChart.YAxis, {
|
|
2405
|
+
domain: [0, 100],
|
|
2406
|
+
width: 42,
|
|
2407
|
+
tickFormatter: (value) => `${value}%`
|
|
2408
|
+
}),
|
|
2409
|
+
/* @__PURE__ */ jsx18("defs", {
|
|
2410
|
+
children: series.map((item, index) => /* @__PURE__ */ jsxs13(Fragment8, {
|
|
2411
|
+
children: [
|
|
2412
|
+
/* @__PURE__ */ jsxs13("linearGradient", {
|
|
2413
|
+
id: `${chartId}-gradient-${index}`,
|
|
2414
|
+
x1: "0",
|
|
2415
|
+
x2: "0",
|
|
2416
|
+
y1: "1",
|
|
2417
|
+
y2: "0",
|
|
2418
|
+
children: [
|
|
2419
|
+
/* @__PURE__ */ jsx18("stop", {
|
|
2420
|
+
offset: "0%",
|
|
2421
|
+
stopColor: `var(--atbl-chart-${index % 8 + 1})`,
|
|
2422
|
+
stopOpacity: 0.5
|
|
2423
|
+
}),
|
|
2424
|
+
/* @__PURE__ */ jsx18("stop", {
|
|
2425
|
+
offset: "100%",
|
|
2426
|
+
stopColor: `var(--atbl-chart-${index % 8 + 1})`,
|
|
2427
|
+
stopOpacity: 0.7
|
|
2428
|
+
})
|
|
2429
|
+
]
|
|
2430
|
+
}),
|
|
2431
|
+
/* @__PURE__ */ jsxs13("pattern", {
|
|
2432
|
+
id: `${chartId}-hatch-${index}`,
|
|
2433
|
+
patternUnits: "userSpaceOnUse",
|
|
2434
|
+
width: "6",
|
|
2435
|
+
height: "6",
|
|
2436
|
+
patternTransform: "rotate(45)",
|
|
2437
|
+
children: [
|
|
2438
|
+
/* @__PURE__ */ jsx18("rect", {
|
|
2439
|
+
width: "8",
|
|
2440
|
+
height: "8",
|
|
2441
|
+
fill: `var(--atbl-chart-${index % 8 + 1})`,
|
|
2442
|
+
fillOpacity: 0.16
|
|
2443
|
+
}),
|
|
2444
|
+
/* @__PURE__ */ jsx18("line", {
|
|
2445
|
+
x1: "0",
|
|
2446
|
+
y1: "0",
|
|
2447
|
+
x2: "0",
|
|
2448
|
+
y2: "8",
|
|
2449
|
+
stroke: `var(--atbl-chart-${index % 8 + 1})`,
|
|
2450
|
+
strokeOpacity: 0.4,
|
|
2451
|
+
strokeWidth: "2"
|
|
2452
|
+
})
|
|
2453
|
+
]
|
|
2454
|
+
})
|
|
2455
|
+
]
|
|
2456
|
+
}, item.id))
|
|
2457
|
+
}),
|
|
2458
|
+
series.map((item, index) => /* @__PURE__ */ jsxs13(Fragment8, {
|
|
2459
|
+
children: [
|
|
2460
|
+
/* @__PURE__ */ jsx18(ComposedChart.Bar, {
|
|
2461
|
+
dataKey: `conversion${index}`,
|
|
2462
|
+
name: item.label,
|
|
2463
|
+
stackId: item.id,
|
|
2464
|
+
fill: `url(#${chartId}-gradient-${index})`,
|
|
2465
|
+
radius: 0,
|
|
2466
|
+
maxBarSize: undefined,
|
|
2467
|
+
activeBar: false,
|
|
2468
|
+
minPointSize: 0,
|
|
2469
|
+
opacity: !highlighted || highlighted.id === item.id ? 1 : 0.2,
|
|
2470
|
+
onMouseEnter: (bar) => highlight(item.id, true, bar.payload.index),
|
|
2471
|
+
onMouseLeave: scheduleClear,
|
|
2472
|
+
onClick: (bar) => highlight(item.id, true, bar.payload.index)
|
|
2473
|
+
}),
|
|
2474
|
+
/* @__PURE__ */ jsx18(ComposedChart.Bar, {
|
|
2475
|
+
dataKey: `drop${index}`,
|
|
2476
|
+
name: `${item.label} drop-off`,
|
|
2477
|
+
stackId: item.id,
|
|
2478
|
+
fill: `url(#${chartId}-hatch-${index})`,
|
|
2479
|
+
opacity: !highlighted || highlighted.id === item.id ? 1 : 0.2,
|
|
2480
|
+
onMouseEnter: (bar) => highlight(item.id, false, bar.payload.index),
|
|
2481
|
+
onMouseLeave: scheduleClear,
|
|
2482
|
+
onClick: (bar) => highlight(item.id, false, bar.payload.index),
|
|
2483
|
+
radius: 0,
|
|
2484
|
+
maxBarSize: undefined,
|
|
2485
|
+
activeBar: false
|
|
2486
|
+
})
|
|
2487
|
+
]
|
|
2488
|
+
}, item.id)),
|
|
2489
|
+
/* @__PURE__ */ jsx18(ComposedChart.Tooltip, {
|
|
2490
|
+
content: ({ active, payload }) => {
|
|
2491
|
+
const index = payload?.[0]?.payload.index;
|
|
2492
|
+
if (!active || typeof index !== "number")
|
|
2493
|
+
return null;
|
|
2494
|
+
const tooltipItems = series.filter((item) => !highlighted || highlighted.step !== index || item.id === highlighted.id).flatMap((item) => {
|
|
2495
|
+
const value = reading(item, index);
|
|
2496
|
+
const selected = highlighted?.step === index;
|
|
2497
|
+
const readings = selected ? [highlighted.conversion] : [true, false];
|
|
2498
|
+
const seriesIndex = series.indexOf(item);
|
|
2499
|
+
return readings.map((conversion) => ({
|
|
2500
|
+
name: item.label,
|
|
2501
|
+
graphicalItemId: `${item.id}-${conversion ? "conversion" : "drop-off"}`,
|
|
2502
|
+
dataKey: `${item.id}-${conversion ? "conversion" : "drop-off"}`,
|
|
2503
|
+
color: `var(--atbl-chart-${seriesIndex % 8 + 1})`,
|
|
2504
|
+
value: (conversion ? value.conversion : value.dropOff) ?? 0,
|
|
2505
|
+
formatter: () => /* @__PURE__ */ jsxs13(Fragment9, {
|
|
2506
|
+
children: [
|
|
2507
|
+
formatPercent2(conversion ? value.conversion : value.dropOff),
|
|
2508
|
+
" ",
|
|
2509
|
+
conversion ? "conversion" : "drop-off",
|
|
2510
|
+
/* @__PURE__ */ jsxs13("span", {
|
|
2511
|
+
className: "altertable-funnel-tooltip-detail",
|
|
2512
|
+
children: [
|
|
2513
|
+
formatValue(conversion ? value.current : value.dropped),
|
|
2514
|
+
" ",
|
|
2515
|
+
"of ",
|
|
2516
|
+
formatValue(value.previous),
|
|
2517
|
+
" ",
|
|
2518
|
+
unit
|
|
2519
|
+
]
|
|
2520
|
+
})
|
|
2521
|
+
]
|
|
2522
|
+
})
|
|
2523
|
+
}));
|
|
2524
|
+
});
|
|
2525
|
+
return /* @__PURE__ */ jsx18(ChartTooltipContent, {
|
|
2526
|
+
active: true,
|
|
2527
|
+
label: steps[index].label,
|
|
2528
|
+
payload: tooltipItems
|
|
2529
|
+
});
|
|
2530
|
+
}
|
|
2531
|
+
})
|
|
2532
|
+
]
|
|
2533
|
+
})
|
|
2534
|
+
})
|
|
2535
|
+
}),
|
|
2536
|
+
/* @__PURE__ */ jsx18("ul", {
|
|
2537
|
+
className: "altertable-sr-only",
|
|
2538
|
+
children: steps.map((step, index) => /* @__PURE__ */ jsxs13("li", {
|
|
2539
|
+
children: [
|
|
2540
|
+
step.label,
|
|
2541
|
+
series.map((item) => {
|
|
2542
|
+
const value = reading(item, index);
|
|
2543
|
+
return /* @__PURE__ */ jsxs13("span", {
|
|
2544
|
+
children: [
|
|
2545
|
+
" ",
|
|
2546
|
+
"· ",
|
|
2547
|
+
item.label,
|
|
2548
|
+
": ",
|
|
2549
|
+
formatValue(value.current),
|
|
2550
|
+
" ",
|
|
2551
|
+
unit,
|
|
2552
|
+
",",
|
|
2553
|
+
" ",
|
|
2554
|
+
formatPercent2(value.conversion),
|
|
2555
|
+
" conversion,",
|
|
2556
|
+
" ",
|
|
2557
|
+
formatValue(value.dropped),
|
|
2558
|
+
" dropped off"
|
|
2559
|
+
]
|
|
2560
|
+
}, item.id);
|
|
2561
|
+
})
|
|
2562
|
+
]
|
|
2563
|
+
}, step.id))
|
|
2564
|
+
})
|
|
2565
|
+
]
|
|
2566
|
+
})
|
|
2567
|
+
});
|
|
2568
|
+
}
|
|
2569
|
+
// src/react/ui/RetentionChart.tsx
|
|
2570
|
+
import { Fragment as Fragment10 } from "react";
|
|
2571
|
+
import { jsx as jsx19, jsxs as jsxs14, Fragment as Fragment11 } from "react/jsx-runtime";
|
|
2572
|
+
function RetentionChart({
|
|
2573
|
+
series,
|
|
2574
|
+
unit,
|
|
2575
|
+
ariaLabel,
|
|
2576
|
+
className,
|
|
2577
|
+
formatValue = formatNumber2
|
|
2578
|
+
}) {
|
|
2579
|
+
const ids = new Set;
|
|
2580
|
+
const offsets = new Map;
|
|
2581
|
+
for (const item of series) {
|
|
2582
|
+
invariant(item.id.trim().length > 0 && !ids.has(item.id), "retention series chart IDs must be nonblank and unique.");
|
|
2583
|
+
ids.add(item.id);
|
|
2584
|
+
invariant(Number.isFinite(item.cohortSize) && item.cohortSize >= 0, "retention chart counts must be finite and nonnegative.");
|
|
2585
|
+
const seen = new Set;
|
|
2586
|
+
for (const point of item.points) {
|
|
2587
|
+
invariant(Number.isSafeInteger(point.offset) && point.offset >= 0 && !seen.has(point.offset), "retention chart offsets must be unique nonnegative integers within each series.");
|
|
2588
|
+
seen.add(point.offset);
|
|
2589
|
+
invariant(point.rate === null || Number.isFinite(point.rate) && point.rate >= 0 && point.rate <= 1, "retention chart rates must be finite fractions between 0 and 1, or null.");
|
|
2590
|
+
invariant(point.rate === null === (point.retainedCount === null), "retention chart unobserved rates and counts must both be null.");
|
|
2591
|
+
if (point.retainedCount !== null) {
|
|
2592
|
+
invariant(Number.isFinite(point.retainedCount) && point.retainedCount >= 0, "retention chart counts must be finite and nonnegative.");
|
|
2593
|
+
invariant(point.retainedCount <= item.cohortSize, "retention chart retained counts cannot exceed cohort size.");
|
|
2594
|
+
}
|
|
2595
|
+
invariant(!offsets.has(point.offset) || offsets.get(point.offset) === point.label, "retention chart labels must agree for the same offset.");
|
|
2596
|
+
offsets.set(point.offset, point.label);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
const data = [...offsets].sort(([a], [b]) => a - b).map(([offset, label]) => ({
|
|
2600
|
+
offset,
|
|
2601
|
+
label,
|
|
2602
|
+
...Object.fromEntries(series.flatMap((_, index) => [
|
|
2603
|
+
[`rate${index}`, null],
|
|
2604
|
+
[`tail${index}`, null]
|
|
2605
|
+
]))
|
|
2606
|
+
}));
|
|
2607
|
+
series.forEach((item, index) => {
|
|
2608
|
+
const points = [...item.points].sort((a, b) => a.offset - b.offset);
|
|
2609
|
+
points.forEach((point, pointIndex) => {
|
|
2610
|
+
const row = data.find((row) => row.offset === point.offset);
|
|
2611
|
+
row[point.incomplete ? `tail${index}` : `rate${index}`] = point.rate === null ? null : point.rate * 100;
|
|
2612
|
+
if (point.incomplete && pointIndex > 0) {
|
|
2613
|
+
const previous = points[pointIndex - 1];
|
|
2614
|
+
data.find((row) => row.offset === previous.offset)[`tail${index}`] = previous.rate === null ? null : previous.rate * 100;
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
});
|
|
2618
|
+
return /* @__PURE__ */ jsx19("section", {
|
|
2619
|
+
className: classNames("altertable-retention-chart", className),
|
|
2620
|
+
"aria-label": ariaLabel,
|
|
2621
|
+
children: data.length === 0 ? /* @__PURE__ */ jsx19("span", {
|
|
2622
|
+
children: "No data"
|
|
2623
|
+
}) : /* @__PURE__ */ jsxs14(Fragment11, {
|
|
2624
|
+
children: [
|
|
2625
|
+
/* @__PURE__ */ jsxs14(ComposedChart, {
|
|
2626
|
+
data,
|
|
2627
|
+
ariaLabel: `${ariaLabel} plot`,
|
|
2628
|
+
children: [
|
|
2629
|
+
/* @__PURE__ */ jsx19(ComposedChart.CartesianGrid, {}),
|
|
2630
|
+
/* @__PURE__ */ jsx19(ComposedChart.XAxis, {
|
|
2631
|
+
dataKey: "offset",
|
|
2632
|
+
type: "number",
|
|
2633
|
+
domain: [0, Math.max(1, ...offsets.keys())],
|
|
2634
|
+
tickFormatter: (value) => offsets.get(Number(value)) ?? (data[0]?.label.replace(/\d+(?:\.\d+)?/, String(value)) || String(value))
|
|
2635
|
+
}),
|
|
2636
|
+
/* @__PURE__ */ jsx19(ComposedChart.YAxis, {
|
|
2637
|
+
domain: [0, 100],
|
|
2638
|
+
width: 42,
|
|
2639
|
+
tickFormatter: (value) => `${value}%`
|
|
2640
|
+
}),
|
|
2641
|
+
series.map((item, index) => /* @__PURE__ */ jsxs14(Fragment10, {
|
|
2642
|
+
children: [
|
|
2643
|
+
/* @__PURE__ */ jsx19(ComposedChart.Line, {
|
|
2644
|
+
dataKey: `rate${index}`,
|
|
2645
|
+
name: item.label,
|
|
2646
|
+
stroke: `var(--atbl-chart-${index % 8 + 1})`,
|
|
2647
|
+
connectNulls: false
|
|
2648
|
+
}),
|
|
2649
|
+
/* @__PURE__ */ jsx19(ComposedChart.Line, {
|
|
2650
|
+
dataKey: `tail${index}`,
|
|
2651
|
+
name: item.label,
|
|
2652
|
+
stroke: `var(--atbl-chart-${index % 8 + 1})`,
|
|
2653
|
+
strokeDasharray: "3 3",
|
|
2654
|
+
dot: false,
|
|
2655
|
+
activeDot: false,
|
|
2656
|
+
connectNulls: false
|
|
2657
|
+
})
|
|
2658
|
+
]
|
|
2659
|
+
}, item.id)),
|
|
2660
|
+
/* @__PURE__ */ jsx19(ComposedChart.Tooltip, {
|
|
2661
|
+
filterNull: false,
|
|
2662
|
+
content: ({ active, payload }) => {
|
|
2663
|
+
const offset = payload?.[0]?.payload.offset;
|
|
2664
|
+
if (!active || typeof offset !== "number")
|
|
2665
|
+
return null;
|
|
2666
|
+
return /* @__PURE__ */ jsxs14(TooltipSurface, {
|
|
2667
|
+
"data-variant": "chart",
|
|
2668
|
+
role: "tooltip",
|
|
2669
|
+
children: [
|
|
2670
|
+
/* @__PURE__ */ jsx19("strong", {
|
|
2671
|
+
children: offsets.get(offset)
|
|
2672
|
+
}),
|
|
2673
|
+
series.map((item) => {
|
|
2674
|
+
const point = item.points.find((point) => point.offset === offset);
|
|
2675
|
+
if (!point)
|
|
2676
|
+
return null;
|
|
2677
|
+
return /* @__PURE__ */ jsxs14("span", {
|
|
2678
|
+
children: [
|
|
2679
|
+
item.label,
|
|
2680
|
+
":",
|
|
2681
|
+
" ",
|
|
2682
|
+
point.rate === null ? "Not observed" : `${formatPercent2(point.rate)} · ${formatValue(point.retainedCount)} / ${formatValue(item.cohortSize)} ${unit}${point.incomplete ? " · Incomplete period" : ""}`
|
|
2683
|
+
]
|
|
2684
|
+
}, item.id);
|
|
2685
|
+
})
|
|
2686
|
+
]
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
})
|
|
2690
|
+
]
|
|
2691
|
+
}),
|
|
2692
|
+
/* @__PURE__ */ jsx19(ChartLegend, {
|
|
2693
|
+
"aria-label": `${ariaLabel} cohorts`,
|
|
2694
|
+
children: series.map((item, index) => /* @__PURE__ */ jsxs14(ChartLegend.Item, {
|
|
2695
|
+
children: [
|
|
2696
|
+
/* @__PURE__ */ jsx19(ChartLegend.Marker, {
|
|
2697
|
+
kind: "square",
|
|
2698
|
+
color: `var(--atbl-chart-${index % 8 + 1})`
|
|
2699
|
+
}),
|
|
2700
|
+
/* @__PURE__ */ jsx19(ChartLegend.Label, {
|
|
2701
|
+
children: item.label
|
|
2702
|
+
})
|
|
2703
|
+
]
|
|
2704
|
+
}, item.id))
|
|
2705
|
+
}),
|
|
2706
|
+
/* @__PURE__ */ jsx19("ul", {
|
|
2707
|
+
className: "altertable-sr-only",
|
|
2708
|
+
children: series.map((item) => /* @__PURE__ */ jsxs14("li", {
|
|
2709
|
+
children: [
|
|
2710
|
+
item.label,
|
|
2711
|
+
item.points.map((point) => /* @__PURE__ */ jsxs14("span", {
|
|
2712
|
+
children: [
|
|
2713
|
+
" ",
|
|
2714
|
+
"· ",
|
|
2715
|
+
point.label,
|
|
2716
|
+
":",
|
|
2717
|
+
" ",
|
|
2718
|
+
point.rate === null ? "Not observed" : `${formatPercent2(point.rate)} · ${formatValue(point.retainedCount)} / ${formatValue(item.cohortSize)} ${unit}${point.incomplete ? " · Incomplete period" : ""}`
|
|
2719
|
+
]
|
|
2720
|
+
}, point.offset))
|
|
2721
|
+
]
|
|
2722
|
+
}, item.id))
|
|
2723
|
+
})
|
|
2724
|
+
]
|
|
2725
|
+
})
|
|
2726
|
+
});
|
|
2727
|
+
}
|
|
2728
|
+
// src/react/ui/JourneyChart.tsx
|
|
2729
|
+
import { useLayoutEffect as useLayoutEffect3, useRef as useRef4, useState as useState7 } from "react";
|
|
2730
|
+
|
|
2731
|
+
// src/react/ui/journey-flow.ts
|
|
2732
|
+
var journeyOutcomeLabels = {
|
|
2733
|
+
truncated: "More steps",
|
|
2734
|
+
"drop-off": "Drop-off",
|
|
2735
|
+
converted: "Converted",
|
|
2736
|
+
end: "End of path"
|
|
2737
|
+
};
|
|
2738
|
+
function journeyNodes(path) {
|
|
2739
|
+
const nodes = path.steps.map((step, depth) => ({
|
|
2740
|
+
key: JSON.stringify([depth, "event", step.event, step.property]),
|
|
2741
|
+
label: step.property === null ? step.event : `${step.event} · ${step.property}`,
|
|
2742
|
+
depth,
|
|
2743
|
+
outcome: "event"
|
|
2744
|
+
}));
|
|
2745
|
+
const outcome = path.truncated ? "truncated" : path.converted === false ? "drop-off" : path.converted === true ? "converted" : "end";
|
|
2746
|
+
nodes.push({
|
|
2747
|
+
key: JSON.stringify([nodes.length, outcome]),
|
|
2748
|
+
label: journeyOutcomeLabels[outcome],
|
|
2749
|
+
depth: nodes.length,
|
|
2750
|
+
outcome
|
|
2751
|
+
});
|
|
2752
|
+
return nodes;
|
|
2753
|
+
}
|
|
2754
|
+
function buildJourneyFlow(paths, expandedBranch) {
|
|
2755
|
+
const nodes = new Map;
|
|
2756
|
+
const links = new Map;
|
|
2757
|
+
for (const path of paths) {
|
|
2758
|
+
const fullSequence = journeyNodes(path);
|
|
2759
|
+
if (path.truncated)
|
|
2760
|
+
fullSequence.pop();
|
|
2761
|
+
const sequence = expandedBranch === undefined ? fullSequence : fullSequence.flatMap((node, index) => {
|
|
2762
|
+
const branchKeys = fullSequence.slice(0, index + 1).map((step) => step.key);
|
|
2763
|
+
const parentExpanded = index <= 1 || branchKeys.slice(0, index).every((key, depth) => expandedBranch[depth] === key);
|
|
2764
|
+
if (!parentExpanded)
|
|
2765
|
+
return [];
|
|
2766
|
+
return [
|
|
2767
|
+
{
|
|
2768
|
+
...node,
|
|
2769
|
+
key: JSON.stringify(branchKeys),
|
|
2770
|
+
branchKeys,
|
|
2771
|
+
expandable: index >= 1 && index < fullSequence.length - 1,
|
|
2772
|
+
expanded: branchKeys.every((key, depth) => expandedBranch[depth] === key)
|
|
2773
|
+
}
|
|
2774
|
+
];
|
|
2775
|
+
});
|
|
2776
|
+
sequence.forEach((node, index) => {
|
|
2777
|
+
const current = nodes.get(node.key) ?? {
|
|
2778
|
+
...node,
|
|
2779
|
+
count: 0,
|
|
2780
|
+
pathKeys: []
|
|
2781
|
+
};
|
|
2782
|
+
current.count += path.count;
|
|
2783
|
+
if ("expandable" in node && node.expandable)
|
|
2784
|
+
current.expandable = true;
|
|
2785
|
+
current.pathKeys.push(path.id);
|
|
2786
|
+
nodes.set(node.key, current);
|
|
2787
|
+
const previous = sequence[index - 1];
|
|
2788
|
+
if (!previous)
|
|
2789
|
+
return;
|
|
2790
|
+
const key = JSON.stringify([previous.key, node.key]);
|
|
2791
|
+
const link = links.get(key) ?? {
|
|
2792
|
+
key,
|
|
2793
|
+
source: previous.key,
|
|
2794
|
+
target: node.key,
|
|
2795
|
+
count: 0,
|
|
2796
|
+
pathKeys: []
|
|
2797
|
+
};
|
|
2798
|
+
link.count += path.count;
|
|
2799
|
+
link.pathKeys.push(path.id);
|
|
2800
|
+
links.set(key, link);
|
|
2801
|
+
});
|
|
2802
|
+
}
|
|
2803
|
+
return { nodes: [...nodes.values()], links: [...links.values()] };
|
|
2804
|
+
}
|
|
2805
|
+
function limitJourneyFlow(graph, visibleEventsByDepth = {}) {
|
|
2806
|
+
const nodes = [];
|
|
2807
|
+
const replacements = new Map;
|
|
2808
|
+
for (const depth of new Set(graph.nodes.map((node) => node.depth))) {
|
|
2809
|
+
const column = graph.nodes.filter((node) => node.depth === depth).sort((a, b) => Number(Boolean(b.expanded)) - Number(Boolean(a.expanded)) || b.count - a.count || a.key.localeCompare(b.key));
|
|
2810
|
+
const events = column.filter((node) => node.outcome === "event");
|
|
2811
|
+
const outcomes = column.filter((node) => node.outcome !== "event");
|
|
2812
|
+
const limit = visibleEventsByDepth[depth] ?? 3;
|
|
2813
|
+
if (events.length <= limit) {
|
|
2814
|
+
nodes.push(...column);
|
|
2815
|
+
continue;
|
|
2816
|
+
}
|
|
2817
|
+
nodes.push(...events.slice(0, limit), ...outcomes);
|
|
2818
|
+
const hidden = events.slice(limit);
|
|
2819
|
+
const key = JSON.stringify(["other", depth]);
|
|
2820
|
+
hidden.forEach((node) => replacements.set(node.key, key));
|
|
2821
|
+
nodes.push({
|
|
2822
|
+
key,
|
|
2823
|
+
label: "More events",
|
|
2824
|
+
depth,
|
|
2825
|
+
outcome: "other",
|
|
2826
|
+
hiddenBranchCount: hidden.length,
|
|
2827
|
+
count: hidden.reduce((sum, node) => sum + node.count, 0),
|
|
2828
|
+
pathKeys: [...new Set(hidden.flatMap((node) => node.pathKeys))]
|
|
2829
|
+
});
|
|
2830
|
+
}
|
|
2831
|
+
const links = new Map;
|
|
2832
|
+
for (const link of graph.links) {
|
|
2833
|
+
const source = replacements.get(link.source) ?? link.source;
|
|
2834
|
+
const target = replacements.get(link.target) ?? link.target;
|
|
2835
|
+
const key = JSON.stringify([source, target]);
|
|
2836
|
+
const existing = links.get(key);
|
|
2837
|
+
if (existing) {
|
|
2838
|
+
existing.count += link.count;
|
|
2839
|
+
existing.pathKeys.push(...link.pathKeys);
|
|
2840
|
+
} else {
|
|
2841
|
+
links.set(key, {
|
|
2842
|
+
...link,
|
|
2843
|
+
key,
|
|
2844
|
+
source,
|
|
2845
|
+
target,
|
|
2846
|
+
pathKeys: [...link.pathKeys]
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
2850
|
+
return { nodes, links: [...links.values()] };
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
// src/react/ui/JourneyChart.tsx
|
|
2854
|
+
import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2855
|
+
function JourneyChart({
|
|
2856
|
+
paths,
|
|
2857
|
+
unit,
|
|
2858
|
+
ariaLabel,
|
|
2859
|
+
className,
|
|
2860
|
+
formatValue = formatNumber2
|
|
2861
|
+
}) {
|
|
2862
|
+
const ids = new Set;
|
|
2863
|
+
let total = 0;
|
|
2864
|
+
for (const path of paths) {
|
|
2865
|
+
invariant(path.id.trim().length > 0 && !ids.has(path.id), "journey path chart IDs must be nonblank and unique.");
|
|
2866
|
+
ids.add(path.id);
|
|
2867
|
+
invariant(Number.isFinite(path.count) && path.count >= 0, "journey chart counts must be finite and nonnegative.");
|
|
2868
|
+
total += path.count;
|
|
2869
|
+
invariant(path.steps.length > 0 && path.steps.every((step) => step.event.trim().length > 0), "journey chart paths require nonblank events.");
|
|
2870
|
+
}
|
|
2871
|
+
invariant(Number.isFinite(total), "journey chart total count must be finite.");
|
|
2872
|
+
return /* @__PURE__ */ jsx20("section", {
|
|
2873
|
+
className: classNames("altertable-journey-chart", className),
|
|
2874
|
+
"aria-label": ariaLabel,
|
|
2875
|
+
children: paths.length === 0 ? /* @__PURE__ */ jsx20("span", {
|
|
2876
|
+
children: "No data"
|
|
2877
|
+
}) : /* @__PURE__ */ jsx20(JourneyFlow, {
|
|
2878
|
+
paths,
|
|
2879
|
+
unit,
|
|
2880
|
+
formatValue
|
|
2881
|
+
}, JSON.stringify(paths))
|
|
2882
|
+
});
|
|
2883
|
+
}
|
|
2884
|
+
function JourneyFlow({
|
|
2885
|
+
paths,
|
|
2886
|
+
unit,
|
|
2887
|
+
formatValue
|
|
2888
|
+
}) {
|
|
2889
|
+
const [expandedBranch, setExpandedBranch] = useState7([]);
|
|
2890
|
+
const [visibleEventsByDepth, setVisibleEventsByDepth] = useState7({});
|
|
2891
|
+
const graph = limitJourneyFlow(buildJourneyFlow(paths, expandedBranch), visibleEventsByDepth);
|
|
2892
|
+
const scrollRef = useRef4(null);
|
|
2893
|
+
const pendingDepth = useRef4(null);
|
|
2894
|
+
const depthCount = Math.max(0, ...graph.nodes.map((node) => node.depth)) + 1;
|
|
2895
|
+
const startingCount = graph.nodes.filter((node) => node.depth === 0).reduce((sum, node) => sum + node.count, 0);
|
|
2896
|
+
function percent(count) {
|
|
2897
|
+
return formatPercent2(startingCount > 0 ? count / startingCount : null);
|
|
2898
|
+
}
|
|
2899
|
+
const nodeWidth = 176;
|
|
2900
|
+
const nodeHeight = 96;
|
|
2901
|
+
const barWidth = 16;
|
|
2902
|
+
const rowSpacing = nodeHeight + 80;
|
|
2903
|
+
const columnSpacing = nodeWidth + 112;
|
|
2904
|
+
const width = 34 + nodeWidth + (depthCount - 1) * columnSpacing;
|
|
2905
|
+
function columnX(depth) {
|
|
2906
|
+
return 10 + depth * columnSpacing;
|
|
2907
|
+
}
|
|
2908
|
+
useLayoutEffect3(() => {
|
|
2909
|
+
const depth = pendingDepth.current;
|
|
2910
|
+
const container = scrollRef.current;
|
|
2911
|
+
if (depth === null || !container || depth >= depthCount)
|
|
2912
|
+
return;
|
|
2913
|
+
pendingDepth.current = null;
|
|
2914
|
+
const left = columnX(depth);
|
|
2915
|
+
const right = left + nodeWidth + 24;
|
|
2916
|
+
const nextLeft = left < container.scrollLeft ? left - 24 : right > container.scrollLeft + container.clientWidth ? right - container.clientWidth : container.scrollLeft;
|
|
2917
|
+
if (nextLeft !== container.scrollLeft)
|
|
2918
|
+
container.scrollTo({
|
|
2919
|
+
left: Math.max(0, nextLeft),
|
|
2920
|
+
behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "instant" : "smooth"
|
|
2921
|
+
});
|
|
2922
|
+
});
|
|
2923
|
+
const columns = Array.from({ length: depthCount }, (_, depth) => graph.nodes.filter((node) => node.depth === depth).sort((a, b) => {
|
|
2924
|
+
function rank(node) {
|
|
2925
|
+
return node.outcome === "event" ? 0 : node.outcome === "other" ? 1 : 2;
|
|
2926
|
+
}
|
|
2927
|
+
return rank(a) - rank(b) || b.count - a.count || a.key.localeCompare(b.key);
|
|
2928
|
+
}));
|
|
2929
|
+
const positions = new Map;
|
|
2930
|
+
columns.forEach((column, depth) => {
|
|
2931
|
+
const parent = columns[depth - 1]?.find((node) => node.expanded);
|
|
2932
|
+
const parentY = parent ? positions.get(parent.key)?.y : undefined;
|
|
2933
|
+
const top = Math.max(104, (parentY ?? 104) - (column.length > 1 ? rowSpacing : 0));
|
|
2934
|
+
column.forEach((node, index) => positions.set(node.key, {
|
|
2935
|
+
node,
|
|
2936
|
+
x: columnX(depth),
|
|
2937
|
+
y: top + index * rowSpacing
|
|
2938
|
+
}));
|
|
2939
|
+
});
|
|
2940
|
+
const height = Math.max(280, ...[...positions.values()].map((position) => position.y + nodeHeight + 40));
|
|
2941
|
+
const outgoingOffsets = new Map;
|
|
2942
|
+
const incomingOffsets = new Map;
|
|
2943
|
+
const orderedLinks = [...graph.links].sort((a, b) => (positions.get(a.source)?.y ?? 0) - (positions.get(b.source)?.y ?? 0) || (positions.get(a.target)?.y ?? 0) - (positions.get(b.target)?.y ?? 0));
|
|
2944
|
+
const links = orderedLinks.flatMap((link) => {
|
|
2945
|
+
const source = positions.get(link.source);
|
|
2946
|
+
const target = positions.get(link.target);
|
|
2947
|
+
if (!source || !target || link.count <= 0 || startingCount <= 0)
|
|
2948
|
+
return [];
|
|
2949
|
+
const x0 = source.x + barWidth;
|
|
2950
|
+
const x1 = target.x;
|
|
2951
|
+
const mid = (x0 + x1) / 2;
|
|
2952
|
+
const sourceTop = source.y + (outgoingOffsets.get(link.source) ?? 0);
|
|
2953
|
+
const targetTop = target.y + (incomingOffsets.get(link.target) ?? 0);
|
|
2954
|
+
const thickness = link.count / startingCount * nodeHeight;
|
|
2955
|
+
outgoingOffsets.set(link.source, (outgoingOffsets.get(link.source) ?? 0) + thickness);
|
|
2956
|
+
incomingOffsets.set(link.target, (incomingOffsets.get(link.target) ?? 0) + thickness);
|
|
2957
|
+
return [
|
|
2958
|
+
{
|
|
2959
|
+
muted: target.node.outcome === "end" || target.node.outcome === "other",
|
|
2960
|
+
d: `M${x0},${sourceTop} C${mid},${sourceTop} ${mid},${targetTop} ${x1},${targetTop} L${x1},${targetTop + thickness} C${mid},${targetTop + thickness} ${mid},${sourceTop + thickness} ${x0},${sourceTop + thickness} Z`
|
|
2961
|
+
}
|
|
2962
|
+
];
|
|
2963
|
+
});
|
|
2964
|
+
return /* @__PURE__ */ jsx20("div", {
|
|
2965
|
+
ref: scrollRef,
|
|
2966
|
+
className: "altertable-journey-scroll",
|
|
2967
|
+
children: /* @__PURE__ */ jsxs15("div", {
|
|
2968
|
+
className: "altertable-journey-plot",
|
|
2969
|
+
style: { minWidth: width, minHeight: height },
|
|
2970
|
+
children: [
|
|
2971
|
+
/* @__PURE__ */ jsxs15("svg", {
|
|
2972
|
+
width: "100%",
|
|
2973
|
+
height: "100%",
|
|
2974
|
+
"aria-hidden": "true",
|
|
2975
|
+
focusable: "false",
|
|
2976
|
+
children: [
|
|
2977
|
+
/* @__PURE__ */ jsx20("path", {
|
|
2978
|
+
d: links.filter((link) => !link.muted).map((link) => link.d).join(" "),
|
|
2979
|
+
fill: "color-mix(in srgb, var(--atbl-chart-5) 22%, var(--atbl-surface))"
|
|
2980
|
+
}),
|
|
2981
|
+
/* @__PURE__ */ jsx20("path", {
|
|
2982
|
+
d: links.filter((link) => link.muted).map((link) => link.d).join(" "),
|
|
2983
|
+
fill: "color-mix(in srgb, var(--atbl-muted) 22%, var(--atbl-surface))"
|
|
2984
|
+
})
|
|
2985
|
+
]
|
|
2986
|
+
}),
|
|
2987
|
+
columns.map((column, depth) => /* @__PURE__ */ jsx20("span", {
|
|
2988
|
+
className: "altertable-journey-stage",
|
|
2989
|
+
style: { left: columnX(depth) },
|
|
2990
|
+
children: column.length > 0 && column.every((node) => node.outcome !== "event" && node.outcome !== "other") ? "Outcome" : `Step ${depth + 1}`
|
|
2991
|
+
}, depth)),
|
|
2992
|
+
[...positions.values()].map(({ node, x, y }) => /* @__PURE__ */ jsxs15("div", {
|
|
2993
|
+
className: "altertable-journey-node",
|
|
2994
|
+
style: {
|
|
2995
|
+
left: x,
|
|
2996
|
+
top: y,
|
|
2997
|
+
width: nodeWidth,
|
|
2998
|
+
height: startingCount > 0 ? node.count / startingCount * nodeHeight : 0
|
|
2999
|
+
},
|
|
3000
|
+
children: [
|
|
3001
|
+
/* @__PURE__ */ jsx20("div", {
|
|
3002
|
+
className: "altertable-journey-bar",
|
|
3003
|
+
"data-event": node.outcome === "event" || undefined,
|
|
3004
|
+
"aria-hidden": "true",
|
|
3005
|
+
style: { width: barWidth }
|
|
3006
|
+
}),
|
|
3007
|
+
/* @__PURE__ */ jsxs15("section", {
|
|
3008
|
+
"aria-label": `${node.label}, step ${node.depth + 1}, ${formatValue(node.count)} ${unit}`,
|
|
3009
|
+
className: "altertable-journey-label",
|
|
3010
|
+
children: [
|
|
3011
|
+
/* @__PURE__ */ jsx20("span", {
|
|
3012
|
+
className: "altertable-journey-name",
|
|
3013
|
+
title: node.label,
|
|
3014
|
+
children: node.label
|
|
3015
|
+
}),
|
|
3016
|
+
/* @__PURE__ */ jsxs15("span", {
|
|
3017
|
+
children: [
|
|
3018
|
+
formatValue(node.count),
|
|
3019
|
+
" ",
|
|
3020
|
+
unit,
|
|
3021
|
+
" · ",
|
|
3022
|
+
percent(node.count)
|
|
3023
|
+
]
|
|
3024
|
+
}),
|
|
3025
|
+
node.hiddenBranchCount ? /* @__PURE__ */ jsxs15("span", {
|
|
3026
|
+
children: [
|
|
3027
|
+
node.hiddenBranchCount,
|
|
3028
|
+
" hidden",
|
|
3029
|
+
" ",
|
|
3030
|
+
node.hiddenBranchCount === 1 ? "event" : "events"
|
|
3031
|
+
]
|
|
3032
|
+
}) : null
|
|
3033
|
+
]
|
|
3034
|
+
}),
|
|
3035
|
+
node.expandable && /* @__PURE__ */ jsx20(Button2, {
|
|
3036
|
+
variant: "outline",
|
|
3037
|
+
size: "icon-compact",
|
|
3038
|
+
className: "altertable-journey-expand",
|
|
3039
|
+
"aria-label": `${node.expanded ? "Less" : "More"} after ${node.label}, step ${node.depth + 1}`,
|
|
3040
|
+
"aria-expanded": Boolean(node.expanded),
|
|
3041
|
+
onClick: () => {
|
|
3042
|
+
pendingDepth.current = node.expanded ? null : node.depth + 1;
|
|
3043
|
+
setVisibleEventsByDepth((limits) => Object.fromEntries(Object.entries(limits).filter(([depth]) => Number(depth) <= node.depth)));
|
|
3044
|
+
setExpandedBranch(node.expanded ? (node.branchKeys ?? []).slice(0, -1) : node.branchKeys ?? []);
|
|
3045
|
+
},
|
|
3046
|
+
children: node.expanded ? "−" : "›"
|
|
3047
|
+
}),
|
|
3048
|
+
node.outcome === "other" && /* @__PURE__ */ jsx20(Button2, {
|
|
3049
|
+
size: "compact",
|
|
3050
|
+
variant: "ghost",
|
|
3051
|
+
className: "altertable-journey-more",
|
|
3052
|
+
"aria-label": `Show more events at step ${node.depth + 1}`,
|
|
3053
|
+
onClick: () => setVisibleEventsByDepth((limits) => ({
|
|
3054
|
+
...limits,
|
|
3055
|
+
[node.depth]: (limits[node.depth] ?? 3) + 3
|
|
3056
|
+
})),
|
|
3057
|
+
children: "Show more events"
|
|
3058
|
+
})
|
|
3059
|
+
]
|
|
3060
|
+
}, node.key))
|
|
3061
|
+
]
|
|
3062
|
+
})
|
|
3063
|
+
});
|
|
3064
|
+
}
|
|
2202
3065
|
export {
|
|
2203
3066
|
AboutData2 as AboutData,
|
|
2204
3067
|
AppIcon2 as AppIcon,
|
|
@@ -2222,11 +3085,13 @@ export {
|
|
|
2222
3085
|
DimensionPicker2 as DimensionPicker,
|
|
2223
3086
|
FilterActions2 as FilterActions,
|
|
2224
3087
|
FilterBar2 as FilterBar,
|
|
3088
|
+
FunnelChart,
|
|
2225
3089
|
GettingStarted,
|
|
2226
3090
|
GlossaryDefinition,
|
|
2227
3091
|
GlossaryExplanation,
|
|
2228
3092
|
GradientScroll2 as GradientScroll,
|
|
2229
3093
|
HelpPopover2 as HelpPopover,
|
|
3094
|
+
JourneyChart,
|
|
2230
3095
|
LineChart,
|
|
2231
3096
|
LiveControl2 as LiveControl,
|
|
2232
3097
|
Menu2 as Menu,
|
|
@@ -2244,6 +3109,7 @@ export {
|
|
|
2244
3109
|
PresentStory2 as PresentStory,
|
|
2245
3110
|
Radio,
|
|
2246
3111
|
RadioGroup,
|
|
3112
|
+
RetentionChart,
|
|
2247
3113
|
ScatterChart,
|
|
2248
3114
|
SearchField2 as SearchField,
|
|
2249
3115
|
SearchMatch2 as SearchMatch,
|
|
@@ -2266,7 +3132,8 @@ export {
|
|
|
2266
3132
|
isPlainKeyEvent2 as isPlainKeyEvent,
|
|
2267
3133
|
searchItems2 as searchItems,
|
|
2268
3134
|
useAppVariables2 as useAppVariables,
|
|
3135
|
+
useSvgId,
|
|
2269
3136
|
useViewTab2 as useViewTab
|
|
2270
3137
|
};
|
|
2271
3138
|
|
|
2272
|
-
//# debugId=
|
|
3139
|
+
//# debugId=6CFD84691873BB8C64756E2164756E21
|