@pixerate/editor 0.164.0 → 0.166.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/agent/index.cjs +54 -39
- package/dist/agent/index.js +24 -9
- package/dist/animation/index.cjs +6 -5
- package/dist/animation/index.js +5 -4
- package/dist/animation-editor/index.cjs +65 -64
- package/dist/animation-editor/index.js +8 -7
- package/dist/bridge/cli.cjs +3 -3
- package/dist/bridge/cli.js +2 -2
- package/dist/bridge/index.cjs +3 -3
- package/dist/bridge/index.js +2 -2
- package/dist/canvas/index.cjs +89 -87
- package/dist/canvas/index.js +11 -9
- package/dist/chart/controller.d.cts +60 -2
- package/dist/chart/controller.d.ts +60 -2
- package/dist/chart/index.cjs +657 -163
- package/dist/chart/index.d.cts +3 -0
- package/dist/chart/index.d.ts +3 -0
- package/dist/chart/index.js +512 -18
- package/dist/chart/layout-families.d.cts +14 -0
- package/dist/chart/layout-families.d.ts +14 -0
- package/dist/chart/layout-shared.d.cts +5 -0
- package/dist/chart/layout-shared.d.ts +5 -0
- package/dist/chart/links.d.cts +34 -0
- package/dist/chart/links.d.ts +34 -0
- package/dist/chart/mermaid.d.cts +9 -6
- package/dist/chart/mermaid.d.ts +9 -6
- package/dist/chart/race.d.cts +26 -0
- package/dist/chart/race.d.ts +26 -0
- package/dist/chart/schemas.d.cts +5 -0
- package/dist/chart/schemas.d.ts +5 -0
- package/dist/chart/spec.d.cts +3 -2
- package/dist/chart/spec.d.ts +3 -2
- package/dist/chart/style.d.cts +33 -0
- package/dist/chart/style.d.ts +33 -0
- package/dist/chart/types.d.cts +28 -2
- package/dist/chart/types.d.ts +28 -2
- package/dist/{chunk-ZVNAMTFO.cjs → chunk-3FSUJIXN.cjs} +18 -174
- package/dist/{chunk-RQCMSEM5.cjs → chunk-4QUUHTGJ.cjs} +2 -2
- package/dist/{chunk-CIPIESX4.cjs → chunk-4ZC5QJE2.cjs} +3 -3
- package/dist/{chunk-S6X2QRVU.cjs → chunk-5UEFBM2M.cjs} +1 -138
- package/dist/{chunk-5FNGLV2D.js → chunk-5UNQDC3Q.js} +18 -3
- package/dist/{chunk-YSAGBIRW.cjs → chunk-6FYIFGBW.cjs} +16 -16
- package/dist/{chunk-QYZGBVNC.js → chunk-7272D3K2.js} +10 -8
- package/dist/{chunk-462WGGXX.cjs → chunk-AMX23CSN.cjs} +27 -16
- package/dist/{chunk-3VLOOO3G.js → chunk-BDIM6NVW.js} +1 -1
- package/dist/{chunk-R3P2M7HU.cjs → chunk-BPX4Z3N3.cjs} +355 -207
- package/dist/{chunk-D54JHQK2.cjs → chunk-CHLV7UFP.cjs} +51 -51
- package/dist/{chunk-OXERWBKE.js → chunk-DFCAR7AW.js} +3 -3
- package/dist/{chunk-GZ2FM3ZB.js → chunk-DKNEQX7T.js} +6 -162
- package/dist/{chunk-JCXMJUQO.cjs → chunk-EGZM52EO.cjs} +3 -3
- package/dist/{chunk-ES3UEVUT.cjs → chunk-EUIDTZNQ.cjs} +112 -110
- package/dist/{chunk-H3OEM3Q2.js → chunk-F6R67I3A.js} +1 -1
- package/dist/{chunk-3NEZJCBY.js → chunk-FR432ROA.js} +349 -201
- package/dist/chunk-HIDFL5LQ.js +484 -0
- package/dist/{chunk-22KLZZGH.cjs → chunk-HXOBOHH2.cjs} +8 -8
- package/dist/{chunk-MKTDELCD.js → chunk-I5MRJW7B.js} +1 -1
- package/dist/{chunk-SDHZG5TF.js → chunk-ILP77UD6.js} +160 -2
- package/dist/{chunk-Z4P2RBMJ.js → chunk-ITANXMZD.js} +177 -3
- package/dist/chunk-KGRZMHZB.js +204 -0
- package/dist/{chunk-SET6KJOP.js → chunk-LPIKXPBM.js} +1 -1
- package/dist/{chunk-BQWGN5WH.js → chunk-M2TPMYJE.js} +1 -1
- package/dist/{chunk-GTELFJRE.cjs → chunk-M5GLQQWG.cjs} +2 -2
- package/dist/{chunk-4VU4CP7R.js → chunk-MHLU6GRJ.js} +1 -138
- package/dist/{chunk-NZD2MZJC.cjs → chunk-MZFZSVUJ.cjs} +38 -23
- package/dist/{chunk-DAPQGA55.js → chunk-NQQDGGXY.js} +2 -2
- package/dist/chunk-O6OGNDND.js +164 -0
- package/dist/{chunk-PA3IMPPS.cjs → chunk-OVYQAYGH.cjs} +81 -81
- package/dist/{chunk-YXOW456B.js → chunk-PPEDZ5WR.js} +1 -1
- package/dist/{chunk-24M7VD6M.cjs → chunk-S4EIAIEW.cjs} +7 -7
- package/dist/{chunk-M4MIZPQI.cjs → chunk-SJ2CPTQC.cjs} +189 -15
- package/dist/{chunk-YGG7WDWT.cjs → chunk-SZWVMB3B.cjs} +24 -18
- package/dist/chunk-TFF2GTGN.cjs +153 -0
- package/dist/chunk-TTCJPX4U.cjs +204 -0
- package/dist/{chunk-SBQVEBSU.cjs → chunk-UUUTEVX6.cjs} +242 -68
- package/dist/{chunk-WI7QJA7A.cjs → chunk-UXETV2NC.cjs} +161 -3
- package/dist/{chunk-GKNEJ6EN.cjs → chunk-V5L2G22R.cjs} +321 -107
- package/dist/{chunk-TALQONZ5.js → chunk-VB2CY6EG.js} +3 -3
- package/dist/{chunk-QY4KUDMY.js → chunk-VRXXAXUT.js} +2 -2
- package/dist/chunk-VU6WQSMB.cjs +164 -0
- package/dist/{chunk-NHLYK3AX.js → chunk-XBQX76CT.js} +182 -8
- package/dist/{chunk-5AARIS3R.cjs → chunk-XCDGM7TD.cjs} +37 -37
- package/dist/{chunk-POR54B55.js → chunk-XNU45TYA.js} +7 -1
- package/dist/{chunk-EVXQNQND.js → chunk-XZB3TJ6K.js} +16 -5
- package/dist/chunk-YBAPT7NL.js +153 -0
- package/dist/{chunk-SZA42R4N.js → chunk-YUFTVNLX.js} +230 -16
- package/dist/chunk-Z2UQYMPS.cjs +484 -0
- package/dist/color/index.cjs +5 -3
- package/dist/color/index.js +12 -10
- package/dist/diagram/index.cjs +55 -243
- package/dist/diagram/index.js +12 -200
- package/dist/export/index.cjs +7 -6
- package/dist/export/index.js +6 -5
- package/dist/extensions/index.cjs +12 -9
- package/dist/extensions/index.js +11 -8
- package/dist/gradient/index.cjs +29 -27
- package/dist/gradient/index.js +6 -4
- package/dist/image-editor/index.cjs +644 -643
- package/dist/image-editor/index.js +7 -6
- package/dist/index.cjs +21 -18
- package/dist/index.js +14 -11
- package/dist/paint-editor/index.cjs +19 -18
- package/dist/paint-editor/index.js +7 -6
- package/dist/parameters/index.cjs +2 -2
- package/dist/parameters/index.js +1 -1
- package/dist/persistence/assetWrites.d.cts +22 -0
- package/dist/persistence/assetWrites.d.ts +22 -0
- package/dist/persistence/assets.d.cts +71 -0
- package/dist/persistence/assets.d.ts +71 -0
- package/dist/persistence/documentStore.d.cts +17 -1
- package/dist/persistence/documentStore.d.ts +17 -1
- package/dist/persistence/documentTools.d.cts +5 -1
- package/dist/persistence/documentTools.d.ts +5 -1
- package/dist/persistence/editorAutosave.d.cts +7 -0
- package/dist/persistence/editorAutosave.d.ts +7 -0
- package/dist/persistence/index.cjs +141 -21
- package/dist/persistence/index.js +130 -10
- package/dist/persistence/storage.d.cts +13 -1
- package/dist/persistence/storage.d.ts +13 -1
- package/dist/spreadsheet/index.cjs +3 -3
- package/dist/spreadsheet/index.js +2 -2
- package/dist/tokens/index.cjs +11 -3
- package/dist/tokens/index.d.cts +1 -0
- package/dist/tokens/index.d.ts +1 -0
- package/dist/tokens/index.js +10 -2
- package/dist/tokens/style.d.cts +36 -0
- package/dist/tokens/style.d.ts +36 -0
- package/dist/tools/index.cjs +15 -3
- package/dist/tools/index.d.cts +2 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +14 -2
- package/dist/tools/style-tools.d.cts +19 -0
- package/dist/tools/style-tools.d.ts +19 -0
- package/dist/tools/style.d.cts +212 -0
- package/dist/tools/style.d.ts +212 -0
- package/dist/tools/view.d.cts +6 -0
- package/dist/tools/view.d.ts +6 -0
- package/dist/typography/index.cjs +8 -6
- package/dist/typography/index.js +5 -3
- package/dist/vector/index.cjs +953 -952
- package/dist/vector/index.js +7 -6
- package/dist/video-editor/index.cjs +8 -7
- package/dist/video-editor/index.js +6 -5
- package/package.json +1 -1
- package/skills/pixerate-chart/SKILL.md +20 -2
- package/dist/chunk-ERAGQL2N.cjs +0 -68
- package/dist/chunk-KMU6QJB7.js +0 -68
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineEditorDocument
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-XBQX76CT.js";
|
|
4
4
|
import {
|
|
5
5
|
hexToRgba,
|
|
6
6
|
normalizeTokenColor
|
|
7
7
|
} from "./chunk-2WPNAGHI.js";
|
|
8
|
+
import {
|
|
9
|
+
contrastRatio,
|
|
10
|
+
parseColor
|
|
11
|
+
} from "./chunk-MHLU6GRJ.js";
|
|
8
12
|
|
|
9
13
|
// src/tokens/types.ts
|
|
10
14
|
var TOKEN_TYPES = ["color", "number", "string", "boolean"];
|
|
@@ -1307,6 +1311,157 @@ function createTokensEditorDocument(store) {
|
|
|
1307
1311
|
});
|
|
1308
1312
|
}
|
|
1309
1313
|
|
|
1314
|
+
// src/tokens/style.ts
|
|
1315
|
+
function findVariable(state, ref) {
|
|
1316
|
+
return getTokenVariable(state, ref) ?? findTokenVariable(state, ref);
|
|
1317
|
+
}
|
|
1318
|
+
function createTokenStyleSource(store) {
|
|
1319
|
+
return {
|
|
1320
|
+
resolve({ token, mode }) {
|
|
1321
|
+
const state = store.getState();
|
|
1322
|
+
const variable = findVariable(state, token);
|
|
1323
|
+
if (!variable) return { ok: false, message: `there is no design token "${token}".` };
|
|
1324
|
+
let modes;
|
|
1325
|
+
if (mode !== void 0) {
|
|
1326
|
+
const collection = getTokenCollection(state, variable.collectionId);
|
|
1327
|
+
const found = findTokenMode(collection, mode);
|
|
1328
|
+
if (!found) return { ok: false, message: `"${collection.name}" has no mode "${mode}"; its modes are ${collection.modes.map((m) => `"${m.name}"`).join(", ")}.` };
|
|
1329
|
+
modes = { [collection.id]: found.id };
|
|
1330
|
+
}
|
|
1331
|
+
const resolved = resolveToken(state, variable.id, modes);
|
|
1332
|
+
return resolved.ok ? { ok: true, value: resolved.value } : { ok: false, message: `"${token}" does not resolve: ${resolved.message}` };
|
|
1333
|
+
},
|
|
1334
|
+
subscribe: (listener) => store.subscribe(() => listener())
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
var profileOf = (profile) => profile;
|
|
1338
|
+
var requiredVariables = {
|
|
1339
|
+
id: "required-variables",
|
|
1340
|
+
description: `Every variable named in the profile's "required" list exists.`,
|
|
1341
|
+
run(state, profile) {
|
|
1342
|
+
const required = profileOf(profile).required;
|
|
1343
|
+
if (!required?.length) throw new Error('the profile has no "required" list.');
|
|
1344
|
+
return required.filter((name) => !findVariable(state, name)).map((name) => ({ severity: "error", message: `The token set has no variable "${name}"; add it (house style requires it).`, target: { variable: name } }));
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
var namePattern = {
|
|
1348
|
+
id: "name-pattern",
|
|
1349
|
+
description: `Every variable name matches the profile's "namePattern" regular expression.`,
|
|
1350
|
+
run(state, profile) {
|
|
1351
|
+
const pattern = profileOf(profile).namePattern;
|
|
1352
|
+
if (!pattern) throw new Error('the profile has no "namePattern".');
|
|
1353
|
+
let re;
|
|
1354
|
+
try {
|
|
1355
|
+
re = new RegExp(pattern);
|
|
1356
|
+
} catch {
|
|
1357
|
+
throw new Error(`"namePattern" ${pattern} is not a valid regular expression.`);
|
|
1358
|
+
}
|
|
1359
|
+
return state.variables.filter((v) => !re.test(v.name)).map((v) => ({ severity: "warning", message: `Variable "${v.name}" does not match the naming pattern ${pattern}; rename it.`, target: { variableId: v.id } }));
|
|
1360
|
+
}
|
|
1361
|
+
};
|
|
1362
|
+
var requiredModes = {
|
|
1363
|
+
id: "required-modes",
|
|
1364
|
+
description: `Every collection has the modes named in the profile's "modes" list (e.g. Light and Dark).`,
|
|
1365
|
+
run(state, profile) {
|
|
1366
|
+
const modes = profileOf(profile).modes;
|
|
1367
|
+
if (!modes?.length) throw new Error('the profile has no "modes" list.');
|
|
1368
|
+
return state.collections.flatMap(
|
|
1369
|
+
(c) => modes.filter((m) => !findTokenMode(c, m)).map((m) => ({ severity: "error", message: `Collection "${c.name}" has no "${m}" mode; add it.`, target: { collectionId: c.id } }))
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
var contrastPairs = {
|
|
1374
|
+
id: "contrast-pairs",
|
|
1375
|
+
description: `Each foreground / background pair in "contrastPairs" reaches its contrast ratio (default 4.5) in every mode of the foreground's collection.`,
|
|
1376
|
+
run(state, profile) {
|
|
1377
|
+
const pairs = profileOf(profile).contrastPairs;
|
|
1378
|
+
if (!pairs?.length) throw new Error('the profile has no "contrastPairs".');
|
|
1379
|
+
const findings = [];
|
|
1380
|
+
for (const { fg, bg, ratio = 4.5 } of pairs) {
|
|
1381
|
+
const f = findVariable(state, fg);
|
|
1382
|
+
const b = findVariable(state, bg);
|
|
1383
|
+
if (!f || !b) {
|
|
1384
|
+
findings.push({ severity: "error", message: `Contrast pair ${fg} on ${bg} names a variable the token set lacks (${!f ? fg : bg}).`, target: { variable: !f ? fg : bg } });
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
const collection = getTokenCollection(state, f.collectionId);
|
|
1388
|
+
for (const mode of collection.modes) {
|
|
1389
|
+
const modes = { [collection.id]: mode.id, [b.collectionId]: findTokenMode(getTokenCollection(state, b.collectionId), mode.name)?.id ?? mode.id };
|
|
1390
|
+
const fv = resolveToken(state, f.id, modes);
|
|
1391
|
+
const bv = resolveToken(state, b.id, modes);
|
|
1392
|
+
const fc = fv.ok && typeof fv.value === "string" ? parseColor(fv.value) : null;
|
|
1393
|
+
const bc = bv.ok && typeof bv.value === "string" ? parseColor(bv.value) : null;
|
|
1394
|
+
if (!fc || !bc) continue;
|
|
1395
|
+
const r = contrastRatio(fc, bc);
|
|
1396
|
+
if (r < ratio) {
|
|
1397
|
+
findings.push({
|
|
1398
|
+
severity: "error",
|
|
1399
|
+
message: `${f.name} on ${b.name} has contrast ${Math.round(r * 100) / 100}:1 in ${mode.name}; want at least ${ratio}:1, so darken or lighten one of them in that mode.`,
|
|
1400
|
+
target: { variableId: f.id, modeId: mode.id }
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
return findings;
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
var tokensStyleDomain = {
|
|
1409
|
+
id: "tokens",
|
|
1410
|
+
title: "Design tokens",
|
|
1411
|
+
description: "Rules for the token set itself: variables that must exist, a naming pattern, required modes and colour pairs that must reach a contrast ratio.",
|
|
1412
|
+
profileSchema: {
|
|
1413
|
+
type: "object",
|
|
1414
|
+
properties: {
|
|
1415
|
+
required: { type: "array", items: { type: "string", minLength: 1 }, description: 'Variable names that must exist, e.g. "brand/primary".' },
|
|
1416
|
+
namePattern: { type: "string", minLength: 1, description: 'Regular expression every variable name must match, e.g. "^[a-z0-9-]+(/[a-z0-9-]+)*$".' },
|
|
1417
|
+
modes: { type: "array", items: { type: "string", minLength: 1 }, description: 'Mode names every collection must have, e.g. ["Light", "Dark"].' },
|
|
1418
|
+
contrastPairs: {
|
|
1419
|
+
type: "array",
|
|
1420
|
+
items: {
|
|
1421
|
+
type: "object",
|
|
1422
|
+
properties: {
|
|
1423
|
+
fg: { type: "string", minLength: 1, description: "Foreground variable name or id." },
|
|
1424
|
+
bg: { type: "string", minLength: 1, description: "Background variable name or id." },
|
|
1425
|
+
ratio: { type: "number", minimum: 1, maximum: 21, description: "Smallest contrast ratio (default 4.5, WCAG AA for text)." }
|
|
1426
|
+
},
|
|
1427
|
+
required: ["fg", "bg"],
|
|
1428
|
+
additionalProperties: false
|
|
1429
|
+
},
|
|
1430
|
+
description: "Colour pairs checked in every mode."
|
|
1431
|
+
}
|
|
1432
|
+
},
|
|
1433
|
+
additionalProperties: false
|
|
1434
|
+
},
|
|
1435
|
+
checks: [requiredVariables, namePattern, requiredModes, contrastPairs]
|
|
1436
|
+
};
|
|
1437
|
+
function createTokensStyleToolProvider(store, styles, options = {}) {
|
|
1438
|
+
if (!styles.domains.some((d) => d.id === "tokens")) throw new Error('The style resolver has no "tokens" domain; add tokensStyleDomain to its domains.');
|
|
1439
|
+
const check = {
|
|
1440
|
+
name: "check_tokens_style",
|
|
1441
|
+
title: "Check token house style",
|
|
1442
|
+
description: 'Checks the design-token set against the house style (see get_style with domain "tokens"): runs the checks its profile turns on (required-variables, name-pattern, required-modes, contrast-pairs). Returns findings with a severity, a sentence saying how to fix it, a target (variable id or name, mode id, collection id) and the layer that asked for it; plus the checks that ran and the ones skipped with the reason. Fix findings with get_tokens_document / load_tokens_document, then check again.',
|
|
1443
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
1444
|
+
annotations: { title: "Check token house style", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1445
|
+
handler: async () => {
|
|
1446
|
+
const { style, ...report } = await styles.check("tokens", store.getState());
|
|
1447
|
+
return {
|
|
1448
|
+
...report,
|
|
1449
|
+
ok: report.findings.length === 0,
|
|
1450
|
+
...style.skills.length ? {} : { note: "No house style applies to design tokens." },
|
|
1451
|
+
...style.issues.length ? { issues: style.issues } : {}
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
};
|
|
1455
|
+
return {
|
|
1456
|
+
id: options.id ?? "tokens-style",
|
|
1457
|
+
namespace: options.namespace ?? "tokens",
|
|
1458
|
+
title: "Token house style",
|
|
1459
|
+
description: "Check the design-token set against the house style.",
|
|
1460
|
+
skill: "pixerate-tokens",
|
|
1461
|
+
tools: [check]
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1310
1465
|
export {
|
|
1311
1466
|
TOKEN_TYPES,
|
|
1312
1467
|
TokenError,
|
|
@@ -1360,5 +1515,8 @@ export {
|
|
|
1360
1515
|
exportW3CTokens,
|
|
1361
1516
|
exportW3CTokensJson,
|
|
1362
1517
|
importW3CTokens,
|
|
1363
|
-
createTokensEditorDocument
|
|
1518
|
+
createTokensEditorDocument,
|
|
1519
|
+
createTokenStyleSource,
|
|
1520
|
+
tokensStyleDomain,
|
|
1521
|
+
createTokensStyleToolProvider
|
|
1364
1522
|
};
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
chartTreeTotal,
|
|
13
13
|
chartVariesColors,
|
|
14
14
|
shadeChartColor
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-FR432ROA.js";
|
|
16
16
|
import {
|
|
17
17
|
NumberFormat
|
|
18
18
|
} from "./chunk-II2WIF5Z.js";
|
|
@@ -948,7 +948,7 @@ function drawCallouts(ctx, plot) {
|
|
|
948
948
|
}
|
|
949
949
|
if (unplaced) ctx.warn("annotations-skipped", `${unplaced} callout${unplaced === 1 ? " points" : "s point"} at a gap or a hidden value and ${unplaced === 1 ? "is" : "are"} not drawn.`);
|
|
950
950
|
}
|
|
951
|
-
var MUTED = /* @__PURE__ */ new Set(["bar", "line", "area", "point", "slice", "cell", "box", "whisker", "outlier", "connector", "trendline", "trendline-label", "error-bar", "data-label", "legend-swatch", "legend-label"]);
|
|
951
|
+
var MUTED = /* @__PURE__ */ new Set(["node", "bar", "line", "area", "point", "slice", "cell", "box", "whisker", "outlier", "connector", "trendline", "trendline-label", "error-bar", "data-label", "legend-swatch", "legend-label"]);
|
|
952
952
|
function applyHighlight(ctx) {
|
|
953
953
|
const { spec, marks } = ctx;
|
|
954
954
|
if (!spec.highlight.length) return;
|
|
@@ -1677,6 +1677,38 @@ function trendlineLegendItems(spec, color) {
|
|
|
1677
1677
|
);
|
|
1678
1678
|
}
|
|
1679
1679
|
|
|
1680
|
+
// src/chart/race.ts
|
|
1681
|
+
function chartRaceValueAt(series, index) {
|
|
1682
|
+
for (let i = Math.min(index, series.values.length - 1); i >= 0; i--) {
|
|
1683
|
+
const v = series.values[i];
|
|
1684
|
+
if (v !== null && v !== void 0 && Number.isFinite(v)) return v;
|
|
1685
|
+
}
|
|
1686
|
+
return 0;
|
|
1687
|
+
}
|
|
1688
|
+
function chartRaceMoment(count, frame) {
|
|
1689
|
+
const last = Math.max(0, count - 1);
|
|
1690
|
+
const at = frame === null || frame === void 0 || !Number.isFinite(frame) ? last : Math.max(0, Math.min(last, frame));
|
|
1691
|
+
const from = Math.floor(at);
|
|
1692
|
+
const to = Math.min(last, from + 1);
|
|
1693
|
+
return { from, to, t: at - from, at };
|
|
1694
|
+
}
|
|
1695
|
+
function chartRaceRanking(spec, frame) {
|
|
1696
|
+
const visible = spec.series.filter((s) => !s.hidden);
|
|
1697
|
+
const { from, to, t } = chartRaceMoment(spec.categories.length, frame);
|
|
1698
|
+
const rankAt = (i) => {
|
|
1699
|
+
const order2 = visible.map((s, k) => ({ id: s.id, v: chartRaceValueAt(s, i), k })).sort((a, b) => b.v - a.v || a.k - b.k);
|
|
1700
|
+
return new Map(order2.map((o, r) => [o.id, r]));
|
|
1701
|
+
};
|
|
1702
|
+
const r0 = rankAt(from);
|
|
1703
|
+
const r1 = rankAt(to);
|
|
1704
|
+
const near = t < 0.5 ? r0 : r1;
|
|
1705
|
+
return visible.map((s) => {
|
|
1706
|
+
const a = chartRaceValueAt(s, from);
|
|
1707
|
+
const b = chartRaceValueAt(s, to);
|
|
1708
|
+
return { seriesId: s.id, name: s.name, value: a + (b - a) * t, position: r0.get(s.id) + (r1.get(s.id) - r0.get(s.id)) * t, rank: near.get(s.id) };
|
|
1709
|
+
}).sort((a, b) => a.position - b.position || b.value - a.value);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1680
1712
|
// src/chart/layout-families.ts
|
|
1681
1713
|
var sliceColor = (ctx, series, i) => series.pointColors?.[i] || chartSliceColor(ctx.spec, i);
|
|
1682
1714
|
function layoutPie(ctx, area, series) {
|
|
@@ -2349,11 +2381,142 @@ function layoutSunburst(ctx, area, series) {
|
|
|
2349
2381
|
}
|
|
2350
2382
|
return { plot };
|
|
2351
2383
|
}
|
|
2384
|
+
function layoutSankey(ctx, area, series) {
|
|
2385
|
+
const { spec, theme, marks, fs, measure } = ctx;
|
|
2386
|
+
const nodes = spec.sankeyNodes;
|
|
2387
|
+
const links = spec.links;
|
|
2388
|
+
const at = new Map(nodes.map((n, i) => [n, i]));
|
|
2389
|
+
const incoming = nodes.map(() => []);
|
|
2390
|
+
const outgoing = nodes.map(() => []);
|
|
2391
|
+
links.forEach((l, k) => {
|
|
2392
|
+
outgoing[at.get(l.source)].push(k);
|
|
2393
|
+
incoming[at.get(l.target)].push(k);
|
|
2394
|
+
});
|
|
2395
|
+
const column = nodes.map(() => -1);
|
|
2396
|
+
const depth = (i) => {
|
|
2397
|
+
if (column[i] >= 0) return column[i];
|
|
2398
|
+
column[i] = 0;
|
|
2399
|
+
column[i] = incoming[i].reduce((d, k) => Math.max(d, depth(at.get(links[k].source)) + 1), 0);
|
|
2400
|
+
return column[i];
|
|
2401
|
+
};
|
|
2402
|
+
nodes.forEach((_, i) => depth(i));
|
|
2403
|
+
const last = Math.max(...column);
|
|
2404
|
+
nodes.forEach((_, i) => {
|
|
2405
|
+
if (!outgoing[i].length) column[i] = last;
|
|
2406
|
+
});
|
|
2407
|
+
const value = series?.values.map((v) => v ?? 0) ?? nodes.map(() => 0);
|
|
2408
|
+
const nodeW = Math.max(6, Math.min(16, area.width * 0.02));
|
|
2409
|
+
const gap = last > 0 ? (area.width - nodeW) / last : 0;
|
|
2410
|
+
const xOf = (c) => last > 0 ? area.x + c * gap : area.x + area.width / 2 - nodeW / 2;
|
|
2411
|
+
const columns = Array.from({ length: last + 1 }, (_, c) => nodes.map((_2, i) => i).filter((i) => column[i] === c));
|
|
2412
|
+
const pad = Math.max(4, Math.min(fs * 1.2, area.height / Math.max(1, Math.max(...columns.map((c) => c.length))) / 3));
|
|
2413
|
+
const ky = Math.min(...columns.filter((c) => c.length).map((c) => (area.height - pad * (c.length - 1)) / Math.max(1e-9, c.reduce((t, i) => t + value[i], 0))));
|
|
2414
|
+
const top = nodes.map(() => 0);
|
|
2415
|
+
for (const col of columns) {
|
|
2416
|
+
const used = col.reduce((t, i) => t + value[i] * ky, 0) + pad * Math.max(0, col.length - 1);
|
|
2417
|
+
let y = area.y + (area.height - used) / 2;
|
|
2418
|
+
for (const i of col) {
|
|
2419
|
+
top[i] = y;
|
|
2420
|
+
y += value[i] * ky + pad;
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
const color = (i) => series?.pointColors?.[i] || chartSliceColor(spec, i);
|
|
2424
|
+
const named = new Set(spec.highlight);
|
|
2425
|
+
const lit = nodes.map((n) => named.has(n));
|
|
2426
|
+
const dimLinks = lit.some(Boolean);
|
|
2427
|
+
const outOffset = nodes.map(() => 0);
|
|
2428
|
+
const inOffset = nodes.map(() => 0);
|
|
2429
|
+
const startY = /* @__PURE__ */ new Map();
|
|
2430
|
+
const endY = /* @__PURE__ */ new Map();
|
|
2431
|
+
nodes.forEach((_, i) => {
|
|
2432
|
+
for (const k of [...outgoing[i]].sort((a, b) => top[at.get(links[a].target)] - top[at.get(links[b].target)])) {
|
|
2433
|
+
startY.set(k, top[i] + outOffset[i]);
|
|
2434
|
+
outOffset[i] += links[k].value * ky;
|
|
2435
|
+
}
|
|
2436
|
+
for (const k of [...incoming[i]].sort((a, b) => top[at.get(links[a].source)] - top[at.get(links[b].source)])) {
|
|
2437
|
+
endY.set(k, top[i] + inOffset[i]);
|
|
2438
|
+
inOffset[i] += links[k].value * ky;
|
|
2439
|
+
}
|
|
2440
|
+
});
|
|
2441
|
+
const selectedNode = (i) => ctx.isSelected({ kind: "category", index: i }) || ctx.isSelected({ kind: "point", seriesId: series?.id ?? "", index: i });
|
|
2442
|
+
links.forEach((l, k) => {
|
|
2443
|
+
const s = at.get(l.source);
|
|
2444
|
+
const t = at.get(l.target);
|
|
2445
|
+
const w = l.value * ky;
|
|
2446
|
+
const x0 = xOf(column[s]) + nodeW;
|
|
2447
|
+
const x1 = xOf(column[t]);
|
|
2448
|
+
const y0 = startY.get(k);
|
|
2449
|
+
const y1 = endY.get(k);
|
|
2450
|
+
const xm = (x0 + x1) / 2;
|
|
2451
|
+
const d = `M${r2(x0)},${r2(y0)}C${r2(xm)},${r2(y0)} ${r2(xm)},${r2(y1)} ${r2(x1)},${r2(y1)}L${r2(x1)},${r2(y1 + w)}C${r2(xm)},${r2(y1 + w)} ${r2(xm)},${r2(y0 + w)} ${r2(x0)},${r2(y0 + w)}Z`;
|
|
2452
|
+
const fill = l.color ?? (spec.sankeyLinkColor === "gray" ? theme.mutedText : color(spec.sankeyLinkColor === "target" ? t : s));
|
|
2453
|
+
const emphasis = selectedNode(s) || selectedNode(t) ? 0.6 : 0.35;
|
|
2454
|
+
const opacity = dimLinks && !lit[s] && !lit[t] ? emphasis * 0.25 : emphasis;
|
|
2455
|
+
marks.push({ kind: "path", key: `link-${k}`, role: "link", d, fill, opacity });
|
|
2456
|
+
});
|
|
2457
|
+
const labelsOn = series?.dataLabels ?? spec.dataLabels;
|
|
2458
|
+
const format = series?.format ?? spec.yAxis.format;
|
|
2459
|
+
let hidden = 0;
|
|
2460
|
+
nodes.forEach((name, i) => {
|
|
2461
|
+
const x = xOf(column[i]);
|
|
2462
|
+
const h = Math.max(1, value[i] * ky);
|
|
2463
|
+
const target = { kind: "category", index: i };
|
|
2464
|
+
const selected = selectedNode(i);
|
|
2465
|
+
marks.push({ kind: "rect", key: `node-${i}`, role: "node", x, y: top[i], width: nodeW, height: h, fill: color(i), radius: 1, ...selected ? { stroke: theme.selection, strokeWidth: 2 } : {}, target, selected });
|
|
2466
|
+
const text = labelsOn ? `${name} ${formatChartNumber(value[i], format)}` : name;
|
|
2467
|
+
const room = (last > 0 ? gap : area.width / 2) - nodeW - 10;
|
|
2468
|
+
const shown = truncate(text, Math.max(fs * 2, room), fs, measure);
|
|
2469
|
+
if (shown !== text) hidden++;
|
|
2470
|
+
const right = column[i] < last || last === 0;
|
|
2471
|
+
marks.push({ kind: "text", key: `label-node-${i}`, role: "data-label", x: right ? x + nodeW + 5 : x - 5, y: top[i] + h / 2, text: shown, anchor: right ? "start" : "end", baseline: "middle", size: fs, weight: selected ? 600 : void 0, fill: selected ? theme.selection : theme.text, target, selected });
|
|
2472
|
+
});
|
|
2473
|
+
if (hidden) ctx.warn("labels-truncated", `${hidden} node label${hidden === 1 ? " was" : "s were"} shortened to fit between the columns.`);
|
|
2474
|
+
return { plot: area };
|
|
2475
|
+
}
|
|
2476
|
+
function layoutBarRace(ctx, area, frame) {
|
|
2477
|
+
const { spec, theme, marks, fs, measure } = ctx;
|
|
2478
|
+
const top = spec.raceTopN;
|
|
2479
|
+
const moment = chartRaceMoment(spec.categories.length, frame);
|
|
2480
|
+
const ranking = chartRaceRanking(spec, frame);
|
|
2481
|
+
const shown = ranking.filter((e) => e.position < top);
|
|
2482
|
+
const labelW = Math.min(area.width * 0.3, Math.max(0, ...ranking.map((e) => measure(e.name, fs)))) + 8;
|
|
2483
|
+
const tickH = spec.yAxis.show ? fs * 1.6 : 4;
|
|
2484
|
+
const plot = { x: area.x + labelW, y: area.y + tickH, width: Math.max(1, area.width - labelW - fs * 4), height: Math.max(1, area.height - tickH) };
|
|
2485
|
+
const lead = Math.max(1e-9, ...shown.map((e) => e.value));
|
|
2486
|
+
const max = spec.yAxis.max !== null && spec.yAxis.max > 0 ? spec.yAxis.max : lead * 1.08;
|
|
2487
|
+
const xOf = (v) => plot.x + Math.max(0, Math.min(max, v)) / max * plot.width;
|
|
2488
|
+
const ticks = niceScale(0, max, 5).ticks.filter((t) => t <= max);
|
|
2489
|
+
const axisTarget = { kind: "axis", axis: "y" };
|
|
2490
|
+
const format = spec.yAxis.format;
|
|
2491
|
+
ticks.forEach((t, k) => {
|
|
2492
|
+
if (spec.yAxis.gridlines) marks.push({ kind: "line", key: `grid-y-${k}`, role: "grid", x1: xOf(t), y1: plot.y, x2: xOf(t), y2: plot.y + plot.height, stroke: theme.grid, strokeWidth: 1 });
|
|
2493
|
+
if (spec.yAxis.show) marks.push({ kind: "text", key: `tick-y-${k}`, role: "tick-label", x: xOf(t), y: area.y, text: formatChartNumber(t, format, format.decimals ?? stepDecimals(ticks[1] - ticks[0] || 1)), anchor: "middle", baseline: "top", size: fs - 1, fill: theme.mutedText, target: axisTarget });
|
|
2494
|
+
});
|
|
2495
|
+
const label = spec.categories[moment.t < 0.5 ? moment.from : moment.to] ?? "";
|
|
2496
|
+
const timeSize = Math.min(theme.titleSize * 3, plot.height * 0.2, plot.width * 0.5 / Math.max(1, label.length * 0.6));
|
|
2497
|
+
marks.push({ kind: "text", key: "race-time", role: "race-time", x: plot.x + plot.width, y: plot.y + plot.height - 4, text: label, anchor: "end", baseline: "bottom", size: timeSize, weight: 700, fill: theme.mutedText, opacity: 0.55, target: { kind: "category", index: moment.t < 0.5 ? moment.from : moment.to } });
|
|
2498
|
+
const band = plot.height / top;
|
|
2499
|
+
const barH = Math.max(2, band * (1 - Math.min(0.8, spec.gapWidth)));
|
|
2500
|
+
const index = Math.round(moment.at);
|
|
2501
|
+
for (const e of shown) {
|
|
2502
|
+
const si = spec.series.findIndex((s2) => s2.id === e.seriesId);
|
|
2503
|
+
const s = spec.series[si];
|
|
2504
|
+
const y = plot.y + e.position * band + (band - barH) / 2;
|
|
2505
|
+
const target = { kind: "point", seriesId: e.seriesId, index };
|
|
2506
|
+
const selected = ctx.isSelected(target) || ctx.seriesSelected(e.seriesId);
|
|
2507
|
+
const width = Math.max(0, xOf(e.value) - plot.x);
|
|
2508
|
+
marks.push({ kind: "rect", key: `bar-${e.seriesId}`, role: "bar", x: plot.x, y, width, height: barH, fill: chartSeriesColor(spec, si), radius: Math.min(3, barH / 4), target, selected, ...selected ? { stroke: theme.selection, strokeWidth: 1.5 } : {} });
|
|
2509
|
+
marks.push({ kind: "text", key: `label-name-${e.seriesId}`, role: "tick-label", x: plot.x - 6, y: y + barH / 2, text: truncate(e.name, labelW - 8, fs, measure), anchor: "end", baseline: "middle", size: fs, fill: selected ? theme.selection : theme.text, target: { kind: "series", seriesId: e.seriesId } });
|
|
2510
|
+
if (s.dataLabels ?? true) marks.push({ kind: "text", key: `label-${e.seriesId}`, role: "data-label", x: plot.x + width + 5, y: y + barH / 2, text: formatChartNumber(e.value, s.format ?? format, (s.format ?? format).decimals ?? (Math.abs(e.value) >= 100 ? 0 : void 0)), anchor: "start", baseline: "middle", size: fs - 1, fill: theme.text, target });
|
|
2511
|
+
}
|
|
2512
|
+
if (ranking.length > top) ctx.warn("values-skipped", `The race shows the top ${top} of ${ranking.length} names at each moment (raceTopN).`);
|
|
2513
|
+
return { plot, valueScale: { axis: "x", domain: [0, max], range: [plot.x, plot.x + plot.width], ticks } };
|
|
2514
|
+
}
|
|
2352
2515
|
|
|
2353
2516
|
// src/chart/layout.ts
|
|
2354
2517
|
function legendItems(spec, ctx) {
|
|
2355
2518
|
const family = chartFamily(spec);
|
|
2356
|
-
if (spec.type === "funnel" || spec.type === "slope") return [];
|
|
2519
|
+
if (spec.type === "funnel" || spec.type === "slope" || spec.type === "sankey" || spec.type === "barrace") return [];
|
|
2357
2520
|
if (chartSpecColorsByCategory(spec)) {
|
|
2358
2521
|
const first = spec.series.find((s) => !s.hidden);
|
|
2359
2522
|
const seats = (i) => Math.max(0, Math.round(first?.values[i] ?? 0));
|
|
@@ -2493,6 +2656,14 @@ function layoutChart(spec, options) {
|
|
|
2493
2656
|
return finish(layoutFunnel(ctx, area, visible[0]));
|
|
2494
2657
|
case "treemap":
|
|
2495
2658
|
return finish(spec.tree.length ? layoutTreemapTree(ctx, area, spec.series[0]) : layoutTreemap(ctx, area, visible[0]));
|
|
2659
|
+
case "barrace":
|
|
2660
|
+
return finish(layoutBarRace(ctx, area, options.raceFrame));
|
|
2661
|
+
case "sankey":
|
|
2662
|
+
if (!spec.links.length) {
|
|
2663
|
+
ctx.warn("type-fallback", "A Sankey diagram needs flows (set_chart_links); it is drawn as a bar chart until then.");
|
|
2664
|
+
return finish(layoutCartesian(ctx, area, visible));
|
|
2665
|
+
}
|
|
2666
|
+
return finish(layoutSankey(ctx, area, spec.series[0]));
|
|
2496
2667
|
case "sunburst":
|
|
2497
2668
|
return finish(layoutSunburst(ctx, area, spec.tree.length ? spec.series[0] : visible[0]));
|
|
2498
2669
|
case "slope":
|
|
@@ -2696,6 +2867,9 @@ export {
|
|
|
2696
2867
|
MAX_CHART_DATA_LABELS,
|
|
2697
2868
|
MAX_CHART_LINE_POINTS,
|
|
2698
2869
|
MAX_CHART_MARKERS,
|
|
2870
|
+
chartRaceValueAt,
|
|
2871
|
+
chartRaceMoment,
|
|
2872
|
+
chartRaceRanking,
|
|
2699
2873
|
layoutChart,
|
|
2700
2874
|
chartValueAtPoint,
|
|
2701
2875
|
chartXAtPoint,
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import {
|
|
2
|
+
splitSource
|
|
3
|
+
} from "./chunk-O6OGNDND.js";
|
|
4
|
+
|
|
5
|
+
// src/diagram/sankey/index.ts
|
|
6
|
+
function parseSankey(source) {
|
|
7
|
+
const split = splitSource(source);
|
|
8
|
+
if (!/^\s*sankey(?:-beta)?\b/i.test(split.header)) {
|
|
9
|
+
return { error: "Not a sankey diagram: the first line should be `sankey-beta`." };
|
|
10
|
+
}
|
|
11
|
+
const firstLine = split.headerIndex + 2;
|
|
12
|
+
const kept = [];
|
|
13
|
+
split.bodyLines.forEach((text, i) => {
|
|
14
|
+
if (/^\s*%%/.test(text)) return;
|
|
15
|
+
kept.push({ text: text.trim(), line: firstLine + i });
|
|
16
|
+
});
|
|
17
|
+
const records = readRecords(kept);
|
|
18
|
+
if ("error" in records) return records;
|
|
19
|
+
const nodes = [];
|
|
20
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21
|
+
const add = (name) => {
|
|
22
|
+
if (!seen.has(name)) {
|
|
23
|
+
seen.add(name);
|
|
24
|
+
nodes.push(name);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const links = [];
|
|
28
|
+
for (const { fields, line } of records) {
|
|
29
|
+
if (fields.length !== 3) {
|
|
30
|
+
return {
|
|
31
|
+
error: `Expected three fields (source,target,value), found ${fields.length}.`,
|
|
32
|
+
line
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const [source2, target, raw] = fields;
|
|
36
|
+
const value = Number.parseFloat(raw);
|
|
37
|
+
if (!Number.isFinite(value)) {
|
|
38
|
+
return { error: `"${raw}" is not a number.`, line };
|
|
39
|
+
}
|
|
40
|
+
add(source2);
|
|
41
|
+
add(target);
|
|
42
|
+
links.push({ source: source2, target, value });
|
|
43
|
+
}
|
|
44
|
+
if (!links.length) return { error: "No links: add lines like `A,B,10`." };
|
|
45
|
+
return { data: { nodes, links } };
|
|
46
|
+
}
|
|
47
|
+
function readRecords(lines) {
|
|
48
|
+
const records = [];
|
|
49
|
+
let i = 0;
|
|
50
|
+
while (i < lines.length) {
|
|
51
|
+
const start = lines[i];
|
|
52
|
+
if (start.text === "") {
|
|
53
|
+
i++;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const fields = [];
|
|
57
|
+
let text = start.text;
|
|
58
|
+
let pos = 0;
|
|
59
|
+
let field = "";
|
|
60
|
+
let quoted = false;
|
|
61
|
+
for (; ; ) {
|
|
62
|
+
if (quoted) {
|
|
63
|
+
const close = text.indexOf('"', pos);
|
|
64
|
+
if (close < 0) {
|
|
65
|
+
field += text.slice(pos);
|
|
66
|
+
i++;
|
|
67
|
+
if (i >= lines.length)
|
|
68
|
+
return { error: "A quoted field is never closed.", line: start.line };
|
|
69
|
+
field += "\n";
|
|
70
|
+
text = lines[i].text;
|
|
71
|
+
pos = 0;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
field += text.slice(pos, close);
|
|
75
|
+
if (text[close + 1] === '"') {
|
|
76
|
+
field += '"';
|
|
77
|
+
pos = close + 2;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
quoted = false;
|
|
81
|
+
pos = close + 1;
|
|
82
|
+
const rest = /^[ \t]*/.exec(text.slice(pos))[0].length;
|
|
83
|
+
pos += rest;
|
|
84
|
+
if (pos < text.length && text[pos] !== ",") {
|
|
85
|
+
return { error: "Unexpected text after a quoted field.", line: lines[i].line };
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (pos >= text.length) {
|
|
90
|
+
fields.push(field.trim());
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
const ch = text[pos];
|
|
94
|
+
if (ch === ",") {
|
|
95
|
+
fields.push(field.trim());
|
|
96
|
+
field = "";
|
|
97
|
+
pos++;
|
|
98
|
+
} else if (ch === '"' && field.trim() === "") {
|
|
99
|
+
quoted = true;
|
|
100
|
+
field = "";
|
|
101
|
+
pos++;
|
|
102
|
+
} else if (ch === '"') {
|
|
103
|
+
return { error: "A quote inside an unquoted field.", line: lines[i].line };
|
|
104
|
+
} else {
|
|
105
|
+
field += ch;
|
|
106
|
+
pos++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
records.push({ fields, line: start.line });
|
|
110
|
+
i++;
|
|
111
|
+
}
|
|
112
|
+
return records;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/diagram/treemap/index.ts
|
|
116
|
+
var NAME = String.raw`(?:"([^"]*)"|'([^']*)')`;
|
|
117
|
+
var CLASS = String.raw`(?:\s*:::\s*([A-Za-z_]\w*))?`;
|
|
118
|
+
var SECTION_RE = new RegExp(String.raw`^${NAME}${CLASS}$`);
|
|
119
|
+
var LEAF_RE = new RegExp(String.raw`^${NAME}\s*[:,]\s*([0-9_.,]+)${CLASS}$`);
|
|
120
|
+
function parseTreemap(source) {
|
|
121
|
+
const split = splitSource(source);
|
|
122
|
+
if (!/^\s*treemap(?:-beta)?\b/i.test(split.header)) {
|
|
123
|
+
return { error: "Not a treemap: the first line should be `treemap-beta`." };
|
|
124
|
+
}
|
|
125
|
+
const data = { items: [] };
|
|
126
|
+
const stack = [];
|
|
127
|
+
const firstLine = split.headerIndex + 2;
|
|
128
|
+
const lines = split.bodyLines;
|
|
129
|
+
for (let i = 0; i < lines.length; i++) {
|
|
130
|
+
const raw = lines[i];
|
|
131
|
+
const line = firstLine + i;
|
|
132
|
+
const indent = /^[ \t]*/.exec(raw)[0].length;
|
|
133
|
+
const text = stripComment(raw).trim();
|
|
134
|
+
if (!text) continue;
|
|
135
|
+
const accBlock = /^accDescr\s*\{(.*)$/.exec(text);
|
|
136
|
+
if (accBlock) {
|
|
137
|
+
let body = accBlock[1];
|
|
138
|
+
while (!body.includes("}") && i + 1 < lines.length) body += `
|
|
139
|
+
${lines[++i]}`;
|
|
140
|
+
if (!body.includes("}")) return { error: "`accDescr {` is never closed.", line };
|
|
141
|
+
data.accDescr = body.slice(0, body.indexOf("}")).trim();
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const acc = /^(accTitle|accDescr)\s*:(.*)$/.exec(text);
|
|
145
|
+
if (acc) {
|
|
146
|
+
data[acc[1]] = acc[2].trim();
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const title = /^title(?:[ \t]+(.*))?$/.exec(text);
|
|
150
|
+
if (title) {
|
|
151
|
+
data.title = (title[1] ?? "").trim() || void 0;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (/^classDef\s/.test(text)) continue;
|
|
155
|
+
let item;
|
|
156
|
+
const leaf = LEAF_RE.exec(text);
|
|
157
|
+
const section = leaf ? null : SECTION_RE.exec(text);
|
|
158
|
+
if (leaf) {
|
|
159
|
+
const value = Number.parseFloat(leaf[3].replace(/,/g, ""));
|
|
160
|
+
if (!Number.isFinite(value)) return { error: `"${leaf[3]}" is not a number.`, line };
|
|
161
|
+
item = { name: leaf[1] ?? leaf[2], value };
|
|
162
|
+
if (leaf[4]) item.className = leaf[4];
|
|
163
|
+
} else if (section) {
|
|
164
|
+
item = { name: section[1] ?? section[2], children: [] };
|
|
165
|
+
if (section[3]) item.className = section[3];
|
|
166
|
+
} else {
|
|
167
|
+
return {
|
|
168
|
+
error: /^["']/.test(text) ? 'Expected `"Name"` for a section or `"Name": value` for a leaf.' : 'Names must be in quotes, e.g. `"Sales": 120`.',
|
|
169
|
+
line
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
while (stack.length && stack[stack.length - 1].indent >= indent) stack.pop();
|
|
173
|
+
const parent = stack[stack.length - 1]?.item;
|
|
174
|
+
if (parent) parent.children.push(item);
|
|
175
|
+
else data.items.push(item);
|
|
176
|
+
if (item.children) stack.push({ item, indent });
|
|
177
|
+
}
|
|
178
|
+
if (!data.items.length) return { error: 'No items: add lines like `"Sales": 120`.' };
|
|
179
|
+
return { data };
|
|
180
|
+
}
|
|
181
|
+
function stripComment(line) {
|
|
182
|
+
let quote = null;
|
|
183
|
+
for (let i = 0; i < line.length; i++) {
|
|
184
|
+
const ch = line[i];
|
|
185
|
+
if (quote) {
|
|
186
|
+
if (ch === quote) quote = null;
|
|
187
|
+
} else if (ch === '"' || ch === "'") {
|
|
188
|
+
quote = ch;
|
|
189
|
+
} else if (ch === "%" && line[i + 1] === "%") {
|
|
190
|
+
return line.slice(0, i);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return line;
|
|
194
|
+
}
|
|
195
|
+
function treemapValue(item) {
|
|
196
|
+
if (!item.children) return item.value ?? 0;
|
|
197
|
+
return item.children.reduce((sum, child) => sum + treemapValue(child), 0);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export {
|
|
201
|
+
parseSankey,
|
|
202
|
+
parseTreemap,
|
|
203
|
+
treemapValue
|
|
204
|
+
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
var _chunkDWJJI6RTcjs = require('./chunk-DWJJI6RT.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
var
|
|
7
|
+
var _chunk4ZC5QJE2cjs = require('./chunk-4ZC5QJE2.cjs');
|
|
8
8
|
|
|
9
9
|
// src/animation/keyframes.ts
|
|
10
10
|
var ANIMATION_TIME_EPSILON = 1e-6;
|
|
@@ -46,7 +46,7 @@ function interpolateAnimationValue(from, to, t, type = "number") {
|
|
|
46
46
|
if (t <= 0) return from;
|
|
47
47
|
if (t >= 1) return to;
|
|
48
48
|
if (type === "color" || typeof from === "string" && typeof to === "string") {
|
|
49
|
-
const mixed =
|
|
49
|
+
const mixed = _chunk4ZC5QJE2cjs.mixColorsOklab.call(void 0, from, to, t);
|
|
50
50
|
if (mixed !== null) return mixed;
|
|
51
51
|
}
|
|
52
52
|
return from;
|