@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.
Files changed (238) hide show
  1. package/README.md +111 -14
  2. package/dist/ExcelWriter-3ZSYUH4T.cjs +154 -0
  3. package/dist/GraphType.js +24 -38
  4. package/dist/PdfWriter-6HJFYMCO.cjs +205 -0
  5. package/dist/catalog-YWX6FHRM.cjs +8 -0
  6. package/dist/catalog.js +5 -1
  7. package/dist/charts/bars/index.js +2 -12
  8. package/dist/charts/comparison/EffectForestPlot.js +1 -1
  9. package/dist/charts/comparison/index.js +1 -8
  10. package/dist/charts/demography/Nomogram.js +3 -3
  11. package/dist/charts/density/HexbinChart.js +6 -4
  12. package/dist/charts/diagrams/ArchitectureCanvas.js +68 -0
  13. package/dist/charts/diagrams/ArchitectureDiagram.js +331 -0
  14. package/dist/charts/diagrams/ArchitectureLayout.js +106 -0
  15. package/dist/charts/diagrams/ArchitectureLinkLabels.js +78 -0
  16. package/dist/charts/diagrams/ArchitectureModel.js +108 -0
  17. package/dist/charts/diagrams/ArchitectureRouter.js +348 -0
  18. package/dist/charts/diagrams/ArchitectureSamples.js +312 -0
  19. package/dist/charts/diagrams/ArchitectureShapes.js +130 -0
  20. package/dist/charts/diagrams/ArchitectureText.js +25 -0
  21. package/dist/charts/diagrams/DiagramText.js +38 -0
  22. package/dist/charts/diagrams/EntityRelationshipDiagram.js +465 -0
  23. package/dist/charts/diagrams/ErCrowsFoot.js +33 -0
  24. package/dist/charts/diagrams/ErSamples.js +205 -0
  25. package/dist/charts/diagrams/ErSchema.js +122 -0
  26. package/dist/charts/diagrams/LogicCircuit.js +154 -0
  27. package/dist/charts/diagrams/LogicCircuitLayout.js +600 -0
  28. package/dist/charts/diagrams/LogicCircuitSamples.js +66 -0
  29. package/dist/charts/diagrams/LogicGateDiagram.js +216 -0
  30. package/dist/charts/diagrams/LogicGateSymbol.js +113 -0
  31. package/dist/charts/diagrams/NetworkTopologyDiagram.js +247 -0
  32. package/dist/charts/diagrams/SequenceDiagram.js +351 -0
  33. package/dist/charts/diagrams/SequenceLayout.js +422 -0
  34. package/dist/charts/diagrams/SequenceModel.js +221 -0
  35. package/dist/charts/diagrams/TopologyLayout.js +143 -0
  36. package/dist/charts/diagrams/architectureEnums.js +50 -0
  37. package/dist/charts/diagrams/index.js +26 -0
  38. package/dist/charts/diagrams/logicEnums.js +42 -0
  39. package/dist/charts/diagrams/sequenceEnums.js +40 -0
  40. package/dist/charts/distribution/HistogramChart.js +25 -3
  41. package/dist/charts/distribution/index.js +1 -3
  42. package/dist/charts/earthscience/HovmollerDiagram.js +477 -0
  43. package/dist/charts/earthscience/HovmollerEnums.js +42 -0
  44. package/dist/charts/earthscience/HovmollerField.js +180 -0
  45. package/dist/charts/earthscience/HovmollerSample.js +40 -0
  46. package/dist/charts/earthscience/HovmollerSpeed.js +71 -0
  47. package/dist/charts/earthscience/index.js +10 -2
  48. package/dist/charts/engineering/ConstellationDiagram.js +26 -6
  49. package/dist/charts/engineering/EyeDiagram.js +2 -1
  50. package/dist/charts/engineering/PoleZeroPlot.js +32 -8
  51. package/dist/charts/financial/RelativeStrengthRotation.js +434 -0
  52. package/dist/charts/financial/RelativeStrengthRotationMath.js +71 -0
  53. package/dist/charts/financial/RelativeStrengthRotationSamples.js +27 -0
  54. package/dist/charts/financial/index.js +5 -3
  55. package/dist/charts/flow/index.js +1 -5
  56. package/dist/charts/frameworks/AnsoffMatrix.js +523 -0
  57. package/dist/charts/frameworks/FrameworkText.js +98 -0
  58. package/dist/charts/frameworks/NpsGauge.js +279 -0
  59. package/dist/charts/frameworks/NpsSurvey.js +58 -0
  60. package/dist/charts/frameworks/PerceptualMap.js +419 -0
  61. package/dist/charts/frameworks/PerceptualProjection.js +142 -0
  62. package/dist/charts/frameworks/PositioningSamples.js +191 -0
  63. package/dist/charts/frameworks/RaciMatrix.js +458 -0
  64. package/dist/charts/frameworks/RaciPlan.js +81 -0
  65. package/dist/charts/frameworks/RaciSamples.js +76 -0
  66. package/dist/charts/frameworks/SwotMatrix.js +358 -0
  67. package/dist/charts/frameworks/SwotSamples.js +85 -0
  68. package/dist/charts/frameworks/VanWestendorpChart.js +187 -0
  69. package/dist/charts/frameworks/VanWestendorpSurvey.js +197 -0
  70. package/dist/charts/frameworks/index.js +26 -0
  71. package/dist/charts/frameworks/raciEnums.js +30 -0
  72. package/dist/charts/frameworks/swotEnums.js +34 -0
  73. package/dist/charts/genomics/CircosPlot.js +1 -1
  74. package/dist/charts/geoflows/GeoFlowChart.js +2 -2
  75. package/dist/charts/geoflows/GeoFlowSample.js +2 -1
  76. package/dist/charts/geomaps/FloorPlanMap.js +324 -0
  77. package/dist/charts/geomaps/FloorPlanModel.js +179 -0
  78. package/dist/charts/geomaps/FloorPlanSamples.js +416 -0
  79. package/dist/charts/geomaps/GeoLand.js +2 -2
  80. package/dist/charts/geomaps/GeoSamples.js +2 -1
  81. package/dist/charts/geomaps/GraticuleMap.js +2 -2
  82. package/dist/charts/geomaps/RoomLabelFit.js +200 -0
  83. package/dist/charts/geomaps/TissotMap.js +2 -2
  84. package/dist/charts/geomaps/index.js +8 -4
  85. package/dist/charts/hierarchy/SunburstChart.js +17 -10
  86. package/dist/charts/hierarchy/index.js +2 -11
  87. package/dist/charts/indicators/BulletChart.js +8 -2
  88. package/dist/charts/indicators/SpeedometerChart.js +1 -1
  89. package/dist/charts/indicators/ThermometerChart.js +1 -1
  90. package/dist/charts/lifescience/LoessFit.js +113 -0
  91. package/dist/charts/lifescience/MaPlot.js +337 -0
  92. package/dist/charts/lifescience/MaPlotSamples.js +35 -0
  93. package/dist/charts/lifescience/OncoGlyph.js +137 -0
  94. package/dist/charts/lifescience/OncoPrint.js +373 -0
  95. package/dist/charts/lifescience/OncoPrintMatrix.js +107 -0
  96. package/dist/charts/lifescience/OncoPrintSamples.js +142 -0
  97. package/dist/charts/lifescience/PedigreeChart.js +306 -0
  98. package/dist/charts/lifescience/PedigreeLayout.js +261 -0
  99. package/dist/charts/lifescience/PedigreeRows.js +70 -0
  100. package/dist/charts/lifescience/PedigreeSamples.js +161 -0
  101. package/dist/charts/lifescience/PedigreeSymbol.js +47 -0
  102. package/dist/charts/lifescience/RamachandranPlot.js +364 -0
  103. package/dist/charts/lifescience/RamachandranRegions.js +2712 -0
  104. package/dist/charts/lifescience/RamachandranSamples.js +235 -0
  105. package/dist/charts/lifescience/index.js +25 -0
  106. package/dist/charts/lifescience/pedigreeEnums.js +21 -0
  107. package/dist/charts/lifescience/ramachandranEnums.js +34 -0
  108. package/dist/charts/linesareas/DualAxisChart.js +1 -1
  109. package/dist/charts/linesareas/LineEnums.js +1 -15
  110. package/dist/charts/linesareas/MultiLineChart.js +1 -1
  111. package/dist/charts/linesareas/TrendChart.js +15 -5
  112. package/dist/charts/linesareas/index.js +2 -6
  113. package/dist/charts/massive/MassiveChart.js +3 -2
  114. package/dist/charts/mathematical/FourierSeriesChart.js +7 -1
  115. package/dist/charts/mathematical/index.js +2 -6
  116. package/dist/charts/matrix/GridPlot.js +27 -2
  117. package/dist/charts/matrix/TilegramChart.js +2 -2
  118. package/dist/charts/mlcharts/AttentionGrid.js +79 -0
  119. package/dist/charts/mlcharts/AttentionHeatmap.js +329 -0
  120. package/dist/charts/mlcharts/AttentionModel.js +132 -0
  121. package/dist/charts/mlcharts/AttentionSamples.js +82 -0
  122. package/dist/charts/mlcharts/CellNumber.js +8 -0
  123. package/dist/charts/mlcharts/CurveDiagnosticSamples.js +95 -0
  124. package/dist/charts/mlcharts/CurvePickMark.js +31 -0
  125. package/dist/charts/mlcharts/DecisionBoundaryChart.js +316 -0
  126. package/dist/charts/mlcharts/DecisionSamples.js +60 -0
  127. package/dist/charts/mlcharts/DecisionSurface.js +130 -0
  128. package/dist/charts/mlcharts/DistinctTicks.js +13 -0
  129. package/dist/charts/mlcharts/ElbowMath.js +153 -0
  130. package/dist/charts/mlcharts/ElbowPlot.js +273 -0
  131. package/dist/charts/mlcharts/ExplainabilitySamples.js +70 -0
  132. package/dist/charts/mlcharts/FeatureImportanceChart.js +150 -0
  133. package/dist/charts/mlcharts/ImportanceRanking.js +98 -0
  134. package/dist/charts/mlcharts/LearningCurveChart.js +276 -0
  135. package/dist/charts/mlcharts/LearningCurveMath.js +92 -0
  136. package/dist/charts/mlcharts/PartialDependence.js +92 -0
  137. package/dist/charts/mlcharts/PartialDependencePlot.js +225 -0
  138. package/dist/charts/mlcharts/WideLabel.js +28 -0
  139. package/dist/charts/mlcharts/curveDiagnosticEnums.js +37 -0
  140. package/dist/charts/mlcharts/index.js +32 -0
  141. package/dist/charts/proportion/index.js +5 -9
  142. package/dist/charts/scatter/CategoryScatterBase.js +1 -4
  143. package/dist/charts/scatter/ConnectedScatter.js +7 -4
  144. package/dist/charts/scatter/MarginalScatter.js +13 -8
  145. package/dist/charts/scatter/index.js +4 -17
  146. package/dist/charts/scientific/SpectrogramChart.js +9 -3
  147. package/dist/charts/scientific/index.js +2 -2
  148. package/dist/charts/signals/index.js +3 -13
  149. package/dist/charts/statistics/LorenzCurve.js +7 -4
  150. package/dist/charts/threebars/Candlestick3DChart.js +18 -2
  151. package/dist/charts/threeglobe/GlobeChart.js +2 -2
  152. package/dist/charts/threeglobe/GlobeSamples.js +2 -1
  153. package/dist/charts/threeglobe/RingGlobe.js +1 -1
  154. package/dist/charts/threenetwork/ConeTreeChart.js +2 -1
  155. package/dist/charts/threenetwork/Gantt3DChart.js +3 -2
  156. package/dist/charts/threesurfaces/Ridgeline3DChart.js +22 -4
  157. package/dist/charts/threevolume/LorenzAttractor.js +19 -0
  158. package/dist/charts/threevolume/VolumeRaycastChart.js +4 -3
  159. package/dist/charts/threevolume/volumeEnums.js +1 -0
  160. package/dist/charts/timeline/index.js +2 -10
  161. package/dist/chunk-753UBLV2.cjs +33 -0
  162. package/dist/chunk-VX54A7KA.cjs +17 -0
  163. package/dist/{chunk-W5GAAAZY.cjs → chunk-WAODDPYX.cjs} +24874 -17620
  164. package/dist/chunk-Z3JPIJSC.cjs +338 -0
  165. package/dist/core/CartesianChart.js +87 -11
  166. package/dist/core/Chart.js +63 -15
  167. package/dist/core/Chart2D.js +54 -17
  168. package/dist/core/Chart3D.js +56 -20
  169. package/dist/core/ChartExportError.js +2 -0
  170. package/dist/core/ChartExporter.js +50 -8
  171. package/dist/core/ColumnCheck.js +53 -0
  172. package/dist/core/Context2D.js +29 -15
  173. package/dist/core/Context3D.js +7 -2
  174. package/dist/core/ExcelWriter.js +152 -0
  175. package/dist/core/ExportText.js +28 -0
  176. package/dist/core/MarkOrigin.js +88 -0
  177. package/dist/core/MarkStyle.js +294 -0
  178. package/dist/core/Palette.js +27 -7
  179. package/dist/core/PdfPage.js +13 -0
  180. package/dist/core/PdfWriter.js +245 -0
  181. package/dist/core/Scales.js +52 -2
  182. package/dist/core/SceneOverlay.js +4 -4
  183. package/dist/core/TextFit.js +37 -0
  184. package/dist/core/Theme.js +28 -1
  185. package/dist/core/enums.js +33 -1
  186. package/dist/core/pathMorph.js +15 -0
  187. package/dist/data/SampleData.js +1 -17
  188. package/dist/data/WorldSample.js +23 -0
  189. package/dist/index.cjs +810 -691
  190. package/dist/index.d.cts +8390 -2925
  191. package/dist/index.d.ts +8392 -2927
  192. package/dist/index.js +106 -56
  193. package/dist/react/ChartView.js +18 -8
  194. package/dist/react/GraphHandle.js +2 -0
  195. package/dist/react/components.js +74 -116
  196. package/package.json +13 -3
  197. package/dist/catalog-4UP2YFNR.cjs +0 -7
  198. package/dist/charts/bars/BulletBar.js +0 -184
  199. package/dist/charts/bars/ErrorBarColumn.js +0 -147
  200. package/dist/charts/bars/HistogramBar.js +0 -217
  201. package/dist/charts/bars/MarimekkoBar.js +0 -191
  202. package/dist/charts/bars/ParetoBar.js +0 -143
  203. package/dist/charts/comparison/PanelChart.js +0 -70
  204. package/dist/charts/comparison/SmallMultipleBars.js +0 -134
  205. package/dist/charts/comparison/SpanChart.js +0 -118
  206. package/dist/charts/comparison/TrellisChart.js +0 -158
  207. package/dist/charts/distribution/RidgeSlabs3DChart.js +0 -169
  208. package/dist/charts/financial/Candlestick3D.js +0 -157
  209. package/dist/charts/flow/BumpFlow.js +0 -212
  210. package/dist/charts/flow/StreamFlow.js +0 -174
  211. package/dist/charts/geomaps/ThiessenMap.js +0 -106
  212. package/dist/charts/hierarchy/ConeTree3D.js +0 -135
  213. package/dist/charts/hierarchy/Hierarchy3DChart.js +0 -27
  214. package/dist/charts/hierarchy/SpherePack3D.js +0 -81
  215. package/dist/charts/hierarchy/SunburstTower3D.js +0 -93
  216. package/dist/charts/hierarchy/TreemapSkyline3D.js +0 -98
  217. package/dist/charts/linesareas/ConnectedScatterTime.js +0 -206
  218. package/dist/charts/linesareas/SmallMultipleLines.js +0 -153
  219. package/dist/charts/mathematical/ParametricSurface3D.js +0 -234
  220. package/dist/charts/mathematical/StrangeAttractor3D.js +0 -251
  221. package/dist/charts/proportion/ConeFunnel.js +0 -126
  222. package/dist/charts/proportion/ExtrudedPieChart.js +0 -123
  223. package/dist/charts/scatter/BeeswarmScatter.js +0 -151
  224. package/dist/charts/scatter/BinnedScatterBase.js +0 -51
  225. package/dist/charts/scatter/ContourScatter.js +0 -116
  226. package/dist/charts/scatter/GridBinScatter.js +0 -98
  227. package/dist/charts/scatter/HexbinScatter.js +0 -74
  228. package/dist/charts/scatter/StripScatter.js +0 -107
  229. package/dist/charts/scatter/SunflowerScatter.js +0 -109
  230. package/dist/charts/signals/ConstellationChart.js +0 -153
  231. package/dist/charts/signals/CorrelogramChart.js +0 -171
  232. package/dist/charts/signals/EyeDiagramChart.js +0 -155
  233. package/dist/charts/signals/PoleZeroChart.js +0 -164
  234. package/dist/charts/signals/SpectrogramChart.js +0 -79
  235. package/dist/charts/timeline/CalendarSkylineChart.js +0 -128
  236. package/dist/charts/timeline/ScheduleBlocksChart.js +0 -119
  237. package/dist/charts/timeline/SpiralTimeline.js +0 -154
  238. 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 };