@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.js
CHANGED
|
@@ -1495,24 +1495,26 @@ function parseSeriesNames(value, lines, lineIndex, palette) {
|
|
|
1495
1495
|
}
|
|
1496
1496
|
return { series, names, nameColors, newIndex };
|
|
1497
1497
|
}
|
|
1498
|
-
function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map()) {
|
|
1498
|
+
function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map(), warnMultiplePipes) {
|
|
1499
|
+
if (segments.length > 2 && warnMultiplePipes) {
|
|
1500
|
+
warnMultiplePipes();
|
|
1501
|
+
}
|
|
1499
1502
|
const metadata = {};
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
}
|
|
1503
|
+
const raw = segments.slice(1).join(",");
|
|
1504
|
+
for (const part of raw.split(",")) {
|
|
1505
|
+
const trimmedPart = part.trim();
|
|
1506
|
+
if (!trimmedPart) continue;
|
|
1507
|
+
const colonIdx = trimmedPart.indexOf(":");
|
|
1508
|
+
if (colonIdx > 0) {
|
|
1509
|
+
const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
|
|
1510
|
+
const key = aliasMap.get(rawKey) ?? rawKey;
|
|
1511
|
+
const value = trimmedPart.substring(colonIdx + 1).trim();
|
|
1512
|
+
metadata[key] = value;
|
|
1511
1513
|
}
|
|
1512
1514
|
}
|
|
1513
1515
|
return metadata;
|
|
1514
1516
|
}
|
|
1515
|
-
var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE;
|
|
1517
|
+
var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE, MULTIPLE_PIPE_WARNING;
|
|
1516
1518
|
var init_parsing = __esm({
|
|
1517
1519
|
"src/utils/parsing.ts"() {
|
|
1518
1520
|
"use strict";
|
|
@@ -1521,6 +1523,7 @@ var init_parsing = __esm({
|
|
|
1521
1523
|
CHART_TYPE_RE = /^chart\s*:\s*(.+)/i;
|
|
1522
1524
|
TITLE_RE = /^title\s*:\s*(.+)/i;
|
|
1523
1525
|
OPTION_RE = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
|
|
1526
|
+
MULTIPLE_PIPE_WARNING = 'Use a single "|" to start metadata, then separate items with commas.';
|
|
1524
1527
|
}
|
|
1525
1528
|
});
|
|
1526
1529
|
|
|
@@ -2021,12 +2024,13 @@ function parseSequenceDgmo(content) {
|
|
|
2021
2024
|
const participantGroupMap = /* @__PURE__ */ new Map();
|
|
2022
2025
|
let currentTagGroup = null;
|
|
2023
2026
|
const aliasMap = /* @__PURE__ */ new Map();
|
|
2024
|
-
const splitPipe = (text) => {
|
|
2027
|
+
const splitPipe = (text, ln) => {
|
|
2025
2028
|
const idx = text.indexOf("|");
|
|
2026
2029
|
if (idx < 0) return { core: text };
|
|
2027
2030
|
const core = text.substring(0, idx).trimEnd();
|
|
2028
2031
|
const segments = text.substring(idx).split("|");
|
|
2029
|
-
const
|
|
2032
|
+
const warnFn = ln != null ? () => pushWarning(ln, MULTIPLE_PIPE_WARNING) : void 0;
|
|
2033
|
+
const meta = parsePipeMetadata(segments, aliasMap, warnFn);
|
|
2030
2034
|
return Object.keys(meta).length > 0 ? { core, meta } : { core };
|
|
2031
2035
|
};
|
|
2032
2036
|
const blockStack = [];
|
|
@@ -2055,7 +2059,7 @@ function parseSequenceDgmo(content) {
|
|
|
2055
2059
|
if (gpipeIdx >= 0) {
|
|
2056
2060
|
const nameAndColor = groupName.substring(0, gpipeIdx).trimEnd();
|
|
2057
2061
|
const segments = groupName.substring(gpipeIdx).split("|");
|
|
2058
|
-
const meta = parsePipeMetadata(segments, aliasMap);
|
|
2062
|
+
const meta = parsePipeMetadata(segments, aliasMap, () => pushWarning(lineNumber, MULTIPLE_PIPE_WARNING));
|
|
2059
2063
|
if (Object.keys(meta).length > 0) groupMeta = meta;
|
|
2060
2064
|
const colorSuffix = nameAndColor.match(/^(.+?)\(([^)]+)\)$/);
|
|
2061
2065
|
if (colorSuffix) {
|
|
@@ -2178,7 +2182,7 @@ function parseSequenceDgmo(content) {
|
|
|
2178
2182
|
continue;
|
|
2179
2183
|
}
|
|
2180
2184
|
}
|
|
2181
|
-
const { core: isACore, meta: isAMeta } = splitPipe(trimmed);
|
|
2185
|
+
const { core: isACore, meta: isAMeta } = splitPipe(trimmed, lineNumber);
|
|
2182
2186
|
const isAMatch = isACore.match(IS_A_PATTERN);
|
|
2183
2187
|
if (isAMatch) {
|
|
2184
2188
|
contentStarted = true;
|
|
@@ -2215,7 +2219,7 @@ function parseSequenceDgmo(content) {
|
|
|
2215
2219
|
}
|
|
2216
2220
|
continue;
|
|
2217
2221
|
}
|
|
2218
|
-
const { core: posCore, meta: posMeta } = splitPipe(trimmed);
|
|
2222
|
+
const { core: posCore, meta: posMeta } = splitPipe(trimmed, lineNumber);
|
|
2219
2223
|
const posOnlyMatch = posCore.match(POSITION_ONLY_PATTERN);
|
|
2220
2224
|
if (posOnlyMatch) {
|
|
2221
2225
|
contentStarted = true;
|
|
@@ -2242,7 +2246,7 @@ function parseSequenceDgmo(content) {
|
|
|
2242
2246
|
}
|
|
2243
2247
|
continue;
|
|
2244
2248
|
}
|
|
2245
|
-
const { core: colorCore, meta: colorMeta } = splitPipe(trimmed);
|
|
2249
|
+
const { core: colorCore, meta: colorMeta } = splitPipe(trimmed, lineNumber);
|
|
2246
2250
|
const coloredMatch = colorCore.match(COLORED_PARTICIPANT_PATTERN);
|
|
2247
2251
|
if (coloredMatch && !ARROW_PATTERN.test(colorCore)) {
|
|
2248
2252
|
const id = coloredMatch[1];
|
|
@@ -2270,7 +2274,7 @@ function parseSequenceDgmo(content) {
|
|
|
2270
2274
|
continue;
|
|
2271
2275
|
}
|
|
2272
2276
|
{
|
|
2273
|
-
const { core: bareCore, meta: bareMeta } = splitPipe(trimmed);
|
|
2277
|
+
const { core: bareCore, meta: bareMeta } = splitPipe(trimmed, lineNumber);
|
|
2274
2278
|
const inGroup = activeGroup && measureIndent(raw) > 0;
|
|
2275
2279
|
if (/^\S+$/.test(bareCore) && !ARROW_PATTERN.test(bareCore) && (inGroup || !contentStarted || bareMeta)) {
|
|
2276
2280
|
contentStarted = true;
|
|
@@ -2306,7 +2310,7 @@ function parseSequenceDgmo(content) {
|
|
|
2306
2310
|
}
|
|
2307
2311
|
blockStack.pop();
|
|
2308
2312
|
}
|
|
2309
|
-
const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed);
|
|
2313
|
+
const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed, lineNumber);
|
|
2310
2314
|
const asyncPrefixMatch = arrowCore.match(/^async\s+(.+)$/i);
|
|
2311
2315
|
if (asyncPrefixMatch && ARROW_PATTERN.test(asyncPrefixMatch[1])) {
|
|
2312
2316
|
pushError(lineNumber, "Use ~> for async messages: A ~> B: message");
|
|
@@ -3757,7 +3761,12 @@ function parseERDiagram(content, palette) {
|
|
|
3757
3761
|
table.lineNumber = lineNumber;
|
|
3758
3762
|
const pipeStr = tableDecl[3]?.trim();
|
|
3759
3763
|
if (pipeStr) {
|
|
3760
|
-
const
|
|
3764
|
+
const pipeSegments = pipeStr.split("|");
|
|
3765
|
+
const meta = parsePipeMetadata(
|
|
3766
|
+
["", ...pipeSegments],
|
|
3767
|
+
aliasMap,
|
|
3768
|
+
() => result.diagnostics.push(makeDgmoError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"))
|
|
3769
|
+
);
|
|
3761
3770
|
Object.assign(table.metadata, meta);
|
|
3762
3771
|
}
|
|
3763
3772
|
currentTable = table;
|
|
@@ -5694,13 +5703,13 @@ function parseOrg(content, palette) {
|
|
|
5694
5703
|
}
|
|
5695
5704
|
} else if (metadataMatch && indentStack.length === 0) {
|
|
5696
5705
|
if (indent === 0) {
|
|
5697
|
-
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
5706
|
+
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
5698
5707
|
attachNode(node, indent, indentStack, result);
|
|
5699
5708
|
} else {
|
|
5700
5709
|
pushError(lineNumber, "Metadata has no parent node");
|
|
5701
5710
|
}
|
|
5702
5711
|
} else {
|
|
5703
|
-
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
5712
|
+
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
5704
5713
|
attachNode(node, indent, indentStack, result);
|
|
5705
5714
|
}
|
|
5706
5715
|
}
|
|
@@ -5722,11 +5731,11 @@ function parseOrg(content, palette) {
|
|
|
5722
5731
|
}
|
|
5723
5732
|
return result;
|
|
5724
5733
|
}
|
|
5725
|
-
function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
|
|
5734
|
+
function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
|
|
5726
5735
|
const segments = trimmed.split("|").map((s) => s.trim());
|
|
5727
5736
|
let rawLabel = segments[0];
|
|
5728
5737
|
const { label, color } = extractColor(rawLabel, palette);
|
|
5729
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
5738
|
+
const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
|
|
5730
5739
|
return {
|
|
5731
5740
|
id: `node-${counter}`,
|
|
5732
5741
|
label,
|
|
@@ -6268,7 +6277,7 @@ function parseC4(content, palette) {
|
|
|
6268
6277
|
}
|
|
6269
6278
|
const segments = trimmed.split("|").map((s) => s.trim());
|
|
6270
6279
|
const nodeName = segments[0];
|
|
6271
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
6280
|
+
const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
|
|
6272
6281
|
const shape = inferC4Shape(nodeName, metadata.tech ?? metadata.technology);
|
|
6273
6282
|
const dNode = {
|
|
6274
6283
|
name: nodeName,
|
|
@@ -6421,7 +6430,7 @@ function parseC4(content, palette) {
|
|
|
6421
6430
|
}
|
|
6422
6431
|
namePart = namePart.substring(0, isAMatch.index).trim();
|
|
6423
6432
|
}
|
|
6424
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
6433
|
+
const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
|
|
6425
6434
|
const shape = explicitShape ?? inferC4Shape(namePart, metadata.tech ?? metadata.technology);
|
|
6426
6435
|
const element = {
|
|
6427
6436
|
name: namePart,
|
|
@@ -7208,14 +7217,14 @@ function parseSitemap(content, palette) {
|
|
|
7208
7217
|
}
|
|
7209
7218
|
} else if (metadataMatch && indentStack.length === 0) {
|
|
7210
7219
|
if (indent === 0) {
|
|
7211
|
-
const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
7220
|
+
const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
7212
7221
|
attachNode2(node, indent, indentStack, result);
|
|
7213
7222
|
labelToNode.set(node.label.toLowerCase(), node);
|
|
7214
7223
|
} else {
|
|
7215
7224
|
pushError(lineNumber, "Metadata has no parent node");
|
|
7216
7225
|
}
|
|
7217
7226
|
} else {
|
|
7218
|
-
const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
7227
|
+
const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
7219
7228
|
attachNode2(node, indent, indentStack, result);
|
|
7220
7229
|
labelToNode.set(node.label.toLowerCase(), node);
|
|
7221
7230
|
}
|
|
@@ -7257,11 +7266,11 @@ function parseSitemap(content, palette) {
|
|
|
7257
7266
|
}
|
|
7258
7267
|
return result;
|
|
7259
7268
|
}
|
|
7260
|
-
function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
|
|
7269
|
+
function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
|
|
7261
7270
|
const segments = trimmed.split("|").map((s) => s.trim());
|
|
7262
7271
|
const rawLabel = segments[0];
|
|
7263
7272
|
const { label, color } = extractColor(rawLabel, palette);
|
|
7264
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
7273
|
+
const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
|
|
7265
7274
|
return {
|
|
7266
7275
|
id: `node-${counter}`,
|
|
7267
7276
|
label,
|
|
@@ -7955,7 +7964,9 @@ function parseGantt(content, palette) {
|
|
|
7955
7964
|
criticalPath: false,
|
|
7956
7965
|
dependencies: false,
|
|
7957
7966
|
sort: "default",
|
|
7958
|
-
defaultSwimlaneGroup: null
|
|
7967
|
+
defaultSwimlaneGroup: null,
|
|
7968
|
+
optionLineNumbers: {},
|
|
7969
|
+
holidaysLineNumber: null
|
|
7959
7970
|
},
|
|
7960
7971
|
diagnostics,
|
|
7961
7972
|
error: null
|
|
@@ -7969,6 +7980,9 @@ function parseGantt(content, palette) {
|
|
|
7969
7980
|
const warn = (line10, message) => {
|
|
7970
7981
|
diagnostics.push(makeDgmoError(line10, message, "warning"));
|
|
7971
7982
|
};
|
|
7983
|
+
const softError = (line10, message) => {
|
|
7984
|
+
diagnostics.push(makeDgmoError(line10, message, "error"));
|
|
7985
|
+
};
|
|
7972
7986
|
const aliasMap = /* @__PURE__ */ new Map();
|
|
7973
7987
|
const blockStack = [];
|
|
7974
7988
|
const currentContainer = () => {
|
|
@@ -8102,10 +8116,10 @@ function parseGantt(content, palette) {
|
|
|
8102
8116
|
const targetName = depParts[0].trim();
|
|
8103
8117
|
let offset;
|
|
8104
8118
|
if (depParts.length > 1) {
|
|
8105
|
-
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
|
|
8119
|
+
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
|
|
8106
8120
|
if (meta.lag || meta.lead) {
|
|
8107
8121
|
const key = meta.lag ? "lag" : "lead";
|
|
8108
|
-
|
|
8122
|
+
softError(lineNumber, `Unknown keyword "${key}". Use "offset: ${meta[key]}" instead.`);
|
|
8109
8123
|
}
|
|
8110
8124
|
if (meta.offset) {
|
|
8111
8125
|
const raw = meta.offset;
|
|
@@ -8137,6 +8151,7 @@ function parseGantt(content, palette) {
|
|
|
8137
8151
|
inHolidaysBlock = true;
|
|
8138
8152
|
holidaysBlockIndent = indent;
|
|
8139
8153
|
inHeaderBlock = false;
|
|
8154
|
+
result.options.holidaysLineNumber = lineNumber;
|
|
8140
8155
|
continue;
|
|
8141
8156
|
}
|
|
8142
8157
|
const tagMatch = matchTagBlockHeading(line10);
|
|
@@ -8163,7 +8178,8 @@ function parseGantt(content, palette) {
|
|
|
8163
8178
|
startDate: eraMatch[1],
|
|
8164
8179
|
endDate: eraMatch[2],
|
|
8165
8180
|
label: eraExtracted.label,
|
|
8166
|
-
color: eraExtracted.color || null
|
|
8181
|
+
color: eraExtracted.color || null,
|
|
8182
|
+
lineNumber
|
|
8167
8183
|
});
|
|
8168
8184
|
inHeaderBlock = false;
|
|
8169
8185
|
continue;
|
|
@@ -8185,6 +8201,7 @@ function parseGantt(content, palette) {
|
|
|
8185
8201
|
if (optMatch && isKnownOption(optMatch[1].toLowerCase())) {
|
|
8186
8202
|
const key = optMatch[1].toLowerCase();
|
|
8187
8203
|
const value = optMatch[2].trim();
|
|
8204
|
+
result.options.optionLineNumbers[key] = lineNumber;
|
|
8188
8205
|
switch (key) {
|
|
8189
8206
|
case "start":
|
|
8190
8207
|
result.options.start = value;
|
|
@@ -8244,16 +8261,18 @@ function parseGantt(content, palette) {
|
|
|
8244
8261
|
const groupMatch = line10.match(GROUP_RE2);
|
|
8245
8262
|
if (groupMatch) {
|
|
8246
8263
|
if (blockStack.length > 0 && blockStack[blockStack.length - 1].containerType === "task") {
|
|
8247
|
-
|
|
8264
|
+
softError(lineNumber, `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`);
|
|
8265
|
+
continue;
|
|
8248
8266
|
}
|
|
8249
8267
|
const afterBrackets = groupMatch[2].trim();
|
|
8250
8268
|
const segments = afterBrackets ? afterBrackets.split("|") : [];
|
|
8251
8269
|
let metadata = {};
|
|
8252
8270
|
let color = null;
|
|
8271
|
+
const pipeWarn = () => warn(lineNumber, MULTIPLE_PIPE_WARNING);
|
|
8253
8272
|
if (segments.length > 0 && segments[0].trim()) {
|
|
8254
|
-
metadata = parsePipeMetadata(["", ...segments], aliasMap);
|
|
8273
|
+
metadata = parsePipeMetadata(["", ...segments], aliasMap, pipeWarn);
|
|
8255
8274
|
} else if (segments.length > 1) {
|
|
8256
|
-
metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap);
|
|
8275
|
+
metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap, pipeWarn);
|
|
8257
8276
|
}
|
|
8258
8277
|
const nameExtracted = extractColor(groupMatch[1], palette);
|
|
8259
8278
|
if (nameExtracted.color) {
|
|
@@ -8284,7 +8303,6 @@ function parseGantt(content, palette) {
|
|
|
8284
8303
|
const uncertain = !!timelineDurMatch[4];
|
|
8285
8304
|
const labelRaw = timelineDurMatch[5];
|
|
8286
8305
|
const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber, startDate);
|
|
8287
|
-
if (result.error) return result;
|
|
8288
8306
|
const taskNode = { kind: "task", ...task };
|
|
8289
8307
|
currentContainer().push(taskNode);
|
|
8290
8308
|
lastTaskNode = taskNode;
|
|
@@ -8298,7 +8316,6 @@ function parseGantt(content, palette) {
|
|
|
8298
8316
|
const uncertain = !!durMatch[3];
|
|
8299
8317
|
const labelRaw = durMatch[4];
|
|
8300
8318
|
const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber);
|
|
8301
|
-
if (result.error) return result;
|
|
8302
8319
|
const taskNode = { kind: "task", ...task };
|
|
8303
8320
|
currentContainer().push(taskNode);
|
|
8304
8321
|
lastTaskNode = taskNode;
|
|
@@ -8315,7 +8332,6 @@ function parseGantt(content, palette) {
|
|
|
8315
8332
|
lineNumber,
|
|
8316
8333
|
explicitDateMatch[1]
|
|
8317
8334
|
);
|
|
8318
|
-
if (result.error) return result;
|
|
8319
8335
|
const taskNode = { kind: "task", ...task };
|
|
8320
8336
|
currentContainer().push(taskNode);
|
|
8321
8337
|
lastTaskNode = taskNode;
|
|
@@ -8325,13 +8341,14 @@ function parseGantt(content, palette) {
|
|
|
8325
8341
|
const depMatch = line10.match(DEPENDENCY_RE);
|
|
8326
8342
|
if (depMatch) {
|
|
8327
8343
|
if (!lastTaskNode) {
|
|
8328
|
-
|
|
8344
|
+
softError(lineNumber, `Dependency "-> ${depMatch[1]}" must be indented under a task.`);
|
|
8345
|
+
continue;
|
|
8329
8346
|
}
|
|
8330
8347
|
const depParts = depMatch[1].split("|");
|
|
8331
8348
|
const targetName = depParts[0].trim();
|
|
8332
8349
|
let offset;
|
|
8333
8350
|
if (depParts.length > 1) {
|
|
8334
|
-
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
|
|
8351
|
+
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
|
|
8335
8352
|
if (meta.lag || meta.lead) {
|
|
8336
8353
|
const key = meta.lag ? "lag" : "lead";
|
|
8337
8354
|
warn(lineNumber, `"${key}" is deprecated \u2014 use "offset: ${meta[key]}" instead.${key === "lead" ? ' Negate the value for lead behavior: "offset: -...".' : ""}`);
|
|
@@ -8351,7 +8368,8 @@ function parseGantt(content, palette) {
|
|
|
8351
8368
|
lastTaskNode.dependencies.push({ targetName, offset, lineNumber });
|
|
8352
8369
|
continue;
|
|
8353
8370
|
}
|
|
8354
|
-
|
|
8371
|
+
softError(lineNumber, `Expected duration (e.g., "10d: Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line10}"`);
|
|
8372
|
+
continue;
|
|
8355
8373
|
}
|
|
8356
8374
|
if (currentTagGroup) {
|
|
8357
8375
|
result.tagGroups.push(currentTagGroup);
|
|
@@ -8365,16 +8383,16 @@ function parseGantt(content, palette) {
|
|
|
8365
8383
|
const segments = labelRaw.split("|");
|
|
8366
8384
|
const label = segments[0].trim();
|
|
8367
8385
|
if (label.toLowerCase() === "parallel") {
|
|
8368
|
-
|
|
8386
|
+
softError(ln, `"parallel" is a reserved keyword and cannot be used as a task name.`);
|
|
8369
8387
|
}
|
|
8370
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap) : {};
|
|
8388
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap, () => warn(ln, MULTIPLE_PIPE_WARNING)) : {};
|
|
8371
8389
|
let progress = null;
|
|
8372
8390
|
if (metadata.progress) {
|
|
8373
8391
|
progress = parseFloat(metadata.progress);
|
|
8374
8392
|
delete metadata.progress;
|
|
8375
8393
|
}
|
|
8376
|
-
for (
|
|
8377
|
-
const seg =
|
|
8394
|
+
for (const part of segments.slice(1).join(",").split(",")) {
|
|
8395
|
+
const seg = part.trim();
|
|
8378
8396
|
const progressMatch = seg.match(/^(\d+)%$/);
|
|
8379
8397
|
if (progressMatch) {
|
|
8380
8398
|
progress = parseInt(progressMatch[1], 10);
|
|
@@ -8382,7 +8400,7 @@ function parseGantt(content, palette) {
|
|
|
8382
8400
|
}
|
|
8383
8401
|
if (metadata.lag || metadata.lead) {
|
|
8384
8402
|
const key = metadata.lag ? "lag" : "lead";
|
|
8385
|
-
|
|
8403
|
+
softError(ln, `Unknown keyword "${key}". Use "offset: ${metadata[key]}" instead.`);
|
|
8386
8404
|
}
|
|
8387
8405
|
let taskOffset;
|
|
8388
8406
|
if (metadata.offset) {
|
|
@@ -8469,7 +8487,7 @@ var init_parser10 = __esm({
|
|
|
8469
8487
|
DEPENDENCY_RE = /^->\s*(.+)$/;
|
|
8470
8488
|
COMMENT_RE = /^\/\//;
|
|
8471
8489
|
ERA_RE = /^era\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*->\s*(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*:\s*(.+)$/i;
|
|
8472
|
-
MARKER_RE = /^marker
|
|
8490
|
+
MARKER_RE = /^marker:\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s+(.+)$/i;
|
|
8473
8491
|
HOLIDAY_DATE_RE = /^(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
|
|
8474
8492
|
HOLIDAY_RANGE_RE = /^(\d{4}-\d{2}-\d{2})\s*->\s*(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
|
|
8475
8493
|
WORKWEEK_RE = /^workweek:\s*(.+)$/i;
|
|
@@ -16096,7 +16114,7 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
|
|
|
16096
16114
|
}
|
|
16097
16115
|
const contentG = svg.append("g").attr("transform", `translate(${offsetX}, ${offsetY}) scale(${scale})`);
|
|
16098
16116
|
const LABEL_CHAR_W = 7;
|
|
16099
|
-
const
|
|
16117
|
+
const LABEL_PAD2 = 8;
|
|
16100
16118
|
const LABEL_H = 16;
|
|
16101
16119
|
const PERP_OFFSET = 10;
|
|
16102
16120
|
const labelPositions = [];
|
|
@@ -16105,7 +16123,7 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
|
|
|
16105
16123
|
if (!edge.label || edge.points.length < 2) continue;
|
|
16106
16124
|
const midIdx = Math.floor(edge.points.length / 2);
|
|
16107
16125
|
const midPt = edge.points[midIdx];
|
|
16108
|
-
const bgW = edge.label.length * LABEL_CHAR_W +
|
|
16126
|
+
const bgW = edge.label.length * LABEL_CHAR_W + LABEL_PAD2;
|
|
16109
16127
|
const prev = edge.points[Math.max(0, midIdx - 1)];
|
|
16110
16128
|
const next = edge.points[Math.min(edge.points.length - 1, midIdx + 1)];
|
|
16111
16129
|
const dx = next.x - prev.x;
|
|
@@ -18882,11 +18900,8 @@ function calculateSchedule(parsed) {
|
|
|
18882
18900
|
for (const dep of task.dependencies) {
|
|
18883
18901
|
const resolved = resolveTaskName(dep.targetName, allTasks);
|
|
18884
18902
|
if (isResolverError(resolved)) {
|
|
18885
|
-
|
|
18886
|
-
|
|
18887
|
-
} else {
|
|
18888
|
-
return fail(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
|
|
18889
|
-
}
|
|
18903
|
+
warn(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
|
|
18904
|
+
continue;
|
|
18890
18905
|
}
|
|
18891
18906
|
const targetNode = taskMap.get(resolved.task.id);
|
|
18892
18907
|
if (targetNode) {
|
|
@@ -18901,14 +18916,26 @@ function calculateSchedule(parsed) {
|
|
|
18901
18916
|
}
|
|
18902
18917
|
}
|
|
18903
18918
|
}
|
|
18904
|
-
|
|
18919
|
+
let sortedIds = topologicalSort(taskMap);
|
|
18905
18920
|
if (!sortedIds) {
|
|
18906
18921
|
const cycle = findCycle(taskMap);
|
|
18907
18922
|
const cycleStr = cycle.map((id) => taskMap.get(id).task.label).join(" \u2192 ");
|
|
18908
|
-
|
|
18923
|
+
warn(
|
|
18909
18924
|
taskMap.get(cycle[0]).task.lineNumber,
|
|
18910
|
-
`Circular dependency detected: ${cycleStr}
|
|
18925
|
+
`Circular dependency detected: ${cycleStr}. The cycle-creating dependency was dropped.`
|
|
18911
18926
|
);
|
|
18927
|
+
breakCycle(cycle, taskMap, depOffsetMap);
|
|
18928
|
+
sortedIds = topologicalSort(taskMap);
|
|
18929
|
+
let safety = 10;
|
|
18930
|
+
while (!sortedIds && safety-- > 0) {
|
|
18931
|
+
const nextCycle = findCycle(taskMap);
|
|
18932
|
+
if (nextCycle.length === 0) break;
|
|
18933
|
+
breakCycle(nextCycle, taskMap, depOffsetMap);
|
|
18934
|
+
sortedIds = topologicalSort(taskMap);
|
|
18935
|
+
}
|
|
18936
|
+
if (!sortedIds) {
|
|
18937
|
+
sortedIds = [...taskMap.keys()];
|
|
18938
|
+
}
|
|
18912
18939
|
}
|
|
18913
18940
|
for (const taskId of sortedIds) {
|
|
18914
18941
|
const node = taskMap.get(taskId);
|
|
@@ -19192,6 +19219,19 @@ function findCycle(taskMap) {
|
|
|
19192
19219
|
return null;
|
|
19193
19220
|
}
|
|
19194
19221
|
}
|
|
19222
|
+
function breakCycle(cycle, taskMap, depOffsetMap) {
|
|
19223
|
+
if (cycle.length < 3) return;
|
|
19224
|
+
const fromId = cycle[cycle.length - 2];
|
|
19225
|
+
const toId = cycle[0];
|
|
19226
|
+
const toNode = taskMap.get(toId);
|
|
19227
|
+
if (toNode) {
|
|
19228
|
+
const idx = toNode.predecessors.indexOf(fromId);
|
|
19229
|
+
if (idx !== -1) {
|
|
19230
|
+
toNode.predecessors.splice(idx, 1);
|
|
19231
|
+
depOffsetMap.delete(`${fromId}->${toId}`);
|
|
19232
|
+
}
|
|
19233
|
+
}
|
|
19234
|
+
}
|
|
19195
19235
|
function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holidaySet) {
|
|
19196
19236
|
if (sortedIds.length === 0) return /* @__PURE__ */ new Set();
|
|
19197
19237
|
const latestEnd = /* @__PURE__ */ new Map();
|
|
@@ -19280,10 +19320,10 @@ function buildResolvedGroups(nodes, taskMap, groups, depth) {
|
|
|
19280
19320
|
if (!resolved?.startDate || !resolved?.endDate) continue;
|
|
19281
19321
|
if (resolved.startDate.getTime() < minStart) minStart = resolved.startDate.getTime();
|
|
19282
19322
|
if (resolved.endDate.getTime() > maxEnd) maxEnd = resolved.endDate.getTime();
|
|
19323
|
+
const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
|
|
19324
|
+
totalDuration += dur;
|
|
19283
19325
|
if (task.progress !== null) {
|
|
19284
|
-
const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
|
|
19285
19326
|
totalProgress += task.progress * dur;
|
|
19286
|
-
totalDuration += dur;
|
|
19287
19327
|
hasProgress = true;
|
|
19288
19328
|
}
|
|
19289
19329
|
}
|
|
@@ -19326,9 +19366,47 @@ __export(renderer_exports9, {
|
|
|
19326
19366
|
});
|
|
19327
19367
|
import * as d3Scale from "d3-scale";
|
|
19328
19368
|
import * as d3Selection10 from "d3-selection";
|
|
19369
|
+
function computeBarLabel(label, x1, barWidth, innerWidth, textColor) {
|
|
19370
|
+
const textWidth = label.length * CHAR_W;
|
|
19371
|
+
const x2 = x1 + barWidth;
|
|
19372
|
+
if (textWidth < barWidth - LABEL_PAD) {
|
|
19373
|
+
return { x: x1 + 6, anchor: "start", fill: textColor, text: label };
|
|
19374
|
+
}
|
|
19375
|
+
if (x2 + LABEL_GAP + textWidth <= innerWidth) {
|
|
19376
|
+
return { x: x2 + LABEL_GAP, anchor: "start", fill: textColor, text: label };
|
|
19377
|
+
}
|
|
19378
|
+
if (x1 - LABEL_GAP - textWidth >= 0) {
|
|
19379
|
+
return { x: x1 - LABEL_GAP, anchor: "end", fill: textColor, text: label };
|
|
19380
|
+
}
|
|
19381
|
+
const availWidth = x1 - LABEL_GAP;
|
|
19382
|
+
if (availWidth > CHAR_W * 3) {
|
|
19383
|
+
const maxChars = Math.floor(availWidth / CHAR_W) - 1;
|
|
19384
|
+
return { x: x1 - LABEL_GAP, anchor: "end", fill: textColor, text: label.slice(0, maxChars) + "\u2026" };
|
|
19385
|
+
}
|
|
19386
|
+
return null;
|
|
19387
|
+
}
|
|
19388
|
+
function renderLabelBand(svg, y, leftMargin, color, palette, cssPrefix, dataAttr) {
|
|
19389
|
+
const bandX = 5;
|
|
19390
|
+
const bandW = leftMargin - 7;
|
|
19391
|
+
const bandY = y - BAR_H / 2;
|
|
19392
|
+
const clipId = `gantt-band-clip-${bandClipCounter++}`;
|
|
19393
|
+
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);
|
|
19394
|
+
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");
|
|
19395
|
+
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");
|
|
19396
|
+
if (dataAttr) {
|
|
19397
|
+
tint2.attr(dataAttr.key, dataAttr.value);
|
|
19398
|
+
accent.attr(dataAttr.key, dataAttr.value);
|
|
19399
|
+
}
|
|
19400
|
+
}
|
|
19401
|
+
function appendTaskIcon(textEl, label, isMilestone, iconColor, textColor) {
|
|
19402
|
+
const icon = isMilestone ? "\u25C6" : "\u25CF";
|
|
19403
|
+
textEl.append("tspan").attr("fill", iconColor).text(icon);
|
|
19404
|
+
textEl.append("tspan").attr("fill", textColor).text(" " + label);
|
|
19405
|
+
}
|
|
19329
19406
|
function renderGantt(container, resolved, palette, isDark, options, exportDims) {
|
|
19330
19407
|
container.innerHTML = "";
|
|
19331
|
-
|
|
19408
|
+
bandClipCounter = 0;
|
|
19409
|
+
if (resolved.tasks.length === 0) return;
|
|
19332
19410
|
const onClickItem = options?.onClickItem;
|
|
19333
19411
|
const collapsedGroups = options?.collapsedGroups;
|
|
19334
19412
|
const onToggleGroup = options?.onToggleGroup;
|
|
@@ -19346,10 +19424,13 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19346
19424
|
const isTagMode = tagRows !== null;
|
|
19347
19425
|
const allLabels = isTagMode ? [
|
|
19348
19426
|
...rows.filter((r) => r.type === "lane-header").map((r) => r.laneName),
|
|
19349
|
-
...rows.filter((r) => r.type === "task").map((r) => r.task.task.label)
|
|
19427
|
+
...rows.filter((r) => r.type === "task").map((r) => "\u25CF " + r.task.task.label)
|
|
19350
19428
|
] : [
|
|
19351
|
-
...resolved.tasks.map((t) => t.task.label),
|
|
19352
|
-
...resolved.groups.map((g2) =>
|
|
19429
|
+
...resolved.tasks.map((t) => "\u25CF " + t.task.label),
|
|
19430
|
+
...resolved.groups.map((g2) => {
|
|
19431
|
+
const px = g2.depth <= 2 ? g2.depth * 14 : 2 * 14 + (g2.depth - 2) * 8;
|
|
19432
|
+
return " ".repeat(Math.ceil(px / 7)) + g2.name;
|
|
19433
|
+
})
|
|
19353
19434
|
];
|
|
19354
19435
|
const maxLabelLen = Math.max(...allLabels.map((l) => l.length), 10);
|
|
19355
19436
|
const leftMargin = Math.max(MIN_LEFT_MARGIN, maxLabelLen * 7 + 30);
|
|
@@ -19358,9 +19439,12 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19358
19439
|
const titleHeight = title ? 50 : 20;
|
|
19359
19440
|
const tagLegendReserve = resolved.tagGroups.length > 0 ? LEGEND_HEIGHT + 8 : 0;
|
|
19360
19441
|
const topDateLabelReserve = 22;
|
|
19361
|
-
const
|
|
19442
|
+
const hasOverheadLabels = resolved.markers.length > 0 || resolved.eras.length > 0;
|
|
19443
|
+
const markerLabelReserve = hasOverheadLabels ? 18 : 0;
|
|
19444
|
+
const CONTENT_TOP_PAD = 16;
|
|
19445
|
+
const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve + markerLabelReserve;
|
|
19362
19446
|
const contentH = isTagMode ? totalRows * (BAR_H + ROW_GAP) : totalRows * (BAR_H + ROW_GAP) + GROUP_GAP2 * resolved.groups.length;
|
|
19363
|
-
const innerHeight = contentH;
|
|
19447
|
+
const innerHeight = CONTENT_TOP_PAD + contentH;
|
|
19364
19448
|
const outerHeight = marginTop + innerHeight + BOTTOM_MARGIN;
|
|
19365
19449
|
const containerWidth = exportDims?.width ?? (container.clientWidth || 800);
|
|
19366
19450
|
const innerWidth = containerWidth - leftMargin - RIGHT_MARGIN;
|
|
@@ -19386,6 +19470,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19386
19470
|
isDark,
|
|
19387
19471
|
hasCriticalPath,
|
|
19388
19472
|
criticalPathActive,
|
|
19473
|
+
resolved.options.optionLineNumbers,
|
|
19389
19474
|
(groupName) => {
|
|
19390
19475
|
currentActiveGroup = currentActiveGroup?.toLowerCase() === groupName.toLowerCase() ? null : groupName;
|
|
19391
19476
|
if (onActiveGroupChange) onActiveGroupChange(currentActiveGroup);
|
|
@@ -19398,7 +19483,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19398
19483
|
},
|
|
19399
19484
|
currentSwimlaneGroup,
|
|
19400
19485
|
onSwimlaneChange,
|
|
19401
|
-
viewMode
|
|
19486
|
+
viewMode,
|
|
19487
|
+
resolved.tasks
|
|
19402
19488
|
);
|
|
19403
19489
|
}
|
|
19404
19490
|
}
|
|
@@ -19423,7 +19509,25 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19423
19509
|
renderTimeScaleHorizontal(g, xScale, innerWidth, innerHeight, palette.text);
|
|
19424
19510
|
renderWeekendBands(g, resolved, xScale, innerHeight, palette, isDark);
|
|
19425
19511
|
renderHolidayBands(g, svg, resolved, xScale, innerHeight, palette, isDark, marginTop - 4, leftMargin, onClickItem);
|
|
19426
|
-
renderErasAndMarkers(g, resolved, xScale, innerHeight, palette);
|
|
19512
|
+
renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette);
|
|
19513
|
+
let todayDate = null;
|
|
19514
|
+
let todayX = -1;
|
|
19515
|
+
const todayColor = palette.accent || "#e74c3c";
|
|
19516
|
+
const todayMarkerLineNum = resolved.options.optionLineNumbers["today-marker"];
|
|
19517
|
+
if (resolved.options.todayMarker !== "off") {
|
|
19518
|
+
if (resolved.options.todayMarker === "on") {
|
|
19519
|
+
todayDate = /* @__PURE__ */ new Date();
|
|
19520
|
+
} else {
|
|
19521
|
+
todayDate = /* @__PURE__ */ new Date(resolved.options.todayMarker + "T00:00:00");
|
|
19522
|
+
}
|
|
19523
|
+
todayX = xScale(dateToFractionalYear(todayDate));
|
|
19524
|
+
if (todayX >= 0 && todayX <= innerWidth) {
|
|
19525
|
+
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");
|
|
19526
|
+
if (todayMarkerLineNum) todayLine.attr("data-line-number", String(todayMarkerLineNum));
|
|
19527
|
+
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");
|
|
19528
|
+
if (todayMarkerLineNum) todayLabel.attr("data-line-number", String(todayMarkerLineNum));
|
|
19529
|
+
}
|
|
19530
|
+
}
|
|
19427
19531
|
const taskPositions = /* @__PURE__ */ new Map();
|
|
19428
19532
|
const groupPositions = /* @__PURE__ */ new Map();
|
|
19429
19533
|
const lanePositions = /* @__PURE__ */ new Map();
|
|
@@ -19444,7 +19548,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19444
19548
|
}
|
|
19445
19549
|
}
|
|
19446
19550
|
}
|
|
19447
|
-
let yOffset =
|
|
19551
|
+
let yOffset = CONTENT_TOP_PAD;
|
|
19448
19552
|
for (const row of rows) {
|
|
19449
19553
|
if (row.type === "lane-header") {
|
|
19450
19554
|
const laneColor = row.laneColor === "#999999" ? palette.textMuted : row.laneColor;
|
|
@@ -19459,6 +19563,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19459
19563
|
laneBarWidth = Math.max(lx2 - lx1, 2);
|
|
19460
19564
|
}
|
|
19461
19565
|
lanePositions.set(row.laneName, { x1: lx1, x2: lx1 + laneBarWidth, y: yOffset + BAR_H / 2 });
|
|
19566
|
+
renderLabelBand(svg, marginTop + yOffset + BAR_H / 2, leftMargin, laneColor, palette, "lane", { key: "data-lane", value: row.laneName });
|
|
19462
19567
|
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", () => {
|
|
19463
19568
|
if (onToggleLane) onToggleLane(row.laneName);
|
|
19464
19569
|
}).on("mouseenter", () => {
|
|
@@ -19484,7 +19589,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19484
19589
|
});
|
|
19485
19590
|
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);
|
|
19486
19591
|
if (row.aggregateProgress !== null && row.aggregateProgress > 0) {
|
|
19487
|
-
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("
|
|
19592
|
+
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");
|
|
19488
19593
|
}
|
|
19489
19594
|
}
|
|
19490
19595
|
yOffset += BAR_H + ROW_GAP;
|
|
@@ -19493,7 +19598,9 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19493
19598
|
const isCollapsed = collapsedGroups?.has(group.name) ?? false;
|
|
19494
19599
|
const indent = " ".repeat(group.depth);
|
|
19495
19600
|
const toggleIcon = isCollapsed ? "\u25BA" : "\u25BC";
|
|
19496
|
-
const
|
|
19601
|
+
const tagColor = resolveTagColor(group.metadata, resolved.tagGroups, currentActiveGroup, true);
|
|
19602
|
+
const groupColor = tagColor && tagColor !== "#999999" ? tagColor : group.color || palette.textMuted;
|
|
19603
|
+
renderLabelBand(svg, marginTop + yOffset + BAR_H / 2, leftMargin, groupColor, palette, "group", { key: "data-group", value: group.name });
|
|
19497
19604
|
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", () => {
|
|
19498
19605
|
if (onToggleGroup) onToggleGroup(group.name);
|
|
19499
19606
|
}).on("mouseenter", () => {
|
|
@@ -19503,7 +19610,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19503
19610
|
resetHighlight(g, svg);
|
|
19504
19611
|
hideGanttDateIndicators(g);
|
|
19505
19612
|
});
|
|
19506
|
-
const
|
|
19613
|
+
const groupIndent = group.depth <= 2 ? group.depth * 14 : 2 * 14 + (group.depth - 2) * 8;
|
|
19614
|
+
const labelX = 10 + groupIndent;
|
|
19507
19615
|
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)}%` : ""));
|
|
19508
19616
|
const gStart = dateToFractionalYear(group.startDate);
|
|
19509
19617
|
const gEnd = dateToFractionalYear(group.endDate);
|
|
@@ -19521,7 +19629,12 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19521
19629
|
});
|
|
19522
19630
|
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);
|
|
19523
19631
|
if (group.progress !== null && group.progress > 0) {
|
|
19524
|
-
summaryG.append("rect").attr("x", gx1).attr("y", yOffset).attr("width", barWidth * Math.min(group.progress / 100, 1)).attr("height", BAR_H).attr("
|
|
19632
|
+
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);
|
|
19633
|
+
}
|
|
19634
|
+
const summaryLabel = group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : "");
|
|
19635
|
+
const summaryPlacement = computeBarLabel(summaryLabel, gx1, barWidth, innerWidth, palette.text);
|
|
19636
|
+
if (summaryPlacement) {
|
|
19637
|
+
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);
|
|
19525
19638
|
}
|
|
19526
19639
|
groupPositions.set(group.name, { x1: gx1, x2: gx1 + barWidth, y: yOffset + BAR_H / 2 });
|
|
19527
19640
|
} else {
|
|
@@ -19536,7 +19649,12 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19536
19649
|
});
|
|
19537
19650
|
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);
|
|
19538
19651
|
if (group.progress !== null && group.progress > 0) {
|
|
19539
|
-
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("
|
|
19652
|
+
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);
|
|
19653
|
+
}
|
|
19654
|
+
const expandedLabel = group.name + (group.progress !== null ? ` ${Math.round(group.progress)}%` : "");
|
|
19655
|
+
const expandedPlacement = computeBarLabel(expandedLabel, gx1, groupBarWidth, innerWidth, palette.text);
|
|
19656
|
+
if (expandedPlacement) {
|
|
19657
|
+
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);
|
|
19540
19658
|
}
|
|
19541
19659
|
}
|
|
19542
19660
|
}
|
|
@@ -19544,33 +19662,40 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19544
19662
|
} else if (row.type === "task") {
|
|
19545
19663
|
const rt = row.task;
|
|
19546
19664
|
const task = rt.task;
|
|
19547
|
-
const
|
|
19665
|
+
const barColor = resolveTaskColor(rt, currentActiveGroup, resolved, seriesColors2, palette);
|
|
19666
|
+
const depth = rt.groupPath.length;
|
|
19667
|
+
const indent = depth <= 2 ? depth * 14 : 2 * 14 + (depth - 2) * 8;
|
|
19668
|
+
const taskLabelX = isTagMode ? 20 : 6 + indent;
|
|
19548
19669
|
const topGroup = rt.groupPath.length > 0 ? rt.groupPath[0] : null;
|
|
19549
|
-
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").
|
|
19670
|
+
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", () => {
|
|
19550
19671
|
if (onClickItem) onClickItem(task.lineNumber);
|
|
19551
19672
|
}).on("mouseenter", () => {
|
|
19552
|
-
|
|
19673
|
+
if (rt.isMilestone) {
|
|
19674
|
+
highlightMilestone(g, svg, task.id);
|
|
19675
|
+
} else {
|
|
19676
|
+
highlightTask(g, svg, task.id);
|
|
19677
|
+
}
|
|
19553
19678
|
}).on("mouseleave", () => {
|
|
19554
19679
|
resetHighlight(g, svg);
|
|
19555
19680
|
});
|
|
19681
|
+
appendTaskIcon(taskLabel, task.label, rt.isMilestone, barColor, palette.text);
|
|
19556
19682
|
for (const [key, value] of Object.entries(rt.effectiveMetadata)) {
|
|
19557
19683
|
taskLabel.attr(`data-tag-${key}`, value.toLowerCase());
|
|
19558
19684
|
}
|
|
19559
19685
|
if (rt.isCriticalPath) {
|
|
19560
19686
|
taskLabel.attr("data-critical-path", "true");
|
|
19561
19687
|
}
|
|
19562
|
-
let barColor = resolveTaskColor(rt, currentActiveGroup, resolved, seriesColors2, palette);
|
|
19563
19688
|
if (rt.isMilestone) {
|
|
19564
19689
|
const mx = xScale(dateToFractionalYear(rt.startDate));
|
|
19565
19690
|
const my = yOffset + BAR_H / 2;
|
|
19566
|
-
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", () => {
|
|
19691
|
+
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", () => {
|
|
19567
19692
|
if (onClickItem) onClickItem(task.lineNumber);
|
|
19568
19693
|
}).on("mouseenter", () => {
|
|
19569
|
-
|
|
19694
|
+
highlightMilestone(g, svg, task.id);
|
|
19570
19695
|
showGanttDateIndicators(g, xScale, rt.startDate, null, innerHeight, barColor);
|
|
19571
19696
|
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);
|
|
19572
19697
|
}).on("mouseleave", () => {
|
|
19573
|
-
|
|
19698
|
+
resetHighlight(g, svg);
|
|
19574
19699
|
hideGanttDateIndicators(g);
|
|
19575
19700
|
g.selectAll(".gantt-milestone-hover-label").remove();
|
|
19576
19701
|
});
|
|
@@ -19637,32 +19762,40 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19637
19762
|
progGrad.append("stop").attr("offset", "100%").attr("stop-color", barColor).attr("stop-opacity", 0);
|
|
19638
19763
|
progressFill = `url(#${progGradId})`;
|
|
19639
19764
|
}
|
|
19640
|
-
taskG.append("rect").attr("class", "gantt-progress").attr("x", x1).attr("y", yOffset).attr("width", progressWidth).attr("height", BAR_H).attr("
|
|
19765
|
+
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);
|
|
19641
19766
|
}
|
|
19642
19767
|
if (rt.isCriticalPath) {
|
|
19643
19768
|
taskG.attr("data-critical-path", "true");
|
|
19644
19769
|
}
|
|
19645
|
-
const
|
|
19646
|
-
if (
|
|
19647
|
-
taskG.append("text").attr("x",
|
|
19770
|
+
const labelPlacement = computeBarLabel(task.label, x1, barWidth, innerWidth, palette.text);
|
|
19771
|
+
if (labelPlacement) {
|
|
19772
|
+
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);
|
|
19648
19773
|
}
|
|
19649
19774
|
taskPositions.set(task.id, { x1, x2: x1 + barWidth, y: yOffset + BAR_H / 2 });
|
|
19650
19775
|
}
|
|
19651
19776
|
yOffset += BAR_H + ROW_GAP;
|
|
19652
19777
|
}
|
|
19653
19778
|
}
|
|
19654
|
-
if (
|
|
19655
|
-
|
|
19656
|
-
|
|
19657
|
-
|
|
19658
|
-
|
|
19659
|
-
|
|
19660
|
-
|
|
19661
|
-
|
|
19662
|
-
|
|
19663
|
-
|
|
19664
|
-
|
|
19665
|
-
|
|
19779
|
+
if (todayDate && todayX >= 0 && todayX <= innerWidth) {
|
|
19780
|
+
const todayHoverG = g.append("g").attr("class", "gantt-today-hover").style("cursor", "pointer");
|
|
19781
|
+
todayHoverG.append("rect").attr("x", todayX - 10).attr("y", -6).attr("width", 20).attr("height", innerHeight + 16).attr("fill", "transparent").attr("pointer-events", "all");
|
|
19782
|
+
const todayDateObj = todayDate;
|
|
19783
|
+
todayHoverG.on("mouseenter", () => {
|
|
19784
|
+
g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
|
|
19785
|
+
g.selectAll(".gantt-milestone").attr("opacity", FADE_OPACITY);
|
|
19786
|
+
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
19787
|
+
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
19788
|
+
svg.selectAll(".gantt-task-label").attr("opacity", FADE_OPACITY);
|
|
19789
|
+
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
19790
|
+
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
19791
|
+
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
19792
|
+
g.selectAll(".gantt-era-group").attr("opacity", FADE_OPACITY);
|
|
19793
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
19794
|
+
showGanttDateIndicators(g, xScale, todayDateObj, null, innerHeight, todayColor);
|
|
19795
|
+
}).on("mouseleave", () => {
|
|
19796
|
+
resetHighlight(g, svg);
|
|
19797
|
+
hideGanttDateIndicators(g);
|
|
19798
|
+
});
|
|
19666
19799
|
}
|
|
19667
19800
|
if (resolved.options.dependencies) {
|
|
19668
19801
|
renderDependencyArrows(g, resolved, taskPositions, groupPositions, collapsedGroups, palette, isDark, isTagMode, lanePositions, collapsedLanes, taskLaneMap);
|
|
@@ -19779,10 +19912,10 @@ function renderDependencyArrows(g, resolved, taskPositions, groupPositions, coll
|
|
|
19779
19912
|
const path = `M ${sx} ${sy} C ${sx + cpOffset} ${sy}, ${tx - cpOffset} ${ty}, ${tx} ${ty}`;
|
|
19780
19913
|
const arrowColor = mix(palette.text, palette.bg, 50);
|
|
19781
19914
|
const isCpArrow = rt.isCriticalPath && targetTask.isCriticalPath;
|
|
19782
|
-
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);
|
|
19915
|
+
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);
|
|
19783
19916
|
const headSize = 5;
|
|
19784
19917
|
const angle = 0;
|
|
19785
|
-
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);
|
|
19918
|
+
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);
|
|
19786
19919
|
}
|
|
19787
19920
|
}
|
|
19788
19921
|
}
|
|
@@ -19803,7 +19936,9 @@ function applyCriticalPathHighlight(svg, chartG) {
|
|
|
19803
19936
|
el.attr("opacity", el.attr("data-critical-path") === "true" ? 1 : FADE_OPACITY);
|
|
19804
19937
|
});
|
|
19805
19938
|
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
19939
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
|
|
19806
19940
|
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
19941
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
|
|
19807
19942
|
chartG.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", FADE_OPACITY);
|
|
19808
19943
|
chartG.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").each(function() {
|
|
19809
19944
|
const el = d3Selection10.select(this);
|
|
@@ -19815,7 +19950,9 @@ function resetHighlightAll(svg, chartG) {
|
|
|
19815
19950
|
chartG.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", 1);
|
|
19816
19951
|
svg.selectAll(".gantt-task-label").attr("opacity", 1);
|
|
19817
19952
|
svg.selectAll(".gantt-group-label").attr("opacity", 1);
|
|
19953
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", 1);
|
|
19818
19954
|
svg.selectAll(".gantt-lane-header").attr("opacity", 1);
|
|
19955
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", 1);
|
|
19819
19956
|
chartG.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", 1);
|
|
19820
19957
|
chartG.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", 0.5);
|
|
19821
19958
|
}
|
|
@@ -19831,7 +19968,7 @@ function drawSwimlaneIcon(parent, x, y, isActive, palette) {
|
|
|
19831
19968
|
}
|
|
19832
19969
|
return iconG;
|
|
19833
19970
|
}
|
|
19834
|
-
function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode) {
|
|
19971
|
+
function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, optionLineNumbers, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode, resolvedTasks) {
|
|
19835
19972
|
const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
|
|
19836
19973
|
let visibleGroups;
|
|
19837
19974
|
if (activeGroupName) {
|
|
@@ -19841,6 +19978,28 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19841
19978
|
} else {
|
|
19842
19979
|
visibleGroups = tagGroups;
|
|
19843
19980
|
}
|
|
19981
|
+
const usedValues = /* @__PURE__ */ new Map();
|
|
19982
|
+
if (resolvedTasks) {
|
|
19983
|
+
for (const group of visibleGroups) {
|
|
19984
|
+
const key = group.name.toLowerCase();
|
|
19985
|
+
const used = /* @__PURE__ */ new Set();
|
|
19986
|
+
for (const rt of resolvedTasks) {
|
|
19987
|
+
const val = rt.effectiveMetadata[key];
|
|
19988
|
+
if (val) used.add(val.toLowerCase());
|
|
19989
|
+
}
|
|
19990
|
+
usedValues.set(key, used);
|
|
19991
|
+
}
|
|
19992
|
+
}
|
|
19993
|
+
const filteredEntries = /* @__PURE__ */ new Map();
|
|
19994
|
+
for (const group of visibleGroups) {
|
|
19995
|
+
const key = group.name.toLowerCase();
|
|
19996
|
+
const used = usedValues.get(key);
|
|
19997
|
+
if (used && used.size > 0) {
|
|
19998
|
+
filteredEntries.set(key, group.entries.filter((e) => used.has(e.value.toLowerCase())));
|
|
19999
|
+
} else {
|
|
20000
|
+
filteredEntries.set(key, group.entries);
|
|
20001
|
+
}
|
|
20002
|
+
}
|
|
19844
20003
|
const groupWidths = [];
|
|
19845
20004
|
let totalW = 0;
|
|
19846
20005
|
for (const group of visibleGroups) {
|
|
@@ -19851,8 +20010,9 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19851
20010
|
const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD + iconReserve;
|
|
19852
20011
|
let groupW = pillW;
|
|
19853
20012
|
if (isActive) {
|
|
20013
|
+
const entries = filteredEntries.get(group.name.toLowerCase()) ?? group.entries;
|
|
19854
20014
|
let entriesW = 0;
|
|
19855
|
-
for (const entry of
|
|
20015
|
+
for (const entry of entries) {
|
|
19856
20016
|
entriesW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
|
|
19857
20017
|
}
|
|
19858
20018
|
groupW = LEGEND_CAPSULE_PAD * 2 + pillW + 4 + entriesW;
|
|
@@ -19869,7 +20029,8 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19869
20029
|
if (visibleGroups.length > 0) totalW += LEGEND_GROUP_GAP;
|
|
19870
20030
|
totalW += cpPillW;
|
|
19871
20031
|
}
|
|
19872
|
-
const
|
|
20032
|
+
const containerWidth = chartLeftMargin + chartInnerWidth + RIGHT_MARGIN;
|
|
20033
|
+
const legendX = (containerWidth - totalW) / 2;
|
|
19873
20034
|
const legendRow = svg.append("g").attr("class", "gantt-tag-legend-container").attr("transform", `translate(${legendX}, ${legendY})`);
|
|
19874
20035
|
let cursorX = 0;
|
|
19875
20036
|
for (let i = 0; i < visibleGroups.length; i++) {
|
|
@@ -19881,7 +20042,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19881
20042
|
const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD + iconReserve;
|
|
19882
20043
|
const pillH = isActive ? LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2 : LEGEND_HEIGHT;
|
|
19883
20044
|
const groupW = groupWidths[i];
|
|
19884
|
-
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", () => {
|
|
20045
|
+
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", () => {
|
|
19885
20046
|
if (onToggle) onToggle(group.name);
|
|
19886
20047
|
});
|
|
19887
20048
|
if (isActive) {
|
|
@@ -19911,10 +20072,11 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19911
20072
|
}
|
|
19912
20073
|
if (isActive) {
|
|
19913
20074
|
const tagKey = group.name.toLowerCase();
|
|
20075
|
+
const entries = filteredEntries.get(tagKey) ?? group.entries;
|
|
19914
20076
|
let ex = pillXOff + pillW + LEGEND_CAPSULE_PAD + 4;
|
|
19915
|
-
for (const entry of
|
|
20077
|
+
for (const entry of entries) {
|
|
19916
20078
|
const entryValue = entry.value.toLowerCase();
|
|
19917
|
-
const entryG = gEl.append("g").attr("class", "gantt-legend-entry").style("cursor", "pointer");
|
|
20079
|
+
const entryG = gEl.append("g").attr("class", "gantt-legend-entry").attr("data-line-number", String(entry.lineNumber)).style("cursor", "pointer");
|
|
19918
20080
|
entryG.append("circle").attr("cx", ex + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
|
|
19919
20081
|
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);
|
|
19920
20082
|
entryG.on("mouseenter", () => {
|
|
@@ -19950,9 +20112,11 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19950
20112
|
cursorX += groupW + LEGEND_GROUP_GAP;
|
|
19951
20113
|
}
|
|
19952
20114
|
if (hasCriticalPath) {
|
|
20115
|
+
const cpLineNum = optionLineNumbers["critical-path"];
|
|
19953
20116
|
const cpG = legendRow.append("g").attr("transform", `translate(${cursorX}, 0)`).attr("class", "gantt-legend-critical-path").style("cursor", "pointer").on("click", () => {
|
|
19954
20117
|
if (onToggleCriticalPath) onToggleCriticalPath();
|
|
19955
20118
|
});
|
|
20119
|
+
if (cpLineNum) cpG.attr("data-line-number", String(cpLineNum));
|
|
19956
20120
|
cpG.append("rect").attr("width", cpPillW).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", criticalPathActive ? palette.bg : groupBg);
|
|
19957
20121
|
if (criticalPathActive) {
|
|
19958
20122
|
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);
|
|
@@ -19970,7 +20134,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19970
20134
|
});
|
|
19971
20135
|
}
|
|
19972
20136
|
}
|
|
19973
|
-
function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
|
|
20137
|
+
function renderErasAndMarkers(g, svg, resolved, xScale, innerHeight, palette) {
|
|
19974
20138
|
for (let i = 0; i < resolved.eras.length; i++) {
|
|
19975
20139
|
const era = resolved.eras[i];
|
|
19976
20140
|
const color = era.color || ERA_COLORS[i % ERA_COLORS.length];
|
|
@@ -19981,13 +20145,23 @@ function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
|
|
|
19981
20145
|
const hoverEraOpacity = 0.16;
|
|
19982
20146
|
const eraStartDate = parseDateStringToDate(era.startDate);
|
|
19983
20147
|
const eraEndDate = parseDateStringToDate(era.endDate);
|
|
19984
|
-
const eraG = g.append("g").attr("class", "gantt-era-group");
|
|
20148
|
+
const eraG = g.append("g").attr("class", "gantt-era-group").attr("data-line-number", String(era.lineNumber));
|
|
19985
20149
|
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);
|
|
19986
|
-
eraG.append("text").attr("class", "gantt-era-label").attr("x", (sx + ex) / 2).attr("y",
|
|
20150
|
+
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);
|
|
19987
20151
|
eraG.on("mouseenter", () => {
|
|
20152
|
+
g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
|
|
20153
|
+
g.selectAll(".gantt-milestone").attr("opacity", FADE_OPACITY);
|
|
20154
|
+
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20155
|
+
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20156
|
+
svg.selectAll(".gantt-task-label").attr("opacity", FADE_OPACITY);
|
|
20157
|
+
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20158
|
+
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
20159
|
+
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
20160
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
19988
20161
|
eraRect.attr("opacity", hoverEraOpacity);
|
|
19989
20162
|
showGanttDateIndicators(g, xScale, eraStartDate, eraEndDate, innerHeight, color);
|
|
19990
20163
|
}).on("mouseleave", () => {
|
|
20164
|
+
resetHighlight(g, svg);
|
|
19991
20165
|
eraRect.attr("opacity", baseEraOpacity);
|
|
19992
20166
|
hideGanttDateIndicators(g);
|
|
19993
20167
|
});
|
|
@@ -19996,13 +20170,35 @@ function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
|
|
|
19996
20170
|
const color = marker.color || palette.accent || "#d08770";
|
|
19997
20171
|
const mx = xScale(parseDateToFractionalYear(marker.date));
|
|
19998
20172
|
const markerDate = parseDateStringToDate(marker.date);
|
|
19999
|
-
const
|
|
20000
|
-
|
|
20001
|
-
|
|
20002
|
-
markerG.append("
|
|
20173
|
+
const diamondSize = 5;
|
|
20174
|
+
const labelY = -24;
|
|
20175
|
+
const diamondY = labelY + 14;
|
|
20176
|
+
const markerG = g.append("g").attr("class", "gantt-marker-group").attr("data-line-number", String(marker.lineNumber)).style("cursor", "pointer");
|
|
20177
|
+
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");
|
|
20178
|
+
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);
|
|
20179
|
+
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);
|
|
20180
|
+
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);
|
|
20181
|
+
const markerLine = markerG.select(".gantt-marker");
|
|
20182
|
+
const markerDiamond = markerG.select("path");
|
|
20003
20183
|
markerG.on("mouseenter", () => {
|
|
20004
|
-
|
|
20184
|
+
g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
|
|
20185
|
+
g.selectAll(".gantt-milestone").attr("opacity", FADE_OPACITY);
|
|
20186
|
+
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20187
|
+
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20188
|
+
svg.selectAll(".gantt-task-label").attr("opacity", FADE_OPACITY);
|
|
20189
|
+
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20190
|
+
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
20191
|
+
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
20192
|
+
g.selectAll(".gantt-era-group").attr("opacity", FADE_OPACITY);
|
|
20193
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20194
|
+
markerG.attr("opacity", 1);
|
|
20195
|
+
markerLine.attr("opacity", 0.8);
|
|
20196
|
+
markerDiamond.attr("opacity", 0);
|
|
20197
|
+
showGanttDateIndicators(g, xScale, markerDate, null, innerHeight, color, { skipStartLine: true });
|
|
20005
20198
|
}).on("mouseleave", () => {
|
|
20199
|
+
resetHighlight(g, svg);
|
|
20200
|
+
markerLine.attr("opacity", 0.5);
|
|
20201
|
+
markerDiamond.attr("opacity", 0.9);
|
|
20006
20202
|
hideGanttDateIndicators(g);
|
|
20007
20203
|
});
|
|
20008
20204
|
}
|
|
@@ -20015,11 +20211,7 @@ function parseDateStringToDate(s) {
|
|
|
20015
20211
|
return new Date(year, month, day);
|
|
20016
20212
|
}
|
|
20017
20213
|
function parseDateToFractionalYear(s) {
|
|
20018
|
-
|
|
20019
|
-
const year = parts[0];
|
|
20020
|
-
const month = parts.length >= 2 ? parts[1] : 1;
|
|
20021
|
-
const day = parts.length >= 3 ? parts[2] : 1;
|
|
20022
|
-
return year + (month - 1) / 12 + (day - 1) / 365;
|
|
20214
|
+
return dateToFractionalYear(parseDateStringToDate(s));
|
|
20023
20215
|
}
|
|
20024
20216
|
function highlightDeps(g, svg, taskId, resolved) {
|
|
20025
20217
|
const related = /* @__PURE__ */ new Set([taskId]);
|
|
@@ -20053,6 +20245,7 @@ function highlightDeps(g, svg, taskId, resolved) {
|
|
|
20053
20245
|
const isRelated = from && related.has(from) || to && related.has(to);
|
|
20054
20246
|
el.attr("opacity", isRelated ? 0.5 : FADE_OPACITY);
|
|
20055
20247
|
});
|
|
20248
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20056
20249
|
}
|
|
20057
20250
|
function highlightGroup(g, svg, groupName) {
|
|
20058
20251
|
g.selectAll(".gantt-task").each(function() {
|
|
@@ -20075,8 +20268,14 @@ function highlightGroup(g, svg, groupName) {
|
|
|
20075
20268
|
const el = d3Selection10.select(this);
|
|
20076
20269
|
el.attr("opacity", el.attr("data-group") === groupName ? 1 : FADE_OPACITY);
|
|
20077
20270
|
});
|
|
20271
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").each(function() {
|
|
20272
|
+
const el = d3Selection10.select(this);
|
|
20273
|
+
el.attr("opacity", el.attr("data-group") === groupName ? 1 : FADE_OPACITY);
|
|
20274
|
+
});
|
|
20078
20275
|
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20276
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
|
|
20079
20277
|
g.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", FADE_OPACITY);
|
|
20278
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20080
20279
|
}
|
|
20081
20280
|
function highlightLane(g, svg, tagKey, laneName) {
|
|
20082
20281
|
const tagAttr = `data-tag-${tagKey}`;
|
|
@@ -20101,8 +20300,14 @@ function highlightLane(g, svg, tagKey, laneName) {
|
|
|
20101
20300
|
const el = d3Selection10.select(this);
|
|
20102
20301
|
el.attr("opacity", el.attr("data-lane") === laneName ? 1 : FADE_OPACITY);
|
|
20103
20302
|
});
|
|
20303
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").each(function() {
|
|
20304
|
+
const el = d3Selection10.select(this);
|
|
20305
|
+
el.attr("opacity", el.attr("data-lane") === laneName ? 1 : FADE_OPACITY);
|
|
20306
|
+
});
|
|
20104
20307
|
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20105
20308
|
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20309
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
|
|
20310
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20106
20311
|
}
|
|
20107
20312
|
function highlightTask(g, svg, taskId) {
|
|
20108
20313
|
g.selectAll(".gantt-task").each(function() {
|
|
@@ -20116,9 +20321,31 @@ function highlightTask(g, svg, taskId) {
|
|
|
20116
20321
|
});
|
|
20117
20322
|
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20118
20323
|
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20324
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
|
|
20119
20325
|
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20326
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
|
|
20120
20327
|
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
20121
20328
|
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
20329
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20330
|
+
}
|
|
20331
|
+
function highlightMilestone(g, svg, taskId) {
|
|
20332
|
+
g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
|
|
20333
|
+
g.selectAll(".gantt-milestone").each(function() {
|
|
20334
|
+
const el = d3Selection10.select(this);
|
|
20335
|
+
el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
|
|
20336
|
+
});
|
|
20337
|
+
svg.selectAll(".gantt-task-label").each(function() {
|
|
20338
|
+
const el = d3Selection10.select(this);
|
|
20339
|
+
el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
|
|
20340
|
+
});
|
|
20341
|
+
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20342
|
+
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20343
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", FADE_OPACITY);
|
|
20344
|
+
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20345
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", FADE_OPACITY);
|
|
20346
|
+
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
20347
|
+
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
20348
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20122
20349
|
}
|
|
20123
20350
|
function highlightTaskLabel(svg, lineNumber) {
|
|
20124
20351
|
const ln = String(lineNumber);
|
|
@@ -20134,10 +20361,14 @@ function resetHighlight(g, svg) {
|
|
|
20134
20361
|
g.selectAll(".gantt-task, .gantt-milestone").attr("opacity", 1);
|
|
20135
20362
|
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", 1);
|
|
20136
20363
|
svg.selectAll(".gantt-group-label").attr("opacity", 1);
|
|
20364
|
+
svg.selectAll(".gantt-group-band-bg, .gantt-group-band-accent").attr("opacity", 1);
|
|
20137
20365
|
svg.selectAll(".gantt-task-label").attr("opacity", 1);
|
|
20138
20366
|
svg.selectAll(".gantt-lane-header").attr("opacity", 1);
|
|
20367
|
+
svg.selectAll(".gantt-lane-band-bg, .gantt-lane-band-accent").attr("opacity", 1);
|
|
20139
20368
|
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", 1);
|
|
20140
20369
|
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", 0.5);
|
|
20370
|
+
g.selectAll(".gantt-marker-group").attr("opacity", 1);
|
|
20371
|
+
g.selectAll(".gantt-era-group").attr("opacity", 1);
|
|
20141
20372
|
}
|
|
20142
20373
|
function buildRowList(resolved, collapsedGroups) {
|
|
20143
20374
|
const rows = [];
|
|
@@ -20216,9 +20447,9 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
|
|
|
20216
20447
|
for (const entry of tagGroup.entries) {
|
|
20217
20448
|
const entryKey = entry.value.toLowerCase();
|
|
20218
20449
|
const tasks = buckets.get(entryKey) ?? [];
|
|
20450
|
+
if (tasks.length === 0) continue;
|
|
20219
20451
|
tasks.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
|
|
20220
|
-
const
|
|
20221
|
-
const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
|
|
20452
|
+
const aggregateProgress = durationWeightedProgress(tasks);
|
|
20222
20453
|
const laneStartDate = tasks.length > 0 ? new Date(Math.min(...tasks.map((t) => t.startDate.getTime()))) : null;
|
|
20223
20454
|
const laneEndDate = tasks.length > 0 ? new Date(Math.max(...tasks.map((t) => t.endDate.getTime()))) : null;
|
|
20224
20455
|
const isCollapsed = collapsedLanes?.has(entry.value) ?? false;
|
|
@@ -20240,8 +20471,7 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
|
|
|
20240
20471
|
}
|
|
20241
20472
|
if (unbucketed.length > 0) {
|
|
20242
20473
|
unbucketed.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
|
|
20243
|
-
const
|
|
20244
|
-
const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
|
|
20474
|
+
const aggregateProgress = durationWeightedProgress(unbucketed);
|
|
20245
20475
|
const noLaneStartDate = unbucketed.length > 0 ? new Date(Math.min(...unbucketed.map((t) => t.startDate.getTime()))) : null;
|
|
20246
20476
|
const noLaneEndDate = unbucketed.length > 0 ? new Date(Math.max(...unbucketed.map((t) => t.endDate.getTime()))) : null;
|
|
20247
20477
|
const noLaneName = `No ${tagGroup.name}`;
|
|
@@ -20264,6 +20494,20 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
|
|
|
20264
20494
|
}
|
|
20265
20495
|
return rows;
|
|
20266
20496
|
}
|
|
20497
|
+
function durationWeightedProgress(tasks) {
|
|
20498
|
+
let totalDuration = 0;
|
|
20499
|
+
let totalProgress = 0;
|
|
20500
|
+
let hasProgress = false;
|
|
20501
|
+
for (const rt of tasks) {
|
|
20502
|
+
const dur = rt.endDate.getTime() - rt.startDate.getTime();
|
|
20503
|
+
totalDuration += dur;
|
|
20504
|
+
if (rt.task.progress !== null) {
|
|
20505
|
+
totalProgress += rt.task.progress * dur;
|
|
20506
|
+
hasProgress = true;
|
|
20507
|
+
}
|
|
20508
|
+
}
|
|
20509
|
+
return hasProgress && totalDuration > 0 ? totalProgress / totalDuration : null;
|
|
20510
|
+
}
|
|
20267
20511
|
function dateToFractionalYear(d) {
|
|
20268
20512
|
const y = d.getFullYear();
|
|
20269
20513
|
const startOfYear = new Date(y, 0, 1);
|
|
@@ -20278,21 +20522,43 @@ function diamondPoints(cx, cy, size) {
|
|
|
20278
20522
|
function formatGanttDate(d) {
|
|
20279
20523
|
return `${MONTH_ABBR[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`;
|
|
20280
20524
|
}
|
|
20281
|
-
function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, color) {
|
|
20525
|
+
function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, color, options) {
|
|
20282
20526
|
g.selectAll(".gantt-scale-tick").attr("opacity", 0.05);
|
|
20283
20527
|
g.selectAll(".gantt-today").attr("opacity", 0.05);
|
|
20528
|
+
const hg = g.append("g").attr("class", "gantt-hover-date").attr("pointer-events", "none");
|
|
20284
20529
|
const tickLen = 6;
|
|
20285
20530
|
const startPos = xScale(dateToFractionalYear(startDate));
|
|
20286
20531
|
const startLabel = formatGanttDate(startDate);
|
|
20287
|
-
|
|
20288
|
-
|
|
20289
|
-
|
|
20532
|
+
if (!options?.skipStartLine) {
|
|
20533
|
+
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);
|
|
20534
|
+
}
|
|
20535
|
+
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);
|
|
20536
|
+
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);
|
|
20290
20537
|
if (endDate && endDate.getTime() !== startDate.getTime()) {
|
|
20291
20538
|
const endPos = xScale(dateToFractionalYear(endDate));
|
|
20292
20539
|
const endLabel = formatGanttDate(endDate);
|
|
20293
|
-
|
|
20294
|
-
|
|
20295
|
-
|
|
20540
|
+
const minLabelGap = 90;
|
|
20541
|
+
const gap = endPos - startPos;
|
|
20542
|
+
let startLabelX = startPos;
|
|
20543
|
+
let endLabelX = endPos;
|
|
20544
|
+
let startAnchor = "middle";
|
|
20545
|
+
let endAnchor = "middle";
|
|
20546
|
+
if (gap < minLabelGap) {
|
|
20547
|
+
const mid = (startPos + endPos) / 2;
|
|
20548
|
+
startLabelX = mid - minLabelGap / 2;
|
|
20549
|
+
endLabelX = mid + minLabelGap / 2;
|
|
20550
|
+
startAnchor = "middle";
|
|
20551
|
+
endAnchor = "middle";
|
|
20552
|
+
}
|
|
20553
|
+
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);
|
|
20554
|
+
hg.selectAll("text.gantt-hover-date").each(function() {
|
|
20555
|
+
const el = d3Selection10.select(this);
|
|
20556
|
+
if (el.text() === startLabel) {
|
|
20557
|
+
el.attr("x", startLabelX).attr("text-anchor", startAnchor);
|
|
20558
|
+
}
|
|
20559
|
+
});
|
|
20560
|
+
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);
|
|
20561
|
+
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);
|
|
20296
20562
|
}
|
|
20297
20563
|
}
|
|
20298
20564
|
function hideGanttDateIndicators(g) {
|
|
@@ -20337,7 +20603,7 @@ function renderTimeScaleHorizontal(g, scale, innerWidth, innerHeight, textColor)
|
|
|
20337
20603
|
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);
|
|
20338
20604
|
}
|
|
20339
20605
|
}
|
|
20340
|
-
var 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;
|
|
20606
|
+
var 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;
|
|
20341
20607
|
var init_renderer9 = __esm({
|
|
20342
20608
|
"src/gantt/renderer.ts"() {
|
|
20343
20609
|
"use strict";
|
|
@@ -20354,6 +20620,12 @@ var init_renderer9 = __esm({
|
|
|
20354
20620
|
MIN_LEFT_MARGIN = 120;
|
|
20355
20621
|
BOTTOM_MARGIN = 40;
|
|
20356
20622
|
RIGHT_MARGIN = 20;
|
|
20623
|
+
CHAR_W = 6.5;
|
|
20624
|
+
LABEL_PAD = 8;
|
|
20625
|
+
LABEL_GAP = 5;
|
|
20626
|
+
BAND_ACCENT_W = 4;
|
|
20627
|
+
BAND_RADIUS = 4;
|
|
20628
|
+
bandClipCounter = 0;
|
|
20357
20629
|
JS_DAY_TO_WEEKDAY2 = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
|
|
20358
20630
|
ERA_COLORS = ["#5e81ac", "#a3be8c", "#ebcb8b", "#d08770", "#b48ead"];
|
|
20359
20631
|
FADE_OPACITY = 0.1;
|
|
@@ -20456,14 +20728,14 @@ function renderState(container, graph, layout, palette, isDark, onClickItem, exp
|
|
|
20456
20728
|
nodePositionMap.set(node.id, node);
|
|
20457
20729
|
}
|
|
20458
20730
|
const LABEL_CHAR_W = 7;
|
|
20459
|
-
const
|
|
20731
|
+
const LABEL_PAD2 = 8;
|
|
20460
20732
|
const LABEL_H = 16;
|
|
20461
20733
|
const PERP_OFFSET = 10;
|
|
20462
20734
|
const labelPositions = [];
|
|
20463
20735
|
for (let ei = 0; ei < layout.edges.length; ei++) {
|
|
20464
20736
|
const edge = layout.edges[ei];
|
|
20465
20737
|
if (!edge.label) continue;
|
|
20466
|
-
const bgW = edge.label.length * LABEL_CHAR_W +
|
|
20738
|
+
const bgW = edge.label.length * LABEL_CHAR_W + LABEL_PAD2;
|
|
20467
20739
|
let lx, ly;
|
|
20468
20740
|
if (edge.source === edge.target) {
|
|
20469
20741
|
const node = nodePositionMap.get(edge.source);
|
|
@@ -22348,7 +22620,7 @@ function parseVisualization(content, palette) {
|
|
|
22348
22620
|
continue;
|
|
22349
22621
|
}
|
|
22350
22622
|
const markerMatch = line10.match(
|
|
22351
|
-
/^marker
|
|
22623
|
+
/^marker:\s+(\d{4}(?:-\d{2})?(?:-\d{2})?)\s+(.+?)(?:\s*\(([^)]+)\))?\s*$/
|
|
22352
22624
|
);
|
|
22353
22625
|
if (markerMatch) {
|
|
22354
22626
|
const colorAnnotation = markerMatch[3]?.trim() || null;
|
|
@@ -22372,7 +22644,7 @@ function parseVisualization(content, palette) {
|
|
|
22372
22644
|
const unit = durationMatch[3];
|
|
22373
22645
|
const endDate = addDurationToDate(startDate, amount, unit);
|
|
22374
22646
|
const segments = durationMatch[5].split("|");
|
|
22375
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
|
|
22647
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
|
|
22376
22648
|
result.timelineEvents.push({
|
|
22377
22649
|
date: startDate,
|
|
22378
22650
|
endDate,
|
|
@@ -22389,7 +22661,7 @@ function parseVisualization(content, palette) {
|
|
|
22389
22661
|
);
|
|
22390
22662
|
if (rangeMatch) {
|
|
22391
22663
|
const segments = rangeMatch[4].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: rangeMatch[1],
|
|
22395
22667
|
endDate: rangeMatch[2],
|
|
@@ -22406,7 +22678,7 @@ function parseVisualization(content, palette) {
|
|
|
22406
22678
|
);
|
|
22407
22679
|
if (pointMatch) {
|
|
22408
22680
|
const segments = pointMatch[2].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: pointMatch[1],
|
|
22412
22684
|
endDate: null,
|
|
@@ -24966,12 +25238,12 @@ function renderQuadrant(container, parsed, palette, isDark, onClickItem, exportD
|
|
|
24966
25238
|
};
|
|
24967
25239
|
const LABEL_MAX_FONT = 48;
|
|
24968
25240
|
const LABEL_MIN_FONT = 14;
|
|
24969
|
-
const
|
|
25241
|
+
const LABEL_PAD2 = 40;
|
|
24970
25242
|
const CHAR_WIDTH_RATIO3 = 0.6;
|
|
24971
25243
|
const estTextWidth = (text, fontSize) => text.length * fontSize * CHAR_WIDTH_RATIO3;
|
|
24972
25244
|
const quadrantLabelLayout = (text, qw2, qh2) => {
|
|
24973
|
-
const availW = qw2 -
|
|
24974
|
-
const availH = qh2 -
|
|
25245
|
+
const availW = qw2 - LABEL_PAD2;
|
|
25246
|
+
const availH = qh2 - LABEL_PAD2;
|
|
24975
25247
|
const words = text.split(/\s+/);
|
|
24976
25248
|
if (estTextWidth(text, LABEL_MAX_FONT) <= availW) {
|
|
24977
25249
|
const fs = Math.min(LABEL_MAX_FONT, availH);
|
|
@@ -25358,9 +25630,8 @@ async function renderForExport(content, theme, palette, orgExportState, options)
|
|
|
25358
25630
|
const { renderGantt: renderGantt2 } = await Promise.resolve().then(() => (init_renderer9(), renderer_exports9));
|
|
25359
25631
|
const effectivePalette2 = await resolveExportPalette(theme, palette);
|
|
25360
25632
|
const ganttParsed = parseGantt2(content, effectivePalette2);
|
|
25361
|
-
if (ganttParsed.error) return "";
|
|
25362
25633
|
const resolved = calculateSchedule2(ganttParsed);
|
|
25363
|
-
if (resolved.
|
|
25634
|
+
if (resolved.tasks.length === 0) return "";
|
|
25364
25635
|
const EXPORT_W = 1200;
|
|
25365
25636
|
const EXPORT_H = 800;
|
|
25366
25637
|
const container2 = createExportContainer(EXPORT_W, EXPORT_H);
|