compasso 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -10
- package/dist/chunk-UJVU7B44.js +764 -0
- package/dist/chunk-UJVU7B44.js.map +1 -0
- package/dist/index.cjs +785 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/labels-RtFw9tX1.d.cts +91 -0
- package/dist/labels-RtFw9tX1.d.ts +91 -0
- package/dist/locales/pt-br.cjs +19 -0
- package/dist/locales/pt-br.cjs.map +1 -1
- package/dist/locales/pt-br.d.cts +4 -1
- package/dist/locales/pt-br.d.ts +4 -1
- package/dist/locales/pt-br.js +18 -1
- package/dist/locales/pt-br.js.map +1 -1
- package/dist/org-chart/index.cjs +853 -0
- package/dist/org-chart/index.cjs.map +1 -0
- package/dist/org-chart/index.d.cts +168 -0
- package/dist/org-chart/index.d.ts +168 -0
- package/dist/org-chart/index.js +4 -0
- package/dist/org-chart/index.js.map +1 -0
- package/package.json +28 -11
package/dist/index.cjs
CHANGED
|
@@ -1592,7 +1592,7 @@ function faultTreeIssues(input) {
|
|
|
1592
1592
|
);
|
|
1593
1593
|
}
|
|
1594
1594
|
}
|
|
1595
|
-
const
|
|
1595
|
+
const GRAPH_BLOCKING2 = /* @__PURE__ */ new Set([
|
|
1596
1596
|
"duplicate-id",
|
|
1597
1597
|
"unknown-input",
|
|
1598
1598
|
"gate-on-non-intermediate",
|
|
@@ -1601,7 +1601,7 @@ function faultTreeIssues(input) {
|
|
|
1601
1601
|
"inhibit-condition",
|
|
1602
1602
|
"top-not-intermediate"
|
|
1603
1603
|
]);
|
|
1604
|
-
if (!issues.some((i) =>
|
|
1604
|
+
if (!issues.some((i) => GRAPH_BLOCKING2.has(i.code))) {
|
|
1605
1605
|
const gateOf = (eventId) => gates.find((g) => g.eventId === eventId);
|
|
1606
1606
|
const reachable = /* @__PURE__ */ new Set();
|
|
1607
1607
|
const queue = [input.topId];
|
|
@@ -2565,13 +2565,13 @@ function pedigreeIssues(input) {
|
|
|
2565
2565
|
);
|
|
2566
2566
|
}
|
|
2567
2567
|
}
|
|
2568
|
-
const
|
|
2568
|
+
const GRAPH_BLOCKING2 = /* @__PURE__ */ new Set([
|
|
2569
2569
|
"duplicate-id",
|
|
2570
2570
|
"unknown-partner",
|
|
2571
2571
|
"unknown-sibship-mating",
|
|
2572
2572
|
"unknown-child"
|
|
2573
2573
|
]);
|
|
2574
|
-
if (!issues.some((i) =>
|
|
2574
|
+
if (!issues.some((i) => GRAPH_BLOCKING2.has(i.code))) {
|
|
2575
2575
|
for (const s of sibships) {
|
|
2576
2576
|
const mating = matingById.get(s.matingId);
|
|
2577
2577
|
if (mating === void 0) continue;
|
|
@@ -3509,13 +3509,13 @@ function phyloIssues(input) {
|
|
|
3509
3509
|
push("tip-with-children", `node ${n.id} is declared internal but has no children`);
|
|
3510
3510
|
}
|
|
3511
3511
|
}
|
|
3512
|
-
const
|
|
3512
|
+
const GRAPH_BLOCKING2 = /* @__PURE__ */ new Set([
|
|
3513
3513
|
"duplicate-id",
|
|
3514
3514
|
"unknown-endpoint",
|
|
3515
3515
|
"unknown-root",
|
|
3516
3516
|
"multiple-parents"
|
|
3517
3517
|
]);
|
|
3518
|
-
if (!issues.some((i) =>
|
|
3518
|
+
if (!issues.some((i) => GRAPH_BLOCKING2.has(i.code))) {
|
|
3519
3519
|
const childrenOf = /* @__PURE__ */ new Map();
|
|
3520
3520
|
for (const e of [...input.edges].sort((a, b) => a.id - b.id)) {
|
|
3521
3521
|
const arr = childrenOf.get(e.parentId) ?? [];
|
|
@@ -3904,6 +3904,765 @@ function phyloSvg(input, opts = {}) {
|
|
|
3904
3904
|
return { svg, layout };
|
|
3905
3905
|
}
|
|
3906
3906
|
|
|
3907
|
+
// src/org-chart/types.ts
|
|
3908
|
+
var ORG_REPORT_KINDS = ["line", "assistant", "dotted"];
|
|
3909
|
+
var ORG_VACANCIES = ["filled", "vacant"];
|
|
3910
|
+
|
|
3911
|
+
// src/org-chart/labels.ts
|
|
3912
|
+
var ORG_CHART_TITLE_LABELS_EN = {
|
|
3913
|
+
reportKinds: { line: "Reports to", assistant: "Assistant to", dotted: "Dotted-line report to" },
|
|
3914
|
+
vacant: "(vacant)"
|
|
3915
|
+
};
|
|
3916
|
+
var ORG_CHART_SVG_LABELS_EN = {
|
|
3917
|
+
legend: { line: "Reports to", assistant: "Assistant", dotted: "Dotted-line / matrix", vacant: "Vacant position" },
|
|
3918
|
+
ariaLabel: "Organizational chart"
|
|
3919
|
+
};
|
|
3920
|
+
|
|
3921
|
+
// src/org-chart/validate.ts
|
|
3922
|
+
var ORG_MAX_MATRIX_EDGES_PER_NODE = 5;
|
|
3923
|
+
var OrgChartValidationError = class extends Error {
|
|
3924
|
+
issues;
|
|
3925
|
+
constructor(issues) {
|
|
3926
|
+
super(`invalid org chart: ${issues.map((i) => i.message).join("; ")}`);
|
|
3927
|
+
this.name = "OrgChartValidationError";
|
|
3928
|
+
this.issues = issues;
|
|
3929
|
+
}
|
|
3930
|
+
};
|
|
3931
|
+
function listIds2(ids) {
|
|
3932
|
+
if (ids.length === 1) return String(ids[0]);
|
|
3933
|
+
return `${ids.slice(0, -1).join(", ")} and ${ids[ids.length - 1]}`;
|
|
3934
|
+
}
|
|
3935
|
+
function sortIssues4(issues) {
|
|
3936
|
+
const unique = /* @__PURE__ */ new Map();
|
|
3937
|
+
for (const issue of issues) unique.set(`${issue.code} ${issue.message}`, issue);
|
|
3938
|
+
return [...unique.values()].sort(
|
|
3939
|
+
(a, b) => a.code !== b.code ? a.code < b.code ? -1 : 1 : a.message < b.message ? -1 : a.message > b.message ? 1 : 0
|
|
3940
|
+
);
|
|
3941
|
+
}
|
|
3942
|
+
var GRAPH_BLOCKING = /* @__PURE__ */ new Set([
|
|
3943
|
+
"duplicate-id",
|
|
3944
|
+
"unknown-manager",
|
|
3945
|
+
"unknown-report",
|
|
3946
|
+
"multiple-managers"
|
|
3947
|
+
]);
|
|
3948
|
+
function orgChartIssues(input) {
|
|
3949
|
+
if (input.positions.length === 0 && input.reports.length === 0) return [];
|
|
3950
|
+
const issues = [];
|
|
3951
|
+
const push = (code, message) => {
|
|
3952
|
+
issues.push({ code, message });
|
|
3953
|
+
};
|
|
3954
|
+
const positionById = /* @__PURE__ */ new Map();
|
|
3955
|
+
const dupPositionIds = /* @__PURE__ */ new Set();
|
|
3956
|
+
for (const p of input.positions) {
|
|
3957
|
+
if (positionById.has(p.id)) dupPositionIds.add(p.id);
|
|
3958
|
+
else positionById.set(p.id, p);
|
|
3959
|
+
}
|
|
3960
|
+
for (const id of [...dupPositionIds].sort((a, b) => a - b)) {
|
|
3961
|
+
push("duplicate-id", `duplicate position id ${id}`);
|
|
3962
|
+
}
|
|
3963
|
+
const reportById = /* @__PURE__ */ new Map();
|
|
3964
|
+
const dupReportIds = /* @__PURE__ */ new Set();
|
|
3965
|
+
for (const r of input.reports) {
|
|
3966
|
+
if (reportById.has(r.id)) dupReportIds.add(r.id);
|
|
3967
|
+
else reportById.set(r.id, r);
|
|
3968
|
+
}
|
|
3969
|
+
for (const id of [...dupReportIds].sort((a, b) => a - b)) {
|
|
3970
|
+
push("duplicate-id", `duplicate report id ${id}`);
|
|
3971
|
+
}
|
|
3972
|
+
if (issues.length > 0) {
|
|
3973
|
+
return sortIssues4(issues);
|
|
3974
|
+
}
|
|
3975
|
+
const reports = [...reportById.values()].sort((a, b) => a.id - b.id);
|
|
3976
|
+
const solidParents = /* @__PURE__ */ new Map();
|
|
3977
|
+
const assistantReportEdge = /* @__PURE__ */ new Map();
|
|
3978
|
+
const solidManagedEdges = /* @__PURE__ */ new Map();
|
|
3979
|
+
const dottedDegree = /* @__PURE__ */ new Map();
|
|
3980
|
+
for (const r of reports) {
|
|
3981
|
+
const hasManager = positionById.has(r.managerId);
|
|
3982
|
+
const hasReport = positionById.has(r.reportId);
|
|
3983
|
+
if (!hasManager) {
|
|
3984
|
+
push("unknown-manager", `report ${r.id} managerId ${r.managerId} is not a declared position`);
|
|
3985
|
+
}
|
|
3986
|
+
if (!hasReport) {
|
|
3987
|
+
push("unknown-report", `report ${r.id} reportId ${r.reportId} is not a declared position`);
|
|
3988
|
+
}
|
|
3989
|
+
if (r.managerId === r.reportId) {
|
|
3990
|
+
push("self-report", `report ${r.id} has managerId === reportId (${r.managerId})`);
|
|
3991
|
+
}
|
|
3992
|
+
if (r.kind !== "dotted") {
|
|
3993
|
+
const arr = solidParents.get(r.reportId) ?? [];
|
|
3994
|
+
arr.push(r.id);
|
|
3995
|
+
solidParents.set(r.reportId, arr);
|
|
3996
|
+
const managed = solidManagedEdges.get(r.managerId) ?? [];
|
|
3997
|
+
managed.push(r.id);
|
|
3998
|
+
solidManagedEdges.set(r.managerId, managed);
|
|
3999
|
+
}
|
|
4000
|
+
if (r.kind === "assistant" && r.managerId !== r.reportId && !assistantReportEdge.has(r.reportId)) {
|
|
4001
|
+
assistantReportEdge.set(r.reportId, r.id);
|
|
4002
|
+
}
|
|
4003
|
+
if (r.kind === "dotted" && r.managerId !== r.reportId) {
|
|
4004
|
+
if (positionById.has(r.managerId)) dottedDegree.set(r.managerId, (dottedDegree.get(r.managerId) ?? 0) + 1);
|
|
4005
|
+
if (positionById.has(r.reportId)) dottedDegree.set(r.reportId, (dottedDegree.get(r.reportId) ?? 0) + 1);
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
for (const [reportId, edgeIds] of [...solidParents.entries()].sort((a, b) => a[0] - b[0])) {
|
|
4009
|
+
if (edgeIds.length > 1) {
|
|
4010
|
+
push(
|
|
4011
|
+
"multiple-managers",
|
|
4012
|
+
`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"`
|
|
4013
|
+
);
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
for (const [positionId, assistEdgeId] of [...assistantReportEdge.entries()].sort((a, b) => a[0] - b[0])) {
|
|
4017
|
+
const managed = solidManagedEdges.get(positionId);
|
|
4018
|
+
if (managed !== void 0 && managed.length > 0) {
|
|
4019
|
+
const firstManaged = Math.min(...managed);
|
|
4020
|
+
push(
|
|
4021
|
+
"assistant-has-reports",
|
|
4022
|
+
`position ${positionId} is an assistant (report of edge ${assistEdgeId}) but manages solid edge ${firstManaged} \u2014 assistants are leaves`
|
|
4023
|
+
);
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
for (const [positionId, count] of [...dottedDegree.entries()].sort((a, b) => a[0] - b[0])) {
|
|
4027
|
+
if (count > ORG_MAX_MATRIX_EDGES_PER_NODE) {
|
|
4028
|
+
push(
|
|
4029
|
+
"too-many-matrix-edges",
|
|
4030
|
+
`position ${positionId} has ${count} dotted (matrix) connections \u2014 at most ${ORG_MAX_MATRIX_EDGES_PER_NODE} are supported per position`
|
|
4031
|
+
);
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
if (!issues.some((i) => GRAPH_BLOCKING.has(i.code))) {
|
|
4035
|
+
const solidChildren = /* @__PURE__ */ new Map();
|
|
4036
|
+
for (const r of reports) {
|
|
4037
|
+
if (r.kind === "dotted") continue;
|
|
4038
|
+
if (r.managerId === r.reportId) continue;
|
|
4039
|
+
const arr = solidChildren.get(r.managerId) ?? [];
|
|
4040
|
+
arr.push(r.reportId);
|
|
4041
|
+
solidChildren.set(r.managerId, arr);
|
|
4042
|
+
}
|
|
4043
|
+
for (const arr of solidChildren.values()) arr.sort((a, b) => a - b);
|
|
4044
|
+
const color = /* @__PURE__ */ new Map();
|
|
4045
|
+
const seenCycles = /* @__PURE__ */ new Set();
|
|
4046
|
+
const dfs = (start) => {
|
|
4047
|
+
const stack = [{ id: start, nextChild: 0 }];
|
|
4048
|
+
color.set(start, 1);
|
|
4049
|
+
while (stack.length > 0) {
|
|
4050
|
+
const frame = stack[stack.length - 1];
|
|
4051
|
+
const children = solidChildren.get(frame.id) ?? [];
|
|
4052
|
+
if (frame.nextChild >= children.length) {
|
|
4053
|
+
color.set(frame.id, 2);
|
|
4054
|
+
stack.pop();
|
|
4055
|
+
continue;
|
|
4056
|
+
}
|
|
4057
|
+
const child = children[frame.nextChild];
|
|
4058
|
+
frame.nextChild += 1;
|
|
4059
|
+
const c = color.get(child) ?? 0;
|
|
4060
|
+
if (c === 1) {
|
|
4061
|
+
const from = stack.findIndex((f) => f.id === child);
|
|
4062
|
+
const cycle = stack.slice(from).map((f) => f.id);
|
|
4063
|
+
const minIdx = cycle.indexOf(Math.min(...cycle));
|
|
4064
|
+
const rotated = [...cycle.slice(minIdx), ...cycle.slice(0, minIdx)];
|
|
4065
|
+
const key = rotated.join(">");
|
|
4066
|
+
if (!seenCycles.has(key)) {
|
|
4067
|
+
seenCycles.add(key);
|
|
4068
|
+
push("cycle", `cycle: ${[...rotated, rotated[0]].join(" \u2192 ")}`);
|
|
4069
|
+
}
|
|
4070
|
+
} else if (c === 0) {
|
|
4071
|
+
color.set(child, 1);
|
|
4072
|
+
stack.push({ id: child, nextChild: 0 });
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
};
|
|
4076
|
+
for (const p of [...positionById.values()].sort((a, b) => a.id - b.id)) {
|
|
4077
|
+
if ((color.get(p.id) ?? 0) === 0) dfs(p.id);
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
return sortIssues4(issues);
|
|
4081
|
+
}
|
|
4082
|
+
function validateOrgChart(input) {
|
|
4083
|
+
const issues = orgChartIssues(input);
|
|
4084
|
+
if (issues.length > 0) throw new OrgChartValidationError(issues);
|
|
4085
|
+
}
|
|
4086
|
+
|
|
4087
|
+
// src/org-chart/layout.ts
|
|
4088
|
+
var ORG_LABEL_FONT = 12;
|
|
4089
|
+
var ORG_TITLE_FONT = 10;
|
|
4090
|
+
var ORG_LABEL_LINE_H = 14;
|
|
4091
|
+
var PADDING7 = 32;
|
|
4092
|
+
var ORG_SIBLING_GAP = 28;
|
|
4093
|
+
var ORG_TREE_GAP = 56;
|
|
4094
|
+
var ORG_CORRIDOR = 30;
|
|
4095
|
+
var ORG_MIN_BOX_W = 120;
|
|
4096
|
+
var ORG_BOX_PAD_X = 12;
|
|
4097
|
+
var ORG_BOX_PAD_Y = 9;
|
|
4098
|
+
var ORG_ASSIST_GAP = 24;
|
|
4099
|
+
var ORG_ASSIST_BAND_DROP = 14;
|
|
4100
|
+
var ORG_MATRIX_GUTTER_GAP = 28;
|
|
4101
|
+
var ORG_MATRIX_LANE_PITCH = 14;
|
|
4102
|
+
var ORG_MATRIX_ESCAPE_STEP = 2;
|
|
4103
|
+
var ORG_STEM_ID_BASE = 1e6;
|
|
4104
|
+
var ORG_BUS_ID_BASE = 2e6;
|
|
4105
|
+
var ORG_DROP_ID_BASE = 3e6;
|
|
4106
|
+
var ORG_ASSIST_ID_BASE = 4e6;
|
|
4107
|
+
var ORG_DOTTED_ID_BASE = 5e6;
|
|
4108
|
+
var round9 = (n) => Math.round(n * 100) / 100;
|
|
4109
|
+
var drawnLeftEdge = (cx, boxW) => round9(round9(cx) - round9(boxW) / 2);
|
|
4110
|
+
var drawnRightEdge = (cx, boxW) => drawnLeftEdge(cx, boxW) + round9(boxW);
|
|
4111
|
+
function packSubtree(node, gaps) {
|
|
4112
|
+
const { ownHalfL, ownHalfR, children } = node;
|
|
4113
|
+
if (children.length === 0) {
|
|
4114
|
+
return { halfL: ownHalfL, halfR: ownHalfR, offsets: [] };
|
|
4115
|
+
}
|
|
4116
|
+
const xs = [0];
|
|
4117
|
+
for (let i = 1; i < children.length; i++) {
|
|
4118
|
+
xs.push(xs[i - 1] + children[i - 1].halfR + gaps.siblingGap + children[i].halfL);
|
|
4119
|
+
}
|
|
4120
|
+
const first = children[0];
|
|
4121
|
+
const last = children[children.length - 1];
|
|
4122
|
+
const axis = (xs[0] + xs[xs.length - 1]) / 2;
|
|
4123
|
+
const offsets = xs.map((x) => x - axis);
|
|
4124
|
+
const halfL = Math.max(ownHalfL, axis - (xs[0] - first.halfL));
|
|
4125
|
+
const halfR = Math.max(ownHalfR, xs[xs.length - 1] + last.halfR - axis);
|
|
4126
|
+
return { halfL, halfR, offsets };
|
|
4127
|
+
}
|
|
4128
|
+
function measurePosition(position, maxLabelChars, vacantWord) {
|
|
4129
|
+
const nameLines = position.name === "" ? [] : wrapLabelBalanced(clampLabel(position.name, maxLabelChars), 2);
|
|
4130
|
+
const titleLines = position.title === null ? [] : wrapLabelBalanced(clampLabel(position.title, maxLabelChars), 2);
|
|
4131
|
+
const subtitleLines = position.subtitle === null ? [] : wrapLabelBalanced(clampLabel(position.subtitle, maxLabelChars), 1);
|
|
4132
|
+
const vacantMarker = position.vacancy === "vacant" ? vacantWord : null;
|
|
4133
|
+
let innerW = 0;
|
|
4134
|
+
for (const l of nameLines) innerW = Math.max(innerW, estimateTextWidth(l, ORG_LABEL_FONT));
|
|
4135
|
+
for (const l of titleLines) innerW = Math.max(innerW, estimateTextWidth(l, ORG_TITLE_FONT));
|
|
4136
|
+
for (const l of subtitleLines) innerW = Math.max(innerW, estimateTextWidth(l, ORG_TITLE_FONT));
|
|
4137
|
+
if (vacantMarker !== null) innerW = Math.max(innerW, estimateTextWidth(vacantMarker, ORG_TITLE_FONT));
|
|
4138
|
+
const boxW = Math.max(ORG_MIN_BOX_W, innerW + ORG_BOX_PAD_X * 2);
|
|
4139
|
+
const lineCount = nameLines.length + titleLines.length + subtitleLines.length + (vacantMarker !== null ? 1 : 0);
|
|
4140
|
+
const boxH = ORG_BOX_PAD_Y * 2 + lineCount * ORG_LABEL_LINE_H;
|
|
4141
|
+
return { boxW, boxH, nameLines, titleLines, subtitleLines, vacantMarker };
|
|
4142
|
+
}
|
|
4143
|
+
function positionTitle(position, vacantWord) {
|
|
4144
|
+
if (position.hover !== void 0) return position.hover;
|
|
4145
|
+
if (position.title !== null) {
|
|
4146
|
+
return position.name === "" ? position.title : `${position.name} \u2014 ${position.title}`;
|
|
4147
|
+
}
|
|
4148
|
+
if (position.name !== "") return position.name;
|
|
4149
|
+
return vacantWord;
|
|
4150
|
+
}
|
|
4151
|
+
function computeOrgChartLayout(input, opts = {}) {
|
|
4152
|
+
if (input.positions.length === 0 && input.reports.length === 0) {
|
|
4153
|
+
return { width: PADDING7 * 2, height: PADDING7 * 2, nodes: [], elements: [], rootPositionIds: [] };
|
|
4154
|
+
}
|
|
4155
|
+
validateOrgChart(input);
|
|
4156
|
+
const titleLabels = opts.titleLabels ?? ORG_CHART_TITLE_LABELS_EN;
|
|
4157
|
+
const vacantWord = titleLabels.vacant;
|
|
4158
|
+
const positionById = new Map(input.positions.map((p) => [p.id, p]));
|
|
4159
|
+
const reports = [...input.reports].sort((a, b) => a.id - b.id);
|
|
4160
|
+
const solidReports = reports.filter((r) => r.kind !== "dotted");
|
|
4161
|
+
const lineChildIds = /* @__PURE__ */ new Map();
|
|
4162
|
+
const assistChildIds = /* @__PURE__ */ new Map();
|
|
4163
|
+
const solidParentReportId = /* @__PURE__ */ new Set();
|
|
4164
|
+
for (const r of solidReports) {
|
|
4165
|
+
solidParentReportId.add(r.reportId);
|
|
4166
|
+
const bucket = r.kind === "assistant" ? assistChildIds : lineChildIds;
|
|
4167
|
+
const arr = bucket.get(r.managerId) ?? [];
|
|
4168
|
+
arr.push(r);
|
|
4169
|
+
bucket.set(r.managerId, arr);
|
|
4170
|
+
}
|
|
4171
|
+
for (const arr of lineChildIds.values()) arr.sort((a, b) => a.reportId - b.reportId);
|
|
4172
|
+
for (const arr of assistChildIds.values()) arr.sort((a, b) => a.reportId - b.reportId);
|
|
4173
|
+
const rootPositionIds = input.positions.map((p) => p.id).filter((id) => !solidParentReportId.has(id)).sort((a, b) => a - b);
|
|
4174
|
+
const newInst = (position, depth, assistReport) => {
|
|
4175
|
+
const m = measurePosition(position, opts.maxLabelChars, vacantWord);
|
|
4176
|
+
return {
|
|
4177
|
+
position,
|
|
4178
|
+
m,
|
|
4179
|
+
style: position.vacancy === "vacant" ? "dashed" : "solid",
|
|
4180
|
+
title: positionTitle(position, vacantWord),
|
|
4181
|
+
depth,
|
|
4182
|
+
lineChildren: [],
|
|
4183
|
+
assistants: [],
|
|
4184
|
+
ownHalfL: m.boxW / 2,
|
|
4185
|
+
ownHalfR: m.boxW / 2,
|
|
4186
|
+
spanL: 0,
|
|
4187
|
+
spanR: 0,
|
|
4188
|
+
offsets: [],
|
|
4189
|
+
cx: 0,
|
|
4190
|
+
assistReport,
|
|
4191
|
+
assistTop: 0
|
|
4192
|
+
};
|
|
4193
|
+
};
|
|
4194
|
+
const allInsts = [];
|
|
4195
|
+
const build = (positionId, depth, assistReport) => {
|
|
4196
|
+
const position = positionById.get(positionId);
|
|
4197
|
+
const inst = newInst(position, depth, assistReport);
|
|
4198
|
+
allInsts.push(inst);
|
|
4199
|
+
for (const r of assistChildIds.get(positionId) ?? []) {
|
|
4200
|
+
const a = build(r.reportId, depth + 1, r);
|
|
4201
|
+
inst.assistants.push({ inst: a, report: r });
|
|
4202
|
+
}
|
|
4203
|
+
for (const r of lineChildIds.get(positionId) ?? []) {
|
|
4204
|
+
inst.lineChildren.push(build(r.reportId, depth + 1, null));
|
|
4205
|
+
}
|
|
4206
|
+
return inst;
|
|
4207
|
+
};
|
|
4208
|
+
const roots = rootPositionIds.map((id) => build(id, 0, null));
|
|
4209
|
+
for (const inst of allInsts) {
|
|
4210
|
+
if (inst.assistants.length > 0) {
|
|
4211
|
+
const widest = inst.assistants.reduce((m, a) => Math.max(m, a.inst.m.boxW), 0);
|
|
4212
|
+
inst.ownHalfL = inst.m.boxW / 2 + ORG_ASSIST_GAP + widest;
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
const gaps = { siblingGap: ORG_SIBLING_GAP };
|
|
4216
|
+
const pack = (inst) => {
|
|
4217
|
+
for (const c of inst.lineChildren) pack(c);
|
|
4218
|
+
const result = packSubtree(
|
|
4219
|
+
{
|
|
4220
|
+
ownHalfL: inst.ownHalfL,
|
|
4221
|
+
ownHalfR: inst.ownHalfR,
|
|
4222
|
+
children: inst.lineChildren.map((c) => ({ halfL: c.spanL, halfR: c.spanR }))
|
|
4223
|
+
},
|
|
4224
|
+
gaps
|
|
4225
|
+
);
|
|
4226
|
+
inst.spanL = result.halfL;
|
|
4227
|
+
inst.spanR = result.halfR;
|
|
4228
|
+
inst.offsets = result.offsets;
|
|
4229
|
+
};
|
|
4230
|
+
for (const root of roots) pack(root);
|
|
4231
|
+
let cursor = PADDING7;
|
|
4232
|
+
for (const root of roots) {
|
|
4233
|
+
root.cx = cursor + root.spanL;
|
|
4234
|
+
const placeX = (inst) => {
|
|
4235
|
+
inst.lineChildren.forEach((c, i) => {
|
|
4236
|
+
c.cx = inst.cx + inst.offsets[i];
|
|
4237
|
+
placeX(c);
|
|
4238
|
+
});
|
|
4239
|
+
};
|
|
4240
|
+
placeX(root);
|
|
4241
|
+
cursor = root.cx + root.spanR + ORG_TREE_GAP;
|
|
4242
|
+
}
|
|
4243
|
+
const canvasRight = roots.length === 0 ? PADDING7 : cursor - ORG_TREE_GAP;
|
|
4244
|
+
const rowInsts = [];
|
|
4245
|
+
for (const inst of allInsts) (rowInsts[inst.depth] ??= []).push(inst);
|
|
4246
|
+
const depthCount = rowInsts.length;
|
|
4247
|
+
const assistStackH = (inst) => {
|
|
4248
|
+
if (inst.assistants.length === 0) return 0;
|
|
4249
|
+
return inst.assistants.reduce((s, a) => s + a.inst.m.boxH + ORG_ASSIST_BAND_DROP, 0) + ORG_ASSIST_BAND_DROP;
|
|
4250
|
+
};
|
|
4251
|
+
const rowH = [];
|
|
4252
|
+
for (let d = 0; d < depthCount; d++) {
|
|
4253
|
+
const lineBoxes = (rowInsts[d] ?? []).filter((i) => i.assistReport === null);
|
|
4254
|
+
rowH.push(lineBoxes.reduce((m, i) => Math.max(m, i.m.boxH), 0));
|
|
4255
|
+
}
|
|
4256
|
+
const assistZone = [];
|
|
4257
|
+
for (let d = 0; d < depthCount; d++) {
|
|
4258
|
+
let tallest = 0;
|
|
4259
|
+
for (const inst of rowInsts[d] ?? []) tallest = Math.max(tallest, assistStackH(inst));
|
|
4260
|
+
assistZone.push(tallest);
|
|
4261
|
+
}
|
|
4262
|
+
const rowTop = [PADDING7];
|
|
4263
|
+
for (let d = 0; d < depthCount - 1; d++) {
|
|
4264
|
+
rowTop.push(rowTop[d] + rowH[d] + assistZone[d] + ORG_CORRIDOR);
|
|
4265
|
+
}
|
|
4266
|
+
const busY = (d) => rowTop[d + 1] - ORG_CORRIDOR / 2;
|
|
4267
|
+
const last = depthCount - 1;
|
|
4268
|
+
const height = Math.ceil(rowTop[last] + rowH[last] + assistZone[last] + PADDING7);
|
|
4269
|
+
const nodes = [];
|
|
4270
|
+
const elements = [];
|
|
4271
|
+
const reportTitle = (kind, label) => {
|
|
4272
|
+
const word = titleLabels.reportKinds[kind];
|
|
4273
|
+
return label !== null && label !== void 0 ? `${word} \xB7 ${label}` : word;
|
|
4274
|
+
};
|
|
4275
|
+
const pushNode = (inst) => {
|
|
4276
|
+
nodes.push({
|
|
4277
|
+
positionId: inst.position.id,
|
|
4278
|
+
cx: round9(inst.cx),
|
|
4279
|
+
top: round9(rowTop[inst.depth]),
|
|
4280
|
+
boxW: round9(inst.m.boxW),
|
|
4281
|
+
boxH: round9(inst.m.boxH),
|
|
4282
|
+
style: inst.style,
|
|
4283
|
+
nameLines: inst.m.nameLines,
|
|
4284
|
+
titleLines: inst.m.titleLines,
|
|
4285
|
+
subtitleLines: inst.m.subtitleLines,
|
|
4286
|
+
vacantMarker: inst.m.vacantMarker,
|
|
4287
|
+
isAssistant: inst.assistReport !== null,
|
|
4288
|
+
depth: inst.depth,
|
|
4289
|
+
title: inst.title
|
|
4290
|
+
});
|
|
4291
|
+
};
|
|
4292
|
+
const emit = (inst) => {
|
|
4293
|
+
const d = inst.depth;
|
|
4294
|
+
let bandY = rowTop[d] + rowH[d] + ORG_ASSIST_BAND_DROP;
|
|
4295
|
+
for (const { inst: a, report } of inst.assistants) {
|
|
4296
|
+
const assistRight = inst.cx - ORG_ASSIST_GAP;
|
|
4297
|
+
a.cx = assistRight - a.m.boxW / 2;
|
|
4298
|
+
const assistTop = bandY;
|
|
4299
|
+
a.assistTop = assistTop;
|
|
4300
|
+
const assistMidY = assistTop + a.m.boxH / 2;
|
|
4301
|
+
nodes.push({
|
|
4302
|
+
positionId: a.position.id,
|
|
4303
|
+
cx: round9(a.cx),
|
|
4304
|
+
top: round9(assistTop),
|
|
4305
|
+
boxW: round9(a.m.boxW),
|
|
4306
|
+
boxH: round9(a.m.boxH),
|
|
4307
|
+
style: a.style,
|
|
4308
|
+
nameLines: a.m.nameLines,
|
|
4309
|
+
titleLines: a.m.titleLines,
|
|
4310
|
+
subtitleLines: a.m.subtitleLines,
|
|
4311
|
+
vacantMarker: a.m.vacantMarker,
|
|
4312
|
+
isAssistant: true,
|
|
4313
|
+
depth: a.depth,
|
|
4314
|
+
title: a.title
|
|
4315
|
+
});
|
|
4316
|
+
elements.push({
|
|
4317
|
+
edgeId: ORG_ASSIST_ID_BASE + report.reportId,
|
|
4318
|
+
kind: "assist",
|
|
4319
|
+
points: [
|
|
4320
|
+
{ x: round9(inst.cx), y: round9(assistMidY) },
|
|
4321
|
+
{ x: drawnRightEdge(a.cx, a.m.boxW), y: round9(assistMidY) }
|
|
4322
|
+
],
|
|
4323
|
+
dashed: false,
|
|
4324
|
+
title: reportTitle("assistant", report.label)
|
|
4325
|
+
});
|
|
4326
|
+
bandY += a.m.boxH + ORG_ASSIST_BAND_DROP;
|
|
4327
|
+
}
|
|
4328
|
+
pushNode(inst);
|
|
4329
|
+
if (inst.lineChildren.length === 0) return;
|
|
4330
|
+
const by = busY(d);
|
|
4331
|
+
const boxBottom = rowTop[d] + inst.m.boxH;
|
|
4332
|
+
elements.push({
|
|
4333
|
+
edgeId: ORG_STEM_ID_BASE + inst.position.id,
|
|
4334
|
+
kind: "stem",
|
|
4335
|
+
points: [
|
|
4336
|
+
{ x: round9(inst.cx), y: round9(boxBottom) },
|
|
4337
|
+
{ x: round9(inst.cx), y: round9(by) }
|
|
4338
|
+
],
|
|
4339
|
+
dashed: false,
|
|
4340
|
+
title: reportTitle("line", null)
|
|
4341
|
+
});
|
|
4342
|
+
const childCxs = inst.lineChildren.map((c) => c.cx);
|
|
4343
|
+
if (inst.lineChildren.length > 1) {
|
|
4344
|
+
elements.push({
|
|
4345
|
+
edgeId: ORG_BUS_ID_BASE + inst.position.id,
|
|
4346
|
+
kind: "bus",
|
|
4347
|
+
points: [
|
|
4348
|
+
{ x: round9(Math.min(...childCxs)), y: round9(by) },
|
|
4349
|
+
{ x: round9(Math.max(...childCxs)), y: round9(by) }
|
|
4350
|
+
],
|
|
4351
|
+
dashed: false,
|
|
4352
|
+
title: reportTitle("line", null)
|
|
4353
|
+
});
|
|
4354
|
+
}
|
|
4355
|
+
for (const c of inst.lineChildren) {
|
|
4356
|
+
elements.push({
|
|
4357
|
+
edgeId: ORG_DROP_ID_BASE + c.position.id,
|
|
4358
|
+
kind: "drop",
|
|
4359
|
+
points: [
|
|
4360
|
+
{ x: round9(c.cx), y: round9(by) },
|
|
4361
|
+
{ x: round9(c.cx), y: round9(rowTop[c.depth]) }
|
|
4362
|
+
],
|
|
4363
|
+
dashed: false,
|
|
4364
|
+
title: reportTitle("line", null)
|
|
4365
|
+
});
|
|
4366
|
+
}
|
|
4367
|
+
for (const c of inst.lineChildren) emit(c);
|
|
4368
|
+
};
|
|
4369
|
+
for (const root of roots) emit(root);
|
|
4370
|
+
const instByPos = new Map(allInsts.map((i) => [i.position.id, i]));
|
|
4371
|
+
const escapeByPos = /* @__PURE__ */ new Map();
|
|
4372
|
+
for (const inst of allInsts) {
|
|
4373
|
+
const isAssist = inst.assistReport !== null;
|
|
4374
|
+
const boxTop = isAssist ? inst.assistTop : rowTop[inst.depth];
|
|
4375
|
+
const leftEdge = drawnLeftEdge(inst.cx, inst.m.boxW);
|
|
4376
|
+
const rightEdge = drawnRightEdge(inst.cx, inst.m.boxW);
|
|
4377
|
+
if (isAssist) {
|
|
4378
|
+
const managerInst = instByPos.get(inst.assistReport.managerId);
|
|
4379
|
+
escapeByPos.set(inst.position.id, {
|
|
4380
|
+
side: -1,
|
|
4381
|
+
boxEdge: leftEdge,
|
|
4382
|
+
escCol: managerInst.cx - managerInst.spanL,
|
|
4383
|
+
midY: boxTop + inst.m.boxH / 2,
|
|
4384
|
+
boxH: inst.m.boxH
|
|
4385
|
+
});
|
|
4386
|
+
} else {
|
|
4387
|
+
escapeByPos.set(inst.position.id, {
|
|
4388
|
+
side: 1,
|
|
4389
|
+
boxEdge: rightEdge,
|
|
4390
|
+
escCol: inst.cx + inst.spanR,
|
|
4391
|
+
midY: boxTop + inst.m.boxH / 2,
|
|
4392
|
+
boxH: inst.m.boxH
|
|
4393
|
+
});
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
const nodeById = new Map(nodes.map((n) => [n.positionId, n]));
|
|
4397
|
+
const dottedReports = reports.filter(
|
|
4398
|
+
(r) => r.kind === "dotted" && nodeById.has(r.managerId) && nodeById.has(r.reportId) && r.managerId !== r.reportId
|
|
4399
|
+
);
|
|
4400
|
+
let maxBoxBottom = PADDING7;
|
|
4401
|
+
for (const n of nodes) maxBoxBottom = Math.max(maxBoxBottom, n.top + n.boxH);
|
|
4402
|
+
let laneCount = 0;
|
|
4403
|
+
let channelLanes = 0;
|
|
4404
|
+
if (dottedReports.length > 0) {
|
|
4405
|
+
const laneOf = /* @__PURE__ */ new Map();
|
|
4406
|
+
laneCount = allocateLanes2(
|
|
4407
|
+
dottedReports.map((r) => {
|
|
4408
|
+
const rep = escapeByPos.get(r.reportId);
|
|
4409
|
+
const mgr = escapeByPos.get(r.managerId);
|
|
4410
|
+
return {
|
|
4411
|
+
lo: Math.min(rep.midY, mgr.midY),
|
|
4412
|
+
hi: Math.max(rep.midY, mgr.midY),
|
|
4413
|
+
set: (lane) => laneOf.set(r.id, lane)
|
|
4414
|
+
};
|
|
4415
|
+
})
|
|
4416
|
+
);
|
|
4417
|
+
channelLanes = dottedReports.length * 2;
|
|
4418
|
+
const channelTop = maxBoxBottom + ORG_MATRIX_GUTTER_GAP;
|
|
4419
|
+
const exitSlot = /* @__PURE__ */ new Map();
|
|
4420
|
+
const nextExitY = (posId, mid, boxH) => {
|
|
4421
|
+
const slot = exitSlot.get(posId) ?? 0;
|
|
4422
|
+
exitSlot.set(posId, slot + 1);
|
|
4423
|
+
if (slot === 0) return mid;
|
|
4424
|
+
const step = Math.min(6, Math.max(2, (boxH / 2 - 6) / Math.max(1, Math.ceil(slot / 2))));
|
|
4425
|
+
const sign = slot % 2 === 1 ? -1 : 1;
|
|
4426
|
+
return mid + sign * Math.ceil(slot / 2) * step;
|
|
4427
|
+
};
|
|
4428
|
+
const geoms = dottedReports.map((r, i) => {
|
|
4429
|
+
const rep = escapeByPos.get(r.reportId);
|
|
4430
|
+
const mgr = escapeByPos.get(r.managerId);
|
|
4431
|
+
return {
|
|
4432
|
+
r,
|
|
4433
|
+
i,
|
|
4434
|
+
repMidY: round9(nextExitY(r.reportId, rep.midY, rep.boxH)),
|
|
4435
|
+
mgrMidY: round9(nextExitY(r.managerId, mgr.midY, mgr.boxH)),
|
|
4436
|
+
repBoxEdge: round9(rep.boxEdge),
|
|
4437
|
+
mgrBoxEdge: round9(mgr.boxEdge),
|
|
4438
|
+
repChY: round9(channelTop + 2 * i * ORG_MATRIX_LANE_PITCH),
|
|
4439
|
+
mgrChY: round9(channelTop + (2 * i + 1) * ORG_MATRIX_LANE_PITCH),
|
|
4440
|
+
laneX: round9(canvasRight + ORG_MATRIX_GUTTER_GAP + laneOf.get(r.id) * ORG_MATRIX_LANE_PITCH),
|
|
4441
|
+
repEscX: 0,
|
|
4442
|
+
mgrEscX: 0
|
|
4443
|
+
};
|
|
4444
|
+
});
|
|
4445
|
+
const verts = [];
|
|
4446
|
+
for (const g of geoms) {
|
|
4447
|
+
const rep = escapeByPos.get(g.r.reportId);
|
|
4448
|
+
const mgr = escapeByPos.get(g.r.managerId);
|
|
4449
|
+
verts.push({
|
|
4450
|
+
escCol: rep.escCol,
|
|
4451
|
+
side: rep.side,
|
|
4452
|
+
lo: Math.min(g.repMidY, g.repChY),
|
|
4453
|
+
hi: Math.max(g.repMidY, g.repChY),
|
|
4454
|
+
assign: (x) => g.repEscX = x
|
|
4455
|
+
});
|
|
4456
|
+
verts.push({
|
|
4457
|
+
escCol: mgr.escCol,
|
|
4458
|
+
side: mgr.side,
|
|
4459
|
+
lo: Math.min(g.mgrMidY, g.mgrChY),
|
|
4460
|
+
hi: Math.max(g.mgrMidY, g.mgrChY),
|
|
4461
|
+
assign: (x) => g.mgrEscX = x
|
|
4462
|
+
});
|
|
4463
|
+
}
|
|
4464
|
+
const VERT_EPS = 0.01;
|
|
4465
|
+
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);
|
|
4466
|
+
const placed = [];
|
|
4467
|
+
const yOverlap = (aLo, aHi, bLo, bHi) => Math.min(aHi, bHi) - Math.max(aLo, bLo) > VERT_EPS;
|
|
4468
|
+
for (const v of ordered) {
|
|
4469
|
+
let band = v.side === 1 ? Math.max(canvasRight - v.escCol, ORG_MATRIX_GUTTER_GAP) : v.escCol;
|
|
4470
|
+
for (const n of nodes) {
|
|
4471
|
+
const bLeft = n.cx - n.boxW / 2;
|
|
4472
|
+
const bRight = n.cx + n.boxW / 2;
|
|
4473
|
+
if (n.top + n.boxH <= v.lo + VERT_EPS || n.top >= v.hi - VERT_EPS) continue;
|
|
4474
|
+
if (v.side === 1 && bLeft > v.escCol + VERT_EPS) band = Math.min(band, bLeft - v.escCol);
|
|
4475
|
+
else if (v.side === -1 && bRight < v.escCol - VERT_EPS) band = Math.min(band, v.escCol - bRight);
|
|
4476
|
+
}
|
|
4477
|
+
let lane = 0;
|
|
4478
|
+
let candidate = round9(v.escCol);
|
|
4479
|
+
while (placed.some((p) => Math.abs(p.x - candidate) < VERT_EPS && yOverlap(p.lo, p.hi, v.lo, v.hi))) {
|
|
4480
|
+
lane += 1;
|
|
4481
|
+
candidate = round9(v.escCol + v.side * lane * ORG_MATRIX_ESCAPE_STEP);
|
|
4482
|
+
}
|
|
4483
|
+
if (lane > 0 && lane * ORG_MATRIX_ESCAPE_STEP >= band - VERT_EPS) {
|
|
4484
|
+
throw new Error(
|
|
4485
|
+
`org-chart: escape-column-overflow \u2014 escape vertical nudged ${lane * ORG_MATRIX_ESCAPE_STEP}px from its base column exceeds the ${round9(band)}px box-free band`
|
|
4486
|
+
);
|
|
4487
|
+
}
|
|
4488
|
+
placed.push({ x: candidate, lo: v.lo, hi: v.hi });
|
|
4489
|
+
v.assign(candidate);
|
|
4490
|
+
}
|
|
4491
|
+
for (const g of geoms) {
|
|
4492
|
+
elements.push({
|
|
4493
|
+
edgeId: ORG_DOTTED_ID_BASE + g.r.id,
|
|
4494
|
+
kind: "dotted",
|
|
4495
|
+
points: collapseDegenerate([
|
|
4496
|
+
{ x: g.repBoxEdge, y: g.repMidY },
|
|
4497
|
+
// out the report box's edge
|
|
4498
|
+
{ x: g.repEscX, y: g.repMidY },
|
|
4499
|
+
// → box-free escape column (in own gutter lane)
|
|
4500
|
+
{ x: g.repEscX, y: g.repChY },
|
|
4501
|
+
// → down into the bottom traverse channel
|
|
4502
|
+
{ x: g.laneX, y: g.repChY },
|
|
4503
|
+
// → across (below all boxes) to the right gutter lane
|
|
4504
|
+
{ x: g.laneX, y: g.mgrChY },
|
|
4505
|
+
// → down the right gutter (right of every box)
|
|
4506
|
+
{ x: g.mgrEscX, y: g.mgrChY },
|
|
4507
|
+
// → back across (below all boxes) to the manager column
|
|
4508
|
+
{ x: g.mgrEscX, y: g.mgrMidY },
|
|
4509
|
+
// → up the box-free escape column
|
|
4510
|
+
{ x: g.mgrBoxEdge, y: g.mgrMidY }
|
|
4511
|
+
// → into the manager box's edge
|
|
4512
|
+
]),
|
|
4513
|
+
dashed: true,
|
|
4514
|
+
title: reportTitle("dotted", g.r.label)
|
|
4515
|
+
});
|
|
4516
|
+
}
|
|
4517
|
+
}
|
|
4518
|
+
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);
|
|
4519
|
+
const finalHeight = channelLanes > 0 ? Math.ceil(maxBoxBottom + ORG_MATRIX_GUTTER_GAP + (channelLanes - 1) * ORG_MATRIX_LANE_PITCH + PADDING7) : height;
|
|
4520
|
+
return { width, height: finalHeight, nodes, elements, rootPositionIds };
|
|
4521
|
+
}
|
|
4522
|
+
function collapseDegenerate(points) {
|
|
4523
|
+
const out = [];
|
|
4524
|
+
for (const p of points) {
|
|
4525
|
+
const prev = out[out.length - 1];
|
|
4526
|
+
if (prev === void 0 || prev.x !== p.x || prev.y !== p.y) out.push(p);
|
|
4527
|
+
}
|
|
4528
|
+
return out;
|
|
4529
|
+
}
|
|
4530
|
+
function allocateLanes2(items) {
|
|
4531
|
+
const lanes = [];
|
|
4532
|
+
for (const it of items) {
|
|
4533
|
+
let chosen = -1;
|
|
4534
|
+
for (let l = 0; l < lanes.length; l++) {
|
|
4535
|
+
if (lanes[l].every((o) => it.hi <= o.lo || it.lo >= o.hi)) {
|
|
4536
|
+
chosen = l;
|
|
4537
|
+
break;
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
if (chosen === -1) {
|
|
4541
|
+
chosen = lanes.length;
|
|
4542
|
+
lanes.push([]);
|
|
4543
|
+
}
|
|
4544
|
+
lanes[chosen].push({ lo: it.lo, hi: it.hi });
|
|
4545
|
+
it.set(chosen);
|
|
4546
|
+
}
|
|
4547
|
+
return lanes.length;
|
|
4548
|
+
}
|
|
4549
|
+
|
|
4550
|
+
// src/org-chart/svg.ts
|
|
4551
|
+
var GLYPH_STROKE5 = "#52525b";
|
|
4552
|
+
var LABEL_FILL6 = "#3f3f46";
|
|
4553
|
+
var EDGE_INK7 = "#71717a";
|
|
4554
|
+
var GLYPH_ATTRS3 = `fill="transparent" stroke="${GLYPH_STROKE5}" stroke-width="2"`;
|
|
4555
|
+
var VACANT_DASH = `stroke-dasharray="6,4"`;
|
|
4556
|
+
var DOTTED = EDGE_STROKE.distant;
|
|
4557
|
+
var round10 = (n) => Math.round(n * 100) / 100;
|
|
4558
|
+
var ORG_BOX_PAD_Y2 = 9;
|
|
4559
|
+
function boxSvg(n) {
|
|
4560
|
+
const left = round10(n.cx - n.boxW / 2);
|
|
4561
|
+
const pieces = [`<title>${xmlEscape(n.title)}</title>`];
|
|
4562
|
+
if (n.style === "dashed") {
|
|
4563
|
+
pieces.push(
|
|
4564
|
+
`<rect x="${left}" y="${n.top}" width="${n.boxW}" height="${n.boxH}" rx="2" ${GLYPH_ATTRS3} ${VACANT_DASH}/>`
|
|
4565
|
+
);
|
|
4566
|
+
const ix = round10(left + 3);
|
|
4567
|
+
const iy = round10(n.top + 3);
|
|
4568
|
+
const iw = round10(n.boxW - 6);
|
|
4569
|
+
const ih = round10(n.boxH - 6);
|
|
4570
|
+
pieces.push(`<rect x="${ix}" y="${iy}" width="${iw}" height="${ih}" rx="2" ${GLYPH_ATTRS3} ${VACANT_DASH}/>`);
|
|
4571
|
+
} else {
|
|
4572
|
+
pieces.push(`<rect x="${left}" y="${n.top}" width="${n.boxW}" height="${n.boxH}" rx="2" ${GLYPH_ATTRS3}/>`);
|
|
4573
|
+
}
|
|
4574
|
+
let lineIndex = 0;
|
|
4575
|
+
const firstBaseline = (i) => round10(n.top + ORG_BOX_PAD_Y2 + ORG_LABEL_LINE_H / 2 + i * ORG_LABEL_LINE_H + ORG_LABEL_FONT * 0.32);
|
|
4576
|
+
const textBlock = (lines, font) => {
|
|
4577
|
+
if (lines.length === 0) return "";
|
|
4578
|
+
const tspans = lines.map((line, i) => `<tspan x="${n.cx}" y="${firstBaseline(lineIndex + i)}">${xmlEscape(line)}</tspan>`).join("");
|
|
4579
|
+
lineIndex += lines.length;
|
|
4580
|
+
return `<text text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${font}" fill="${LABEL_FILL6}">${tspans}</text>`;
|
|
4581
|
+
};
|
|
4582
|
+
pieces.push(textBlock(n.nameLines, ORG_LABEL_FONT));
|
|
4583
|
+
pieces.push(textBlock(n.titleLines, ORG_TITLE_FONT));
|
|
4584
|
+
pieces.push(textBlock(n.subtitleLines, ORG_TITLE_FONT));
|
|
4585
|
+
if (n.vacantMarker !== null) pieces.push(textBlock([n.vacantMarker], ORG_TITLE_FONT));
|
|
4586
|
+
return `<g data-node-id="p${n.positionId}">${pieces.filter((p) => p !== "").join("")}</g>`;
|
|
4587
|
+
}
|
|
4588
|
+
function elementSvg5(el) {
|
|
4589
|
+
const head = `<g data-edge-id="${el.edgeId}"><title>${xmlEscape(el.title)}</title>`;
|
|
4590
|
+
if (el.dashed) {
|
|
4591
|
+
const dash = DOTTED.dash === null ? "" : ` stroke-dasharray="${DOTTED.dash[0]},${DOTTED.dash[1]}"`;
|
|
4592
|
+
return head + `<path d="${pathData(el.points)}" fill="none" stroke="${EDGE_INK7}" stroke-width="${DOTTED.width}" stroke-opacity="${DOTTED.opacity}"${dash}/></g>`;
|
|
4593
|
+
}
|
|
4594
|
+
if (el.points.length === 2) {
|
|
4595
|
+
const a = el.points[0];
|
|
4596
|
+
const b = el.points[1];
|
|
4597
|
+
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"/></g>`;
|
|
4598
|
+
}
|
|
4599
|
+
return head + `<path d="${pathData(el.points)}" fill="none" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/></g>`;
|
|
4600
|
+
}
|
|
4601
|
+
var MINI_ATTRS3 = `fill="transparent" stroke="${GLYPH_STROKE5}" stroke-width="1.5"`;
|
|
4602
|
+
function lineSwatch(x, y) {
|
|
4603
|
+
const x1 = round10(x + 2);
|
|
4604
|
+
const x2 = round10(x + LEGEND_SWATCH_W - 2);
|
|
4605
|
+
return `<line x1="${x1}" y1="${y}" x2="${x2}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/>`;
|
|
4606
|
+
}
|
|
4607
|
+
function assistantSwatch(x, y) {
|
|
4608
|
+
const stemX = round10(x + LEGEND_SWATCH_W - 4);
|
|
4609
|
+
const boxX = round10(x + 1);
|
|
4610
|
+
return `<line x1="${stemX}" y1="${round10(y - 4)}" x2="${stemX}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/><line x1="${stemX}" y1="${y}" x2="${round10(boxX + 8)}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/><rect x="${boxX}" y="${round10(y - 3.5)}" width="8" height="7" rx="1" ${MINI_ATTRS3}/>`;
|
|
4611
|
+
}
|
|
4612
|
+
function dottedSwatch(x, y) {
|
|
4613
|
+
const x1 = round10(x + 2);
|
|
4614
|
+
const x2 = round10(x + LEGEND_SWATCH_W - 2);
|
|
4615
|
+
const dash = DOTTED.dash === null ? "" : ` stroke-dasharray="${DOTTED.dash[0]},${DOTTED.dash[1]}"`;
|
|
4616
|
+
return `<line x1="${x1}" y1="${y}" x2="${x2}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="${DOTTED.width}" stroke-opacity="${DOTTED.opacity}"${dash}/>`;
|
|
4617
|
+
}
|
|
4618
|
+
function vacantSwatch(x, y) {
|
|
4619
|
+
const cx = round10(x + LEGEND_SWATCH_W / 2);
|
|
4620
|
+
const bx = round10(cx - 8);
|
|
4621
|
+
const by = round10(y - 5);
|
|
4622
|
+
return `<rect x="${bx}" y="${by}" width="16" height="10" rx="1" ${MINI_ATTRS3} stroke-dasharray="3,2"/><rect x="${round10(bx + 2)}" y="${round10(by + 2)}" width="12" height="6" rx="1" ${MINI_ATTRS3} stroke-dasharray="3,2"/>`;
|
|
4623
|
+
}
|
|
4624
|
+
function orgChartLayoutSvg(layout, opts = {}) {
|
|
4625
|
+
const labels = opts.labels ?? ORG_CHART_SVG_LABELS_EN;
|
|
4626
|
+
const parts = [];
|
|
4627
|
+
for (const el of layout.elements) parts.push(elementSvg5(el));
|
|
4628
|
+
for (const n of layout.nodes) parts.push(boxSvg(n));
|
|
4629
|
+
let width = layout.width;
|
|
4630
|
+
let height = layout.height;
|
|
4631
|
+
if (opts.legend !== false && layout.nodes.length > 0) {
|
|
4632
|
+
const hasSolid = layout.elements.some((e) => e.kind === "stem" || e.kind === "bus" || e.kind === "drop");
|
|
4633
|
+
const hasAssistant = layout.nodes.some((n) => n.isAssistant);
|
|
4634
|
+
const hasDotted = layout.elements.some((e) => e.kind === "dotted");
|
|
4635
|
+
const hasVacant = layout.nodes.some((n) => n.style === "dashed");
|
|
4636
|
+
const entries = [];
|
|
4637
|
+
if (hasSolid) entries.push({ swatch: lineSwatch, label: labels.legend.line });
|
|
4638
|
+
if (hasAssistant) entries.push({ swatch: assistantSwatch, label: labels.legend.assistant });
|
|
4639
|
+
if (hasDotted) entries.push({ swatch: dottedSwatch, label: labels.legend.dotted });
|
|
4640
|
+
if (hasVacant) entries.push({ swatch: vacantSwatch, label: labels.legend.vacant });
|
|
4641
|
+
const block = legendBlock(entries, layout.height);
|
|
4642
|
+
if (block.svg !== "") {
|
|
4643
|
+
parts.push(block.svg);
|
|
4644
|
+
width = Math.max(width, block.width);
|
|
4645
|
+
height = block.height;
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
const w = Math.ceil(width);
|
|
4649
|
+
const h = Math.ceil(height);
|
|
4650
|
+
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>`;
|
|
4651
|
+
}
|
|
4652
|
+
|
|
4653
|
+
// src/org-chart/render.ts
|
|
4654
|
+
function orgChartSvg(input, opts = {}) {
|
|
4655
|
+
const layout = computeOrgChartLayout(input, {
|
|
4656
|
+
...opts.maxLabelChars !== void 0 ? { maxLabelChars: opts.maxLabelChars } : {},
|
|
4657
|
+
...opts.titleLabels !== void 0 ? { titleLabels: opts.titleLabels } : {}
|
|
4658
|
+
});
|
|
4659
|
+
const svg = orgChartLayoutSvg(layout, {
|
|
4660
|
+
...opts.legend === false ? { legend: false } : {},
|
|
4661
|
+
...opts.svgLabels !== void 0 ? { labels: opts.svgLabels } : {}
|
|
4662
|
+
});
|
|
4663
|
+
return { svg, layout };
|
|
4664
|
+
}
|
|
4665
|
+
|
|
3907
4666
|
exports.CHAR_W = CHAR_W;
|
|
3908
4667
|
exports.CODE_FONT = CODE_FONT;
|
|
3909
4668
|
exports.ECOMAP_LABELS_EN = ECOMAP_LABELS_EN;
|
|
@@ -3938,6 +4697,20 @@ exports.LEGEND_SWATCH_W = LEGEND_SWATCH_W;
|
|
|
3938
4697
|
exports.LIFE_STATUSES = LIFE_STATUSES;
|
|
3939
4698
|
exports.MAX_CONDITIONS_PER_INDIVIDUAL = MAX_CONDITIONS_PER_INDIVIDUAL;
|
|
3940
4699
|
exports.NODE_SIZE = NODE_SIZE;
|
|
4700
|
+
exports.ORG_ASSIST_ID_BASE = ORG_ASSIST_ID_BASE;
|
|
4701
|
+
exports.ORG_BUS_ID_BASE = ORG_BUS_ID_BASE;
|
|
4702
|
+
exports.ORG_CHART_SVG_LABELS_EN = ORG_CHART_SVG_LABELS_EN;
|
|
4703
|
+
exports.ORG_CHART_TITLE_LABELS_EN = ORG_CHART_TITLE_LABELS_EN;
|
|
4704
|
+
exports.ORG_DOTTED_ID_BASE = ORG_DOTTED_ID_BASE;
|
|
4705
|
+
exports.ORG_DROP_ID_BASE = ORG_DROP_ID_BASE;
|
|
4706
|
+
exports.ORG_LABEL_FONT = ORG_LABEL_FONT;
|
|
4707
|
+
exports.ORG_LABEL_LINE_H = ORG_LABEL_LINE_H;
|
|
4708
|
+
exports.ORG_MAX_MATRIX_EDGES_PER_NODE = ORG_MAX_MATRIX_EDGES_PER_NODE;
|
|
4709
|
+
exports.ORG_REPORT_KINDS = ORG_REPORT_KINDS;
|
|
4710
|
+
exports.ORG_STEM_ID_BASE = ORG_STEM_ID_BASE;
|
|
4711
|
+
exports.ORG_TITLE_FONT = ORG_TITLE_FONT;
|
|
4712
|
+
exports.ORG_VACANCIES = ORG_VACANCIES;
|
|
4713
|
+
exports.OrgChartValidationError = OrgChartValidationError;
|
|
3941
4714
|
exports.PARENT_REL_ID_BASE = PARENT_REL_ID_BASE;
|
|
3942
4715
|
exports.PEDIGREE_SVG_LABELS_EN = PEDIGREE_SVG_LABELS_EN;
|
|
3943
4716
|
exports.PEDIGREE_TITLE_LABELS_EN = PEDIGREE_TITLE_LABELS_EN;
|
|
@@ -3974,6 +4747,7 @@ exports.clampLabel = clampLabel;
|
|
|
3974
4747
|
exports.classifyRelationshipType = classifyRelationshipType;
|
|
3975
4748
|
exports.computeFaultTreeLayout = computeFaultTreeLayout;
|
|
3976
4749
|
exports.computeGenogramLayout = computeGenogramLayout;
|
|
4750
|
+
exports.computeOrgChartLayout = computeOrgChartLayout;
|
|
3977
4751
|
exports.computePedigreeLayout = computePedigreeLayout;
|
|
3978
4752
|
exports.computePhyloLayout = computePhyloLayout;
|
|
3979
4753
|
exports.ecomapSvg = ecomapSvg;
|
|
@@ -3988,6 +4762,10 @@ exports.latestUnionPerPair = latestUnionPerPair;
|
|
|
3988
4762
|
exports.legendBlock = legendBlock;
|
|
3989
4763
|
exports.niceScaleStep = niceScaleStep;
|
|
3990
4764
|
exports.normalizeText = normalizeText;
|
|
4765
|
+
exports.orgChartIssues = orgChartIssues;
|
|
4766
|
+
exports.orgChartLayoutSvg = orgChartLayoutSvg;
|
|
4767
|
+
exports.orgChartSvg = orgChartSvg;
|
|
4768
|
+
exports.packSubtree = packSubtree;
|
|
3991
4769
|
exports.pathData = pathData;
|
|
3992
4770
|
exports.pedigreeIssues = pedigreeIssues;
|
|
3993
4771
|
exports.pedigreeLayoutSvg = pedigreeLayoutSvg;
|
|
@@ -3999,6 +4777,7 @@ exports.qualityLineStyle = qualityLineStyle;
|
|
|
3999
4777
|
exports.relationshipTypeTokens = relationshipTypeTokens;
|
|
4000
4778
|
exports.romanNumeral = romanNumeral;
|
|
4001
4779
|
exports.validateFaultTree = validateFaultTree;
|
|
4780
|
+
exports.validateOrgChart = validateOrgChart;
|
|
4002
4781
|
exports.validatePedigree = validatePedigree;
|
|
4003
4782
|
exports.validatePhylo = validatePhylo;
|
|
4004
4783
|
exports.wrapLabel = wrapLabel;
|