@bpmn-ld/bpmn-ld 0.0.0-stage → 0.2.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/LICENSE +201 -0
- package/README.md +125 -2
- package/contexts/combined.jsonld +3 -0
- package/contexts/core.jsonld +170 -0
- package/contexts/di.jsonld +37 -0
- package/dist/analyze/analyze.d.ts +3 -0
- package/dist/analyze/analyze.js +11 -0
- package/dist/analyze/findings.d.ts +8 -0
- package/dist/analyze/findings.js +1 -0
- package/dist/analyze/report.d.ts +3 -0
- package/dist/analyze/report.js +9 -0
- package/dist/analyze/rules/ad-hoc-completion-condition.d.ts +2 -0
- package/dist/analyze/rules/ad-hoc-completion-condition.js +7 -0
- package/dist/analyze/rules/cannot-reach-end.d.ts +2 -0
- package/dist/analyze/rules/cannot-reach-end.js +28 -0
- package/dist/analyze/rules/compensation-association.d.ts +2 -0
- package/dist/analyze/rules/compensation-association.js +13 -0
- package/dist/analyze/rules/dead-end.d.ts +2 -0
- package/dist/analyze/rules/dead-end.js +13 -0
- package/dist/analyze/rules/end-event.d.ts +2 -0
- package/dist/analyze/rules/end-event.js +8 -0
- package/dist/analyze/rules/error-catch.d.ts +2 -0
- package/dist/analyze/rules/error-catch.js +14 -0
- package/dist/analyze/rules/escalation-catch.d.ts +2 -0
- package/dist/analyze/rules/escalation-catch.js +14 -0
- package/dist/analyze/rules/gateway-cardinality.d.ts +3 -0
- package/dist/analyze/rules/gateway-cardinality.js +17 -0
- package/dist/analyze/rules/index.d.ts +4 -0
- package/dist/analyze/rules/index.js +21 -0
- package/dist/analyze/rules/instantiating-gateway.d.ts +2 -0
- package/dist/analyze/rules/instantiating-gateway.js +14 -0
- package/dist/analyze/rules/non-interrupting-start.d.ts +2 -0
- package/dist/analyze/rules/non-interrupting-start.js +14 -0
- package/dist/analyze/rules/reachability.d.ts +2 -0
- package/dist/analyze/rules/reachability.js +25 -0
- package/dist/analyze/rules/rule.d.ts +7 -0
- package/dist/analyze/rules/rule.js +1 -0
- package/dist/analyze/rules/signal-catch.d.ts +2 -0
- package/dist/analyze/rules/signal-catch.js +12 -0
- package/dist/analyze/rules/split-join-pairing.d.ts +3 -0
- package/dist/analyze/rules/split-join-pairing.js +50 -0
- package/dist/analyze/rules/start-event.d.ts +2 -0
- package/dist/analyze/rules/start-event.js +8 -0
- package/dist/analyze/rules/task-message-flow.d.ts +2 -0
- package/dist/analyze/rules/task-message-flow.js +19 -0
- package/dist/analyze/scope-graph.d.ts +56 -0
- package/dist/analyze/scope-graph.js +89 -0
- package/dist/analyze-cli.d.ts +1 -0
- package/dist/analyze-cli.js +5 -0
- package/dist/analyze-run.d.ts +12 -0
- package/dist/analyze-run.js +85 -0
- package/dist/bpmn-ld-cli.d.ts +2 -0
- package/dist/bpmn-ld-cli.js +97 -0
- package/dist/build-cli.d.ts +1 -0
- package/dist/build-cli.js +2 -0
- package/dist/build.d.ts +3 -0
- package/dist/build.js +38 -0
- package/dist/cheat-sheet-catalog.d.ts +10 -0
- package/dist/cheat-sheet-catalog.js +72 -0
- package/dist/cheat-sheet-cli.d.ts +1 -0
- package/dist/cheat-sheet-cli.js +7 -0
- package/dist/cheat-sheet-compose.d.ts +13 -0
- package/dist/cheat-sheet-compose.js +107 -0
- package/dist/cheat-sheet-run.d.ts +11 -0
- package/dist/cheat-sheet-run.js +65 -0
- package/dist/cheat-sheet-svg.d.ts +2 -0
- package/dist/cheat-sheet-svg.js +2 -0
- package/dist/cheat-sheet.d.ts +2 -0
- package/dist/cheat-sheet.js +2 -0
- package/dist/check-xml/check.d.ts +3 -0
- package/dist/check-xml/check.js +11 -0
- package/dist/check-xml/lint.d.ts +2 -0
- package/dist/check-xml/lint.js +47 -0
- package/dist/check-xml/moddle.d.ts +6 -0
- package/dist/check-xml/moddle.js +23 -0
- package/dist/check-xml/types.d.ts +20 -0
- package/dist/check-xml/types.js +1 -0
- package/dist/check-xml/xsd.d.ts +2 -0
- package/dist/check-xml/xsd.js +25 -0
- package/dist/check-xml-cli.d.ts +1 -0
- package/dist/check-xml-cli.js +5 -0
- package/dist/check-xml-run.d.ts +10 -0
- package/dist/check-xml-run.js +77 -0
- package/dist/convert/analyze-output.d.ts +3 -0
- package/dist/convert/analyze-output.js +8 -0
- package/dist/convert/convert.d.ts +19 -0
- package/dist/convert/convert.js +96 -0
- package/dist/convert/errors.d.ts +5 -0
- package/dist/convert/errors.js +8 -0
- package/dist/convert/extensions.d.ts +23 -0
- package/dist/convert/extensions.js +103 -0
- package/dist/convert/mapping-check.d.ts +15 -0
- package/dist/convert/mapping-check.js +66 -0
- package/dist/convert/mapping.d.ts +52 -0
- package/dist/convert/mapping.js +273 -0
- package/dist/convert/namespaces.d.ts +2 -0
- package/dist/convert/namespaces.js +19 -0
- package/dist/convert/validate-output.d.ts +2 -0
- package/dist/convert/validate-output.js +28 -0
- package/dist/convert/warnings.d.ts +9 -0
- package/dist/convert/warnings.js +67 -0
- package/dist/convert/xml-reader.d.ts +11 -0
- package/dist/convert/xml-reader.js +84 -0
- package/dist/convert-cli.d.ts +1 -0
- package/dist/convert-cli.js +5 -0
- package/dist/convert-run.d.ts +10 -0
- package/dist/convert-run.js +125 -0
- package/dist/coverage-cli.d.ts +1 -0
- package/dist/coverage-cli.js +8 -0
- package/dist/coverage-mappings.d.ts +3 -0
- package/dist/coverage-mappings.js +34 -0
- package/dist/coverage-scope.d.ts +11 -0
- package/dist/coverage-scope.js +187 -0
- package/dist/coverage-xsd.d.ts +15 -0
- package/dist/coverage-xsd.js +55 -0
- package/dist/coverage.d.ts +35 -0
- package/dist/coverage.js +152 -0
- package/dist/docs-cli.d.ts +1 -0
- package/dist/docs-cli.js +18 -0
- package/dist/docs.d.ts +8 -0
- package/dist/docs.js +136 -0
- package/dist/eval/checks.d.ts +19 -0
- package/dist/eval/checks.js +252 -0
- package/dist/eval/regenerate.d.ts +10 -0
- package/dist/eval/regenerate.js +48 -0
- package/dist/eval-regenerate-cli.d.ts +1 -0
- package/dist/eval-regenerate-cli.js +16 -0
- package/dist/export/data.d.ts +10 -0
- package/dist/export/data.js +127 -0
- package/dist/export/diagram.d.ts +8 -0
- package/dist/export/diagram.js +112 -0
- package/dist/export/element.d.ts +27 -0
- package/dist/export/element.js +175 -0
- package/dist/export/export.d.ts +22 -0
- package/dist/export/export.js +124 -0
- package/dist/export/extensions.d.ts +18 -0
- package/dist/export/extensions.js +122 -0
- package/dist/export/layout-export.d.ts +4 -0
- package/dist/export/layout-export.js +25 -0
- package/dist/export/mapping-check.d.ts +39 -0
- package/dist/export/mapping-check.js +175 -0
- package/dist/export/normalize.d.ts +3 -0
- package/dist/export/normalize.js +67 -0
- package/dist/export-cli.d.ts +1 -0
- package/dist/export-cli.js +5 -0
- package/dist/export-run.d.ts +10 -0
- package/dist/export-run.js +157 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/install-skill-run.d.ts +8 -0
- package/dist/install-skill-run.js +30 -0
- package/dist/install-skill.d.ts +23 -0
- package/dist/install-skill.js +76 -0
- package/dist/layout/collaboration.d.ts +36 -0
- package/dist/layout/collaboration.js +152 -0
- package/dist/layout/di.d.ts +15 -0
- package/dist/layout/di.js +52 -0
- package/dist/layout/extract.d.ts +2 -0
- package/dist/layout/extract.js +99 -0
- package/dist/layout/lanes.d.ts +30 -0
- package/dist/layout/lanes.js +112 -0
- package/dist/layout/layering.d.ts +7 -0
- package/dist/layout/layering.js +53 -0
- package/dist/layout/layout.d.ts +12 -0
- package/dist/layout/layout.js +299 -0
- package/dist/layout/model.d.ts +52 -0
- package/dist/layout/model.js +10 -0
- package/dist/layout/placement.d.ts +25 -0
- package/dist/layout/placement.js +256 -0
- package/dist/layout/standalone.d.ts +8 -0
- package/dist/layout/standalone.js +50 -0
- package/dist/layout-cli.d.ts +1 -0
- package/dist/layout-cli.js +5 -0
- package/dist/layout-run.d.ts +9 -0
- package/dist/layout-run.js +105 -0
- package/dist/loader.d.ts +5 -0
- package/dist/loader.js +7 -0
- package/dist/namespace.d.ts +5 -0
- package/dist/namespace.js +5 -0
- package/dist/ontology.d.ts +19 -0
- package/dist/ontology.js +94 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +2 -0
- package/dist/render/geometry.d.ts +40 -0
- package/dist/render/geometry.js +81 -0
- package/dist/render/renderer.d.ts +31 -0
- package/dist/render/renderer.js +369 -0
- package/dist/render/symbols.d.ts +10 -0
- package/dist/render/symbols.js +26 -0
- package/dist/render/text.d.ts +20 -0
- package/dist/render/text.js +28 -0
- package/dist/render-cli.d.ts +1 -0
- package/dist/render-cli.js +7 -0
- package/dist/render-run.d.ts +9 -0
- package/dist/render-run.js +161 -0
- package/dist/render.d.ts +4 -0
- package/dist/render.js +4 -0
- package/dist/schema-overrides.d.ts +14 -0
- package/dist/schema-overrides.js +36 -0
- package/dist/schema-validate.d.ts +5 -0
- package/dist/schema-validate.js +8 -0
- package/dist/schema.d.ts +6 -0
- package/dist/schema.js +114 -0
- package/dist/shacl.d.ts +15 -0
- package/dist/shacl.js +37 -0
- package/dist/validate-cli.d.ts +1 -0
- package/dist/validate-cli.js +5 -0
- package/dist/validate-run.d.ts +8 -0
- package/dist/validate-run.js +58 -0
- package/dist/validate.d.ts +11 -0
- package/dist/validate.js +94 -0
- package/dist/versioning-cli.d.ts +1 -0
- package/dist/versioning-cli.js +18 -0
- package/dist/versioning.d.ts +17 -0
- package/dist/versioning.js +45 -0
- package/ontology/core.ttl +993 -0
- package/ontology/di.ttl +167 -0
- package/package.json +77 -4
- package/reference/bpmn-xsd/BPMN20.xsd +37 -0
- package/reference/bpmn-xsd/BPMNDI.xsd +100 -0
- package/reference/bpmn-xsd/DC.xsd +29 -0
- package/reference/bpmn-xsd/DI.xsd +100 -0
- package/reference/bpmn-xsd/Semantic.xsd +1562 -0
- package/reference/rospocher/ATTRIBUTION.md +22 -0
- package/reference/rospocher/BPMNOntology.owl +8937 -0
- package/reference/rospocher-verified.json +221 -0
- package/schema/bpmn-ld.schema.json +15230 -0
- package/shapes/core.shapes.ttl +398 -0
- package/skills/bpmn-ld/SKILL.md +154 -0
- package/skills/bpmn-ld/evals/models/order-approval-di.jsonld +296 -0
- package/skills/bpmn-ld/evals/models/refund-di.bpmn +82 -0
- package/skills/bpmn-ld/evals/models/refund.bpmn +18 -0
- package/skills/bpmn-ld/evals/outputs/explain-collaboration.md +22 -0
- package/skills/bpmn-ld/evals/outputs/explain-order-approval.md +24 -0
- package/skills/bpmn-ld/evals/outputs/explain-refund.md +17 -0
- package/skills/bpmn-ld/evals/outputs/generate-activity-compensation.jsonld +39 -0
- package/skills/bpmn-ld/evals/outputs/generate-ad-hoc-sub-process.jsonld +52 -0
- package/skills/bpmn-ld/evals/outputs/generate-boundary-event.jsonld +27 -0
- package/skills/bpmn-ld/evals/outputs/generate-collaboration.jsonld +49 -0
- package/skills/bpmn-ld/evals/outputs/generate-conditions.jsonld +93 -0
- package/skills/bpmn-ld/evals/outputs/generate-diagram-properties.jsonld +340 -0
- package/skills/bpmn-ld/evals/outputs/generate-error.jsonld +217 -0
- package/skills/bpmn-ld/evals/outputs/generate-escalation.jsonld +231 -0
- package/skills/bpmn-ld/evals/outputs/generate-event-definition-ref.jsonld +67 -0
- package/skills/bpmn-ld/evals/outputs/generate-event-sub-process.jsonld +54 -0
- package/skills/bpmn-ld/evals/outputs/generate-exclusive-choice.jsonld +32 -0
- package/skills/bpmn-ld/evals/outputs/generate-instantiating-gateway.jsonld +24 -0
- package/skills/bpmn-ld/evals/outputs/generate-lane-partition.jsonld +46 -0
- package/skills/bpmn-ld/evals/outputs/generate-link-event.expect.json +11 -0
- package/skills/bpmn-ld/evals/outputs/generate-link-event.jsonld +57 -0
- package/skills/bpmn-ld/evals/outputs/generate-loop.jsonld +30 -0
- package/skills/bpmn-ld/evals/outputs/generate-message.jsonld +61 -0
- package/skills/bpmn-ld/evals/outputs/generate-parallel-branches.jsonld +25 -0
- package/skills/bpmn-ld/evals/outputs/generate-process-attributes.jsonld +49 -0
- package/skills/bpmn-ld/evals/outputs/generate-script-task.jsonld +18 -0
- package/skills/bpmn-ld/evals/outputs/generate-signal.jsonld +178 -0
- package/skills/bpmn-ld/evals/outputs/generate-sub-process.jsonld +31 -0
- package/skills/bpmn-ld/evals/outputs/generate-targeted-compensation.jsonld +40 -0
- package/skills/bpmn-ld/evals/outputs/generate-task-implementation.jsonld +18 -0
- package/skills/bpmn-ld/evals/outputs/generate-task-message-references.jsonld +49 -0
- package/skills/bpmn-ld/evals/outputs/generate-transaction.jsonld +48 -0
- package/skills/bpmn-ld/evals/outputs/modify-order-approval.jsonld +376 -0
- package/skills/bpmn-ld/evals/outputs/modify-refund.jsonld +373 -0
- package/skills/bpmn-ld/evals/outputs/order-approval.jsonld +29 -0
- package/skills/bpmn-ld/evals/outputs/refine-order-approval.jsonld +377 -0
- package/skills/bpmn-ld/evals/prompts/explain-collaboration.md +3 -0
- package/skills/bpmn-ld/evals/prompts/explain-order-approval.md +3 -0
- package/skills/bpmn-ld/evals/prompts/explain-refund.md +3 -0
- package/skills/bpmn-ld/evals/prompts/generate-activity-compensation.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-ad-hoc-sub-process.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-boundary-event.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-collaboration.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-conditions.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-diagram-properties.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-error.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-escalation.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-event-definition-ref.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-event-sub-process.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-exclusive-choice.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-instantiating-gateway.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-lane-partition.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-link-event.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-loop.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-message.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-parallel-branches.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-process-attributes.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-script-task.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-signal.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-sub-process.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-targeted-compensation.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-task-implementation.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-task-message-references.md +1 -0
- package/skills/bpmn-ld/evals/prompts/generate-transaction.md +1 -0
- package/skills/bpmn-ld/evals/prompts/modify-order-approval.md +4 -0
- package/skills/bpmn-ld/evals/prompts/modify-refund.md +4 -0
- package/skills/bpmn-ld/evals/prompts/order-approval.md +1 -0
- package/skills/bpmn-ld/evals/prompts/refine-order-approval.md +8 -0
- package/skills/bpmn-ld/examples/positive/activity-compensation.jsonld +39 -0
- package/skills/bpmn-ld/examples/positive/ad-hoc-sub-process.jsonld +52 -0
- package/skills/bpmn-ld/examples/positive/boundary-event.jsonld +27 -0
- package/skills/bpmn-ld/examples/positive/collaboration.jsonld +49 -0
- package/skills/bpmn-ld/examples/positive/conditions.jsonld +93 -0
- package/skills/bpmn-ld/examples/positive/diagram-properties.jsonld +340 -0
- package/skills/bpmn-ld/examples/positive/error.jsonld +208 -0
- package/skills/bpmn-ld/examples/positive/escalation.jsonld +231 -0
- package/skills/bpmn-ld/examples/positive/event-definition-ref.jsonld +142 -0
- package/skills/bpmn-ld/examples/positive/event-sub-process.jsonld +54 -0
- package/skills/bpmn-ld/examples/positive/exclusive-choice.jsonld +32 -0
- package/skills/bpmn-ld/examples/positive/instantiating-gateway.jsonld +24 -0
- package/skills/bpmn-ld/examples/positive/lane-partition.jsonld +46 -0
- package/skills/bpmn-ld/examples/positive/link-event.expect.json +10 -0
- package/skills/bpmn-ld/examples/positive/link-event.jsonld +138 -0
- package/skills/bpmn-ld/examples/positive/loop.jsonld +30 -0
- package/skills/bpmn-ld/examples/positive/message.jsonld +60 -0
- package/skills/bpmn-ld/examples/positive/order-approval.jsonld +29 -0
- package/skills/bpmn-ld/examples/positive/parallel-branches.jsonld +25 -0
- package/skills/bpmn-ld/examples/positive/process-attributes.jsonld +49 -0
- package/skills/bpmn-ld/examples/positive/script-task.jsonld +18 -0
- package/skills/bpmn-ld/examples/positive/signal.jsonld +150 -0
- package/skills/bpmn-ld/examples/positive/sub-process.jsonld +31 -0
- package/skills/bpmn-ld/examples/positive/targeted-compensation.jsonld +40 -0
- package/skills/bpmn-ld/examples/positive/task-implementation.jsonld +18 -0
- package/skills/bpmn-ld/examples/positive/task-message-references.jsonld +151 -0
- package/skills/bpmn-ld/examples/positive/transaction.jsonld +48 -0
- package/skills/bpmn-ld/references/clarification-and-refinement.md +60 -0
- package/skills/bpmn-ld/references/common-mistakes.md +110 -0
- package/skills/bpmn-ld/references/explain-and-document.md +36 -0
- package/skills/bpmn-ld/references/modify-existing-model.md +49 -0
- package/skills/bpmn-ld/references/patterns.md +292 -0
- package/skills/bpmn-ld/references/render-output.md +1 -0
- package/skills/bpmn-ld/references/rule-catalogue.md +183 -0
- package/skills/bpmn-ld/references/vocabulary-summary.md +65 -0
- package/skills/bpmn-ld/template.jsonld +14 -0
- package/templates/combined.jsonld +3 -0
- package/templates/core.jsonld +170 -0
- package/templates/core.shapes.ttl +398 -0
- package/templates/core.ttl +993 -0
- package/templates/di.jsonld +37 -0
- package/templates/di.ttl +167 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { compileSchema, validateWithSchema } from "../schema-validate.js";
|
|
2
|
+
import { layoutCollaboration } from "./collaboration.js";
|
|
3
|
+
import { buildDiagram, containerElement, diagramProperties, edgeElement, shapeElement } from "./di.js";
|
|
4
|
+
import { extractProcess } from "./extract.js";
|
|
5
|
+
import { extractLanes, flattenLanes } from "./lanes.js";
|
|
6
|
+
import { allFlows, allNodes } from "./model.js";
|
|
7
|
+
import { placeStandalone } from "./standalone.js";
|
|
8
|
+
export class LayoutInputError extends Error {
|
|
9
|
+
errors;
|
|
10
|
+
constructor(errors) {
|
|
11
|
+
super(`invalid BPMN-LD document: ${errors[0]}`);
|
|
12
|
+
this.name = "LayoutInputError";
|
|
13
|
+
this.errors = errors;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
17
|
+
let compiled;
|
|
18
|
+
const schema = () => (compiled ??= compileSchema());
|
|
19
|
+
const collectIds = (value, found) => {
|
|
20
|
+
if (Array.isArray(value))
|
|
21
|
+
value.forEach((item) => collectIds(item, found));
|
|
22
|
+
else if (isObject(value)) {
|
|
23
|
+
if (typeof value.id === "string")
|
|
24
|
+
found.add(value.id);
|
|
25
|
+
Object.values(value).forEach((item) => collectIds(item, found));
|
|
26
|
+
}
|
|
27
|
+
return found;
|
|
28
|
+
};
|
|
29
|
+
const diagramsOf = (graph, processId) => graph.filter((node) => node.type === "BPMNDiagram" && isObject(node.plane) && node.plane.bpmnElement === processId);
|
|
30
|
+
const isBox = (bounds) => isObject(bounds) && ["x", "y", "width", "height"].every((key) => Number.isFinite(bounds[key]));
|
|
31
|
+
const elementsOf = (diagrams) => diagrams.flatMap((diagram) => (Array.isArray(diagram.plane.planeElements) ? diagram.plane.planeElements.filter(isObject) : []));
|
|
32
|
+
const previousIds = (diagrams) => {
|
|
33
|
+
const previous = {
|
|
34
|
+
diagram: diagrams[0].id,
|
|
35
|
+
plane: diagrams[0].plane.id,
|
|
36
|
+
name: diagrams[0].name,
|
|
37
|
+
resolution: diagrams[0].resolution,
|
|
38
|
+
messageVisibleKinds: new Map(),
|
|
39
|
+
shapes: new Map(),
|
|
40
|
+
edges: new Map(),
|
|
41
|
+
};
|
|
42
|
+
for (const element of elementsOf(diagrams)) {
|
|
43
|
+
if (typeof element.id !== "string" || typeof element.bpmnElement !== "string")
|
|
44
|
+
continue;
|
|
45
|
+
const table = element.type === "BPMNShape" ? previous.shapes : element.type === "BPMNEdge" ? previous.edges : undefined;
|
|
46
|
+
if (table && !table.has(element.bpmnElement)) {
|
|
47
|
+
table.set(element.bpmnElement, element.id);
|
|
48
|
+
if (element.type === "BPMNEdge" && element.messageVisibleKind !== undefined)
|
|
49
|
+
previous.messageVisibleKinds.set(element.bpmnElement, element.messageVisibleKind);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return previous;
|
|
53
|
+
};
|
|
54
|
+
const collapsedShapes = (diagrams) => new Set(elementsOf(diagrams)
|
|
55
|
+
.filter((element) => element.type === "BPMNShape" && element.isExpanded === false && typeof element.bpmnElement === "string")
|
|
56
|
+
.map((element) => element.bpmnElement));
|
|
57
|
+
const nestedLaneIds = (model) => model.nodes.flatMap((node) => (node.content ? [...(node.content.lanes?.ids ?? []), ...nestedLaneIds(node.content)] : []));
|
|
58
|
+
const ownersOf = (model, owners = new Map()) => {
|
|
59
|
+
for (const item of model.data)
|
|
60
|
+
owners.set(item.id, model.id);
|
|
61
|
+
for (const node of model.nodes) {
|
|
62
|
+
owners.set(node.id, model.id);
|
|
63
|
+
if (node.content)
|
|
64
|
+
ownersOf(node.content, owners);
|
|
65
|
+
}
|
|
66
|
+
return owners;
|
|
67
|
+
};
|
|
68
|
+
export const layoutDocument = (document, options = {}) => {
|
|
69
|
+
if (!isObject(document))
|
|
70
|
+
throw new LayoutInputError(["document must be a JSON object"]);
|
|
71
|
+
const errors = [...new Set(validateWithSchema(document, schema()))];
|
|
72
|
+
if (errors.length > 0)
|
|
73
|
+
throw new LayoutInputError(errors);
|
|
74
|
+
const output = structuredClone(document);
|
|
75
|
+
const graph = Array.isArray(output["@graph"]) ? output["@graph"] : [];
|
|
76
|
+
if (!Array.isArray(output["@graph"])) {
|
|
77
|
+
const { "@context": context, ...root } = output;
|
|
78
|
+
output["@graph"] = graph;
|
|
79
|
+
for (const key of Object.keys(root))
|
|
80
|
+
delete output[key];
|
|
81
|
+
graph.push(root);
|
|
82
|
+
output["@context"] = context;
|
|
83
|
+
}
|
|
84
|
+
const used = collectIds(graph, new Set());
|
|
85
|
+
const unique = (preferred) => {
|
|
86
|
+
let id = preferred;
|
|
87
|
+
for (let n = 2; used.has(id); n++)
|
|
88
|
+
id = `${preferred}_${n}`;
|
|
89
|
+
used.add(id);
|
|
90
|
+
return id;
|
|
91
|
+
};
|
|
92
|
+
const processes = new Map();
|
|
93
|
+
for (const node of graph)
|
|
94
|
+
if (node.type === "Process" && typeof node.id === "string")
|
|
95
|
+
processes.set(node.id, node);
|
|
96
|
+
const claimed = new Set();
|
|
97
|
+
const participantsOf = (collaboration) => (Array.isArray(collaboration.participants) ? collaboration.participants : []).filter((item) => isObject(item) && typeof item.id === "string");
|
|
98
|
+
for (const root of graph)
|
|
99
|
+
if (root.type === "Collaboration")
|
|
100
|
+
for (const participant of participantsOf(root))
|
|
101
|
+
if (typeof participant.processRef === "string" && processes.has(participant.processRef))
|
|
102
|
+
claimed.add(participant.processRef);
|
|
103
|
+
const tied = new Map();
|
|
104
|
+
for (const root of graph) {
|
|
105
|
+
if (root.type !== "Collaboration" || participantsOf(root).length === 0)
|
|
106
|
+
continue;
|
|
107
|
+
const endpoints = new Set();
|
|
108
|
+
for (const flow of Array.isArray(root.messageFlows) ? root.messageFlows : [])
|
|
109
|
+
if (isObject(flow))
|
|
110
|
+
for (const key of ["sourceRef", "targetRef"])
|
|
111
|
+
if (typeof flow[key] === "string")
|
|
112
|
+
endpoints.add(flow[key]);
|
|
113
|
+
const drawn = new Set(elementsOf(diagramsOf(graph, root.id)).map((element) => element.bpmnElement));
|
|
114
|
+
const related = [];
|
|
115
|
+
for (const [id, process] of processes) {
|
|
116
|
+
if (claimed.has(id))
|
|
117
|
+
continue;
|
|
118
|
+
const model = extractProcess(process);
|
|
119
|
+
const elements = [...allNodes(model), ...allFlows(model)];
|
|
120
|
+
if (!elements.some((element) => endpoints.has(element.id) || drawn.has(element.id)) || model.nodes.length === 0)
|
|
121
|
+
continue;
|
|
122
|
+
claimed.add(id);
|
|
123
|
+
related.push(process);
|
|
124
|
+
}
|
|
125
|
+
tied.set(root.id, related);
|
|
126
|
+
}
|
|
127
|
+
const layoutCollaborationRoot = (root) => {
|
|
128
|
+
const participantInputs = participantsOf(root);
|
|
129
|
+
if (participantInputs.length === 0)
|
|
130
|
+
return;
|
|
131
|
+
const diagrams = diagramsOf(graph, root.id);
|
|
132
|
+
const collapsed = collapsedShapes(diagrams);
|
|
133
|
+
const seen = new Set();
|
|
134
|
+
const participants = participantInputs.map((participant) => {
|
|
135
|
+
const process = typeof participant.processRef === "string" && !seen.has(participant.processRef) ? processes.get(participant.processRef) : undefined;
|
|
136
|
+
if (!process)
|
|
137
|
+
return { id: participant.id };
|
|
138
|
+
seen.add(process.id);
|
|
139
|
+
return { id: participant.id, model: extractProcess(process, collapsed), lanes: extractLanes(process) };
|
|
140
|
+
});
|
|
141
|
+
for (const process of tied.get(root.id) ?? [])
|
|
142
|
+
participants.push({ id: process.id, model: extractProcess(process, collapsed), lanes: extractLanes(process), unframed: true });
|
|
143
|
+
const messageFlows = (Array.isArray(root.messageFlows) ? root.messageFlows : [])
|
|
144
|
+
.filter((flow) => isObject(flow) && typeof flow.id === "string" && typeof flow.sourceRef === "string" && typeof flow.targetRef === "string")
|
|
145
|
+
.map((flow) => ({ id: flow.id, source: flow.sourceRef, target: flow.targetRef }));
|
|
146
|
+
const build = (layout, shapeIds, shapeId, edgeId, skipShape, skipEdge) => {
|
|
147
|
+
const added = [];
|
|
148
|
+
for (const shape of layout.shapes) {
|
|
149
|
+
if (skipShape(shape.element))
|
|
150
|
+
continue;
|
|
151
|
+
const id = shapeId(shape.element);
|
|
152
|
+
shapeIds.set(shape.element, id);
|
|
153
|
+
added.push(shape.kind === "node" ? shapeElement(id, shape.node, shape.box) : containerElement(id, shape.element, shape.box));
|
|
154
|
+
}
|
|
155
|
+
for (const edge of layout.edges)
|
|
156
|
+
if (!skipEdge(edge.element))
|
|
157
|
+
added.push(edgeElement(edgeId(edge.element), edge.element, edge.source && shapeIds.get(edge.source), edge.target && shapeIds.get(edge.target), edge.route));
|
|
158
|
+
return added;
|
|
159
|
+
};
|
|
160
|
+
if (diagrams.length === 0 || options.force) {
|
|
161
|
+
const previous = diagrams.length > 0 ? previousIds(diagrams) : undefined;
|
|
162
|
+
if (previous)
|
|
163
|
+
collectIds(diagrams, new Set()).forEach((id) => used.delete(id));
|
|
164
|
+
const layout = layoutCollaboration(participants, messageFlows, { fixed: new Map(), obstacles: new Map() });
|
|
165
|
+
const shapeIds = new Map();
|
|
166
|
+
const planeElements = build(layout, shapeIds, (element) => unique(previous?.shapes.get(element) ?? `Shape_${element}`), (element) => unique(previous?.edges.get(element) ?? `Edge_${element}`), () => false, () => false);
|
|
167
|
+
for (const element of planeElements) {
|
|
168
|
+
const kind = element.type === "BPMNEdge" ? previous?.messageVisibleKinds.get(element.bpmnElement) : undefined;
|
|
169
|
+
if (kind !== undefined)
|
|
170
|
+
element.messageVisibleKind = kind;
|
|
171
|
+
}
|
|
172
|
+
const replacement = {
|
|
173
|
+
id: unique(previous?.diagram ?? `Diagram_${root.id}`),
|
|
174
|
+
type: "BPMNDiagram",
|
|
175
|
+
...diagramProperties(previous),
|
|
176
|
+
plane: { id: unique(previous?.plane ?? `Plane_${root.id}`), type: "BPMNPlane", bpmnElement: root.id, planeElements },
|
|
177
|
+
};
|
|
178
|
+
if (diagrams.length === 0)
|
|
179
|
+
graph.push(replacement);
|
|
180
|
+
else {
|
|
181
|
+
const position = graph.indexOf(diagrams[0]);
|
|
182
|
+
for (const diagram of diagrams.slice(1))
|
|
183
|
+
graph.splice(graph.indexOf(diagram), 1);
|
|
184
|
+
graph.splice(position, 1, replacement);
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const elements = elementsOf(diagrams);
|
|
189
|
+
const shapeIds = new Map();
|
|
190
|
+
const fixed = new Map();
|
|
191
|
+
const drawnEdges = new Set();
|
|
192
|
+
for (const element of elements) {
|
|
193
|
+
if (typeof element.bpmnElement !== "string")
|
|
194
|
+
continue;
|
|
195
|
+
if (element.type === "BPMNShape" && isBox(element.bounds) && !shapeIds.has(element.bpmnElement)) {
|
|
196
|
+
shapeIds.set(element.bpmnElement, element.id);
|
|
197
|
+
fixed.set(element.bpmnElement, element.bounds);
|
|
198
|
+
}
|
|
199
|
+
else if (element.type === "BPMNEdge")
|
|
200
|
+
drawnEdges.add(element.bpmnElement);
|
|
201
|
+
}
|
|
202
|
+
const models = participants.flatMap((participant) => (participant.model ? [participant.model] : []));
|
|
203
|
+
const missingFlows = new Set(models.flatMap((model) => allFlows(model)).filter((flow) => !drawnEdges.has(flow.id)).map((flow) => flow.id));
|
|
204
|
+
const owners = new Map();
|
|
205
|
+
for (const model of models)
|
|
206
|
+
ownersOf(model, owners);
|
|
207
|
+
const obstacles = new Map();
|
|
208
|
+
for (const element of elements) {
|
|
209
|
+
if (element.type !== "BPMNShape" || !isBox(element.bounds) || !owners.has(element.bpmnElement))
|
|
210
|
+
continue;
|
|
211
|
+
const owner = owners.get(element.bpmnElement);
|
|
212
|
+
obstacles.set(owner, [...(obstacles.get(owner) ?? []), element.bounds]);
|
|
213
|
+
}
|
|
214
|
+
const layout = layoutCollaboration(participants, messageFlows, { fixed, obstacles, missingFlows });
|
|
215
|
+
const skipShape = (element) => fixed.has(element);
|
|
216
|
+
const skipEdge = (element) => drawnEdges.has(element);
|
|
217
|
+
if (layout.shapes.every((shape) => skipShape(shape.element)) && layout.edges.every((edge) => skipEdge(edge.element)))
|
|
218
|
+
return;
|
|
219
|
+
const added = build(layout, shapeIds, (element) => unique(`Shape_${element}`), (element) => unique(`Edge_${element}`), skipShape, skipEdge);
|
|
220
|
+
const plane = diagrams[0].plane;
|
|
221
|
+
plane.planeElements = [...(Array.isArray(plane.planeElements) ? plane.planeElements : []), ...added];
|
|
222
|
+
};
|
|
223
|
+
for (const root of [...graph]) {
|
|
224
|
+
if (root.type === "Collaboration" && typeof root.id === "string") {
|
|
225
|
+
layoutCollaborationRoot(root);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (root.type !== "Process" || claimed.has(root.id) || typeof root.id !== "string")
|
|
229
|
+
continue;
|
|
230
|
+
const diagrams = diagramsOf(graph, root.id);
|
|
231
|
+
const model = extractProcess(root, collapsedShapes(diagrams));
|
|
232
|
+
if (model.nodes.length === 0)
|
|
233
|
+
continue;
|
|
234
|
+
const laneNodes = extractLanes(root);
|
|
235
|
+
if (diagrams.length === 0) {
|
|
236
|
+
graph.push(buildDiagram(model, placeStandalone(model, laneNodes), unique));
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (options.force) {
|
|
240
|
+
const previous = previousIds(diagrams);
|
|
241
|
+
collectIds(diagrams, new Set()).forEach((id) => used.delete(id));
|
|
242
|
+
const position = graph.indexOf(diagrams[0]);
|
|
243
|
+
const replacement = buildDiagram(model, placeStandalone(model, laneNodes), unique, previous);
|
|
244
|
+
for (const diagram of diagrams.slice(1))
|
|
245
|
+
graph.splice(graph.indexOf(diagram), 1);
|
|
246
|
+
graph.splice(position, 1, replacement);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const elements = elementsOf(diagrams);
|
|
250
|
+
const shapeIds = new Map();
|
|
251
|
+
const fixed = new Map();
|
|
252
|
+
const drawnFlows = new Set();
|
|
253
|
+
for (const element of elements) {
|
|
254
|
+
if (typeof element.bpmnElement !== "string")
|
|
255
|
+
continue;
|
|
256
|
+
if (element.type === "BPMNShape" && isBox(element.bounds) && !shapeIds.has(element.bpmnElement)) {
|
|
257
|
+
shapeIds.set(element.bpmnElement, element.id);
|
|
258
|
+
fixed.set(element.bpmnElement, element.bounds);
|
|
259
|
+
}
|
|
260
|
+
else if (element.type === "BPMNEdge")
|
|
261
|
+
drawnFlows.add(element.bpmnElement);
|
|
262
|
+
}
|
|
263
|
+
const nodes = allNodes(model);
|
|
264
|
+
const missingNodes = nodes.filter((node) => !fixed.has(node.id));
|
|
265
|
+
const missingFlows = allFlows(model).filter((flow) => !drawnFlows.has(flow.id));
|
|
266
|
+
const laneIds = new Set([...flattenLanes(laneNodes ?? []).map((lane) => lane.id), ...nestedLaneIds(model)]);
|
|
267
|
+
const missingLanes = [...laneIds].filter((id) => !fixed.has(id));
|
|
268
|
+
if (missingNodes.length === 0 && missingFlows.length === 0 && missingLanes.length === 0)
|
|
269
|
+
continue;
|
|
270
|
+
const owners = ownersOf(model);
|
|
271
|
+
const obstacles = new Map();
|
|
272
|
+
for (const element of elements) {
|
|
273
|
+
if (element.type !== "BPMNShape" || !isBox(element.bounds) || laneIds.has(element.bpmnElement))
|
|
274
|
+
continue;
|
|
275
|
+
const owner = owners.get(element.bpmnElement) ?? model.id;
|
|
276
|
+
obstacles.set(owner, [...(obstacles.get(owner) ?? []), element.bounds]);
|
|
277
|
+
}
|
|
278
|
+
const placed = placeStandalone(model, laneNodes, { fixed, obstacles, missingFlows: new Set(missingFlows.map((flow) => flow.id)) });
|
|
279
|
+
const added = [];
|
|
280
|
+
for (const lane of missingLanes) {
|
|
281
|
+
const id = unique(`Shape_${lane}`);
|
|
282
|
+
shapeIds.set(lane, id);
|
|
283
|
+
added.push(containerElement(id, lane, placed.lanes.get(lane)));
|
|
284
|
+
}
|
|
285
|
+
for (const node of missingNodes) {
|
|
286
|
+
const id = unique(`Shape_${node.id}`);
|
|
287
|
+
shapeIds.set(node.id, id);
|
|
288
|
+
added.push(shapeElement(id, node, placed.boxes.get(node.id)));
|
|
289
|
+
}
|
|
290
|
+
for (const flow of missingFlows) {
|
|
291
|
+
const route = placed.routes.get(flow.id);
|
|
292
|
+
if (route)
|
|
293
|
+
added.push(edgeElement(unique(`Edge_${flow.id}`), flow.id, flow.source && shapeIds.get(flow.source), flow.target && shapeIds.get(flow.target), route));
|
|
294
|
+
}
|
|
295
|
+
const plane = diagrams[0].plane;
|
|
296
|
+
plane.planeElements = [...(Array.isArray(plane.planeElements) ? plane.planeElements : []), ...added];
|
|
297
|
+
}
|
|
298
|
+
return { document: output };
|
|
299
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Layering } from "./layering.ts";
|
|
2
|
+
export type Json = Record<string, any>;
|
|
3
|
+
export interface Size {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
export interface Point {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
}
|
|
11
|
+
export interface Box extends Point, Size {
|
|
12
|
+
}
|
|
13
|
+
export interface LaneHooks {
|
|
14
|
+
ids: string[];
|
|
15
|
+
indent: number;
|
|
16
|
+
arrange: (layering: Layering, layered: Map<string, Box>, origin: Point) => void;
|
|
17
|
+
boxes: (frame: Box, boxes: Map<string, Box>, anchored: boolean) => Map<string, Box>;
|
|
18
|
+
}
|
|
19
|
+
export interface FlowNode {
|
|
20
|
+
id: string;
|
|
21
|
+
type: string;
|
|
22
|
+
size: Size;
|
|
23
|
+
attachedTo?: string;
|
|
24
|
+
collapsed?: boolean;
|
|
25
|
+
content?: ProcessModel;
|
|
26
|
+
}
|
|
27
|
+
export interface Flow {
|
|
28
|
+
id: string;
|
|
29
|
+
source: string;
|
|
30
|
+
target: string;
|
|
31
|
+
}
|
|
32
|
+
export interface Edge {
|
|
33
|
+
id: string;
|
|
34
|
+
source?: string;
|
|
35
|
+
target?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ProcessModel {
|
|
38
|
+
id: string;
|
|
39
|
+
nodes: FlowNode[];
|
|
40
|
+
flows: Flow[];
|
|
41
|
+
data: FlowNode[];
|
|
42
|
+
stubs: Edge[];
|
|
43
|
+
associations: Flow[];
|
|
44
|
+
lanes?: LaneHooks;
|
|
45
|
+
}
|
|
46
|
+
export interface Placement {
|
|
47
|
+
boxes: Map<string, Box>;
|
|
48
|
+
routes: Map<string, Point[]>;
|
|
49
|
+
lanes?: Map<string, Box>;
|
|
50
|
+
}
|
|
51
|
+
export declare const allNodes: (model: ProcessModel) => FlowNode[];
|
|
52
|
+
export declare const allFlows: (model: ProcessModel) => Edge[];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const allNodes = (model) => [
|
|
2
|
+
...model.nodes.flatMap((node) => [node, ...(node.content ? allNodes(node.content) : [])]),
|
|
3
|
+
...model.data,
|
|
4
|
+
];
|
|
5
|
+
export const allFlows = (model) => [
|
|
6
|
+
...model.flows,
|
|
7
|
+
...model.stubs,
|
|
8
|
+
...model.associations,
|
|
9
|
+
...model.nodes.flatMap((node) => (node.content ? allFlows(node.content) : [])),
|
|
10
|
+
];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Layering } from "./layering.ts";
|
|
2
|
+
import type { Box, Flow, FlowNode, Point, ProcessModel } from "./model.ts";
|
|
3
|
+
export declare const ORIGIN: Point;
|
|
4
|
+
export declare const ROW_GAP = 40;
|
|
5
|
+
export declare const reserveBelow: (model: ProcessModel, hostId: string) => number;
|
|
6
|
+
export declare const boundaryBox: (model: ProcessModel, node: FlowNode, host: Box) => Box;
|
|
7
|
+
export declare const placeNodes: (model: ProcessModel, layering: Layering, origin?: Point) => Map<string, Box>;
|
|
8
|
+
export declare const routeFlows: (model: ProcessModel, flows: Flow[], layering: Layering, boxes: Map<string, Box>) => Map<string, Point[]>;
|
|
9
|
+
export declare const placeData: (model: ProcessModel, boxes: Map<string, Box>, routes: Map<string, Point[]>, fixed?: Map<string, Box>) => Map<string, Box>;
|
|
10
|
+
export declare const routeExtras: (model: ProcessModel, boxes: Map<string, Box>, only?: Set<string>) => Map<string, Point[]>;
|
|
11
|
+
export declare const measure: (model: ProcessModel) => void;
|
|
12
|
+
export interface ScopeContext {
|
|
13
|
+
fixed: Map<string, Box>;
|
|
14
|
+
obstacles: Map<string, Box[]>;
|
|
15
|
+
missingFlows?: Set<string>;
|
|
16
|
+
boxes: Map<string, Box>;
|
|
17
|
+
routes: Map<string, Point[]>;
|
|
18
|
+
arrange?: (model: ProcessModel, layering: Layering, layered: Map<string, Box>, origin: Point) => void;
|
|
19
|
+
lanes?: Map<string, Box>;
|
|
20
|
+
}
|
|
21
|
+
export declare const placeScope: (model: ProcessModel, origin: Point, context: ScopeContext) => void;
|
|
22
|
+
export declare const place: (model: ProcessModel) => {
|
|
23
|
+
boxes: Map<string, Box>;
|
|
24
|
+
routes: Map<string, Point[]>;
|
|
25
|
+
};
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { assignLayers } from "./layering.js";
|
|
2
|
+
export const ORIGIN = { x: 100, y: 100 };
|
|
3
|
+
const LAYER_GAP = 60;
|
|
4
|
+
export const ROW_GAP = 40;
|
|
5
|
+
const LOOP_CLEARANCE = 30;
|
|
6
|
+
const CONTAINER_MARGIN = 30;
|
|
7
|
+
const BOUNDARY_SIZE = 36;
|
|
8
|
+
const BOUNDARY_PITCH = 44;
|
|
9
|
+
const ACTIVITY_SIZE = { width: 100, height: 80 };
|
|
10
|
+
const DATA_GAP = 40;
|
|
11
|
+
const DATA_SPACING = 20;
|
|
12
|
+
const STUB_LENGTH = 40;
|
|
13
|
+
const boundariesOf = (model, hostId) => model.nodes.filter((node) => node.attachedTo === hostId);
|
|
14
|
+
export const reserveBelow = (model, hostId) => {
|
|
15
|
+
const count = boundariesOf(model, hostId).length;
|
|
16
|
+
return count === 0 ? 0 : BOUNDARY_SIZE / 2 + 10 + 8 * count;
|
|
17
|
+
};
|
|
18
|
+
export const boundaryBox = (model, node, host) => {
|
|
19
|
+
const siblings = boundariesOf(model, node.attachedTo);
|
|
20
|
+
const span = siblings.length * BOUNDARY_PITCH - (BOUNDARY_PITCH - BOUNDARY_SIZE);
|
|
21
|
+
return {
|
|
22
|
+
x: host.x + Math.round((host.width - span) / 2) + siblings.indexOf(node) * BOUNDARY_PITCH,
|
|
23
|
+
y: host.y + host.height - BOUNDARY_SIZE / 2,
|
|
24
|
+
width: BOUNDARY_SIZE,
|
|
25
|
+
height: BOUNDARY_SIZE,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export const placeNodes = (model, layering, origin = ORIGIN) => {
|
|
29
|
+
const sizes = new Map(model.nodes.map((node) => [node.id, node.size]));
|
|
30
|
+
const slot = (id) => sizes.get(id).height + reserveBelow(model, id);
|
|
31
|
+
const columnWidths = layering.layers.map((layer) => Math.max(0, ...layer.map((id) => sizes.get(id).width)));
|
|
32
|
+
const columnHeights = layering.layers.map((layer) => layer.reduce((sum, id) => sum + slot(id), 0) + ROW_GAP * Math.max(0, layer.length - 1));
|
|
33
|
+
const tallest = Math.max(0, ...columnHeights);
|
|
34
|
+
const boxes = new Map();
|
|
35
|
+
let x = origin.x;
|
|
36
|
+
layering.layers.forEach((layer, index) => {
|
|
37
|
+
let y = origin.y + (tallest - columnHeights[index]) / 2;
|
|
38
|
+
for (const id of layer) {
|
|
39
|
+
const { width, height } = sizes.get(id);
|
|
40
|
+
boxes.set(id, { x: x + (columnWidths[index] - width) / 2, y, width, height });
|
|
41
|
+
y += slot(id) + ROW_GAP;
|
|
42
|
+
}
|
|
43
|
+
x += columnWidths[index] + LAYER_GAP;
|
|
44
|
+
});
|
|
45
|
+
for (const node of model.nodes)
|
|
46
|
+
if (node.attachedTo)
|
|
47
|
+
boxes.set(node.id, boundaryBox(model, node, boxes.get(node.attachedTo)));
|
|
48
|
+
return boxes;
|
|
49
|
+
};
|
|
50
|
+
const centerY = (box) => box.y + box.height / 2;
|
|
51
|
+
const centerX = (box) => box.x + box.width / 2;
|
|
52
|
+
const dedupe = (points) => points.filter((point, index) => index === 0 || point.x !== points[index - 1].x || point.y !== points[index - 1].y);
|
|
53
|
+
const routeForward = (source, target) => {
|
|
54
|
+
const start = { x: source.x + source.width, y: centerY(source) };
|
|
55
|
+
const end = { x: target.x, y: centerY(target) };
|
|
56
|
+
if (start.y === end.y)
|
|
57
|
+
return [start, end];
|
|
58
|
+
const middle = Math.round((start.x + end.x) / 2);
|
|
59
|
+
return [start, { x: middle, y: start.y }, { x: middle, y: end.y }, end];
|
|
60
|
+
};
|
|
61
|
+
const routeBack = (source, target, floor) => {
|
|
62
|
+
const lane = floor + LOOP_CLEARANCE;
|
|
63
|
+
const start = { x: centerX(source), y: source.y + source.height };
|
|
64
|
+
const end = { x: centerX(target), y: target.y + target.height };
|
|
65
|
+
return [start, { x: start.x, y: lane }, { x: end.x, y: lane }, end];
|
|
66
|
+
};
|
|
67
|
+
const routeBoundary = (model, event, source, host, target) => {
|
|
68
|
+
const siblings = boundariesOf(model, event.attachedTo);
|
|
69
|
+
const index = siblings.indexOf(event);
|
|
70
|
+
const start = { x: centerX(source), y: source.y + source.height };
|
|
71
|
+
const lane = start.y + 8 + 8 * (siblings.length - 1 - index);
|
|
72
|
+
const end = { x: target.x, y: centerY(target) };
|
|
73
|
+
const middle = Math.round((host.x + host.width + target.x) / 2) + 6 * index;
|
|
74
|
+
return dedupe([start, { x: start.x, y: lane }, { x: middle, y: lane }, { x: middle, y: end.y }, end]);
|
|
75
|
+
};
|
|
76
|
+
export const routeFlows = (model, flows, layering, boxes) => {
|
|
77
|
+
const floor = Math.max(0, ...[...boxes.values()].map((box) => box.y + box.height));
|
|
78
|
+
const routes = new Map();
|
|
79
|
+
const nodes = new Map(model.nodes.map((node) => [node.id, node]));
|
|
80
|
+
for (const flow of flows) {
|
|
81
|
+
const source = boxes.get(flow.source);
|
|
82
|
+
const target = boxes.get(flow.target);
|
|
83
|
+
const back = layering.backFlows.has(flow.id) || layering.layerOf.get(flow.target) <= layering.layerOf.get(flow.source);
|
|
84
|
+
const event = nodes.get(flow.source);
|
|
85
|
+
if (back)
|
|
86
|
+
routes.set(flow.id, routeBack(source, target, floor));
|
|
87
|
+
else if (event.attachedTo)
|
|
88
|
+
routes.set(flow.id, routeBoundary(model, event, source, boxes.get(event.attachedTo), target));
|
|
89
|
+
else
|
|
90
|
+
routes.set(flow.id, routeForward(source, target));
|
|
91
|
+
}
|
|
92
|
+
return routes;
|
|
93
|
+
};
|
|
94
|
+
export const placeData = (model, boxes, routes, fixed = new Map()) => {
|
|
95
|
+
const placed = new Map();
|
|
96
|
+
const open = model.data.filter((item) => !fixed.has(item.id));
|
|
97
|
+
if (open.length === 0)
|
|
98
|
+
return placed;
|
|
99
|
+
const scope = model.nodes.map((node) => boxes.get(node.id)).filter((box) => box !== undefined);
|
|
100
|
+
const left = Math.min(...scope.map((box) => box.x));
|
|
101
|
+
const top = Math.max(...scope.map((box) => box.y + box.height), ...[...routes.values()].flat().map((point) => point.y)) + DATA_GAP;
|
|
102
|
+
const wanted = open.map((item) => {
|
|
103
|
+
const link = model.associations.find((association) => association.source === item.id || association.target === item.id);
|
|
104
|
+
const other = link ? boxes.get(link.source === item.id ? link.target : link.source) : undefined;
|
|
105
|
+
return { item, x: other ? other.x + other.width / 2 - item.size.width / 2 : left };
|
|
106
|
+
});
|
|
107
|
+
wanted.sort((a, b) => a.x - b.x);
|
|
108
|
+
let cursor = -Infinity;
|
|
109
|
+
for (const { item, x } of wanted) {
|
|
110
|
+
const at = Math.max(x, cursor);
|
|
111
|
+
placed.set(item.id, { x: at, y: top, width: item.size.width, height: item.size.height });
|
|
112
|
+
cursor = at + item.size.width + DATA_SPACING;
|
|
113
|
+
}
|
|
114
|
+
return placed;
|
|
115
|
+
};
|
|
116
|
+
const routeAssociation = (node, data) => {
|
|
117
|
+
const nx = node.x + node.width / 2;
|
|
118
|
+
const dx = data.x + data.width / 2;
|
|
119
|
+
if (data.y >= node.y + node.height) {
|
|
120
|
+
const start = { x: nx, y: node.y + node.height };
|
|
121
|
+
const end = { x: dx, y: data.y };
|
|
122
|
+
if (nx === dx)
|
|
123
|
+
return [start, end];
|
|
124
|
+
const middle = Math.round((start.y + end.y) / 2);
|
|
125
|
+
return [start, { x: nx, y: middle }, { x: dx, y: middle }, end];
|
|
126
|
+
}
|
|
127
|
+
if (data.y + data.height <= node.y) {
|
|
128
|
+
const start = { x: nx, y: node.y };
|
|
129
|
+
const end = { x: dx, y: data.y + data.height };
|
|
130
|
+
if (nx === dx)
|
|
131
|
+
return [start, end];
|
|
132
|
+
const middle = Math.round((start.y + end.y) / 2);
|
|
133
|
+
return [start, { x: nx, y: middle }, { x: dx, y: middle }, end];
|
|
134
|
+
}
|
|
135
|
+
return [{ x: nx, y: node.y + node.height / 2 }, { x: dx, y: data.y + data.height / 2 }];
|
|
136
|
+
};
|
|
137
|
+
export const routeExtras = (model, boxes, only) => {
|
|
138
|
+
const routes = new Map();
|
|
139
|
+
const dataIds = new Set(model.data.map((item) => item.id));
|
|
140
|
+
for (const stub of model.stubs) {
|
|
141
|
+
if (only && !only.has(stub.id))
|
|
142
|
+
continue;
|
|
143
|
+
const source = stub.source ? boxes.get(stub.source) : undefined;
|
|
144
|
+
const target = stub.target ? boxes.get(stub.target) : undefined;
|
|
145
|
+
if (source) {
|
|
146
|
+
const y = centerY(source);
|
|
147
|
+
routes.set(stub.id, [{ x: source.x + source.width, y }, { x: source.x + source.width + STUB_LENGTH, y }]);
|
|
148
|
+
}
|
|
149
|
+
else if (target) {
|
|
150
|
+
const y = centerY(target);
|
|
151
|
+
routes.set(stub.id, [{ x: target.x - STUB_LENGTH, y }, { x: target.x, y }]);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (const association of model.associations) {
|
|
155
|
+
if (only && !only.has(association.id))
|
|
156
|
+
continue;
|
|
157
|
+
const source = boxes.get(association.source);
|
|
158
|
+
const target = boxes.get(association.target);
|
|
159
|
+
if (!source || !target)
|
|
160
|
+
continue;
|
|
161
|
+
const route = dataIds.has(association.source) ? routeAssociation(target, source).reverse() : routeAssociation(source, target);
|
|
162
|
+
routes.set(association.id, route);
|
|
163
|
+
}
|
|
164
|
+
return routes;
|
|
165
|
+
};
|
|
166
|
+
export const measure = (model) => {
|
|
167
|
+
for (const node of model.nodes) {
|
|
168
|
+
if (node.content) {
|
|
169
|
+
measure(node.content);
|
|
170
|
+
const layering = assignLayers(node.content);
|
|
171
|
+
const origin = { x: node.content.lanes?.indent ?? 0, y: 0 };
|
|
172
|
+
const boxes = placeNodes(node.content, layering, origin);
|
|
173
|
+
node.content.lanes?.arrange(layering, boxes, origin);
|
|
174
|
+
const routes = routeFlows(node.content, node.content.flows, layering, boxes);
|
|
175
|
+
for (const [id, box] of placeData(node.content, boxes, routes))
|
|
176
|
+
boxes.set(id, box);
|
|
177
|
+
for (const [id, route] of routeExtras(node.content, boxes))
|
|
178
|
+
routes.set(id, route);
|
|
179
|
+
const points = [...routes.values()].flat();
|
|
180
|
+
const right = Math.max(0, ...[...boxes.values()].map((box) => box.x + box.width), ...points.map((point) => point.x));
|
|
181
|
+
const bottom = Math.max(0, ...[...boxes.values()].map((box) => box.y + box.height), ...points.map((point) => point.y));
|
|
182
|
+
node.size = {
|
|
183
|
+
width: Math.max(ACTIVITY_SIZE.width, right + 2 * CONTAINER_MARGIN),
|
|
184
|
+
height: Math.max(ACTIVITY_SIZE.height, bottom + 2 * CONTAINER_MARGIN),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
node.size = { ...node.size, width: Math.max(node.size.width, boundariesOf(model, node.id).length * BOUNDARY_PITCH) };
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const intersects = (a, b) => a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;
|
|
191
|
+
export const placeScope = (model, origin, context) => {
|
|
192
|
+
const layering = assignLayers(model);
|
|
193
|
+
const layered = placeNodes(model, layering, origin);
|
|
194
|
+
context.arrange?.(model, layering, layered, origin);
|
|
195
|
+
model.lanes?.arrange(layering, layered, origin);
|
|
196
|
+
const anchor = model.nodes.find((node) => !node.attachedTo && context.fixed.has(node.id));
|
|
197
|
+
const dx = anchor ? context.fixed.get(anchor.id).x - layered.get(anchor.id).x : 0;
|
|
198
|
+
const dy = anchor ? context.fixed.get(anchor.id).y - layered.get(anchor.id).y : 0;
|
|
199
|
+
const taken = [...(context.obstacles.get(model.id) ?? [])];
|
|
200
|
+
const boxes = new Map();
|
|
201
|
+
for (const node of model.nodes) {
|
|
202
|
+
if (node.attachedTo)
|
|
203
|
+
continue;
|
|
204
|
+
const existing = context.fixed.get(node.id);
|
|
205
|
+
if (existing) {
|
|
206
|
+
boxes.set(node.id, existing);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const base = layered.get(node.id);
|
|
210
|
+
const box = { ...base, x: base.x + dx, y: base.y + dy };
|
|
211
|
+
for (let hit = taken.filter((other) => intersects(box, other)); hit.length > 0; hit = taken.filter((other) => intersects(box, other)))
|
|
212
|
+
box.y = Math.max(...hit.map((other) => other.y + other.height)) + ROW_GAP;
|
|
213
|
+
taken.push(box);
|
|
214
|
+
boxes.set(node.id, box);
|
|
215
|
+
}
|
|
216
|
+
for (const node of model.nodes)
|
|
217
|
+
if (node.attachedTo)
|
|
218
|
+
boxes.set(node.id, context.fixed.get(node.id) ?? boundaryBox(model, node, boxes.get(node.attachedTo)));
|
|
219
|
+
const flows = context.missingFlows ? model.flows.filter((flow) => context.missingFlows.has(flow.id)) : model.flows;
|
|
220
|
+
const routed = routeFlows(model, flows, layering, boxes);
|
|
221
|
+
for (const [id, route] of routed)
|
|
222
|
+
context.routes.set(id, route);
|
|
223
|
+
const scopeRoutes = new Map(routed);
|
|
224
|
+
for (const [id, box] of placeData(model, boxes, scopeRoutes, context.fixed)) {
|
|
225
|
+
const placed = { ...box };
|
|
226
|
+
for (let hit = taken.filter((other) => intersects(placed, other)); hit.length > 0; hit = taken.filter((other) => intersects(placed, other)))
|
|
227
|
+
placed.y = Math.max(...hit.map((other) => other.y + other.height)) + ROW_GAP;
|
|
228
|
+
taken.push(placed);
|
|
229
|
+
boxes.set(id, placed);
|
|
230
|
+
}
|
|
231
|
+
for (const item of model.data)
|
|
232
|
+
if (context.fixed.has(item.id))
|
|
233
|
+
boxes.set(item.id, context.fixed.get(item.id));
|
|
234
|
+
for (const [id, route] of routeExtras(model, boxes, context.missingFlows))
|
|
235
|
+
context.routes.set(id, route);
|
|
236
|
+
for (const [id, box] of boxes)
|
|
237
|
+
context.boxes.set(id, box);
|
|
238
|
+
for (const node of model.nodes) {
|
|
239
|
+
if (!node.content)
|
|
240
|
+
continue;
|
|
241
|
+
const frame = boxes.get(node.id);
|
|
242
|
+
placeScope(node.content, { x: frame.x + CONTAINER_MARGIN + (node.content.lanes?.indent ?? 0), y: frame.y + CONTAINER_MARGIN }, context);
|
|
243
|
+
if (node.content.lanes && context.lanes) {
|
|
244
|
+
const inner = { x: frame.x + CONTAINER_MARGIN, y: frame.y + CONTAINER_MARGIN, width: frame.width - 2 * CONTAINER_MARGIN, height: frame.height - 2 * CONTAINER_MARGIN };
|
|
245
|
+
const anchored = node.content.nodes.some((child) => context.fixed.has(child.id));
|
|
246
|
+
for (const [id, box] of node.content.lanes.boxes(inner, context.boxes, anchored))
|
|
247
|
+
context.lanes.set(id, box);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
export const place = (model) => {
|
|
252
|
+
measure(model);
|
|
253
|
+
const context = { fixed: new Map(), obstacles: new Map(), boxes: new Map(), routes: new Map() };
|
|
254
|
+
placeScope(model, ORIGIN, context);
|
|
255
|
+
return context;
|
|
256
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type LaneNode } from "./lanes.ts";
|
|
2
|
+
import { type Box, type Placement, type ProcessModel } from "./model.ts";
|
|
3
|
+
export interface StandaloneExisting {
|
|
4
|
+
fixed: Map<string, Box>;
|
|
5
|
+
obstacles: Map<string, Box[]>;
|
|
6
|
+
missingFlows?: Set<string>;
|
|
7
|
+
}
|
|
8
|
+
export declare const placeStandalone: (model: ProcessModel, lanes: LaneNode[] | undefined, existing?: StandaloneExisting) => Required<Placement>;
|