@diagrammo/dgmo 0.7.0 → 0.7.2
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 +180 -180
- package/dist/index.cjs +415 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +415 -144
- 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 +31 -20
- package/src/gantt/renderer.ts +524 -132
- package/src/gantt/types.ts +4 -0
- 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,
|
|
@@ -7977,7 +7986,9 @@ function parseGantt(content, palette) {
|
|
|
7977
7986
|
criticalPath: false,
|
|
7978
7987
|
dependencies: false,
|
|
7979
7988
|
sort: "default",
|
|
7980
|
-
defaultSwimlaneGroup: null
|
|
7989
|
+
defaultSwimlaneGroup: null,
|
|
7990
|
+
optionLineNumbers: {},
|
|
7991
|
+
holidaysLineNumber: null
|
|
7981
7992
|
},
|
|
7982
7993
|
diagnostics,
|
|
7983
7994
|
error: null
|
|
@@ -7991,6 +8002,9 @@ function parseGantt(content, palette) {
|
|
|
7991
8002
|
const warn = (line10, message) => {
|
|
7992
8003
|
diagnostics.push(makeDgmoError(line10, message, "warning"));
|
|
7993
8004
|
};
|
|
8005
|
+
const softError = (line10, message) => {
|
|
8006
|
+
diagnostics.push(makeDgmoError(line10, message, "error"));
|
|
8007
|
+
};
|
|
7994
8008
|
const aliasMap = /* @__PURE__ */ new Map();
|
|
7995
8009
|
const blockStack = [];
|
|
7996
8010
|
const currentContainer = () => {
|
|
@@ -8124,10 +8138,10 @@ function parseGantt(content, palette) {
|
|
|
8124
8138
|
const targetName = depParts[0].trim();
|
|
8125
8139
|
let offset;
|
|
8126
8140
|
if (depParts.length > 1) {
|
|
8127
|
-
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
|
|
8141
|
+
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
|
|
8128
8142
|
if (meta.lag || meta.lead) {
|
|
8129
8143
|
const key = meta.lag ? "lag" : "lead";
|
|
8130
|
-
|
|
8144
|
+
softError(lineNumber, `Unknown keyword "${key}". Use "offset: ${meta[key]}" instead.`);
|
|
8131
8145
|
}
|
|
8132
8146
|
if (meta.offset) {
|
|
8133
8147
|
const raw = meta.offset;
|
|
@@ -8159,6 +8173,7 @@ function parseGantt(content, palette) {
|
|
|
8159
8173
|
inHolidaysBlock = true;
|
|
8160
8174
|
holidaysBlockIndent = indent;
|
|
8161
8175
|
inHeaderBlock = false;
|
|
8176
|
+
result.options.holidaysLineNumber = lineNumber;
|
|
8162
8177
|
continue;
|
|
8163
8178
|
}
|
|
8164
8179
|
const tagMatch = matchTagBlockHeading(line10);
|
|
@@ -8185,7 +8200,8 @@ function parseGantt(content, palette) {
|
|
|
8185
8200
|
startDate: eraMatch[1],
|
|
8186
8201
|
endDate: eraMatch[2],
|
|
8187
8202
|
label: eraExtracted.label,
|
|
8188
|
-
color: eraExtracted.color || null
|
|
8203
|
+
color: eraExtracted.color || null,
|
|
8204
|
+
lineNumber
|
|
8189
8205
|
});
|
|
8190
8206
|
inHeaderBlock = false;
|
|
8191
8207
|
continue;
|
|
@@ -8207,6 +8223,7 @@ function parseGantt(content, palette) {
|
|
|
8207
8223
|
if (optMatch && isKnownOption(optMatch[1].toLowerCase())) {
|
|
8208
8224
|
const key = optMatch[1].toLowerCase();
|
|
8209
8225
|
const value = optMatch[2].trim();
|
|
8226
|
+
result.options.optionLineNumbers[key] = lineNumber;
|
|
8210
8227
|
switch (key) {
|
|
8211
8228
|
case "start":
|
|
8212
8229
|
result.options.start = value;
|
|
@@ -8266,16 +8283,18 @@ function parseGantt(content, palette) {
|
|
|
8266
8283
|
const groupMatch = line10.match(GROUP_RE2);
|
|
8267
8284
|
if (groupMatch) {
|
|
8268
8285
|
if (blockStack.length > 0 && blockStack[blockStack.length - 1].containerType === "task") {
|
|
8269
|
-
|
|
8286
|
+
softError(lineNumber, `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`);
|
|
8287
|
+
continue;
|
|
8270
8288
|
}
|
|
8271
8289
|
const afterBrackets = groupMatch[2].trim();
|
|
8272
8290
|
const segments = afterBrackets ? afterBrackets.split("|") : [];
|
|
8273
8291
|
let metadata = {};
|
|
8274
8292
|
let color = null;
|
|
8293
|
+
const pipeWarn = () => warn(lineNumber, MULTIPLE_PIPE_WARNING);
|
|
8275
8294
|
if (segments.length > 0 && segments[0].trim()) {
|
|
8276
|
-
metadata = parsePipeMetadata(["", ...segments], aliasMap);
|
|
8295
|
+
metadata = parsePipeMetadata(["", ...segments], aliasMap, pipeWarn);
|
|
8277
8296
|
} else if (segments.length > 1) {
|
|
8278
|
-
metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap);
|
|
8297
|
+
metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap, pipeWarn);
|
|
8279
8298
|
}
|
|
8280
8299
|
const nameExtracted = extractColor(groupMatch[1], palette);
|
|
8281
8300
|
if (nameExtracted.color) {
|
|
@@ -8306,7 +8325,6 @@ function parseGantt(content, palette) {
|
|
|
8306
8325
|
const uncertain = !!timelineDurMatch[4];
|
|
8307
8326
|
const labelRaw = timelineDurMatch[5];
|
|
8308
8327
|
const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber, startDate);
|
|
8309
|
-
if (result.error) return result;
|
|
8310
8328
|
const taskNode = { kind: "task", ...task };
|
|
8311
8329
|
currentContainer().push(taskNode);
|
|
8312
8330
|
lastTaskNode = taskNode;
|
|
@@ -8320,7 +8338,6 @@ function parseGantt(content, palette) {
|
|
|
8320
8338
|
const uncertain = !!durMatch[3];
|
|
8321
8339
|
const labelRaw = durMatch[4];
|
|
8322
8340
|
const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber);
|
|
8323
|
-
if (result.error) return result;
|
|
8324
8341
|
const taskNode = { kind: "task", ...task };
|
|
8325
8342
|
currentContainer().push(taskNode);
|
|
8326
8343
|
lastTaskNode = taskNode;
|
|
@@ -8337,7 +8354,6 @@ function parseGantt(content, palette) {
|
|
|
8337
8354
|
lineNumber,
|
|
8338
8355
|
explicitDateMatch[1]
|
|
8339
8356
|
);
|
|
8340
|
-
if (result.error) return result;
|
|
8341
8357
|
const taskNode = { kind: "task", ...task };
|
|
8342
8358
|
currentContainer().push(taskNode);
|
|
8343
8359
|
lastTaskNode = taskNode;
|
|
@@ -8347,13 +8363,14 @@ function parseGantt(content, palette) {
|
|
|
8347
8363
|
const depMatch = line10.match(DEPENDENCY_RE);
|
|
8348
8364
|
if (depMatch) {
|
|
8349
8365
|
if (!lastTaskNode) {
|
|
8350
|
-
|
|
8366
|
+
softError(lineNumber, `Dependency "-> ${depMatch[1]}" must be indented under a task.`);
|
|
8367
|
+
continue;
|
|
8351
8368
|
}
|
|
8352
8369
|
const depParts = depMatch[1].split("|");
|
|
8353
8370
|
const targetName = depParts[0].trim();
|
|
8354
8371
|
let offset;
|
|
8355
8372
|
if (depParts.length > 1) {
|
|
8356
|
-
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
|
|
8373
|
+
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
|
|
8357
8374
|
if (meta.lag || meta.lead) {
|
|
8358
8375
|
const key = meta.lag ? "lag" : "lead";
|
|
8359
8376
|
warn(lineNumber, `"${key}" is deprecated \u2014 use "offset: ${meta[key]}" instead.${key === "lead" ? ' Negate the value for lead behavior: "offset: -...".' : ""}`);
|
|
@@ -8373,7 +8390,8 @@ function parseGantt(content, palette) {
|
|
|
8373
8390
|
lastTaskNode.dependencies.push({ targetName, offset, lineNumber });
|
|
8374
8391
|
continue;
|
|
8375
8392
|
}
|
|
8376
|
-
|
|
8393
|
+
softError(lineNumber, `Expected duration (e.g., "10d: Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line10}"`);
|
|
8394
|
+
continue;
|
|
8377
8395
|
}
|
|
8378
8396
|
if (currentTagGroup) {
|
|
8379
8397
|
result.tagGroups.push(currentTagGroup);
|
|
@@ -8387,16 +8405,16 @@ function parseGantt(content, palette) {
|
|
|
8387
8405
|
const segments = labelRaw.split("|");
|
|
8388
8406
|
const label = segments[0].trim();
|
|
8389
8407
|
if (label.toLowerCase() === "parallel") {
|
|
8390
|
-
|
|
8408
|
+
softError(ln, `"parallel" is a reserved keyword and cannot be used as a task name.`);
|
|
8391
8409
|
}
|
|
8392
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap) : {};
|
|
8410
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap, () => warn(ln, MULTIPLE_PIPE_WARNING)) : {};
|
|
8393
8411
|
let progress = null;
|
|
8394
8412
|
if (metadata.progress) {
|
|
8395
8413
|
progress = parseFloat(metadata.progress);
|
|
8396
8414
|
delete metadata.progress;
|
|
8397
8415
|
}
|
|
8398
|
-
for (
|
|
8399
|
-
const seg =
|
|
8416
|
+
for (const part of segments.slice(1).join(",").split(",")) {
|
|
8417
|
+
const seg = part.trim();
|
|
8400
8418
|
const progressMatch = seg.match(/^(\d+)%$/);
|
|
8401
8419
|
if (progressMatch) {
|
|
8402
8420
|
progress = parseInt(progressMatch[1], 10);
|
|
@@ -8404,7 +8422,7 @@ function parseGantt(content, palette) {
|
|
|
8404
8422
|
}
|
|
8405
8423
|
if (metadata.lag || metadata.lead) {
|
|
8406
8424
|
const key = metadata.lag ? "lag" : "lead";
|
|
8407
|
-
|
|
8425
|
+
softError(ln, `Unknown keyword "${key}". Use "offset: ${metadata[key]}" instead.`);
|
|
8408
8426
|
}
|
|
8409
8427
|
let taskOffset;
|
|
8410
8428
|
if (metadata.offset) {
|
|
@@ -8491,7 +8509,7 @@ var init_parser10 = __esm({
|
|
|
8491
8509
|
DEPENDENCY_RE = /^->\s*(.+)$/;
|
|
8492
8510
|
COMMENT_RE = /^\/\//;
|
|
8493
8511
|
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
|
|
8512
|
+
MARKER_RE = /^marker:\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s+(.+)$/i;
|
|
8495
8513
|
HOLIDAY_DATE_RE = /^(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
|
|
8496
8514
|
HOLIDAY_RANGE_RE = /^(\d{4}-\d{2}-\d{2})\s*->\s*(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
|
|
8497
8515
|
WORKWEEK_RE = /^workweek:\s*(.+)$/i;
|
|
@@ -16116,7 +16134,7 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
|
|
|
16116
16134
|
}
|
|
16117
16135
|
const contentG = svg.append("g").attr("transform", `translate(${offsetX}, ${offsetY}) scale(${scale})`);
|
|
16118
16136
|
const LABEL_CHAR_W = 7;
|
|
16119
|
-
const
|
|
16137
|
+
const LABEL_PAD2 = 8;
|
|
16120
16138
|
const LABEL_H = 16;
|
|
16121
16139
|
const PERP_OFFSET = 10;
|
|
16122
16140
|
const labelPositions = [];
|
|
@@ -16125,7 +16143,7 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
|
|
|
16125
16143
|
if (!edge.label || edge.points.length < 2) continue;
|
|
16126
16144
|
const midIdx = Math.floor(edge.points.length / 2);
|
|
16127
16145
|
const midPt = edge.points[midIdx];
|
|
16128
|
-
const bgW = edge.label.length * LABEL_CHAR_W +
|
|
16146
|
+
const bgW = edge.label.length * LABEL_CHAR_W + LABEL_PAD2;
|
|
16129
16147
|
const prev = edge.points[Math.max(0, midIdx - 1)];
|
|
16130
16148
|
const next = edge.points[Math.min(edge.points.length - 1, midIdx + 1)];
|
|
16131
16149
|
const dx = next.x - prev.x;
|
|
@@ -18904,11 +18922,8 @@ function calculateSchedule(parsed) {
|
|
|
18904
18922
|
for (const dep of task.dependencies) {
|
|
18905
18923
|
const resolved = resolveTaskName(dep.targetName, allTasks);
|
|
18906
18924
|
if (isResolverError(resolved)) {
|
|
18907
|
-
|
|
18908
|
-
|
|
18909
|
-
} else {
|
|
18910
|
-
return fail(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
|
|
18911
|
-
}
|
|
18925
|
+
warn(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
|
|
18926
|
+
continue;
|
|
18912
18927
|
}
|
|
18913
18928
|
const targetNode = taskMap.get(resolved.task.id);
|
|
18914
18929
|
if (targetNode) {
|
|
@@ -18923,14 +18938,26 @@ function calculateSchedule(parsed) {
|
|
|
18923
18938
|
}
|
|
18924
18939
|
}
|
|
18925
18940
|
}
|
|
18926
|
-
|
|
18941
|
+
let sortedIds = topologicalSort(taskMap);
|
|
18927
18942
|
if (!sortedIds) {
|
|
18928
18943
|
const cycle = findCycle(taskMap);
|
|
18929
18944
|
const cycleStr = cycle.map((id) => taskMap.get(id).task.label).join(" \u2192 ");
|
|
18930
|
-
|
|
18945
|
+
warn(
|
|
18931
18946
|
taskMap.get(cycle[0]).task.lineNumber,
|
|
18932
|
-
`Circular dependency detected: ${cycleStr}
|
|
18947
|
+
`Circular dependency detected: ${cycleStr}. The cycle-creating dependency was dropped.`
|
|
18933
18948
|
);
|
|
18949
|
+
breakCycle(cycle, taskMap, depOffsetMap);
|
|
18950
|
+
sortedIds = topologicalSort(taskMap);
|
|
18951
|
+
let safety = 10;
|
|
18952
|
+
while (!sortedIds && safety-- > 0) {
|
|
18953
|
+
const nextCycle = findCycle(taskMap);
|
|
18954
|
+
if (nextCycle.length === 0) break;
|
|
18955
|
+
breakCycle(nextCycle, taskMap, depOffsetMap);
|
|
18956
|
+
sortedIds = topologicalSort(taskMap);
|
|
18957
|
+
}
|
|
18958
|
+
if (!sortedIds) {
|
|
18959
|
+
sortedIds = [...taskMap.keys()];
|
|
18960
|
+
}
|
|
18934
18961
|
}
|
|
18935
18962
|
for (const taskId of sortedIds) {
|
|
18936
18963
|
const node = taskMap.get(taskId);
|
|
@@ -19214,6 +19241,19 @@ function findCycle(taskMap) {
|
|
|
19214
19241
|
return null;
|
|
19215
19242
|
}
|
|
19216
19243
|
}
|
|
19244
|
+
function breakCycle(cycle, taskMap, depOffsetMap) {
|
|
19245
|
+
if (cycle.length < 3) return;
|
|
19246
|
+
const fromId = cycle[cycle.length - 2];
|
|
19247
|
+
const toId = cycle[0];
|
|
19248
|
+
const toNode = taskMap.get(toId);
|
|
19249
|
+
if (toNode) {
|
|
19250
|
+
const idx = toNode.predecessors.indexOf(fromId);
|
|
19251
|
+
if (idx !== -1) {
|
|
19252
|
+
toNode.predecessors.splice(idx, 1);
|
|
19253
|
+
depOffsetMap.delete(`${fromId}->${toId}`);
|
|
19254
|
+
}
|
|
19255
|
+
}
|
|
19256
|
+
}
|
|
19217
19257
|
function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holidaySet) {
|
|
19218
19258
|
if (sortedIds.length === 0) return /* @__PURE__ */ new Set();
|
|
19219
19259
|
const latestEnd = /* @__PURE__ */ new Map();
|
|
@@ -19302,10 +19342,10 @@ function buildResolvedGroups(nodes, taskMap, groups, depth) {
|
|
|
19302
19342
|
if (!resolved?.startDate || !resolved?.endDate) continue;
|
|
19303
19343
|
if (resolved.startDate.getTime() < minStart) minStart = resolved.startDate.getTime();
|
|
19304
19344
|
if (resolved.endDate.getTime() > maxEnd) maxEnd = resolved.endDate.getTime();
|
|
19345
|
+
const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
|
|
19346
|
+
totalDuration += dur;
|
|
19305
19347
|
if (task.progress !== null) {
|
|
19306
|
-
const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
|
|
19307
19348
|
totalProgress += task.progress * dur;
|
|
19308
|
-
totalDuration += dur;
|
|
19309
19349
|
hasProgress = true;
|
|
19310
19350
|
}
|
|
19311
19351
|
}
|
|
@@ -19346,9 +19386,47 @@ __export(renderer_exports9, {
|
|
|
19346
19386
|
buildTagLaneRowList: () => buildTagLaneRowList,
|
|
19347
19387
|
renderGantt: () => renderGantt
|
|
19348
19388
|
});
|
|
19389
|
+
function computeBarLabel(label, x1, barWidth, innerWidth, textColor) {
|
|
19390
|
+
const textWidth = label.length * CHAR_W;
|
|
19391
|
+
const x2 = x1 + barWidth;
|
|
19392
|
+
if (textWidth < barWidth - LABEL_PAD) {
|
|
19393
|
+
return { x: x1 + 6, anchor: "start", fill: textColor, text: label };
|
|
19394
|
+
}
|
|
19395
|
+
if (x2 + LABEL_GAP + textWidth <= innerWidth) {
|
|
19396
|
+
return { x: x2 + LABEL_GAP, anchor: "start", fill: textColor, text: label };
|
|
19397
|
+
}
|
|
19398
|
+
if (x1 - LABEL_GAP - textWidth >= 0) {
|
|
19399
|
+
return { x: x1 - LABEL_GAP, anchor: "end", fill: textColor, text: label };
|
|
19400
|
+
}
|
|
19401
|
+
const availWidth = x1 - LABEL_GAP;
|
|
19402
|
+
if (availWidth > CHAR_W * 3) {
|
|
19403
|
+
const maxChars = Math.floor(availWidth / CHAR_W) - 1;
|
|
19404
|
+
return { x: x1 - LABEL_GAP, anchor: "end", fill: textColor, text: label.slice(0, maxChars) + "\u2026" };
|
|
19405
|
+
}
|
|
19406
|
+
return null;
|
|
19407
|
+
}
|
|
19408
|
+
function renderLabelBand(svg, y, leftMargin, color, palette, cssPrefix, dataAttr) {
|
|
19409
|
+
const bandX = 5;
|
|
19410
|
+
const bandW = leftMargin - 7;
|
|
19411
|
+
const bandY = y - BAR_H / 2;
|
|
19412
|
+
const clipId = `gantt-band-clip-${bandClipCounter++}`;
|
|
19413
|
+
svg.append("clipPath").attr("id", clipId).append("rect").attr("x", bandX).attr("y", bandY).attr("width", bandW).attr("height", BAR_H).attr("rx", BAND_RADIUS);
|
|
19414
|
+
const tint2 = svg.append("rect").attr("class", `gantt-${cssPrefix}-band-bg`).attr("x", bandX).attr("y", bandY).attr("width", bandW).attr("height", BAR_H).attr("rx", BAND_RADIUS).attr("fill", mix(color, palette.bg, 20)).style("pointer-events", "none");
|
|
19415
|
+
const accent = svg.append("rect").attr("class", `gantt-${cssPrefix}-band-accent`).attr("x", bandX).attr("y", bandY).attr("width", BAND_ACCENT_W).attr("height", BAR_H).attr("fill", color).attr("clip-path", `url(#${clipId})`).style("pointer-events", "none");
|
|
19416
|
+
if (dataAttr) {
|
|
19417
|
+
tint2.attr(dataAttr.key, dataAttr.value);
|
|
19418
|
+
accent.attr(dataAttr.key, dataAttr.value);
|
|
19419
|
+
}
|
|
19420
|
+
}
|
|
19421
|
+
function appendTaskIcon(textEl, label, isMilestone, iconColor, textColor) {
|
|
19422
|
+
const icon = isMilestone ? "\u25C6" : "\u25CF";
|
|
19423
|
+
textEl.append("tspan").attr("fill", iconColor).text(icon);
|
|
19424
|
+
textEl.append("tspan").attr("fill", textColor).text(" " + label);
|
|
19425
|
+
}
|
|
19349
19426
|
function renderGantt(container, resolved, palette, isDark, options, exportDims) {
|
|
19350
19427
|
container.innerHTML = "";
|
|
19351
|
-
|
|
19428
|
+
bandClipCounter = 0;
|
|
19429
|
+
if (resolved.tasks.length === 0) return;
|
|
19352
19430
|
const onClickItem = options?.onClickItem;
|
|
19353
19431
|
const collapsedGroups = options?.collapsedGroups;
|
|
19354
19432
|
const onToggleGroup = options?.onToggleGroup;
|
|
@@ -19366,10 +19444,13 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19366
19444
|
const isTagMode = tagRows !== null;
|
|
19367
19445
|
const allLabels = isTagMode ? [
|
|
19368
19446
|
...rows.filter((r) => r.type === "lane-header").map((r) => r.laneName),
|
|
19369
|
-
...rows.filter((r) => r.type === "task").map((r) => r.task.task.label)
|
|
19447
|
+
...rows.filter((r) => r.type === "task").map((r) => "\u25CF " + r.task.task.label)
|
|
19370
19448
|
] : [
|
|
19371
|
-
...resolved.tasks.map((t) => t.task.label),
|
|
19372
|
-
...resolved.groups.map((g2) =>
|
|
19449
|
+
...resolved.tasks.map((t) => "\u25CF " + t.task.label),
|
|
19450
|
+
...resolved.groups.map((g2) => {
|
|
19451
|
+
const px = g2.depth <= 2 ? g2.depth * 14 : 2 * 14 + (g2.depth - 2) * 8;
|
|
19452
|
+
return " ".repeat(Math.ceil(px / 7)) + g2.name;
|
|
19453
|
+
})
|
|
19373
19454
|
];
|
|
19374
19455
|
const maxLabelLen = Math.max(...allLabels.map((l) => l.length), 10);
|
|
19375
19456
|
const leftMargin = Math.max(MIN_LEFT_MARGIN, maxLabelLen * 7 + 30);
|
|
@@ -19378,9 +19459,12 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19378
19459
|
const titleHeight = title ? 50 : 20;
|
|
19379
19460
|
const tagLegendReserve = resolved.tagGroups.length > 0 ? LEGEND_HEIGHT + 8 : 0;
|
|
19380
19461
|
const topDateLabelReserve = 22;
|
|
19381
|
-
const
|
|
19462
|
+
const hasOverheadLabels = resolved.markers.length > 0 || resolved.eras.length > 0;
|
|
19463
|
+
const markerLabelReserve = hasOverheadLabels ? 18 : 0;
|
|
19464
|
+
const CONTENT_TOP_PAD = 16;
|
|
19465
|
+
const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve + markerLabelReserve;
|
|
19382
19466
|
const contentH = isTagMode ? totalRows * (BAR_H + ROW_GAP) : totalRows * (BAR_H + ROW_GAP) + GROUP_GAP2 * resolved.groups.length;
|
|
19383
|
-
const innerHeight = contentH;
|
|
19467
|
+
const innerHeight = CONTENT_TOP_PAD + contentH;
|
|
19384
19468
|
const outerHeight = marginTop + innerHeight + BOTTOM_MARGIN;
|
|
19385
19469
|
const containerWidth = exportDims?.width ?? (container.clientWidth || 800);
|
|
19386
19470
|
const innerWidth = containerWidth - leftMargin - RIGHT_MARGIN;
|
|
@@ -19406,6 +19490,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19406
19490
|
isDark,
|
|
19407
19491
|
hasCriticalPath,
|
|
19408
19492
|
criticalPathActive,
|
|
19493
|
+
resolved.options.optionLineNumbers,
|
|
19409
19494
|
(groupName) => {
|
|
19410
19495
|
currentActiveGroup = currentActiveGroup?.toLowerCase() === groupName.toLowerCase() ? null : groupName;
|
|
19411
19496
|
if (onActiveGroupChange) onActiveGroupChange(currentActiveGroup);
|
|
@@ -19418,7 +19503,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19418
19503
|
},
|
|
19419
19504
|
currentSwimlaneGroup,
|
|
19420
19505
|
onSwimlaneChange,
|
|
19421
|
-
viewMode
|
|
19506
|
+
viewMode,
|
|
19507
|
+
resolved.tasks
|
|
19422
19508
|
);
|
|
19423
19509
|
}
|
|
19424
19510
|
}
|
|
@@ -19443,7 +19529,25 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19443
19529
|
renderTimeScaleHorizontal(g, xScale, innerWidth, innerHeight, palette.text);
|
|
19444
19530
|
renderWeekendBands(g, resolved, xScale, innerHeight, palette, isDark);
|
|
19445
19531
|
renderHolidayBands(g, svg, resolved, xScale, innerHeight, palette, isDark, marginTop - 4, leftMargin, onClickItem);
|
|
19446
|
-
renderErasAndMarkers(g, resolved, xScale, innerHeight, palette);
|
|
19532
|
+
renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette);
|
|
19533
|
+
let todayDate = null;
|
|
19534
|
+
let todayX = -1;
|
|
19535
|
+
const todayColor = palette.accent || "#e74c3c";
|
|
19536
|
+
const todayMarkerLineNum = resolved.options.optionLineNumbers["today-marker"];
|
|
19537
|
+
if (resolved.options.todayMarker !== "off") {
|
|
19538
|
+
if (resolved.options.todayMarker === "on") {
|
|
19539
|
+
todayDate = /* @__PURE__ */ new Date();
|
|
19540
|
+
} else {
|
|
19541
|
+
todayDate = /* @__PURE__ */ new Date(resolved.options.todayMarker + "T00:00:00");
|
|
19542
|
+
}
|
|
19543
|
+
todayX = xScale(dateToFractionalYear(todayDate));
|
|
19544
|
+
if (todayX >= 0 && todayX <= innerWidth) {
|
|
19545
|
+
const todayLine = g.append("line").attr("class", "gantt-today").attr("x1", todayX).attr("y1", 0).attr("x2", todayX).attr("y2", innerHeight + 10).attr("stroke", todayColor).attr("stroke-width", 2).attr("stroke-dasharray", "6 4").attr("opacity", 0.7).attr("pointer-events", "none");
|
|
19546
|
+
if (todayMarkerLineNum) todayLine.attr("data-line-number", String(todayMarkerLineNum));
|
|
19547
|
+
const todayLabel = g.append("text").attr("class", "gantt-today").attr("x", todayX).attr("y", innerHeight + 24).attr("text-anchor", "middle").attr("font-size", "10px").attr("fill", todayColor).attr("opacity", 0.7).attr("pointer-events", "none").text("Today");
|
|
19548
|
+
if (todayMarkerLineNum) todayLabel.attr("data-line-number", String(todayMarkerLineNum));
|
|
19549
|
+
}
|
|
19550
|
+
}
|
|
19447
19551
|
const taskPositions = /* @__PURE__ */ new Map();
|
|
19448
19552
|
const groupPositions = /* @__PURE__ */ new Map();
|
|
19449
19553
|
const lanePositions = /* @__PURE__ */ new Map();
|
|
@@ -19464,7 +19568,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19464
19568
|
}
|
|
19465
19569
|
}
|
|
19466
19570
|
}
|
|
19467
|
-
let yOffset =
|
|
19571
|
+
let yOffset = CONTENT_TOP_PAD;
|
|
19468
19572
|
for (const row of rows) {
|
|
19469
19573
|
if (row.type === "lane-header") {
|
|
19470
19574
|
const laneColor = row.laneColor === "#999999" ? palette.textMuted : row.laneColor;
|
|
@@ -19479,6 +19583,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19479
19583
|
laneBarWidth = Math.max(lx2 - lx1, 2);
|
|
19480
19584
|
}
|
|
19481
19585
|
lanePositions.set(row.laneName, { x1: lx1, x2: lx1 + laneBarWidth, y: yOffset + BAR_H / 2 });
|
|
19586
|
+
renderLabelBand(svg, marginTop + yOffset + BAR_H / 2, leftMargin, laneColor, palette, "lane", { key: "data-lane", value: row.laneName });
|
|
19482
19587
|
const labelG = svg.append("g").attr("class", "gantt-lane-header").attr(`data-tag-${row.tagKey}`, row.laneName.toLowerCase()).attr("data-lane", row.laneName).style("cursor", onToggleLane ? "pointer" : "default").on("click", () => {
|
|
19483
19588
|
if (onToggleLane) onToggleLane(row.laneName);
|
|
19484
19589
|
}).on("mouseenter", () => {
|
|
@@ -19504,7 +19609,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19504
19609
|
});
|
|
19505
19610
|
laneBandG.append("rect").attr("class", "gantt-lane-band").attr("x", lx1).attr("y", yOffset).attr("width", laneBarWidth).attr("height", BAR_H).attr("rx", 4).attr("fill", barFill).attr("stroke", laneColor).attr("stroke-width", 2);
|
|
19506
19611
|
if (row.aggregateProgress !== null && row.aggregateProgress > 0) {
|
|
19507
|
-
laneBandG.append("rect").attr("class", "gantt-lane-progress").attr("x", lx1).attr("y", yOffset).attr("width", laneBarWidth * Math.min(row.aggregateProgress / 100, 1)).attr("height", BAR_H).attr("
|
|
19612
|
+
laneBandG.append("rect").attr("class", "gantt-lane-progress").attr("x", lx1).attr("y", yOffset).attr("width", laneBarWidth * Math.min(row.aggregateProgress / 100, 1)).attr("height", BAR_H).attr("fill", laneColor).attr("opacity", 0.5).attr("pointer-events", "none");
|
|
19508
19613
|
}
|
|
19509
19614
|
}
|
|
19510
19615
|
yOffset += BAR_H + ROW_GAP;
|
|
@@ -19513,7 +19618,9 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19513
19618
|
const isCollapsed = collapsedGroups?.has(group.name) ?? false;
|
|
19514
19619
|
const indent = " ".repeat(group.depth);
|
|
19515
19620
|
const toggleIcon = isCollapsed ? "\u25BA" : "\u25BC";
|
|
19516
|
-
const
|
|
19621
|
+
const tagColor = resolveTagColor(group.metadata, resolved.tagGroups, currentActiveGroup, true);
|
|
19622
|
+
const groupColor = tagColor && tagColor !== "#999999" ? tagColor : group.color || palette.textMuted;
|
|
19623
|
+
renderLabelBand(svg, marginTop + yOffset + BAR_H / 2, leftMargin, groupColor, palette, "group", { key: "data-group", value: group.name });
|
|
19517
19624
|
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
19625
|
if (onToggleGroup) onToggleGroup(group.name);
|
|
19519
19626
|
}).on("mouseenter", () => {
|
|
@@ -19523,7 +19630,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19523
19630
|
resetHighlight(g, svg);
|
|
19524
19631
|
hideGanttDateIndicators(g);
|
|
19525
19632
|
});
|
|
19526
|
-
const
|
|
19633
|
+
const groupIndent = group.depth <= 2 ? group.depth * 14 : 2 * 14 + (group.depth - 2) * 8;
|
|
19634
|
+
const labelX = 10 + groupIndent;
|
|
19527
19635
|
labelG.append("text").attr("x", labelX).attr("y", marginTop + yOffset + BAR_H / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", "11px").attr("font-weight", "bold").attr("fill", palette.text).text(toggleIcon + " " + group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : ""));
|
|
19528
19636
|
const gStart = dateToFractionalYear(group.startDate);
|
|
19529
19637
|
const gEnd = dateToFractionalYear(group.endDate);
|
|
@@ -19541,7 +19649,12 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19541
19649
|
});
|
|
19542
19650
|
summaryG.append("rect").attr("x", gx1).attr("y", yOffset).attr("width", barWidth).attr("height", BAR_H).attr("rx", 4).attr("fill", mix(groupColor, palette.bg, 30)).attr("stroke", groupColor).attr("stroke-width", 2);
|
|
19543
19651
|
if (group.progress !== null && group.progress > 0) {
|
|
19544
|
-
summaryG.append("rect").attr("x", gx1).attr("y", yOffset).attr("width", barWidth * Math.min(group.progress / 100, 1)).attr("height", BAR_H).attr("
|
|
19652
|
+
summaryG.append("rect").attr("x", gx1).attr("y", yOffset).attr("width", barWidth * Math.min(group.progress / 100, 1)).attr("height", BAR_H).attr("fill", groupColor).attr("opacity", 0.5);
|
|
19653
|
+
}
|
|
19654
|
+
const summaryLabel = group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : "");
|
|
19655
|
+
const summaryPlacement = computeBarLabel(summaryLabel, gx1, barWidth, innerWidth, palette.text);
|
|
19656
|
+
if (summaryPlacement) {
|
|
19657
|
+
summaryG.append("text").attr("x", summaryPlacement.x).attr("y", yOffset + BAR_H / 2).attr("dy", "0.35em").attr("font-size", "10px").attr("font-weight", "bold").attr("text-anchor", summaryPlacement.anchor).attr("fill", summaryPlacement.fill).attr("pointer-events", "none").text(summaryPlacement.text);
|
|
19545
19658
|
}
|
|
19546
19659
|
groupPositions.set(group.name, { x1: gx1, x2: gx1 + barWidth, y: yOffset + BAR_H / 2 });
|
|
19547
19660
|
} else {
|
|
@@ -19556,7 +19669,12 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19556
19669
|
});
|
|
19557
19670
|
groupBarG.append("rect").attr("x", gx1).attr("y", yOffset).attr("width", groupBarWidth).attr("height", BAR_H).attr("rx", 4).attr("fill", bandFill).attr("stroke", groupColor).attr("stroke-width", 2);
|
|
19558
19671
|
if (group.progress !== null && group.progress > 0) {
|
|
19559
|
-
groupBarG.append("rect").attr("class", "gantt-group-progress").attr("x", gx1).attr("y", yOffset).attr("width", groupBarWidth * Math.min(group.progress / 100, 1)).attr("height", BAR_H).attr("
|
|
19672
|
+
groupBarG.append("rect").attr("class", "gantt-group-progress").attr("x", gx1).attr("y", yOffset).attr("width", groupBarWidth * Math.min(group.progress / 100, 1)).attr("height", BAR_H).attr("fill", groupColor).attr("opacity", 0.5);
|
|
19673
|
+
}
|
|
19674
|
+
const expandedLabel = group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : "");
|
|
19675
|
+
const expandedPlacement = computeBarLabel(expandedLabel, gx1, groupBarWidth, innerWidth, palette.text);
|
|
19676
|
+
if (expandedPlacement) {
|
|
19677
|
+
groupBarG.append("text").attr("x", expandedPlacement.x).attr("y", yOffset + BAR_H / 2).attr("dy", "0.35em").attr("font-size", "10px").attr("font-weight", "bold").attr("text-anchor", expandedPlacement.anchor).attr("fill", expandedPlacement.fill).attr("pointer-events", "none").text(expandedPlacement.text);
|
|
19560
19678
|
}
|
|
19561
19679
|
}
|
|
19562
19680
|
}
|
|
@@ -19564,33 +19682,40 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19564
19682
|
} else if (row.type === "task") {
|
|
19565
19683
|
const rt = row.task;
|
|
19566
19684
|
const task = rt.task;
|
|
19567
|
-
const
|
|
19685
|
+
const barColor = resolveTaskColor(rt, currentActiveGroup, resolved, seriesColors2, palette);
|
|
19686
|
+
const depth = rt.groupPath.length;
|
|
19687
|
+
const indent = depth <= 2 ? depth * 14 : 2 * 14 + (depth - 2) * 8;
|
|
19688
|
+
const taskLabelX = isTagMode ? 20 : 6 + indent;
|
|
19568
19689
|
const topGroup = rt.groupPath.length > 0 ? rt.groupPath[0] : null;
|
|
19569
|
-
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").
|
|
19690
|
+
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").on("click", () => {
|
|
19570
19691
|
if (onClickItem) onClickItem(task.lineNumber);
|
|
19571
19692
|
}).on("mouseenter", () => {
|
|
19572
|
-
|
|
19693
|
+
if (rt.isMilestone) {
|
|
19694
|
+
highlightMilestone(g, svg, task.id);
|
|
19695
|
+
} else {
|
|
19696
|
+
highlightTask(g, svg, task.id);
|
|
19697
|
+
}
|
|
19573
19698
|
}).on("mouseleave", () => {
|
|
19574
19699
|
resetHighlight(g, svg);
|
|
19575
19700
|
});
|
|
19701
|
+
appendTaskIcon(taskLabel, task.label, rt.isMilestone, barColor, palette.text);
|
|
19576
19702
|
for (const [key, value] of Object.entries(rt.effectiveMetadata)) {
|
|
19577
19703
|
taskLabel.attr(`data-tag-${key}`, value.toLowerCase());
|
|
19578
19704
|
}
|
|
19579
19705
|
if (rt.isCriticalPath) {
|
|
19580
19706
|
taskLabel.attr("data-critical-path", "true");
|
|
19581
19707
|
}
|
|
19582
|
-
let barColor = resolveTaskColor(rt, currentActiveGroup, resolved, seriesColors2, palette);
|
|
19583
19708
|
if (rt.isMilestone) {
|
|
19584
19709
|
const mx = xScale(dateToFractionalYear(rt.startDate));
|
|
19585
19710
|
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", () => {
|
|
19711
|
+
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
19712
|
if (onClickItem) onClickItem(task.lineNumber);
|
|
19588
19713
|
}).on("mouseenter", () => {
|
|
19589
|
-
|
|
19714
|
+
highlightMilestone(g, svg, task.id);
|
|
19590
19715
|
showGanttDateIndicators(g, xScale, rt.startDate, null, innerHeight, barColor);
|
|
19591
19716
|
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
19717
|
}).on("mouseleave", () => {
|
|
19593
|
-
|
|
19718
|
+
resetHighlight(g, svg);
|
|
19594
19719
|
hideGanttDateIndicators(g);
|
|
19595
19720
|
g.selectAll(".gantt-milestone-hover-label").remove();
|
|
19596
19721
|
});
|
|
@@ -19657,32 +19782,40 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19657
19782
|
progGrad.append("stop").attr("offset", "100%").attr("stop-color", barColor).attr("stop-opacity", 0);
|
|
19658
19783
|
progressFill = `url(#${progGradId})`;
|
|
19659
19784
|
}
|
|
19660
|
-
taskG.append("rect").attr("class", "gantt-progress").attr("x", x1).attr("y", yOffset).attr("width", progressWidth).attr("height", BAR_H).attr("
|
|
19785
|
+
taskG.append("rect").attr("class", "gantt-progress").attr("x", x1).attr("y", yOffset).attr("width", progressWidth).attr("height", BAR_H).attr("fill", progressFill).attr("opacity", 0.5);
|
|
19661
19786
|
}
|
|
19662
19787
|
if (rt.isCriticalPath) {
|
|
19663
19788
|
taskG.attr("data-critical-path", "true");
|
|
19664
19789
|
}
|
|
19665
|
-
const
|
|
19666
|
-
if (
|
|
19667
|
-
taskG.append("text").attr("x",
|
|
19790
|
+
const labelPlacement = computeBarLabel(task.label, x1, barWidth, innerWidth, palette.text);
|
|
19791
|
+
if (labelPlacement) {
|
|
19792
|
+
taskG.append("text").attr("x", labelPlacement.x).attr("y", yOffset + BAR_H / 2).attr("dy", "0.35em").attr("font-size", "10px").attr("text-anchor", labelPlacement.anchor).attr("fill", labelPlacement.fill).attr("pointer-events", "none").text(labelPlacement.text);
|
|
19668
19793
|
}
|
|
19669
19794
|
taskPositions.set(task.id, { x1, x2: x1 + barWidth, y: yOffset + BAR_H / 2 });
|
|
19670
19795
|
}
|
|
19671
19796
|
yOffset += BAR_H + ROW_GAP;
|
|
19672
19797
|
}
|
|
19673
19798
|
}
|
|
19674
|
-
if (
|
|
19675
|
-
|
|
19676
|
-
|
|
19677
|
-
|
|
19678
|
-
|
|
19679
|
-
|
|
19680
|
-
|
|
19681
|
-
|
|
19682
|
-
|
|
19683
|
-
|
|
19684
|
-
|
|
19685
|
-
|
|
19799
|
+
if (todayDate && todayX >= 0 && todayX <= innerWidth) {
|
|
19800
|
+
const todayHoverG = g.append("g").attr("class", "gantt-today-hover").style("cursor", "pointer");
|
|
19801
|
+
todayHoverG.append("rect").attr("x", todayX - 10).attr("y", -6).attr("width", 20).attr("height", innerHeight + 16).attr("fill", "transparent").attr("pointer-events", "all");
|
|
19802
|
+
const todayDateObj = todayDate;
|
|
19803
|
+
todayHoverG.on("mouseenter", () => {
|
|
19804
|
+
g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
|
|
19805
|
+
g.selectAll(".gantt-milestone").attr("opacity", FADE_OPACITY);
|
|
19806
|
+
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
19807
|
+
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
19808
|
+
svg.selectAll(".gantt-task-label").attr("opacity", FADE_OPACITY);
|
|
19809
|
+
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
19810
|
+
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
19811
|
+
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
19812
|
+
g.selectAll(".gantt-era-group").attr("opacity", FADE_OPACITY);
|
|
19813
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
19814
|
+
showGanttDateIndicators(g, xScale, todayDateObj, null, innerHeight, todayColor);
|
|
19815
|
+
}).on("mouseleave", () => {
|
|
19816
|
+
resetHighlight(g, svg);
|
|
19817
|
+
hideGanttDateIndicators(g);
|
|
19818
|
+
});
|
|
19686
19819
|
}
|
|
19687
19820
|
if (resolved.options.dependencies) {
|
|
19688
19821
|
renderDependencyArrows(g, resolved, taskPositions, groupPositions, collapsedGroups, palette, isDark, isTagMode, lanePositions, collapsedLanes, taskLaneMap);
|
|
@@ -19799,10 +19932,10 @@ function renderDependencyArrows(g, resolved, taskPositions, groupPositions, coll
|
|
|
19799
19932
|
const path = `M ${sx} ${sy} C ${sx + cpOffset} ${sy}, ${tx - cpOffset} ${ty}, ${tx} ${ty}`;
|
|
19800
19933
|
const arrowColor = mix(palette.text, palette.bg, 50);
|
|
19801
19934
|
const isCpArrow = rt.isCriticalPath && targetTask.isCriticalPath;
|
|
19802
|
-
g.append("path").attr("class", "gantt-dep-arrow").attr("data-dep-from", rt.task.id).attr("data-dep-to", targetTask.task.id).attr("data-critical-path", isCpArrow ? "true" : null).attr("d", path).attr("fill", "none").attr("stroke", arrowColor).attr("stroke-width", 1.5).attr("opacity", 0.5);
|
|
19935
|
+
g.append("path").attr("class", "gantt-dep-arrow").attr("data-dep-from", rt.task.id).attr("data-dep-to", targetTask.task.id).attr("data-line-number", String(dep.lineNumber)).attr("data-critical-path", isCpArrow ? "true" : null).attr("d", path).attr("fill", "none").attr("stroke", arrowColor).attr("stroke-width", 1.5).attr("opacity", 0.5);
|
|
19803
19936
|
const headSize = 5;
|
|
19804
19937
|
const angle = 0;
|
|
19805
|
-
g.append("polygon").attr("class", "gantt-dep-arrowhead").attr("data-dep-from", rt.task.id).attr("data-dep-to", targetTask.task.id).attr("data-critical-path", isCpArrow ? "true" : null).attr("points", arrowheadPoints(tx, ty, headSize, angle)).attr("fill", arrowColor).attr("opacity", 0.5);
|
|
19938
|
+
g.append("polygon").attr("class", "gantt-dep-arrowhead").attr("data-dep-from", rt.task.id).attr("data-dep-to", targetTask.task.id).attr("data-line-number", String(dep.lineNumber)).attr("data-critical-path", isCpArrow ? "true" : null).attr("points", arrowheadPoints(tx, ty, headSize, angle)).attr("fill", arrowColor).attr("opacity", 0.5);
|
|
19806
19939
|
}
|
|
19807
19940
|
}
|
|
19808
19941
|
}
|
|
@@ -19823,7 +19956,9 @@ function applyCriticalPathHighlight(svg, chartG) {
|
|
|
19823
19956
|
el.attr("opacity", el.attr("data-critical-path") === "true" ? 1 : FADE_OPACITY);
|
|
19824
19957
|
});
|
|
19825
19958
|
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
19959
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
|
|
19826
19960
|
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
19961
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
|
|
19827
19962
|
chartG.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", FADE_OPACITY);
|
|
19828
19963
|
chartG.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").each(function() {
|
|
19829
19964
|
const el = d3Selection10.select(this);
|
|
@@ -19835,7 +19970,9 @@ function resetHighlightAll(svg, chartG) {
|
|
|
19835
19970
|
chartG.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", 1);
|
|
19836
19971
|
svg.selectAll(".gantt-task-label").attr("opacity", 1);
|
|
19837
19972
|
svg.selectAll(".gantt-group-label").attr("opacity", 1);
|
|
19973
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", 1);
|
|
19838
19974
|
svg.selectAll(".gantt-lane-header").attr("opacity", 1);
|
|
19975
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", 1);
|
|
19839
19976
|
chartG.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", 1);
|
|
19840
19977
|
chartG.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", 0.5);
|
|
19841
19978
|
}
|
|
@@ -19851,7 +19988,7 @@ function drawSwimlaneIcon(parent, x, y, isActive, palette) {
|
|
|
19851
19988
|
}
|
|
19852
19989
|
return iconG;
|
|
19853
19990
|
}
|
|
19854
|
-
function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode) {
|
|
19991
|
+
function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, optionLineNumbers, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode, resolvedTasks) {
|
|
19855
19992
|
const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
|
|
19856
19993
|
let visibleGroups;
|
|
19857
19994
|
if (activeGroupName) {
|
|
@@ -19861,6 +19998,28 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19861
19998
|
} else {
|
|
19862
19999
|
visibleGroups = tagGroups;
|
|
19863
20000
|
}
|
|
20001
|
+
const usedValues = /* @__PURE__ */ new Map();
|
|
20002
|
+
if (resolvedTasks) {
|
|
20003
|
+
for (const group of visibleGroups) {
|
|
20004
|
+
const key = group.name.toLowerCase();
|
|
20005
|
+
const used = /* @__PURE__ */ new Set();
|
|
20006
|
+
for (const rt of resolvedTasks) {
|
|
20007
|
+
const val = rt.effectiveMetadata[key];
|
|
20008
|
+
if (val) used.add(val.toLowerCase());
|
|
20009
|
+
}
|
|
20010
|
+
usedValues.set(key, used);
|
|
20011
|
+
}
|
|
20012
|
+
}
|
|
20013
|
+
const filteredEntries = /* @__PURE__ */ new Map();
|
|
20014
|
+
for (const group of visibleGroups) {
|
|
20015
|
+
const key = group.name.toLowerCase();
|
|
20016
|
+
const used = usedValues.get(key);
|
|
20017
|
+
if (used && used.size > 0) {
|
|
20018
|
+
filteredEntries.set(key, group.entries.filter((e) => used.has(e.value.toLowerCase())));
|
|
20019
|
+
} else {
|
|
20020
|
+
filteredEntries.set(key, group.entries);
|
|
20021
|
+
}
|
|
20022
|
+
}
|
|
19864
20023
|
const groupWidths = [];
|
|
19865
20024
|
let totalW = 0;
|
|
19866
20025
|
for (const group of visibleGroups) {
|
|
@@ -19871,8 +20030,9 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19871
20030
|
const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD + iconReserve;
|
|
19872
20031
|
let groupW = pillW;
|
|
19873
20032
|
if (isActive) {
|
|
20033
|
+
const entries = filteredEntries.get(group.name.toLowerCase()) ?? group.entries;
|
|
19874
20034
|
let entriesW = 0;
|
|
19875
|
-
for (const entry of
|
|
20035
|
+
for (const entry of entries) {
|
|
19876
20036
|
entriesW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
|
|
19877
20037
|
}
|
|
19878
20038
|
groupW = LEGEND_CAPSULE_PAD * 2 + pillW + 4 + entriesW;
|
|
@@ -19889,7 +20049,8 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19889
20049
|
if (visibleGroups.length > 0) totalW += LEGEND_GROUP_GAP;
|
|
19890
20050
|
totalW += cpPillW;
|
|
19891
20051
|
}
|
|
19892
|
-
const
|
|
20052
|
+
const containerWidth = chartLeftMargin + chartInnerWidth + RIGHT_MARGIN;
|
|
20053
|
+
const legendX = (containerWidth - totalW) / 2;
|
|
19893
20054
|
const legendRow = svg.append("g").attr("class", "gantt-tag-legend-container").attr("transform", `translate(${legendX}, ${legendY})`);
|
|
19894
20055
|
let cursorX = 0;
|
|
19895
20056
|
for (let i = 0; i < visibleGroups.length; i++) {
|
|
@@ -19901,7 +20062,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19901
20062
|
const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD + iconReserve;
|
|
19902
20063
|
const pillH = isActive ? LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2 : LEGEND_HEIGHT;
|
|
19903
20064
|
const groupW = groupWidths[i];
|
|
19904
|
-
const gEl = legendRow.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "gantt-tag-legend-group").attr("data-tag-group", group.name).style("cursor", "pointer").on("click", () => {
|
|
20065
|
+
const gEl = legendRow.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "gantt-tag-legend-group").attr("data-tag-group", group.name).attr("data-line-number", String(group.lineNumber)).style("cursor", "pointer").on("click", () => {
|
|
19905
20066
|
if (onToggle) onToggle(group.name);
|
|
19906
20067
|
});
|
|
19907
20068
|
if (isActive) {
|
|
@@ -19931,10 +20092,11 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19931
20092
|
}
|
|
19932
20093
|
if (isActive) {
|
|
19933
20094
|
const tagKey = group.name.toLowerCase();
|
|
20095
|
+
const entries = filteredEntries.get(tagKey) ?? group.entries;
|
|
19934
20096
|
let ex = pillXOff + pillW + LEGEND_CAPSULE_PAD + 4;
|
|
19935
|
-
for (const entry of
|
|
20097
|
+
for (const entry of entries) {
|
|
19936
20098
|
const entryValue = entry.value.toLowerCase();
|
|
19937
|
-
const entryG = gEl.append("g").attr("class", "gantt-legend-entry").style("cursor", "pointer");
|
|
20099
|
+
const entryG = gEl.append("g").attr("class", "gantt-legend-entry").attr("data-line-number", String(entry.lineNumber)).style("cursor", "pointer");
|
|
19938
20100
|
entryG.append("circle").attr("cx", ex + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
|
|
19939
20101
|
entryG.append("text").attr("x", ex + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP).attr("y", LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 2).attr("text-anchor", "start").attr("font-size", `${LEGEND_ENTRY_FONT_SIZE}px`).attr("fill", palette.textMuted).text(entry.value);
|
|
19940
20102
|
entryG.on("mouseenter", () => {
|
|
@@ -19970,9 +20132,11 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19970
20132
|
cursorX += groupW + LEGEND_GROUP_GAP;
|
|
19971
20133
|
}
|
|
19972
20134
|
if (hasCriticalPath) {
|
|
20135
|
+
const cpLineNum = optionLineNumbers["critical-path"];
|
|
19973
20136
|
const cpG = legendRow.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "gantt-legend-critical-path").style("cursor", "pointer").on("click", () => {
|
|
19974
20137
|
if (onToggleCriticalPath) onToggleCriticalPath();
|
|
19975
20138
|
});
|
|
20139
|
+
if (cpLineNum) cpG.attr("data-line-number", String(cpLineNum));
|
|
19976
20140
|
cpG.append("rect").attr("width", cpPillW).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", criticalPathActive ? palette.bg : groupBg);
|
|
19977
20141
|
if (criticalPathActive) {
|
|
19978
20142
|
cpG.append("rect").attr("width", cpPillW).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
|
|
@@ -19990,7 +20154,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19990
20154
|
});
|
|
19991
20155
|
}
|
|
19992
20156
|
}
|
|
19993
|
-
function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
|
|
20157
|
+
function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
|
|
19994
20158
|
for (let i = 0; i < resolved.eras.length; i++) {
|
|
19995
20159
|
const era = resolved.eras[i];
|
|
19996
20160
|
const color = era.color || ERA_COLORS[i % ERA_COLORS.length];
|
|
@@ -20001,13 +20165,23 @@ function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
|
|
|
20001
20165
|
const hoverEraOpacity = 0.16;
|
|
20002
20166
|
const eraStartDate = parseDateStringToDate(era.startDate);
|
|
20003
20167
|
const eraEndDate = parseDateStringToDate(era.endDate);
|
|
20004
|
-
const eraG = g.append("g").attr("class", "gantt-era-group");
|
|
20168
|
+
const eraG = g.append("g").attr("class", "gantt-era-group").attr("data-line-number", String(era.lineNumber));
|
|
20005
20169
|
const eraRect = eraG.append("rect").attr("class", "gantt-era").attr("x", sx).attr("y", 0).attr("width", ex - sx).attr("height", innerHeight).attr("fill", color).attr("opacity", baseEraOpacity);
|
|
20006
|
-
eraG.append("text").attr("class", "gantt-era-label").attr("x", (sx + ex) / 2).attr("y",
|
|
20170
|
+
eraG.append("text").attr("class", "gantt-era-label").attr("x", (sx + ex) / 2).attr("y", -24).attr("text-anchor", "middle").attr("font-size", "10px").attr("fill", color).attr("opacity", 0.7).style("cursor", "pointer").text(era.label);
|
|
20007
20171
|
eraG.on("mouseenter", () => {
|
|
20172
|
+
g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
|
|
20173
|
+
g.selectAll(".gantt-milestone").attr("opacity", FADE_OPACITY);
|
|
20174
|
+
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20175
|
+
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20176
|
+
svg.selectAll(".gantt-task-label").attr("opacity", FADE_OPACITY);
|
|
20177
|
+
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20178
|
+
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
20179
|
+
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
20180
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20008
20181
|
eraRect.attr("opacity", hoverEraOpacity);
|
|
20009
20182
|
showGanttDateIndicators(g, xScale, eraStartDate, eraEndDate, innerHeight, color);
|
|
20010
20183
|
}).on("mouseleave", () => {
|
|
20184
|
+
resetHighlight(g, svg);
|
|
20011
20185
|
eraRect.attr("opacity", baseEraOpacity);
|
|
20012
20186
|
hideGanttDateIndicators(g);
|
|
20013
20187
|
});
|
|
@@ -20016,13 +20190,35 @@ function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
|
|
|
20016
20190
|
const color = marker.color || palette.accent || "#d08770";
|
|
20017
20191
|
const mx = xScale(parseDateToFractionalYear(marker.date));
|
|
20018
20192
|
const markerDate = parseDateStringToDate(marker.date);
|
|
20019
|
-
const
|
|
20020
|
-
|
|
20021
|
-
|
|
20022
|
-
markerG.append("
|
|
20193
|
+
const diamondSize = 5;
|
|
20194
|
+
const labelY = -24;
|
|
20195
|
+
const diamondY = labelY + 14;
|
|
20196
|
+
const markerG = g.append("g").attr("class", "gantt-marker-group").attr("data-line-number", String(marker.lineNumber)).style("cursor", "pointer");
|
|
20197
|
+
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");
|
|
20198
|
+
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);
|
|
20199
|
+
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);
|
|
20200
|
+
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);
|
|
20201
|
+
const markerLine = markerG.select(".gantt-marker");
|
|
20202
|
+
const markerDiamond = markerG.select("path");
|
|
20023
20203
|
markerG.on("mouseenter", () => {
|
|
20024
|
-
|
|
20204
|
+
g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
|
|
20205
|
+
g.selectAll(".gantt-milestone").attr("opacity", FADE_OPACITY);
|
|
20206
|
+
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20207
|
+
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20208
|
+
svg.selectAll(".gantt-task-label").attr("opacity", FADE_OPACITY);
|
|
20209
|
+
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20210
|
+
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
20211
|
+
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
20212
|
+
g.selectAll(".gantt-era-group").attr("opacity", FADE_OPACITY);
|
|
20213
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20214
|
+
markerG.attr("opacity", 1);
|
|
20215
|
+
markerLine.attr("opacity", 0.8);
|
|
20216
|
+
markerDiamond.attr("opacity", 0);
|
|
20217
|
+
showGanttDateIndicators(g, xScale, markerDate, null, innerHeight, color, { skipStartLine: true });
|
|
20025
20218
|
}).on("mouseleave", () => {
|
|
20219
|
+
resetHighlight(g, svg);
|
|
20220
|
+
markerLine.attr("opacity", 0.5);
|
|
20221
|
+
markerDiamond.attr("opacity", 0.9);
|
|
20026
20222
|
hideGanttDateIndicators(g);
|
|
20027
20223
|
});
|
|
20028
20224
|
}
|
|
@@ -20035,11 +20231,7 @@ function parseDateStringToDate(s) {
|
|
|
20035
20231
|
return new Date(year, month, day);
|
|
20036
20232
|
}
|
|
20037
20233
|
function parseDateToFractionalYear(s) {
|
|
20038
|
-
|
|
20039
|
-
const year = parts[0];
|
|
20040
|
-
const month = parts.length >= 2 ? parts[1] : 1;
|
|
20041
|
-
const day = parts.length >= 3 ? parts[2] : 1;
|
|
20042
|
-
return year + (month - 1) / 12 + (day - 1) / 365;
|
|
20234
|
+
return dateToFractionalYear(parseDateStringToDate(s));
|
|
20043
20235
|
}
|
|
20044
20236
|
function highlightDeps(g, svg, taskId, resolved) {
|
|
20045
20237
|
const related = /* @__PURE__ */ new Set([taskId]);
|
|
@@ -20073,6 +20265,7 @@ function highlightDeps(g, svg, taskId, resolved) {
|
|
|
20073
20265
|
const isRelated = from && related.has(from) || to && related.has(to);
|
|
20074
20266
|
el.attr("opacity", isRelated ? 0.5 : FADE_OPACITY);
|
|
20075
20267
|
});
|
|
20268
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20076
20269
|
}
|
|
20077
20270
|
function highlightGroup(g, svg, groupName) {
|
|
20078
20271
|
g.selectAll(".gantt-task").each(function() {
|
|
@@ -20095,8 +20288,14 @@ function highlightGroup(g, svg, groupName) {
|
|
|
20095
20288
|
const el = d3Selection10.select(this);
|
|
20096
20289
|
el.attr("opacity", el.attr("data-group") === groupName ? 1 : FADE_OPACITY);
|
|
20097
20290
|
});
|
|
20291
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").each(function() {
|
|
20292
|
+
const el = d3Selection10.select(this);
|
|
20293
|
+
el.attr("opacity", el.attr("data-group") === groupName ? 1 : FADE_OPACITY);
|
|
20294
|
+
});
|
|
20098
20295
|
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20296
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
|
|
20099
20297
|
g.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", FADE_OPACITY);
|
|
20298
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20100
20299
|
}
|
|
20101
20300
|
function highlightLane(g, svg, tagKey, laneName) {
|
|
20102
20301
|
const tagAttr = `data-tag-${tagKey}`;
|
|
@@ -20121,8 +20320,14 @@ function highlightLane(g, svg, tagKey, laneName) {
|
|
|
20121
20320
|
const el = d3Selection10.select(this);
|
|
20122
20321
|
el.attr("opacity", el.attr("data-lane") === laneName ? 1 : FADE_OPACITY);
|
|
20123
20322
|
});
|
|
20323
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").each(function() {
|
|
20324
|
+
const el = d3Selection10.select(this);
|
|
20325
|
+
el.attr("opacity", el.attr("data-lane") === laneName ? 1 : FADE_OPACITY);
|
|
20326
|
+
});
|
|
20124
20327
|
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20125
20328
|
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20329
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
|
|
20330
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20126
20331
|
}
|
|
20127
20332
|
function highlightTask(g, svg, taskId) {
|
|
20128
20333
|
g.selectAll(".gantt-task").each(function() {
|
|
@@ -20136,9 +20341,31 @@ function highlightTask(g, svg, taskId) {
|
|
|
20136
20341
|
});
|
|
20137
20342
|
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20138
20343
|
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20344
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
|
|
20139
20345
|
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20346
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
|
|
20140
20347
|
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
20141
20348
|
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
20349
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20350
|
+
}
|
|
20351
|
+
function highlightMilestone(g, svg, taskId) {
|
|
20352
|
+
g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
|
|
20353
|
+
g.selectAll(".gantt-milestone").each(function() {
|
|
20354
|
+
const el = d3Selection10.select(this);
|
|
20355
|
+
el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
|
|
20356
|
+
});
|
|
20357
|
+
svg.selectAll(".gantt-task-label").each(function() {
|
|
20358
|
+
const el = d3Selection10.select(this);
|
|
20359
|
+
el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
|
|
20360
|
+
});
|
|
20361
|
+
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20362
|
+
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20363
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
|
|
20364
|
+
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20365
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
|
|
20366
|
+
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
20367
|
+
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
20368
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20142
20369
|
}
|
|
20143
20370
|
function highlightTaskLabel(svg, lineNumber) {
|
|
20144
20371
|
const ln = String(lineNumber);
|
|
@@ -20154,10 +20381,14 @@ function resetHighlight(g, svg) {
|
|
|
20154
20381
|
g.selectAll(".gantt-task, .gantt-milestone").attr("opacity", 1);
|
|
20155
20382
|
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", 1);
|
|
20156
20383
|
svg.selectAll(".gantt-group-label").attr("opacity", 1);
|
|
20384
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", 1);
|
|
20157
20385
|
svg.selectAll(".gantt-task-label").attr("opacity", 1);
|
|
20158
20386
|
svg.selectAll(".gantt-lane-header").attr("opacity", 1);
|
|
20387
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", 1);
|
|
20159
20388
|
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", 1);
|
|
20160
20389
|
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", 0.5);
|
|
20390
|
+
g.selectAll(".gantt-marker-group").attr("opacity", 1);
|
|
20391
|
+
g.selectAll(".gantt-era-group").attr("opacity", 1);
|
|
20161
20392
|
}
|
|
20162
20393
|
function buildRowList(resolved, collapsedGroups) {
|
|
20163
20394
|
const rows = [];
|
|
@@ -20236,9 +20467,9 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
|
|
|
20236
20467
|
for (const entry of tagGroup.entries) {
|
|
20237
20468
|
const entryKey = entry.value.toLowerCase();
|
|
20238
20469
|
const tasks = buckets.get(entryKey) ?? [];
|
|
20470
|
+
if (tasks.length === 0) continue;
|
|
20239
20471
|
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;
|
|
20472
|
+
const aggregateProgress = durationWeightedProgress(tasks);
|
|
20242
20473
|
const laneStartDate = tasks.length > 0 ? new Date(Math.min(...tasks.map((t) => t.startDate.getTime()))) : null;
|
|
20243
20474
|
const laneEndDate = tasks.length > 0 ? new Date(Math.max(...tasks.map((t) => t.endDate.getTime()))) : null;
|
|
20244
20475
|
const isCollapsed = collapsedLanes?.has(entry.value) ?? false;
|
|
@@ -20260,8 +20491,7 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
|
|
|
20260
20491
|
}
|
|
20261
20492
|
if (unbucketed.length > 0) {
|
|
20262
20493
|
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;
|
|
20494
|
+
const aggregateProgress = durationWeightedProgress(unbucketed);
|
|
20265
20495
|
const noLaneStartDate = unbucketed.length > 0 ? new Date(Math.min(...unbucketed.map((t) => t.startDate.getTime()))) : null;
|
|
20266
20496
|
const noLaneEndDate = unbucketed.length > 0 ? new Date(Math.max(...unbucketed.map((t) => t.endDate.getTime()))) : null;
|
|
20267
20497
|
const noLaneName = `No ${tagGroup.name}`;
|
|
@@ -20284,6 +20514,20 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
|
|
|
20284
20514
|
}
|
|
20285
20515
|
return rows;
|
|
20286
20516
|
}
|
|
20517
|
+
function durationWeightedProgress(tasks) {
|
|
20518
|
+
let totalDuration = 0;
|
|
20519
|
+
let totalProgress = 0;
|
|
20520
|
+
let hasProgress = false;
|
|
20521
|
+
for (const rt of tasks) {
|
|
20522
|
+
const dur = rt.endDate.getTime() - rt.startDate.getTime();
|
|
20523
|
+
totalDuration += dur;
|
|
20524
|
+
if (rt.task.progress !== null) {
|
|
20525
|
+
totalProgress += rt.task.progress * dur;
|
|
20526
|
+
hasProgress = true;
|
|
20527
|
+
}
|
|
20528
|
+
}
|
|
20529
|
+
return hasProgress && totalDuration > 0 ? totalProgress / totalDuration : null;
|
|
20530
|
+
}
|
|
20287
20531
|
function dateToFractionalYear(d) {
|
|
20288
20532
|
const y = d.getFullYear();
|
|
20289
20533
|
const startOfYear = new Date(y, 0, 1);
|
|
@@ -20298,21 +20542,43 @@ function diamondPoints(cx, cy, size) {
|
|
|
20298
20542
|
function formatGanttDate(d) {
|
|
20299
20543
|
return `${MONTH_ABBR[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
|
|
20300
20544
|
}
|
|
20301
|
-
function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, color) {
|
|
20545
|
+
function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, color, options) {
|
|
20302
20546
|
g.selectAll(".gantt-scale-tick").attr("opacity", 0.05);
|
|
20303
20547
|
g.selectAll(".gantt-today").attr("opacity", 0.05);
|
|
20548
|
+
const hg = g.append("g").attr("class", "gantt-hover-date").attr("pointer-events", "none");
|
|
20304
20549
|
const tickLen = 6;
|
|
20305
20550
|
const startPos = xScale(dateToFractionalYear(startDate));
|
|
20306
20551
|
const startLabel = formatGanttDate(startDate);
|
|
20307
|
-
|
|
20308
|
-
|
|
20309
|
-
|
|
20552
|
+
if (!options?.skipStartLine) {
|
|
20553
|
+
hg.append("line").attr("class", "gantt-hover-date").attr("x1", startPos).attr("y1", -tickLen).attr("x2", startPos).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "4 4").attr("opacity", 0.6);
|
|
20554
|
+
}
|
|
20555
|
+
hg.append("text").attr("class", "gantt-hover-date").attr("x", startPos).attr("y", -tickLen - 4).attr("text-anchor", "middle").attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(startLabel);
|
|
20556
|
+
hg.append("text").attr("class", "gantt-hover-date").attr("x", startPos).attr("y", innerHeight + tickLen + 12).attr("text-anchor", "middle").attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(startLabel);
|
|
20310
20557
|
if (endDate && endDate.getTime() !== startDate.getTime()) {
|
|
20311
20558
|
const endPos = xScale(dateToFractionalYear(endDate));
|
|
20312
20559
|
const endLabel = formatGanttDate(endDate);
|
|
20313
|
-
|
|
20314
|
-
|
|
20315
|
-
|
|
20560
|
+
const minLabelGap = 90;
|
|
20561
|
+
const gap = endPos - startPos;
|
|
20562
|
+
let startLabelX = startPos;
|
|
20563
|
+
let endLabelX = endPos;
|
|
20564
|
+
let startAnchor = "middle";
|
|
20565
|
+
let endAnchor = "middle";
|
|
20566
|
+
if (gap < minLabelGap) {
|
|
20567
|
+
const mid = (startPos + endPos) / 2;
|
|
20568
|
+
startLabelX = mid - minLabelGap / 2;
|
|
20569
|
+
endLabelX = mid + minLabelGap / 2;
|
|
20570
|
+
startAnchor = "middle";
|
|
20571
|
+
endAnchor = "middle";
|
|
20572
|
+
}
|
|
20573
|
+
hg.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);
|
|
20574
|
+
hg.selectAll("text.gantt-hover-date").each(function() {
|
|
20575
|
+
const el = d3Selection10.select(this);
|
|
20576
|
+
if (el.text() === startLabel) {
|
|
20577
|
+
el.attr("x", startLabelX).attr("text-anchor", startAnchor);
|
|
20578
|
+
}
|
|
20579
|
+
});
|
|
20580
|
+
hg.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);
|
|
20581
|
+
hg.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
20582
|
}
|
|
20317
20583
|
}
|
|
20318
20584
|
function hideGanttDateIndicators(g) {
|
|
@@ -20357,7 +20623,7 @@ function renderTimeScaleHorizontal(g, scale, innerWidth, innerHeight, textColor)
|
|
|
20357
20623
|
g.append("text").attr("class", "gantt-scale-tick").attr("x", tick.pos).attr("y", innerHeight + tickLen + 12).attr("text-anchor", "middle").attr("font-size", "10px").attr("fill", textColor).attr("opacity", opacity).text(tick.label);
|
|
20358
20624
|
}
|
|
20359
20625
|
}
|
|
20360
|
-
var d3Scale, d3Selection10, BAR_H, ROW_GAP, GROUP_GAP2, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, JS_DAY_TO_WEEKDAY2, ERA_COLORS, FADE_OPACITY, MONTH_ABBR;
|
|
20626
|
+
var d3Scale, d3Selection10, BAR_H, ROW_GAP, GROUP_GAP2, MILESTONE_SIZE, MIN_LEFT_MARGIN, BOTTOM_MARGIN, RIGHT_MARGIN, CHAR_W, LABEL_PAD, LABEL_GAP, BAND_ACCENT_W, BAND_RADIUS, bandClipCounter, JS_DAY_TO_WEEKDAY2, ERA_COLORS, FADE_OPACITY, MONTH_ABBR;
|
|
20361
20627
|
var init_renderer9 = __esm({
|
|
20362
20628
|
"src/gantt/renderer.ts"() {
|
|
20363
20629
|
"use strict";
|
|
@@ -20376,6 +20642,12 @@ var init_renderer9 = __esm({
|
|
|
20376
20642
|
MIN_LEFT_MARGIN = 120;
|
|
20377
20643
|
BOTTOM_MARGIN = 40;
|
|
20378
20644
|
RIGHT_MARGIN = 20;
|
|
20645
|
+
CHAR_W = 6.5;
|
|
20646
|
+
LABEL_PAD = 8;
|
|
20647
|
+
LABEL_GAP = 5;
|
|
20648
|
+
BAND_ACCENT_W = 4;
|
|
20649
|
+
BAND_RADIUS = 4;
|
|
20650
|
+
bandClipCounter = 0;
|
|
20379
20651
|
JS_DAY_TO_WEEKDAY2 = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
|
20380
20652
|
ERA_COLORS = ["#5e81ac", "#a3be8c", "#ebcb8b", "#d08770", "#b48ead"];
|
|
20381
20653
|
FADE_OPACITY = 0.1;
|
|
@@ -20476,14 +20748,14 @@ function renderState(container, graph, layout, palette, isDark, onClickItem, exp
|
|
|
20476
20748
|
nodePositionMap.set(node.id, node);
|
|
20477
20749
|
}
|
|
20478
20750
|
const LABEL_CHAR_W = 7;
|
|
20479
|
-
const
|
|
20751
|
+
const LABEL_PAD2 = 8;
|
|
20480
20752
|
const LABEL_H = 16;
|
|
20481
20753
|
const PERP_OFFSET = 10;
|
|
20482
20754
|
const labelPositions = [];
|
|
20483
20755
|
for (let ei = 0; ei < layout.edges.length; ei++) {
|
|
20484
20756
|
const edge = layout.edges[ei];
|
|
20485
20757
|
if (!edge.label) continue;
|
|
20486
|
-
const bgW = edge.label.length * LABEL_CHAR_W +
|
|
20758
|
+
const bgW = edge.label.length * LABEL_CHAR_W + LABEL_PAD2;
|
|
20487
20759
|
let lx, ly;
|
|
20488
20760
|
if (edge.source === edge.target) {
|
|
20489
20761
|
const node = nodePositionMap.get(edge.source);
|
|
@@ -22365,7 +22637,7 @@ function parseVisualization(content, palette) {
|
|
|
22365
22637
|
continue;
|
|
22366
22638
|
}
|
|
22367
22639
|
const markerMatch = line10.match(
|
|
22368
|
-
/^marker
|
|
22640
|
+
/^marker:\s+(\d{4}(?:-\d{2})?(?:-\d{2})?)\s+(.+?)(?:\s*\(([^)]+)\))?\s*$/
|
|
22369
22641
|
);
|
|
22370
22642
|
if (markerMatch) {
|
|
22371
22643
|
const colorAnnotation = markerMatch[3]?.trim() || null;
|
|
@@ -22389,7 +22661,7 @@ function parseVisualization(content, palette) {
|
|
|
22389
22661
|
const unit = durationMatch[3];
|
|
22390
22662
|
const endDate = addDurationToDate(startDate, amount, unit);
|
|
22391
22663
|
const segments = durationMatch[5].split("|");
|
|
22392
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
|
|
22664
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
|
|
22393
22665
|
result.timelineEvents.push({
|
|
22394
22666
|
date: startDate,
|
|
22395
22667
|
endDate,
|
|
@@ -22406,7 +22678,7 @@ function parseVisualization(content, palette) {
|
|
|
22406
22678
|
);
|
|
22407
22679
|
if (rangeMatch) {
|
|
22408
22680
|
const segments = rangeMatch[4].split("|");
|
|
22409
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
|
|
22681
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
|
|
22410
22682
|
result.timelineEvents.push({
|
|
22411
22683
|
date: rangeMatch[1],
|
|
22412
22684
|
endDate: rangeMatch[2],
|
|
@@ -22423,7 +22695,7 @@ function parseVisualization(content, palette) {
|
|
|
22423
22695
|
);
|
|
22424
22696
|
if (pointMatch) {
|
|
22425
22697
|
const segments = pointMatch[2].split("|");
|
|
22426
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
|
|
22698
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
|
|
22427
22699
|
result.timelineEvents.push({
|
|
22428
22700
|
date: pointMatch[1],
|
|
22429
22701
|
endDate: null,
|
|
@@ -24983,12 +25255,12 @@ function renderQuadrant(container, parsed, palette, isDark, onClickItem, exportD
|
|
|
24983
25255
|
};
|
|
24984
25256
|
const LABEL_MAX_FONT = 48;
|
|
24985
25257
|
const LABEL_MIN_FONT = 14;
|
|
24986
|
-
const
|
|
25258
|
+
const LABEL_PAD2 = 40;
|
|
24987
25259
|
const CHAR_WIDTH_RATIO3 = 0.6;
|
|
24988
25260
|
const estTextWidth = (text, fontSize) => text.length * fontSize * CHAR_WIDTH_RATIO3;
|
|
24989
25261
|
const quadrantLabelLayout = (text, qw2, qh2) => {
|
|
24990
|
-
const availW = qw2 -
|
|
24991
|
-
const availH = qh2 -
|
|
25262
|
+
const availW = qw2 - LABEL_PAD2;
|
|
25263
|
+
const availH = qh2 - LABEL_PAD2;
|
|
24992
25264
|
const words = text.split(/\s+/);
|
|
24993
25265
|
if (estTextWidth(text, LABEL_MAX_FONT) <= availW) {
|
|
24994
25266
|
const fs = Math.min(LABEL_MAX_FONT, availH);
|
|
@@ -25375,9 +25647,8 @@ async function renderForExport(content, theme, palette, orgExportState, options)
|
|
|
25375
25647
|
const { renderGantt: renderGantt2 } = await Promise.resolve().then(() => (init_renderer9(), renderer_exports9));
|
|
25376
25648
|
const effectivePalette2 = await resolveExportPalette(theme, palette);
|
|
25377
25649
|
const ganttParsed = parseGantt2(content, effectivePalette2);
|
|
25378
|
-
if (ganttParsed.error) return "";
|
|
25379
25650
|
const resolved = calculateSchedule2(ganttParsed);
|
|
25380
|
-
if (resolved.
|
|
25651
|
+
if (resolved.tasks.length === 0) return "";
|
|
25381
25652
|
const EXPORT_W = 1200;
|
|
25382
25653
|
const EXPORT_H = 800;
|
|
25383
25654
|
const container2 = createExportContainer(EXPORT_W, EXPORT_H);
|