@diagrammo/dgmo 0.45.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advanced.cjs +31 -25
- package/dist/advanced.d.cts +3 -855
- package/dist/advanced.d.ts +3 -855
- package/dist/advanced.js +541 -70101
- package/dist/analyzer-UYKBXAKR.js +15 -0
- package/dist/auto.cjs +500 -341
- package/dist/auto.css +342 -159
- package/dist/auto.d.cts +10 -8
- package/dist/auto.d.ts +10 -8
- package/dist/auto.js +373 -265
- package/dist/auto.mjs +500 -341
- package/dist/block.cjs +31 -25
- package/dist/block.js +83 -69411
- package/dist/calculator-LDYQCDDY.js +11 -0
- package/dist/charts-d3-QOZHKTCT.js +25 -0
- package/dist/chunk-2DXMZRWJ.js +800 -0
- package/dist/chunk-2LACINWN.js +786 -0
- package/dist/chunk-33LCUH4H.js +893 -0
- package/dist/chunk-3CIOJ5YJ.js +35 -0
- package/dist/chunk-3HDBAXF5.js +86 -0
- package/dist/chunk-3JLKYGBQ.js +290 -0
- package/dist/chunk-3MQQNXPD.js +415 -0
- package/dist/chunk-3R5POCTZ.js +967 -0
- package/dist/chunk-3SMGIJDD.js +112 -0
- package/dist/chunk-44CM72SQ.js +409 -0
- package/dist/chunk-4GRI7MEB.js +1604 -0
- package/dist/chunk-4OHACBGX.js +606 -0
- package/dist/chunk-5DH3KYFH.js +21 -0
- package/dist/chunk-5MLYVTYM.js +76 -0
- package/dist/chunk-5QXL4NYS.js +553 -0
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-6EK55ART.js +117 -0
- package/dist/chunk-6FZYCKXB.js +2065 -0
- package/dist/chunk-6HO5ZTXK.js +277 -0
- package/dist/chunk-6KFWC4CO.js +7 -0
- package/dist/chunk-6MTWIQVO.js +121 -0
- package/dist/chunk-6NYWYNBI.js +329 -0
- package/dist/chunk-6SM5NJQH.js +620 -0
- package/dist/chunk-725LDASD.js +422 -0
- package/dist/chunk-76OWZ3BA.js +130 -0
- package/dist/chunk-7JEGTRLD.js +338 -0
- package/dist/chunk-A44USUV7.js +923 -0
- package/dist/chunk-A4GOY3PE.js +25 -0
- package/dist/chunk-A4VFQH5G.js +251 -0
- package/dist/chunk-AELOBFW5.js +375 -0
- package/dist/chunk-AN2MOO5T.js +522 -0
- package/dist/chunk-BMR4CIEO.js +41 -0
- package/dist/chunk-BS7SV7C3.js +51 -0
- package/dist/chunk-BUPDIHML.js +613 -0
- package/dist/chunk-BUVJFRPY.js +887 -0
- package/dist/chunk-C3Z4DX3V.js +112 -0
- package/dist/chunk-CI2G3OEB.js +1818 -0
- package/dist/chunk-CKNVO24D.js +54 -0
- package/dist/chunk-CUVHQHZ7.js +391 -0
- package/dist/chunk-CXK4DZH3.js +114 -0
- package/dist/chunk-DKEPS5LH.js +315 -0
- package/dist/chunk-DU7W62N5.js +487 -0
- package/dist/chunk-DZAQBW35.js +751 -0
- package/dist/chunk-EB6FPQUP.js +316 -0
- package/dist/chunk-EMS2SK26.js +376 -0
- package/dist/chunk-ERLBPDGU.js +510 -0
- package/dist/chunk-EVCVDTXE.js +732 -0
- package/dist/chunk-FCIIHT24.js +384 -0
- package/dist/chunk-FFUN5D66.js +97 -0
- package/dist/chunk-FITOLXSX.js +714 -0
- package/dist/chunk-FMDSF3OV.js +68 -0
- package/dist/chunk-FNB3PNGF.js +410 -0
- package/dist/chunk-G2H74XJZ.js +2104 -0
- package/dist/chunk-GMR5MFHJ.js +339 -0
- package/dist/chunk-H6PI3KJT.js +305 -0
- package/dist/chunk-I46X5VUA.js +59 -0
- package/dist/chunk-I5IGB56C.js +255 -0
- package/dist/chunk-IH6ZMVQ5.js +3950 -0
- package/dist/chunk-IJZFN3HM.js +463 -0
- package/dist/chunk-IPWPWM6R.js +3079 -0
- package/dist/chunk-IYULMRBE.js +23 -0
- package/dist/chunk-IZMRON6C.js +167 -0
- package/dist/chunk-J5FIXQRT.js +813 -0
- package/dist/chunk-JED6ZDEE.js +73 -0
- package/dist/chunk-JEUJOX5X.js +136 -0
- package/dist/chunk-JM4LEPY3.js +436 -0
- package/dist/chunk-L3UHP6TD.js +101 -0
- package/dist/chunk-L66GEFB6.js +524 -0
- package/dist/chunk-LIOHW36Z.js +469 -0
- package/dist/chunk-LP7EC4MI.js +16 -0
- package/dist/chunk-MITI6NOL.js +548 -0
- package/dist/chunk-MO5YHHMQ.js +168 -0
- package/dist/chunk-MZSBFAPJ.js +885 -0
- package/dist/chunk-NNAAJVBU.js +9 -0
- package/dist/chunk-NOTA6TEI.js +47 -0
- package/dist/chunk-NURGB5Z4.js +335 -0
- package/dist/chunk-NX4EQS3U.js +1062 -0
- package/dist/chunk-NXMESY4J.js +1 -0
- package/dist/chunk-OJR3MQYV.js +1458 -0
- package/dist/chunk-OURHMODR.js +454 -0
- package/dist/chunk-P235SVAH.js +1057 -0
- package/dist/chunk-P4RH4OSP.js +1038 -0
- package/dist/chunk-P6T66CCZ.js +226 -0
- package/dist/chunk-PBAHEPLQ.js +223 -0
- package/dist/chunk-PBHE5UG6.js +1530 -0
- package/dist/chunk-PDKUANDZ.js +35 -0
- package/dist/chunk-PWIHZ6NJ.js +296 -0
- package/dist/chunk-QEBUY5NY.js +1685 -0
- package/dist/chunk-QLCCJEJI.js +253 -0
- package/dist/chunk-QOOUC2J5.js +52 -0
- package/dist/chunk-QYTPPDAO.js +402 -0
- package/dist/chunk-R3XI2VC7.js +801 -0
- package/dist/chunk-RCERWOJR.js +154 -0
- package/dist/chunk-RJE5JTVK.js +362 -0
- package/dist/chunk-RTLBSQ3G.js +1155 -0
- package/dist/chunk-RUNNI6VE.js +448 -0
- package/dist/chunk-RVMRZAPB.js +354 -0
- package/dist/chunk-RYEGFMUO.js +955 -0
- package/dist/chunk-RZCF2VVM.js +528 -0
- package/dist/chunk-SML67VPV.js +1025 -0
- package/dist/chunk-SQ7IY53Q.js +44 -0
- package/dist/chunk-SQAG3UXH.js +161 -0
- package/dist/chunk-SRTU6HMQ.js +1851 -0
- package/dist/chunk-T2OE3YQB.js +550 -0
- package/dist/chunk-T3UBLACL.js +299 -0
- package/dist/chunk-TLVIHFRO.js +296 -0
- package/dist/chunk-TYS6CSS5.js +52 -0
- package/dist/chunk-U6EQI3U6.js +130 -0
- package/dist/chunk-UJ4FJLBF.js +86 -0
- package/dist/chunk-ULOEATBZ.js +347 -0
- package/dist/chunk-UNCCZ3NU.js +127 -0
- package/dist/chunk-USZW2HHU.js +197 -0
- package/dist/chunk-UWN33VNX.js +1610 -0
- package/dist/chunk-UZ7EZW3B.js +40 -0
- package/dist/chunk-VOKGH627.js +37 -0
- package/dist/chunk-WBRZJOEC.js +204 -0
- package/dist/chunk-WCSOJFU3.js +380 -0
- package/dist/chunk-WCT3NYCV.js +160 -0
- package/dist/chunk-WVAJFAIA.js +529 -0
- package/dist/chunk-X5LCYCL5.js +394 -0
- package/dist/chunk-XAKEUMEJ.js +1256 -0
- package/dist/chunk-Y2ZE3MSG.js +590 -0
- package/dist/chunk-Y3J7VR52.js +646 -0
- package/dist/chunk-Y5QGVTVC.js +33 -0
- package/dist/chunk-YAA6IID3.js +548 -0
- package/dist/chunk-YGJTDXVR.js +692 -0
- package/dist/chunk-YRYKTYMQ.js +186 -0
- package/dist/chunk-YYVTBZZC.js +61 -0
- package/dist/chunk-Z5CGSFJB.js +647 -0
- package/dist/chunk-Z72XRXD4.js +787 -0
- package/dist/chunk-ZGR7ULFJ.js +978 -0
- package/dist/chunk-ZJEL4H2R.js +851 -0
- package/dist/chunk-ZO27FGJS.js +554 -0
- package/dist/cli.cjs +112 -112
- package/dist/collapse-CLPJD2RL.js +11 -0
- package/dist/collapse-SLINJQW6.js +9 -0
- package/dist/collapse-ZWUFFCK2.js +9 -0
- package/dist/compute-G55RG53N.js +8 -0
- package/dist/dgmo-router-DZCIDUO5.js +73 -0
- package/dist/dimensions-J3FHIMJ6.js +22 -0
- package/dist/element.cjs +492 -325
- package/dist/element.js +367 -259
- package/dist/element.mjs +492 -325
- package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
- package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
- package/dist/index.cjs +34 -28
- package/dist/index.d.cts +3 -469
- package/dist/index.d.ts +3 -469
- package/dist/index.js +137 -69679
- package/dist/layout-46N4JJYK.js +17 -0
- package/dist/layout-5RA6QWR6.js +26 -0
- package/dist/layout-5T6RH5M6.js +14 -0
- package/dist/layout-7QXCNOVS.js +13 -0
- package/dist/layout-EE6GE46Y.js +17 -0
- package/dist/layout-G23F7LDH.js +9 -0
- package/dist/layout-HRBZPFCN.js +23 -0
- package/dist/layout-HWDSRX5E.js +20 -0
- package/dist/layout-HYZPWAK5.js +18 -0
- package/dist/layout-N76NR4ZO.js +19 -0
- package/dist/layout-OB3RI63S.js +14 -0
- package/dist/layout-S2QBYSK2.js +15 -0
- package/dist/layout-UM5F4DJB.js +15 -0
- package/dist/layout-search-OYV77HHS.js +1655 -0
- package/dist/load-data-EHDDZ5ZS.js +8 -0
- package/dist/palettes-PRJ2RI6D.js +56 -0
- package/dist/parser-2MFP2H2E.js +11 -0
- package/dist/parser-3STE62GB.js +21 -0
- package/dist/parser-4FL2CMDG.js +18 -0
- package/dist/parser-4OGOBJJ4.js +13 -0
- package/dist/parser-6BTRLKRZ.js +15 -0
- package/dist/parser-AQETEVRW.js +21 -0
- package/dist/parser-CFZD5IBL.js +12 -0
- package/dist/parser-FUSKFGZN.js +16 -0
- package/dist/parser-I6KXEYFK.js +13 -0
- package/dist/parser-IXSZZQHY.js +18 -0
- package/dist/parser-JAL2HD4I.js +13 -0
- package/dist/parser-KNSQSKQO.js +20 -0
- package/dist/parser-N5O4IHUN.js +13 -0
- package/dist/parser-NGU3ETO6.js +16 -0
- package/dist/parser-NRAI6O25.js +19 -0
- package/dist/parser-O5J4VBII.js +11 -0
- package/dist/parser-OV7NOBW6.js +21 -0
- package/dist/parser-PQ3POX45.js +23 -0
- package/dist/parser-TFZFBVWN.js +11 -0
- package/dist/parser-TRTHDFPP.js +18 -0
- package/dist/parser-VQP2UJ6W.js +11 -0
- package/dist/parser-XDVELHRK.js +13 -0
- package/dist/parser-XKRP7QKP.js +11 -0
- package/dist/parser-Y6WTBU76.js +14 -0
- package/dist/parser-ZSDPZYQP.js +12 -0
- package/dist/renderer-2XQQQ6QR.js +28 -0
- package/dist/renderer-5NDVNDAZ.js +26 -0
- package/dist/renderer-6EEZHMH4.js +26 -0
- package/dist/renderer-6W5462RB.js +43 -0
- package/dist/renderer-7MVLWWQD.js +22 -0
- package/dist/renderer-AWWAF6XD.js +33 -0
- package/dist/renderer-BYCNMBF7.js +34 -0
- package/dist/renderer-CLJHQPBJ.js +29 -0
- package/dist/renderer-GHPZMFU2.js +15 -0
- package/dist/renderer-JGBKUCZD.js +16 -0
- package/dist/renderer-KQB7UGDS.js +19 -0
- package/dist/renderer-PGX2TKVS.js +24 -0
- package/dist/renderer-QIWVHWZO.js +15 -0
- package/dist/renderer-RPOA5YPZ.js +35 -0
- package/dist/renderer-SMIBCRVB.js +24 -0
- package/dist/renderer-T7YKC5VP.js +28 -0
- package/dist/renderer-TOCOVLHS.js +37 -0
- package/dist/renderer-UWA5XS74.js +39 -0
- package/dist/renderer-VACVQYML.js +25 -0
- package/dist/renderer-W4M6QLIJ.js +37 -0
- package/dist/renderer-WZRREQ2P.js +23 -0
- package/dist/renderer-YY2OSF6E.js +27 -0
- package/dist/renderer-Z7I3W2PP.js +24 -0
- package/dist/renderer-ZGNTUWC4.js +22 -0
- package/dist/renderer-ZTR3DER3.js +19 -0
- package/dist/renderer-radial-C32GPJQ7.js +21 -0
- package/dist/resolver-NOAA7ID7.js +13 -0
- package/dist/state-collapse-HCGD6IVJ.js +8 -0
- package/dist/state-parser-Z2V23ATR.js +18 -0
- package/dist/state-renderer-RFYHSP4K.js +34 -0
- package/dist/themes-1-CuKpeH.d.cts +908 -0
- package/dist/themes-1-CuKpeH.d.ts +908 -0
- package/package.json +1 -1
- package/src/auto/index.ts +55 -57
- package/src/auto/shared.ts +142 -142
- package/src/auto/styles.ts +44 -199
- package/src/c4/renderer.ts +2 -2
- package/src/element/index.ts +29 -21
- package/src/infra/renderer.ts +1 -1
- package/src/render.ts +34 -14
- package/src/sitemap/renderer.ts +1 -1
- package/src/timeline/renderer.ts +1 -1
- package/src/utils/hover-styles.ts +6 -1
- package/src/utils/legend-d3.ts +1 -4
- package/src/utils/legend-svg.ts +1 -1
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NODE_STROKE_WIDTH
|
|
3
|
+
} from "./chunk-3CIOJ5YJ.js";
|
|
4
|
+
import {
|
|
5
|
+
ScaleContext
|
|
6
|
+
} from "./chunk-YYVTBZZC.js";
|
|
7
|
+
import {
|
|
8
|
+
TITLE_FONT_SIZE,
|
|
9
|
+
TITLE_FONT_WEIGHT,
|
|
10
|
+
TITLE_Y
|
|
11
|
+
} from "./chunk-A4GOY3PE.js";
|
|
12
|
+
import {
|
|
13
|
+
FONT_FAMILY
|
|
14
|
+
} from "./chunk-6KFWC4CO.js";
|
|
15
|
+
import {
|
|
16
|
+
measureText,
|
|
17
|
+
truncateText,
|
|
18
|
+
wrapTextToWidth
|
|
19
|
+
} from "./chunk-WCT3NYCV.js";
|
|
20
|
+
import {
|
|
21
|
+
VARIANTS,
|
|
22
|
+
allTasks
|
|
23
|
+
} from "./chunk-RYEGFMUO.js";
|
|
24
|
+
import {
|
|
25
|
+
contrastText,
|
|
26
|
+
mix,
|
|
27
|
+
themeBaseBg
|
|
28
|
+
} from "./chunk-P6T66CCZ.js";
|
|
29
|
+
|
|
30
|
+
// src/raci/renderer.ts
|
|
31
|
+
import * as d3Selection from "d3-selection";
|
|
32
|
+
function groupDiagnosticsByTask(parsed) {
|
|
33
|
+
const map = /* @__PURE__ */ new Map();
|
|
34
|
+
if (parsed.diagnostics.length === 0) return map;
|
|
35
|
+
const tasks = [];
|
|
36
|
+
for (const t of parsed.tasksWithoutPhase) tasks.push(t);
|
|
37
|
+
for (const p of parsed.phases) for (const t of p.tasks) tasks.push(t);
|
|
38
|
+
for (const d of parsed.diagnostics) {
|
|
39
|
+
if (d.line <= 0) continue;
|
|
40
|
+
const owner = tasks.find(
|
|
41
|
+
(t) => d.line >= t.lineNumber && d.line <= t.endLineNumber
|
|
42
|
+
);
|
|
43
|
+
if (!owner) continue;
|
|
44
|
+
let bucket = map.get(owner.id);
|
|
45
|
+
if (!bucket) {
|
|
46
|
+
bucket = { errors: [], warnings: [], firstLine: d.line };
|
|
47
|
+
map.set(owner.id, bucket);
|
|
48
|
+
} else if (d.line < bucket.firstLine) {
|
|
49
|
+
bucket.firstLine = d.line;
|
|
50
|
+
}
|
|
51
|
+
const entry = { line: d.line, message: d.message };
|
|
52
|
+
if (d.severity === "error") bucket.errors.push(entry);
|
|
53
|
+
else bucket.warnings.push(entry);
|
|
54
|
+
}
|
|
55
|
+
return map;
|
|
56
|
+
}
|
|
57
|
+
function trimTaskPrefix(message) {
|
|
58
|
+
const prefix = message.match(/^Task '[^']*'\s+(.+)$/);
|
|
59
|
+
if (prefix) {
|
|
60
|
+
const rest = prefix[1];
|
|
61
|
+
return rest.charAt(0).toUpperCase() + rest.slice(1);
|
|
62
|
+
}
|
|
63
|
+
return message.replace(/\s+on task '[^']*'/, "");
|
|
64
|
+
}
|
|
65
|
+
function topRoundedRectPath(x, y, w, h, r) {
|
|
66
|
+
const rr = Math.min(r, w / 2, h / 2);
|
|
67
|
+
return `M ${x} ${y + rr} A ${rr} ${rr} 0 0 1 ${x + rr} ${y} H ${x + w - rr} A ${rr} ${rr} 0 0 1 ${x + w} ${y + rr} V ${y + h} H ${x} Z`;
|
|
68
|
+
}
|
|
69
|
+
function sortByAlphabet(markers, alphabet) {
|
|
70
|
+
const order = new Map(
|
|
71
|
+
alphabet.map((m, i) => [m, i])
|
|
72
|
+
);
|
|
73
|
+
return [...markers].sort(
|
|
74
|
+
(a, b) => (order.get(a) ?? 99) - (order.get(b) ?? 99)
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
var MARKER_LABELS = {
|
|
78
|
+
raci: { R: "Responsible", A: "Accountable", C: "Consulted", I: "Informed" },
|
|
79
|
+
rasci: {
|
|
80
|
+
R: "Responsible",
|
|
81
|
+
A: "Accountable",
|
|
82
|
+
S: "Support",
|
|
83
|
+
C: "Consulted",
|
|
84
|
+
I: "Informed"
|
|
85
|
+
},
|
|
86
|
+
daci: { D: "Driver", A: "Approver", C: "Contributor", I: "Informed" }
|
|
87
|
+
};
|
|
88
|
+
var TITLE_AREA_HEIGHT = 50;
|
|
89
|
+
var H_MARGIN = 24;
|
|
90
|
+
var V_MARGIN = 16;
|
|
91
|
+
var LEGEND_HEIGHT = 28;
|
|
92
|
+
var LEGEND_CHIP_GAP = 8;
|
|
93
|
+
var LEGEND_CHIP_LABEL_MIN = 96;
|
|
94
|
+
var LEGEND_LETTER_CHIP_W = 28;
|
|
95
|
+
var TITLE_LEGEND_GAP = 16;
|
|
96
|
+
var LEGEND_LABEL_FONT = 12;
|
|
97
|
+
var LEGEND_LETTER_FONT = 14;
|
|
98
|
+
var HEADER_HEIGHT = 36;
|
|
99
|
+
var ROW_HEIGHT = 36;
|
|
100
|
+
var PHASE_HEIGHT = 40;
|
|
101
|
+
var TASK_LABEL_MIN = 200;
|
|
102
|
+
var TASK_LABEL_MAX = 360;
|
|
103
|
+
var ROLE_COL_MIN = 64;
|
|
104
|
+
var ROLE_COL_MAX = 120;
|
|
105
|
+
var CELL_PAD = 4;
|
|
106
|
+
var VIOLATION_LINE_HEIGHT = 14;
|
|
107
|
+
var STACK_TOP_GAP = 8;
|
|
108
|
+
var DESC_FONT = 11;
|
|
109
|
+
var COLUMN_RADIUS = 8;
|
|
110
|
+
var COLUMN_INSET = 8;
|
|
111
|
+
var COLUMN_BOTTOM_PAD = 14;
|
|
112
|
+
var MARKER_FONT = 14;
|
|
113
|
+
var MARKER_FONT_WEIGHT = 600;
|
|
114
|
+
var LABEL_FONT = 13;
|
|
115
|
+
var ROLE_HEADER_FONT = 12;
|
|
116
|
+
var PHASE_FONT = 13;
|
|
117
|
+
var TINT_PCT = 25;
|
|
118
|
+
var NODE_RADIUS = 6;
|
|
119
|
+
var AUTO_ACCENTS = [
|
|
120
|
+
"blue",
|
|
121
|
+
"orange",
|
|
122
|
+
"green",
|
|
123
|
+
"purple",
|
|
124
|
+
"yellow",
|
|
125
|
+
"cyan",
|
|
126
|
+
"red",
|
|
127
|
+
"teal"
|
|
128
|
+
];
|
|
129
|
+
function autoAccent(index, palette) {
|
|
130
|
+
return palette.colors[AUTO_ACCENTS[index % AUTO_ACCENTS.length]];
|
|
131
|
+
}
|
|
132
|
+
function markerColor(marker, palette) {
|
|
133
|
+
switch (marker) {
|
|
134
|
+
case "R":
|
|
135
|
+
return palette.colors.green;
|
|
136
|
+
case "A":
|
|
137
|
+
return palette.colors.red;
|
|
138
|
+
case "S":
|
|
139
|
+
return palette.colors.teal;
|
|
140
|
+
case "C":
|
|
141
|
+
return palette.colors.yellow;
|
|
142
|
+
case "I":
|
|
143
|
+
return palette.colors.gray;
|
|
144
|
+
case "D":
|
|
145
|
+
return palette.colors.purple;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function renderRaci(container, parsed, palette, isDark, handlers, exportDims) {
|
|
149
|
+
const opts = typeof handlers === "function" ? { onClickLine: handlers } : handlers ?? {};
|
|
150
|
+
const onClickLine = opts.onClickLine;
|
|
151
|
+
const onMarkerDragStart = opts.onMarkerDragStart;
|
|
152
|
+
const hideLegend = opts.hideLegend ?? false;
|
|
153
|
+
const collapsedPhases = opts.collapsedPhases ?? /* @__PURE__ */ new Set();
|
|
154
|
+
const hideTitle = (opts.hideTitle ?? false) || parsed.options["no-title"] === "on";
|
|
155
|
+
d3Selection.select(container).selectAll(":not([data-d3-tooltip])").remove();
|
|
156
|
+
const width = exportDims?.width ?? container.clientWidth;
|
|
157
|
+
const height = exportDims?.height ?? container.clientHeight;
|
|
158
|
+
if (width <= 0 || height <= 0) return;
|
|
159
|
+
const tasksAll = [...allTasks(parsed)];
|
|
160
|
+
if (tasksAll.length === 0 && parsed.phases.length === 0) return;
|
|
161
|
+
const solid = parsed.options["solid-fill"] === "on";
|
|
162
|
+
const surfaceBg = themeBaseBg(palette, isDark);
|
|
163
|
+
const roleCount = Math.max(1, parsed.roles.length);
|
|
164
|
+
const idealWidth = roleCount * ROLE_COL_MAX + TASK_LABEL_MAX + 2 * H_MARGIN;
|
|
165
|
+
const ctx = exportDims ? ScaleContext.identity() : ScaleContext.from(width, idealWidth);
|
|
166
|
+
const sTitleFontSize = ctx.text(TITLE_FONT_SIZE);
|
|
167
|
+
const sTitleY = ctx.structural(TITLE_Y);
|
|
168
|
+
const sHMargin = ctx.aesthetic(H_MARGIN);
|
|
169
|
+
const sVMargin = ctx.aesthetic(V_MARGIN);
|
|
170
|
+
const sTitleAreaHeight = ctx.structural(TITLE_AREA_HEIGHT);
|
|
171
|
+
const sHeaderHeight = ctx.structural(HEADER_HEIGHT);
|
|
172
|
+
const sRowHeight = ctx.structural(ROW_HEIGHT);
|
|
173
|
+
const sPhaseHeight = ctx.structural(PHASE_HEIGHT);
|
|
174
|
+
const sTaskLabelMin = ctx.structural(TASK_LABEL_MIN);
|
|
175
|
+
const sTaskLabelMax = ctx.structural(TASK_LABEL_MAX);
|
|
176
|
+
const sRoleColMin = ctx.structural(ROLE_COL_MIN);
|
|
177
|
+
const sRoleColMax = ctx.structural(ROLE_COL_MAX);
|
|
178
|
+
const sCellPad = ctx.structural(CELL_PAD);
|
|
179
|
+
const sLabelFont = ctx.text(LABEL_FONT);
|
|
180
|
+
const sRoleHeaderFont = ctx.text(ROLE_HEADER_FONT);
|
|
181
|
+
const sPhaseFont = ctx.text(PHASE_FONT);
|
|
182
|
+
const sMarkerFont = ctx.text(MARKER_FONT);
|
|
183
|
+
const sDescFont = ctx.text(DESC_FONT);
|
|
184
|
+
const sColumnRadius = ctx.structural(COLUMN_RADIUS);
|
|
185
|
+
const sColumnInset = ctx.structural(COLUMN_INSET);
|
|
186
|
+
const sColumnBottomPad = ctx.structural(COLUMN_BOTTOM_PAD);
|
|
187
|
+
const sLegendHeight = ctx.structural(LEGEND_HEIGHT);
|
|
188
|
+
const sLegendChipGap = ctx.structural(LEGEND_CHIP_GAP);
|
|
189
|
+
const sLegendChipLabelMin = ctx.structural(LEGEND_CHIP_LABEL_MIN);
|
|
190
|
+
const sLegendLetterChipW = ctx.structural(LEGEND_LETTER_CHIP_W);
|
|
191
|
+
const sTitleLegendGap = ctx.aesthetic(TITLE_LEGEND_GAP);
|
|
192
|
+
const sLegendLabelFont = ctx.text(LEGEND_LABEL_FONT);
|
|
193
|
+
const sLegendLetterFont = ctx.text(LEGEND_LETTER_FONT);
|
|
194
|
+
const sViolationLineHeight = ctx.structural(VIOLATION_LINE_HEIGHT);
|
|
195
|
+
const sStackTopGap = ctx.structural(STACK_TOP_GAP);
|
|
196
|
+
const sNodeStrokeWidth = ctx.structural(NODE_STROKE_WIDTH);
|
|
197
|
+
const sNodeRadius = ctx.structural(NODE_RADIUS);
|
|
198
|
+
const innerWidth = Math.max(0, width - 2 * sHMargin);
|
|
199
|
+
let roleColW = Math.max(
|
|
200
|
+
sRoleColMin,
|
|
201
|
+
Math.min(sRoleColMax, Math.floor((innerWidth - sTaskLabelMin) / roleCount))
|
|
202
|
+
);
|
|
203
|
+
let labelW = innerWidth - roleColW * roleCount;
|
|
204
|
+
if (labelW > sTaskLabelMax) {
|
|
205
|
+
labelW = sTaskLabelMax;
|
|
206
|
+
roleColW = Math.max(
|
|
207
|
+
sRoleColMin,
|
|
208
|
+
Math.floor((innerWidth - labelW) / roleCount)
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
if (labelW < sTaskLabelMin) labelW = sTaskLabelMin;
|
|
212
|
+
const taskDiagnostics = groupDiagnosticsByTask(parsed);
|
|
213
|
+
const phasedRows = [];
|
|
214
|
+
for (const t of parsed.tasksWithoutPhase)
|
|
215
|
+
phasedRows.push({ kind: "task", task: t });
|
|
216
|
+
for (const p of parsed.phases) {
|
|
217
|
+
const collapsed = collapsedPhases.has(p.id);
|
|
218
|
+
phasedRows.push({ kind: "phase", phase: p, collapsed });
|
|
219
|
+
if (!collapsed) {
|
|
220
|
+
for (const t of p.tasks) phasedRows.push({ kind: "task", task: t });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const hasTopRow = !hideTitle || !hideLegend;
|
|
224
|
+
const titleArea = hasTopRow ? sTitleAreaHeight : sVMargin;
|
|
225
|
+
const legendY = (sTitleAreaHeight - sLegendHeight) / 2;
|
|
226
|
+
const headerY = titleArea + sVMargin;
|
|
227
|
+
const bodyTop = headerY + sHeaderHeight;
|
|
228
|
+
let cursorY = bodyTop;
|
|
229
|
+
const rowYs = [];
|
|
230
|
+
const labelMaxW = labelW - 16;
|
|
231
|
+
const taskRowContent = /* @__PURE__ */ new Map();
|
|
232
|
+
for (const row of phasedRows) {
|
|
233
|
+
if (row.kind === "task") {
|
|
234
|
+
const bucket = taskDiagnostics.get(row.task.id);
|
|
235
|
+
taskRowContent.set(
|
|
236
|
+
row.task.id,
|
|
237
|
+
prepareRowContent(
|
|
238
|
+
row.task,
|
|
239
|
+
bucket,
|
|
240
|
+
labelMaxW,
|
|
241
|
+
sLabelFont,
|
|
242
|
+
sDescFont,
|
|
243
|
+
sRowHeight,
|
|
244
|
+
sStackTopGap,
|
|
245
|
+
sViolationLineHeight
|
|
246
|
+
)
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
for (const row of phasedRows) {
|
|
251
|
+
rowYs.push(cursorY);
|
|
252
|
+
if (row.kind === "phase") {
|
|
253
|
+
cursorY += sPhaseHeight;
|
|
254
|
+
} else {
|
|
255
|
+
const content = taskRowContent.get(row.task.id);
|
|
256
|
+
cursorY += content?.rowHeight ?? sRowHeight;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const colBottomY = cursorY + sColumnBottomPad;
|
|
260
|
+
const totalHeight = colBottomY + sVMargin;
|
|
261
|
+
const svgHeight = exportDims ? totalHeight : Math.max(height, totalHeight);
|
|
262
|
+
const svg = d3Selection.select(container).append("svg").attr("xmlns", "http://www.w3.org/2000/svg").attr("width", width).attr("height", svgHeight).attr("viewBox", `0 0 ${width} ${svgHeight}`).attr("preserveAspectRatio", "xMidYMin meet").attr("font-family", FONT_FAMILY).style("background", "transparent");
|
|
263
|
+
if (ctx.isBelowFloor) {
|
|
264
|
+
svg.attr("width", "100%");
|
|
265
|
+
}
|
|
266
|
+
const variantLabels = MARKER_LABELS[parsed.variant];
|
|
267
|
+
const legendMarkers = Object.keys(variantLabels).filter(
|
|
268
|
+
(m) => variantLabels[m] !== void 0
|
|
269
|
+
);
|
|
270
|
+
const numChips = legendMarkers.length;
|
|
271
|
+
const chipGapTotal = sLegendChipGap * Math.max(0, numChips - 1);
|
|
272
|
+
const longestLabelPx = legendMarkers.reduce(
|
|
273
|
+
(n, m) => Math.max(n, measureText(variantLabels[m] ?? "", sLegendLabelFont)),
|
|
274
|
+
0
|
|
275
|
+
);
|
|
276
|
+
const fullChipW = Math.max(
|
|
277
|
+
sLegendChipLabelMin,
|
|
278
|
+
Math.ceil(4 + 24 + 8 + longestLabelPx + 8)
|
|
279
|
+
);
|
|
280
|
+
const fullLegendW = numChips * fullChipW + chipGapTotal;
|
|
281
|
+
const letterLegendW = numChips * sLegendLetterChipW + chipGapTotal;
|
|
282
|
+
const titleEstW = parsed.title && !hideTitle ? measureText(parsed.title, sTitleFontSize) : 0;
|
|
283
|
+
let legendMode = "full";
|
|
284
|
+
let legendTotalW = fullLegendW;
|
|
285
|
+
let legendChipW = fullChipW;
|
|
286
|
+
if (!hideLegend) {
|
|
287
|
+
const remaining = innerWidth - titleEstW - sTitleLegendGap;
|
|
288
|
+
if (remaining < fullLegendW) {
|
|
289
|
+
legendMode = "letters";
|
|
290
|
+
legendTotalW = letterLegendW;
|
|
291
|
+
legendChipW = sLegendLetterChipW;
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
legendTotalW = 0;
|
|
295
|
+
}
|
|
296
|
+
const legendX = sHMargin + innerWidth - legendTotalW;
|
|
297
|
+
const titleMaxW = hideLegend ? innerWidth : legendX - sHMargin - sTitleLegendGap;
|
|
298
|
+
if (parsed.title && !hideTitle) {
|
|
299
|
+
svg.append("text").attr("x", sHMargin).attr("y", sTitleY).attr("text-anchor", "start").attr("font-size", sTitleFontSize).attr("font-weight", TITLE_FONT_WEIGHT).attr("fill", palette.text).text(truncateText(parsed.title, sTitleFontSize, titleMaxW));
|
|
300
|
+
}
|
|
301
|
+
if (!hideLegend && numChips > 0) {
|
|
302
|
+
renderLegend(
|
|
303
|
+
svg,
|
|
304
|
+
legendMarkers,
|
|
305
|
+
parsed.variant,
|
|
306
|
+
legendX,
|
|
307
|
+
legendY,
|
|
308
|
+
legendMode,
|
|
309
|
+
legendChipW,
|
|
310
|
+
palette,
|
|
311
|
+
surfaceBg,
|
|
312
|
+
solid,
|
|
313
|
+
onMarkerDragStart,
|
|
314
|
+
sLegendChipGap,
|
|
315
|
+
sLegendHeight,
|
|
316
|
+
sNodeRadius,
|
|
317
|
+
sNodeStrokeWidth,
|
|
318
|
+
sLegendLetterFont,
|
|
319
|
+
sLegendLabelFont
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
const roleX = (i) => sHMargin + labelW + i * roleColW;
|
|
323
|
+
const colTotalHeight = colBottomY - headerY;
|
|
324
|
+
if (parsed.phases.length === 0) {
|
|
325
|
+
const bandsG = svg.append("g").attr("class", "raci-row-bands");
|
|
326
|
+
phasedRows.forEach((row, i) => {
|
|
327
|
+
if (row.kind !== "task") return;
|
|
328
|
+
const taskIdx = parsed.tasksWithoutPhase.indexOf(row.task);
|
|
329
|
+
if (taskIdx < 0) return;
|
|
330
|
+
const yTop = rowYs[i];
|
|
331
|
+
const rh = taskRowContent.get(row.task.id)?.rowHeight ?? sRowHeight;
|
|
332
|
+
bandsG.append("rect").attr("class", "raci-row-band").attr("data-task-id", row.task.id).attr("x", sHMargin).attr("y", yTop + 2).attr("width", innerWidth).attr("height", rh - 4).attr("rx", 6).attr("fill", mix(autoAccent(taskIdx, palette), surfaceBg, 12));
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
const columnsG = svg.append("g").attr("class", "raci-columns");
|
|
336
|
+
parsed.roles.forEach((roleId, i) => {
|
|
337
|
+
const cx = roleX(i) + sColumnInset;
|
|
338
|
+
const cw = roleColW - 2 * sColumnInset;
|
|
339
|
+
const roleColor = parsed.roleColors[i] ?? autoAccent(i, palette);
|
|
340
|
+
const bodyFill = mix(roleColor, themeBaseBg(palette, isDark), 16);
|
|
341
|
+
const headerFill = mix(
|
|
342
|
+
roleColor,
|
|
343
|
+
themeBaseBg(palette, isDark),
|
|
344
|
+
30
|
|
345
|
+
);
|
|
346
|
+
const colG = columnsG.append("g").attr("class", "raci-column").attr("data-role-id", roleId);
|
|
347
|
+
colG.append("rect").attr("class", "raci-column-body").attr("x", cx).attr("y", headerY).attr("width", cw).attr("height", colTotalHeight).attr("rx", sColumnRadius).attr("fill", bodyFill);
|
|
348
|
+
colG.append("path").attr("class", "raci-column-header").attr(
|
|
349
|
+
"d",
|
|
350
|
+
topRoundedRectPath(cx, headerY, cw, sHeaderHeight, sColumnRadius)
|
|
351
|
+
).attr("fill", headerFill);
|
|
352
|
+
const cxText = roleX(i) + roleColW / 2;
|
|
353
|
+
colG.append("text").attr("class", "raci-column-label").attr("x", cxText).attr("y", headerY + sHeaderHeight / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", sRoleHeaderFont).attr("font-weight", 600).attr("fill", palette.text).text(
|
|
354
|
+
truncateText(
|
|
355
|
+
parsed.roleDisplayNames[i] ?? "",
|
|
356
|
+
sRoleHeaderFont,
|
|
357
|
+
roleColW - 2 * sCellPad
|
|
358
|
+
)
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
const hasAnyDiagnostic = taskDiagnostics.size > 0;
|
|
362
|
+
const SUMMARY_MIN_SLICE_W = 22;
|
|
363
|
+
const summaryInsetForCheck = sColumnInset + 4;
|
|
364
|
+
const summaryColBodyWForCheck = roleColW - 2 * summaryInsetForCheck;
|
|
365
|
+
let chartCanShowSummaries = true;
|
|
366
|
+
for (const row of phasedRows) {
|
|
367
|
+
if (row.kind !== "phase" || !row.collapsed) continue;
|
|
368
|
+
for (const roleId of parsed.roles) {
|
|
369
|
+
const used = /* @__PURE__ */ new Set();
|
|
370
|
+
for (const t of row.phase.tasks) {
|
|
371
|
+
const a = t.roleAssignments.find((r) => r.id === roleId);
|
|
372
|
+
if (!a) continue;
|
|
373
|
+
for (const m of a.markers) used.add(m);
|
|
374
|
+
}
|
|
375
|
+
if (used.size === 0) continue;
|
|
376
|
+
const totalGap = SLICE_GAP * Math.max(0, used.size - 1);
|
|
377
|
+
const sliceW = (summaryColBodyWForCheck - totalGap) / used.size;
|
|
378
|
+
if (sliceW < SUMMARY_MIN_SLICE_W) {
|
|
379
|
+
chartCanShowSummaries = false;
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (!chartCanShowSummaries) break;
|
|
384
|
+
}
|
|
385
|
+
for (let i = 0; i < phasedRows.length; i++) {
|
|
386
|
+
const row = phasedRows[i];
|
|
387
|
+
const y = rowYs[i];
|
|
388
|
+
if (row.kind === "phase") {
|
|
389
|
+
const phaseIdx = parsed.phases.indexOf(row.phase);
|
|
390
|
+
renderPhaseBar(
|
|
391
|
+
svg,
|
|
392
|
+
row.phase,
|
|
393
|
+
sHMargin,
|
|
394
|
+
y,
|
|
395
|
+
innerWidth,
|
|
396
|
+
palette,
|
|
397
|
+
row.collapsed,
|
|
398
|
+
autoAccent(Math.max(0, phaseIdx), palette),
|
|
399
|
+
parsed.roles,
|
|
400
|
+
roleX,
|
|
401
|
+
roleColW,
|
|
402
|
+
parsed.variant,
|
|
403
|
+
surfaceBg,
|
|
404
|
+
solid,
|
|
405
|
+
chartCanShowSummaries,
|
|
406
|
+
sPhaseHeight,
|
|
407
|
+
sPhaseFont,
|
|
408
|
+
sColumnInset,
|
|
409
|
+
sNodeRadius
|
|
410
|
+
);
|
|
411
|
+
} else {
|
|
412
|
+
renderTaskRow(
|
|
413
|
+
svg,
|
|
414
|
+
row.task,
|
|
415
|
+
parsed,
|
|
416
|
+
sHMargin,
|
|
417
|
+
y,
|
|
418
|
+
labelW,
|
|
419
|
+
roleX,
|
|
420
|
+
roleColW,
|
|
421
|
+
palette,
|
|
422
|
+
surfaceBg,
|
|
423
|
+
solid,
|
|
424
|
+
taskDiagnostics,
|
|
425
|
+
hasAnyDiagnostic,
|
|
426
|
+
taskRowContent.get(row.task.id),
|
|
427
|
+
onClickLine,
|
|
428
|
+
onMarkerDragStart,
|
|
429
|
+
sLabelFont,
|
|
430
|
+
sDescFont,
|
|
431
|
+
sCellPad,
|
|
432
|
+
sRowHeight,
|
|
433
|
+
sColumnInset,
|
|
434
|
+
sNodeRadius,
|
|
435
|
+
sNodeStrokeWidth,
|
|
436
|
+
sMarkerFont,
|
|
437
|
+
sLegendLabelFont,
|
|
438
|
+
sStackTopGap,
|
|
439
|
+
sViolationLineHeight
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const anyMarker = tasksAll.some(
|
|
444
|
+
(t) => t.roleAssignments.some((a) => a.markers.length > 0)
|
|
445
|
+
);
|
|
446
|
+
if (!anyMarker) {
|
|
447
|
+
svg.append("text").attr("x", width / 2).attr("y", totalHeight - sVMargin - 2).attr("text-anchor", "middle").attr("font-size", 12).attr("fill", palette.textMuted).text(
|
|
448
|
+
"Drag a marker from the legend onto any cell. Drag a marker out to remove."
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
setupColumnHover(svg);
|
|
452
|
+
}
|
|
453
|
+
function setupColumnHover(svg) {
|
|
454
|
+
const node = svg.node();
|
|
455
|
+
if (!node) return;
|
|
456
|
+
let current = null;
|
|
457
|
+
const findRole = (target) => {
|
|
458
|
+
let el = target;
|
|
459
|
+
while (el && el !== node) {
|
|
460
|
+
const r = el.getAttribute && el.getAttribute("data-role-id");
|
|
461
|
+
if (r) return r;
|
|
462
|
+
el = el.parentElement;
|
|
463
|
+
}
|
|
464
|
+
return null;
|
|
465
|
+
};
|
|
466
|
+
const set = (role) => {
|
|
467
|
+
if (role === current) return;
|
|
468
|
+
if (current) {
|
|
469
|
+
svg.selectAll(
|
|
470
|
+
`.raci-cell[data-role-id="${current}"], .raci-column[data-role-id="${current}"]`
|
|
471
|
+
).classed("raci-col-active", false);
|
|
472
|
+
}
|
|
473
|
+
current = role;
|
|
474
|
+
if (role) {
|
|
475
|
+
svg.attr("data-hover-col", role);
|
|
476
|
+
svg.selectAll(
|
|
477
|
+
`.raci-cell[data-role-id="${role}"], .raci-column[data-role-id="${role}"]`
|
|
478
|
+
).classed("raci-col-active", true);
|
|
479
|
+
} else {
|
|
480
|
+
svg.attr("data-hover-col", null);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
svg.on("mouseover", (event) => set(findRole(event.target)));
|
|
484
|
+
svg.on("mouseleave", () => set(null));
|
|
485
|
+
}
|
|
486
|
+
function renderLegend(svg, markers, variant, x, y, mode, chipW, palette, surfaceBg, solid, _onMarkerDragStart, sLegendChipGap, sLegendHeight, sNodeRadius, sNodeStrokeWidth, sLegendLetterFont, sLegendLabelFont) {
|
|
487
|
+
if (markers.length === 0) return;
|
|
488
|
+
const labels = MARKER_LABELS[variant];
|
|
489
|
+
const legendG = svg.append("g").attr("class", "raci-legend").attr("aria-label", `${variant.toUpperCase()} marker legend`);
|
|
490
|
+
markers.forEach((marker, i) => {
|
|
491
|
+
const cx = x + i * (chipW + sLegendChipGap);
|
|
492
|
+
const rawColor = markerColor(marker, palette);
|
|
493
|
+
const labelText = labels[marker];
|
|
494
|
+
const chipG = legendG.append("g").attr("class", "raci-legend-chip").attr("data-marker", marker).style("cursor", "grab");
|
|
495
|
+
if (mode === "letters") {
|
|
496
|
+
const fill2 = solid ? rawColor : mix(rawColor, surfaceBg, TINT_PCT);
|
|
497
|
+
const stroke2 = solid ? mix(rawColor, surfaceBg, 70) : rawColor;
|
|
498
|
+
chipG.append("rect").attr("x", cx).attr("y", y).attr("width", chipW).attr("height", sLegendHeight).attr("rx", sNodeRadius).attr("fill", fill2).attr("stroke", stroke2).attr("stroke-width", sNodeStrokeWidth);
|
|
499
|
+
chipG.append("text").attr("x", cx + chipW / 2).attr("y", y + sLegendHeight / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", sLegendLetterFont).attr("font-weight", 700).attr(
|
|
500
|
+
"fill",
|
|
501
|
+
solid ? contrastText(
|
|
502
|
+
fill2,
|
|
503
|
+
palette.textOnFillLight,
|
|
504
|
+
palette.textOnFillDark
|
|
505
|
+
) : palette.text
|
|
506
|
+
).text(marker);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const fill = solid ? rawColor : mix(rawColor, surfaceBg, TINT_PCT);
|
|
510
|
+
const stroke = mix(rawColor, surfaceBg, 70);
|
|
511
|
+
chipG.append("rect").attr("x", cx).attr("y", y).attr("width", chipW).attr("height", sLegendHeight).attr("rx", sNodeRadius).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", sNodeStrokeWidth);
|
|
512
|
+
const slabPad = 4;
|
|
513
|
+
const slabW = 24;
|
|
514
|
+
const slabFill = solid ? mix(rawColor, surfaceBg, 70) : mix(rawColor, surfaceBg, 55);
|
|
515
|
+
chipG.append("rect").attr("x", cx + slabPad).attr("y", y + slabPad).attr("width", slabW).attr("height", sLegendHeight - 2 * slabPad).attr("rx", 4).attr("fill", slabFill);
|
|
516
|
+
chipG.append("text").attr("x", cx + slabPad + slabW / 2).attr("y", y + sLegendHeight / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", sLegendLetterFont).attr("font-weight", 700).attr(
|
|
517
|
+
"fill",
|
|
518
|
+
solid ? contrastText(
|
|
519
|
+
slabFill,
|
|
520
|
+
palette.textOnFillLight,
|
|
521
|
+
palette.textOnFillDark
|
|
522
|
+
) : palette.text
|
|
523
|
+
).text(marker);
|
|
524
|
+
chipG.append("text").attr("x", cx + slabPad + slabW + 8).attr("y", y + sLegendHeight / 2).attr("dominant-baseline", "central").attr("font-size", sLegendLabelFont).attr("font-weight", 600).attr("fill", palette.text).text(
|
|
525
|
+
truncateText(
|
|
526
|
+
labelText,
|
|
527
|
+
sLegendLabelFont,
|
|
528
|
+
chipW - slabW - slabPad * 2 - 12
|
|
529
|
+
)
|
|
530
|
+
);
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
function renderRaciForExport(container, parsed, palette, isDark, exportDims) {
|
|
534
|
+
renderRaci(container, parsed, palette, isDark, {}, exportDims);
|
|
535
|
+
}
|
|
536
|
+
function renderPhaseBar(svg, phase, x, y, width, palette, collapsed, autoColor, roles, roleX, roleColW, variant, surfaceBg, solid, showSummary, sPhaseHeight, sPhaseFont, sColumnInset, sNodeRadius) {
|
|
537
|
+
const phaseG = svg.append("g").attr("class", "raci-phase").attr("data-section", `phase-${phase.id}`).attr("data-section-toggle", "").attr("data-line-number", String(phase.lineNumber)).attr("data-phase-id", phase.id).attr("data-collapsed", collapsed ? "1" : "0").style("cursor", "pointer");
|
|
538
|
+
const phaseFill = mix(phase.color ?? autoColor, palette.bg, 30);
|
|
539
|
+
phaseG.append("rect").attr("x", x).attr("y", y + 4).attr("width", width).attr("height", sPhaseHeight - 8).attr("fill", phaseFill).attr("rx", 4);
|
|
540
|
+
const chevX = x + 12;
|
|
541
|
+
const chevY = y + sPhaseHeight / 2;
|
|
542
|
+
phaseG.append("text").attr("x", chevX).attr("y", chevY).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-family", FONT_FAMILY).attr("font-size", 9).attr("fill", palette.text).attr("opacity", 0.6).text(collapsed ? "\u25B6" : "\u25BC");
|
|
543
|
+
phaseG.append("text").attr("x", x + 26).attr("y", y + (sPhaseHeight - 4) / 2 + 2).attr("dominant-baseline", "central").attr("font-size", sPhaseFont).attr("font-weight", 600).attr("fill", palette.text).text(phase.displayName);
|
|
544
|
+
if (collapsed) {
|
|
545
|
+
const taskCount = phase.tasks.length;
|
|
546
|
+
if (taskCount > 0) {
|
|
547
|
+
const labelTextWidth = measureText(phase.displayName, sPhaseFont);
|
|
548
|
+
phaseG.append("text").attr("x", x + 26 + labelTextWidth + 10).attr("y", y + (sPhaseHeight - 4) / 2 + 2).attr("dominant-baseline", "central").attr("font-size", sPhaseFont - 1).attr("font-weight", 500).attr("fill", palette.textMuted).text(`${taskCount} ${taskCount === 1 ? "task" : "tasks"}`);
|
|
549
|
+
}
|
|
550
|
+
if (!showSummary) return;
|
|
551
|
+
const alphabet = VARIANTS[variant].alphabet;
|
|
552
|
+
const summaryInset = sColumnInset + 4;
|
|
553
|
+
const summaryColBodyW = roleColW - 2 * summaryInset;
|
|
554
|
+
const SUMMARY_CHIP_H = 24;
|
|
555
|
+
const SUMMARY_LETTER_FONT = 13;
|
|
556
|
+
const summaryY = y + (sPhaseHeight - SUMMARY_CHIP_H) / 2;
|
|
557
|
+
roles.forEach((roleId, colIdx) => {
|
|
558
|
+
const used = /* @__PURE__ */ new Set();
|
|
559
|
+
for (const t of phase.tasks) {
|
|
560
|
+
const a = t.roleAssignments.find((r) => r.id === roleId);
|
|
561
|
+
if (!a) continue;
|
|
562
|
+
for (const m of a.markers) used.add(m);
|
|
563
|
+
}
|
|
564
|
+
if (used.size === 0) return;
|
|
565
|
+
const ordered = sortByAlphabet([...used], alphabet);
|
|
566
|
+
const totalGap = SLICE_GAP * Math.max(0, ordered.length - 1);
|
|
567
|
+
const sliceW = (summaryColBodyW - totalGap) / ordered.length;
|
|
568
|
+
const startX = roleX(colIdx) + summaryInset;
|
|
569
|
+
ordered.forEach((marker, i) => {
|
|
570
|
+
const cx = startX + i * (sliceW + SLICE_GAP);
|
|
571
|
+
const rawColor = markerColor(marker, palette);
|
|
572
|
+
const fill = solid ? rawColor : mix(rawColor, surfaceBg, TINT_PCT);
|
|
573
|
+
const stroke = solid ? mix(rawColor, surfaceBg, 70) : rawColor;
|
|
574
|
+
const chipG = phaseG.append("g").attr("class", "raci-phase-summary");
|
|
575
|
+
chipG.append("rect").attr("x", cx).attr("y", summaryY).attr("width", sliceW).attr("height", SUMMARY_CHIP_H).attr("rx", sNodeRadius).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", 1.25);
|
|
576
|
+
chipG.append("text").attr("x", cx + sliceW / 2).attr("y", summaryY + SUMMARY_CHIP_H / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", SUMMARY_LETTER_FONT).attr("font-weight", 700).attr(
|
|
577
|
+
"fill",
|
|
578
|
+
solid ? contrastText(
|
|
579
|
+
fill,
|
|
580
|
+
palette.textOnFillLight,
|
|
581
|
+
palette.textOnFillDark
|
|
582
|
+
) : palette.text
|
|
583
|
+
).text(marker);
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
var SLICE_GAP = 4;
|
|
589
|
+
function renderTaskRow(svg, task, parsed, x, y, labelW, roleX, roleColW, palette, surfaceBg, solid, taskDiagnostics, _hasAnyDiagnostic, rowContent, onClickLine, _onMarkerDragStart, sLabelFont, sDescFont, sCellPad, sRowHeight, sColumnInset, sNodeRadius, sNodeStrokeWidth, sMarkerFont, sLegendLabelFont, sStackTopGap, sViolationLineHeight) {
|
|
590
|
+
const rowG = svg.append("g").attr("class", "raci-task-row").attr("data-task-id", task.id).attr("data-line-number", String(task.lineNumber));
|
|
591
|
+
const labelX = x + 8;
|
|
592
|
+
const labelMaxW = labelW - 16;
|
|
593
|
+
const content = rowContent ?? prepareRowContent(
|
|
594
|
+
task,
|
|
595
|
+
taskDiagnostics?.get(task.id),
|
|
596
|
+
labelMaxW,
|
|
597
|
+
sLabelFont,
|
|
598
|
+
sDescFont,
|
|
599
|
+
sRowHeight,
|
|
600
|
+
sStackTopGap,
|
|
601
|
+
sViolationLineHeight
|
|
602
|
+
);
|
|
603
|
+
const { nameLines, descLines, violations, rowHeight } = content;
|
|
604
|
+
const labelG = rowG.append("g").attr("class", "raci-task-label-area");
|
|
605
|
+
labelG.append("rect").attr("class", "raci-task-label-zone").attr("x", x).attr("y", y).attr("width", labelW).attr("height", rowHeight).attr("fill", "transparent").attr("pointer-events", "all");
|
|
606
|
+
const nameTopY = y + sLabelFont / 2 + 6;
|
|
607
|
+
const nameEl = labelG.append("text").attr("class", "raci-task-label").attr("x", labelX).attr("y", nameTopY).attr("dominant-baseline", "central").attr("font-size", sLabelFont).attr("fill", palette.text);
|
|
608
|
+
nameLines.forEach((line, i) => {
|
|
609
|
+
const tspan = nameEl.append("tspan").attr("x", labelX).text(line);
|
|
610
|
+
if (i > 0) tspan.attr("dy", NAME_LINE_HEIGHT);
|
|
611
|
+
});
|
|
612
|
+
let stackY = nameTopY + Math.max(0, nameLines.length - 1) * NAME_LINE_HEIGHT + sLabelFont / 2 + 4 + sStackTopGap;
|
|
613
|
+
if (descLines.length > 0) {
|
|
614
|
+
const descEl = labelG.append("text").attr("class", "raci-task-description").attr("x", labelX).attr("y", stackY).attr("dominant-baseline", "central").attr("font-size", sDescFont).attr("fill", palette.textMuted);
|
|
615
|
+
descLines.forEach((line, i) => {
|
|
616
|
+
const tspan = descEl.append("tspan").attr("x", labelX).text(line);
|
|
617
|
+
if (i > 0) tspan.attr("dy", DESC_LINE_HEIGHT);
|
|
618
|
+
});
|
|
619
|
+
stackY += descLines.length * DESC_LINE_HEIGHT;
|
|
620
|
+
}
|
|
621
|
+
for (const v of violations) {
|
|
622
|
+
const lineColor = v.severity === "error" ? palette.colors.red : palette.colors.orange;
|
|
623
|
+
const lineG = labelG.append("g").attr("class", "raci-violation-line").attr("data-severity", v.severity).style("cursor", onClickLine ? "pointer" : "default");
|
|
624
|
+
const violationFont = sLabelFont - 2;
|
|
625
|
+
const textEl = lineG.append("text").attr("x", labelX).attr("y", stackY).attr("dominant-baseline", "central").attr("font-size", violationFont).attr("fill", lineColor);
|
|
626
|
+
v.lines.forEach((line, i) => {
|
|
627
|
+
const segs = parseQuotedSegments(line);
|
|
628
|
+
segs.forEach((seg, segIdx) => {
|
|
629
|
+
const tspan = textEl.append("tspan").text(seg.text);
|
|
630
|
+
if (i > 0 && segIdx === 0)
|
|
631
|
+
tspan.attr("x", labelX).attr("dy", sViolationLineHeight);
|
|
632
|
+
if (seg.bold) tspan.attr("font-weight", 700);
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
if (onClickLine) {
|
|
636
|
+
const targetLine = v.sourceLine;
|
|
637
|
+
lineG.on("click", (e) => {
|
|
638
|
+
e.stopPropagation();
|
|
639
|
+
onClickLine(targetLine);
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
stackY += v.lines.length * sViolationLineHeight;
|
|
643
|
+
}
|
|
644
|
+
const cellByRole = /* @__PURE__ */ new Map();
|
|
645
|
+
for (const a of task.roleAssignments) {
|
|
646
|
+
cellByRole.set(a.id, a.markers);
|
|
647
|
+
}
|
|
648
|
+
const alphabet = VARIANTS[parsed.variant].alphabet;
|
|
649
|
+
parsed.roles.forEach((roleId, idx) => {
|
|
650
|
+
const cellInset = sColumnInset + 4;
|
|
651
|
+
const cx = roleX(idx) + cellInset;
|
|
652
|
+
const cy = y + sCellPad;
|
|
653
|
+
const cw = roleColW - 2 * cellInset;
|
|
654
|
+
const ch = sRowHeight - 2 * sCellPad;
|
|
655
|
+
const markersSource = cellByRole.get(roleId) ?? [];
|
|
656
|
+
const markers = sortByAlphabet(markersSource, alphabet);
|
|
657
|
+
const assignment = task.roleAssignments.find((a) => a.id === roleId);
|
|
658
|
+
const cellG = rowG.append("g").attr("class", "raci-cell").attr("data-task-id", task.id).attr("data-role-id", roleId);
|
|
659
|
+
if (assignment) {
|
|
660
|
+
cellG.attr("data-line-number", String(assignment.lineNumber));
|
|
661
|
+
}
|
|
662
|
+
cellG.append("rect").attr("class", "raci-cell-hit").attr("x", roleX(idx) + sColumnInset).attr("y", y).attr("width", roleColW - 2 * sColumnInset).attr("height", sRowHeight).attr("fill", "transparent").attr("pointer-events", "all");
|
|
663
|
+
const totalGap = SLICE_GAP * (markers.length - 1);
|
|
664
|
+
const sliceW = markers.length > 0 ? (cw - totalGap) / markers.length : 0;
|
|
665
|
+
const variantLabels = MARKER_LABELS[parsed.variant];
|
|
666
|
+
markers.forEach((m, i) => {
|
|
667
|
+
const rawColor = markerColor(m, palette);
|
|
668
|
+
const fill = solid ? rawColor : mix(rawColor, surfaceBg, TINT_PCT);
|
|
669
|
+
const stroke = rawColor;
|
|
670
|
+
const sliceX = cx + i * (sliceW + SLICE_GAP);
|
|
671
|
+
const sliceG = cellG.append("g").attr("class", "raci-marker-slice").attr("data-marker", m).style("cursor", "grab");
|
|
672
|
+
sliceG.append("rect").attr("x", sliceX).attr("y", cy).attr("width", sliceW).attr("height", ch).attr("rx", sNodeRadius).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", sNodeStrokeWidth);
|
|
673
|
+
const fullLabel = variantLabels[m] ?? m;
|
|
674
|
+
const labelPx = measureText(fullLabel, sLegendLabelFont);
|
|
675
|
+
const showFullLabel = labelPx + 16 <= sliceW;
|
|
676
|
+
const textContent = showFullLabel ? fullLabel : m;
|
|
677
|
+
const textFont = showFullLabel ? sLegendLabelFont : sMarkerFont;
|
|
678
|
+
sliceG.append("text").attr("x", sliceX + sliceW / 2).attr("y", cy + ch / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", textFont).attr("font-weight", MARKER_FONT_WEIGHT).attr(
|
|
679
|
+
"fill",
|
|
680
|
+
solid ? contrastText(
|
|
681
|
+
fill,
|
|
682
|
+
palette.textOnFillLight,
|
|
683
|
+
palette.textOnFillDark
|
|
684
|
+
) : palette.text
|
|
685
|
+
).text(textContent);
|
|
686
|
+
});
|
|
687
|
+
cellG.append("rect").attr("class", "raci-cell-drop-preview").attr("x", cx).attr("y", cy).attr("width", cw).attr("height", ch).attr("rx", sNodeRadius).attr("fill", "transparent").attr("stroke", "transparent").attr("pointer-events", "none");
|
|
688
|
+
if (onClickLine && assignment) {
|
|
689
|
+
cellG.on("click", () => onClickLine(assignment.lineNumber));
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
var NAME_LINE_HEIGHT = 16;
|
|
694
|
+
var DESC_LINE_HEIGHT = 16;
|
|
695
|
+
function prepareRowContent(task, bucket, labelMaxW, sLabelFont = LABEL_FONT, sDescFont = DESC_FONT, sRowHeight = ROW_HEIGHT, sStackTopGap = STACK_TOP_GAP, sViolationLineHeight = VIOLATION_LINE_HEIGHT) {
|
|
696
|
+
const nameLines = wrapTextToWidth(task.displayName, sLabelFont, labelMaxW, {
|
|
697
|
+
hardBreak: true
|
|
698
|
+
});
|
|
699
|
+
const description = task.description?.trim() ?? "";
|
|
700
|
+
const descLines = description.length > 0 ? description.split("\n").flatMap(
|
|
701
|
+
(line) => wrapTextToWidth(line, sDescFont, labelMaxW, { hardBreak: true })
|
|
702
|
+
) : [];
|
|
703
|
+
const violations = [];
|
|
704
|
+
if (bucket) {
|
|
705
|
+
const collect = (arr, severity) => {
|
|
706
|
+
for (const e of arr) {
|
|
707
|
+
const text = trimTaskPrefix(e.message);
|
|
708
|
+
violations.push({
|
|
709
|
+
severity,
|
|
710
|
+
sourceLine: e.line,
|
|
711
|
+
text,
|
|
712
|
+
lines: wrapTextToWidth(text, sLabelFont - 2, labelMaxW, {
|
|
713
|
+
hardBreak: true
|
|
714
|
+
})
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
collect(bucket.errors, "error");
|
|
719
|
+
collect(bucket.warnings, "warning");
|
|
720
|
+
}
|
|
721
|
+
const totalViolationLines = violations.reduce(
|
|
722
|
+
(n, v) => n + v.lines.length,
|
|
723
|
+
0
|
|
724
|
+
);
|
|
725
|
+
const hasStacked = descLines.length > 0 || violations.length > 0;
|
|
726
|
+
const labelExtra = Math.max(0, nameLines.length - 1) * NAME_LINE_HEIGHT;
|
|
727
|
+
const rowHeight = sRowHeight + labelExtra + (hasStacked ? sStackTopGap : 0) + descLines.length * DESC_LINE_HEIGHT + totalViolationLines * sViolationLineHeight;
|
|
728
|
+
return { nameLines, descLines, violations, rowHeight };
|
|
729
|
+
}
|
|
730
|
+
function parseQuotedSegments(message) {
|
|
731
|
+
const out = [];
|
|
732
|
+
const re = /'([^']+)'/g;
|
|
733
|
+
let last = 0;
|
|
734
|
+
let m;
|
|
735
|
+
while ((m = re.exec(message)) !== null) {
|
|
736
|
+
if (m.index > last)
|
|
737
|
+
out.push({ text: message.slice(last, m.index), bold: false });
|
|
738
|
+
out.push({ text: m[1], bold: true });
|
|
739
|
+
last = m.index + m[0].length;
|
|
740
|
+
}
|
|
741
|
+
if (last < message.length)
|
|
742
|
+
out.push({ text: message.slice(last), bold: false });
|
|
743
|
+
return out;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
export {
|
|
747
|
+
MARKER_LABELS,
|
|
748
|
+
renderRaci,
|
|
749
|
+
renderRaciForExport
|
|
750
|
+
};
|
|
751
|
+
//# sourceMappingURL=chunk-DZAQBW35.js.map
|