compasso 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -10
- package/dist/{chunk-RWPGGWO5.js → chunk-FYBABYC7.js} +34 -10
- package/dist/chunk-FYBABYC7.js.map +1 -0
- package/dist/{chunk-F47C6ZEB.js → chunk-IPE7JZO5.js} +3 -3
- package/dist/{chunk-F47C6ZEB.js.map → chunk-IPE7JZO5.js.map} +1 -1
- package/dist/{chunk-Q6DVTCXD.js → chunk-LR7BXUWM.js} +18 -6
- package/dist/chunk-LR7BXUWM.js.map +1 -0
- package/dist/{chunk-LRHHUJFZ.js → chunk-M4WA6ME7.js} +3 -3
- package/dist/{chunk-LRHHUJFZ.js.map → chunk-M4WA6ME7.js.map} +1 -1
- package/dist/{chunk-JP4N42AY.js → chunk-PGUMLTIM.js} +3 -3
- package/dist/{chunk-JP4N42AY.js.map → chunk-PGUMLTIM.js.map} +1 -1
- package/dist/{chunk-O3BT2O42.js → chunk-SD4NTRBM.js} +29 -3
- package/dist/chunk-SD4NTRBM.js.map +1 -0
- package/dist/chunk-TAE2UB7D.js +780 -0
- package/dist/chunk-TAE2UB7D.js.map +1 -0
- package/dist/{chunk-ZBDABVIO.js → chunk-WJYYBGZW.js} +3 -3
- package/dist/{chunk-ZBDABVIO.js.map → chunk-WJYYBGZW.js.map} +1 -1
- package/dist/core/index.cjs +30 -0
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +18 -2
- package/dist/core/index.d.ts +18 -2
- package/dist/core/index.js +1 -1
- package/dist/ecomap/index.cjs +34 -11
- package/dist/ecomap/index.cjs.map +1 -1
- package/dist/ecomap/index.d.cts +12 -0
- package/dist/ecomap/index.d.ts +12 -0
- package/dist/ecomap/index.js +2 -2
- package/dist/fault-tree/index.js +2 -2
- package/dist/fishbone/index.js +2 -2
- package/dist/genogram/index.cjs +57 -7
- package/dist/genogram/index.cjs.map +1 -1
- package/dist/genogram/index.d.cts +20 -4
- package/dist/genogram/index.d.ts +20 -4
- package/dist/genogram/index.js +2 -2
- package/dist/index.cjs +1052 -191
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +8 -7
- package/dist/{kinship-DqEklrDN.d.ts → kinship-BF90HyyS.d.ts} +1 -1
- package/dist/{kinship-Dy_ijjJV.d.cts → kinship-BOUss5cT.d.cts} +1 -1
- package/dist/labels-B0aOMbHy.d.cts +103 -0
- package/dist/labels-B0aOMbHy.d.ts +103 -0
- package/dist/{labels-DNqRkWuI.d.ts → labels-CuLbFyrz.d.ts} +1 -1
- package/dist/{labels-CBQ_3Ec9.d.cts → labels-DhQe7I8m.d.cts} +1 -1
- package/dist/locales/pt-br.cjs +19 -0
- package/dist/locales/pt-br.cjs.map +1 -1
- package/dist/locales/pt-br.d.cts +7 -4
- package/dist/locales/pt-br.d.ts +7 -4
- package/dist/locales/pt-br.js +18 -1
- package/dist/locales/pt-br.js.map +1 -1
- package/dist/org-chart/index.cjs +895 -0
- package/dist/org-chart/index.cjs.map +1 -0
- package/dist/org-chart/index.d.cts +185 -0
- package/dist/org-chart/index.d.ts +185 -0
- package/dist/org-chart/index.js +4 -0
- package/dist/org-chart/index.js.map +1 -0
- package/dist/pedigree/index.d.cts +4 -4
- package/dist/pedigree/index.d.ts +4 -4
- package/dist/pedigree/index.js +2 -2
- package/dist/phylo/index.js +2 -2
- package/dist/{types-BnMG7TCd.d.cts → types-jE2fdM1t.d.cts} +8 -0
- package/dist/{types-BnMG7TCd.d.ts → types-jE2fdM1t.d.ts} +8 -0
- package/package.json +28 -11
- package/dist/chunk-O3BT2O42.js.map +0 -1
- package/dist/chunk-Q6DVTCXD.js.map +0 -1
- package/dist/chunk-RWPGGWO5.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -142,6 +142,32 @@ function qualityLineStyle(quality, lexicon = QUALITY_LEXICON_EN) {
|
|
|
142
142
|
return matched.length === 1 ? matched[0] : "plain";
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
// src/core/annotation.ts
|
|
146
|
+
var round = (n) => Math.round(n * 100) / 100;
|
|
147
|
+
var ANNOTATION_INK = "#52525b";
|
|
148
|
+
var DOT_R = 3;
|
|
149
|
+
var TICK_HALF = 3;
|
|
150
|
+
function annotationDot(cx, cy) {
|
|
151
|
+
return `<circle cx="${round(cx)}" cy="${round(cy)}" r="${DOT_R}" fill="${ANNOTATION_INK}" fill-opacity="0.6"/>`;
|
|
152
|
+
}
|
|
153
|
+
function annotationTick(points) {
|
|
154
|
+
if (points.length < 2) return "";
|
|
155
|
+
const i = Math.floor((points.length - 1) / 2);
|
|
156
|
+
const a = points[i];
|
|
157
|
+
const b = points[i + 1] ?? points[i];
|
|
158
|
+
const mx = (a.x + b.x) / 2;
|
|
159
|
+
const my = (a.y + b.y) / 2;
|
|
160
|
+
const horizontal = Math.abs(a.y - b.y) <= Math.abs(a.x - b.x);
|
|
161
|
+
const x1 = horizontal ? mx : mx - TICK_HALF;
|
|
162
|
+
const x2 = horizontal ? mx : mx + TICK_HALF;
|
|
163
|
+
const y1 = horizontal ? my - TICK_HALF : my;
|
|
164
|
+
const y2 = horizontal ? my + TICK_HALF : my;
|
|
165
|
+
return `<line x1="${round(x1)}" y1="${round(y1)}" x2="${round(x2)}" y2="${round(y2)}" stroke="${ANNOTATION_INK}" stroke-width="1.5"/>`;
|
|
166
|
+
}
|
|
167
|
+
function annotationSwatch(x, yCenter) {
|
|
168
|
+
return annotationDot(x + LEGEND_SWATCH_W / 2, yCenter);
|
|
169
|
+
}
|
|
170
|
+
|
|
145
171
|
// src/genogram/types.ts
|
|
146
172
|
var UNION_STATUSES = [
|
|
147
173
|
"married",
|
|
@@ -317,7 +343,8 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
317
343
|
parentId: l.parentId,
|
|
318
344
|
childId: l.childId,
|
|
319
345
|
quality: l.quality,
|
|
320
|
-
edgeId: PARENT_REL_ID_BASE + l.id
|
|
346
|
+
edgeId: PARENT_REL_ID_BASE + l.id,
|
|
347
|
+
annotated: l.annotated ?? false
|
|
321
348
|
}));
|
|
322
349
|
const declaredPairs = new Set(validLinks.map((l) => pairKey(l.parentId, l.childId)));
|
|
323
350
|
const promotedByPair = /* @__PURE__ */ new Map();
|
|
@@ -336,7 +363,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
336
363
|
[parentId, childId] = fromIsChild ? [r.toPersonId, r.fromPersonId] : [r.fromPersonId, r.toPersonId];
|
|
337
364
|
}
|
|
338
365
|
if (parentId === childId) continue;
|
|
339
|
-
promotedByPair.set(key, { parentId, childId, quality: r.quality, edgeId: PROMOTED_REL_ID_BASE + r.id });
|
|
366
|
+
promotedByPair.set(key, { parentId, childId, quality: r.quality, edgeId: PROMOTED_REL_ID_BASE + r.id, annotated: r.annotated ?? false });
|
|
340
367
|
}
|
|
341
368
|
const allLinks = [...realLinks, ...promotedByPair.values()].sort((a, b) => a.edgeId - b.edgeId);
|
|
342
369
|
const coupleByPair = /* @__PURE__ */ new Map();
|
|
@@ -669,6 +696,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
669
696
|
toPersonId: u.personBId,
|
|
670
697
|
titles: [unionTitle(u)],
|
|
671
698
|
lineStyle: "plain",
|
|
699
|
+
annotated: u.annotated ?? false,
|
|
672
700
|
build: () => {
|
|
673
701
|
const cy = geo.cy(rowOfPerson.get(leftId));
|
|
674
702
|
if (dipLevel === 0) {
|
|
@@ -703,6 +731,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
703
731
|
toPersonId: u.personBId,
|
|
704
732
|
titles: [unionTitle(u)],
|
|
705
733
|
lineStyle: "plain",
|
|
734
|
+
annotated: u.annotated ?? false,
|
|
706
735
|
build
|
|
707
736
|
});
|
|
708
737
|
}
|
|
@@ -759,6 +788,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
759
788
|
};
|
|
760
789
|
gutterReqs.push(spineReq);
|
|
761
790
|
const aId = (c) => linkOf.get(`${u.personAId}>${c}`).edgeId;
|
|
791
|
+
const aLinkAnnotated = (c) => linkOf.get(`${u.personAId}>${c}`).annotated ?? false;
|
|
762
792
|
const childDrop = (c, parentId) => {
|
|
763
793
|
const link = linkOf.get(`${parentId}>${c}`);
|
|
764
794
|
const edgeId = link.edgeId;
|
|
@@ -771,6 +801,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
771
801
|
toPersonId: c,
|
|
772
802
|
titles: [linkTitle(link)],
|
|
773
803
|
lineStyle: "plain",
|
|
804
|
+
annotated: link.annotated,
|
|
774
805
|
build: () => {
|
|
775
806
|
const y = geo.corridorLaneY(sibReq);
|
|
776
807
|
const x = geo.cx(colOrThrow(c)) + arrivalOffset(arr.slot, arrivalCount.get(c) ?? 1);
|
|
@@ -791,6 +822,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
791
822
|
toPersonId: null,
|
|
792
823
|
titles: [linkTitle(linkOf.get(`${u.personAId}>${c}`))],
|
|
793
824
|
lineStyle: "plain",
|
|
825
|
+
annotated: aLinkAnnotated(c),
|
|
794
826
|
build: () => {
|
|
795
827
|
const sy = geo.corridorLaneY(sibReq);
|
|
796
828
|
const sx = geo.gutterCenterX(interGutter);
|
|
@@ -816,6 +848,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
816
848
|
// declared quality word is dropped from the drawn element (FIX C-2 / SPEC inv #4).
|
|
817
849
|
titles: [aLinkTitle(groupKids[0]), ...groupKids.slice(2).map(aLinkTitle)],
|
|
818
850
|
lineStyle: "plain",
|
|
851
|
+
annotated: [groupKids[0], ...groupKids.slice(2)].some(aLinkAnnotated),
|
|
819
852
|
build: () => {
|
|
820
853
|
const y = geo.corridorLaneY(sibReq);
|
|
821
854
|
const xs = sibSpan();
|
|
@@ -834,6 +867,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
834
867
|
titles: [aLinkTitle(groupKids[1])],
|
|
835
868
|
// the A-side link this spine carries, verbatim
|
|
836
869
|
lineStyle: "plain",
|
|
870
|
+
annotated: aLinkAnnotated(groupKids[1]),
|
|
837
871
|
build: () => {
|
|
838
872
|
const sy = geo.corridorLaneY(sibReq);
|
|
839
873
|
const x = geo.gutterCenterX(interGutter);
|
|
@@ -861,6 +895,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
861
895
|
titles: [linkTitle(l)],
|
|
862
896
|
lineStyle: "plain",
|
|
863
897
|
dotted: true,
|
|
898
|
+
annotated: l.annotated,
|
|
864
899
|
build
|
|
865
900
|
});
|
|
866
901
|
}
|
|
@@ -873,10 +908,11 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
873
908
|
const title = r.quality !== null ? `${r.type} \xB7 ${r.quality}` : r.type;
|
|
874
909
|
const g = bondGroups.get(key);
|
|
875
910
|
if (g === void 0) {
|
|
876
|
-
bondGroups.set(key, { relIds: [r.id], titles: [title], style, aId: r.fromPersonId, bId: r.toPersonId });
|
|
911
|
+
bondGroups.set(key, { relIds: [r.id], titles: [title], style, aId: r.fromPersonId, bId: r.toPersonId, annotated: r.annotated ?? false });
|
|
877
912
|
} else {
|
|
878
913
|
g.relIds.push(r.id);
|
|
879
914
|
g.titles.push(title);
|
|
915
|
+
if (r.annotated) g.annotated = true;
|
|
880
916
|
}
|
|
881
917
|
}
|
|
882
918
|
const bondList = [...bondGroups.values()].sort((a, b) => Math.max(...a.relIds) - Math.max(...b.relIds));
|
|
@@ -895,6 +931,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
895
931
|
toPersonId: g.bId,
|
|
896
932
|
titles: g.titles,
|
|
897
933
|
lineStyle: g.style,
|
|
934
|
+
annotated: g.annotated,
|
|
898
935
|
build
|
|
899
936
|
});
|
|
900
937
|
}
|
|
@@ -991,6 +1028,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
991
1028
|
label: p.label,
|
|
992
1029
|
shape: shapeForSex(p.sex),
|
|
993
1030
|
deceased: p.deceased,
|
|
1031
|
+
annotated: p.annotated ?? false,
|
|
994
1032
|
cx: colCenterX[c],
|
|
995
1033
|
cy: rowCenterY[r],
|
|
996
1034
|
size: NODE_SIZE,
|
|
@@ -1007,6 +1045,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
|
|
|
1007
1045
|
points: pl.build(),
|
|
1008
1046
|
titles: pl.titles,
|
|
1009
1047
|
lineStyle: pl.lineStyle,
|
|
1048
|
+
annotated: pl.annotated,
|
|
1010
1049
|
...pl.unionStyle !== void 0 ? { unionStyle: pl.unionStyle } : {},
|
|
1011
1050
|
...pl.dotted ? { dotted: true } : {}
|
|
1012
1051
|
}));
|
|
@@ -1111,6 +1150,7 @@ function elementSvg(el, override) {
|
|
|
1111
1150
|
}
|
|
1112
1151
|
return `<path d="${pathData2(pts)}" fill="none" stroke="${EDGE_INK}" stroke-width="${width}" stroke-opacity="${opacity}"${dashAttr}/>`;
|
|
1113
1152
|
};
|
|
1153
|
+
const tick = el.annotated ? annotationTick(el.points) : "";
|
|
1114
1154
|
if (el.kind === "union-bar" || el.kind === "union-elbow") {
|
|
1115
1155
|
const style = resolveUnionStyle(el, override);
|
|
1116
1156
|
const body = [drawLine(style.dash ?? null, STRUCT_WIDTH, STRUCT_OPACITY)];
|
|
@@ -1119,15 +1159,15 @@ function elementSvg(el, override) {
|
|
|
1119
1159
|
const [a, b] = pts.length === 2 ? [pts[0], pts[1]] : longestHSegment(pts);
|
|
1120
1160
|
body.push(slashMarks(a, b, slashes, STRUCT_WIDTH));
|
|
1121
1161
|
}
|
|
1122
|
-
return `<g data-edge-id="${el.edgeId}">${title}${body.join("")}</g>`;
|
|
1162
|
+
return `<g data-edge-id="${el.edgeId}">${title}${body.join("")}${tick}</g>`;
|
|
1123
1163
|
}
|
|
1124
1164
|
if (el.kind === "descent" || el.kind === "sibling-bar") {
|
|
1125
1165
|
const dash = el.dotted ? DOTTED_DASH : null;
|
|
1126
1166
|
const opacity = el.dotted ? DOTTED_OPACITY : STRUCT_OPACITY;
|
|
1127
|
-
return `<g data-edge-id="${el.edgeId}">${title}${drawLine(dash, STRUCT_WIDTH, opacity)}</g>`;
|
|
1167
|
+
return `<g data-edge-id="${el.edgeId}">${title}${drawLine(dash, STRUCT_WIDTH, opacity)}${tick}</g>`;
|
|
1128
1168
|
}
|
|
1129
1169
|
const ink = EDGE_STROKE[el.lineStyle];
|
|
1130
|
-
return `<g data-edge-id="${el.edgeId}">${title}${drawLine(ink.dash, ink.width, ink.opacity)}</g>`;
|
|
1170
|
+
return `<g data-edge-id="${el.edgeId}">${title}${drawLine(ink.dash, ink.width, ink.opacity)}${tick}</g>`;
|
|
1131
1171
|
}
|
|
1132
1172
|
function genogramLayoutSvg(layout, opts = {}) {
|
|
1133
1173
|
const override = opts.unionStyleByRelId ?? /* @__PURE__ */ new Map();
|
|
@@ -1145,6 +1185,9 @@ function genogramLayoutSvg(layout, opts = {}) {
|
|
|
1145
1185
|
`<line x1="${node.cx - half}" y1="${node.cy - half}" x2="${node.cx + half}" y2="${node.cy + half}" stroke="${GLYPH_STROKE}" stroke-width="2"/>`
|
|
1146
1186
|
);
|
|
1147
1187
|
}
|
|
1188
|
+
if (node.annotated) {
|
|
1189
|
+
pieces.push(annotationDot(node.cx + 0.7 * half, node.cy - 0.7 * half));
|
|
1190
|
+
}
|
|
1148
1191
|
const tspans = node.labelLines.map(
|
|
1149
1192
|
(line, i) => `<tspan x="${node.cx}" y="${node.labelTop + 10 + i * LABEL_LINE_H}">${xmlEscape(line)}</tspan>`
|
|
1150
1193
|
).join("");
|
|
@@ -1187,6 +1230,12 @@ function genogramLayoutSvg(layout, opts = {}) {
|
|
|
1187
1230
|
label: labels.isolated
|
|
1188
1231
|
});
|
|
1189
1232
|
}
|
|
1233
|
+
if (opts.annotationLabel !== void 0 && (layout.nodes.some((n) => n.annotated) || layout.elements.some((e) => e.annotated))) {
|
|
1234
|
+
entries.push({
|
|
1235
|
+
swatch: (x, y) => annotationSwatch(x, y),
|
|
1236
|
+
label: opts.annotationLabel
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1190
1239
|
const block = legendBlock(entries, layout.height);
|
|
1191
1240
|
if (block.svg !== "") {
|
|
1192
1241
|
parts.push(block.svg);
|
|
@@ -1238,7 +1287,8 @@ function genogramSvg(input, opts = {}) {
|
|
|
1238
1287
|
const svg = genogramLayoutSvg(layout, {
|
|
1239
1288
|
unionStyleByRelId,
|
|
1240
1289
|
...opts.legend === false ? { legend: false } : {},
|
|
1241
|
-
...opts.svgLabels !== void 0 ? { labels: opts.svgLabels } : {}
|
|
1290
|
+
...opts.svgLabels !== void 0 ? { labels: opts.svgLabels } : {},
|
|
1291
|
+
...opts.annotationLabel !== void 0 ? { annotationLabel: opts.annotationLabel } : {}
|
|
1242
1292
|
});
|
|
1243
1293
|
return { svg, layout };
|
|
1244
1294
|
}
|
|
@@ -1268,7 +1318,7 @@ var SINGLE_RING_MAX = 8;
|
|
|
1268
1318
|
var NODE_STROKE = "#52525b";
|
|
1269
1319
|
var LABEL_FILL = "#3f3f46";
|
|
1270
1320
|
var EDGE_INK2 = "#71717a";
|
|
1271
|
-
var
|
|
1321
|
+
var round2 = (n) => Math.round(n * 100) / 100;
|
|
1272
1322
|
function arrowHead(tipX, tipY, ux, uy, opacity) {
|
|
1273
1323
|
const LEN = 9;
|
|
1274
1324
|
const HALF_W = 4.5;
|
|
@@ -1277,9 +1327,9 @@ function arrowHead(tipX, tipY, ux, uy, opacity) {
|
|
|
1277
1327
|
const px = -uy;
|
|
1278
1328
|
const py = ux;
|
|
1279
1329
|
const points = [
|
|
1280
|
-
`${
|
|
1281
|
-
`${
|
|
1282
|
-
`${
|
|
1330
|
+
`${round2(tipX)},${round2(tipY)}`,
|
|
1331
|
+
`${round2(bx + px * HALF_W)},${round2(by + py * HALF_W)}`,
|
|
1332
|
+
`${round2(bx - px * HALF_W)},${round2(by - py * HALF_W)}`
|
|
1283
1333
|
].join(" ");
|
|
1284
1334
|
return `<polygon points="${points}" fill="${EDGE_INK2}" fill-opacity="${opacity}"/>`;
|
|
1285
1335
|
}
|
|
@@ -1369,7 +1419,7 @@ function ecomapSvg(input, opts = {}) {
|
|
|
1369
1419
|
const dashAttr = ink.dash === null ? "" : ` stroke-dasharray="${ink.dash[0]} ${ink.dash[1]}"`;
|
|
1370
1420
|
const title = s.tie.title ?? (s.tie.quality !== null ? `${s.tie.label} \xB7 ${s.tie.quality}` : s.tie.label);
|
|
1371
1421
|
const body = [
|
|
1372
|
-
`<line x1="${
|
|
1422
|
+
`<line x1="${round2(x1)}" y1="${round2(y1)}" x2="${round2(x2)}" y2="${round2(y2)}" stroke="${EDGE_INK2}" stroke-width="${ink.width}" stroke-opacity="${ink.opacity}"${dashAttr}/>`
|
|
1373
1423
|
];
|
|
1374
1424
|
if (s.tie.direction === "in" || s.tie.direction === "both") {
|
|
1375
1425
|
body.push(arrowHead(x2, y2, ux, uy, ink.opacity));
|
|
@@ -1381,19 +1431,25 @@ function ecomapSvg(input, opts = {}) {
|
|
|
1381
1431
|
}
|
|
1382
1432
|
{
|
|
1383
1433
|
const tspans = centerLines.map(
|
|
1384
|
-
(line, i) => `<tspan x="${
|
|
1434
|
+
(line, i) => `<tspan x="${round2(cx)}" y="${round2(cy - (centerLines.length - 1) * LINE_H / 2 + i * LINE_H + fontSize * 0.32)}">${xmlEscape(line)}</tspan>`
|
|
1385
1435
|
).join("");
|
|
1386
1436
|
parts.push(
|
|
1387
|
-
`<g data-individual-id="center"><title>${xmlEscape(input.centerLabel)}</title><circle cx="${
|
|
1437
|
+
`<g data-individual-id="center"><title>${xmlEscape(input.centerLabel)}</title><circle cx="${round2(cx)}" cy="${round2(cy)}" r="${round2(centerR)}" fill="transparent" stroke="${NODE_STROKE}" stroke-width="2"/><text text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${fontSize}" fill="${LABEL_FILL}">${tspans}</text></g>`
|
|
1388
1438
|
);
|
|
1389
1439
|
}
|
|
1390
1440
|
for (const s of sats) {
|
|
1391
1441
|
const tspans = s.lines.map(
|
|
1392
|
-
(line, i) => `<tspan x="${
|
|
1442
|
+
(line, i) => `<tspan x="${round2(s.x)}" y="${round2(s.y - (s.lines.length - 1) * LINE_H / 2 + i * LINE_H + fontSize * 0.32)}">${xmlEscape(line)}</tspan>`
|
|
1393
1443
|
).join("");
|
|
1394
|
-
|
|
1395
|
-
`<
|
|
1396
|
-
|
|
1444
|
+
const pieces = [
|
|
1445
|
+
`<title>${xmlEscape(s.tie.label)}</title>`,
|
|
1446
|
+
`<ellipse cx="${round2(s.x)}" cy="${round2(s.y)}" rx="${round2(s.rx)}" ry="${round2(s.ry)}" fill="transparent" stroke="${NODE_STROKE}" stroke-width="1.5"/>`,
|
|
1447
|
+
`<text text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${fontSize}" fill="${LABEL_FILL}">${tspans}</text>`
|
|
1448
|
+
];
|
|
1449
|
+
if (s.tie.annotated) {
|
|
1450
|
+
pieces.push(annotationDot(s.x + 0.7 * s.rx, s.y - 0.7 * s.ry));
|
|
1451
|
+
}
|
|
1452
|
+
parts.push(`<g data-individual-id="e${s.tie.id}">${pieces.join("")}</g>`);
|
|
1397
1453
|
}
|
|
1398
1454
|
if (opts.legend !== false && sats.length > 0) {
|
|
1399
1455
|
const entries = [];
|
|
@@ -1419,6 +1475,12 @@ function ecomapSvg(input, opts = {}) {
|
|
|
1419
1475
|
label: labels.direction
|
|
1420
1476
|
});
|
|
1421
1477
|
}
|
|
1478
|
+
if (opts.annotationLabel !== void 0 && sats.some((s) => s.tie.annotated)) {
|
|
1479
|
+
entries.push({
|
|
1480
|
+
swatch: (x, y) => annotationSwatch(x, y),
|
|
1481
|
+
label: opts.annotationLabel
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1422
1484
|
if (entries.length > 0) {
|
|
1423
1485
|
const block = legendBlock(entries, height);
|
|
1424
1486
|
parts.push(block.svg);
|
|
@@ -1592,7 +1654,7 @@ function faultTreeIssues(input) {
|
|
|
1592
1654
|
);
|
|
1593
1655
|
}
|
|
1594
1656
|
}
|
|
1595
|
-
const
|
|
1657
|
+
const GRAPH_BLOCKING2 = /* @__PURE__ */ new Set([
|
|
1596
1658
|
"duplicate-id",
|
|
1597
1659
|
"unknown-input",
|
|
1598
1660
|
"gate-on-non-intermediate",
|
|
@@ -1601,7 +1663,7 @@ function faultTreeIssues(input) {
|
|
|
1601
1663
|
"inhibit-condition",
|
|
1602
1664
|
"top-not-intermediate"
|
|
1603
1665
|
]);
|
|
1604
|
-
if (!issues.some((i) =>
|
|
1666
|
+
if (!issues.some((i) => GRAPH_BLOCKING2.has(i.code))) {
|
|
1605
1667
|
const gateOf = (eventId) => gates.find((g) => g.eventId === eventId);
|
|
1606
1668
|
const reachable = /* @__PURE__ */ new Set();
|
|
1607
1669
|
const queue = [input.topId];
|
|
@@ -1681,7 +1743,7 @@ var FT_DROP_ID_BASE = 2e6;
|
|
|
1681
1743
|
var FT_BUS_ID_BASE = 3e6;
|
|
1682
1744
|
var FT_RISER_ID_BASE = 4e6;
|
|
1683
1745
|
var FT_CONDITION_ID_BASE = 5e6;
|
|
1684
|
-
var
|
|
1746
|
+
var round3 = (n) => Math.round(n * 100) / 100;
|
|
1685
1747
|
function wrapLeafLabel(displayLabel) {
|
|
1686
1748
|
const perLine = Math.min(20, Math.max(12, Math.ceil(displayLabel.length / 2) + 2));
|
|
1687
1749
|
return wrapLabel(displayLabel, perLine);
|
|
@@ -1868,14 +1930,14 @@ function computeFaultTreeLayout(input, opts = {}) {
|
|
|
1868
1930
|
eventId: inst.event.id,
|
|
1869
1931
|
kind: inst.event.kind,
|
|
1870
1932
|
instance: instanceOf(inst),
|
|
1871
|
-
cx:
|
|
1872
|
-
top:
|
|
1873
|
-
nodeW:
|
|
1874
|
-
nodeH:
|
|
1875
|
-
glyphW:
|
|
1876
|
-
glyphH:
|
|
1933
|
+
cx: round3(inst.cx),
|
|
1934
|
+
top: round3(top),
|
|
1935
|
+
nodeW: round3(inst.m.nodeW),
|
|
1936
|
+
nodeH: round3(inst.m.nodeH),
|
|
1937
|
+
glyphW: round3(inst.m.glyphW),
|
|
1938
|
+
glyphH: round3(inst.m.glyphH),
|
|
1877
1939
|
labelLines: inst.m.labelLines,
|
|
1878
|
-
labelTop: labelTop === null ? null :
|
|
1940
|
+
labelTop: labelTop === null ? null : round3(labelTop),
|
|
1879
1941
|
code: inst.m.code,
|
|
1880
1942
|
depth: inst.depth,
|
|
1881
1943
|
title: inst.title
|
|
@@ -1893,8 +1955,8 @@ function computeFaultTreeLayout(input, opts = {}) {
|
|
|
1893
1955
|
gates.push({
|
|
1894
1956
|
gateId: g.id,
|
|
1895
1957
|
type: g.type,
|
|
1896
|
-
cx:
|
|
1897
|
-
top:
|
|
1958
|
+
cx: round3(inst.cx),
|
|
1959
|
+
top: round3(gateTop),
|
|
1898
1960
|
glyphW,
|
|
1899
1961
|
glyphH,
|
|
1900
1962
|
title,
|
|
@@ -1911,8 +1973,8 @@ function computeFaultTreeLayout(input, opts = {}) {
|
|
|
1911
1973
|
edgeId: FT_CONDITION_ID_BASE + g.id,
|
|
1912
1974
|
kind: "condition",
|
|
1913
1975
|
points: [
|
|
1914
|
-
{ x:
|
|
1915
|
-
{ x:
|
|
1976
|
+
{ x: round3(inst.cx + INHIBIT_W / 2), y: round3(gateTop + INHIBIT_CY) },
|
|
1977
|
+
{ x: round3(ovalLeft), y: round3(gateTop + INHIBIT_CY) }
|
|
1916
1978
|
],
|
|
1917
1979
|
instance: null,
|
|
1918
1980
|
title: titleLabels.condition
|
|
@@ -1922,8 +1984,8 @@ function computeFaultTreeLayout(input, opts = {}) {
|
|
|
1922
1984
|
edgeId: FT_STEM_ID_BASE + g.id,
|
|
1923
1985
|
kind: "stem",
|
|
1924
1986
|
points: [
|
|
1925
|
-
{ x:
|
|
1926
|
-
{ x:
|
|
1987
|
+
{ x: round3(inst.cx), y: round3(rowTop[d] + inst.m.nodeH) },
|
|
1988
|
+
{ x: round3(inst.cx), y: round3(gateTop) }
|
|
1927
1989
|
],
|
|
1928
1990
|
instance: null,
|
|
1929
1991
|
title
|
|
@@ -1933,8 +1995,8 @@ function computeFaultTreeLayout(input, opts = {}) {
|
|
|
1933
1995
|
edgeId: FT_DROP_ID_BASE + g.id,
|
|
1934
1996
|
kind: "drop",
|
|
1935
1997
|
points: [
|
|
1936
|
-
{ x:
|
|
1937
|
-
{ x:
|
|
1998
|
+
{ x: round3(inst.cx), y: round3(gateTop + glyphH) },
|
|
1999
|
+
{ x: round3(inst.cx), y: round3(by) }
|
|
1938
2000
|
],
|
|
1939
2001
|
instance: null,
|
|
1940
2002
|
title
|
|
@@ -1945,8 +2007,8 @@ function computeFaultTreeLayout(input, opts = {}) {
|
|
|
1945
2007
|
edgeId: FT_BUS_ID_BASE + g.id,
|
|
1946
2008
|
kind: "bus",
|
|
1947
2009
|
points: [
|
|
1948
|
-
{ x:
|
|
1949
|
-
{ x:
|
|
2010
|
+
{ x: round3(Math.min(...xs)), y: round3(by) },
|
|
2011
|
+
{ x: round3(Math.max(...xs)), y: round3(by) }
|
|
1950
2012
|
],
|
|
1951
2013
|
instance: null,
|
|
1952
2014
|
title
|
|
@@ -1957,8 +2019,8 @@ function computeFaultTreeLayout(input, opts = {}) {
|
|
|
1957
2019
|
edgeId: FT_RISER_ID_BASE + c.event.id,
|
|
1958
2020
|
kind: "riser",
|
|
1959
2021
|
points: [
|
|
1960
|
-
{ x:
|
|
1961
|
-
{ x:
|
|
2022
|
+
{ x: round3(c.cx), y: round3(by) },
|
|
2023
|
+
{ x: round3(c.cx), y: round3(rowTop[d + 1]) }
|
|
1962
2024
|
],
|
|
1963
2025
|
instance: instanceOf(c),
|
|
1964
2026
|
title
|
|
@@ -1975,42 +2037,42 @@ var GLYPH_STROKE2 = "#52525b";
|
|
|
1975
2037
|
var LABEL_FILL2 = "#3f3f46";
|
|
1976
2038
|
var EDGE_INK3 = "#71717a";
|
|
1977
2039
|
var GLYPH_ATTRS = `fill="transparent" stroke="${GLYPH_STROKE2}" stroke-width="2"`;
|
|
1978
|
-
var
|
|
2040
|
+
var round4 = (n) => Math.round(n * 100) / 100;
|
|
1979
2041
|
function eventGlyph(n) {
|
|
1980
2042
|
const cx = n.cx;
|
|
1981
2043
|
const top = n.top;
|
|
1982
2044
|
if (n.kind === "intermediate") {
|
|
1983
|
-
return `<rect x="${
|
|
2045
|
+
return `<rect x="${round4(cx - n.nodeW / 2)}" y="${top}" width="${n.nodeW}" height="${n.nodeH}" rx="2" ${GLYPH_ATTRS}/>`;
|
|
1984
2046
|
}
|
|
1985
2047
|
if (n.kind === "basic") {
|
|
1986
|
-
return `<circle cx="${cx}" cy="${
|
|
2048
|
+
return `<circle cx="${cx}" cy="${round4(top + 22)}" r="22" ${GLYPH_ATTRS}/>`;
|
|
1987
2049
|
}
|
|
1988
2050
|
if (n.kind === "undeveloped") {
|
|
1989
|
-
const pts2 = `${cx},${top} ${
|
|
2051
|
+
const pts2 = `${cx},${top} ${round4(cx + 24)},${round4(top + 24)} ${cx},${round4(top + 48)} ${round4(cx - 24)},${round4(top + 24)}`;
|
|
1990
2052
|
return `<polygon points="${pts2}" ${GLYPH_ATTRS}/>`;
|
|
1991
2053
|
}
|
|
1992
2054
|
if (n.kind === "house") {
|
|
1993
|
-
const yB =
|
|
1994
|
-
const eave =
|
|
1995
|
-
const pts2 = `${
|
|
2055
|
+
const yB = round4(top + 40);
|
|
2056
|
+
const eave = round4(top + 16);
|
|
2057
|
+
const pts2 = `${round4(cx - 22)},${yB} ${round4(cx - 22)},${eave} ${cx},${top} ${round4(cx + 22)},${eave} ${round4(cx + 22)},${yB}`;
|
|
1996
2058
|
return `<polygon points="${pts2}" ${GLYPH_ATTRS}/>`;
|
|
1997
2059
|
}
|
|
1998
2060
|
if (n.kind === "conditioning") {
|
|
1999
|
-
return `<ellipse cx="${cx}" cy="${
|
|
2061
|
+
return `<ellipse cx="${cx}" cy="${round4(top + 16)}" rx="${round4(n.glyphW / 2)}" ry="16" ${GLYPH_ATTRS}/>`;
|
|
2000
2062
|
}
|
|
2001
|
-
const pts = `${cx},${top} ${
|
|
2063
|
+
const pts = `${cx},${top} ${round4(cx + 22)},${round4(top + 35)} ${round4(cx - 22)},${round4(top + 35)}`;
|
|
2002
2064
|
return `<polygon points="${pts}" ${GLYPH_ATTRS}/>`;
|
|
2003
2065
|
}
|
|
2004
2066
|
function nodeSvg(n) {
|
|
2005
2067
|
const pieces = [`<title>${xmlEscape(n.title)}</title>`, eventGlyph(n)];
|
|
2006
2068
|
if (n.code !== null && n.kind !== "intermediate") {
|
|
2007
2069
|
pieces.push(
|
|
2008
|
-
`<text x="${n.cx}" y="${
|
|
2070
|
+
`<text x="${n.cx}" y="${round4(n.top + n.glyphH / 2 + CODE_FONT * 0.32)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${CODE_FONT}" fill="${LABEL_FILL2}">${xmlEscape(n.code)}</text>`
|
|
2009
2071
|
);
|
|
2010
2072
|
}
|
|
2011
2073
|
if (n.labelLines.length > 0) {
|
|
2012
|
-
const firstBaseline = n.labelTop === null ?
|
|
2013
|
-
const tspans = n.labelLines.map((line, i) => `<tspan x="${n.cx}" y="${
|
|
2074
|
+
const firstBaseline = n.labelTop === null ? round4(n.top + 19) : round4(n.labelTop + 10);
|
|
2075
|
+
const tspans = n.labelLines.map((line, i) => `<tspan x="${n.cx}" y="${round4(firstBaseline + i * FT_LABEL_LINE_H)}">${xmlEscape(line)}</tspan>`).join("");
|
|
2014
2076
|
pieces.push(
|
|
2015
2077
|
`<text text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${FT_LABEL_FONT}" fill="${LABEL_FILL2}">${tspans}</text>`
|
|
2016
2078
|
);
|
|
@@ -2019,28 +2081,28 @@ function nodeSvg(n) {
|
|
|
2019
2081
|
return `<g data-node-id="e${n.eventId}"${instance}>${pieces.join("")}</g>`;
|
|
2020
2082
|
}
|
|
2021
2083
|
function orBodyPath(cx, top, yB) {
|
|
2022
|
-
return `M ${
|
|
2084
|
+
return `M ${round4(cx - 22)} ${yB} Q ${cx} ${round4(yB - 14)} ${round4(cx + 22)} ${yB} Q ${round4(cx + 22)} ${round4(top + 14)} ${cx} ${top} Q ${round4(cx - 22)} ${round4(top + 14)} ${round4(cx - 22)} ${yB} Z`;
|
|
2023
2085
|
}
|
|
2024
2086
|
function gateGlyph(g) {
|
|
2025
2087
|
const cx = g.cx;
|
|
2026
2088
|
const top = g.top;
|
|
2027
|
-
const yB =
|
|
2089
|
+
const yB = round4(top + 36);
|
|
2028
2090
|
if (g.type === "and") {
|
|
2029
|
-
const d = `M ${
|
|
2091
|
+
const d = `M ${round4(cx - 22)} ${yB} L ${round4(cx - 22)} ${round4(yB - 14)} A 22 22 0 0 1 ${round4(cx + 22)} ${round4(yB - 14)} L ${round4(cx + 22)} ${yB} Z`;
|
|
2030
2092
|
return `<path d="${d}" ${GLYPH_ATTRS}/>`;
|
|
2031
2093
|
}
|
|
2032
2094
|
if (g.type === "or") {
|
|
2033
2095
|
return `<path d="${orBodyPath(cx, top, yB)}" ${GLYPH_ATTRS}/>`;
|
|
2034
2096
|
}
|
|
2035
2097
|
if (g.type === "xor") {
|
|
2036
|
-
const arc = `M ${
|
|
2098
|
+
const arc = `M ${round4(cx - 22)} ${round4(yB + 5)} Q ${cx} ${round4(yB - 9)} ${round4(cx + 22)} ${round4(yB + 5)}`;
|
|
2037
2099
|
return `<path d="${orBodyPath(cx, top, yB)}" ${GLYPH_ATTRS}/><path d="${arc}" ${GLYPH_ATTRS}/>`;
|
|
2038
2100
|
}
|
|
2039
2101
|
if (g.type === "inhibit") {
|
|
2040
|
-
const pts = `${cx},${top} ${
|
|
2102
|
+
const pts = `${cx},${top} ${round4(cx + 18)},${round4(top + 12)} ${round4(cx + 18)},${round4(top + 34)} ${cx},${round4(top + 46)} ${round4(cx - 18)},${round4(top + 34)} ${round4(cx - 18)},${round4(top + 12)}`;
|
|
2041
2103
|
return `<polygon points="${pts}" ${GLYPH_ATTRS}/>`;
|
|
2042
2104
|
}
|
|
2043
|
-
return `<path d="${orBodyPath(cx, top, yB)}" ${GLYPH_ATTRS}/><text x="${cx}" y="${
|
|
2105
|
+
return `<path d="${orBodyPath(cx, top, yB)}" ${GLYPH_ATTRS}/><text x="${cx}" y="${round4(yB - 10)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="9" fill="${LABEL_FILL2}">${xmlEscape(g.voteText ?? "")}</text>`;
|
|
2044
2106
|
}
|
|
2045
2107
|
function gateSvg(g) {
|
|
2046
2108
|
return `<g data-node-id="g${g.gateId}"><title>${xmlEscape(g.title)}</title>${gateGlyph(g)}</g>`;
|
|
@@ -2053,34 +2115,34 @@ function elementSvg2(el) {
|
|
|
2053
2115
|
}
|
|
2054
2116
|
var MINI_ATTRS = `fill="transparent" stroke="${GLYPH_STROKE2}" stroke-width="1.5"`;
|
|
2055
2117
|
function miniEventSwatch(kind, x, y) {
|
|
2056
|
-
const cx =
|
|
2118
|
+
const cx = round4(x + LEGEND_SWATCH_W / 2);
|
|
2057
2119
|
if (kind === "intermediate") {
|
|
2058
|
-
return `<rect x="${
|
|
2120
|
+
return `<rect x="${round4(cx - 7)}" y="${round4(y - 4.5)}" width="14" height="9" rx="1" ${MINI_ATTRS}/>`;
|
|
2059
2121
|
}
|
|
2060
2122
|
if (kind === "basic") return `<circle cx="${cx}" cy="${y}" r="6" ${MINI_ATTRS}/>`;
|
|
2061
2123
|
if (kind === "undeveloped") {
|
|
2062
|
-
return `<polygon points="${cx},${
|
|
2124
|
+
return `<polygon points="${cx},${round4(y - 7)} ${round4(cx + 7)},${y} ${cx},${round4(y + 7)} ${round4(cx - 7)},${y}" ${MINI_ATTRS}/>`;
|
|
2063
2125
|
}
|
|
2064
2126
|
if (kind === "house") {
|
|
2065
|
-
return `<polygon points="${
|
|
2127
|
+
return `<polygon points="${round4(cx - 6)},${round4(y + 5.5)} ${round4(cx - 6)},${round4(y - 1)} ${cx},${round4(y - 5.5)} ${round4(cx + 6)},${round4(y - 1)} ${round4(cx + 6)},${round4(y + 5.5)}" ${MINI_ATTRS}/>`;
|
|
2066
2128
|
}
|
|
2067
2129
|
if (kind === "conditioning") return `<ellipse cx="${cx}" cy="${y}" rx="9" ry="5.5" ${MINI_ATTRS}/>`;
|
|
2068
|
-
return `<polygon points="${cx},${
|
|
2130
|
+
return `<polygon points="${cx},${round4(y - 5)} ${round4(cx + 6)},${round4(y + 5)} ${round4(cx - 6)},${round4(y + 5)}" ${MINI_ATTRS}/>`;
|
|
2069
2131
|
}
|
|
2070
2132
|
function miniOrPath(cx, y) {
|
|
2071
|
-
return `M ${
|
|
2133
|
+
return `M ${round4(cx - 7)} ${round4(y + 5.5)} Q ${cx} ${round4(y + 1)} ${round4(cx + 7)} ${round4(y + 5.5)} Q ${round4(cx + 7)} ${round4(y - 1.5)} ${cx} ${round4(y - 5.5)} Q ${round4(cx - 7)} ${round4(y - 1.5)} ${round4(cx - 7)} ${round4(y + 5.5)} Z`;
|
|
2072
2134
|
}
|
|
2073
2135
|
function miniGateSwatch(type, x, y) {
|
|
2074
|
-
const cx =
|
|
2136
|
+
const cx = round4(x + LEGEND_SWATCH_W / 2);
|
|
2075
2137
|
if (type === "and") {
|
|
2076
|
-
const d = `M ${
|
|
2138
|
+
const d = `M ${round4(cx - 7)} ${round4(y + 5.5)} L ${round4(cx - 7)} ${round4(y + 1.5)} A 7 7 0 0 1 ${round4(cx + 7)} ${round4(y + 1.5)} L ${round4(cx + 7)} ${round4(y + 5.5)} Z`;
|
|
2077
2139
|
return `<path d="${d}" ${MINI_ATTRS}/>`;
|
|
2078
2140
|
}
|
|
2079
2141
|
if (type === "xor") {
|
|
2080
|
-
return `<path d="${miniOrPath(cx, y)}" ${MINI_ATTRS}/><path d="M ${
|
|
2142
|
+
return `<path d="${miniOrPath(cx, y)}" ${MINI_ATTRS}/><path d="M ${round4(cx - 7)} ${round4(y + 7.5)} Q ${cx} ${round4(y + 3)} ${round4(cx + 7)} ${round4(y + 7.5)}" ${MINI_ATTRS}/>`;
|
|
2081
2143
|
}
|
|
2082
2144
|
if (type === "inhibit") {
|
|
2083
|
-
return `<polygon points="${cx},${
|
|
2145
|
+
return `<polygon points="${cx},${round4(y - 5.5)} ${round4(cx + 4.5)},${round4(y - 2.5)} ${round4(cx + 4.5)},${round4(y + 2.5)} ${cx},${round4(y + 5.5)} ${round4(cx - 4.5)},${round4(y + 2.5)} ${round4(cx - 4.5)},${round4(y - 2.5)}" ${MINI_ATTRS}/>`;
|
|
2084
2146
|
}
|
|
2085
2147
|
return `<path d="${miniOrPath(cx, y)}" ${MINI_ATTRS}/>`;
|
|
2086
2148
|
}
|
|
@@ -2209,7 +2271,7 @@ var TWIG_W = 1.5;
|
|
|
2209
2271
|
var TWIG_OP = 0.75;
|
|
2210
2272
|
var SUB_W = 1.2;
|
|
2211
2273
|
var SUB_OP = 0.7;
|
|
2212
|
-
var
|
|
2274
|
+
var round5 = (n) => Math.round(n * 100) / 100;
|
|
2213
2275
|
function arrowHead2(tipX, tipY, ux, uy, opacity) {
|
|
2214
2276
|
const LEN = 9;
|
|
2215
2277
|
const HALF_W = 4.5;
|
|
@@ -2218,14 +2280,14 @@ function arrowHead2(tipX, tipY, ux, uy, opacity) {
|
|
|
2218
2280
|
const px = -uy;
|
|
2219
2281
|
const py = ux;
|
|
2220
2282
|
const points = [
|
|
2221
|
-
`${
|
|
2222
|
-
`${
|
|
2223
|
-
`${
|
|
2283
|
+
`${round5(tipX)},${round5(tipY)}`,
|
|
2284
|
+
`${round5(bx + px * HALF_W)},${round5(by + py * HALF_W)}`,
|
|
2285
|
+
`${round5(bx - px * HALF_W)},${round5(by - py * HALF_W)}`
|
|
2224
2286
|
].join(" ");
|
|
2225
2287
|
return `<polygon points="${points}" fill="${EDGE_INK4}" fill-opacity="${opacity}"/>`;
|
|
2226
2288
|
}
|
|
2227
2289
|
function lineEl(x1, y1, x2, y2, w, op) {
|
|
2228
|
-
return `<line x1="${
|
|
2290
|
+
return `<line x1="${round5(x1)}" y1="${round5(y1)}" x2="${round5(x2)}" y2="${round5(y2)}" stroke="${EDGE_INK4}" stroke-width="${w}" stroke-opacity="${op}"/>`;
|
|
2229
2291
|
}
|
|
2230
2292
|
function fishboneSvg(input, opts = {}) {
|
|
2231
2293
|
validateIds(input);
|
|
@@ -2306,7 +2368,7 @@ function fishboneSvg(input, opts = {}) {
|
|
|
2306
2368
|
{ length: n },
|
|
2307
2369
|
(_, k) => up ? spineY - (e + twigGap + (n - 1 - k) * lineH) : spineY + e + twigGap + ascent + k * lineH
|
|
2308
2370
|
);
|
|
2309
|
-
const textBlock = (anchor, x, ys, lines) => `<text text-anchor="${anchor}" font-family="${FONT_FAMILY}" font-size="${fontSize}" fill="${LABEL_FILL3}">` + lines.map((line, i) => `<tspan x="${
|
|
2371
|
+
const textBlock = (anchor, x, ys, lines) => `<text text-anchor="${anchor}" font-family="${FONT_FAMILY}" font-size="${fontSize}" fill="${LABEL_FILL3}">` + lines.map((line, i) => `<tspan x="${round5(x)}" y="${round5(ys[i])}">${xmlEscape(line)}</tspan>`).join("") + `</text>`;
|
|
2310
2372
|
const parts = [];
|
|
2311
2373
|
{
|
|
2312
2374
|
const body = [lineEl(tailX + dx, spineY, headLeft + dx, spineY, SPINE_W, SPINE_OP)];
|
|
@@ -2321,7 +2383,7 @@ function fishboneSvg(input, opts = {}) {
|
|
|
2321
2383
|
if (arrows) body.push(arrowHead2(ax, spineY, COS60, -sgn * SIN60, BONE_OP));
|
|
2322
2384
|
const boxTop = bone.up ? spineY - bone.B - CAT_GAP - bone.boxH : spineY + bone.B + CAT_GAP;
|
|
2323
2385
|
body.push(
|
|
2324
|
-
`<rect x="${
|
|
2386
|
+
`<rect x="${round5(tipX - bone.boxW / 2)}" y="${round5(boxTop)}" width="${round5(bone.boxW)}" height="${round5(bone.boxH)}" rx="2" fill="transparent" stroke="${BOX_STROKE}" stroke-width="1.5"/>`
|
|
2325
2387
|
);
|
|
2326
2388
|
body.push(textBlock("middle", tipX, centeredYs(boxTop + bone.boxH / 2, bone.catLines.length), bone.catLines));
|
|
2327
2389
|
parts.push(
|
|
@@ -2351,7 +2413,7 @@ function fishboneSvg(input, opts = {}) {
|
|
|
2351
2413
|
{
|
|
2352
2414
|
const x = headLeft + dx;
|
|
2353
2415
|
parts.push(
|
|
2354
|
-
`<g data-node-id="head"><title>${xmlEscape(input.effectLabel)}</title><rect x="${
|
|
2416
|
+
`<g data-node-id="head"><title>${xmlEscape(input.effectLabel)}</title><rect x="${round5(x)}" y="${round5(spineY - headH / 2)}" width="${round5(headW)}" height="${round5(headH)}" rx="2" fill="transparent" stroke="${BOX_STROKE}" stroke-width="2"/>` + textBlock("middle", x + headW / 2, centeredYs(spineY, effLines.length), effLines) + `</g>`
|
|
2355
2417
|
);
|
|
2356
2418
|
}
|
|
2357
2419
|
const anySubs = input.categories.some((c) => c.causes.some((k) => k.subCauses.length > 0));
|
|
@@ -2565,13 +2627,13 @@ function pedigreeIssues(input) {
|
|
|
2565
2627
|
);
|
|
2566
2628
|
}
|
|
2567
2629
|
}
|
|
2568
|
-
const
|
|
2630
|
+
const GRAPH_BLOCKING2 = /* @__PURE__ */ new Set([
|
|
2569
2631
|
"duplicate-id",
|
|
2570
2632
|
"unknown-partner",
|
|
2571
2633
|
"unknown-sibship-mating",
|
|
2572
2634
|
"unknown-child"
|
|
2573
2635
|
]);
|
|
2574
|
-
if (!issues.some((i) =>
|
|
2636
|
+
if (!issues.some((i) => GRAPH_BLOCKING2.has(i.code))) {
|
|
2575
2637
|
for (const s of sibships) {
|
|
2576
2638
|
const mating = matingById.get(s.matingId);
|
|
2577
2639
|
if (mating === void 0) continue;
|
|
@@ -2619,7 +2681,7 @@ var PED_DESCENT_ID_BASE = 2e6;
|
|
|
2619
2681
|
var PED_SIBBAR_ID_BASE = 3e6;
|
|
2620
2682
|
var PED_RISER_ID_BASE = 4e6;
|
|
2621
2683
|
var PED_TWINBAR_ID_BASE = 5e6;
|
|
2622
|
-
var
|
|
2684
|
+
var round6 = (n) => Math.round(n * 100) / 100;
|
|
2623
2685
|
function shapeForSex2(sex) {
|
|
2624
2686
|
if (sex === "male") return "square";
|
|
2625
2687
|
if (sex === "female") return "circle";
|
|
@@ -2885,8 +2947,8 @@ function computePedigreeLayout(input, opts = {}) {
|
|
|
2885
2947
|
nodes.push({
|
|
2886
2948
|
individualId: ind.id,
|
|
2887
2949
|
shape: shapeForSex2(ind.sex),
|
|
2888
|
-
cx:
|
|
2889
|
-
cy:
|
|
2950
|
+
cx: round6(placed.cx),
|
|
2951
|
+
cy: round6(cy),
|
|
2890
2952
|
size: PED_GLYPH,
|
|
2891
2953
|
deceased: ind.deceased,
|
|
2892
2954
|
carrier: ind.carrier,
|
|
@@ -2896,7 +2958,7 @@ function computePedigreeLayout(input, opts = {}) {
|
|
|
2896
2958
|
labelLines: lines,
|
|
2897
2959
|
// Labels sit BELOW the row's serial-union dip band (rowDipBand) so a hub's bridge dips
|
|
2898
2960
|
// never cross a label box.
|
|
2899
|
-
labelTop:
|
|
2961
|
+
labelTop: round6(cy + PED_GLYPH / 2 + PED_LABEL_GAP + rowDipBand[row]),
|
|
2900
2962
|
addressLabel: address,
|
|
2901
2963
|
title: individualTitle(ind, address, conditionLabelById, titleLabels)
|
|
2902
2964
|
});
|
|
@@ -2939,23 +3001,23 @@ function computePedigreeLayout(input, opts = {}) {
|
|
|
2939
3001
|
edgeId: PED_MATING_ID_BASE + m.id,
|
|
2940
3002
|
kind: "mating-elbow",
|
|
2941
3003
|
points: [
|
|
2942
|
-
{ x:
|
|
2943
|
-
{ x:
|
|
2944
|
-
{ x:
|
|
2945
|
-
{ x:
|
|
3004
|
+
{ x: round6(hubStubX), y: round6(glyphBottom) },
|
|
3005
|
+
{ x: round6(hubStubX), y: round6(dipY) },
|
|
3006
|
+
{ x: round6(spouseStubX), y: round6(dipY) },
|
|
3007
|
+
{ x: round6(spouseStubX), y: round6(glyphBottom) }
|
|
2946
3008
|
],
|
|
2947
3009
|
consanguineous: m.consanguineous,
|
|
2948
3010
|
title
|
|
2949
3011
|
});
|
|
2950
|
-
matingMidpoint.set(m.id, { x:
|
|
3012
|
+
matingMidpoint.set(m.id, { x: round6((hubStubX + spouseStubX) / 2), y: round6(dipY) });
|
|
2951
3013
|
} else if (ay === by) {
|
|
2952
3014
|
const y = ay;
|
|
2953
3015
|
elements.push({
|
|
2954
3016
|
edgeId: PED_MATING_ID_BASE + m.id,
|
|
2955
3017
|
kind: "mating",
|
|
2956
3018
|
points: [
|
|
2957
|
-
{ x:
|
|
2958
|
-
{ x:
|
|
3019
|
+
{ x: round6(lx), y: round6(y) },
|
|
3020
|
+
{ x: round6(rx), y: round6(y) }
|
|
2959
3021
|
],
|
|
2960
3022
|
consanguineous: m.consanguineous,
|
|
2961
3023
|
title
|
|
@@ -2965,7 +3027,7 @@ function computePedigreeLayout(input, opts = {}) {
|
|
|
2965
3027
|
const channelRight = cxOf(rightId) - nodeHalfWidth(individualById.get(rightId));
|
|
2966
3028
|
const barCenter = matingBarCenter.get(m.id);
|
|
2967
3029
|
const originX = barCenter !== void 0 && barCenter >= channelLeft && barCenter <= channelRight ? barCenter : midX;
|
|
2968
|
-
matingMidpoint.set(m.id, { x:
|
|
3030
|
+
matingMidpoint.set(m.id, { x: round6(originX), y: round6(y) });
|
|
2969
3031
|
} else {
|
|
2970
3032
|
const upId = ay <= by ? aId : bId;
|
|
2971
3033
|
const downId = ay <= by ? bId : aId;
|
|
@@ -2984,32 +3046,32 @@ function computePedigreeLayout(input, opts = {}) {
|
|
|
2984
3046
|
edgeId: PED_MATING_ID_BASE + m.id,
|
|
2985
3047
|
kind: "mating-elbow",
|
|
2986
3048
|
points: [
|
|
2987
|
-
{ x:
|
|
2988
|
-
{ x:
|
|
2989
|
-
{ x:
|
|
2990
|
-
{ x:
|
|
2991
|
-
{ x:
|
|
2992
|
-
{ x:
|
|
3049
|
+
{ x: round6(upX + (downRight ? -GLYPH_HALF : GLYPH_HALF)), y: round6(upY) },
|
|
3050
|
+
{ x: round6(upExitX), y: round6(upY) },
|
|
3051
|
+
{ x: round6(upExitX), y: round6(laneY) },
|
|
3052
|
+
{ x: round6(downFarX), y: round6(laneY) },
|
|
3053
|
+
{ x: round6(downFarX), y: round6(downY) },
|
|
3054
|
+
{ x: round6(downSideX), y: round6(downY) }
|
|
2993
3055
|
],
|
|
2994
3056
|
consanguineous: m.consanguineous,
|
|
2995
3057
|
title
|
|
2996
3058
|
});
|
|
2997
|
-
matingMidpoint.set(m.id, { x:
|
|
3059
|
+
matingMidpoint.set(m.id, { x: round6(downFarX), y: round6(downY) });
|
|
2998
3060
|
} else {
|
|
2999
3061
|
const elbowX = (upX + downX) / 2;
|
|
3000
3062
|
elements.push({
|
|
3001
3063
|
edgeId: PED_MATING_ID_BASE + m.id,
|
|
3002
3064
|
kind: "mating-elbow",
|
|
3003
3065
|
points: [
|
|
3004
|
-
{ x:
|
|
3005
|
-
{ x:
|
|
3006
|
-
{ x:
|
|
3007
|
-
{ x:
|
|
3066
|
+
{ x: round6(upX + (downX >= upX ? GLYPH_HALF : -GLYPH_HALF)), y: round6(upY) },
|
|
3067
|
+
{ x: round6(elbowX), y: round6(upY) },
|
|
3068
|
+
{ x: round6(elbowX), y: round6(downY) },
|
|
3069
|
+
{ x: round6(downX + (upX >= downX ? GLYPH_HALF : -GLYPH_HALF)), y: round6(downY) }
|
|
3008
3070
|
],
|
|
3009
3071
|
consanguineous: m.consanguineous,
|
|
3010
3072
|
title
|
|
3011
3073
|
});
|
|
3012
|
-
matingMidpoint.set(m.id, { x:
|
|
3074
|
+
matingMidpoint.set(m.id, { x: round6(elbowX), y: round6(downY) });
|
|
3013
3075
|
}
|
|
3014
3076
|
}
|
|
3015
3077
|
}
|
|
@@ -3031,13 +3093,13 @@ function computePedigreeLayout(input, opts = {}) {
|
|
|
3031
3093
|
const barCenterX = (barLeft + barRight) / 2;
|
|
3032
3094
|
const bendY = barY - 2 - lane % 3 * 3;
|
|
3033
3095
|
const descentPoints = Math.abs(mid.x - barCenterX) < 0.01 ? [
|
|
3034
|
-
{ x:
|
|
3035
|
-
{ x:
|
|
3096
|
+
{ x: round6(mid.x), y: round6(mid.y) },
|
|
3097
|
+
{ x: round6(mid.x), y: round6(barY) }
|
|
3036
3098
|
] : [
|
|
3037
|
-
{ x:
|
|
3038
|
-
{ x:
|
|
3039
|
-
{ x:
|
|
3040
|
-
{ x:
|
|
3099
|
+
{ x: round6(mid.x), y: round6(mid.y) },
|
|
3100
|
+
{ x: round6(mid.x), y: round6(bendY) },
|
|
3101
|
+
{ x: round6(barCenterX), y: round6(bendY) },
|
|
3102
|
+
{ x: round6(barCenterX), y: round6(barY) }
|
|
3041
3103
|
];
|
|
3042
3104
|
elements.push({
|
|
3043
3105
|
edgeId: PED_DESCENT_ID_BASE + s.id,
|
|
@@ -3052,8 +3114,8 @@ function computePedigreeLayout(input, opts = {}) {
|
|
|
3052
3114
|
edgeId: PED_SIBBAR_ID_BASE + s.id,
|
|
3053
3115
|
kind: "sibship-bar",
|
|
3054
3116
|
points: [
|
|
3055
|
-
{ x:
|
|
3056
|
-
{ x:
|
|
3117
|
+
{ x: round6(barLeft), y: round6(barY) },
|
|
3118
|
+
{ x: round6(barRight), y: round6(barY) }
|
|
3057
3119
|
],
|
|
3058
3120
|
consanguineous: false,
|
|
3059
3121
|
title: titleLabels.sibship
|
|
@@ -3074,8 +3136,8 @@ function computePedigreeLayout(input, opts = {}) {
|
|
|
3074
3136
|
edgeId: PED_RISER_ID_BASE + childId,
|
|
3075
3137
|
kind: "riser",
|
|
3076
3138
|
points: [
|
|
3077
|
-
{ x:
|
|
3078
|
-
{ x:
|
|
3139
|
+
{ x: round6(cx), y: round6(barY) },
|
|
3140
|
+
{ x: round6(cx), y: round6(childTop) }
|
|
3079
3141
|
],
|
|
3080
3142
|
consanguineous: false,
|
|
3081
3143
|
title: titleLabels.sibship
|
|
@@ -3089,15 +3151,15 @@ function computePedigreeLayout(input, opts = {}) {
|
|
|
3089
3151
|
if (!emittedTwinOrdinals.has(tg.ordinal)) {
|
|
3090
3152
|
emittedTwinOrdinals.add(tg.ordinal);
|
|
3091
3153
|
if (tg.zygosity === "unknown") {
|
|
3092
|
-
unknownTwinJunctions.push({ x:
|
|
3154
|
+
unknownTwinJunctions.push({ x: round6(junctionX), y: round6(junctionY) });
|
|
3093
3155
|
}
|
|
3094
3156
|
elements.push({
|
|
3095
3157
|
edgeId: PED_RISER_ID_BASE + childId,
|
|
3096
3158
|
// anchored on the first member for a stable id
|
|
3097
3159
|
kind: "riser",
|
|
3098
3160
|
points: [
|
|
3099
|
-
{ x:
|
|
3100
|
-
{ x:
|
|
3161
|
+
{ x: round6(junctionX), y: round6(barY) },
|
|
3162
|
+
{ x: round6(junctionX), y: round6(junctionY) }
|
|
3101
3163
|
],
|
|
3102
3164
|
consanguineous: false,
|
|
3103
3165
|
title: titleLabels.twins[tg.zygosity]
|
|
@@ -3108,8 +3170,8 @@ function computePedigreeLayout(input, opts = {}) {
|
|
|
3108
3170
|
edgeId: PED_TWINBAR_ID_BASE + s.id * 100 + tg.ordinal,
|
|
3109
3171
|
kind: "twin-bar",
|
|
3110
3172
|
points: [
|
|
3111
|
-
{ x:
|
|
3112
|
-
{ x:
|
|
3173
|
+
{ x: round6(Math.min(...memberXs)), y: round6(tieY) },
|
|
3174
|
+
{ x: round6(Math.max(...memberXs)), y: round6(tieY) }
|
|
3113
3175
|
],
|
|
3114
3176
|
consanguineous: false,
|
|
3115
3177
|
title: titleLabels.twins.mz
|
|
@@ -3117,12 +3179,12 @@ function computePedigreeLayout(input, opts = {}) {
|
|
|
3117
3179
|
}
|
|
3118
3180
|
}
|
|
3119
3181
|
const horizontalThenDown = Math.abs(cx - junctionX) < 0.01 ? [
|
|
3120
|
-
{ x:
|
|
3121
|
-
{ x:
|
|
3182
|
+
{ x: round6(cx), y: round6(junctionY) },
|
|
3183
|
+
{ x: round6(cx), y: round6(childTop) }
|
|
3122
3184
|
] : [
|
|
3123
|
-
{ x:
|
|
3124
|
-
{ x:
|
|
3125
|
-
{ x:
|
|
3185
|
+
{ x: round6(junctionX), y: round6(junctionY) },
|
|
3186
|
+
{ x: round6(cx), y: round6(junctionY) },
|
|
3187
|
+
{ x: round6(cx), y: round6(childTop) }
|
|
3126
3188
|
];
|
|
3127
3189
|
elements.push({
|
|
3128
3190
|
edgeId: PED_RISER_ID_BASE + childId,
|
|
@@ -3150,7 +3212,7 @@ function computePedigreeLayout(input, opts = {}) {
|
|
|
3150
3212
|
}
|
|
3151
3213
|
const generations = [];
|
|
3152
3214
|
for (let row = 0; row < rowCount; row++) {
|
|
3153
|
-
generations.push({ roman: romanNumeral(row + 1), y:
|
|
3215
|
+
generations.push({ roman: romanNumeral(row + 1), y: round6(glyphCyOfRow(row)) });
|
|
3154
3216
|
}
|
|
3155
3217
|
const ZYGOSITY_ORDER = ["mz", "dz", "unknown"];
|
|
3156
3218
|
const twinZygositiesUsed = ZYGOSITY_ORDER.filter((z) => twinZygositiesUsedSet.has(z));
|
|
@@ -3174,15 +3236,15 @@ var EDGE_INK5 = "#71717a";
|
|
|
3174
3236
|
var GLYPH_ATTRS2 = `fill="transparent" stroke="${GLYPH_STROKE3}" stroke-width="2"`;
|
|
3175
3237
|
var CONSANG_GAP = 3;
|
|
3176
3238
|
var ZYGOSITIES = ["mz", "dz", "unknown"];
|
|
3177
|
-
var
|
|
3239
|
+
var round7 = (n) => Math.round(n * 100) / 100;
|
|
3178
3240
|
function glyphOutline(shape, cx, cy, half) {
|
|
3179
3241
|
if (shape === "square") {
|
|
3180
|
-
return `<rect x="${
|
|
3242
|
+
return `<rect x="${round7(cx - half)}" y="${round7(cy - half)}" width="${half * 2}" height="${half * 2}" ${GLYPH_ATTRS2}/>`;
|
|
3181
3243
|
}
|
|
3182
3244
|
if (shape === "circle") {
|
|
3183
3245
|
return `<circle cx="${cx}" cy="${cy}" r="${half}" ${GLYPH_ATTRS2}/>`;
|
|
3184
3246
|
}
|
|
3185
|
-
return `<polygon points="${cx},${
|
|
3247
|
+
return `<polygon points="${cx},${round7(cy - half)} ${round7(cx + half)},${cy} ${cx},${round7(cy + half)} ${round7(cx - half)},${cy}" ${GLYPH_ATTRS2}/>`;
|
|
3186
3248
|
}
|
|
3187
3249
|
function glyphVerticalExtentAt(shape, cy, half, dx) {
|
|
3188
3250
|
const ax = Math.min(Math.abs(dx), half);
|
|
@@ -3208,8 +3270,8 @@ function fillPartitions(n, half, inkByCondition) {
|
|
|
3208
3270
|
for (let s = 0; s <= SAMPLES; s++) {
|
|
3209
3271
|
const x = left + sliceW * s / SAMPLES;
|
|
3210
3272
|
const [yt, yb] = glyphVerticalExtentAt(n.shape, cy, half, x - cx);
|
|
3211
|
-
top.push(`${
|
|
3212
|
-
bottom.push(`${
|
|
3273
|
+
top.push(`${round7(x)},${round7(yt)}`);
|
|
3274
|
+
bottom.push(`${round7(x)},${round7(yb)}`);
|
|
3213
3275
|
}
|
|
3214
3276
|
const pts = [...top, ...bottom.reverse()].join(" ");
|
|
3215
3277
|
const ink = inkByCondition.get(id) ?? GLYPH_STROKE3;
|
|
@@ -3223,24 +3285,24 @@ function carrierDot(n) {
|
|
|
3223
3285
|
function deceasedSlash(n, half) {
|
|
3224
3286
|
if (!n.deceased) return "";
|
|
3225
3287
|
const ext = half + 4;
|
|
3226
|
-
return `<line x1="${
|
|
3288
|
+
return `<line x1="${round7(n.cx - ext)}" y1="${round7(n.cy - ext)}" x2="${round7(n.cx + ext)}" y2="${round7(n.cy + ext)}" stroke="${GLYPH_STROKE3}" stroke-width="2"/>`;
|
|
3227
3289
|
}
|
|
3228
3290
|
function probandArrow(n, half) {
|
|
3229
3291
|
if (n.role === null) return "";
|
|
3230
|
-
const tipX =
|
|
3231
|
-
const tipY =
|
|
3232
|
-
const tailX =
|
|
3233
|
-
const tailY =
|
|
3292
|
+
const tipX = round7(n.cx - half);
|
|
3293
|
+
const tipY = round7(n.cy + half);
|
|
3294
|
+
const tailX = round7(tipX - 16);
|
|
3295
|
+
const tailY = round7(tipY + 16);
|
|
3234
3296
|
const filled = n.role === "proband";
|
|
3235
3297
|
const fill = filled ? GLYPH_STROKE3 : "transparent";
|
|
3236
3298
|
const shaft = `<line x1="${tailX}" y1="${tailY}" x2="${tipX}" y2="${tipY}" stroke="${GLYPH_STROKE3}" stroke-width="2"/>`;
|
|
3237
|
-
const head = `<polygon points="${tipX},${tipY} ${
|
|
3299
|
+
const head = `<polygon points="${tipX},${tipY} ${round7(tipX - 8)},${round7(tipY + 2)} ${round7(tipX - 2)},${round7(tipY + 8)}" fill="${fill}" stroke="${GLYPH_STROKE3}" stroke-width="1.5"/>`;
|
|
3238
3300
|
return shaft + head;
|
|
3239
3301
|
}
|
|
3240
3302
|
function stillbirthMark(n, half) {
|
|
3241
3303
|
if (!n.stillbirth) return "";
|
|
3242
|
-
const y =
|
|
3243
|
-
return `<text x="${
|
|
3304
|
+
const y = round7(n.cy + half + PED_ADDRESS_FONT);
|
|
3305
|
+
return `<text x="${round7(n.cx - half - 2)}" y="${y}" text-anchor="end" font-family="${FONT_FAMILY}" font-size="${PED_ADDRESS_FONT}" font-weight="bold" fill="${LABEL_FILL4}">SB</text>`;
|
|
3244
3306
|
}
|
|
3245
3307
|
function nodeSvg2(n, inkByCondition) {
|
|
3246
3308
|
const half = n.size / 2;
|
|
@@ -3254,13 +3316,13 @@ function nodeSvg2(n, inkByCondition) {
|
|
|
3254
3316
|
probandArrow(n, half),
|
|
3255
3317
|
stillbirthMark(n, half)
|
|
3256
3318
|
];
|
|
3257
|
-
const addressY =
|
|
3319
|
+
const addressY = round7(n.labelTop + PED_ADDRESS_FONT);
|
|
3258
3320
|
pieces.push(
|
|
3259
3321
|
`<text x="${n.cx}" y="${addressY}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${PED_ADDRESS_FONT}" fill="${LABEL_FILL4}">${xmlEscape(n.addressLabel)}</text>`
|
|
3260
3322
|
);
|
|
3261
3323
|
if (n.labelLines.length > 0) {
|
|
3262
|
-
const firstBaseline =
|
|
3263
|
-
const tspans = n.labelLines.map((line, i) => `<tspan x="${n.cx}" y="${
|
|
3324
|
+
const firstBaseline = round7(n.labelTop + PED_LABEL_LINE_H + 10);
|
|
3325
|
+
const tspans = n.labelLines.map((line, i) => `<tspan x="${n.cx}" y="${round7(firstBaseline + i * PED_LABEL_LINE_H)}">${xmlEscape(line)}</tspan>`).join("");
|
|
3264
3326
|
pieces.push(
|
|
3265
3327
|
`<text text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${PED_LABEL_FONT}" fill="${LABEL_FILL4}">${tspans}</text>`
|
|
3266
3328
|
);
|
|
@@ -3275,7 +3337,7 @@ function elementSvg3(el) {
|
|
|
3275
3337
|
const title = `<title>${xmlEscape(el.title)}</title>`;
|
|
3276
3338
|
const stroke = `stroke="${EDGE_INK5}" stroke-width="1.5" stroke-opacity="0.75"`;
|
|
3277
3339
|
const draw = (offsetY) => {
|
|
3278
|
-
const shifted = pts.map((p) => ({ x: p.x, y:
|
|
3340
|
+
const shifted = pts.map((p) => ({ x: p.x, y: round7(p.y + offsetY) }));
|
|
3279
3341
|
if (shifted.length === 2) {
|
|
3280
3342
|
return `<line x1="${shifted[0].x}" y1="${shifted[0].y}" x2="${shifted[1].x}" y2="${shifted[1].y}" ${stroke}/>`;
|
|
3281
3343
|
}
|
|
@@ -3286,51 +3348,51 @@ function elementSvg3(el) {
|
|
|
3286
3348
|
}
|
|
3287
3349
|
function unknownTwinMarks(layout) {
|
|
3288
3350
|
return layout.unknownTwinJunctions.map(
|
|
3289
|
-
(p) => `<text x="${
|
|
3351
|
+
(p) => `<text x="${round7(p.x + 6)}" y="${round7(p.y + 4)}" font-family="${FONT_FAMILY}" font-size="12" font-weight="bold" fill="${LABEL_FILL4}">?</text>`
|
|
3290
3352
|
).join("");
|
|
3291
3353
|
}
|
|
3292
3354
|
var MINI_ATTRS2 = `fill="transparent" stroke="${GLYPH_STROKE3}" stroke-width="1.5"`;
|
|
3293
3355
|
function miniShapeSwatch(shape, x, y) {
|
|
3294
|
-
const cx =
|
|
3295
|
-
if (shape === "square") return `<rect x="${
|
|
3356
|
+
const cx = round7(x + LEGEND_SWATCH_W / 2);
|
|
3357
|
+
if (shape === "square") return `<rect x="${round7(cx - 6)}" y="${round7(y - 6)}" width="12" height="12" ${MINI_ATTRS2}/>`;
|
|
3296
3358
|
if (shape === "circle") return `<circle cx="${cx}" cy="${y}" r="6" ${MINI_ATTRS2}/>`;
|
|
3297
|
-
return `<polygon points="${cx},${
|
|
3359
|
+
return `<polygon points="${cx},${round7(y - 7)} ${round7(cx + 7)},${y} ${cx},${round7(y + 7)} ${round7(cx - 7)},${y}" ${MINI_ATTRS2}/>`;
|
|
3298
3360
|
}
|
|
3299
3361
|
function miniSwatchCircle(filled, ink, x, y) {
|
|
3300
|
-
const cx =
|
|
3362
|
+
const cx = round7(x + LEGEND_SWATCH_W / 2);
|
|
3301
3363
|
return `<circle cx="${cx}" cy="${y}" r="6" fill="${filled ? ink : "transparent"}" stroke="${GLYPH_STROKE3}" stroke-width="1.5"/>`;
|
|
3302
3364
|
}
|
|
3303
3365
|
function miniCarrierSwatch(x, y) {
|
|
3304
|
-
const cx =
|
|
3366
|
+
const cx = round7(x + LEGEND_SWATCH_W / 2);
|
|
3305
3367
|
return `<circle cx="${cx}" cy="${y}" r="6" ${MINI_ATTRS2}/><circle cx="${cx}" cy="${y}" r="2" fill="${GLYPH_STROKE3}" stroke="none"/>`;
|
|
3306
3368
|
}
|
|
3307
3369
|
function miniDeceasedSwatch(x, y) {
|
|
3308
|
-
const cx =
|
|
3309
|
-
return `<circle cx="${cx}" cy="${y}" r="6" ${MINI_ATTRS2}/><line x1="${
|
|
3370
|
+
const cx = round7(x + LEGEND_SWATCH_W / 2);
|
|
3371
|
+
return `<circle cx="${cx}" cy="${y}" r="6" ${MINI_ATTRS2}/><line x1="${round7(cx - 7)}" y1="${round7(y - 7)}" x2="${round7(cx + 7)}" y2="${round7(y + 7)}" stroke="${GLYPH_STROKE3}" stroke-width="1.5"/>`;
|
|
3310
3372
|
}
|
|
3311
3373
|
function miniArrowSwatch(filled, x, y) {
|
|
3312
|
-
const cx =
|
|
3313
|
-
const tipX =
|
|
3314
|
-
const tipY =
|
|
3315
|
-
return `<line x1="${
|
|
3374
|
+
const cx = round7(x + LEGEND_SWATCH_W / 2);
|
|
3375
|
+
const tipX = round7(cx - 2);
|
|
3376
|
+
const tipY = round7(y + 2);
|
|
3377
|
+
return `<line x1="${round7(tipX - 8)}" y1="${round7(tipY + 8)}" x2="${tipX}" y2="${tipY}" stroke="${GLYPH_STROKE3}" stroke-width="1.5"/><polygon points="${tipX},${tipY} ${round7(tipX - 5)},${round7(tipY + 1)} ${round7(tipX - 1)},${round7(tipY + 5)}" fill="${filled ? GLYPH_STROKE3 : "transparent"}" stroke="${GLYPH_STROKE3}" stroke-width="1"/>`;
|
|
3316
3378
|
}
|
|
3317
3379
|
function miniConsanguineousSwatch(x, y) {
|
|
3318
|
-
const x1 =
|
|
3319
|
-
const x2 =
|
|
3320
|
-
return `<line x1="${x1}" y1="${
|
|
3380
|
+
const x1 = round7(x + 2);
|
|
3381
|
+
const x2 = round7(x + LEGEND_SWATCH_W - 2);
|
|
3382
|
+
return `<line x1="${x1}" y1="${round7(y - 1.5)}" x2="${x2}" y2="${round7(y - 1.5)}" stroke="${EDGE_INK5}" stroke-width="1.5"/><line x1="${x1}" y1="${round7(y + 1.5)}" x2="${x2}" y2="${round7(y + 1.5)}" stroke="${EDGE_INK5}" stroke-width="1.5"/>`;
|
|
3321
3383
|
}
|
|
3322
3384
|
function miniTwinSwatch(zygosity, x, y) {
|
|
3323
|
-
const cx =
|
|
3324
|
-
const apexY =
|
|
3325
|
-
const baseY =
|
|
3326
|
-
const left =
|
|
3327
|
-
const right =
|
|
3385
|
+
const cx = round7(x + LEGEND_SWATCH_W / 2);
|
|
3386
|
+
const apexY = round7(y - 6);
|
|
3387
|
+
const baseY = round7(y + 6);
|
|
3388
|
+
const left = round7(cx - 6);
|
|
3389
|
+
const right = round7(cx + 6);
|
|
3328
3390
|
const stub = `<line x1="${cx}" y1="${apexY}" x2="${cx}" y2="${y}" stroke="${EDGE_INK5}" stroke-width="1.5"/><line x1="${left}" y1="${y}" x2="${right}" y2="${y}" stroke="${EDGE_INK5}" stroke-width="1.5"/><line x1="${left}" y1="${y}" x2="${left}" y2="${baseY}" stroke="${EDGE_INK5}" stroke-width="1.5"/><line x1="${right}" y1="${y}" x2="${right}" y2="${baseY}" stroke="${EDGE_INK5}" stroke-width="1.5"/>`;
|
|
3329
3391
|
if (zygosity === "mz") {
|
|
3330
|
-
return stub + `<line x1="${left}" y1="${
|
|
3392
|
+
return stub + `<line x1="${left}" y1="${round7(y + 3)}" x2="${right}" y2="${round7(y + 3)}" stroke="${EDGE_INK5}" stroke-width="1.5"/>`;
|
|
3331
3393
|
}
|
|
3332
3394
|
if (zygosity === "unknown") {
|
|
3333
|
-
return stub + `<text x="${
|
|
3395
|
+
return stub + `<text x="${round7(cx + 8)}" y="${round7(y + 3)}" font-family="${FONT_FAMILY}" font-size="9" fill="${LABEL_FILL4}">?</text>`;
|
|
3334
3396
|
}
|
|
3335
3397
|
return stub;
|
|
3336
3398
|
}
|
|
@@ -3343,7 +3405,7 @@ function pedigreeLayoutSvg(layout, opts = {}) {
|
|
|
3343
3405
|
for (const n of layout.nodes) parts.push(nodeSvg2(n, inkByCondition));
|
|
3344
3406
|
for (const g of layout.generations) {
|
|
3345
3407
|
parts.push(
|
|
3346
|
-
`<text x="${
|
|
3408
|
+
`<text x="${round7(16)}" y="${round7(g.y + PED_LABEL_FONT * 0.32)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${PED_LABEL_FONT}" font-weight="bold" fill="${LABEL_FILL4}">${xmlEscape(g.roman)}</text>`
|
|
3347
3409
|
);
|
|
3348
3410
|
}
|
|
3349
3411
|
let width = layout.width;
|
|
@@ -3509,13 +3571,13 @@ function phyloIssues(input) {
|
|
|
3509
3571
|
push("tip-with-children", `node ${n.id} is declared internal but has no children`);
|
|
3510
3572
|
}
|
|
3511
3573
|
}
|
|
3512
|
-
const
|
|
3574
|
+
const GRAPH_BLOCKING2 = /* @__PURE__ */ new Set([
|
|
3513
3575
|
"duplicate-id",
|
|
3514
3576
|
"unknown-endpoint",
|
|
3515
3577
|
"unknown-root",
|
|
3516
3578
|
"multiple-parents"
|
|
3517
3579
|
]);
|
|
3518
|
-
if (!issues.some((i) =>
|
|
3580
|
+
if (!issues.some((i) => GRAPH_BLOCKING2.has(i.code))) {
|
|
3519
3581
|
const childrenOf = /* @__PURE__ */ new Map();
|
|
3520
3582
|
for (const e of [...input.edges].sort((a, b) => a.id - b.id)) {
|
|
3521
3583
|
const arr = childrenOf.get(e.parentId) ?? [];
|
|
@@ -3593,7 +3655,7 @@ var PHYLO_BRANCH_ID_BASE = 2e6;
|
|
|
3593
3655
|
var PHYLO_EXTENSION_ID_BASE = 3e6;
|
|
3594
3656
|
var PHYLO_ROOTSTUB_ID_BASE = 4e6;
|
|
3595
3657
|
var PHYLO_SCALEBAR_ID = 5e6;
|
|
3596
|
-
var
|
|
3658
|
+
var round8 = (n) => Math.round(n * 100) / 100;
|
|
3597
3659
|
function niceScaleStep(maxLen) {
|
|
3598
3660
|
if (!(maxLen > 0)) return 0;
|
|
3599
3661
|
const target = maxLen / 4;
|
|
@@ -3719,8 +3781,8 @@ function computePhyloLayout(input, opts = {}) {
|
|
|
3719
3781
|
edgeId: PHYLO_ROOTSTUB_ID_BASE + root.node.id,
|
|
3720
3782
|
kind: "root-stub",
|
|
3721
3783
|
points: [
|
|
3722
|
-
{ x:
|
|
3723
|
-
{ x:
|
|
3784
|
+
{ x: round8(Math.max(PADDING6 / 2, rootDrawX - ROOT_STUB)), y: round8(root.cy) },
|
|
3785
|
+
{ x: round8(rootDrawX), y: round8(root.cy) }
|
|
3724
3786
|
],
|
|
3725
3787
|
dotted: false,
|
|
3726
3788
|
length: null,
|
|
@@ -3735,11 +3797,11 @@ function computePhyloLayout(input, opts = {}) {
|
|
|
3735
3797
|
nodes.push({
|
|
3736
3798
|
nodeId: w.node.id,
|
|
3737
3799
|
isTip: tip,
|
|
3738
|
-
cx:
|
|
3739
|
-
cy:
|
|
3800
|
+
cx: round8(drawX),
|
|
3801
|
+
cy: round8(w.cy),
|
|
3740
3802
|
support,
|
|
3741
3803
|
labelLines: labelLine,
|
|
3742
|
-
labelLeft:
|
|
3804
|
+
labelLeft: round8(drawX + PHYLO_TIP_R + PHYLO_LABEL_GAP),
|
|
3743
3805
|
depth: w.depth,
|
|
3744
3806
|
title: nodeTitle(w.node, tip, isRoot, w.inLength, titleLabels)
|
|
3745
3807
|
});
|
|
@@ -3748,8 +3810,8 @@ function computePhyloLayout(input, opts = {}) {
|
|
|
3748
3810
|
edgeId: PHYLO_EXTENSION_ID_BASE + w.node.id,
|
|
3749
3811
|
kind: "extension",
|
|
3750
3812
|
points: [
|
|
3751
|
-
{ x:
|
|
3752
|
-
{ x:
|
|
3813
|
+
{ x: round8(w.cx), y: round8(w.cy) },
|
|
3814
|
+
{ x: round8(alignX), y: round8(w.cy) }
|
|
3753
3815
|
],
|
|
3754
3816
|
dotted: true,
|
|
3755
3817
|
length: null,
|
|
@@ -3765,8 +3827,8 @@ function computePhyloLayout(input, opts = {}) {
|
|
|
3765
3827
|
edgeId: PHYLO_CLADEBAR_ID_BASE + w.node.id,
|
|
3766
3828
|
kind: "clade-bar",
|
|
3767
3829
|
points: [
|
|
3768
|
-
{ x:
|
|
3769
|
-
{ x:
|
|
3830
|
+
{ x: round8(w.cx), y: round8(barTop) },
|
|
3831
|
+
{ x: round8(w.cx), y: round8(barBottom) }
|
|
3770
3832
|
],
|
|
3771
3833
|
dotted: false,
|
|
3772
3834
|
length: null,
|
|
@@ -3778,8 +3840,8 @@ function computePhyloLayout(input, opts = {}) {
|
|
|
3778
3840
|
edgeId: PHYLO_BRANCH_ID_BASE + c.node.id,
|
|
3779
3841
|
kind: "branch",
|
|
3780
3842
|
points: [
|
|
3781
|
-
{ x:
|
|
3782
|
-
{ x:
|
|
3843
|
+
{ x: round8(w.cx), y: round8(c.cy) },
|
|
3844
|
+
{ x: round8(c.cx), y: round8(c.cy) }
|
|
3783
3845
|
],
|
|
3784
3846
|
dotted: false,
|
|
3785
3847
|
length: c.inLength,
|
|
@@ -3796,17 +3858,17 @@ function computePhyloLayout(input, opts = {}) {
|
|
|
3796
3858
|
const barY = treeBottom + SCALEBAR_ZONE / 2;
|
|
3797
3859
|
scaleBar = {
|
|
3798
3860
|
length: stepLen,
|
|
3799
|
-
x:
|
|
3800
|
-
y:
|
|
3801
|
-
pxLength:
|
|
3861
|
+
x: round8(barX),
|
|
3862
|
+
y: round8(barY),
|
|
3863
|
+
pxLength: round8(scaleBarPx),
|
|
3802
3864
|
valueLabel: trimNumber(stepLen)
|
|
3803
3865
|
};
|
|
3804
3866
|
elements.push({
|
|
3805
3867
|
edgeId: PHYLO_SCALEBAR_ID,
|
|
3806
3868
|
kind: "scale-bar",
|
|
3807
3869
|
points: [
|
|
3808
|
-
{ x:
|
|
3809
|
-
{ x:
|
|
3870
|
+
{ x: round8(barX), y: round8(barY) },
|
|
3871
|
+
{ x: round8(barX + scaleBarPx), y: round8(barY) }
|
|
3810
3872
|
],
|
|
3811
3873
|
dotted: false,
|
|
3812
3874
|
length: stepLen,
|
|
@@ -3820,18 +3882,18 @@ function computePhyloLayout(input, opts = {}) {
|
|
|
3820
3882
|
var GLYPH_STROKE4 = "#52525b";
|
|
3821
3883
|
var LABEL_FILL5 = "#3f3f46";
|
|
3822
3884
|
var EDGE_INK6 = "#71717a";
|
|
3823
|
-
var
|
|
3885
|
+
var round9 = (n) => Math.round(n * 100) / 100;
|
|
3824
3886
|
function nodeSvg3(n, showSupport) {
|
|
3825
3887
|
const pieces = [`<title>${xmlEscape(n.title)}</title>`];
|
|
3826
3888
|
pieces.push(`<circle cx="${n.cx}" cy="${n.cy}" r="2.5" fill="${GLYPH_STROKE4}"/>`);
|
|
3827
3889
|
if (n.labelLines.length > 0) {
|
|
3828
3890
|
pieces.push(
|
|
3829
|
-
`<text x="${n.labelLeft}" y="${
|
|
3891
|
+
`<text x="${n.labelLeft}" y="${round9(n.cy + PHYLO_LABEL_FONT * 0.32)}" font-family="${FONT_FAMILY}" font-size="${PHYLO_LABEL_FONT}" fill="${LABEL_FILL5}">${xmlEscape(n.labelLines[0])}</text>`
|
|
3830
3892
|
);
|
|
3831
3893
|
}
|
|
3832
3894
|
if (showSupport && !n.isTip && n.support !== null) {
|
|
3833
3895
|
pieces.push(
|
|
3834
|
-
`<text x="${
|
|
3896
|
+
`<text x="${round9(n.cx - 4)}" y="${round9(n.cy - 3)}" text-anchor="end" font-family="${FONT_FAMILY}" font-size="${PHYLO_SUPPORT_FONT}" fill="${LABEL_FILL5}">${xmlEscape(String(n.support))}</text>`
|
|
3835
3897
|
);
|
|
3836
3898
|
}
|
|
3837
3899
|
return `<g data-node-id="n${n.nodeId}">${pieces.join("")}</g>`;
|
|
@@ -3846,18 +3908,18 @@ function elementSvg4(el) {
|
|
|
3846
3908
|
function scaleBarLabelSvg(layout) {
|
|
3847
3909
|
const bar = layout.scaleBar;
|
|
3848
3910
|
if (bar === null) return "";
|
|
3849
|
-
const tick = (x) => `<line x1="${x}" y1="${
|
|
3850
|
-
return tick(bar.x) + tick(
|
|
3911
|
+
const tick = (x) => `<line x1="${x}" y1="${round9(bar.y - 3)}" x2="${x}" y2="${round9(bar.y + 3)}" stroke="${EDGE_INK6}" stroke-width="1.5" stroke-opacity="0.75"/>`;
|
|
3912
|
+
return tick(bar.x) + tick(round9(bar.x + bar.pxLength)) + `<text x="${round9(bar.x + bar.pxLength / 2)}" y="${round9(bar.y - 6)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${PHYLO_SUPPORT_FONT}" fill="${LABEL_FILL5}">${xmlEscape(bar.valueLabel)}</text>`;
|
|
3851
3913
|
}
|
|
3852
3914
|
function supportSwatch(x, y) {
|
|
3853
|
-
const cx =
|
|
3854
|
-
return `<circle cx="${cx}" cy="${y}" r="2.5" fill="${GLYPH_STROKE4}"/><text x="${
|
|
3915
|
+
const cx = round9(x + LEGEND_SWATCH_W / 2);
|
|
3916
|
+
return `<circle cx="${cx}" cy="${y}" r="2.5" fill="${GLYPH_STROKE4}"/><text x="${round9(cx + 5)}" y="${round9(y + PHYLO_SUPPORT_FONT * 0.32)}" font-family="${FONT_FAMILY}" font-size="${PHYLO_SUPPORT_FONT}" fill="${LABEL_FILL5}">95</text>`;
|
|
3855
3917
|
}
|
|
3856
3918
|
function scaleSwatch(x, y) {
|
|
3857
|
-
return `<line x1="${
|
|
3919
|
+
return `<line x1="${round9(x)}" y1="${y}" x2="${round9(x + LEGEND_SWATCH_W)}" y2="${y}" stroke="${EDGE_INK6}" stroke-width="1.5" stroke-opacity="0.75"/>`;
|
|
3858
3920
|
}
|
|
3859
3921
|
function alignedTipSwatch(x, y) {
|
|
3860
|
-
return `<line x1="${
|
|
3922
|
+
return `<line x1="${round9(x)}" y1="${y}" x2="${round9(x + LEGEND_SWATCH_W)}" y2="${y}" stroke="${EDGE_INK6}" stroke-width="1.5" stroke-opacity="0.5" stroke-dasharray="2,3"/>`;
|
|
3861
3923
|
}
|
|
3862
3924
|
function phyloLayoutSvg(layout, opts = {}) {
|
|
3863
3925
|
const labels = opts.labels ?? PHYLO_SVG_LABELS_EN;
|
|
@@ -3904,6 +3966,782 @@ function phyloSvg(input, opts = {}) {
|
|
|
3904
3966
|
return { svg, layout };
|
|
3905
3967
|
}
|
|
3906
3968
|
|
|
3969
|
+
// src/org-chart/types.ts
|
|
3970
|
+
var ORG_REPORT_KINDS = ["line", "assistant", "dotted"];
|
|
3971
|
+
var ORG_VACANCIES = ["filled", "vacant"];
|
|
3972
|
+
|
|
3973
|
+
// src/org-chart/labels.ts
|
|
3974
|
+
var ORG_CHART_TITLE_LABELS_EN = {
|
|
3975
|
+
reportKinds: { line: "Reports to", assistant: "Assistant to", dotted: "Dotted-line report to" },
|
|
3976
|
+
vacant: "(vacant)"
|
|
3977
|
+
};
|
|
3978
|
+
var ORG_CHART_SVG_LABELS_EN = {
|
|
3979
|
+
legend: { line: "Reports to", assistant: "Assistant", dotted: "Dotted-line / matrix", vacant: "Vacant position" },
|
|
3980
|
+
ariaLabel: "Organizational chart"
|
|
3981
|
+
};
|
|
3982
|
+
|
|
3983
|
+
// src/org-chart/validate.ts
|
|
3984
|
+
var ORG_MAX_MATRIX_EDGES_PER_NODE = 5;
|
|
3985
|
+
var OrgChartValidationError = class extends Error {
|
|
3986
|
+
issues;
|
|
3987
|
+
constructor(issues) {
|
|
3988
|
+
super(`invalid org chart: ${issues.map((i) => i.message).join("; ")}`);
|
|
3989
|
+
this.name = "OrgChartValidationError";
|
|
3990
|
+
this.issues = issues;
|
|
3991
|
+
}
|
|
3992
|
+
};
|
|
3993
|
+
function listIds2(ids) {
|
|
3994
|
+
if (ids.length === 1) return String(ids[0]);
|
|
3995
|
+
return `${ids.slice(0, -1).join(", ")} and ${ids[ids.length - 1]}`;
|
|
3996
|
+
}
|
|
3997
|
+
function sortIssues4(issues) {
|
|
3998
|
+
const unique = /* @__PURE__ */ new Map();
|
|
3999
|
+
for (const issue of issues) unique.set(`${issue.code} ${issue.message}`, issue);
|
|
4000
|
+
return [...unique.values()].sort(
|
|
4001
|
+
(a, b) => a.code !== b.code ? a.code < b.code ? -1 : 1 : a.message < b.message ? -1 : a.message > b.message ? 1 : 0
|
|
4002
|
+
);
|
|
4003
|
+
}
|
|
4004
|
+
var GRAPH_BLOCKING = /* @__PURE__ */ new Set([
|
|
4005
|
+
"duplicate-id",
|
|
4006
|
+
"unknown-manager",
|
|
4007
|
+
"unknown-report",
|
|
4008
|
+
"multiple-managers"
|
|
4009
|
+
]);
|
|
4010
|
+
function orgChartIssues(input) {
|
|
4011
|
+
if (input.positions.length === 0 && input.reports.length === 0) return [];
|
|
4012
|
+
const issues = [];
|
|
4013
|
+
const push = (code, message) => {
|
|
4014
|
+
issues.push({ code, message });
|
|
4015
|
+
};
|
|
4016
|
+
const positionById = /* @__PURE__ */ new Map();
|
|
4017
|
+
const dupPositionIds = /* @__PURE__ */ new Set();
|
|
4018
|
+
for (const p of input.positions) {
|
|
4019
|
+
if (positionById.has(p.id)) dupPositionIds.add(p.id);
|
|
4020
|
+
else positionById.set(p.id, p);
|
|
4021
|
+
}
|
|
4022
|
+
for (const id of [...dupPositionIds].sort((a, b) => a - b)) {
|
|
4023
|
+
push("duplicate-id", `duplicate position id ${id}`);
|
|
4024
|
+
}
|
|
4025
|
+
const reportById = /* @__PURE__ */ new Map();
|
|
4026
|
+
const dupReportIds = /* @__PURE__ */ new Set();
|
|
4027
|
+
for (const r of input.reports) {
|
|
4028
|
+
if (reportById.has(r.id)) dupReportIds.add(r.id);
|
|
4029
|
+
else reportById.set(r.id, r);
|
|
4030
|
+
}
|
|
4031
|
+
for (const id of [...dupReportIds].sort((a, b) => a - b)) {
|
|
4032
|
+
push("duplicate-id", `duplicate report id ${id}`);
|
|
4033
|
+
}
|
|
4034
|
+
if (issues.length > 0) {
|
|
4035
|
+
return sortIssues4(issues);
|
|
4036
|
+
}
|
|
4037
|
+
const reports = [...reportById.values()].sort((a, b) => a.id - b.id);
|
|
4038
|
+
const solidParents = /* @__PURE__ */ new Map();
|
|
4039
|
+
const assistantReportEdge = /* @__PURE__ */ new Map();
|
|
4040
|
+
const solidManagedEdges = /* @__PURE__ */ new Map();
|
|
4041
|
+
const dottedDegree = /* @__PURE__ */ new Map();
|
|
4042
|
+
for (const r of reports) {
|
|
4043
|
+
const hasManager = positionById.has(r.managerId);
|
|
4044
|
+
const hasReport = positionById.has(r.reportId);
|
|
4045
|
+
if (!hasManager) {
|
|
4046
|
+
push("unknown-manager", `report ${r.id} managerId ${r.managerId} is not a declared position`);
|
|
4047
|
+
}
|
|
4048
|
+
if (!hasReport) {
|
|
4049
|
+
push("unknown-report", `report ${r.id} reportId ${r.reportId} is not a declared position`);
|
|
4050
|
+
}
|
|
4051
|
+
if (r.managerId === r.reportId) {
|
|
4052
|
+
push("self-report", `report ${r.id} has managerId === reportId (${r.managerId})`);
|
|
4053
|
+
}
|
|
4054
|
+
if (r.kind !== "dotted") {
|
|
4055
|
+
const arr = solidParents.get(r.reportId) ?? [];
|
|
4056
|
+
arr.push(r.id);
|
|
4057
|
+
solidParents.set(r.reportId, arr);
|
|
4058
|
+
const managed = solidManagedEdges.get(r.managerId) ?? [];
|
|
4059
|
+
managed.push(r.id);
|
|
4060
|
+
solidManagedEdges.set(r.managerId, managed);
|
|
4061
|
+
}
|
|
4062
|
+
if (r.kind === "assistant" && r.managerId !== r.reportId && !assistantReportEdge.has(r.reportId)) {
|
|
4063
|
+
assistantReportEdge.set(r.reportId, r.id);
|
|
4064
|
+
}
|
|
4065
|
+
if (r.kind === "dotted" && r.managerId !== r.reportId) {
|
|
4066
|
+
if (positionById.has(r.managerId)) dottedDegree.set(r.managerId, (dottedDegree.get(r.managerId) ?? 0) + 1);
|
|
4067
|
+
if (positionById.has(r.reportId)) dottedDegree.set(r.reportId, (dottedDegree.get(r.reportId) ?? 0) + 1);
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
for (const [reportId, edgeIds] of [...solidParents.entries()].sort((a, b) => a[0] - b[0])) {
|
|
4071
|
+
if (edgeIds.length > 1) {
|
|
4072
|
+
push(
|
|
4073
|
+
"multiple-managers",
|
|
4074
|
+
`position ${reportId} is the report of solid edges ${listIds2(edgeIds.sort((a, b) => a - b))} \u2014 keep one solid line, make the rest kind "dotted"`
|
|
4075
|
+
);
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
for (const [positionId, assistEdgeId] of [...assistantReportEdge.entries()].sort((a, b) => a[0] - b[0])) {
|
|
4079
|
+
const managed = solidManagedEdges.get(positionId);
|
|
4080
|
+
if (managed !== void 0 && managed.length > 0) {
|
|
4081
|
+
const firstManaged = Math.min(...managed);
|
|
4082
|
+
push(
|
|
4083
|
+
"assistant-has-reports",
|
|
4084
|
+
`position ${positionId} is an assistant (report of edge ${assistEdgeId}) but manages solid edge ${firstManaged} \u2014 assistants are leaves`
|
|
4085
|
+
);
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
for (const [positionId, count] of [...dottedDegree.entries()].sort((a, b) => a[0] - b[0])) {
|
|
4089
|
+
if (count > ORG_MAX_MATRIX_EDGES_PER_NODE) {
|
|
4090
|
+
push(
|
|
4091
|
+
"too-many-matrix-edges",
|
|
4092
|
+
`position ${positionId} has ${count} dotted (matrix) connections \u2014 at most ${ORG_MAX_MATRIX_EDGES_PER_NODE} are supported per position`
|
|
4093
|
+
);
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
if (!issues.some((i) => GRAPH_BLOCKING.has(i.code))) {
|
|
4097
|
+
const solidChildren = /* @__PURE__ */ new Map();
|
|
4098
|
+
for (const r of reports) {
|
|
4099
|
+
if (r.kind === "dotted") continue;
|
|
4100
|
+
if (r.managerId === r.reportId) continue;
|
|
4101
|
+
const arr = solidChildren.get(r.managerId) ?? [];
|
|
4102
|
+
arr.push(r.reportId);
|
|
4103
|
+
solidChildren.set(r.managerId, arr);
|
|
4104
|
+
}
|
|
4105
|
+
for (const arr of solidChildren.values()) arr.sort((a, b) => a - b);
|
|
4106
|
+
const color = /* @__PURE__ */ new Map();
|
|
4107
|
+
const seenCycles = /* @__PURE__ */ new Set();
|
|
4108
|
+
const dfs = (start) => {
|
|
4109
|
+
const stack = [{ id: start, nextChild: 0 }];
|
|
4110
|
+
color.set(start, 1);
|
|
4111
|
+
while (stack.length > 0) {
|
|
4112
|
+
const frame = stack[stack.length - 1];
|
|
4113
|
+
const children = solidChildren.get(frame.id) ?? [];
|
|
4114
|
+
if (frame.nextChild >= children.length) {
|
|
4115
|
+
color.set(frame.id, 2);
|
|
4116
|
+
stack.pop();
|
|
4117
|
+
continue;
|
|
4118
|
+
}
|
|
4119
|
+
const child = children[frame.nextChild];
|
|
4120
|
+
frame.nextChild += 1;
|
|
4121
|
+
const c = color.get(child) ?? 0;
|
|
4122
|
+
if (c === 1) {
|
|
4123
|
+
const from = stack.findIndex((f) => f.id === child);
|
|
4124
|
+
const cycle = stack.slice(from).map((f) => f.id);
|
|
4125
|
+
const minIdx = cycle.indexOf(Math.min(...cycle));
|
|
4126
|
+
const rotated = [...cycle.slice(minIdx), ...cycle.slice(0, minIdx)];
|
|
4127
|
+
const key = rotated.join(">");
|
|
4128
|
+
if (!seenCycles.has(key)) {
|
|
4129
|
+
seenCycles.add(key);
|
|
4130
|
+
push("cycle", `cycle: ${[...rotated, rotated[0]].join(" \u2192 ")}`);
|
|
4131
|
+
}
|
|
4132
|
+
} else if (c === 0) {
|
|
4133
|
+
color.set(child, 1);
|
|
4134
|
+
stack.push({ id: child, nextChild: 0 });
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
};
|
|
4138
|
+
for (const p of [...positionById.values()].sort((a, b) => a.id - b.id)) {
|
|
4139
|
+
if ((color.get(p.id) ?? 0) === 0) dfs(p.id);
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
return sortIssues4(issues);
|
|
4143
|
+
}
|
|
4144
|
+
function validateOrgChart(input) {
|
|
4145
|
+
const issues = orgChartIssues(input);
|
|
4146
|
+
if (issues.length > 0) throw new OrgChartValidationError(issues);
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
// src/org-chart/layout.ts
|
|
4150
|
+
var ORG_LABEL_FONT = 12;
|
|
4151
|
+
var ORG_TITLE_FONT = 10;
|
|
4152
|
+
var ORG_LABEL_LINE_H = 14;
|
|
4153
|
+
var PADDING7 = 32;
|
|
4154
|
+
var ORG_SIBLING_GAP = 28;
|
|
4155
|
+
var ORG_TREE_GAP = 56;
|
|
4156
|
+
var ORG_CORRIDOR = 30;
|
|
4157
|
+
var ORG_MIN_BOX_W = 120;
|
|
4158
|
+
var ORG_BOX_PAD_X = 12;
|
|
4159
|
+
var ORG_BOX_PAD_Y = 9;
|
|
4160
|
+
var ORG_ASSIST_GAP = 24;
|
|
4161
|
+
var ORG_ASSIST_BAND_DROP = 14;
|
|
4162
|
+
var ORG_MATRIX_GUTTER_GAP = 28;
|
|
4163
|
+
var ORG_MATRIX_LANE_PITCH = 14;
|
|
4164
|
+
var ORG_MATRIX_ESCAPE_STEP = 2;
|
|
4165
|
+
var ORG_STEM_ID_BASE = 1e6;
|
|
4166
|
+
var ORG_BUS_ID_BASE = 2e6;
|
|
4167
|
+
var ORG_DROP_ID_BASE = 3e6;
|
|
4168
|
+
var ORG_ASSIST_ID_BASE = 4e6;
|
|
4169
|
+
var ORG_DOTTED_ID_BASE = 5e6;
|
|
4170
|
+
var round10 = (n) => Math.round(n * 100) / 100;
|
|
4171
|
+
var drawnLeftEdge = (cx, boxW) => round10(round10(cx) - round10(boxW) / 2);
|
|
4172
|
+
var drawnRightEdge = (cx, boxW) => drawnLeftEdge(cx, boxW) + round10(boxW);
|
|
4173
|
+
function packSubtree(node, gaps) {
|
|
4174
|
+
const { ownHalfL, ownHalfR, children } = node;
|
|
4175
|
+
if (children.length === 0) {
|
|
4176
|
+
return { halfL: ownHalfL, halfR: ownHalfR, offsets: [] };
|
|
4177
|
+
}
|
|
4178
|
+
const xs = [0];
|
|
4179
|
+
for (let i = 1; i < children.length; i++) {
|
|
4180
|
+
xs.push(xs[i - 1] + children[i - 1].halfR + gaps.siblingGap + children[i].halfL);
|
|
4181
|
+
}
|
|
4182
|
+
const first = children[0];
|
|
4183
|
+
const last = children[children.length - 1];
|
|
4184
|
+
const axis = (xs[0] + xs[xs.length - 1]) / 2;
|
|
4185
|
+
const offsets = xs.map((x) => x - axis);
|
|
4186
|
+
const halfL = Math.max(ownHalfL, axis - (xs[0] - first.halfL));
|
|
4187
|
+
const halfR = Math.max(ownHalfR, xs[xs.length - 1] + last.halfR - axis);
|
|
4188
|
+
return { halfL, halfR, offsets };
|
|
4189
|
+
}
|
|
4190
|
+
function measurePosition(position, maxLabelChars, vacantWord) {
|
|
4191
|
+
const nameLines = position.name === "" ? [] : wrapLabelBalanced(clampLabel(position.name, maxLabelChars), 2);
|
|
4192
|
+
const titleLines = position.title === null ? [] : wrapLabelBalanced(clampLabel(position.title, maxLabelChars), 2);
|
|
4193
|
+
const subtitleLines = position.subtitle === null ? [] : wrapLabelBalanced(clampLabel(position.subtitle, maxLabelChars), 1);
|
|
4194
|
+
const vacantMarker = position.vacancy === "vacant" ? vacantWord : null;
|
|
4195
|
+
let innerW = 0;
|
|
4196
|
+
for (const l of nameLines) innerW = Math.max(innerW, estimateTextWidth(l, ORG_LABEL_FONT));
|
|
4197
|
+
for (const l of titleLines) innerW = Math.max(innerW, estimateTextWidth(l, ORG_TITLE_FONT));
|
|
4198
|
+
for (const l of subtitleLines) innerW = Math.max(innerW, estimateTextWidth(l, ORG_TITLE_FONT));
|
|
4199
|
+
if (vacantMarker !== null) innerW = Math.max(innerW, estimateTextWidth(vacantMarker, ORG_TITLE_FONT));
|
|
4200
|
+
const boxW = Math.max(ORG_MIN_BOX_W, innerW + ORG_BOX_PAD_X * 2);
|
|
4201
|
+
const lineCount = nameLines.length + titleLines.length + subtitleLines.length + (vacantMarker !== null ? 1 : 0);
|
|
4202
|
+
const boxH = ORG_BOX_PAD_Y * 2 + lineCount * ORG_LABEL_LINE_H;
|
|
4203
|
+
return { boxW, boxH, nameLines, titleLines, subtitleLines, vacantMarker };
|
|
4204
|
+
}
|
|
4205
|
+
function positionTitle(position, vacantWord) {
|
|
4206
|
+
if (position.hover !== void 0) return position.hover;
|
|
4207
|
+
if (position.title !== null) {
|
|
4208
|
+
return position.name === "" ? position.title : `${position.name} \u2014 ${position.title}`;
|
|
4209
|
+
}
|
|
4210
|
+
if (position.name !== "") return position.name;
|
|
4211
|
+
return vacantWord;
|
|
4212
|
+
}
|
|
4213
|
+
function computeOrgChartLayout(input, opts = {}) {
|
|
4214
|
+
if (input.positions.length === 0 && input.reports.length === 0) {
|
|
4215
|
+
return { width: PADDING7 * 2, height: PADDING7 * 2, nodes: [], elements: [], rootPositionIds: [] };
|
|
4216
|
+
}
|
|
4217
|
+
validateOrgChart(input);
|
|
4218
|
+
const titleLabels = opts.titleLabels ?? ORG_CHART_TITLE_LABELS_EN;
|
|
4219
|
+
const vacantWord = titleLabels.vacant;
|
|
4220
|
+
const positionById = new Map(input.positions.map((p) => [p.id, p]));
|
|
4221
|
+
const reports = [...input.reports].sort((a, b) => a.id - b.id);
|
|
4222
|
+
const solidReports = reports.filter((r) => r.kind !== "dotted");
|
|
4223
|
+
const lineChildIds = /* @__PURE__ */ new Map();
|
|
4224
|
+
const assistChildIds = /* @__PURE__ */ new Map();
|
|
4225
|
+
const solidParentReportId = /* @__PURE__ */ new Set();
|
|
4226
|
+
for (const r of solidReports) {
|
|
4227
|
+
solidParentReportId.add(r.reportId);
|
|
4228
|
+
const bucket = r.kind === "assistant" ? assistChildIds : lineChildIds;
|
|
4229
|
+
const arr = bucket.get(r.managerId) ?? [];
|
|
4230
|
+
arr.push(r);
|
|
4231
|
+
bucket.set(r.managerId, arr);
|
|
4232
|
+
}
|
|
4233
|
+
for (const arr of lineChildIds.values()) arr.sort((a, b) => a.reportId - b.reportId);
|
|
4234
|
+
for (const arr of assistChildIds.values()) arr.sort((a, b) => a.reportId - b.reportId);
|
|
4235
|
+
const rootPositionIds = input.positions.map((p) => p.id).filter((id) => !solidParentReportId.has(id)).sort((a, b) => a - b);
|
|
4236
|
+
const newInst = (position, depth, assistReport) => {
|
|
4237
|
+
const m = measurePosition(position, opts.maxLabelChars, vacantWord);
|
|
4238
|
+
return {
|
|
4239
|
+
position,
|
|
4240
|
+
m,
|
|
4241
|
+
style: position.vacancy === "vacant" ? "dashed" : "solid",
|
|
4242
|
+
title: positionTitle(position, vacantWord),
|
|
4243
|
+
depth,
|
|
4244
|
+
lineChildren: [],
|
|
4245
|
+
assistants: [],
|
|
4246
|
+
ownHalfL: m.boxW / 2,
|
|
4247
|
+
ownHalfR: m.boxW / 2,
|
|
4248
|
+
spanL: 0,
|
|
4249
|
+
spanR: 0,
|
|
4250
|
+
offsets: [],
|
|
4251
|
+
cx: 0,
|
|
4252
|
+
assistReport,
|
|
4253
|
+
assistTop: 0
|
|
4254
|
+
};
|
|
4255
|
+
};
|
|
4256
|
+
const allInsts = [];
|
|
4257
|
+
const build = (positionId, depth, assistReport) => {
|
|
4258
|
+
const position = positionById.get(positionId);
|
|
4259
|
+
const inst = newInst(position, depth, assistReport);
|
|
4260
|
+
allInsts.push(inst);
|
|
4261
|
+
for (const r of assistChildIds.get(positionId) ?? []) {
|
|
4262
|
+
const a = build(r.reportId, depth + 1, r);
|
|
4263
|
+
inst.assistants.push({ inst: a, report: r });
|
|
4264
|
+
}
|
|
4265
|
+
for (const r of lineChildIds.get(positionId) ?? []) {
|
|
4266
|
+
inst.lineChildren.push(build(r.reportId, depth + 1, null));
|
|
4267
|
+
}
|
|
4268
|
+
return inst;
|
|
4269
|
+
};
|
|
4270
|
+
const roots = rootPositionIds.map((id) => build(id, 0, null));
|
|
4271
|
+
for (const inst of allInsts) {
|
|
4272
|
+
if (inst.assistants.length > 0) {
|
|
4273
|
+
const widest = inst.assistants.reduce((m, a) => Math.max(m, a.inst.m.boxW), 0);
|
|
4274
|
+
inst.ownHalfL = inst.m.boxW / 2 + ORG_ASSIST_GAP + widest;
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
const gaps = { siblingGap: ORG_SIBLING_GAP };
|
|
4278
|
+
const pack = (inst) => {
|
|
4279
|
+
for (const c of inst.lineChildren) pack(c);
|
|
4280
|
+
const result = packSubtree(
|
|
4281
|
+
{
|
|
4282
|
+
ownHalfL: inst.ownHalfL,
|
|
4283
|
+
ownHalfR: inst.ownHalfR,
|
|
4284
|
+
children: inst.lineChildren.map((c) => ({ halfL: c.spanL, halfR: c.spanR }))
|
|
4285
|
+
},
|
|
4286
|
+
gaps
|
|
4287
|
+
);
|
|
4288
|
+
inst.spanL = result.halfL;
|
|
4289
|
+
inst.spanR = result.halfR;
|
|
4290
|
+
inst.offsets = result.offsets;
|
|
4291
|
+
};
|
|
4292
|
+
for (const root of roots) pack(root);
|
|
4293
|
+
let cursor = PADDING7;
|
|
4294
|
+
for (const root of roots) {
|
|
4295
|
+
root.cx = cursor + root.spanL;
|
|
4296
|
+
const placeX = (inst) => {
|
|
4297
|
+
inst.lineChildren.forEach((c, i) => {
|
|
4298
|
+
c.cx = inst.cx + inst.offsets[i];
|
|
4299
|
+
placeX(c);
|
|
4300
|
+
});
|
|
4301
|
+
};
|
|
4302
|
+
placeX(root);
|
|
4303
|
+
cursor = root.cx + root.spanR + ORG_TREE_GAP;
|
|
4304
|
+
}
|
|
4305
|
+
const canvasRight = roots.length === 0 ? PADDING7 : cursor - ORG_TREE_GAP;
|
|
4306
|
+
const rowInsts = [];
|
|
4307
|
+
for (const inst of allInsts) (rowInsts[inst.depth] ??= []).push(inst);
|
|
4308
|
+
const depthCount = rowInsts.length;
|
|
4309
|
+
const assistStackH = (inst) => {
|
|
4310
|
+
if (inst.assistants.length === 0) return 0;
|
|
4311
|
+
return inst.assistants.reduce((s, a) => s + a.inst.m.boxH + ORG_ASSIST_BAND_DROP, 0) + ORG_ASSIST_BAND_DROP;
|
|
4312
|
+
};
|
|
4313
|
+
const rowH = [];
|
|
4314
|
+
for (let d = 0; d < depthCount; d++) {
|
|
4315
|
+
const lineBoxes = (rowInsts[d] ?? []).filter((i) => i.assistReport === null);
|
|
4316
|
+
rowH.push(lineBoxes.reduce((m, i) => Math.max(m, i.m.boxH), 0));
|
|
4317
|
+
}
|
|
4318
|
+
const assistZone = [];
|
|
4319
|
+
for (let d = 0; d < depthCount; d++) {
|
|
4320
|
+
let tallest = 0;
|
|
4321
|
+
for (const inst of rowInsts[d] ?? []) tallest = Math.max(tallest, assistStackH(inst));
|
|
4322
|
+
assistZone.push(tallest);
|
|
4323
|
+
}
|
|
4324
|
+
const rowTop = [PADDING7];
|
|
4325
|
+
for (let d = 0; d < depthCount - 1; d++) {
|
|
4326
|
+
rowTop.push(rowTop[d] + rowH[d] + assistZone[d] + ORG_CORRIDOR);
|
|
4327
|
+
}
|
|
4328
|
+
const busY = (d) => rowTop[d + 1] - ORG_CORRIDOR / 2;
|
|
4329
|
+
const last = depthCount - 1;
|
|
4330
|
+
const height = Math.ceil(rowTop[last] + rowH[last] + assistZone[last] + PADDING7);
|
|
4331
|
+
const nodes = [];
|
|
4332
|
+
const elements = [];
|
|
4333
|
+
const reportTitle = (kind, label) => {
|
|
4334
|
+
const word = titleLabels.reportKinds[kind];
|
|
4335
|
+
return label !== null && label !== void 0 ? `${word} \xB7 ${label}` : word;
|
|
4336
|
+
};
|
|
4337
|
+
const pushNode = (inst) => {
|
|
4338
|
+
nodes.push({
|
|
4339
|
+
positionId: inst.position.id,
|
|
4340
|
+
cx: round10(inst.cx),
|
|
4341
|
+
top: round10(rowTop[inst.depth]),
|
|
4342
|
+
boxW: round10(inst.m.boxW),
|
|
4343
|
+
boxH: round10(inst.m.boxH),
|
|
4344
|
+
style: inst.style,
|
|
4345
|
+
nameLines: inst.m.nameLines,
|
|
4346
|
+
titleLines: inst.m.titleLines,
|
|
4347
|
+
subtitleLines: inst.m.subtitleLines,
|
|
4348
|
+
vacantMarker: inst.m.vacantMarker,
|
|
4349
|
+
isAssistant: inst.assistReport !== null,
|
|
4350
|
+
depth: inst.depth,
|
|
4351
|
+
title: inst.title,
|
|
4352
|
+
annotated: inst.position.annotated ?? false
|
|
4353
|
+
});
|
|
4354
|
+
};
|
|
4355
|
+
const emit = (inst) => {
|
|
4356
|
+
const d = inst.depth;
|
|
4357
|
+
let bandY = rowTop[d] + rowH[d] + ORG_ASSIST_BAND_DROP;
|
|
4358
|
+
for (const { inst: a, report } of inst.assistants) {
|
|
4359
|
+
const assistRight = inst.cx - ORG_ASSIST_GAP;
|
|
4360
|
+
a.cx = assistRight - a.m.boxW / 2;
|
|
4361
|
+
const assistTop = bandY;
|
|
4362
|
+
a.assistTop = assistTop;
|
|
4363
|
+
const assistMidY = assistTop + a.m.boxH / 2;
|
|
4364
|
+
nodes.push({
|
|
4365
|
+
positionId: a.position.id,
|
|
4366
|
+
cx: round10(a.cx),
|
|
4367
|
+
top: round10(assistTop),
|
|
4368
|
+
boxW: round10(a.m.boxW),
|
|
4369
|
+
boxH: round10(a.m.boxH),
|
|
4370
|
+
style: a.style,
|
|
4371
|
+
nameLines: a.m.nameLines,
|
|
4372
|
+
titleLines: a.m.titleLines,
|
|
4373
|
+
subtitleLines: a.m.subtitleLines,
|
|
4374
|
+
vacantMarker: a.m.vacantMarker,
|
|
4375
|
+
isAssistant: true,
|
|
4376
|
+
depth: a.depth,
|
|
4377
|
+
title: a.title,
|
|
4378
|
+
annotated: a.position.annotated ?? false
|
|
4379
|
+
});
|
|
4380
|
+
elements.push({
|
|
4381
|
+
edgeId: ORG_ASSIST_ID_BASE + report.reportId,
|
|
4382
|
+
kind: "assist",
|
|
4383
|
+
points: [
|
|
4384
|
+
{ x: round10(inst.cx), y: round10(assistMidY) },
|
|
4385
|
+
{ x: drawnRightEdge(a.cx, a.m.boxW), y: round10(assistMidY) }
|
|
4386
|
+
],
|
|
4387
|
+
dashed: false,
|
|
4388
|
+
title: reportTitle("assistant", report.label),
|
|
4389
|
+
annotated: report.annotated ?? false
|
|
4390
|
+
});
|
|
4391
|
+
bandY += a.m.boxH + ORG_ASSIST_BAND_DROP;
|
|
4392
|
+
}
|
|
4393
|
+
pushNode(inst);
|
|
4394
|
+
if (inst.lineChildren.length === 0) return;
|
|
4395
|
+
const by = busY(d);
|
|
4396
|
+
const boxBottom = rowTop[d] + inst.m.boxH;
|
|
4397
|
+
elements.push({
|
|
4398
|
+
edgeId: ORG_STEM_ID_BASE + inst.position.id,
|
|
4399
|
+
kind: "stem",
|
|
4400
|
+
points: [
|
|
4401
|
+
{ x: round10(inst.cx), y: round10(boxBottom) },
|
|
4402
|
+
{ x: round10(inst.cx), y: round10(by) }
|
|
4403
|
+
],
|
|
4404
|
+
dashed: false,
|
|
4405
|
+
title: reportTitle("line", null),
|
|
4406
|
+
annotated: false
|
|
4407
|
+
});
|
|
4408
|
+
const childCxs = inst.lineChildren.map((c) => c.cx);
|
|
4409
|
+
if (inst.lineChildren.length > 1) {
|
|
4410
|
+
elements.push({
|
|
4411
|
+
edgeId: ORG_BUS_ID_BASE + inst.position.id,
|
|
4412
|
+
kind: "bus",
|
|
4413
|
+
points: [
|
|
4414
|
+
{ x: round10(Math.min(...childCxs)), y: round10(by) },
|
|
4415
|
+
{ x: round10(Math.max(...childCxs)), y: round10(by) }
|
|
4416
|
+
],
|
|
4417
|
+
dashed: false,
|
|
4418
|
+
title: reportTitle("line", null),
|
|
4419
|
+
annotated: false
|
|
4420
|
+
});
|
|
4421
|
+
}
|
|
4422
|
+
const lineReports = lineChildIds.get(inst.position.id) ?? [];
|
|
4423
|
+
const lineReportByChildId = new Map(lineReports.map((r) => [r.reportId, r]));
|
|
4424
|
+
for (const c of inst.lineChildren) {
|
|
4425
|
+
const lineReport = lineReportByChildId.get(c.position.id);
|
|
4426
|
+
elements.push({
|
|
4427
|
+
edgeId: ORG_DROP_ID_BASE + c.position.id,
|
|
4428
|
+
kind: "drop",
|
|
4429
|
+
points: [
|
|
4430
|
+
{ x: round10(c.cx), y: round10(by) },
|
|
4431
|
+
{ x: round10(c.cx), y: round10(rowTop[c.depth]) }
|
|
4432
|
+
],
|
|
4433
|
+
dashed: false,
|
|
4434
|
+
title: reportTitle("line", null),
|
|
4435
|
+
annotated: lineReport?.annotated ?? false
|
|
4436
|
+
});
|
|
4437
|
+
}
|
|
4438
|
+
for (const c of inst.lineChildren) emit(c);
|
|
4439
|
+
};
|
|
4440
|
+
for (const root of roots) emit(root);
|
|
4441
|
+
const instByPos = new Map(allInsts.map((i) => [i.position.id, i]));
|
|
4442
|
+
const escapeByPos = /* @__PURE__ */ new Map();
|
|
4443
|
+
for (const inst of allInsts) {
|
|
4444
|
+
const isAssist = inst.assistReport !== null;
|
|
4445
|
+
const boxTop = isAssist ? inst.assistTop : rowTop[inst.depth];
|
|
4446
|
+
const leftEdge = drawnLeftEdge(inst.cx, inst.m.boxW);
|
|
4447
|
+
const rightEdge = drawnRightEdge(inst.cx, inst.m.boxW);
|
|
4448
|
+
if (isAssist) {
|
|
4449
|
+
const managerInst = instByPos.get(inst.assistReport.managerId);
|
|
4450
|
+
escapeByPos.set(inst.position.id, {
|
|
4451
|
+
side: -1,
|
|
4452
|
+
boxEdge: leftEdge,
|
|
4453
|
+
escCol: managerInst.cx - managerInst.spanL,
|
|
4454
|
+
midY: boxTop + inst.m.boxH / 2,
|
|
4455
|
+
boxH: inst.m.boxH
|
|
4456
|
+
});
|
|
4457
|
+
} else {
|
|
4458
|
+
escapeByPos.set(inst.position.id, {
|
|
4459
|
+
side: 1,
|
|
4460
|
+
boxEdge: rightEdge,
|
|
4461
|
+
escCol: inst.cx + inst.spanR,
|
|
4462
|
+
midY: boxTop + inst.m.boxH / 2,
|
|
4463
|
+
boxH: inst.m.boxH
|
|
4464
|
+
});
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4467
|
+
const nodeById = new Map(nodes.map((n) => [n.positionId, n]));
|
|
4468
|
+
const dottedReports = reports.filter(
|
|
4469
|
+
(r) => r.kind === "dotted" && nodeById.has(r.managerId) && nodeById.has(r.reportId) && r.managerId !== r.reportId
|
|
4470
|
+
);
|
|
4471
|
+
let maxBoxBottom = PADDING7;
|
|
4472
|
+
for (const n of nodes) maxBoxBottom = Math.max(maxBoxBottom, n.top + n.boxH);
|
|
4473
|
+
let laneCount = 0;
|
|
4474
|
+
let channelLanes = 0;
|
|
4475
|
+
if (dottedReports.length > 0) {
|
|
4476
|
+
const laneOf = /* @__PURE__ */ new Map();
|
|
4477
|
+
laneCount = allocateLanes2(
|
|
4478
|
+
dottedReports.map((r) => {
|
|
4479
|
+
const rep = escapeByPos.get(r.reportId);
|
|
4480
|
+
const mgr = escapeByPos.get(r.managerId);
|
|
4481
|
+
return {
|
|
4482
|
+
lo: Math.min(rep.midY, mgr.midY),
|
|
4483
|
+
hi: Math.max(rep.midY, mgr.midY),
|
|
4484
|
+
set: (lane) => laneOf.set(r.id, lane)
|
|
4485
|
+
};
|
|
4486
|
+
})
|
|
4487
|
+
);
|
|
4488
|
+
channelLanes = dottedReports.length * 2;
|
|
4489
|
+
const channelTop = maxBoxBottom + ORG_MATRIX_GUTTER_GAP;
|
|
4490
|
+
const exitSlot = /* @__PURE__ */ new Map();
|
|
4491
|
+
const nextExitY = (posId, mid, boxH) => {
|
|
4492
|
+
const slot = exitSlot.get(posId) ?? 0;
|
|
4493
|
+
exitSlot.set(posId, slot + 1);
|
|
4494
|
+
if (slot === 0) return mid;
|
|
4495
|
+
const step = Math.min(6, Math.max(2, (boxH / 2 - 6) / Math.max(1, Math.ceil(slot / 2))));
|
|
4496
|
+
const sign = slot % 2 === 1 ? -1 : 1;
|
|
4497
|
+
return mid + sign * Math.ceil(slot / 2) * step;
|
|
4498
|
+
};
|
|
4499
|
+
const geoms = dottedReports.map((r, i) => {
|
|
4500
|
+
const rep = escapeByPos.get(r.reportId);
|
|
4501
|
+
const mgr = escapeByPos.get(r.managerId);
|
|
4502
|
+
return {
|
|
4503
|
+
r,
|
|
4504
|
+
i,
|
|
4505
|
+
repMidY: round10(nextExitY(r.reportId, rep.midY, rep.boxH)),
|
|
4506
|
+
mgrMidY: round10(nextExitY(r.managerId, mgr.midY, mgr.boxH)),
|
|
4507
|
+
repBoxEdge: round10(rep.boxEdge),
|
|
4508
|
+
mgrBoxEdge: round10(mgr.boxEdge),
|
|
4509
|
+
repChY: round10(channelTop + 2 * i * ORG_MATRIX_LANE_PITCH),
|
|
4510
|
+
mgrChY: round10(channelTop + (2 * i + 1) * ORG_MATRIX_LANE_PITCH),
|
|
4511
|
+
laneX: round10(canvasRight + ORG_MATRIX_GUTTER_GAP + laneOf.get(r.id) * ORG_MATRIX_LANE_PITCH),
|
|
4512
|
+
repEscX: 0,
|
|
4513
|
+
mgrEscX: 0
|
|
4514
|
+
};
|
|
4515
|
+
});
|
|
4516
|
+
const verts = [];
|
|
4517
|
+
for (const g of geoms) {
|
|
4518
|
+
const rep = escapeByPos.get(g.r.reportId);
|
|
4519
|
+
const mgr = escapeByPos.get(g.r.managerId);
|
|
4520
|
+
verts.push({
|
|
4521
|
+
escCol: rep.escCol,
|
|
4522
|
+
side: rep.side,
|
|
4523
|
+
lo: Math.min(g.repMidY, g.repChY),
|
|
4524
|
+
hi: Math.max(g.repMidY, g.repChY),
|
|
4525
|
+
assign: (x) => g.repEscX = x
|
|
4526
|
+
});
|
|
4527
|
+
verts.push({
|
|
4528
|
+
escCol: mgr.escCol,
|
|
4529
|
+
side: mgr.side,
|
|
4530
|
+
lo: Math.min(g.mgrMidY, g.mgrChY),
|
|
4531
|
+
hi: Math.max(g.mgrMidY, g.mgrChY),
|
|
4532
|
+
assign: (x) => g.mgrEscX = x
|
|
4533
|
+
});
|
|
4534
|
+
}
|
|
4535
|
+
const VERT_EPS = 0.01;
|
|
4536
|
+
const ordered = verts.map((v, idx) => ({ v, key: geoms[Math.floor(idx / 2)].r.id * 2 + idx % 2 })).sort((a, b) => a.key - b.key).map((e) => e.v);
|
|
4537
|
+
const placed = [];
|
|
4538
|
+
const yOverlap = (aLo, aHi, bLo, bHi) => Math.min(aHi, bHi) - Math.max(aLo, bLo) > VERT_EPS;
|
|
4539
|
+
for (const v of ordered) {
|
|
4540
|
+
let band = v.side === 1 ? Math.max(canvasRight - v.escCol, ORG_MATRIX_GUTTER_GAP) : v.escCol;
|
|
4541
|
+
for (const n of nodes) {
|
|
4542
|
+
const bLeft = n.cx - n.boxW / 2;
|
|
4543
|
+
const bRight = n.cx + n.boxW / 2;
|
|
4544
|
+
if (n.top + n.boxH <= v.lo + VERT_EPS || n.top >= v.hi - VERT_EPS) continue;
|
|
4545
|
+
if (v.side === 1 && bLeft > v.escCol + VERT_EPS) band = Math.min(band, bLeft - v.escCol);
|
|
4546
|
+
else if (v.side === -1 && bRight < v.escCol - VERT_EPS) band = Math.min(band, v.escCol - bRight);
|
|
4547
|
+
}
|
|
4548
|
+
let lane = 0;
|
|
4549
|
+
let candidate = round10(v.escCol);
|
|
4550
|
+
while (placed.some((p) => Math.abs(p.x - candidate) < VERT_EPS && yOverlap(p.lo, p.hi, v.lo, v.hi))) {
|
|
4551
|
+
lane += 1;
|
|
4552
|
+
candidate = round10(v.escCol + v.side * lane * ORG_MATRIX_ESCAPE_STEP);
|
|
4553
|
+
}
|
|
4554
|
+
if (lane > 0 && lane * ORG_MATRIX_ESCAPE_STEP >= band - VERT_EPS) {
|
|
4555
|
+
throw new Error(
|
|
4556
|
+
`org-chart: escape-column-overflow \u2014 escape vertical nudged ${lane * ORG_MATRIX_ESCAPE_STEP}px from its base column exceeds the ${round10(band)}px box-free band`
|
|
4557
|
+
);
|
|
4558
|
+
}
|
|
4559
|
+
placed.push({ x: candidate, lo: v.lo, hi: v.hi });
|
|
4560
|
+
v.assign(candidate);
|
|
4561
|
+
}
|
|
4562
|
+
for (const g of geoms) {
|
|
4563
|
+
elements.push({
|
|
4564
|
+
edgeId: ORG_DOTTED_ID_BASE + g.r.id,
|
|
4565
|
+
kind: "dotted",
|
|
4566
|
+
points: collapseDegenerate([
|
|
4567
|
+
{ x: g.repBoxEdge, y: g.repMidY },
|
|
4568
|
+
// out the report box's edge
|
|
4569
|
+
{ x: g.repEscX, y: g.repMidY },
|
|
4570
|
+
// → box-free escape column (in own gutter lane)
|
|
4571
|
+
{ x: g.repEscX, y: g.repChY },
|
|
4572
|
+
// → down into the bottom traverse channel
|
|
4573
|
+
{ x: g.laneX, y: g.repChY },
|
|
4574
|
+
// → across (below all boxes) to the right gutter lane
|
|
4575
|
+
{ x: g.laneX, y: g.mgrChY },
|
|
4576
|
+
// → down the right gutter (right of every box)
|
|
4577
|
+
{ x: g.mgrEscX, y: g.mgrChY },
|
|
4578
|
+
// → back across (below all boxes) to the manager column
|
|
4579
|
+
{ x: g.mgrEscX, y: g.mgrMidY },
|
|
4580
|
+
// → up the box-free escape column
|
|
4581
|
+
{ x: g.mgrBoxEdge, y: g.mgrMidY }
|
|
4582
|
+
// → into the manager box's edge
|
|
4583
|
+
]),
|
|
4584
|
+
dashed: true,
|
|
4585
|
+
title: reportTitle("dotted", g.r.label),
|
|
4586
|
+
annotated: g.r.annotated ?? false
|
|
4587
|
+
});
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
const width = dottedReports.length > 0 ? Math.ceil(canvasRight + ORG_MATRIX_GUTTER_GAP + (laneCount - 1) * ORG_MATRIX_LANE_PITCH + ORG_MATRIX_GUTTER_GAP + PADDING7) : Math.ceil(canvasRight + PADDING7);
|
|
4591
|
+
const finalHeight = channelLanes > 0 ? Math.ceil(maxBoxBottom + ORG_MATRIX_GUTTER_GAP + (channelLanes - 1) * ORG_MATRIX_LANE_PITCH + PADDING7) : height;
|
|
4592
|
+
return { width, height: finalHeight, nodes, elements, rootPositionIds };
|
|
4593
|
+
}
|
|
4594
|
+
function collapseDegenerate(points) {
|
|
4595
|
+
const out = [];
|
|
4596
|
+
for (const p of points) {
|
|
4597
|
+
const prev = out[out.length - 1];
|
|
4598
|
+
if (prev === void 0 || prev.x !== p.x || prev.y !== p.y) out.push(p);
|
|
4599
|
+
}
|
|
4600
|
+
return out;
|
|
4601
|
+
}
|
|
4602
|
+
function allocateLanes2(items) {
|
|
4603
|
+
const lanes = [];
|
|
4604
|
+
for (const it of items) {
|
|
4605
|
+
let chosen = -1;
|
|
4606
|
+
for (let l = 0; l < lanes.length; l++) {
|
|
4607
|
+
if (lanes[l].every((o) => it.hi <= o.lo || it.lo >= o.hi)) {
|
|
4608
|
+
chosen = l;
|
|
4609
|
+
break;
|
|
4610
|
+
}
|
|
4611
|
+
}
|
|
4612
|
+
if (chosen === -1) {
|
|
4613
|
+
chosen = lanes.length;
|
|
4614
|
+
lanes.push([]);
|
|
4615
|
+
}
|
|
4616
|
+
lanes[chosen].push({ lo: it.lo, hi: it.hi });
|
|
4617
|
+
it.set(chosen);
|
|
4618
|
+
}
|
|
4619
|
+
return lanes.length;
|
|
4620
|
+
}
|
|
4621
|
+
|
|
4622
|
+
// src/org-chart/svg.ts
|
|
4623
|
+
var GLYPH_STROKE5 = "#52525b";
|
|
4624
|
+
var LABEL_FILL6 = "#3f3f46";
|
|
4625
|
+
var EDGE_INK7 = "#71717a";
|
|
4626
|
+
var GLYPH_ATTRS3 = `fill="transparent" stroke="${GLYPH_STROKE5}" stroke-width="2"`;
|
|
4627
|
+
var VACANT_DASH = `stroke-dasharray="6,4"`;
|
|
4628
|
+
var DOTTED = EDGE_STROKE.distant;
|
|
4629
|
+
var round11 = (n) => Math.round(n * 100) / 100;
|
|
4630
|
+
var ORG_BOX_PAD_Y2 = 9;
|
|
4631
|
+
function boxSvg(n) {
|
|
4632
|
+
const left = round11(n.cx - n.boxW / 2);
|
|
4633
|
+
const pieces = [`<title>${xmlEscape(n.title)}</title>`];
|
|
4634
|
+
if (n.style === "dashed") {
|
|
4635
|
+
pieces.push(
|
|
4636
|
+
`<rect x="${left}" y="${n.top}" width="${n.boxW}" height="${n.boxH}" rx="2" ${GLYPH_ATTRS3} ${VACANT_DASH}/>`
|
|
4637
|
+
);
|
|
4638
|
+
const ix = round11(left + 3);
|
|
4639
|
+
const iy = round11(n.top + 3);
|
|
4640
|
+
const iw = round11(n.boxW - 6);
|
|
4641
|
+
const ih = round11(n.boxH - 6);
|
|
4642
|
+
pieces.push(`<rect x="${ix}" y="${iy}" width="${iw}" height="${ih}" rx="2" ${GLYPH_ATTRS3} ${VACANT_DASH}/>`);
|
|
4643
|
+
} else {
|
|
4644
|
+
pieces.push(`<rect x="${left}" y="${n.top}" width="${n.boxW}" height="${n.boxH}" rx="2" ${GLYPH_ATTRS3}/>`);
|
|
4645
|
+
}
|
|
4646
|
+
let lineIndex = 0;
|
|
4647
|
+
const firstBaseline = (i) => round11(n.top + ORG_BOX_PAD_Y2 + ORG_LABEL_LINE_H / 2 + i * ORG_LABEL_LINE_H + ORG_LABEL_FONT * 0.32);
|
|
4648
|
+
const textBlock = (lines, font) => {
|
|
4649
|
+
if (lines.length === 0) return "";
|
|
4650
|
+
const tspans = lines.map((line, i) => `<tspan x="${n.cx}" y="${firstBaseline(lineIndex + i)}">${xmlEscape(line)}</tspan>`).join("");
|
|
4651
|
+
lineIndex += lines.length;
|
|
4652
|
+
return `<text text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${font}" fill="${LABEL_FILL6}">${tspans}</text>`;
|
|
4653
|
+
};
|
|
4654
|
+
pieces.push(textBlock(n.nameLines, ORG_LABEL_FONT));
|
|
4655
|
+
pieces.push(textBlock(n.titleLines, ORG_TITLE_FONT));
|
|
4656
|
+
pieces.push(textBlock(n.subtitleLines, ORG_TITLE_FONT));
|
|
4657
|
+
if (n.vacantMarker !== null) pieces.push(textBlock([n.vacantMarker], ORG_TITLE_FONT));
|
|
4658
|
+
if (n.annotated) pieces.push(annotationDot(round11(n.cx + n.boxW / 2) - 4, n.top + 4));
|
|
4659
|
+
return `<g data-node-id="p${n.positionId}">${pieces.filter((p) => p !== "").join("")}</g>`;
|
|
4660
|
+
}
|
|
4661
|
+
function elementSvg5(el) {
|
|
4662
|
+
const head = `<g data-edge-id="${el.edgeId}"><title>${xmlEscape(el.title)}</title>`;
|
|
4663
|
+
const tick = el.annotated ? annotationTick(el.points) : "";
|
|
4664
|
+
if (el.dashed) {
|
|
4665
|
+
const dash = DOTTED.dash === null ? "" : ` stroke-dasharray="${DOTTED.dash[0]},${DOTTED.dash[1]}"`;
|
|
4666
|
+
return head + `<path d="${pathData(el.points)}" fill="none" stroke="${EDGE_INK7}" stroke-width="${DOTTED.width}" stroke-opacity="${DOTTED.opacity}"${dash}/>${tick}</g>`;
|
|
4667
|
+
}
|
|
4668
|
+
if (el.points.length === 2) {
|
|
4669
|
+
const a = el.points[0];
|
|
4670
|
+
const b = el.points[1];
|
|
4671
|
+
return head + `<line x1="${a.x}" y1="${a.y}" x2="${b.x}" y2="${b.y}" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/>${tick}</g>`;
|
|
4672
|
+
}
|
|
4673
|
+
return head + `<path d="${pathData(el.points)}" fill="none" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/>${tick}</g>`;
|
|
4674
|
+
}
|
|
4675
|
+
var MINI_ATTRS3 = `fill="transparent" stroke="${GLYPH_STROKE5}" stroke-width="1.5"`;
|
|
4676
|
+
function lineSwatch(x, y) {
|
|
4677
|
+
const x1 = round11(x + 2);
|
|
4678
|
+
const x2 = round11(x + LEGEND_SWATCH_W - 2);
|
|
4679
|
+
return `<line x1="${x1}" y1="${y}" x2="${x2}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/>`;
|
|
4680
|
+
}
|
|
4681
|
+
function assistantSwatch(x, y) {
|
|
4682
|
+
const stemX = round11(x + LEGEND_SWATCH_W - 4);
|
|
4683
|
+
const boxX = round11(x + 1);
|
|
4684
|
+
return `<line x1="${stemX}" y1="${round11(y - 4)}" x2="${stemX}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/><line x1="${stemX}" y1="${y}" x2="${round11(boxX + 8)}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/><rect x="${boxX}" y="${round11(y - 3.5)}" width="8" height="7" rx="1" ${MINI_ATTRS3}/>`;
|
|
4685
|
+
}
|
|
4686
|
+
function dottedSwatch(x, y) {
|
|
4687
|
+
const x1 = round11(x + 2);
|
|
4688
|
+
const x2 = round11(x + LEGEND_SWATCH_W - 2);
|
|
4689
|
+
const dash = DOTTED.dash === null ? "" : ` stroke-dasharray="${DOTTED.dash[0]},${DOTTED.dash[1]}"`;
|
|
4690
|
+
return `<line x1="${x1}" y1="${y}" x2="${x2}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="${DOTTED.width}" stroke-opacity="${DOTTED.opacity}"${dash}/>`;
|
|
4691
|
+
}
|
|
4692
|
+
function vacantSwatch(x, y) {
|
|
4693
|
+
const cx = round11(x + LEGEND_SWATCH_W / 2);
|
|
4694
|
+
const bx = round11(cx - 8);
|
|
4695
|
+
const by = round11(y - 5);
|
|
4696
|
+
return `<rect x="${bx}" y="${by}" width="16" height="10" rx="1" ${MINI_ATTRS3} stroke-dasharray="3,2"/><rect x="${round11(bx + 2)}" y="${round11(by + 2)}" width="12" height="6" rx="1" ${MINI_ATTRS3} stroke-dasharray="3,2"/>`;
|
|
4697
|
+
}
|
|
4698
|
+
function orgChartLayoutSvg(layout, opts = {}) {
|
|
4699
|
+
const labels = opts.labels ?? ORG_CHART_SVG_LABELS_EN;
|
|
4700
|
+
const parts = [];
|
|
4701
|
+
for (const el of layout.elements) parts.push(elementSvg5(el));
|
|
4702
|
+
for (const n of layout.nodes) parts.push(boxSvg(n));
|
|
4703
|
+
let width = layout.width;
|
|
4704
|
+
let height = layout.height;
|
|
4705
|
+
if (opts.legend !== false && layout.nodes.length > 0) {
|
|
4706
|
+
const hasSolid = layout.elements.some((e) => e.kind === "stem" || e.kind === "bus" || e.kind === "drop");
|
|
4707
|
+
const hasAssistant = layout.nodes.some((n) => n.isAssistant);
|
|
4708
|
+
const hasDotted = layout.elements.some((e) => e.kind === "dotted");
|
|
4709
|
+
const hasVacant = layout.nodes.some((n) => n.style === "dashed");
|
|
4710
|
+
const entries = [];
|
|
4711
|
+
if (hasSolid) entries.push({ swatch: lineSwatch, label: labels.legend.line });
|
|
4712
|
+
if (hasAssistant) entries.push({ swatch: assistantSwatch, label: labels.legend.assistant });
|
|
4713
|
+
if (hasDotted) entries.push({ swatch: dottedSwatch, label: labels.legend.dotted });
|
|
4714
|
+
if (hasVacant) entries.push({ swatch: vacantSwatch, label: labels.legend.vacant });
|
|
4715
|
+
if (opts.annotationLabel !== void 0 && (layout.nodes.some((n) => n.annotated) || layout.elements.some((e) => e.annotated))) {
|
|
4716
|
+
entries.push({ swatch: annotationSwatch, label: opts.annotationLabel });
|
|
4717
|
+
}
|
|
4718
|
+
const block = legendBlock(entries, layout.height);
|
|
4719
|
+
if (block.svg !== "") {
|
|
4720
|
+
parts.push(block.svg);
|
|
4721
|
+
width = Math.max(width, block.width);
|
|
4722
|
+
height = block.height;
|
|
4723
|
+
}
|
|
4724
|
+
}
|
|
4725
|
+
const w = Math.ceil(width);
|
|
4726
|
+
const h = Math.ceil(height);
|
|
4727
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${w} ${h}" width="${w}" height="${h}" role="img" aria-label="${xmlEscape(labels.ariaLabel)}">` + parts.join("") + `</svg>`;
|
|
4728
|
+
}
|
|
4729
|
+
|
|
4730
|
+
// src/org-chart/render.ts
|
|
4731
|
+
function orgChartSvg(input, opts = {}) {
|
|
4732
|
+
const layout = computeOrgChartLayout(input, {
|
|
4733
|
+
...opts.maxLabelChars !== void 0 ? { maxLabelChars: opts.maxLabelChars } : {},
|
|
4734
|
+
...opts.titleLabels !== void 0 ? { titleLabels: opts.titleLabels } : {}
|
|
4735
|
+
});
|
|
4736
|
+
const svg = orgChartLayoutSvg(layout, {
|
|
4737
|
+
...opts.legend === false ? { legend: false } : {},
|
|
4738
|
+
...opts.svgLabels !== void 0 ? { labels: opts.svgLabels } : {},
|
|
4739
|
+
...opts.annotationLabel !== void 0 ? { annotationLabel: opts.annotationLabel } : {}
|
|
4740
|
+
});
|
|
4741
|
+
return { svg, layout };
|
|
4742
|
+
}
|
|
4743
|
+
|
|
4744
|
+
exports.ANNOTATION_INK = ANNOTATION_INK;
|
|
3907
4745
|
exports.CHAR_W = CHAR_W;
|
|
3908
4746
|
exports.CODE_FONT = CODE_FONT;
|
|
3909
4747
|
exports.ECOMAP_LABELS_EN = ECOMAP_LABELS_EN;
|
|
@@ -3938,6 +4776,20 @@ exports.LEGEND_SWATCH_W = LEGEND_SWATCH_W;
|
|
|
3938
4776
|
exports.LIFE_STATUSES = LIFE_STATUSES;
|
|
3939
4777
|
exports.MAX_CONDITIONS_PER_INDIVIDUAL = MAX_CONDITIONS_PER_INDIVIDUAL;
|
|
3940
4778
|
exports.NODE_SIZE = NODE_SIZE;
|
|
4779
|
+
exports.ORG_ASSIST_ID_BASE = ORG_ASSIST_ID_BASE;
|
|
4780
|
+
exports.ORG_BUS_ID_BASE = ORG_BUS_ID_BASE;
|
|
4781
|
+
exports.ORG_CHART_SVG_LABELS_EN = ORG_CHART_SVG_LABELS_EN;
|
|
4782
|
+
exports.ORG_CHART_TITLE_LABELS_EN = ORG_CHART_TITLE_LABELS_EN;
|
|
4783
|
+
exports.ORG_DOTTED_ID_BASE = ORG_DOTTED_ID_BASE;
|
|
4784
|
+
exports.ORG_DROP_ID_BASE = ORG_DROP_ID_BASE;
|
|
4785
|
+
exports.ORG_LABEL_FONT = ORG_LABEL_FONT;
|
|
4786
|
+
exports.ORG_LABEL_LINE_H = ORG_LABEL_LINE_H;
|
|
4787
|
+
exports.ORG_MAX_MATRIX_EDGES_PER_NODE = ORG_MAX_MATRIX_EDGES_PER_NODE;
|
|
4788
|
+
exports.ORG_REPORT_KINDS = ORG_REPORT_KINDS;
|
|
4789
|
+
exports.ORG_STEM_ID_BASE = ORG_STEM_ID_BASE;
|
|
4790
|
+
exports.ORG_TITLE_FONT = ORG_TITLE_FONT;
|
|
4791
|
+
exports.ORG_VACANCIES = ORG_VACANCIES;
|
|
4792
|
+
exports.OrgChartValidationError = OrgChartValidationError;
|
|
3941
4793
|
exports.PARENT_REL_ID_BASE = PARENT_REL_ID_BASE;
|
|
3942
4794
|
exports.PEDIGREE_SVG_LABELS_EN = PEDIGREE_SVG_LABELS_EN;
|
|
3943
4795
|
exports.PEDIGREE_TITLE_LABELS_EN = PEDIGREE_TITLE_LABELS_EN;
|
|
@@ -3970,10 +4822,14 @@ exports.QUALITY_LEXICON_EN = QUALITY_LEXICON_EN;
|
|
|
3970
4822
|
exports.UNION_NOTATION = UNION_NOTATION;
|
|
3971
4823
|
exports.UNION_REL_ID_BASE = UNION_REL_ID_BASE;
|
|
3972
4824
|
exports.UNION_STATUSES = UNION_STATUSES;
|
|
4825
|
+
exports.annotationDot = annotationDot;
|
|
4826
|
+
exports.annotationSwatch = annotationSwatch;
|
|
4827
|
+
exports.annotationTick = annotationTick;
|
|
3973
4828
|
exports.clampLabel = clampLabel;
|
|
3974
4829
|
exports.classifyRelationshipType = classifyRelationshipType;
|
|
3975
4830
|
exports.computeFaultTreeLayout = computeFaultTreeLayout;
|
|
3976
4831
|
exports.computeGenogramLayout = computeGenogramLayout;
|
|
4832
|
+
exports.computeOrgChartLayout = computeOrgChartLayout;
|
|
3977
4833
|
exports.computePedigreeLayout = computePedigreeLayout;
|
|
3978
4834
|
exports.computePhyloLayout = computePhyloLayout;
|
|
3979
4835
|
exports.ecomapSvg = ecomapSvg;
|
|
@@ -3988,6 +4844,10 @@ exports.latestUnionPerPair = latestUnionPerPair;
|
|
|
3988
4844
|
exports.legendBlock = legendBlock;
|
|
3989
4845
|
exports.niceScaleStep = niceScaleStep;
|
|
3990
4846
|
exports.normalizeText = normalizeText;
|
|
4847
|
+
exports.orgChartIssues = orgChartIssues;
|
|
4848
|
+
exports.orgChartLayoutSvg = orgChartLayoutSvg;
|
|
4849
|
+
exports.orgChartSvg = orgChartSvg;
|
|
4850
|
+
exports.packSubtree = packSubtree;
|
|
3991
4851
|
exports.pathData = pathData;
|
|
3992
4852
|
exports.pedigreeIssues = pedigreeIssues;
|
|
3993
4853
|
exports.pedigreeLayoutSvg = pedigreeLayoutSvg;
|
|
@@ -3999,6 +4859,7 @@ exports.qualityLineStyle = qualityLineStyle;
|
|
|
3999
4859
|
exports.relationshipTypeTokens = relationshipTypeTokens;
|
|
4000
4860
|
exports.romanNumeral = romanNumeral;
|
|
4001
4861
|
exports.validateFaultTree = validateFaultTree;
|
|
4862
|
+
exports.validateOrgChart = validateOrgChart;
|
|
4002
4863
|
exports.validatePedigree = validatePedigree;
|
|
4003
4864
|
exports.validatePhylo = validatePhylo;
|
|
4004
4865
|
exports.wrapLabel = wrapLabel;
|