@stacksjs/defaults 0.75.361 → 0.75.362
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/ai/skills/stacks-ai/SKILL.md +22 -0
- package/ai/skills/stacks-native/CATALOG.md +2 -2
- package/ai/skills/stacks-technical-diagrams/LICENSE +202 -22
- package/ai/skills/stacks-technical-diagrams/LICENSE-MIT +22 -0
- package/ai/skills/stacks-technical-diagrams/NOTICE +26 -0
- package/ai/skills/stacks-technical-diagrams/SKILL.md +106 -266
- package/ai/skills/stacks-technical-diagrams/THIRD_PARTY_NOTICES.md +11 -2
- package/ai/skills/stacks-technical-diagrams/UPSTREAM.json +12 -0
- package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +2 -2
- package/ai/skills/stacks-technical-diagrams/assets/template.html +3655 -3798
- package/ai/skills/stacks-technical-diagrams/bin/archify.mjs +7290 -0
- package/ai/skills/stacks-technical-diagrams/bin/delivery-update.mjs +7 -0
- package/ai/skills/stacks-technical-diagrams/bin/diagrams +1 -0
- package/ai/skills/stacks-technical-diagrams/bin/finalize.mjs +1056 -0
- package/ai/skills/stacks-technical-diagrams/bin/open-artifact.mjs +72 -12
- package/ai/skills/stacks-technical-diagrams/bin/preview.mjs +348 -31
- package/ai/skills/stacks-technical-diagrams/bin/recover-output.mjs +41 -0
- package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +3 -2097
- package/ai/skills/stacks-technical-diagrams/bin/visual-check.mjs +2142 -157
- package/ai/skills/stacks-technical-diagrams/brand-marks/README.md +3 -3
- package/ai/skills/stacks-technical-diagrams/delta/architecture-delta.mjs +130 -33
- package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +25 -30
- package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +0 -20
- package/ai/skills/stacks-technical-diagrams/examples/archify-dev-activity.timeline.json +176 -0
- package/ai/skills/stacks-technical-diagrams/examples/archify-repository.tree.json +197 -0
- package/ai/skills/stacks-technical-diagrams/examples/async-job-roundtrip.sequence.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/brand-aware-delivery.architecture.json +3 -16
- package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +2 -7
- package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.base.architecture.json +2 -1
- package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.head.architecture.json +2 -1
- package/ai/skills/stacks-technical-diagrams/examples/checkout-request.waterfall.json +20 -0
- package/ai/skills/stacks-technical-diagrams/examples/class-payment-processors-rendered.html +14987 -0
- package/ai/skills/stacks-technical-diagrams/examples/class-payments-rendered.html +14787 -0
- package/ai/skills/stacks-technical-diagrams/examples/dataflow-product-analytics.html +14880 -0
- package/ai/skills/stacks-technical-diagrams/examples/delivery.explainer.json +155 -0
- package/ai/skills/stacks-technical-diagrams/examples/deployment-release.lifecycle.json +57 -34
- package/ai/skills/stacks-technical-diagrams/examples/erd-orders-rendered.html +15112 -0
- package/ai/skills/stacks-technical-diagrams/examples/event-stream.dataflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/example-rebuild.waterfall.json +212 -0
- package/ai/skills/stacks-technical-diagrams/examples/holiday-planning.workflow.json +143 -0
- package/ai/skills/stacks-technical-diagrams/examples/incident-response.workflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/lifecycle-agent-run.html +14892 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/de.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/fr.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/fr.partial.json +22 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/it.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/ja.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/pt.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/locales/ru.json +463 -0
- package/ai/skills/stacks-technical-diagrams/examples/orders.erd.json +337 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-incident.timeline.json +24 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-platform.tree.json +22 -0
- package/ai/skills/stacks-technical-diagrams/examples/payment-processors.class.json +177 -0
- package/ai/skills/stacks-technical-diagrams/examples/payments.class.json +85 -0
- package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/production-deployment.architecture.json +3 -23
- package/ai/skills/stacks-technical-diagrams/examples/release-delivery.workflow.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/sequence-cache-miss-request.html +14895 -0
- package/ai/skills/stacks-technical-diagrams/examples/subscription-billing-rendered.html +15369 -0
- package/ai/skills/stacks-technical-diagrams/examples/subscription-billing.erd.json +548 -0
- package/ai/skills/stacks-technical-diagrams/examples/timeline-archify-dev-activity-rendered.html +15002 -0
- package/ai/skills/stacks-technical-diagrams/examples/timeline-payment-incident-rendered.html +14772 -0
- package/ai/skills/stacks-technical-diagrams/examples/tree-archify-repository-rendered.html +15071 -0
- package/ai/skills/stacks-technical-diagrams/examples/tree-payment-platform-rendered.html +14810 -0
- package/ai/skills/stacks-technical-diagrams/examples/waterfall-checkout-request-rendered.html +14754 -0
- package/ai/skills/stacks-technical-diagrams/examples/waterfall-example-rebuild-rendered.html +14940 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app-rendered.html +14889 -0
- package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +1 -6
- package/ai/skills/stacks-technical-diagrams/examples/workflow-agent-tool-call-rendered.html +14907 -0
- package/ai/skills/stacks-technical-diagrams/explainer/browser-check.mjs +208 -0
- package/ai/skills/stacks-technical-diagrams/explainer/charts.mjs +70 -0
- package/ai/skills/stacks-technical-diagrams/explainer/check.mjs +168 -0
- package/ai/skills/stacks-technical-diagrams/explainer/diagrams.mjs +518 -0
- package/ai/skills/stacks-technical-diagrams/explainer/diff.mjs +124 -0
- package/ai/skills/stacks-technical-diagrams/explainer/inline.mjs +155 -0
- package/ai/skills/stacks-technical-diagrams/explainer/layout.mjs +35 -0
- package/ai/skills/stacks-technical-diagrams/explainer/page-bundle.mjs +16 -0
- package/ai/skills/stacks-technical-diagrams/explainer/parse.mjs +300 -0
- package/ai/skills/stacks-technical-diagrams/explainer/patch.mjs +117 -0
- package/ai/skills/stacks-technical-diagrams/explainer/prepare.mjs +131 -0
- package/ai/skills/stacks-technical-diagrams/explainer/reader.css +472 -0
- package/ai/skills/stacks-technical-diagrams/explainer/reader.js +667 -0
- package/ai/skills/stacks-technical-diagrams/explainer/render.mjs +224 -0
- package/ai/skills/stacks-technical-diagrams/explainer/revision.mjs +20 -0
- package/ai/skills/stacks-technical-diagrams/explainer/sources.mjs +318 -0
- package/ai/skills/stacks-technical-diagrams/explainer/validate.mjs +138 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/LICENSE +40 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/README.md +5 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/3DKXF73K.js +4 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/42UTT654.js +3 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/DKTLEL5E.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EEIX5ZYH.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EJ5KOXYD.js +25 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/GUJTEFZK.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/IZOKQ2GT.js +4 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/L4LY553T.js +3 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/MSKIJHB2.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/RVARI57O.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UKSIWDDN.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UPTXNAXN.js +2 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/XTDUIGUI.js +9 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/YAYTP6L5.js +9 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/core.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/pie.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/quadrantChart.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/radar.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/sankey.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/venn.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/xychart.js +1 -0
- package/ai/skills/stacks-technical-diagrams/explainer/writing.mjs +139 -0
- package/ai/skills/stacks-technical-diagrams/locales/README.md +40 -0
- package/ai/skills/stacks-technical-diagrams/locales/ar.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/en.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/es.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/ko.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/manifest.json +10 -0
- package/ai/skills/stacks-technical-diagrams/locales/zh-CN.json +488 -0
- package/ai/skills/stacks-technical-diagrams/locales/zh-TW.json +488 -0
- package/ai/skills/stacks-technical-diagrams/migrations/workflow-v2.mjs +1 -0
- package/ai/skills/stacks-technical-diagrams/recipes/scenarios.mjs +115 -51
- package/ai/skills/stacks-technical-diagrams/references/architecture-layout-repair.md +18 -0
- package/ai/skills/stacks-technical-diagrams/references/authoring-contract.md +453 -51
- package/ai/skills/stacks-technical-diagrams/references/authoring-defaults.md +57 -0
- package/ai/skills/stacks-technical-diagrams/references/brand-marks.md +57 -3
- package/ai/skills/stacks-technical-diagrams/references/delivery-contract.md +603 -58
- package/ai/skills/stacks-technical-diagrams/references/explainer-authoring.md +274 -0
- package/ai/skills/stacks-technical-diagrams/references/repository-authoring.md +126 -0
- package/ai/skills/stacks-technical-diagrams/references/update-awareness.md +13 -0
- package/ai/skills/stacks-technical-diagrams/references/viewer-runtime.md +18 -11
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +1 -1
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/labels.mjs +2 -0
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +550 -424
- package/ai/skills/stacks-technical-diagrams/renderers/architecture/routing.mjs +1469 -0
- package/ai/skills/stacks-technical-diagrams/renderers/class/README.md +129 -0
- package/ai/skills/stacks-technical-diagrams/renderers/class/render-class.mjs +1095 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +89 -17
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/dataflow-compiler.mjs +1605 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/joint-label-routing.mjs +165 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/label-capacity.mjs +81 -0
- package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +14 -474
- package/ai/skills/stacks-technical-diagrams/renderers/erd/README.md +246 -0
- package/ai/skills/stacks-technical-diagrams/renderers/erd/grid.mjs +150 -0
- package/ai/skills/stacks-technical-diagrams/renderers/erd/render-erd.mjs +1588 -0
- package/ai/skills/stacks-technical-diagrams/renderers/explainer/render-explainer.mjs +42 -0
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +83 -94
- package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +990 -417
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +49 -17
- package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +735 -94
- package/ai/skills/stacks-technical-diagrams/renderers/shared/atomic-output.mjs +1982 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/automatic-labels.mjs +290 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/brand-marks.mjs +169 -26
- package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +266 -54
- package/ai/skills/stacks-technical-diagrams/renderers/shared/desktop-readability.mjs +106 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/diagnostics.mjs +0 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +268 -2
- package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +774 -124
- package/ai/skills/stacks-technical-diagrams/renderers/shared/i18n.mjs +289 -556
- package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +18 -1
- package/ai/skills/stacks-technical-diagrams/renderers/shared/legend.mjs +16 -6
- package/ai/skills/stacks-technical-diagrams/renderers/shared/node-card-style.mjs +32 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/output-path.mjs +329 -229
- package/ai/skills/stacks-technical-diagrams/renderers/shared/owned-temp-directory.mjs +82 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/path-semantics.mjs +1017 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/portable-path.mjs +408 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-evidence.mjs +235 -79
- package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-location.mjs +17 -4
- package/ai/skills/stacks-technical-diagrams/renderers/shared/route-quality.mjs +670 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/sidecar-path.mjs +38 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/software-notices.mjs +6 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/spatial-grid.mjs +66 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/svg-path-analysis.mjs +135 -0
- package/ai/skills/stacks-technical-diagrams/renderers/shared/text-fit.mjs +59 -3
- package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +63 -35
- package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +2 -5
- package/ai/skills/stacks-technical-diagrams/renderers/timeline/README.md +76 -0
- package/ai/skills/stacks-technical-diagrams/renderers/timeline/render-timeline.mjs +576 -0
- package/ai/skills/stacks-technical-diagrams/renderers/tree/README.md +96 -0
- package/ai/skills/stacks-technical-diagrams/renderers/tree/render-tree.mjs +573 -0
- package/ai/skills/stacks-technical-diagrams/renderers/waterfall/README.md +73 -0
- package/ai/skills/stacks-technical-diagrams/renderers/waterfall/render-waterfall.mjs +413 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +137 -23
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +18 -6
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-compiler.mjs +2230 -710
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-migration-geometry.mjs +17 -0
- package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-placement-repair.mjs +94 -0
- package/ai/skills/stacks-technical-diagrams/schemas/README.md +87 -40
- package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +7 -33
- package/ai/skills/stacks-technical-diagrams/schemas/class.schema.json +345 -0
- package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +68 -2
- package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +14 -3
- package/ai/skills/stacks-technical-diagrams/schemas/erd.schema.json +381 -0
- package/ai/skills/stacks-technical-diagrams/schemas/explainer.schema.json +927 -0
- package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +23 -110
- package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +15 -4
- package/ai/skills/stacks-technical-diagrams/schemas/timeline.schema.json +160 -0
- package/ai/skills/stacks-technical-diagrams/schemas/tree.schema.json +141 -0
- package/ai/skills/stacks-technical-diagrams/schemas/waterfall.schema.json +147 -0
- package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +14 -3
- package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +876 -243
- package/ai/skills/stacks-technical-diagrams/scripts/check-update.mjs +1717 -0
- package/ai/skills/stacks-technical-diagrams/scripts/delivery-update-child.mjs +26 -0
- package/ai/skills/stacks-technical-diagrams/scripts/generate-brand-marks.mjs +141 -0
- package/ai/skills/stacks-technical-diagrams/scripts/generate-validators.mjs +105 -0
- package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +11 -1
- package/ai/skills/stacks-technical-diagrams/scripts/update-contract.mjs +187 -0
- package/ai/skills/stacks-technical-diagrams/skill-release.json +10 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
|
@@ -1,483 +1,23 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { resolveLegend, renderLegend as renderResolvedLegend } from '../shared/legend.mjs';
|
|
7
|
-
import { availableNodeTextWidth, fittedNodeFontSize, minimumNodeTextWidth } from '../shared/text-fit.mjs';
|
|
8
|
-
import { brandLabelFitWidth, brandMetadataFor, brandTopRailProblem, renderBrandMark } from '../shared/brand-marks.mjs';
|
|
9
|
-
import { translateMessage as i18nText } from '../shared/i18n.mjs';
|
|
10
|
-
import {
|
|
11
|
-
asArray,
|
|
12
|
-
isFinitePoint,
|
|
13
|
-
rectsOverlap,
|
|
14
|
-
cleanEndpointSideProblems,
|
|
15
|
-
cleanFlowProblems,
|
|
16
|
-
cleanCrossingProblems,
|
|
17
|
-
cleanAmbiguousCorridorProblems,
|
|
18
|
-
cleanBorderRunProblems,
|
|
19
|
-
cleanRouteRhythmProblems,
|
|
20
|
-
cleanLabelRouteClearanceProblems,
|
|
21
|
-
suggestLabelObstacleFix,
|
|
22
|
-
suggestLabelPairFix,
|
|
23
|
-
anchor,
|
|
24
|
-
automaticPortSpread,
|
|
25
|
-
defaultFromSide,
|
|
26
|
-
defaultToSide,
|
|
27
|
-
chosenSide,
|
|
28
|
-
polylinePath,
|
|
29
|
-
routePointsValue,
|
|
30
|
-
labelPoint,
|
|
31
|
-
componentFill,
|
|
32
|
-
componentText,
|
|
33
|
-
arrowClassMap,
|
|
34
|
-
variantAccent
|
|
35
|
-
} from '../shared/geometry.mjs';
|
|
36
|
-
|
|
37
|
-
const nodeTextFit = {
|
|
38
|
-
sublabelPreferred: 7,
|
|
39
|
-
sublabelMinimum: 6,
|
|
40
|
-
tagPreferred: 7,
|
|
41
|
-
tagMinimum: 6,
|
|
42
|
-
};
|
|
3
|
+
import { loadDiagramWithBrandMarks, writeDiagram } from '../shared/cli.mjs';
|
|
4
|
+
import { recordDiagnostic } from '../shared/diagnostics.mjs';
|
|
5
|
+
import { compileDataflow } from './dataflow-compiler.mjs';
|
|
43
6
|
|
|
44
7
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
45
|
-
const { diagram: dataflow, template, outPath } = await loadDiagramWithBrandMarks({
|
|
8
|
+
const { diagram: dataflow, template, outPath, sourceEvidence } = await loadDiagramWithBrandMarks({
|
|
46
9
|
rendererDir: __dirname,
|
|
47
10
|
diagramType: 'dataflow',
|
|
48
|
-
defaultExample: 'product-analytics.dataflow.json'
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const viewBox = dataflow.meta?.viewBox || [940, 720];
|
|
52
|
-
const layout = {
|
|
53
|
-
stageY: 46,
|
|
54
|
-
stageH: 36,
|
|
55
|
-
stageBottomPad: 74,
|
|
56
|
-
leftX: 100,
|
|
57
|
-
colGap: 215,
|
|
58
|
-
stageW: 168,
|
|
59
|
-
nodeW: 112,
|
|
60
|
-
nodeH: 58,
|
|
61
|
-
rowYs: [128, 242, 356, 470, 584],
|
|
62
|
-
labelH: 16
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
function flowLabelSize(flow) {
|
|
66
|
-
const longestLine = Math.max(textUnits(flow.label), textUnits(flow.classification || ''));
|
|
67
|
-
return {
|
|
68
|
-
width: Math.round(Math.max(34, longestLine * 4.9 + 12) * 10) / 10,
|
|
69
|
-
height: flow.classification ? 27 : layout.labelH,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function stageX(index) {
|
|
74
|
-
return layout.leftX + index * layout.colGap;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function stageFrame(stage, index) {
|
|
78
|
-
return {
|
|
79
|
-
id: index,
|
|
80
|
-
label: stage.label,
|
|
81
|
-
kind: 'stage',
|
|
82
|
-
x: stageX(index) - layout.stageW / 2,
|
|
83
|
-
y: layout.stageY,
|
|
84
|
-
width: layout.stageW,
|
|
85
|
-
height: viewBox[1] - layout.stageY - layout.stageBottomPad,
|
|
86
|
-
radius: 10,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const compositionFrames = asArray(dataflow.stages).map(stageFrame);
|
|
91
|
-
|
|
92
|
-
function measureNode(node) {
|
|
93
|
-
const width = node.width || layout.nodeW;
|
|
94
|
-
const height = node.height || layout.nodeH;
|
|
95
|
-
const cx = stageX(node.stage);
|
|
96
|
-
const y = layout.rowYs[node.row] + (node.yOffset || 0);
|
|
97
|
-
return {
|
|
98
|
-
...node,
|
|
99
|
-
width,
|
|
100
|
-
height,
|
|
101
|
-
cx,
|
|
102
|
-
cy: y + height / 2,
|
|
103
|
-
x: cx - width / 2,
|
|
104
|
-
y
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const nodes = new Map(asArray(dataflow.nodes).map((node) => [node.id, measureNode(node)]));
|
|
109
|
-
const nodeSteps = new Map();
|
|
110
|
-
for (const [index, flow] of asArray(dataflow.flows).entries()) {
|
|
111
|
-
if (!nodeSteps.has(flow.from)) nodeSteps.set(flow.from, index);
|
|
112
|
-
if (!nodeSteps.has(flow.to)) nodeSteps.set(flow.to, index + 1);
|
|
113
|
-
}
|
|
114
|
-
for (const [index, node] of asArray(dataflow.nodes).entries()) {
|
|
115
|
-
if (!nodeSteps.has(node.id)) nodeSteps.set(node.id, index);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function validateDataflow() {
|
|
119
|
-
const problems = [];
|
|
120
|
-
if (nodes.size !== asArray(dataflow.nodes).length) problems.push('Node ids must be unique.');
|
|
121
|
-
|
|
122
|
-
const stageCount = asArray(dataflow.stages).length;
|
|
123
|
-
for (const node of nodes.values()) {
|
|
124
|
-
if (typeof node.stage !== 'number' || node.stage < 0 || node.stage >= stageCount) {
|
|
125
|
-
problems.push(`Node "${node.id}" uses invalid stage ${node.stage} - valid stages are 0..${stageCount - 1}.`);
|
|
126
|
-
}
|
|
127
|
-
if (typeof node.row !== 'number' || node.row < 0 || node.row >= layout.rowYs.length) {
|
|
128
|
-
problems.push(`Node "${node.id}" uses invalid row ${node.row} - valid rows are 0..${layout.rowYs.length - 1}.`);
|
|
129
|
-
}
|
|
130
|
-
if (!isFinitePoint(node.x, node.y, node.cx, node.cy)) {
|
|
131
|
-
problems.push(`Node "${node.id}" produced non-finite coordinates - check stage, row, width, height, and yOffset are numbers.`);
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
if (node.x < 24 || node.x + node.width > viewBox[0] - 24) {
|
|
135
|
-
problems.push(`Node "${node.id}" exceeds the horizontal bounds of the viewBox - reduce node.width or increase meta.viewBox[0].`);
|
|
136
|
-
}
|
|
137
|
-
if (node.y < layout.stageY + layout.stageH + 22 || node.y + node.height > viewBox[1] - layout.stageBottomPad) {
|
|
138
|
-
problems.push(`Node "${node.id}" exceeds the readable diagram area - keep y between ${layout.stageY + layout.stageH + 22} and ${viewBox[1] - layout.stageBottomPad} (adjust row/yOffset or increase meta.viewBox[1]).`);
|
|
139
|
-
}
|
|
140
|
-
const estLabelW = textUnits(node.label) * 6.2;
|
|
141
|
-
if (estLabelW > node.width + 6) {
|
|
142
|
-
problems.push(`Label "${node.label}" (~${Math.round(estLabelW)}px) is wider than node "${node.id}" (${node.width}px) - shorten the label or increase node.width.`);
|
|
143
|
-
}
|
|
144
|
-
const brandRailProblem = brandTopRailProblem(node, node.width, 8);
|
|
145
|
-
if (brandRailProblem) problems.push(brandRailProblem);
|
|
146
|
-
// sublabel and tag render as single unwrapped <text> elements; shrink-to-fit
|
|
147
|
-
// handles the ordinary case, this rejects what it cannot rescue.
|
|
148
|
-
const availableTextW = availableNodeTextWidth(node.width);
|
|
149
|
-
for (const [field, value, minimum] of [
|
|
150
|
-
['Sublabel', node.sublabel, nodeTextFit.sublabelMinimum],
|
|
151
|
-
['Tag', node.tag, nodeTextFit.tagMinimum],
|
|
152
|
-
]) {
|
|
153
|
-
if (!value) continue;
|
|
154
|
-
const minimumW = minimumNodeTextWidth(value, minimum);
|
|
155
|
-
if (minimumW > availableTextW) {
|
|
156
|
-
problems.push(`${field} "${value}" needs ~${Math.ceil(minimumW)}px at the ${minimum}px legible minimum, but node "${node.id}" provides ${availableTextW}px - shorten the ${field.toLowerCase()} or increase node.width.`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const nodeList = asArray(dataflow.nodes);
|
|
162
|
-
for (let i = 0; i < nodeList.length; i += 1) {
|
|
163
|
-
for (let j = i + 1; j < nodeList.length; j += 1) {
|
|
164
|
-
const a = nodes.get(nodeList[i].id);
|
|
165
|
-
const b = nodes.get(nodeList[j].id);
|
|
166
|
-
if (rectsOverlap(a, b, 10)) {
|
|
167
|
-
problems.push(`Nodes "${a.id}" and "${b.id}" are less than 10px apart - move one to another stage/row or adjust yOffset.`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
for (const flow of asArray(dataflow.flows)) {
|
|
173
|
-
if (!nodes.has(flow.from)) problems.push(`Flow "${flow.label || flow.from}" references unknown source "${flow.from}".`);
|
|
174
|
-
if (!nodes.has(flow.to)) problems.push(`Flow "${flow.label || flow.to}" references unknown target "${flow.to}".`);
|
|
175
|
-
if (!flow.label) problems.push(`Flow "${flow.from}" -> "${flow.to}" must include a short data label.`);
|
|
176
|
-
if (nodes.has(flow.from) && nodes.has(flow.to)) {
|
|
177
|
-
const routed = pathFor(flow);
|
|
178
|
-
const [start, end] = [routed.points[0], routed.points[routed.points.length - 1]];
|
|
179
|
-
const distance = Math.hypot(end[0] - start[0], end[1] - start[1]);
|
|
180
|
-
if (distance < 34) problems.push(`Flow "${flow.label}" is too short (${Math.round(distance)}px; minimum 34px) - route it through a channel or spread its nodes.`);
|
|
181
|
-
if (Array.isArray(flow.via)) {
|
|
182
|
-
for (let segmentIndex = 0; segmentIndex < routed.points.length - 1; segmentIndex += 1) {
|
|
183
|
-
const segmentStart = routed.points[segmentIndex];
|
|
184
|
-
const segmentEnd = routed.points[segmentIndex + 1];
|
|
185
|
-
const isDiagonal = Math.abs(segmentStart[0] - segmentEnd[0]) > 0.01
|
|
186
|
-
&& Math.abs(segmentStart[1] - segmentEnd[1]) > 0.01;
|
|
187
|
-
if (!isDiagonal) continue;
|
|
188
|
-
const viaIndex = Math.min(segmentIndex, flow.via.length - 1);
|
|
189
|
-
problems.push(`Flow "${flow.label}" has a diagonal segment from (${segmentStart.join(', ')}) to (${segmentEnd.join(', ')}) - align via[${viaIndex}] with its adjacent point by sharing the same x or y coordinate.`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
problems.push(...cleanEndpointSideProblems({
|
|
196
|
-
relations: dataflow.flows,
|
|
197
|
-
endpointIds: new Set(nodes.keys()),
|
|
198
|
-
pathFor,
|
|
199
|
-
diagramType: 'dataflow',
|
|
200
|
-
relationCollection: 'flows',
|
|
201
|
-
fromSideFor: (flow) => flowSides(flow).fromSide,
|
|
202
|
-
toSideFor: (flow) => flowSides(flow).toSide,
|
|
203
|
-
routeHint: 'keep automatic routing, or choose fromSide/toSide and via points whose first and final segments cross node borders perpendicularly',
|
|
204
|
-
}));
|
|
205
|
-
problems.push(...cleanFlowProblems({
|
|
206
|
-
relations: dataflow.flows,
|
|
207
|
-
obstacles: nodes.values(),
|
|
208
|
-
pathFor,
|
|
209
|
-
diagramType: 'dataflow',
|
|
210
|
-
relationCollection: 'flows',
|
|
211
|
-
obstacleKind: 'node',
|
|
212
|
-
routeHint: 'adjust fromSide/toSide, set route/via or channelX/channelY, or move the node to another stage/row'
|
|
213
|
-
}));
|
|
214
|
-
problems.push(...cleanCrossingProblems({
|
|
215
|
-
relations: dataflow.flows,
|
|
216
|
-
endpointIds: new Set(nodes.keys()),
|
|
217
|
-
pathFor,
|
|
218
|
-
diagramType: 'dataflow',
|
|
219
|
-
relationCollection: 'flows',
|
|
220
|
-
profile: dataflow.meta?.quality_profile,
|
|
221
|
-
routeHint: 'adjust route/via or channelX/channelY so the flows use separate stage corridors'
|
|
222
|
-
}));
|
|
223
|
-
problems.push(...cleanAmbiguousCorridorProblems({
|
|
224
|
-
relations: dataflow.flows,
|
|
225
|
-
endpointIds: new Set(nodes.keys()),
|
|
226
|
-
pathFor,
|
|
227
|
-
diagramType: 'dataflow',
|
|
228
|
-
relationCollection: 'flows',
|
|
229
|
-
profile: dataflow.meta?.quality_profile,
|
|
230
|
-
routeHint: 'adjust route/via or channelX/channelY so unrelated flows do not visually merge'
|
|
231
|
-
}));
|
|
232
|
-
problems.push(...cleanBorderRunProblems({
|
|
233
|
-
relations: dataflow.flows,
|
|
234
|
-
endpointIds: new Set(nodes.keys()),
|
|
235
|
-
frames: compositionFrames,
|
|
236
|
-
pathFor,
|
|
237
|
-
diagramType: 'dataflow',
|
|
238
|
-
relationCollection: 'flows',
|
|
239
|
-
profile: dataflow.meta?.quality_profile,
|
|
240
|
-
routeHint: 'adjust route/via or channelX/channelY so the flow crosses the stage perpendicularly instead of following its border'
|
|
241
|
-
}));
|
|
242
|
-
problems.push(...cleanRouteRhythmProblems({
|
|
243
|
-
relations: dataflow.flows,
|
|
244
|
-
endpointIds: new Set(nodes.keys()),
|
|
245
|
-
pathFor,
|
|
246
|
-
diagramType: 'dataflow',
|
|
247
|
-
relationCollection: 'flows',
|
|
248
|
-
profile: dataflow.meta?.quality_profile,
|
|
249
|
-
routeHint: 'adjust route/via or channelX/channelY so each turn uses a clear inter-stage corridor'
|
|
250
|
-
}));
|
|
251
|
-
|
|
252
|
-
const labelRects = [];
|
|
253
|
-
for (const [flowIndex, flow] of asArray(dataflow.flows).entries()) {
|
|
254
|
-
if (!flow.label || !nodes.has(flow.from) || !nodes.has(flow.to)) continue;
|
|
255
|
-
const [lx, ly] = labelPoint(flow, pathFor(flow).points);
|
|
256
|
-
const { width, height } = flowLabelSize(flow);
|
|
257
|
-
labelRects.push({ relation: flow, relationIndex: flowIndex, label: flow.label, x: lx - width / 2, y: ly - 11, width, height, lx, ly });
|
|
258
|
-
}
|
|
259
|
-
for (const rect of labelRects) {
|
|
260
|
-
for (const node of nodes.values()) {
|
|
261
|
-
if (rectsOverlap(rect, node, -2)) {
|
|
262
|
-
problems.push(`Label "${rect.label}" overlaps node "${node.id}" - adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, node, 'node')}`);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
for (let i = 0; i < labelRects.length; i += 1) {
|
|
267
|
-
for (let j = i + 1; j < labelRects.length; j += 1) {
|
|
268
|
-
if (rectsOverlap(labelRects[i], labelRects[j], -2)) {
|
|
269
|
-
problems.push(`Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap - adjust labelDx/labelDy.\n${suggestLabelPairFix(labelRects[i], labelRects[j])}`);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
problems.push(...cleanLabelRouteClearanceProblems({
|
|
274
|
-
relations: dataflow.flows,
|
|
275
|
-
labels: labelRects,
|
|
276
|
-
endpointIds: new Set(nodes.keys()),
|
|
277
|
-
pathFor,
|
|
278
|
-
diagramType: 'dataflow',
|
|
279
|
-
relationCollection: 'flows',
|
|
280
|
-
profile: dataflow.meta?.quality_profile,
|
|
281
|
-
routeHint: 'adjust labelAt, labelDx, labelDy, or labelSegment; otherwise adjust the other flow route/via/channelX/channelY'
|
|
282
|
-
}));
|
|
283
|
-
|
|
284
|
-
const lastStageX = stageX(asArray(dataflow.stages).length - 1);
|
|
285
|
-
if (lastStageX + layout.stageW / 2 > viewBox[0] - 24) {
|
|
286
|
-
problems.push(`Stages exceed viewBox width - set meta.viewBox[0] to at least ${Math.ceil(lastStageX + layout.stageW / 2 + 24)}.`);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
if (problems.length) {
|
|
290
|
-
throwDiagnosticProblems('Data-flow layout validation failed', problems, {
|
|
291
|
-
subject: { diagramType: 'dataflow' },
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function routeVia(flow, from, to, start, end) {
|
|
297
|
-
if (flow.via) return flow.via;
|
|
298
|
-
switch (flow.route || 'auto') {
|
|
299
|
-
case 'straight':
|
|
300
|
-
return [];
|
|
301
|
-
case 'vertical-channel': {
|
|
302
|
-
const x = flow.channelX ?? start[0] + (end[0] > start[0] ? 44 : -44);
|
|
303
|
-
return [[x, start[1]], [x, end[1]]];
|
|
304
|
-
}
|
|
305
|
-
case 'bottom-channel': {
|
|
306
|
-
const y = flow.channelY ?? Math.max(from.y + from.height, to.y + to.height) + 26;
|
|
307
|
-
return [[start[0], y], [end[0], y]];
|
|
308
|
-
}
|
|
309
|
-
case 'top-channel': {
|
|
310
|
-
const y = flow.channelY ?? Math.min(from.y, to.y) - 24;
|
|
311
|
-
return [[start[0], y], [end[0], y]];
|
|
312
|
-
}
|
|
313
|
-
case 'auto':
|
|
314
|
-
default: {
|
|
315
|
-
if (Math.abs(start[1] - end[1]) < 4) return [];
|
|
316
|
-
const midX = start[0] + (end[0] - start[0]) / 2;
|
|
317
|
-
return [[midX, start[1]], [midX, end[1]]];
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const pathCache = new Map();
|
|
323
|
-
|
|
324
|
-
function flowSides(flow) {
|
|
325
|
-
const from = nodes.get(flow.from);
|
|
326
|
-
const to = nodes.get(flow.to);
|
|
327
|
-
return {
|
|
328
|
-
fromSide: chosenSide(flow.fromSide, defaultFromSide(from, to)),
|
|
329
|
-
toSide: chosenSide(flow.toSide, defaultToSide(from, to)),
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const automaticPorts = automaticPortSpread(dataflow.flows, nodes, {
|
|
334
|
-
sideFor: (flow, endpoint) => flowSides(flow)[endpoint === 'source' ? 'fromSide' : 'toSide'],
|
|
11
|
+
defaultExample: 'product-analytics.dataflow.json',
|
|
335
12
|
});
|
|
336
13
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
const end = ports?.to || anchor(to, toSide);
|
|
345
|
-
// Drop consecutive duplicate points so a purely vertical (or horizontal)
|
|
346
|
-
// auto-route never emits a zero-length final segment - SVG derives
|
|
347
|
-
// marker-end orientation from the last segment, and a degenerate segment
|
|
348
|
-
// leaves the arrowhead angle undefined (see #169).
|
|
349
|
-
const rawPoints = [start, ...routeVia(flow, from, to, start, end), end];
|
|
350
|
-
const points = [];
|
|
351
|
-
for (const p of rawPoints) {
|
|
352
|
-
const prev = points.at(-1);
|
|
353
|
-
if (!prev || Math.abs(p[0] - prev[0]) > 0.0001 || Math.abs(p[1] - prev[1]) > 0.0001) {
|
|
354
|
-
points.push(p);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
// Guard against an all-degenerate route (e.g. start === end): keep both
|
|
358
|
-
// endpoints so the path is still well-formed even if the marker is hidden.
|
|
359
|
-
if (points.length < 2) points.push(end);
|
|
360
|
-
const routed = { d: polylinePath(points), points };
|
|
361
|
-
pathCache.set(flow, routed);
|
|
362
|
-
return routed;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function renderStage(stage, index) {
|
|
366
|
-
const frame = compositionFrames[index];
|
|
367
|
-
const cx = stageX(index);
|
|
368
|
-
return ` <rect data-graph-role="structural-frame" data-composition-frame-kind="stage" data-composition-frame-id="${index}" x="${frame.x}" y="${frame.y}" width="${frame.width}" height="${frame.height}" rx="${frame.radius}" class="c-lane" stroke-width="1"/>
|
|
369
|
-
<text x="${cx}" y="${layout.stageY + 22}" class="t-dim" font-size="9" font-weight="600" text-anchor="middle">${String(index + 1).padStart(2, '0')} / ${esc(stage.label)}</text>`;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function renderNode(node) {
|
|
373
|
-
const fill = componentFill[node.type] || 'c-external';
|
|
374
|
-
const accent = componentText[node.type] || 't-muted';
|
|
375
|
-
const hasSub = node.sublabel != null && node.sublabel !== '';
|
|
376
|
-
const sub = hasSub
|
|
377
|
-
? `\n <text data-detail="context" x="${node.cx}" y="${node.y + 37}" class="t-muted" font-size="${fittedNodeFontSize(node.sublabel, node.width, nodeTextFit.sublabelPreferred, nodeTextFit.sublabelMinimum)}" text-anchor="middle">${esc(node.sublabel)}</text>`
|
|
378
|
-
: '';
|
|
379
|
-
const tag = node.tag
|
|
380
|
-
? `\n <text data-detail="fine" x="${node.cx}" y="${node.y + node.height - 11}" class="${accent}" font-size="${fittedNodeFontSize(node.tag, node.width, nodeTextFit.tagPreferred, nodeTextFit.tagMinimum)}" text-anchor="middle">${esc(node.tag)}</text>`
|
|
381
|
-
: '';
|
|
382
|
-
const stage = asArray(dataflow.stages)[node.stage];
|
|
383
|
-
const context = stage
|
|
384
|
-
? `${String(node.stage + 1).padStart(2, '0')} / ${stage.label}`
|
|
385
|
-
: i18nText(dataflow.meta.locale, 'node.context.dataflow');
|
|
386
|
-
const brand = renderBrandMark(node, { x: node.x + node.width - 22, y: node.y + 6 });
|
|
387
|
-
const labelFontSize = fittedNodeFontSize(node.label, brandLabelFitWidth(node, node.width), 10, 8);
|
|
388
|
-
const passport = { kind: node.type, sublabel: node.sublabel, tag: node.tag, context, ...brandMetadataFor(node) };
|
|
389
|
-
return ` <g ${focusNodeAttrs(node.id, node.label, passport, dataflow.meta.locale)}>
|
|
390
|
-
${focusNodeTitle(node.label, passport)}
|
|
391
|
-
<rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="6" class="c-mask"/>
|
|
392
|
-
<rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="6" class="${fill}"${animateAttr(dataflow.meta, 'node', nodeSteps.get(node.id))} stroke-width="1.5"/>
|
|
393
|
-
${renderSemanticSigil(node.type, { x: node.x + 6, y: node.y + 6 })}${brand ? `\n ${brand}` : ''}
|
|
394
|
-
<text data-node-label=""${hasSub ? ' data-detail-anchor=""' : ''} x="${node.cx}" y="${node.y + 21}" class="t-primary" font-size="${labelFontSize}" font-weight="600" text-anchor="middle">${esc(node.label)}</text>${sub}${tag}
|
|
395
|
-
</g>`;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function renderFlowPath(flow, index) {
|
|
399
|
-
const [cls, marker] = arrowClassMap[flow.variant || 'default'] || arrowClassMap.default;
|
|
400
|
-
const routed = pathFor(flow);
|
|
401
|
-
const strokeWidth = flow.width || (flow.variant === 'emphasis' ? 1.8 : 1.4);
|
|
402
|
-
return ` <path ${focusEdgeAttrs(flow.from, flow.to, flow.label, index, flow.id)} data-composition-points="${routePointsValue(routed.points)}" d="${routed.d}" class="${cls}"${animateAttr(dataflow.meta, 'edge', index)} stroke-width="${strokeWidth}" marker-end="url(#${marker})"/>`;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function renderFlowLabel(flow, index) {
|
|
406
|
-
const routed = pathFor(flow);
|
|
407
|
-
const [lx, ly] = labelPoint(flow, routed.points);
|
|
408
|
-
const { width: labelW, height: labelH } = flowLabelSize(flow);
|
|
409
|
-
const classification = flow.classification
|
|
410
|
-
? `\n <text data-detail="fine" x="${lx}" y="${ly + 11}" class="t-dim" font-size="7" text-anchor="middle">${esc(flow.classification)}</text>`
|
|
411
|
-
: '';
|
|
412
|
-
return ` <g data-detail="context" ${focusEdgeAttrs(flow.from, flow.to, flow.label, index, flow.id)}>
|
|
413
|
-
<rect x="${lx - labelW / 2}" y="${ly - 11}" width="${labelW}" height="${labelH}" rx="4" class="c-mask"/>
|
|
414
|
-
<text x="${lx}" y="${ly}" class="${variantAccent(flow.variant)}" font-size="8" text-anchor="middle">${esc(flow.label)}</text>${classification}
|
|
415
|
-
</g>`;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const LEGEND_CATALOG = [
|
|
419
|
-
{ kind: 'emphasis', className: 'a-emphasis', marker: 'arrowhead-emphasis', strokeWidth: 1.8, swatchWidth: 34, swatchGap: 9, interactive: false },
|
|
420
|
-
{ kind: 'security', className: 'a-security', marker: 'arrowhead-security', swatchWidth: 34, swatchGap: 9, interactive: false },
|
|
421
|
-
{ kind: 'dashed', className: 'a-dashed', marker: 'arrowhead-dashed', swatchWidth: 34, swatchGap: 9, interactive: false },
|
|
422
|
-
{ kind: 'database' },
|
|
423
|
-
{ kind: 'default', className: 'a-default', marker: 'arrowhead', swatchWidth: 34, swatchGap: 9, interactive: false },
|
|
424
|
-
].map((entry) => ({
|
|
425
|
-
...entry,
|
|
426
|
-
label: i18nText(dataflow.meta.locale, `legend.dataflow.${entry.kind}`),
|
|
427
|
-
}));
|
|
428
|
-
|
|
429
|
-
function renderLegend() {
|
|
430
|
-
const presentKinds = new Set(asArray(dataflow.flows).map((flow) => flow.variant || 'default'));
|
|
431
|
-
if ([...nodes.values()].some((node) => node.type === 'database')) presentKinds.add('database');
|
|
432
|
-
const entries = resolveLegend(dataflow.meta?.legend, LEGEND_CATALOG, presentKinds);
|
|
433
|
-
return renderResolvedLegend({
|
|
434
|
-
entries,
|
|
435
|
-
locale: dataflow.meta.locale,
|
|
436
|
-
layout: {
|
|
437
|
-
x: 40,
|
|
438
|
-
baselineY: viewBox[1] - 36,
|
|
439
|
-
width: viewBox[0] - 80,
|
|
440
|
-
minTitleY: viewBox[1] - 66,
|
|
441
|
-
unfit: dataflow.meta?.legend === undefined ? 'hide' : 'error',
|
|
442
|
-
diagramType: 'dataflow',
|
|
443
|
-
},
|
|
444
|
-
renderSwatch: (entry) => entry.kind === 'database'
|
|
445
|
-
? `<rect x="${entry.x}" y="${entry.baseline - 8}" width="14" height="9" rx="2" class="c-database" stroke-width="1"/>`
|
|
446
|
-
: `<path d="M ${entry.x} ${entry.baseline - 3} L ${entry.x + 34} ${entry.baseline - 3}" class="${entry.className}" stroke-width="${entry.strokeWidth || 1.4}" marker-end="url(#${entry.marker})"/>`,
|
|
447
|
-
});
|
|
14
|
+
let svg;
|
|
15
|
+
try {
|
|
16
|
+
svg = compileDataflow({ dataflow, sourceEvidence,
|
|
17
|
+
qualityProfile: process.env.ARCHIFY_QUALITY_PROFILE || dataflow.meta?.quality_profile });
|
|
18
|
+
} catch (error) {
|
|
19
|
+
for (const diagnostic of error?.archifyDiagnostics || []) recordDiagnostic(diagnostic);
|
|
20
|
+
throw error;
|
|
448
21
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
return ` <svg viewBox="0 0 ${viewBox[0]} ${viewBox[1]}" ${svgRootAttrs(dataflow.meta)}>
|
|
452
|
-
${svgAccessibleText(dataflow.meta, 'dataflow')}
|
|
453
|
-
${renderDefinitions()}
|
|
454
|
-
|
|
455
|
-
<!-- Background Grid -->
|
|
456
|
-
<rect width="100%" height="100%" fill="url(#grid)" />
|
|
457
|
-
|
|
458
|
-
<!-- Data Stages -->
|
|
459
|
-
${dataflow.stages.map(renderStage).join('\n\n')}
|
|
460
|
-
|
|
461
|
-
<!-- Flow paths -->
|
|
462
|
-
${asArray(dataflow.flows).map(renderFlowPath).join('\n')}
|
|
463
|
-
|
|
464
|
-
<!-- Nodes -->
|
|
465
|
-
${[...nodes.values()].map(renderNode).join('\n\n')}
|
|
466
|
-
|
|
467
|
-
<!-- Flow labels -->
|
|
468
|
-
${asArray(dataflow.flows).map(renderFlowLabel).join('\n')}
|
|
469
|
-
|
|
470
|
-
<!-- Legend -->
|
|
471
|
-
${renderLegend()}
|
|
472
|
-
</svg>`;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
validateDataflow();
|
|
476
|
-
writeDiagram({
|
|
477
|
-
outPath,
|
|
478
|
-
template,
|
|
479
|
-
diagramType: 'dataflow',
|
|
480
|
-
meta: dataflow.meta,
|
|
481
|
-
svg: renderSvg(),
|
|
482
|
-
cards: dataflow.cards,
|
|
483
|
-
});
|
|
22
|
+
writeDiagram({ outPath, template, diagramType: 'dataflow', meta: dataflow.meta,
|
|
23
|
+
svg, cards: dataflow.cards, sourceEvidence });
|