@mstone6969/graph 0.1.0 → 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/README.md +111 -14
- package/dist/ExcelWriter-3ZSYUH4T.cjs +154 -0
- package/dist/GraphType.js +24 -38
- package/dist/PdfWriter-6HJFYMCO.cjs +205 -0
- package/dist/catalog-YWX6FHRM.cjs +8 -0
- package/dist/catalog.js +5 -1
- package/dist/charts/bars/index.js +2 -12
- package/dist/charts/comparison/EffectForestPlot.js +1 -1
- package/dist/charts/comparison/index.js +1 -8
- package/dist/charts/demography/Nomogram.js +3 -3
- package/dist/charts/density/HexbinChart.js +6 -4
- package/dist/charts/diagrams/ArchitectureCanvas.js +68 -0
- package/dist/charts/diagrams/ArchitectureDiagram.js +331 -0
- package/dist/charts/diagrams/ArchitectureLayout.js +106 -0
- package/dist/charts/diagrams/ArchitectureLinkLabels.js +78 -0
- package/dist/charts/diagrams/ArchitectureModel.js +108 -0
- package/dist/charts/diagrams/ArchitectureRouter.js +348 -0
- package/dist/charts/diagrams/ArchitectureSamples.js +312 -0
- package/dist/charts/diagrams/ArchitectureShapes.js +130 -0
- package/dist/charts/diagrams/ArchitectureText.js +25 -0
- package/dist/charts/diagrams/DiagramText.js +38 -0
- package/dist/charts/diagrams/EntityRelationshipDiagram.js +465 -0
- package/dist/charts/diagrams/ErCrowsFoot.js +33 -0
- package/dist/charts/diagrams/ErSamples.js +205 -0
- package/dist/charts/diagrams/ErSchema.js +122 -0
- package/dist/charts/diagrams/LogicCircuit.js +154 -0
- package/dist/charts/diagrams/LogicCircuitLayout.js +600 -0
- package/dist/charts/diagrams/LogicCircuitSamples.js +66 -0
- package/dist/charts/diagrams/LogicGateDiagram.js +216 -0
- package/dist/charts/diagrams/LogicGateSymbol.js +113 -0
- package/dist/charts/diagrams/NetworkTopologyDiagram.js +247 -0
- package/dist/charts/diagrams/SequenceDiagram.js +351 -0
- package/dist/charts/diagrams/SequenceLayout.js +422 -0
- package/dist/charts/diagrams/SequenceModel.js +221 -0
- package/dist/charts/diagrams/TopologyLayout.js +143 -0
- package/dist/charts/diagrams/architectureEnums.js +50 -0
- package/dist/charts/diagrams/index.js +26 -0
- package/dist/charts/diagrams/logicEnums.js +42 -0
- package/dist/charts/diagrams/sequenceEnums.js +40 -0
- package/dist/charts/distribution/HistogramChart.js +25 -3
- package/dist/charts/distribution/index.js +1 -3
- package/dist/charts/earthscience/HovmollerDiagram.js +477 -0
- package/dist/charts/earthscience/HovmollerEnums.js +42 -0
- package/dist/charts/earthscience/HovmollerField.js +180 -0
- package/dist/charts/earthscience/HovmollerSample.js +40 -0
- package/dist/charts/earthscience/HovmollerSpeed.js +71 -0
- package/dist/charts/earthscience/index.js +10 -2
- package/dist/charts/engineering/ConstellationDiagram.js +26 -6
- package/dist/charts/engineering/EyeDiagram.js +2 -1
- package/dist/charts/engineering/PoleZeroPlot.js +32 -8
- package/dist/charts/financial/RelativeStrengthRotation.js +434 -0
- package/dist/charts/financial/RelativeStrengthRotationMath.js +71 -0
- package/dist/charts/financial/RelativeStrengthRotationSamples.js +27 -0
- package/dist/charts/financial/index.js +5 -3
- package/dist/charts/flow/index.js +1 -5
- package/dist/charts/frameworks/AnsoffMatrix.js +523 -0
- package/dist/charts/frameworks/FrameworkText.js +98 -0
- package/dist/charts/frameworks/NpsGauge.js +279 -0
- package/dist/charts/frameworks/NpsSurvey.js +58 -0
- package/dist/charts/frameworks/PerceptualMap.js +419 -0
- package/dist/charts/frameworks/PerceptualProjection.js +142 -0
- package/dist/charts/frameworks/PositioningSamples.js +191 -0
- package/dist/charts/frameworks/RaciMatrix.js +458 -0
- package/dist/charts/frameworks/RaciPlan.js +81 -0
- package/dist/charts/frameworks/RaciSamples.js +76 -0
- package/dist/charts/frameworks/SwotMatrix.js +358 -0
- package/dist/charts/frameworks/SwotSamples.js +85 -0
- package/dist/charts/frameworks/VanWestendorpChart.js +187 -0
- package/dist/charts/frameworks/VanWestendorpSurvey.js +197 -0
- package/dist/charts/frameworks/index.js +26 -0
- package/dist/charts/frameworks/raciEnums.js +30 -0
- package/dist/charts/frameworks/swotEnums.js +34 -0
- package/dist/charts/genomics/CircosPlot.js +1 -1
- package/dist/charts/geoflows/GeoFlowChart.js +2 -2
- package/dist/charts/geoflows/GeoFlowSample.js +2 -1
- package/dist/charts/geomaps/FloorPlanMap.js +324 -0
- package/dist/charts/geomaps/FloorPlanModel.js +179 -0
- package/dist/charts/geomaps/FloorPlanSamples.js +416 -0
- package/dist/charts/geomaps/GeoLand.js +2 -2
- package/dist/charts/geomaps/GeoSamples.js +2 -1
- package/dist/charts/geomaps/GraticuleMap.js +2 -2
- package/dist/charts/geomaps/RoomLabelFit.js +200 -0
- package/dist/charts/geomaps/TissotMap.js +2 -2
- package/dist/charts/geomaps/index.js +8 -4
- package/dist/charts/hierarchy/SunburstChart.js +17 -10
- package/dist/charts/hierarchy/index.js +2 -11
- package/dist/charts/indicators/BulletChart.js +8 -2
- package/dist/charts/indicators/SpeedometerChart.js +1 -1
- package/dist/charts/indicators/ThermometerChart.js +1 -1
- package/dist/charts/lifescience/LoessFit.js +113 -0
- package/dist/charts/lifescience/MaPlot.js +337 -0
- package/dist/charts/lifescience/MaPlotSamples.js +35 -0
- package/dist/charts/lifescience/OncoGlyph.js +137 -0
- package/dist/charts/lifescience/OncoPrint.js +373 -0
- package/dist/charts/lifescience/OncoPrintMatrix.js +107 -0
- package/dist/charts/lifescience/OncoPrintSamples.js +142 -0
- package/dist/charts/lifescience/PedigreeChart.js +306 -0
- package/dist/charts/lifescience/PedigreeLayout.js +261 -0
- package/dist/charts/lifescience/PedigreeRows.js +70 -0
- package/dist/charts/lifescience/PedigreeSamples.js +161 -0
- package/dist/charts/lifescience/PedigreeSymbol.js +47 -0
- package/dist/charts/lifescience/RamachandranPlot.js +364 -0
- package/dist/charts/lifescience/RamachandranRegions.js +2712 -0
- package/dist/charts/lifescience/RamachandranSamples.js +235 -0
- package/dist/charts/lifescience/index.js +25 -0
- package/dist/charts/lifescience/pedigreeEnums.js +21 -0
- package/dist/charts/lifescience/ramachandranEnums.js +34 -0
- package/dist/charts/linesareas/DualAxisChart.js +1 -1
- package/dist/charts/linesareas/LineEnums.js +1 -15
- package/dist/charts/linesareas/MultiLineChart.js +1 -1
- package/dist/charts/linesareas/TrendChart.js +15 -5
- package/dist/charts/linesareas/index.js +2 -6
- package/dist/charts/massive/MassiveChart.js +3 -2
- package/dist/charts/mathematical/FourierSeriesChart.js +7 -1
- package/dist/charts/mathematical/index.js +2 -6
- package/dist/charts/matrix/GridPlot.js +27 -2
- package/dist/charts/matrix/TilegramChart.js +2 -2
- package/dist/charts/mlcharts/AttentionGrid.js +79 -0
- package/dist/charts/mlcharts/AttentionHeatmap.js +329 -0
- package/dist/charts/mlcharts/AttentionModel.js +132 -0
- package/dist/charts/mlcharts/AttentionSamples.js +82 -0
- package/dist/charts/mlcharts/CellNumber.js +8 -0
- package/dist/charts/mlcharts/CurveDiagnosticSamples.js +95 -0
- package/dist/charts/mlcharts/CurvePickMark.js +31 -0
- package/dist/charts/mlcharts/DecisionBoundaryChart.js +316 -0
- package/dist/charts/mlcharts/DecisionSamples.js +60 -0
- package/dist/charts/mlcharts/DecisionSurface.js +130 -0
- package/dist/charts/mlcharts/DistinctTicks.js +13 -0
- package/dist/charts/mlcharts/ElbowMath.js +153 -0
- package/dist/charts/mlcharts/ElbowPlot.js +273 -0
- package/dist/charts/mlcharts/ExplainabilitySamples.js +70 -0
- package/dist/charts/mlcharts/FeatureImportanceChart.js +150 -0
- package/dist/charts/mlcharts/ImportanceRanking.js +98 -0
- package/dist/charts/mlcharts/LearningCurveChart.js +276 -0
- package/dist/charts/mlcharts/LearningCurveMath.js +92 -0
- package/dist/charts/mlcharts/PartialDependence.js +92 -0
- package/dist/charts/mlcharts/PartialDependencePlot.js +225 -0
- package/dist/charts/mlcharts/WideLabel.js +28 -0
- package/dist/charts/mlcharts/curveDiagnosticEnums.js +37 -0
- package/dist/charts/mlcharts/index.js +32 -0
- package/dist/charts/proportion/index.js +5 -9
- package/dist/charts/scatter/CategoryScatterBase.js +1 -4
- package/dist/charts/scatter/ConnectedScatter.js +7 -4
- package/dist/charts/scatter/MarginalScatter.js +13 -8
- package/dist/charts/scatter/index.js +4 -17
- package/dist/charts/scientific/SpectrogramChart.js +9 -3
- package/dist/charts/scientific/index.js +2 -2
- package/dist/charts/signals/index.js +3 -13
- package/dist/charts/statistics/LorenzCurve.js +7 -4
- package/dist/charts/threebars/Candlestick3DChart.js +18 -2
- package/dist/charts/threeglobe/GlobeChart.js +2 -2
- package/dist/charts/threeglobe/GlobeSamples.js +2 -1
- package/dist/charts/threeglobe/RingGlobe.js +1 -1
- package/dist/charts/threenetwork/ConeTreeChart.js +2 -1
- package/dist/charts/threenetwork/Gantt3DChart.js +3 -2
- package/dist/charts/threesurfaces/Ridgeline3DChart.js +22 -4
- package/dist/charts/threevolume/LorenzAttractor.js +19 -0
- package/dist/charts/threevolume/VolumeRaycastChart.js +4 -3
- package/dist/charts/threevolume/volumeEnums.js +1 -0
- package/dist/charts/timeline/index.js +2 -10
- package/dist/chunk-753UBLV2.cjs +33 -0
- package/dist/chunk-VX54A7KA.cjs +17 -0
- package/dist/{chunk-W5GAAAZY.cjs → chunk-WAODDPYX.cjs} +24874 -17620
- package/dist/chunk-Z3JPIJSC.cjs +338 -0
- package/dist/core/CartesianChart.js +87 -11
- package/dist/core/Chart.js +63 -15
- package/dist/core/Chart2D.js +54 -17
- package/dist/core/Chart3D.js +56 -20
- package/dist/core/ChartExportError.js +2 -0
- package/dist/core/ChartExporter.js +50 -8
- package/dist/core/ColumnCheck.js +53 -0
- package/dist/core/Context2D.js +29 -15
- package/dist/core/Context3D.js +7 -2
- package/dist/core/ExcelWriter.js +152 -0
- package/dist/core/ExportText.js +28 -0
- package/dist/core/MarkOrigin.js +88 -0
- package/dist/core/MarkStyle.js +294 -0
- package/dist/core/Palette.js +27 -7
- package/dist/core/PdfPage.js +13 -0
- package/dist/core/PdfWriter.js +245 -0
- package/dist/core/Scales.js +52 -2
- package/dist/core/SceneOverlay.js +4 -4
- package/dist/core/TextFit.js +37 -0
- package/dist/core/Theme.js +28 -1
- package/dist/core/enums.js +33 -1
- package/dist/core/pathMorph.js +15 -0
- package/dist/data/SampleData.js +1 -17
- package/dist/data/WorldSample.js +23 -0
- package/dist/index.cjs +810 -691
- package/dist/index.d.cts +8390 -2925
- package/dist/index.d.ts +8392 -2927
- package/dist/index.js +106 -56
- package/dist/react/ChartView.js +18 -8
- package/dist/react/GraphHandle.js +2 -0
- package/dist/react/components.js +74 -116
- package/package.json +13 -3
- package/dist/catalog-4UP2YFNR.cjs +0 -7
- package/dist/charts/bars/BulletBar.js +0 -184
- package/dist/charts/bars/ErrorBarColumn.js +0 -147
- package/dist/charts/bars/HistogramBar.js +0 -217
- package/dist/charts/bars/MarimekkoBar.js +0 -191
- package/dist/charts/bars/ParetoBar.js +0 -143
- package/dist/charts/comparison/PanelChart.js +0 -70
- package/dist/charts/comparison/SmallMultipleBars.js +0 -134
- package/dist/charts/comparison/SpanChart.js +0 -118
- package/dist/charts/comparison/TrellisChart.js +0 -158
- package/dist/charts/distribution/RidgeSlabs3DChart.js +0 -169
- package/dist/charts/financial/Candlestick3D.js +0 -157
- package/dist/charts/flow/BumpFlow.js +0 -212
- package/dist/charts/flow/StreamFlow.js +0 -174
- package/dist/charts/geomaps/ThiessenMap.js +0 -106
- package/dist/charts/hierarchy/ConeTree3D.js +0 -135
- package/dist/charts/hierarchy/Hierarchy3DChart.js +0 -27
- package/dist/charts/hierarchy/SpherePack3D.js +0 -81
- package/dist/charts/hierarchy/SunburstTower3D.js +0 -93
- package/dist/charts/hierarchy/TreemapSkyline3D.js +0 -98
- package/dist/charts/linesareas/ConnectedScatterTime.js +0 -206
- package/dist/charts/linesareas/SmallMultipleLines.js +0 -153
- package/dist/charts/mathematical/ParametricSurface3D.js +0 -234
- package/dist/charts/mathematical/StrangeAttractor3D.js +0 -251
- package/dist/charts/proportion/ConeFunnel.js +0 -126
- package/dist/charts/proportion/ExtrudedPieChart.js +0 -123
- package/dist/charts/scatter/BeeswarmScatter.js +0 -151
- package/dist/charts/scatter/BinnedScatterBase.js +0 -51
- package/dist/charts/scatter/ContourScatter.js +0 -116
- package/dist/charts/scatter/GridBinScatter.js +0 -98
- package/dist/charts/scatter/HexbinScatter.js +0 -74
- package/dist/charts/scatter/StripScatter.js +0 -107
- package/dist/charts/scatter/SunflowerScatter.js +0 -109
- package/dist/charts/signals/ConstellationChart.js +0 -153
- package/dist/charts/signals/CorrelogramChart.js +0 -171
- package/dist/charts/signals/EyeDiagramChart.js +0 -155
- package/dist/charts/signals/PoleZeroChart.js +0 -164
- package/dist/charts/signals/SpectrogramChart.js +0 -79
- package/dist/charts/timeline/CalendarSkylineChart.js +0 -128
- package/dist/charts/timeline/ScheduleBlocksChart.js +0 -119
- package/dist/charts/timeline/SpiralTimeline.js +0 -154
- package/dist/charts/timeline/StreamTimeline.js +0 -141
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { ChartFamily, DataShape, Dimension } from "../../core/enums.js";
|
|
2
|
+
import { ArchitectureCanvas } from "./ArchitectureCanvas.js";
|
|
3
|
+
import { ArchitectureDetail, ArchitectureDirection, ArchitectureKind, ArchitectureLevel, ArchitectureRouting } from "./architectureEnums.js";
|
|
4
|
+
import { ArchitectureLayout } from "./ArchitectureLayout.js";
|
|
5
|
+
import { ArchitectureLinkLabels } from "./ArchitectureLinkLabels.js";
|
|
6
|
+
import { ArchitectureModel } from "./ArchitectureModel.js";
|
|
7
|
+
import { ArchitectureFlow, ArchitectureRouter } from "./ArchitectureRouter.js";
|
|
8
|
+
import { ArchitectureSamples } from "./ArchitectureSamples.js";
|
|
9
|
+
import { ArchitectureShapes } from "./ArchitectureShapes.js";
|
|
10
|
+
import { ArchitectureText } from "./ArchitectureText.js";
|
|
11
|
+
// src/charts/diagrams/ArchitectureDiagram.ts
|
|
12
|
+
var HEADER = 24;
|
|
13
|
+
var HEAD = 20;
|
|
14
|
+
var LID = 8;
|
|
15
|
+
var BOX = 164;
|
|
16
|
+
var NAME = 12;
|
|
17
|
+
var SMALL = 10;
|
|
18
|
+
var LEADING = 1.25;
|
|
19
|
+
var PAD = 8;
|
|
20
|
+
var BOUNDARY_TEXT = 11;
|
|
21
|
+
var LINK_TEXT = 10;
|
|
22
|
+
var WIDEST_LINK = 118;
|
|
23
|
+
/** On-screen pixels below which small text is dropped, and the least a remaining name or link label is enlarged to. */
|
|
24
|
+
var SMALLEST = 6.5;
|
|
25
|
+
var LEGIBLE = 8;
|
|
26
|
+
var ROUND_LABEL = 9;
|
|
27
|
+
var LEGEND = [
|
|
28
|
+
[[ArchitectureKind.Person], "Person"],
|
|
29
|
+
[[ArchitectureKind.System], "Software system"],
|
|
30
|
+
[[ArchitectureKind.Container, ArchitectureKind.Database], "Container"],
|
|
31
|
+
[[ArchitectureKind.Component], "Component"]
|
|
32
|
+
];
|
|
33
|
+
var TYPE = {
|
|
34
|
+
[ArchitectureKind.Person]: "Person",
|
|
35
|
+
[ArchitectureKind.System]: "Software System",
|
|
36
|
+
[ArchitectureKind.Container]: "Container",
|
|
37
|
+
[ArchitectureKind.Database]: "Container",
|
|
38
|
+
[ArchitectureKind.Component]: "Component",
|
|
39
|
+
[ArchitectureKind.Boundary]: "Boundary"
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* C4 architecture diagram (Simon Brown's C4 model; shapes after C4-PlantUML): people, software
|
|
43
|
+
* systems, containers and components as labelled boxes, with one data set zooming through the
|
|
44
|
+
* context, container and component levels. Use it to explain how a system is put together. Layout
|
|
45
|
+
* comes from the nodes, their `parent` column and the links, with arrows routed around boxes.
|
|
46
|
+
*/
|
|
47
|
+
var ArchitectureDiagram = class extends ArchitectureCanvas {
|
|
48
|
+
static meta = {
|
|
49
|
+
id: "architecture-diagram",
|
|
50
|
+
name: "C4 architecture diagram",
|
|
51
|
+
family: ChartFamily.Network,
|
|
52
|
+
dimension: Dimension.Two,
|
|
53
|
+
shape: DataShape.Graph,
|
|
54
|
+
description: "Shows software as C4 people, systems, containers and components in nested boundaries, at the context, container or component level.",
|
|
55
|
+
tags: [
|
|
56
|
+
"c4",
|
|
57
|
+
"architecture",
|
|
58
|
+
"software",
|
|
59
|
+
"boundary",
|
|
60
|
+
"container"
|
|
61
|
+
],
|
|
62
|
+
sample: (seed) => ArchitectureSamples.parcelTracking(seed),
|
|
63
|
+
variants: {
|
|
64
|
+
level: [
|
|
65
|
+
ArchitectureLevel.Containers,
|
|
66
|
+
ArchitectureLevel.Context,
|
|
67
|
+
ArchitectureLevel.Components
|
|
68
|
+
],
|
|
69
|
+
direction: [ArchitectureDirection.TopDown, ArchitectureDirection.LeftRight],
|
|
70
|
+
routing: [ArchitectureRouting.Orthogonal, ArchitectureRouting.Direct],
|
|
71
|
+
detail: [ArchitectureDetail.Full, ArchitectureDetail.Names]
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
defaults() {
|
|
75
|
+
return {
|
|
76
|
+
level: ArchitectureLevel.Containers,
|
|
77
|
+
direction: ArchitectureDirection.TopDown,
|
|
78
|
+
routing: ArchitectureRouting.Orthogonal,
|
|
79
|
+
detail: ArchitectureDetail.Full,
|
|
80
|
+
kind: "kind",
|
|
81
|
+
parent: "parent",
|
|
82
|
+
name: "name",
|
|
83
|
+
technology: "technology",
|
|
84
|
+
description: "description",
|
|
85
|
+
external: "external",
|
|
86
|
+
linkLabel: "label",
|
|
87
|
+
linkTechnology: "technology"
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
get linkFields() {
|
|
91
|
+
return [this.options.linkLabel, this.options.linkTechnology];
|
|
92
|
+
}
|
|
93
|
+
legendItems() {
|
|
94
|
+
return this.#external ? [...this.palette.items(), {
|
|
95
|
+
key: "External",
|
|
96
|
+
color: this.theme.axis
|
|
97
|
+
}] : this.palette.items();
|
|
98
|
+
}
|
|
99
|
+
#external = false;
|
|
100
|
+
#type(item) {
|
|
101
|
+
const technology = this.#column(item, this.options.technology), base = TYPE[item.kind];
|
|
102
|
+
return `[${technology ? `${base}: ${technology}` : base}]`;
|
|
103
|
+
}
|
|
104
|
+
#column(item, column) {
|
|
105
|
+
return String(item.node[column] ?? "").trim();
|
|
106
|
+
}
|
|
107
|
+
#full() {
|
|
108
|
+
return this.options.detail === ArchitectureDetail.Full;
|
|
109
|
+
}
|
|
110
|
+
#measure = (item) => {
|
|
111
|
+
const full = this.#full();
|
|
112
|
+
const width = full ? BOX : Math.min(BOX, Math.max(104, ArchitectureText.width(item.name, NAME * 1.05) + 16, ArchitectureText.width(this.#type(item), SMALL) + 16));
|
|
113
|
+
const room = width - 16;
|
|
114
|
+
const name = full ? ArchitectureText.wrap(item.name, room, NAME * 1.05, 2).length : 2;
|
|
115
|
+
const about = full ? ArchitectureText.wrap(this.#column(item, this.options.description), room, SMALL, 2).length : 0;
|
|
116
|
+
const text = name * NAME * LEADING + SMALL * LEADING + (about ? about * SMALL * LEADING + 4 : 0);
|
|
117
|
+
const extra = item.kind === ArchitectureKind.Person ? HEAD : item.kind === ArchitectureKind.Database ? 16 : 0;
|
|
118
|
+
return {
|
|
119
|
+
width: Math.ceil(width),
|
|
120
|
+
height: Math.round(text + 16 + extra)
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
/** The path of an element, plus the lid line a cylinder needs on top of its fill. */
|
|
124
|
+
#shape(view) {
|
|
125
|
+
const { item, rect } = view;
|
|
126
|
+
if (item.kind === ArchitectureKind.Person) return {
|
|
127
|
+
body: ArchitectureShapes.person(rect, HEAD),
|
|
128
|
+
lid: ""
|
|
129
|
+
};
|
|
130
|
+
if (item.kind === ArchitectureKind.Database) return ArchitectureShapes.cylinder(rect, LID);
|
|
131
|
+
return {
|
|
132
|
+
body: ArchitectureShapes.box(rect),
|
|
133
|
+
lid: ""
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
#fill(ctx, item) {
|
|
137
|
+
if (item.external) return ctx.theme.axis;
|
|
138
|
+
return ctx.palette.color(LEGEND.find(([kinds]) => kinds.includes(item.kind))?.[1] ?? "Container");
|
|
139
|
+
}
|
|
140
|
+
/** The space a box's text may use, under a person's head or a cylinder's lid. */
|
|
141
|
+
#textSpan({ item, rect }) {
|
|
142
|
+
return {
|
|
143
|
+
top: rect.y + PAD + (item.kind === ArchitectureKind.Person ? HEAD : item.kind === ArchitectureKind.Database ? 16 : 0),
|
|
144
|
+
bottom: rect.y + rect.height - PAD
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/** The biggest size, up to `target`, at which this box holds its whole name. */
|
|
148
|
+
#largestName(view, target) {
|
|
149
|
+
const { top, bottom } = this.#textSpan(view), room = view.rect.width - 16;
|
|
150
|
+
for (let size = Math.min(24, target); size > NAME; size -= .5) {
|
|
151
|
+
const rows = ArchitectureText.wrap(view.item.name, room, size * 1.05, 2);
|
|
152
|
+
if (rows.length * size * LEADING <= bottom - top && !rows.at(-1).endsWith("…")) return size;
|
|
153
|
+
}
|
|
154
|
+
return NAME;
|
|
155
|
+
}
|
|
156
|
+
/** Text of one closed box. Every name takes the same `size`; below that scale the smaller lines are dropped. */
|
|
157
|
+
#lines(ctx, view, ratio, size) {
|
|
158
|
+
const { item, rect } = view;
|
|
159
|
+
const ink = ctx.palette.inkOn(this.#fill(ctx, item));
|
|
160
|
+
const { top, bottom } = this.#textSpan(view), room = rect.width - 16;
|
|
161
|
+
const fine = SMALL * ratio >= SMALLEST;
|
|
162
|
+
const rows = ArchitectureText.wrap(item.name, room, size * 1.05, Math.max(1, Math.min(2, Math.floor((bottom - top) / (size * LEADING)))));
|
|
163
|
+
const lines = [];
|
|
164
|
+
let y = top;
|
|
165
|
+
const put = (text, tall, weight) => {
|
|
166
|
+
if (!text || y + tall * LEADING > bottom + 1) return;
|
|
167
|
+
y += tall * LEADING;
|
|
168
|
+
lines.push({
|
|
169
|
+
key: `${item.id}:${lines.length}`,
|
|
170
|
+
text,
|
|
171
|
+
x: rect.x + rect.width / 2,
|
|
172
|
+
y: y - tall * .28,
|
|
173
|
+
size: tall,
|
|
174
|
+
weight,
|
|
175
|
+
fill: ink
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
for (const line of rows) put(line, size, 600);
|
|
179
|
+
if (fine) {
|
|
180
|
+
put(ArchitectureText.fit(this.#type(item), room, SMALL), SMALL, 400);
|
|
181
|
+
if (this.#full()) {
|
|
182
|
+
y += 4;
|
|
183
|
+
for (const line of ArchitectureText.wrap(this.#column(item, this.options.description), room, SMALL, 2)) put(line, SMALL, 400);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const slack = (bottom - y) / 2;
|
|
187
|
+
return lines.map((line) => ({
|
|
188
|
+
...line,
|
|
189
|
+
y: line.y + slack
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
#boundaryTitle(item, rect, ratio) {
|
|
193
|
+
const size = Math.min(BOUNDARY_TEXT * 1.4, Math.max(BOUNDARY_TEXT, LEGIBLE / ratio));
|
|
194
|
+
return {
|
|
195
|
+
text: ArchitectureText.fit(`${item.name} ${this.#type(item)}`, rect.width - 18, size),
|
|
196
|
+
size
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
#requests(model, rects, titles) {
|
|
200
|
+
return model.relations.map(({ source, target }) => {
|
|
201
|
+
const free = /* @__PURE__ */ new Set([...ArchitectureModel.lineage(source), ...ArchitectureModel.lineage(target)]);
|
|
202
|
+
const closed = model.items.filter((item) => !free.has(item)).map((item) => rects.get(item));
|
|
203
|
+
return {
|
|
204
|
+
from: rects.get(source),
|
|
205
|
+
to: rects.get(target),
|
|
206
|
+
blocks: [...closed, ...titles]
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
#link(relation, route) {
|
|
211
|
+
const { linkLabel, linkTechnology } = this.options;
|
|
212
|
+
const rows = relation.links.map((link) => {
|
|
213
|
+
const technology = String(link[linkTechnology] ?? "");
|
|
214
|
+
return [String(link[linkLabel] ?? "Link"), technology ? `[${technology}]` : ""];
|
|
215
|
+
});
|
|
216
|
+
return {
|
|
217
|
+
key: relation.key,
|
|
218
|
+
links: relation.links,
|
|
219
|
+
route,
|
|
220
|
+
arrow: true,
|
|
221
|
+
tip: {
|
|
222
|
+
title: `${relation.source.name} → ${relation.target.name}`,
|
|
223
|
+
rows
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
#linkLines({ links }, size) {
|
|
228
|
+
const label = String(links[0][this.options.linkLabel] ?? ""), technology = String(links[0][this.options.linkTechnology] ?? "");
|
|
229
|
+
if (!label) return [];
|
|
230
|
+
const more = links.length > 1 ? ` +${links.length - 1}` : "", room = WIDEST_LINK * size / LINK_TEXT;
|
|
231
|
+
return [...ArchitectureText.wrap(`${label}${more}`, room, size, 2), ...technology ? [ArchitectureText.fit(`[${technology}]`, room, size)] : []];
|
|
232
|
+
}
|
|
233
|
+
draw(ctx) {
|
|
234
|
+
const { level, direction, routing } = this.options;
|
|
235
|
+
const horizontal = direction === ArchitectureDirection.LeftRight;
|
|
236
|
+
const model = new ArchitectureModel(this.options.data, {
|
|
237
|
+
kind: this.options.kind,
|
|
238
|
+
parent: this.options.parent,
|
|
239
|
+
name: this.options.name,
|
|
240
|
+
external: this.options.external,
|
|
241
|
+
linkLabel: this.options.linkLabel,
|
|
242
|
+
linkTechnology: this.options.linkTechnology
|
|
243
|
+
}, level);
|
|
244
|
+
for (const [kinds, label] of LEGEND) if (model.items.some((item) => !item.open && !item.external && kinds.includes(item.kind))) ctx.palette.color(label);
|
|
245
|
+
this.#external = model.items.some((item) => !item.open && item.external);
|
|
246
|
+
const plan = ArchitectureLayout.place(model, {
|
|
247
|
+
horizontal,
|
|
248
|
+
measure: this.#measure,
|
|
249
|
+
header: HEADER
|
|
250
|
+
});
|
|
251
|
+
const titles = model.items.filter((item) => item.open).map((item) => {
|
|
252
|
+
const rect = plan.rects.get(item);
|
|
253
|
+
return {
|
|
254
|
+
x: rect.x + 2,
|
|
255
|
+
y: rect.y + 2,
|
|
256
|
+
width: Math.min(rect.width - 4, Math.ceil(ArchitectureText.width(`${item.name} ${this.#type(item)}`, BOUNDARY_TEXT * 1.4)) + ROUND_LABEL),
|
|
257
|
+
height: 20
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
const routes = ArchitectureRouter.route(this.#requests(model, plan.rects, titles), {
|
|
261
|
+
rects: [...plan.rects.values(), ...titles],
|
|
262
|
+
flow: horizontal ? ArchitectureFlow.Right : ArchitectureFlow.Down,
|
|
263
|
+
direct: routing === ArchitectureRouting.Direct,
|
|
264
|
+
skirt: model.items.filter((item) => item.open).map((item) => plan.rects.get(item))
|
|
265
|
+
});
|
|
266
|
+
const views = model.items.map((item) => ({
|
|
267
|
+
item,
|
|
268
|
+
rect: plan.rects.get(item)
|
|
269
|
+
}));
|
|
270
|
+
const links = model.relations.map((relation, i) => this.#link(relation, routes[i]));
|
|
271
|
+
const corners = [...views.map(({ rect }) => rect), ...routes.flat().map((at) => ({
|
|
272
|
+
...at,
|
|
273
|
+
width: 0,
|
|
274
|
+
height: 0
|
|
275
|
+
}))];
|
|
276
|
+
const box = {
|
|
277
|
+
x: Math.min(0, ...corners.map((at) => at.x)),
|
|
278
|
+
y: Math.min(0, ...corners.map((at) => at.y)),
|
|
279
|
+
right: Math.max(1, ...corners.map((at) => at.x + at.width)),
|
|
280
|
+
bottom: Math.max(1, ...corners.map((at) => at.y + at.height))
|
|
281
|
+
};
|
|
282
|
+
const width = box.right - box.x, height = box.bottom - box.y;
|
|
283
|
+
const ratio = Math.min(1, ctx.plot.width / width, ctx.plot.height / height);
|
|
284
|
+
const stage = ctx.nest(`translate(${(ctx.plot.width - width * ratio) / 2 - box.x * ratio},${(ctx.plot.height - height * ratio) / 2 - box.y * ratio}) scale(${ratio})`, "architecture");
|
|
285
|
+
const open = views.filter((view) => view.item.open), closed = views.filter((view) => !view.item.open);
|
|
286
|
+
const frames = stage.marks("boundaries", "path", open, (d) => d.item.id).attr("fill", "none").attr("stroke", ctx.theme.inkMuted).attr("stroke-width", 1.5 / ratio).attr("stroke-dasharray", "7 5");
|
|
287
|
+
stage.set(frames, { d: (d) => ArchitectureShapes.box(d.rect, 8) });
|
|
288
|
+
stage.hover(frames, (d) => ({
|
|
289
|
+
title: d.item.name,
|
|
290
|
+
rows: [["Type", TYPE[d.item.kind]], ["Holds", d.item.children.length]]
|
|
291
|
+
}));
|
|
292
|
+
this.drawLinks(stage, links, ratio);
|
|
293
|
+
const boxes = stage.marks("elements", "path", closed, (d) => d.item.id).attr("fill", (d) => this.#fill(ctx, d.item));
|
|
294
|
+
stage.set(boxes, { d: (d) => this.#shape(d).body });
|
|
295
|
+
stage.hover(boxes, (d) => ({
|
|
296
|
+
title: d.item.name,
|
|
297
|
+
color: this.#fill(ctx, d.item),
|
|
298
|
+
rows: [["Type", this.#type(d.item).slice(1, -1)], ...this.#column(d.item, this.options.description) ? [["About", this.#column(d.item, this.options.description)]] : []]
|
|
299
|
+
}));
|
|
300
|
+
const lids = stage.marks("lids", "path", closed.filter((view) => view.item.kind === ArchitectureKind.Database), (d) => d.item.id).attr("fill", "none").attr("stroke", ctx.theme.surface).attr("stroke-width", 2 / ratio).style("pointer-events", "none");
|
|
301
|
+
stage.set(lids, { d: (d) => this.#shape(d).lid });
|
|
302
|
+
const nameSize = SMALL * ratio >= SMALLEST ? NAME : Math.min(...closed.map((view) => this.#largestName(view, LEGIBLE / ratio)));
|
|
303
|
+
const words = stage.marks("words", "text", closed.flatMap((view) => this.#lines(ctx, view, ratio, nameSize)), (d) => d.key).attr("text-anchor", "middle").attr("font-size", (d) => d.size).attr("font-weight", (d) => d.weight).attr("fill", (d) => d.fill).style("pointer-events", "none").text((d) => d.text);
|
|
304
|
+
stage.set(words, {
|
|
305
|
+
x: (d) => d.x,
|
|
306
|
+
y: (d) => d.y
|
|
307
|
+
});
|
|
308
|
+
const headings = open.map(({ item, rect }) => ({
|
|
309
|
+
item,
|
|
310
|
+
rect,
|
|
311
|
+
...this.#boundaryTitle(item, rect, ratio)
|
|
312
|
+
}));
|
|
313
|
+
const heads = stage.marks("boundary-titles", "text", headings, (d) => d.item.id).attr("font-size", (d) => d.size).attr("fill", ctx.theme.inkSecondary).style("pointer-events", "none").text((d) => d.text);
|
|
314
|
+
stage.set(heads, {
|
|
315
|
+
x: (d) => d.rect.x + ROUND_LABEL,
|
|
316
|
+
y: (d) => d.rect.y + 17
|
|
317
|
+
});
|
|
318
|
+
const size = Math.max(LINK_TEXT, SMALLEST / ratio);
|
|
319
|
+
const placed = ArchitectureLinkLabels.place(model.relations.map((relation, i) => ({
|
|
320
|
+
lines: this.#linkLines(relation, size),
|
|
321
|
+
route: routes[i]
|
|
322
|
+
})), [...closed.map((view) => view.rect), ...titles], size, {
|
|
323
|
+
x: box.x,
|
|
324
|
+
y: box.y,
|
|
325
|
+
width,
|
|
326
|
+
height
|
|
327
|
+
});
|
|
328
|
+
this.drawLinkLabels(stage, links, placed, size);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
export { ArchitectureDiagram };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { UmlLayout } from "../structure/UmlLayout.js";
|
|
2
|
+
import { ArchitectureModel } from "./ArchitectureModel.js";
|
|
3
|
+
// src/charts/diagrams/ArchitectureLayout.ts
|
|
4
|
+
var INSET = 18;
|
|
5
|
+
var ROOM_ACROSS = 36;
|
|
6
|
+
var ROOM_DOWN = 24;
|
|
7
|
+
var ROOM_RIGHT = 54;
|
|
8
|
+
/**
|
|
9
|
+
* Compound layered layout: every boundary is laid out by itself, inner scope first, and then acts
|
|
10
|
+
* as one box in the scope around it, as in ELK Layered with separate children. A link between
|
|
11
|
+
* scopes counts in their lowest common scope, between the two children that hold its ends.
|
|
12
|
+
*/
|
|
13
|
+
var ArchitectureLayout = class ArchitectureLayout {
|
|
14
|
+
#model;
|
|
15
|
+
#options;
|
|
16
|
+
#rects = /* @__PURE__ */ new Map();
|
|
17
|
+
static place(model, options) {
|
|
18
|
+
return new ArchitectureLayout(model, options).#plan();
|
|
19
|
+
}
|
|
20
|
+
constructor(model, options) {
|
|
21
|
+
this.#model = model;
|
|
22
|
+
this.#options = options;
|
|
23
|
+
}
|
|
24
|
+
#plan() {
|
|
25
|
+
const { width, height } = this.#scope(null, this.#model.items.filter((item) => !item.parent), 0, 0);
|
|
26
|
+
return {
|
|
27
|
+
rects: this.#rects,
|
|
28
|
+
width,
|
|
29
|
+
height
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Lays `items` out from (0,0), leaving each child's subtree in local coordinates to be moved by `#shift`. */
|
|
33
|
+
#scope(owner, items, x, y) {
|
|
34
|
+
const { horizontal } = this.#options;
|
|
35
|
+
const sizes = new Map(items.map((item) => [item, item.open ? this.#open(item) : this.#options.measure(item)]));
|
|
36
|
+
const boxes = items.map((item) => {
|
|
37
|
+
const size = sizes.get(item);
|
|
38
|
+
return {
|
|
39
|
+
id: item.id,
|
|
40
|
+
width: size.width + (horizontal ? 0 : ROOM_ACROSS),
|
|
41
|
+
height: size.height + (horizontal ? ROOM_ACROSS : 0)
|
|
42
|
+
};
|
|
43
|
+
});
|
|
44
|
+
const plan = UmlLayout.place(boxes, this.#edges(owner, items), {
|
|
45
|
+
horizontal,
|
|
46
|
+
orthogonal: true,
|
|
47
|
+
clearance: horizontal ? ROOM_RIGHT : ROOM_DOWN
|
|
48
|
+
});
|
|
49
|
+
const byId = new Map(items.map((item) => [item.id, item]));
|
|
50
|
+
for (const box of plan.boxes) {
|
|
51
|
+
const item = byId.get(box.id), size = sizes.get(item);
|
|
52
|
+
const left = Math.round(x + box.x + (box.width - size.width) / 2), top = Math.round(y + box.y + (box.height - size.height) / 2);
|
|
53
|
+
this.#shift(item, left, top, size);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
width: plan.width,
|
|
57
|
+
height: plan.height
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
#open(item) {
|
|
61
|
+
const shown = new Set(this.#model.items);
|
|
62
|
+
const inner = this.#scope(item, item.children.filter((child) => shown.has(child)), INSET, this.#options.header + INSET / 2);
|
|
63
|
+
return {
|
|
64
|
+
width: Math.max(inner.width + 36, 150),
|
|
65
|
+
height: inner.height + this.#options.header + INSET * 1.5
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Children were placed relative to their boundary, so they move with it. */
|
|
69
|
+
#shift(item, left, top, size) {
|
|
70
|
+
this.#rects.set(item, {
|
|
71
|
+
x: left,
|
|
72
|
+
y: top,
|
|
73
|
+
width: Math.round(size.width),
|
|
74
|
+
height: Math.round(size.height)
|
|
75
|
+
});
|
|
76
|
+
const move = (parent) => {
|
|
77
|
+
for (const child of parent.children) {
|
|
78
|
+
const rect = this.#rects.get(child);
|
|
79
|
+
if (!rect) continue;
|
|
80
|
+
rect.x += left;
|
|
81
|
+
rect.y += top;
|
|
82
|
+
move(child);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
move(item);
|
|
86
|
+
}
|
|
87
|
+
#edges(owner, items) {
|
|
88
|
+
const members = new Set(items), seen = /* @__PURE__ */ new Set(), edges = [];
|
|
89
|
+
const inScope = (item) => [...ArchitectureModel.lineage(item)].find((at) => members.has(at));
|
|
90
|
+
for (const { source, target } of this.#model.relations) {
|
|
91
|
+
const upper = inScope(source), lower = inScope(target);
|
|
92
|
+
if (!upper || !lower || upper === lower || upper.parent !== owner) continue;
|
|
93
|
+
const pair = `${upper.id}>${lower.id}`;
|
|
94
|
+
if (seen.has(pair)) continue;
|
|
95
|
+
seen.add(pair);
|
|
96
|
+
edges.push({
|
|
97
|
+
upper: upper.id,
|
|
98
|
+
lower: lower.id,
|
|
99
|
+
priority: 0,
|
|
100
|
+
trunk: null
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return edges;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
export { ArchitectureLayout };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ArchitectureText } from "./ArchitectureText.js";
|
|
2
|
+
// src/charts/diagrams/ArchitectureLinkLabels.ts
|
|
3
|
+
var LINE = 12;
|
|
4
|
+
/** A label beside an upright line leaves the line showing. */
|
|
5
|
+
var GAP = 3;
|
|
6
|
+
var FRACTIONS = [
|
|
7
|
+
.5,
|
|
8
|
+
.32,
|
|
9
|
+
.68,
|
|
10
|
+
.18,
|
|
11
|
+
.82
|
|
12
|
+
];
|
|
13
|
+
var overlap = (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;
|
|
14
|
+
var crossed = (a, b, rect) => {
|
|
15
|
+
const steps = Math.ceil(Math.hypot(b.x - a.x, b.y - a.y) / 6);
|
|
16
|
+
for (let i = 0; i <= steps; i++) {
|
|
17
|
+
const x = a.x + (b.x - a.x) * i / steps, y = a.y + (b.y - a.y) * i / steps;
|
|
18
|
+
if (x > rect.x && x < rect.x + rect.width && y > rect.y && y < rect.y + rect.height) return true;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
};
|
|
22
|
+
/** Puts each label on its own line, at the first spot that hits no box, label or other line. Labels with no such spot are dropped, not stacked. */
|
|
23
|
+
var ArchitectureLinkLabels = class ArchitectureLinkLabels {
|
|
24
|
+
static place(requests, obstacles, size, bounds) {
|
|
25
|
+
const placed = [];
|
|
26
|
+
const order = requests.map((_, i) => i).sort((a, b) => ArchitectureLinkLabels.#length(requests[b].route) - ArchitectureLinkLabels.#length(requests[a].route));
|
|
27
|
+
for (const index of [...order].reverse()) {
|
|
28
|
+
const { lines } = requests[index];
|
|
29
|
+
if (!lines.length) continue;
|
|
30
|
+
for (const shown of lines.length > 1 ? [lines, lines.slice(0, 1)] : [lines]) {
|
|
31
|
+
const rect = ArchitectureLinkLabels.#spot(requests, index, shown, obstacles, placed, size, bounds);
|
|
32
|
+
if (rect) {
|
|
33
|
+
placed.push({
|
|
34
|
+
index,
|
|
35
|
+
lines: shown,
|
|
36
|
+
rect
|
|
37
|
+
});
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return placed;
|
|
43
|
+
}
|
|
44
|
+
static #length(route) {
|
|
45
|
+
return route.slice(1).reduce((sum, at, i) => sum + Math.hypot(at.x - route[i].x, at.y - route[i].y), 0);
|
|
46
|
+
}
|
|
47
|
+
static #spot(requests, index, lines, obstacles, placed, size, bounds) {
|
|
48
|
+
const route = requests[index].route;
|
|
49
|
+
const width = Math.max(...lines.map((line) => ArchitectureText.width(line, size))) + 6;
|
|
50
|
+
const height = lines.length * LINE * size / 10 + 6;
|
|
51
|
+
const segments = route.slice(1).map((end, i) => ({
|
|
52
|
+
start: route[i],
|
|
53
|
+
end
|
|
54
|
+
})).sort((a, b) => Math.hypot(b.end.x - b.start.x, b.end.y - b.start.y) - Math.hypot(a.end.x - a.start.x, a.end.y - a.start.y));
|
|
55
|
+
for (const { start, end } of segments) {
|
|
56
|
+
const upright = Math.abs(end.x - start.x) < 1;
|
|
57
|
+
for (const t of FRACTIONS) {
|
|
58
|
+
const x = start.x + (end.x - start.x) * t, y = start.y + (end.y - start.y) * t - height / 2;
|
|
59
|
+
for (const left of upright ? [x + GAP, x - GAP - width] : [x - width / 2]) {
|
|
60
|
+
const rect = {
|
|
61
|
+
x: left,
|
|
62
|
+
y,
|
|
63
|
+
width,
|
|
64
|
+
height
|
|
65
|
+
};
|
|
66
|
+
if (ArchitectureLinkLabels.#clear(rect, requests, index, obstacles, placed, bounds)) return rect;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
static #clear(rect, requests, index, obstacles, placed, bounds) {
|
|
73
|
+
if (rect.x < bounds.x || rect.y < bounds.y || rect.x + rect.width > bounds.x + bounds.width || rect.y + rect.height > bounds.y + bounds.height) return false;
|
|
74
|
+
if (obstacles.some((box) => overlap(rect, box)) || placed.some((label) => overlap(rect, label.rect))) return false;
|
|
75
|
+
return !requests.some((other, i) => i !== index && other.route.slice(1).some((at, k) => crossed(other.route[k], at, rect)));
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
export { ArchitectureLinkLabels };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ArchitectureKind, ArchitectureLevel } from "./architectureEnums.js";
|
|
2
|
+
// src/charts/diagrams/ArchitectureModel.ts
|
|
3
|
+
var DEPTH = {
|
|
4
|
+
[ArchitectureKind.Person]: 0,
|
|
5
|
+
[ArchitectureKind.System]: 0,
|
|
6
|
+
[ArchitectureKind.Boundary]: 0,
|
|
7
|
+
[ArchitectureKind.Container]: 1,
|
|
8
|
+
[ArchitectureKind.Database]: 1,
|
|
9
|
+
[ArchitectureKind.Component]: 2
|
|
10
|
+
};
|
|
11
|
+
var ZOOM = {
|
|
12
|
+
[ArchitectureLevel.Context]: 0,
|
|
13
|
+
[ArchitectureLevel.Containers]: 1,
|
|
14
|
+
[ArchitectureLevel.Components]: 2
|
|
15
|
+
};
|
|
16
|
+
var KINDS = new Set(Object.values(ArchitectureKind));
|
|
17
|
+
var truthy = (value) => value === true || value === 1 || typeof value === "string" && /^(true|yes|1)$/i.test(value);
|
|
18
|
+
var key = (value) => value === null || value === void 0 || value === "" ? null : String(value);
|
|
19
|
+
/**
|
|
20
|
+
* The element tree of a C4 model and what one zoom level shows of it. An opened element becomes a
|
|
21
|
+
* boundary, a hidden element's links rise to its nearest visible ancestor, and duplicates merge:
|
|
22
|
+
* Structurizr's implied relationships.
|
|
23
|
+
*/
|
|
24
|
+
var ArchitectureModel = class {
|
|
25
|
+
roots = [];
|
|
26
|
+
/** Everything drawn, parents before children. */
|
|
27
|
+
items = [];
|
|
28
|
+
relations = [];
|
|
29
|
+
#all = /* @__PURE__ */ new Map();
|
|
30
|
+
constructor(data, fields, level) {
|
|
31
|
+
this.#read(data?.nodes ?? [], fields);
|
|
32
|
+
this.#show(level);
|
|
33
|
+
this.#relate(data?.links ?? []);
|
|
34
|
+
}
|
|
35
|
+
#read(nodes, fields) {
|
|
36
|
+
for (const node of Array.isArray(nodes) ? nodes : []) {
|
|
37
|
+
const id = key(node?.id);
|
|
38
|
+
if (id === null || this.#all.has(id)) continue;
|
|
39
|
+
const raw = String(node[fields.kind] ?? "").trim().toLowerCase().replace(/^software\s*/, "");
|
|
40
|
+
this.#all.set(id, {
|
|
41
|
+
id,
|
|
42
|
+
node,
|
|
43
|
+
kind: KINDS.has(raw) ? raw : ArchitectureKind.Container,
|
|
44
|
+
name: key(node[fields.name]) ?? id,
|
|
45
|
+
external: truthy(node[fields.external]),
|
|
46
|
+
parent: null,
|
|
47
|
+
children: [],
|
|
48
|
+
open: false
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
for (const item of this.#all.values()) {
|
|
52
|
+
const above = this.#all.get(key(item.node[fields.parent]) ?? "");
|
|
53
|
+
if (!above || this.#above(above, item)) this.roots.push(item);
|
|
54
|
+
else {
|
|
55
|
+
item.parent = above;
|
|
56
|
+
above.children.push(item);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** True when `a` is `b` or contains it. A parent column that loops stops at the first repeat. */
|
|
61
|
+
#above(a, b) {
|
|
62
|
+
const seen = /* @__PURE__ */ new Set();
|
|
63
|
+
for (let at = a; at && !seen.has(at); at = at.parent) {
|
|
64
|
+
if (at === b) return true;
|
|
65
|
+
seen.add(at);
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
#show(level) {
|
|
70
|
+
const zoom = ZOOM[level];
|
|
71
|
+
const shown = (item) => DEPTH[item.kind] <= zoom;
|
|
72
|
+
const visit = (item) => {
|
|
73
|
+
this.items.push(item);
|
|
74
|
+
item.open = (item.kind === ArchitectureKind.Boundary || !item.external) && item.children.some(shown);
|
|
75
|
+
if (item.open) item.children.filter(shown).forEach(visit);
|
|
76
|
+
};
|
|
77
|
+
this.roots.forEach(visit);
|
|
78
|
+
}
|
|
79
|
+
#visible(id) {
|
|
80
|
+
const shown = new Set(this.items);
|
|
81
|
+
for (let at = this.#all.get(id ?? "") ?? null; at; at = at.parent) if (shown.has(at)) return at;
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
#relate(links) {
|
|
85
|
+
const merged = /* @__PURE__ */ new Map();
|
|
86
|
+
for (const link of Array.isArray(links) ? links : []) {
|
|
87
|
+
const source = this.#visible(key(link?.source)), target = this.#visible(key(link?.target));
|
|
88
|
+
if (!source || !target || this.#above(source, target) || this.#above(target, source)) continue;
|
|
89
|
+
const id = `${source.id}\0${target.id}`;
|
|
90
|
+
const relation = merged.get(id);
|
|
91
|
+
if (relation) relation.links.push(link);
|
|
92
|
+
else merged.set(id, {
|
|
93
|
+
key: id,
|
|
94
|
+
source,
|
|
95
|
+
target,
|
|
96
|
+
links: [link]
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
this.relations.push(...merged.values());
|
|
100
|
+
}
|
|
101
|
+
/** The item and all that contain it. */
|
|
102
|
+
static lineage(item) {
|
|
103
|
+
const line = /* @__PURE__ */ new Set();
|
|
104
|
+
for (let at = item; at; at = at.parent) line.add(at);
|
|
105
|
+
return line;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
export { ArchitectureModel };
|