@diagrammo/dgmo 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +178 -178
- package/dist/index.cjs +218 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +218 -87
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/c4/parser.ts +3 -2
- package/src/d3.ts +7 -9
- package/src/er/parser.ts +5 -3
- package/src/gantt/calculator.ts +51 -11
- package/src/gantt/parser.ts +26 -20
- package/src/gantt/renderer.ts +177 -51
- package/src/org/parser.ts +7 -5
- package/src/sequence/parser.ts +10 -9
- package/src/sitemap/parser.ts +5 -3
- package/src/utils/parsing.ts +23 -12
package/dist/index.cjs
CHANGED
|
@@ -1517,24 +1517,26 @@ function parseSeriesNames(value, lines, lineIndex, palette) {
|
|
|
1517
1517
|
}
|
|
1518
1518
|
return { series, names, nameColors, newIndex };
|
|
1519
1519
|
}
|
|
1520
|
-
function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map()) {
|
|
1520
|
+
function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map(), warnMultiplePipes) {
|
|
1521
|
+
if (segments.length > 2 && warnMultiplePipes) {
|
|
1522
|
+
warnMultiplePipes();
|
|
1523
|
+
}
|
|
1521
1524
|
const metadata = {};
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
}
|
|
1525
|
+
const raw = segments.slice(1).join(",");
|
|
1526
|
+
for (const part of raw.split(",")) {
|
|
1527
|
+
const trimmedPart = part.trim();
|
|
1528
|
+
if (!trimmedPart) continue;
|
|
1529
|
+
const colonIdx = trimmedPart.indexOf(":");
|
|
1530
|
+
if (colonIdx > 0) {
|
|
1531
|
+
const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
|
|
1532
|
+
const key = aliasMap.get(rawKey) ?? rawKey;
|
|
1533
|
+
const value = trimmedPart.substring(colonIdx + 1).trim();
|
|
1534
|
+
metadata[key] = value;
|
|
1533
1535
|
}
|
|
1534
1536
|
}
|
|
1535
1537
|
return metadata;
|
|
1536
1538
|
}
|
|
1537
|
-
var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE;
|
|
1539
|
+
var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE, MULTIPLE_PIPE_WARNING;
|
|
1538
1540
|
var init_parsing = __esm({
|
|
1539
1541
|
"src/utils/parsing.ts"() {
|
|
1540
1542
|
"use strict";
|
|
@@ -1543,6 +1545,7 @@ var init_parsing = __esm({
|
|
|
1543
1545
|
CHART_TYPE_RE = /^chart\s*:\s*(.+)/i;
|
|
1544
1546
|
TITLE_RE = /^title\s*:\s*(.+)/i;
|
|
1545
1547
|
OPTION_RE = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
|
|
1548
|
+
MULTIPLE_PIPE_WARNING = 'Use a single "|" to start metadata, then separate items with commas.';
|
|
1546
1549
|
}
|
|
1547
1550
|
});
|
|
1548
1551
|
|
|
@@ -2043,12 +2046,13 @@ function parseSequenceDgmo(content) {
|
|
|
2043
2046
|
const participantGroupMap = /* @__PURE__ */ new Map();
|
|
2044
2047
|
let currentTagGroup = null;
|
|
2045
2048
|
const aliasMap = /* @__PURE__ */ new Map();
|
|
2046
|
-
const splitPipe = (text) => {
|
|
2049
|
+
const splitPipe = (text, ln) => {
|
|
2047
2050
|
const idx = text.indexOf("|");
|
|
2048
2051
|
if (idx < 0) return { core: text };
|
|
2049
2052
|
const core = text.substring(0, idx).trimEnd();
|
|
2050
2053
|
const segments = text.substring(idx).split("|");
|
|
2051
|
-
const
|
|
2054
|
+
const warnFn = ln != null ? () => pushWarning(ln, MULTIPLE_PIPE_WARNING) : void 0;
|
|
2055
|
+
const meta = parsePipeMetadata(segments, aliasMap, warnFn);
|
|
2052
2056
|
return Object.keys(meta).length > 0 ? { core, meta } : { core };
|
|
2053
2057
|
};
|
|
2054
2058
|
const blockStack = [];
|
|
@@ -2077,7 +2081,7 @@ function parseSequenceDgmo(content) {
|
|
|
2077
2081
|
if (gpipeIdx >= 0) {
|
|
2078
2082
|
const nameAndColor = groupName.substring(0, gpipeIdx).trimEnd();
|
|
2079
2083
|
const segments = groupName.substring(gpipeIdx).split("|");
|
|
2080
|
-
const meta = parsePipeMetadata(segments, aliasMap);
|
|
2084
|
+
const meta = parsePipeMetadata(segments, aliasMap, () => pushWarning(lineNumber, MULTIPLE_PIPE_WARNING));
|
|
2081
2085
|
if (Object.keys(meta).length > 0) groupMeta = meta;
|
|
2082
2086
|
const colorSuffix = nameAndColor.match(/^(.+?)\(([^)]+)\)$/);
|
|
2083
2087
|
if (colorSuffix) {
|
|
@@ -2200,7 +2204,7 @@ function parseSequenceDgmo(content) {
|
|
|
2200
2204
|
continue;
|
|
2201
2205
|
}
|
|
2202
2206
|
}
|
|
2203
|
-
const { core: isACore, meta: isAMeta } = splitPipe(trimmed);
|
|
2207
|
+
const { core: isACore, meta: isAMeta } = splitPipe(trimmed, lineNumber);
|
|
2204
2208
|
const isAMatch = isACore.match(IS_A_PATTERN);
|
|
2205
2209
|
if (isAMatch) {
|
|
2206
2210
|
contentStarted = true;
|
|
@@ -2237,7 +2241,7 @@ function parseSequenceDgmo(content) {
|
|
|
2237
2241
|
}
|
|
2238
2242
|
continue;
|
|
2239
2243
|
}
|
|
2240
|
-
const { core: posCore, meta: posMeta } = splitPipe(trimmed);
|
|
2244
|
+
const { core: posCore, meta: posMeta } = splitPipe(trimmed, lineNumber);
|
|
2241
2245
|
const posOnlyMatch = posCore.match(POSITION_ONLY_PATTERN);
|
|
2242
2246
|
if (posOnlyMatch) {
|
|
2243
2247
|
contentStarted = true;
|
|
@@ -2264,7 +2268,7 @@ function parseSequenceDgmo(content) {
|
|
|
2264
2268
|
}
|
|
2265
2269
|
continue;
|
|
2266
2270
|
}
|
|
2267
|
-
const { core: colorCore, meta: colorMeta } = splitPipe(trimmed);
|
|
2271
|
+
const { core: colorCore, meta: colorMeta } = splitPipe(trimmed, lineNumber);
|
|
2268
2272
|
const coloredMatch = colorCore.match(COLORED_PARTICIPANT_PATTERN);
|
|
2269
2273
|
if (coloredMatch && !ARROW_PATTERN.test(colorCore)) {
|
|
2270
2274
|
const id = coloredMatch[1];
|
|
@@ -2292,7 +2296,7 @@ function parseSequenceDgmo(content) {
|
|
|
2292
2296
|
continue;
|
|
2293
2297
|
}
|
|
2294
2298
|
{
|
|
2295
|
-
const { core: bareCore, meta: bareMeta } = splitPipe(trimmed);
|
|
2299
|
+
const { core: bareCore, meta: bareMeta } = splitPipe(trimmed, lineNumber);
|
|
2296
2300
|
const inGroup = activeGroup && measureIndent(raw) > 0;
|
|
2297
2301
|
if (/^\S+$/.test(bareCore) && !ARROW_PATTERN.test(bareCore) && (inGroup || !contentStarted || bareMeta)) {
|
|
2298
2302
|
contentStarted = true;
|
|
@@ -2328,7 +2332,7 @@ function parseSequenceDgmo(content) {
|
|
|
2328
2332
|
}
|
|
2329
2333
|
blockStack.pop();
|
|
2330
2334
|
}
|
|
2331
|
-
const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed);
|
|
2335
|
+
const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed, lineNumber);
|
|
2332
2336
|
const asyncPrefixMatch = arrowCore.match(/^async\s+(.+)$/i);
|
|
2333
2337
|
if (asyncPrefixMatch && ARROW_PATTERN.test(asyncPrefixMatch[1])) {
|
|
2334
2338
|
pushError(lineNumber, "Use ~> for async messages: A ~> B: message");
|
|
@@ -3779,7 +3783,12 @@ function parseERDiagram(content, palette) {
|
|
|
3779
3783
|
table.lineNumber = lineNumber;
|
|
3780
3784
|
const pipeStr = tableDecl[3]?.trim();
|
|
3781
3785
|
if (pipeStr) {
|
|
3782
|
-
const
|
|
3786
|
+
const pipeSegments = pipeStr.split("|");
|
|
3787
|
+
const meta = parsePipeMetadata(
|
|
3788
|
+
["", ...pipeSegments],
|
|
3789
|
+
aliasMap,
|
|
3790
|
+
() => result.diagnostics.push(makeDgmoError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"))
|
|
3791
|
+
);
|
|
3783
3792
|
Object.assign(table.metadata, meta);
|
|
3784
3793
|
}
|
|
3785
3794
|
currentTable = table;
|
|
@@ -5716,13 +5725,13 @@ function parseOrg(content, palette) {
|
|
|
5716
5725
|
}
|
|
5717
5726
|
} else if (metadataMatch && indentStack.length === 0) {
|
|
5718
5727
|
if (indent === 0) {
|
|
5719
|
-
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
5728
|
+
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
5720
5729
|
attachNode(node, indent, indentStack, result);
|
|
5721
5730
|
} else {
|
|
5722
5731
|
pushError(lineNumber, "Metadata has no parent node");
|
|
5723
5732
|
}
|
|
5724
5733
|
} else {
|
|
5725
|
-
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
5734
|
+
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
5726
5735
|
attachNode(node, indent, indentStack, result);
|
|
5727
5736
|
}
|
|
5728
5737
|
}
|
|
@@ -5744,11 +5753,11 @@ function parseOrg(content, palette) {
|
|
|
5744
5753
|
}
|
|
5745
5754
|
return result;
|
|
5746
5755
|
}
|
|
5747
|
-
function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
|
|
5756
|
+
function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
|
|
5748
5757
|
const segments = trimmed.split("|").map((s) => s.trim());
|
|
5749
5758
|
let rawLabel = segments[0];
|
|
5750
5759
|
const { label, color } = extractColor(rawLabel, palette);
|
|
5751
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
5760
|
+
const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
|
|
5752
5761
|
return {
|
|
5753
5762
|
id: `node-${counter}`,
|
|
5754
5763
|
label,
|
|
@@ -6290,7 +6299,7 @@ function parseC4(content, palette) {
|
|
|
6290
6299
|
}
|
|
6291
6300
|
const segments = trimmed.split("|").map((s) => s.trim());
|
|
6292
6301
|
const nodeName = segments[0];
|
|
6293
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
6302
|
+
const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
|
|
6294
6303
|
const shape = inferC4Shape(nodeName, metadata.tech ?? metadata.technology);
|
|
6295
6304
|
const dNode = {
|
|
6296
6305
|
name: nodeName,
|
|
@@ -6443,7 +6452,7 @@ function parseC4(content, palette) {
|
|
|
6443
6452
|
}
|
|
6444
6453
|
namePart = namePart.substring(0, isAMatch.index).trim();
|
|
6445
6454
|
}
|
|
6446
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
6455
|
+
const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
|
|
6447
6456
|
const shape = explicitShape ?? inferC4Shape(namePart, metadata.tech ?? metadata.technology);
|
|
6448
6457
|
const element = {
|
|
6449
6458
|
name: namePart,
|
|
@@ -7230,14 +7239,14 @@ function parseSitemap(content, palette) {
|
|
|
7230
7239
|
}
|
|
7231
7240
|
} else if (metadataMatch && indentStack.length === 0) {
|
|
7232
7241
|
if (indent === 0) {
|
|
7233
|
-
const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
7242
|
+
const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
7234
7243
|
attachNode2(node, indent, indentStack, result);
|
|
7235
7244
|
labelToNode.set(node.label.toLowerCase(), node);
|
|
7236
7245
|
} else {
|
|
7237
7246
|
pushError(lineNumber, "Metadata has no parent node");
|
|
7238
7247
|
}
|
|
7239
7248
|
} else {
|
|
7240
|
-
const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
7249
|
+
const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
7241
7250
|
attachNode2(node, indent, indentStack, result);
|
|
7242
7251
|
labelToNode.set(node.label.toLowerCase(), node);
|
|
7243
7252
|
}
|
|
@@ -7279,11 +7288,11 @@ function parseSitemap(content, palette) {
|
|
|
7279
7288
|
}
|
|
7280
7289
|
return result;
|
|
7281
7290
|
}
|
|
7282
|
-
function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
|
|
7291
|
+
function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
|
|
7283
7292
|
const segments = trimmed.split("|").map((s) => s.trim());
|
|
7284
7293
|
const rawLabel = segments[0];
|
|
7285
7294
|
const { label, color } = extractColor(rawLabel, palette);
|
|
7286
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
7295
|
+
const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
|
|
7287
7296
|
return {
|
|
7288
7297
|
id: `node-${counter}`,
|
|
7289
7298
|
label,
|
|
@@ -7991,6 +8000,9 @@ function parseGantt(content, palette) {
|
|
|
7991
8000
|
const warn = (line10, message) => {
|
|
7992
8001
|
diagnostics.push(makeDgmoError(line10, message, "warning"));
|
|
7993
8002
|
};
|
|
8003
|
+
const softError = (line10, message) => {
|
|
8004
|
+
diagnostics.push(makeDgmoError(line10, message, "error"));
|
|
8005
|
+
};
|
|
7994
8006
|
const aliasMap = /* @__PURE__ */ new Map();
|
|
7995
8007
|
const blockStack = [];
|
|
7996
8008
|
const currentContainer = () => {
|
|
@@ -8124,10 +8136,10 @@ function parseGantt(content, palette) {
|
|
|
8124
8136
|
const targetName = depParts[0].trim();
|
|
8125
8137
|
let offset;
|
|
8126
8138
|
if (depParts.length > 1) {
|
|
8127
|
-
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
|
|
8139
|
+
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
|
|
8128
8140
|
if (meta.lag || meta.lead) {
|
|
8129
8141
|
const key = meta.lag ? "lag" : "lead";
|
|
8130
|
-
|
|
8142
|
+
softError(lineNumber, `Unknown keyword "${key}". Use "offset: ${meta[key]}" instead.`);
|
|
8131
8143
|
}
|
|
8132
8144
|
if (meta.offset) {
|
|
8133
8145
|
const raw = meta.offset;
|
|
@@ -8266,16 +8278,18 @@ function parseGantt(content, palette) {
|
|
|
8266
8278
|
const groupMatch = line10.match(GROUP_RE2);
|
|
8267
8279
|
if (groupMatch) {
|
|
8268
8280
|
if (blockStack.length > 0 && blockStack[blockStack.length - 1].containerType === "task") {
|
|
8269
|
-
|
|
8281
|
+
softError(lineNumber, `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`);
|
|
8282
|
+
continue;
|
|
8270
8283
|
}
|
|
8271
8284
|
const afterBrackets = groupMatch[2].trim();
|
|
8272
8285
|
const segments = afterBrackets ? afterBrackets.split("|") : [];
|
|
8273
8286
|
let metadata = {};
|
|
8274
8287
|
let color = null;
|
|
8288
|
+
const pipeWarn = () => warn(lineNumber, MULTIPLE_PIPE_WARNING);
|
|
8275
8289
|
if (segments.length > 0 && segments[0].trim()) {
|
|
8276
|
-
metadata = parsePipeMetadata(["", ...segments], aliasMap);
|
|
8290
|
+
metadata = parsePipeMetadata(["", ...segments], aliasMap, pipeWarn);
|
|
8277
8291
|
} else if (segments.length > 1) {
|
|
8278
|
-
metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap);
|
|
8292
|
+
metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap, pipeWarn);
|
|
8279
8293
|
}
|
|
8280
8294
|
const nameExtracted = extractColor(groupMatch[1], palette);
|
|
8281
8295
|
if (nameExtracted.color) {
|
|
@@ -8306,7 +8320,6 @@ function parseGantt(content, palette) {
|
|
|
8306
8320
|
const uncertain = !!timelineDurMatch[4];
|
|
8307
8321
|
const labelRaw = timelineDurMatch[5];
|
|
8308
8322
|
const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber, startDate);
|
|
8309
|
-
if (result.error) return result;
|
|
8310
8323
|
const taskNode = { kind: "task", ...task };
|
|
8311
8324
|
currentContainer().push(taskNode);
|
|
8312
8325
|
lastTaskNode = taskNode;
|
|
@@ -8320,7 +8333,6 @@ function parseGantt(content, palette) {
|
|
|
8320
8333
|
const uncertain = !!durMatch[3];
|
|
8321
8334
|
const labelRaw = durMatch[4];
|
|
8322
8335
|
const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber);
|
|
8323
|
-
if (result.error) return result;
|
|
8324
8336
|
const taskNode = { kind: "task", ...task };
|
|
8325
8337
|
currentContainer().push(taskNode);
|
|
8326
8338
|
lastTaskNode = taskNode;
|
|
@@ -8337,7 +8349,6 @@ function parseGantt(content, palette) {
|
|
|
8337
8349
|
lineNumber,
|
|
8338
8350
|
explicitDateMatch[1]
|
|
8339
8351
|
);
|
|
8340
|
-
if (result.error) return result;
|
|
8341
8352
|
const taskNode = { kind: "task", ...task };
|
|
8342
8353
|
currentContainer().push(taskNode);
|
|
8343
8354
|
lastTaskNode = taskNode;
|
|
@@ -8347,13 +8358,14 @@ function parseGantt(content, palette) {
|
|
|
8347
8358
|
const depMatch = line10.match(DEPENDENCY_RE);
|
|
8348
8359
|
if (depMatch) {
|
|
8349
8360
|
if (!lastTaskNode) {
|
|
8350
|
-
|
|
8361
|
+
softError(lineNumber, `Dependency "-> ${depMatch[1]}" must be indented under a task.`);
|
|
8362
|
+
continue;
|
|
8351
8363
|
}
|
|
8352
8364
|
const depParts = depMatch[1].split("|");
|
|
8353
8365
|
const targetName = depParts[0].trim();
|
|
8354
8366
|
let offset;
|
|
8355
8367
|
if (depParts.length > 1) {
|
|
8356
|
-
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
|
|
8368
|
+
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
|
|
8357
8369
|
if (meta.lag || meta.lead) {
|
|
8358
8370
|
const key = meta.lag ? "lag" : "lead";
|
|
8359
8371
|
warn(lineNumber, `"${key}" is deprecated \u2014 use "offset: ${meta[key]}" instead.${key === "lead" ? ' Negate the value for lead behavior: "offset: -...".' : ""}`);
|
|
@@ -8373,7 +8385,8 @@ function parseGantt(content, palette) {
|
|
|
8373
8385
|
lastTaskNode.dependencies.push({ targetName, offset, lineNumber });
|
|
8374
8386
|
continue;
|
|
8375
8387
|
}
|
|
8376
|
-
|
|
8388
|
+
softError(lineNumber, `Expected duration (e.g., "10d: Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line10}"`);
|
|
8389
|
+
continue;
|
|
8377
8390
|
}
|
|
8378
8391
|
if (currentTagGroup) {
|
|
8379
8392
|
result.tagGroups.push(currentTagGroup);
|
|
@@ -8387,16 +8400,16 @@ function parseGantt(content, palette) {
|
|
|
8387
8400
|
const segments = labelRaw.split("|");
|
|
8388
8401
|
const label = segments[0].trim();
|
|
8389
8402
|
if (label.toLowerCase() === "parallel") {
|
|
8390
|
-
|
|
8403
|
+
softError(ln, `"parallel" is a reserved keyword and cannot be used as a task name.`);
|
|
8391
8404
|
}
|
|
8392
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap) : {};
|
|
8405
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap, () => warn(ln, MULTIPLE_PIPE_WARNING)) : {};
|
|
8393
8406
|
let progress = null;
|
|
8394
8407
|
if (metadata.progress) {
|
|
8395
8408
|
progress = parseFloat(metadata.progress);
|
|
8396
8409
|
delete metadata.progress;
|
|
8397
8410
|
}
|
|
8398
|
-
for (
|
|
8399
|
-
const seg =
|
|
8411
|
+
for (const part of segments.slice(1).join(",").split(",")) {
|
|
8412
|
+
const seg = part.trim();
|
|
8400
8413
|
const progressMatch = seg.match(/^(\d+)%$/);
|
|
8401
8414
|
if (progressMatch) {
|
|
8402
8415
|
progress = parseInt(progressMatch[1], 10);
|
|
@@ -8404,7 +8417,7 @@ function parseGantt(content, palette) {
|
|
|
8404
8417
|
}
|
|
8405
8418
|
if (metadata.lag || metadata.lead) {
|
|
8406
8419
|
const key = metadata.lag ? "lag" : "lead";
|
|
8407
|
-
|
|
8420
|
+
softError(ln, `Unknown keyword "${key}". Use "offset: ${metadata[key]}" instead.`);
|
|
8408
8421
|
}
|
|
8409
8422
|
let taskOffset;
|
|
8410
8423
|
if (metadata.offset) {
|
|
@@ -8491,7 +8504,7 @@ var init_parser10 = __esm({
|
|
|
8491
8504
|
DEPENDENCY_RE = /^->\s*(.+)$/;
|
|
8492
8505
|
COMMENT_RE = /^\/\//;
|
|
8493
8506
|
ERA_RE = /^era\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*->\s*(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*:\s*(.+)$/i;
|
|
8494
|
-
MARKER_RE = /^marker
|
|
8507
|
+
MARKER_RE = /^marker:\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s+(.+)$/i;
|
|
8495
8508
|
HOLIDAY_DATE_RE = /^(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
|
|
8496
8509
|
HOLIDAY_RANGE_RE = /^(\d{4}-\d{2}-\d{2})\s*->\s*(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
|
|
8497
8510
|
WORKWEEK_RE = /^workweek:\s*(.+)$/i;
|
|
@@ -18904,11 +18917,8 @@ function calculateSchedule(parsed) {
|
|
|
18904
18917
|
for (const dep of task.dependencies) {
|
|
18905
18918
|
const resolved = resolveTaskName(dep.targetName, allTasks);
|
|
18906
18919
|
if (isResolverError(resolved)) {
|
|
18907
|
-
|
|
18908
|
-
|
|
18909
|
-
} else {
|
|
18910
|
-
return fail(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
|
|
18911
|
-
}
|
|
18920
|
+
warn(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
|
|
18921
|
+
continue;
|
|
18912
18922
|
}
|
|
18913
18923
|
const targetNode = taskMap.get(resolved.task.id);
|
|
18914
18924
|
if (targetNode) {
|
|
@@ -18923,14 +18933,26 @@ function calculateSchedule(parsed) {
|
|
|
18923
18933
|
}
|
|
18924
18934
|
}
|
|
18925
18935
|
}
|
|
18926
|
-
|
|
18936
|
+
let sortedIds = topologicalSort(taskMap);
|
|
18927
18937
|
if (!sortedIds) {
|
|
18928
18938
|
const cycle = findCycle(taskMap);
|
|
18929
18939
|
const cycleStr = cycle.map((id) => taskMap.get(id).task.label).join(" \u2192 ");
|
|
18930
|
-
|
|
18940
|
+
warn(
|
|
18931
18941
|
taskMap.get(cycle[0]).task.lineNumber,
|
|
18932
|
-
`Circular dependency detected: ${cycleStr}
|
|
18942
|
+
`Circular dependency detected: ${cycleStr}. The cycle-creating dependency was dropped.`
|
|
18933
18943
|
);
|
|
18944
|
+
breakCycle(cycle, taskMap, depOffsetMap);
|
|
18945
|
+
sortedIds = topologicalSort(taskMap);
|
|
18946
|
+
let safety = 10;
|
|
18947
|
+
while (!sortedIds && safety-- > 0) {
|
|
18948
|
+
const nextCycle = findCycle(taskMap);
|
|
18949
|
+
if (nextCycle.length === 0) break;
|
|
18950
|
+
breakCycle(nextCycle, taskMap, depOffsetMap);
|
|
18951
|
+
sortedIds = topologicalSort(taskMap);
|
|
18952
|
+
}
|
|
18953
|
+
if (!sortedIds) {
|
|
18954
|
+
sortedIds = [...taskMap.keys()];
|
|
18955
|
+
}
|
|
18934
18956
|
}
|
|
18935
18957
|
for (const taskId of sortedIds) {
|
|
18936
18958
|
const node = taskMap.get(taskId);
|
|
@@ -19214,6 +19236,19 @@ function findCycle(taskMap) {
|
|
|
19214
19236
|
return null;
|
|
19215
19237
|
}
|
|
19216
19238
|
}
|
|
19239
|
+
function breakCycle(cycle, taskMap, depOffsetMap) {
|
|
19240
|
+
if (cycle.length < 3) return;
|
|
19241
|
+
const fromId = cycle[cycle.length - 2];
|
|
19242
|
+
const toId = cycle[0];
|
|
19243
|
+
const toNode = taskMap.get(toId);
|
|
19244
|
+
if (toNode) {
|
|
19245
|
+
const idx = toNode.predecessors.indexOf(fromId);
|
|
19246
|
+
if (idx !== -1) {
|
|
19247
|
+
toNode.predecessors.splice(idx, 1);
|
|
19248
|
+
depOffsetMap.delete(`${fromId}->${toId}`);
|
|
19249
|
+
}
|
|
19250
|
+
}
|
|
19251
|
+
}
|
|
19217
19252
|
function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holidaySet) {
|
|
19218
19253
|
if (sortedIds.length === 0) return /* @__PURE__ */ new Set();
|
|
19219
19254
|
const latestEnd = /* @__PURE__ */ new Map();
|
|
@@ -19302,10 +19337,10 @@ function buildResolvedGroups(nodes, taskMap, groups, depth) {
|
|
|
19302
19337
|
if (!resolved?.startDate || !resolved?.endDate) continue;
|
|
19303
19338
|
if (resolved.startDate.getTime() < minStart) minStart = resolved.startDate.getTime();
|
|
19304
19339
|
if (resolved.endDate.getTime() > maxEnd) maxEnd = resolved.endDate.getTime();
|
|
19340
|
+
const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
|
|
19341
|
+
totalDuration += dur;
|
|
19305
19342
|
if (task.progress !== null) {
|
|
19306
|
-
const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
|
|
19307
19343
|
totalProgress += task.progress * dur;
|
|
19308
|
-
totalDuration += dur;
|
|
19309
19344
|
hasProgress = true;
|
|
19310
19345
|
}
|
|
19311
19346
|
}
|
|
@@ -19348,7 +19383,7 @@ __export(renderer_exports9, {
|
|
|
19348
19383
|
});
|
|
19349
19384
|
function renderGantt(container, resolved, palette, isDark, options, exportDims) {
|
|
19350
19385
|
container.innerHTML = "";
|
|
19351
|
-
if (resolved.
|
|
19386
|
+
if (resolved.tasks.length === 0) return;
|
|
19352
19387
|
const onClickItem = options?.onClickItem;
|
|
19353
19388
|
const collapsedGroups = options?.collapsedGroups;
|
|
19354
19389
|
const onToggleGroup = options?.onToggleGroup;
|
|
@@ -19378,9 +19413,10 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19378
19413
|
const titleHeight = title ? 50 : 20;
|
|
19379
19414
|
const tagLegendReserve = resolved.tagGroups.length > 0 ? LEGEND_HEIGHT + 8 : 0;
|
|
19380
19415
|
const topDateLabelReserve = 22;
|
|
19416
|
+
const CONTENT_TOP_PAD = 16;
|
|
19381
19417
|
const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve;
|
|
19382
19418
|
const contentH = isTagMode ? totalRows * (BAR_H + ROW_GAP) : totalRows * (BAR_H + ROW_GAP) + GROUP_GAP2 * resolved.groups.length;
|
|
19383
|
-
const innerHeight = contentH;
|
|
19419
|
+
const innerHeight = CONTENT_TOP_PAD + contentH;
|
|
19384
19420
|
const outerHeight = marginTop + innerHeight + BOTTOM_MARGIN;
|
|
19385
19421
|
const containerWidth = exportDims?.width ?? (container.clientWidth || 800);
|
|
19386
19422
|
const innerWidth = containerWidth - leftMargin - RIGHT_MARGIN;
|
|
@@ -19418,7 +19454,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19418
19454
|
},
|
|
19419
19455
|
currentSwimlaneGroup,
|
|
19420
19456
|
onSwimlaneChange,
|
|
19421
|
-
viewMode
|
|
19457
|
+
viewMode,
|
|
19458
|
+
resolved.tasks
|
|
19422
19459
|
);
|
|
19423
19460
|
}
|
|
19424
19461
|
}
|
|
@@ -19464,7 +19501,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19464
19501
|
}
|
|
19465
19502
|
}
|
|
19466
19503
|
}
|
|
19467
|
-
let yOffset =
|
|
19504
|
+
let yOffset = CONTENT_TOP_PAD;
|
|
19468
19505
|
for (const row of rows) {
|
|
19469
19506
|
if (row.type === "lane-header") {
|
|
19470
19507
|
const laneColor = row.laneColor === "#999999" ? palette.textMuted : row.laneColor;
|
|
@@ -19513,7 +19550,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19513
19550
|
const isCollapsed = collapsedGroups?.has(group.name) ?? false;
|
|
19514
19551
|
const indent = " ".repeat(group.depth);
|
|
19515
19552
|
const toggleIcon = isCollapsed ? "\u25BA" : "\u25BC";
|
|
19516
|
-
const
|
|
19553
|
+
const tagColor = resolveTagColor(group.metadata, resolved.tagGroups, currentActiveGroup, true);
|
|
19554
|
+
const groupColor = tagColor && tagColor !== "#999999" ? tagColor : group.color || palette.textMuted;
|
|
19517
19555
|
const labelG = svg.append("g").attr("class", "gantt-group-label").attr("data-group", group.name).attr("data-line-number", String(group.lineNumber)).style("cursor", onToggleGroup ? "pointer" : "default").on("click", () => {
|
|
19518
19556
|
if (onToggleGroup) onToggleGroup(group.name);
|
|
19519
19557
|
}).on("mouseenter", () => {
|
|
@@ -19569,7 +19607,11 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19569
19607
|
const taskLabel = svg.append("text").attr("class", "gantt-task-label").attr("x", taskLabelX).attr("y", marginTop + yOffset + BAR_H / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", "11px").attr("fill", palette.text).attr("data-line-number", String(task.lineNumber)).attr("data-task-id", task.id).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").text(task.label).on("click", () => {
|
|
19570
19608
|
if (onClickItem) onClickItem(task.lineNumber);
|
|
19571
19609
|
}).on("mouseenter", () => {
|
|
19572
|
-
|
|
19610
|
+
if (rt.isMilestone) {
|
|
19611
|
+
highlightMilestone(g, svg, task.id);
|
|
19612
|
+
} else {
|
|
19613
|
+
highlightTask(g, svg, task.id);
|
|
19614
|
+
}
|
|
19573
19615
|
}).on("mouseleave", () => {
|
|
19574
19616
|
resetHighlight(g, svg);
|
|
19575
19617
|
});
|
|
@@ -19583,14 +19625,14 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19583
19625
|
if (rt.isMilestone) {
|
|
19584
19626
|
const mx = xScale(dateToFractionalYear(rt.startDate));
|
|
19585
19627
|
const my = yOffset + BAR_H / 2;
|
|
19586
|
-
g.append("polygon").attr("class", "gantt-milestone").attr("points", diamondPoints(mx, my, MILESTONE_SIZE)).attr("fill", barColor).attr("stroke", barColor).attr("stroke-width", 1.5).attr("data-line-number", String(task.lineNumber)).attr("data-task-name", task.label).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").on("click", () => {
|
|
19628
|
+
g.append("polygon").attr("class", "gantt-milestone").attr("points", diamondPoints(mx, my, MILESTONE_SIZE)).attr("fill", barColor).attr("stroke", barColor).attr("stroke-width", 1.5).attr("data-line-number", String(task.lineNumber)).attr("data-task-name", task.label).attr("data-task-id", task.id).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").on("click", () => {
|
|
19587
19629
|
if (onClickItem) onClickItem(task.lineNumber);
|
|
19588
19630
|
}).on("mouseenter", () => {
|
|
19589
|
-
|
|
19631
|
+
highlightMilestone(g, svg, task.id);
|
|
19590
19632
|
showGanttDateIndicators(g, xScale, rt.startDate, null, innerHeight, barColor);
|
|
19591
19633
|
g.append("text").attr("class", "gantt-milestone-hover-label").attr("x", mx - MILESTONE_SIZE - 4).attr("y", my).attr("dy", "0.35em").attr("text-anchor", "end").attr("font-size", "10px").attr("fill", barColor).attr("font-weight", "600").text(task.label);
|
|
19592
19634
|
}).on("mouseleave", () => {
|
|
19593
|
-
|
|
19635
|
+
resetHighlight(g, svg);
|
|
19594
19636
|
hideGanttDateIndicators(g);
|
|
19595
19637
|
g.selectAll(".gantt-milestone-hover-label").remove();
|
|
19596
19638
|
});
|
|
@@ -19851,7 +19893,7 @@ function drawSwimlaneIcon(parent, x, y, isActive, palette) {
|
|
|
19851
19893
|
}
|
|
19852
19894
|
return iconG;
|
|
19853
19895
|
}
|
|
19854
|
-
function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode) {
|
|
19896
|
+
function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode, resolvedTasks) {
|
|
19855
19897
|
const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
|
|
19856
19898
|
let visibleGroups;
|
|
19857
19899
|
if (activeGroupName) {
|
|
@@ -19861,6 +19903,28 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19861
19903
|
} else {
|
|
19862
19904
|
visibleGroups = tagGroups;
|
|
19863
19905
|
}
|
|
19906
|
+
const usedValues = /* @__PURE__ */ new Map();
|
|
19907
|
+
if (resolvedTasks) {
|
|
19908
|
+
for (const group of visibleGroups) {
|
|
19909
|
+
const key = group.name.toLowerCase();
|
|
19910
|
+
const used = /* @__PURE__ */ new Set();
|
|
19911
|
+
for (const rt of resolvedTasks) {
|
|
19912
|
+
const val = rt.effectiveMetadata[key];
|
|
19913
|
+
if (val) used.add(val.toLowerCase());
|
|
19914
|
+
}
|
|
19915
|
+
usedValues.set(key, used);
|
|
19916
|
+
}
|
|
19917
|
+
}
|
|
19918
|
+
const filteredEntries = /* @__PURE__ */ new Map();
|
|
19919
|
+
for (const group of visibleGroups) {
|
|
19920
|
+
const key = group.name.toLowerCase();
|
|
19921
|
+
const used = usedValues.get(key);
|
|
19922
|
+
if (used && used.size > 0) {
|
|
19923
|
+
filteredEntries.set(key, group.entries.filter((e) => used.has(e.value.toLowerCase())));
|
|
19924
|
+
} else {
|
|
19925
|
+
filteredEntries.set(key, group.entries);
|
|
19926
|
+
}
|
|
19927
|
+
}
|
|
19864
19928
|
const groupWidths = [];
|
|
19865
19929
|
let totalW = 0;
|
|
19866
19930
|
for (const group of visibleGroups) {
|
|
@@ -19871,8 +19935,9 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19871
19935
|
const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD + iconReserve;
|
|
19872
19936
|
let groupW = pillW;
|
|
19873
19937
|
if (isActive) {
|
|
19938
|
+
const entries = filteredEntries.get(group.name.toLowerCase()) ?? group.entries;
|
|
19874
19939
|
let entriesW = 0;
|
|
19875
|
-
for (const entry of
|
|
19940
|
+
for (const entry of entries) {
|
|
19876
19941
|
entriesW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
|
|
19877
19942
|
}
|
|
19878
19943
|
groupW = LEGEND_CAPSULE_PAD * 2 + pillW + 4 + entriesW;
|
|
@@ -19931,8 +19996,9 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19931
19996
|
}
|
|
19932
19997
|
if (isActive) {
|
|
19933
19998
|
const tagKey = group.name.toLowerCase();
|
|
19999
|
+
const entries = filteredEntries.get(tagKey) ?? group.entries;
|
|
19934
20000
|
let ex = pillXOff + pillW + LEGEND_CAPSULE_PAD + 4;
|
|
19935
|
-
for (const entry of
|
|
20001
|
+
for (const entry of entries) {
|
|
19936
20002
|
const entryValue = entry.value.toLowerCase();
|
|
19937
20003
|
const entryG = gEl.append("g").attr("class", "gantt-legend-entry").style("cursor", "pointer");
|
|
19938
20004
|
entryG.append("circle").attr("cx", ex + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
|
|
@@ -20016,13 +20082,26 @@ function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
|
|
|
20016
20082
|
const color = marker.color || palette.accent || "#d08770";
|
|
20017
20083
|
const mx = xScale(parseDateToFractionalYear(marker.date));
|
|
20018
20084
|
const markerDate = parseDateStringToDate(marker.date);
|
|
20019
|
-
const
|
|
20020
|
-
|
|
20021
|
-
|
|
20022
|
-
markerG.append("
|
|
20085
|
+
const diamondSize = 5;
|
|
20086
|
+
const labelY = -24;
|
|
20087
|
+
const diamondY = labelY + 14;
|
|
20088
|
+
const markerG = g.append("g").attr("class", "gantt-marker-group").style("cursor", "pointer");
|
|
20089
|
+
markerG.append("rect").attr("x", mx - 40).attr("y", labelY - 12).attr("width", 80).attr("height", innerHeight - labelY + 12).attr("fill", "transparent").attr("pointer-events", "all");
|
|
20090
|
+
markerG.append("text").attr("class", "gantt-marker-label").attr("x", mx).attr("y", labelY).attr("text-anchor", "middle").attr("font-size", "11px").attr("font-weight", "600").attr("fill", color).text(marker.label);
|
|
20091
|
+
markerG.append("path").attr("d", `M${mx},${diamondY - diamondSize} l${diamondSize},${diamondSize} l-${diamondSize},${diamondSize} l-${diamondSize},-${diamondSize} Z`).attr("fill", color).attr("opacity", 0.9);
|
|
20092
|
+
markerG.append("line").attr("class", "gantt-marker").attr("x1", mx).attr("y1", diamondY + diamondSize).attr("x2", mx).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "6 4").attr("opacity", 0.5);
|
|
20093
|
+
const markerLine = markerG.select(".gantt-marker");
|
|
20094
|
+
const markerLabel = markerG.select(".gantt-marker-label");
|
|
20095
|
+
const markerDiamond = markerG.select("path");
|
|
20023
20096
|
markerG.on("mouseenter", () => {
|
|
20097
|
+
markerLine.attr("opacity", 0);
|
|
20098
|
+
markerLabel.attr("opacity", 0);
|
|
20099
|
+
markerDiamond.attr("opacity", 0);
|
|
20024
20100
|
showGanttDateIndicators(g, xScale, markerDate, null, innerHeight, color);
|
|
20025
20101
|
}).on("mouseleave", () => {
|
|
20102
|
+
markerLine.attr("opacity", 0.5);
|
|
20103
|
+
markerLabel.attr("opacity", 1);
|
|
20104
|
+
markerDiamond.attr("opacity", 0.9);
|
|
20026
20105
|
hideGanttDateIndicators(g);
|
|
20027
20106
|
});
|
|
20028
20107
|
}
|
|
@@ -20097,6 +20176,7 @@ function highlightGroup(g, svg, groupName) {
|
|
|
20097
20176
|
});
|
|
20098
20177
|
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20099
20178
|
g.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", FADE_OPACITY);
|
|
20179
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20100
20180
|
}
|
|
20101
20181
|
function highlightLane(g, svg, tagKey, laneName) {
|
|
20102
20182
|
const tagAttr = `data-tag-${tagKey}`;
|
|
@@ -20123,6 +20203,7 @@ function highlightLane(g, svg, tagKey, laneName) {
|
|
|
20123
20203
|
});
|
|
20124
20204
|
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20125
20205
|
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20206
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20126
20207
|
}
|
|
20127
20208
|
function highlightTask(g, svg, taskId) {
|
|
20128
20209
|
g.selectAll(".gantt-task").each(function() {
|
|
@@ -20139,6 +20220,24 @@ function highlightTask(g, svg, taskId) {
|
|
|
20139
20220
|
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20140
20221
|
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
20141
20222
|
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
20223
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20224
|
+
}
|
|
20225
|
+
function highlightMilestone(g, svg, taskId) {
|
|
20226
|
+
g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
|
|
20227
|
+
g.selectAll(".gantt-milestone").each(function() {
|
|
20228
|
+
const el = d3Selection10.select(this);
|
|
20229
|
+
el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
|
|
20230
|
+
});
|
|
20231
|
+
svg.selectAll(".gantt-task-label").each(function() {
|
|
20232
|
+
const el = d3Selection10.select(this);
|
|
20233
|
+
el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
|
|
20234
|
+
});
|
|
20235
|
+
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20236
|
+
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20237
|
+
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20238
|
+
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
20239
|
+
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
20240
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20142
20241
|
}
|
|
20143
20242
|
function highlightTaskLabel(svg, lineNumber) {
|
|
20144
20243
|
const ln = String(lineNumber);
|
|
@@ -20158,6 +20257,7 @@ function resetHighlight(g, svg) {
|
|
|
20158
20257
|
svg.selectAll(".gantt-lane-header").attr("opacity", 1);
|
|
20159
20258
|
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", 1);
|
|
20160
20259
|
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", 0.5);
|
|
20260
|
+
g.selectAll(".gantt-marker-group").attr("opacity", 1);
|
|
20161
20261
|
}
|
|
20162
20262
|
function buildRowList(resolved, collapsedGroups) {
|
|
20163
20263
|
const rows = [];
|
|
@@ -20236,9 +20336,9 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
|
|
|
20236
20336
|
for (const entry of tagGroup.entries) {
|
|
20237
20337
|
const entryKey = entry.value.toLowerCase();
|
|
20238
20338
|
const tasks = buckets.get(entryKey) ?? [];
|
|
20339
|
+
if (tasks.length === 0) continue;
|
|
20239
20340
|
tasks.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
|
|
20240
|
-
const
|
|
20241
|
-
const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
|
|
20341
|
+
const aggregateProgress = durationWeightedProgress(tasks);
|
|
20242
20342
|
const laneStartDate = tasks.length > 0 ? new Date(Math.min(...tasks.map((t) => t.startDate.getTime()))) : null;
|
|
20243
20343
|
const laneEndDate = tasks.length > 0 ? new Date(Math.max(...tasks.map((t) => t.endDate.getTime()))) : null;
|
|
20244
20344
|
const isCollapsed = collapsedLanes?.has(entry.value) ?? false;
|
|
@@ -20260,8 +20360,7 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
|
|
|
20260
20360
|
}
|
|
20261
20361
|
if (unbucketed.length > 0) {
|
|
20262
20362
|
unbucketed.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
|
|
20263
|
-
const
|
|
20264
|
-
const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
|
|
20363
|
+
const aggregateProgress = durationWeightedProgress(unbucketed);
|
|
20265
20364
|
const noLaneStartDate = unbucketed.length > 0 ? new Date(Math.min(...unbucketed.map((t) => t.startDate.getTime()))) : null;
|
|
20266
20365
|
const noLaneEndDate = unbucketed.length > 0 ? new Date(Math.max(...unbucketed.map((t) => t.endDate.getTime()))) : null;
|
|
20267
20366
|
const noLaneName = `No ${tagGroup.name}`;
|
|
@@ -20284,6 +20383,20 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
|
|
|
20284
20383
|
}
|
|
20285
20384
|
return rows;
|
|
20286
20385
|
}
|
|
20386
|
+
function durationWeightedProgress(tasks) {
|
|
20387
|
+
let totalDuration = 0;
|
|
20388
|
+
let totalProgress = 0;
|
|
20389
|
+
let hasProgress = false;
|
|
20390
|
+
for (const rt of tasks) {
|
|
20391
|
+
const dur = rt.endDate.getTime() - rt.startDate.getTime();
|
|
20392
|
+
totalDuration += dur;
|
|
20393
|
+
if (rt.task.progress !== null) {
|
|
20394
|
+
totalProgress += rt.task.progress * dur;
|
|
20395
|
+
hasProgress = true;
|
|
20396
|
+
}
|
|
20397
|
+
}
|
|
20398
|
+
return hasProgress && totalDuration > 0 ? totalProgress / totalDuration : null;
|
|
20399
|
+
}
|
|
20287
20400
|
function dateToFractionalYear(d) {
|
|
20288
20401
|
const y = d.getFullYear();
|
|
20289
20402
|
const startOfYear = new Date(y, 0, 1);
|
|
@@ -20310,9 +20423,28 @@ function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, col
|
|
|
20310
20423
|
if (endDate && endDate.getTime() !== startDate.getTime()) {
|
|
20311
20424
|
const endPos = xScale(dateToFractionalYear(endDate));
|
|
20312
20425
|
const endLabel = formatGanttDate(endDate);
|
|
20426
|
+
const minLabelGap = 90;
|
|
20427
|
+
const gap = endPos - startPos;
|
|
20428
|
+
let startLabelX = startPos;
|
|
20429
|
+
let endLabelX = endPos;
|
|
20430
|
+
let startAnchor = "middle";
|
|
20431
|
+
let endAnchor = "middle";
|
|
20432
|
+
if (gap < minLabelGap) {
|
|
20433
|
+
const mid = (startPos + endPos) / 2;
|
|
20434
|
+
startLabelX = mid - minLabelGap / 2;
|
|
20435
|
+
endLabelX = mid + minLabelGap / 2;
|
|
20436
|
+
startAnchor = "middle";
|
|
20437
|
+
endAnchor = "middle";
|
|
20438
|
+
}
|
|
20313
20439
|
g.append("line").attr("class", "gantt-hover-date").attr("x1", endPos).attr("y1", -tickLen).attr("x2", endPos).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "4 4").attr("opacity", 0.6);
|
|
20314
|
-
g.
|
|
20315
|
-
|
|
20440
|
+
g.selectAll("text.gantt-hover-date").each(function() {
|
|
20441
|
+
const el = d3Selection10.select(this);
|
|
20442
|
+
if (el.text() === startLabel) {
|
|
20443
|
+
el.attr("x", startLabelX).attr("text-anchor", startAnchor);
|
|
20444
|
+
}
|
|
20445
|
+
});
|
|
20446
|
+
g.append("text").attr("class", "gantt-hover-date").attr("x", endLabelX).attr("y", -tickLen - 4).attr("text-anchor", endAnchor).attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(endLabel);
|
|
20447
|
+
g.append("text").attr("class", "gantt-hover-date").attr("x", endLabelX).attr("y", innerHeight + tickLen + 12).attr("text-anchor", endAnchor).attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(endLabel);
|
|
20316
20448
|
}
|
|
20317
20449
|
}
|
|
20318
20450
|
function hideGanttDateIndicators(g) {
|
|
@@ -22365,7 +22497,7 @@ function parseVisualization(content, palette) {
|
|
|
22365
22497
|
continue;
|
|
22366
22498
|
}
|
|
22367
22499
|
const markerMatch = line10.match(
|
|
22368
|
-
/^marker
|
|
22500
|
+
/^marker:\s+(\d{4}(?:-\d{2})?(?:-\d{2})?)\s+(.+?)(?:\s*\(([^)]+)\))?\s*$/
|
|
22369
22501
|
);
|
|
22370
22502
|
if (markerMatch) {
|
|
22371
22503
|
const colorAnnotation = markerMatch[3]?.trim() || null;
|
|
@@ -22389,7 +22521,7 @@ function parseVisualization(content, palette) {
|
|
|
22389
22521
|
const unit = durationMatch[3];
|
|
22390
22522
|
const endDate = addDurationToDate(startDate, amount, unit);
|
|
22391
22523
|
const segments = durationMatch[5].split("|");
|
|
22392
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
|
|
22524
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
|
|
22393
22525
|
result.timelineEvents.push({
|
|
22394
22526
|
date: startDate,
|
|
22395
22527
|
endDate,
|
|
@@ -22406,7 +22538,7 @@ function parseVisualization(content, palette) {
|
|
|
22406
22538
|
);
|
|
22407
22539
|
if (rangeMatch) {
|
|
22408
22540
|
const segments = rangeMatch[4].split("|");
|
|
22409
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
|
|
22541
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
|
|
22410
22542
|
result.timelineEvents.push({
|
|
22411
22543
|
date: rangeMatch[1],
|
|
22412
22544
|
endDate: rangeMatch[2],
|
|
@@ -22423,7 +22555,7 @@ function parseVisualization(content, palette) {
|
|
|
22423
22555
|
);
|
|
22424
22556
|
if (pointMatch) {
|
|
22425
22557
|
const segments = pointMatch[2].split("|");
|
|
22426
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
|
|
22558
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
|
|
22427
22559
|
result.timelineEvents.push({
|
|
22428
22560
|
date: pointMatch[1],
|
|
22429
22561
|
endDate: null,
|
|
@@ -25375,9 +25507,8 @@ async function renderForExport(content, theme, palette, orgExportState, options)
|
|
|
25375
25507
|
const { renderGantt: renderGantt2 } = await Promise.resolve().then(() => (init_renderer9(), renderer_exports9));
|
|
25376
25508
|
const effectivePalette2 = await resolveExportPalette(theme, palette);
|
|
25377
25509
|
const ganttParsed = parseGantt2(content, effectivePalette2);
|
|
25378
|
-
if (ganttParsed.error) return "";
|
|
25379
25510
|
const resolved = calculateSchedule2(ganttParsed);
|
|
25380
|
-
if (resolved.
|
|
25511
|
+
if (resolved.tasks.length === 0) return "";
|
|
25381
25512
|
const EXPORT_W = 1200;
|
|
25382
25513
|
const EXPORT_H = 800;
|
|
25383
25514
|
const container2 = createExportContainer(EXPORT_W, EXPORT_H);
|