@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,422 @@
|
|
|
1
|
+
import { DiagramText } from "./DiagramText.js";
|
|
2
|
+
import { SequenceFragmentKind, SequenceMessageKind, SequenceNoteSide, SequenceNumbering, SequenceSpacing } from "./sequenceEnums.js";
|
|
3
|
+
// src/charts/diagrams/SequenceLayout.ts
|
|
4
|
+
var SIZE = 10.5;
|
|
5
|
+
var HEAD_HEIGHT = 28;
|
|
6
|
+
var HEAD_MIN = 56;
|
|
7
|
+
var HEAD_MAX = 148;
|
|
8
|
+
var BAR = 10;
|
|
9
|
+
var STEP = 5;
|
|
10
|
+
var LOOP_WIDTH = 26;
|
|
11
|
+
var LOOP_HEIGHT = 15;
|
|
12
|
+
var LABEL_ROOM = 16;
|
|
13
|
+
var LABEL_CAP = 220;
|
|
14
|
+
var HEADER = 21;
|
|
15
|
+
var GUARD_ROW = 18;
|
|
16
|
+
var FRAME_PAD = 12;
|
|
17
|
+
var NOTE_WIDTH = 156;
|
|
18
|
+
var NOTE_LINES = 4;
|
|
19
|
+
var NOTE_LINE = 13;
|
|
20
|
+
var PILL_HEIGHT = 14;
|
|
21
|
+
var GAP_MIN = 40;
|
|
22
|
+
var GAP_MAX = 250;
|
|
23
|
+
var ROW_GAP_MIN = 2;
|
|
24
|
+
var ROW_GAP_MAX = 11;
|
|
25
|
+
/** Below this the text is too small to be worth keeping whole, so labels are cut instead. */
|
|
26
|
+
var SHRINK_LIMIT = .75;
|
|
27
|
+
/** Places a sequence diagram top to bottom: every message, operand guard and note has a band of its own, so vertical collisions cannot occur, and gaps widen to fit their labels. */
|
|
28
|
+
var SequenceLayout = class SequenceLayout {
|
|
29
|
+
static width(text, size = SIZE) {
|
|
30
|
+
return DiagramText.width(text, size);
|
|
31
|
+
}
|
|
32
|
+
static fit(text, room, size = SIZE) {
|
|
33
|
+
return DiagramText.fit(text, room, size);
|
|
34
|
+
}
|
|
35
|
+
/** Greedy word wrap, cutting the text where it would pass `NOTE_LINES`. */
|
|
36
|
+
static wrap(text) {
|
|
37
|
+
const lines = [];
|
|
38
|
+
for (const word of text.split(/\s+/).filter(Boolean)) {
|
|
39
|
+
const joined = lines.length ? `${lines.at(-1)} ${word}` : word;
|
|
40
|
+
if (lines.length && SequenceLayout.width(joined) <= NOTE_WIDTH) lines[lines.length - 1] = joined;
|
|
41
|
+
else lines.push(SequenceLayout.fit(word, NOTE_WIDTH));
|
|
42
|
+
}
|
|
43
|
+
if (lines.length <= NOTE_LINES) return lines;
|
|
44
|
+
const kept = lines.slice(0, NOTE_LINES);
|
|
45
|
+
kept[3] = `${SequenceLayout.fit(kept[3], 144).replace(/…$/, "")}…`;
|
|
46
|
+
return kept;
|
|
47
|
+
}
|
|
48
|
+
static number(step, index, numbering) {
|
|
49
|
+
if (numbering === SequenceNumbering.Flat) return String(index + 1);
|
|
50
|
+
return numbering === SequenceNumbering.Nested ? step.decimal : "";
|
|
51
|
+
}
|
|
52
|
+
static place(scene, options) {
|
|
53
|
+
const { lanes } = scene;
|
|
54
|
+
const headHeight = lanes.some((lane) => lane.actor) ? 46 : HEAD_HEIGHT;
|
|
55
|
+
const measured = SequenceLayout.vertical(scene, headHeight, 0, options.foot);
|
|
56
|
+
const bands = scene.steps.length + scene.remarks.length + 1;
|
|
57
|
+
const rowGap = Math.max(ROW_GAP_MIN, Math.min(ROW_GAP_MAX, (options.height - measured.height) / bands));
|
|
58
|
+
const rows = SequenceLayout.vertical(scene, headHeight, rowGap, options.foot);
|
|
59
|
+
const tall = Math.min(1, options.height / rows.height);
|
|
60
|
+
let gaps = SequenceLayout.gaps(scene, options);
|
|
61
|
+
const measure = (candidate) => SequenceLayout.horizontal(scene, options, rows, headHeight, candidate);
|
|
62
|
+
const natural = measure(gaps).width;
|
|
63
|
+
const room = options.width / tall, limit = options.width / Math.min(tall, SHRINK_LIMIT);
|
|
64
|
+
if (gaps.length && natural > limit) {
|
|
65
|
+
const excess = natural - limit, spare = gaps.reduce((sum, gap) => sum + Math.max(0, gap - GAP_MIN), 0);
|
|
66
|
+
gaps = gaps.map((gap) => gap - Math.max(0, gap - GAP_MIN) * Math.min(1, spare ? excess / spare : 0));
|
|
67
|
+
} else if (gaps.length && natural < room) {
|
|
68
|
+
const extra = (room - natural) / gaps.length;
|
|
69
|
+
gaps = gaps.map((gap) => Math.max(gap, Math.min(GAP_MAX, gap + extra)));
|
|
70
|
+
}
|
|
71
|
+
const plan = measure(gaps);
|
|
72
|
+
return {
|
|
73
|
+
...plan,
|
|
74
|
+
scale: plan.width ? Math.min(1, options.width / plan.width, options.height / plan.height) : 1
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Top-to-bottom bands, so a row can only ever hold one message, operand guard, note or fragment header. */
|
|
78
|
+
static vertical(scene, headHeight, rowGap, foot) {
|
|
79
|
+
const { steps, frames, remarks } = scene;
|
|
80
|
+
const rows = {
|
|
81
|
+
y: [],
|
|
82
|
+
end: [],
|
|
83
|
+
frameTop: /* @__PURE__ */ new Map(),
|
|
84
|
+
frameBottom: /* @__PURE__ */ new Map(),
|
|
85
|
+
branchY: /* @__PURE__ */ new Map(),
|
|
86
|
+
noteY: /* @__PURE__ */ new Map(),
|
|
87
|
+
top: headHeight,
|
|
88
|
+
bottom: 0,
|
|
89
|
+
height: 0
|
|
90
|
+
};
|
|
91
|
+
let y = headHeight + 8;
|
|
92
|
+
const notesAfter = (index) => {
|
|
93
|
+
for (const remark of remarks.filter((other) => other.after === index)) {
|
|
94
|
+
y += rowGap / 2;
|
|
95
|
+
rows.noteY.set(remark.key, y);
|
|
96
|
+
y += SequenceLayout.noteHeight(remark) + rowGap / 2 + 4;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
notesAfter(-1);
|
|
100
|
+
const outerFirst = [...frames].sort((a, b) => a.depth - b.depth);
|
|
101
|
+
steps.forEach((step, index) => {
|
|
102
|
+
for (const frame of outerFirst) {
|
|
103
|
+
const branch = frame.branches.find((other) => other.first === index);
|
|
104
|
+
if (branch) {
|
|
105
|
+
rows.branchY.set(`${frame.key}:${branch.first}`, y);
|
|
106
|
+
y += GUARD_ROW;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (const frame of outerFirst) if (frame.first === index) {
|
|
110
|
+
rows.frameTop.set(frame.key, y);
|
|
111
|
+
y += HEADER;
|
|
112
|
+
}
|
|
113
|
+
y += LABEL_ROOM + rowGap / 2;
|
|
114
|
+
rows.y[index] = y;
|
|
115
|
+
y += step.from === step.to ? 23 : 0;
|
|
116
|
+
rows.end[index] = y;
|
|
117
|
+
y += 4 + rowGap / 2;
|
|
118
|
+
notesAfter(index);
|
|
119
|
+
for (const frame of [...outerFirst].reverse()) if (frame.last === index) {
|
|
120
|
+
y += 2;
|
|
121
|
+
rows.frameBottom.set(frame.key, y);
|
|
122
|
+
y += 5;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
rows.bottom = y + 8;
|
|
126
|
+
rows.height = rows.bottom + (foot ? headHeight : 0);
|
|
127
|
+
return rows;
|
|
128
|
+
}
|
|
129
|
+
static noteHeight(remark) {
|
|
130
|
+
return SequenceLayout.wrap(remark.text).length * NOTE_LINE + 9;
|
|
131
|
+
}
|
|
132
|
+
/** Label widths become minimum gaps, narrow spans first; each shortfall is shared across the gaps it covers. */
|
|
133
|
+
static gaps(scene, options) {
|
|
134
|
+
const { lanes, steps, bars } = scene;
|
|
135
|
+
const heads = lanes.map((lane) => SequenceLayout.headWidth(lane.label));
|
|
136
|
+
const gaps = heads.slice(1).map((width, i) => Math.max(GAP_MIN, (heads[i] + width) / 2 + 12));
|
|
137
|
+
const reach = BAR + (options.activations ? Math.max(0, ...bars.map((bar) => bar.depth)) * STEP * 2 : 0);
|
|
138
|
+
const needs = steps.map((step, index) => {
|
|
139
|
+
const labelled = Math.min(options.width, SequenceLayout.width(step.label));
|
|
140
|
+
const pill = SequenceLayout.pillWidth(SequenceLayout.number(step, index, options.numbering));
|
|
141
|
+
if (step.from === step.to) return {
|
|
142
|
+
a: step.from,
|
|
143
|
+
b: step.from + 1,
|
|
144
|
+
need: Math.max(32, pill + 4) + labelled + reach + 14
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
a: Math.min(step.from, step.to),
|
|
148
|
+
b: Math.max(step.from, step.to),
|
|
149
|
+
need: labelled + pill + reach + 14
|
|
150
|
+
};
|
|
151
|
+
}).filter(({ b }) => b < lanes.length).sort((x, y) => x.b - x.a - (y.b - y.a));
|
|
152
|
+
for (const { a, b, need } of needs) {
|
|
153
|
+
const span = gaps.slice(a, b).reduce((sum, gap) => sum + gap, 0);
|
|
154
|
+
if (span < need) for (let i = a; i < b; i++) gaps[i] += (need - span) / (b - a);
|
|
155
|
+
}
|
|
156
|
+
const widest = Math.max(0, ...gaps);
|
|
157
|
+
return options.spacing === SequenceSpacing.Even ? gaps.map(() => widest) : gaps;
|
|
158
|
+
}
|
|
159
|
+
static headWidth(label) {
|
|
160
|
+
return Math.max(HEAD_MIN, Math.min(HEAD_MAX, SequenceLayout.width(label, 11.5) + 20));
|
|
161
|
+
}
|
|
162
|
+
static pillWidth(number) {
|
|
163
|
+
return number ? Math.max(PILL_HEIGHT, number.length * 5 + 8) : 0;
|
|
164
|
+
}
|
|
165
|
+
static horizontal(scene, options, rows, headHeight, gaps) {
|
|
166
|
+
const { lanes, steps, bars, remarks } = scene;
|
|
167
|
+
const xs = [0];
|
|
168
|
+
for (const gap of gaps) xs.push(xs.at(-1) + gap);
|
|
169
|
+
const depthOf = (depth) => options.activations ? depth : null;
|
|
170
|
+
const edge = (lane, depth, side) => depth === null ? xs[lane] : xs[lane] + depth * STEP + side * BAR / 2;
|
|
171
|
+
const heads = lanes.flatMap((lane, i) => {
|
|
172
|
+
const clear = Math.min(gaps[i - 1] ?? Infinity, gaps[i] ?? Infinity) - 8;
|
|
173
|
+
const width = Math.min(SequenceLayout.headWidth(lane.label), clear);
|
|
174
|
+
const text = SequenceLayout.fit(lane.label, width - (lane.actor ? 0 : 12), 11.5);
|
|
175
|
+
const at = (y, boxY, tag) => ({
|
|
176
|
+
key: `${tag}${lane.id}`,
|
|
177
|
+
lane: i,
|
|
178
|
+
x: xs[i] - width / 2,
|
|
179
|
+
y,
|
|
180
|
+
boxY,
|
|
181
|
+
width,
|
|
182
|
+
height: headHeight,
|
|
183
|
+
text,
|
|
184
|
+
actor: lane.actor,
|
|
185
|
+
textY: lane.actor ? y + headHeight - 3 : boxY + HEAD_HEIGHT / 2 + 4
|
|
186
|
+
});
|
|
187
|
+
const top = at(0, headHeight - HEAD_HEIGHT, "h:");
|
|
188
|
+
return options.foot ? [top, at(rows.bottom, rows.bottom, "f:")] : [top];
|
|
189
|
+
});
|
|
190
|
+
const lifelines = lanes.map((lane, i) => ({
|
|
191
|
+
key: lane.id,
|
|
192
|
+
x: xs[i],
|
|
193
|
+
y1: rows.top,
|
|
194
|
+
y2: rows.bottom
|
|
195
|
+
}));
|
|
196
|
+
const placedBars = options.activations ? bars.map((bar, i) => {
|
|
197
|
+
const y = bar.leaf ? rows.y[bar.first] + LOOP_HEIGHT - 3 : rows.y[bar.first] - 2;
|
|
198
|
+
const bottom = bar.leaf ? rows.end[bar.first] : rows.end[bar.last] + (bar.returned ? 2 : 7);
|
|
199
|
+
return {
|
|
200
|
+
key: `b${i}`,
|
|
201
|
+
bar,
|
|
202
|
+
x: xs[bar.lane] + bar.depth * STEP - BAR / 2,
|
|
203
|
+
y,
|
|
204
|
+
width: BAR,
|
|
205
|
+
height: Math.max(8, bottom - y)
|
|
206
|
+
};
|
|
207
|
+
}) : [];
|
|
208
|
+
const arrows = steps.map((step, index) => SequenceLayout.arrow(step, index, rows.y[index], xs, edge, depthOf, options));
|
|
209
|
+
const noteBoxes = remarks.map((remark) => SequenceLayout.note(remark, rows.noteY.get(remark.key), xs));
|
|
210
|
+
const frames = SequenceLayout.frames(scene, rows, xs, arrows, noteBoxes);
|
|
211
|
+
const ends = [
|
|
212
|
+
...heads.flatMap((head) => [head.x, head.x + head.width]),
|
|
213
|
+
...placedBars.flatMap((bar) => [bar.x, bar.x + bar.width]),
|
|
214
|
+
...frames.flatMap((frame) => [frame.x, frame.x + frame.width]),
|
|
215
|
+
...noteBoxes.flatMap((note) => [note.x, note.x + note.width]),
|
|
216
|
+
...arrows.flatMap((arrow, i) => SequenceLayout.reach(arrow, xs[steps[i].from]))
|
|
217
|
+
];
|
|
218
|
+
const left = ends.length ? Math.min(...ends) : 0;
|
|
219
|
+
return {
|
|
220
|
+
width: (ends.length ? Math.max(...ends) : 0) - left,
|
|
221
|
+
height: lanes.length ? rows.height : 0,
|
|
222
|
+
left,
|
|
223
|
+
heads,
|
|
224
|
+
lifelines,
|
|
225
|
+
bars: placedBars,
|
|
226
|
+
arrows,
|
|
227
|
+
frames,
|
|
228
|
+
notes: noteBoxes
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/** Horizontal extent of an arrow's parts, for bounding boxes. */
|
|
232
|
+
static reach(arrow, x) {
|
|
233
|
+
const { label, pill, loop } = arrow;
|
|
234
|
+
const out = [x];
|
|
235
|
+
if (loop !== null) out.push(loop);
|
|
236
|
+
if (label) out.push(label.anchor === "start" ? label.x + SequenceLayout.width(label.text) : label.x);
|
|
237
|
+
if (pill) out.push(pill.x, pill.x + pill.width);
|
|
238
|
+
return out;
|
|
239
|
+
}
|
|
240
|
+
static arrow(step, index, y, xs, edge, depthOf, options) {
|
|
241
|
+
const fromDepth = depthOf(step.fromDepth), toDepth = depthOf(step.toDepth);
|
|
242
|
+
const dashed = step.kind === SequenceMessageKind.Return;
|
|
243
|
+
const filled = step.kind === SequenceMessageKind.Sync || step.kind === SequenceMessageKind.Self;
|
|
244
|
+
const number = SequenceLayout.number(step, index, options.numbering);
|
|
245
|
+
const pillWide = SequenceLayout.pillWidth(number);
|
|
246
|
+
const shared = {
|
|
247
|
+
key: `m${index}`,
|
|
248
|
+
step,
|
|
249
|
+
number,
|
|
250
|
+
filled,
|
|
251
|
+
dashed
|
|
252
|
+
};
|
|
253
|
+
if (step.from === step.to) {
|
|
254
|
+
const start = edge(step.from, fromDepth, 1), back = edge(step.to, toDepth, 1);
|
|
255
|
+
const out = Math.max(start, back) + LOOP_WIDTH, bottom = y + LOOP_HEIGHT;
|
|
256
|
+
const textX = Math.max(out + 6, start + pillWide + 4);
|
|
257
|
+
const next = xs[step.from + 1];
|
|
258
|
+
const room = next === void 0 ? LABEL_CAP : next - textX - BAR - 2;
|
|
259
|
+
const text = SequenceLayout.fit(step.label, room);
|
|
260
|
+
return {
|
|
261
|
+
...shared,
|
|
262
|
+
d: `M${start},${y}L${out},${y}L${out},${bottom}L${back},${bottom}`,
|
|
263
|
+
head: SequenceLayout.tip(back, bottom, -1, filled),
|
|
264
|
+
loop: out,
|
|
265
|
+
label: text ? {
|
|
266
|
+
x: textX,
|
|
267
|
+
y: y + LOOP_HEIGHT / 2 + 4,
|
|
268
|
+
text,
|
|
269
|
+
anchor: "start"
|
|
270
|
+
} : null,
|
|
271
|
+
pill: pillWide ? {
|
|
272
|
+
x: start,
|
|
273
|
+
y: y - PILL_HEIGHT / 2,
|
|
274
|
+
width: pillWide
|
|
275
|
+
} : null
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const way = step.to > step.from ? 1 : -1;
|
|
279
|
+
const start = edge(step.from, fromDepth, way), tip = edge(step.to, toDepth, -way);
|
|
280
|
+
const pill = pillWide ? {
|
|
281
|
+
x: way > 0 ? start + 2 : start - 2 - pillWide,
|
|
282
|
+
y: y - PILL_HEIGHT / 2,
|
|
283
|
+
width: pillWide
|
|
284
|
+
} : null;
|
|
285
|
+
const low = Math.min(start, tip) + (way > 0 ? pillWide + 2 : 0), high = Math.max(start, tip) - (way < 0 ? pillWide + 2 : 0);
|
|
286
|
+
return {
|
|
287
|
+
...shared,
|
|
288
|
+
d: `M${start},${y}L${tip},${y}`,
|
|
289
|
+
head: SequenceLayout.tip(tip, y, way, filled),
|
|
290
|
+
label: SequenceLayout.label(step, y, low, high, xs, way),
|
|
291
|
+
pill,
|
|
292
|
+
loop: null
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
/** Prefers the first gap from the sender that holds the label whole, so it does not sit across a lifeline. */
|
|
296
|
+
static label(step, y, low, high, xs, way) {
|
|
297
|
+
const width = SequenceLayout.width(step.label);
|
|
298
|
+
const above = y - 5;
|
|
299
|
+
if (!step.label) return null;
|
|
300
|
+
const a = Math.min(step.from, step.to), b = Math.max(step.from, step.to);
|
|
301
|
+
const seats = Array.from({ length: b - a }, (_, i) => a + i);
|
|
302
|
+
for (const seat of way > 0 ? seats : seats.reverse()) {
|
|
303
|
+
const from = Math.max(low, xs[seat] + BAR), to = Math.min(high, xs[seat + 1] - BAR);
|
|
304
|
+
if (to - from >= width + 4) return {
|
|
305
|
+
x: (from + to) / 2,
|
|
306
|
+
y: above,
|
|
307
|
+
text: step.label,
|
|
308
|
+
anchor: "middle"
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const text = SequenceLayout.fit(step.label, high - low - 6);
|
|
312
|
+
return text ? {
|
|
313
|
+
x: (low + high) / 2,
|
|
314
|
+
y: above,
|
|
315
|
+
text,
|
|
316
|
+
anchor: "middle"
|
|
317
|
+
} : null;
|
|
318
|
+
}
|
|
319
|
+
/** `way` is the direction of travel. UML draws a filled triangle on synchronous calls, an open head otherwise. */
|
|
320
|
+
static tip(x, y, way, filled) {
|
|
321
|
+
const back = x - way * 10;
|
|
322
|
+
return filled ? `M${x},${y}L${back},${y - 5}L${back},${y + 5}Z` : `M${back},${y - 5}L${x},${y}L${back},${y + 5}`;
|
|
323
|
+
}
|
|
324
|
+
static note(remark, y, xs) {
|
|
325
|
+
const lines = SequenceLayout.wrap(remark.text);
|
|
326
|
+
const textWidth = Math.max(...lines.map((line) => SequenceLayout.width(line))) + 18;
|
|
327
|
+
const height = lines.length * NOTE_LINE + 9;
|
|
328
|
+
const a = xs[remark.lanes[0]], b = xs[remark.lanes[1]];
|
|
329
|
+
const width = remark.side === SequenceNoteSide.Over ? Math.max(textWidth, b - a + 24) : textWidth;
|
|
330
|
+
const x = remark.side === SequenceNoteSide.Left ? a - BAR - width : remark.side === SequenceNoteSide.Right ? a + BAR : (a + b - width) / 2;
|
|
331
|
+
const fold = 7;
|
|
332
|
+
return {
|
|
333
|
+
key: remark.key,
|
|
334
|
+
remark,
|
|
335
|
+
x,
|
|
336
|
+
y,
|
|
337
|
+
width,
|
|
338
|
+
height,
|
|
339
|
+
d: `M${x},${y}L${x + width - fold},${y}L${x + width},${y + fold}L${x + width},${y + height}L${x},${y + height}ZM${x + width - fold},${y}L${x + width - fold},${y + fold}L${x + width},${y + fold}`,
|
|
340
|
+
lines: lines.map((text, i) => ({
|
|
341
|
+
key: `${remark.key}:${i}`,
|
|
342
|
+
text,
|
|
343
|
+
x: x + 7,
|
|
344
|
+
y: y + 14 + i * NOTE_LINE
|
|
345
|
+
}))
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
/** Each frame wraps what it holds, and an outer frame wraps its inner frames, so nesting always reads. */
|
|
349
|
+
static frames(scene, rows, xs, arrows, notes) {
|
|
350
|
+
const { steps, remarks } = scene;
|
|
351
|
+
const spans = /* @__PURE__ */ new Map();
|
|
352
|
+
return [...scene.frames].sort((a, b) => b.depth - a.depth).map((frame) => {
|
|
353
|
+
const held = steps.flatMap((step, i) => i >= frame.first && i <= frame.last ? [
|
|
354
|
+
xs[step.from],
|
|
355
|
+
xs[step.to],
|
|
356
|
+
...SequenceLayout.reach(arrows[i], xs[step.from])
|
|
357
|
+
] : []);
|
|
358
|
+
const noted = remarks.flatMap((remark, i) => remark.after >= frame.first && remark.after <= frame.last ? [notes[i].x + 4, notes[i].x + notes[i].width - 4] : []);
|
|
359
|
+
const inner = scene.frames.filter((other) => other.depth > frame.depth && other.first >= frame.first && other.last <= frame.last).flatMap((other) => {
|
|
360
|
+
const span = spans.get(other.key);
|
|
361
|
+
return [span.x0 - 8 + FRAME_PAD, span.x1 + 8 - FRAME_PAD];
|
|
362
|
+
});
|
|
363
|
+
const tabText = frame.bounds && frame.kind === SequenceFragmentKind.Loop ? `loop(${frame.bounds})` : frame.kind;
|
|
364
|
+
const tabWidth = SequenceLayout.width(tabText, 10.5) * 1.08 + 16;
|
|
365
|
+
const guard = frame.guard ? `[${frame.guard}]` : "";
|
|
366
|
+
const all = [
|
|
367
|
+
...held,
|
|
368
|
+
...noted,
|
|
369
|
+
...inner
|
|
370
|
+
];
|
|
371
|
+
const x0 = Math.min(...all) - FRAME_PAD;
|
|
372
|
+
const x1 = Math.max(Math.max(...all) + FRAME_PAD, x0 + tabWidth + 10 + Math.min(120, SequenceLayout.width(guard)));
|
|
373
|
+
spans.set(frame.key, {
|
|
374
|
+
x0,
|
|
375
|
+
x1
|
|
376
|
+
});
|
|
377
|
+
const y = rows.frameTop.get(frame.key), bottom = rows.frameBottom.get(frame.key);
|
|
378
|
+
const guards = [];
|
|
379
|
+
const fitted = SequenceLayout.fit(guard, x1 - x0 - tabWidth - 10);
|
|
380
|
+
if (fitted) guards.push({
|
|
381
|
+
key: `${frame.key}:g`,
|
|
382
|
+
x: x0 + tabWidth + 6,
|
|
383
|
+
y: y + 14,
|
|
384
|
+
text: fitted,
|
|
385
|
+
anchor: "start"
|
|
386
|
+
});
|
|
387
|
+
const separators = frame.branches.map((branch) => {
|
|
388
|
+
const at = rows.branchY.get(`${frame.key}:${branch.first}`);
|
|
389
|
+
const text = SequenceLayout.fit(branch.guard ? `[${branch.guard}]` : "[else]", x1 - x0 - 12);
|
|
390
|
+
if (text) guards.push({
|
|
391
|
+
key: `${frame.key}:${branch.first}`,
|
|
392
|
+
x: x0 + 8,
|
|
393
|
+
y: at + 13,
|
|
394
|
+
text,
|
|
395
|
+
anchor: "start"
|
|
396
|
+
});
|
|
397
|
+
return {
|
|
398
|
+
key: `${frame.key}:${branch.first}`,
|
|
399
|
+
x1: x0,
|
|
400
|
+
x2: x1,
|
|
401
|
+
y: at
|
|
402
|
+
};
|
|
403
|
+
});
|
|
404
|
+
const h = 16, notch = 5;
|
|
405
|
+
return {
|
|
406
|
+
key: frame.key,
|
|
407
|
+
frame,
|
|
408
|
+
x: x0,
|
|
409
|
+
y,
|
|
410
|
+
width: x1 - x0,
|
|
411
|
+
height: bottom - y,
|
|
412
|
+
separators,
|
|
413
|
+
guards,
|
|
414
|
+
tabText,
|
|
415
|
+
tabX: x0 + 7,
|
|
416
|
+
tabY: y + 12,
|
|
417
|
+
tab: `M${x0},${y}L${x0 + tabWidth},${y}L${x0 + tabWidth},${y + h - notch}L${x0 + tabWidth - notch},${y + h}L${x0},${y + h}Z`
|
|
418
|
+
};
|
|
419
|
+
}).sort((a, b) => a.frame.depth - b.frame.depth);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
export { SequenceLayout };
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { SequenceFragmentKind, SequenceMessageKind, SequenceNoteSide, SequenceParticipantKind } from "./sequenceEnums.js";
|
|
2
|
+
// src/charts/diagrams/SequenceModel.ts
|
|
3
|
+
var present = (cell) => cell !== null && cell !== void 0 && cell !== "";
|
|
4
|
+
var KINDS = new Set(Object.values(SequenceMessageKind));
|
|
5
|
+
var FRAGMENTS = new Set(Object.values(SequenceFragmentKind));
|
|
6
|
+
var SIDES = new Set(Object.values(SequenceNoteSide));
|
|
7
|
+
var list = (value) => Array.isArray(value) ? value : [];
|
|
8
|
+
/** UML prints guards in square brackets, so a guard that already has them keeps one pair. */
|
|
9
|
+
var guardOf = (guard) => present(guard) ? String(guard).trim().replace(/^\[(.*)\]$/, "$1") : "";
|
|
10
|
+
/** Turns raw rows into lanes, ordered steps and well-nested fragments, then replays the calls to find each activation. */
|
|
11
|
+
var SequenceModel = class SequenceModel {
|
|
12
|
+
static rows(data) {
|
|
13
|
+
const rows = Array.isArray(data) ? data : data?.messages;
|
|
14
|
+
return Array.isArray(rows) ? rows : [];
|
|
15
|
+
}
|
|
16
|
+
static build(data, columns) {
|
|
17
|
+
const source = Array.isArray(data) ? { messages: data } : data;
|
|
18
|
+
const lanes = /* @__PURE__ */ new Map();
|
|
19
|
+
const enlist = (id, label = id, actor = false) => {
|
|
20
|
+
if (!lanes.has(id)) lanes.set(id, {
|
|
21
|
+
id,
|
|
22
|
+
label,
|
|
23
|
+
actor
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
for (const entry of list(source.participants)) if (typeof entry === "number" || typeof entry === "string" && entry) enlist(String(entry));
|
|
27
|
+
else if (typeof entry === "object" && entry && present(entry.id)) enlist(String(entry.id), entry.label || String(entry.id), entry.kind === SequenceParticipantKind.Actor);
|
|
28
|
+
const kept = [];
|
|
29
|
+
const drafts = SequenceModel.rows(data).flatMap((row, index) => {
|
|
30
|
+
const draft = SequenceModel.message(row, columns);
|
|
31
|
+
if (!draft) return [];
|
|
32
|
+
kept.push(index);
|
|
33
|
+
enlist(draft.from);
|
|
34
|
+
enlist(draft.to);
|
|
35
|
+
return [draft];
|
|
36
|
+
});
|
|
37
|
+
const order = [...lanes.values()];
|
|
38
|
+
const seat = new Map(order.map((lane, i) => [lane.id, i]));
|
|
39
|
+
const steps = drafts.map((draft, i) => ({
|
|
40
|
+
...draft,
|
|
41
|
+
row: kept[i],
|
|
42
|
+
from: seat.get(draft.from),
|
|
43
|
+
to: seat.get(draft.to),
|
|
44
|
+
decimal: "",
|
|
45
|
+
fromDepth: null,
|
|
46
|
+
toDepth: null
|
|
47
|
+
}));
|
|
48
|
+
const frames = SequenceModel.fragments(list(source.fragments), kept);
|
|
49
|
+
return {
|
|
50
|
+
lanes: order,
|
|
51
|
+
steps,
|
|
52
|
+
frames,
|
|
53
|
+
remarks: SequenceModel.notes(list(source.notes), kept, seat),
|
|
54
|
+
bars: SequenceModel.replay(steps, order, frames)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** A row needs a sender. A self call may leave out `to`, and a self kind between two lifelines is a plain call. */
|
|
58
|
+
static message(row, columns) {
|
|
59
|
+
if (row === null || typeof row !== "object") return null;
|
|
60
|
+
const sender = row[columns.from];
|
|
61
|
+
if (!present(sender)) return null;
|
|
62
|
+
const from = String(sender);
|
|
63
|
+
const given = String(row[columns.kind] ?? "").toLowerCase();
|
|
64
|
+
const named = KINDS.has(given) ? given : SequenceMessageKind.Sync;
|
|
65
|
+
const target = row[columns.to];
|
|
66
|
+
if (!present(target) && named !== SequenceMessageKind.Self) return null;
|
|
67
|
+
const to = present(target) ? String(target) : from;
|
|
68
|
+
return {
|
|
69
|
+
from,
|
|
70
|
+
to,
|
|
71
|
+
kind: named === SequenceMessageKind.Self && to !== from ? SequenceMessageKind.Sync : named,
|
|
72
|
+
label: present(row[columns.label]) ? String(row[columns.label]) : ""
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** Maps row indexes onto kept messages, then drops what is empty or crosses a fragment already accepted. */
|
|
76
|
+
static fragments(fragments, kept) {
|
|
77
|
+
const after = (index) => {
|
|
78
|
+
const at = kept.findIndex((row) => row >= index);
|
|
79
|
+
return at < 0 ? kept.length : at;
|
|
80
|
+
};
|
|
81
|
+
const before = (index) => after(index + 1) - 1;
|
|
82
|
+
const ranges = fragments.flatMap((fragment, i) => {
|
|
83
|
+
if (!fragment || !FRAGMENTS.has(fragment.kind) || !Number.isInteger(fragment.first) || !Number.isInteger(fragment.last)) return [];
|
|
84
|
+
const first = after(fragment.first), last = before(fragment.last);
|
|
85
|
+
if (first > last) return [];
|
|
86
|
+
const seen = /* @__PURE__ */ new Set();
|
|
87
|
+
const branches = (fragment.kind === SequenceFragmentKind.Alt ? list(fragment.branches) : []).filter((branch) => Number.isInteger(branch?.first)).map((branch) => ({
|
|
88
|
+
first: after(branch.first),
|
|
89
|
+
guard: guardOf(branch.guard)
|
|
90
|
+
})).filter((branch) => branch.first > first && branch.first <= last && !seen.has(branch.first) && !!seen.add(branch.first)).sort((a, b) => a.first - b.first);
|
|
91
|
+
return [{
|
|
92
|
+
key: `f${i}`,
|
|
93
|
+
kind: fragment.kind,
|
|
94
|
+
first,
|
|
95
|
+
last,
|
|
96
|
+
guard: guardOf(fragment.guard),
|
|
97
|
+
bounds: fragment.bounds ?? "",
|
|
98
|
+
branches,
|
|
99
|
+
depth: 0
|
|
100
|
+
}];
|
|
101
|
+
}).sort((a, b) => a.first - b.first || b.last - a.last);
|
|
102
|
+
const accepted = [];
|
|
103
|
+
for (const frame of ranges) {
|
|
104
|
+
const hosts = accepted.filter((other) => frame.first >= other.first && frame.last <= other.last);
|
|
105
|
+
const crossesFrame = accepted.some((other) => frame.first > other.first && frame.first <= other.last && frame.last > other.last);
|
|
106
|
+
const crossesBranch = hosts.some((host) => host.branches.some((branch) => frame.first < branch.first && branch.first <= frame.last));
|
|
107
|
+
if (crossesFrame || crossesBranch) continue;
|
|
108
|
+
accepted.push({
|
|
109
|
+
...frame,
|
|
110
|
+
depth: hosts.length
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return accepted;
|
|
114
|
+
}
|
|
115
|
+
static notes(notes, kept, seat) {
|
|
116
|
+
return notes.flatMap((note, i) => {
|
|
117
|
+
if (!note || !present(note.text)) return [];
|
|
118
|
+
const named = (Array.isArray(note.over) ? note.over : [note.over]).map((id) => seat.get(String(id)));
|
|
119
|
+
const lanes = [...new Set(named.filter((lane) => lane !== void 0))].sort((a, b) => a - b);
|
|
120
|
+
if (!lanes.length) return [];
|
|
121
|
+
const after = typeof note.after === "number" && Number.isFinite(note.after) ? kept.findLastIndex((row) => row <= note.after) : kept.length - 1;
|
|
122
|
+
const side = lanes.length === 1 && SIDES.has(note.side) ? note.side : SequenceNoteSide.Over;
|
|
123
|
+
return [{
|
|
124
|
+
key: `n${i}`,
|
|
125
|
+
after,
|
|
126
|
+
lanes: [lanes[0], lanes.at(-1)],
|
|
127
|
+
side,
|
|
128
|
+
text: String(note.text)
|
|
129
|
+
}];
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Replays messages as calls. A return ends the sender's innermost activation; a caller that sends again
|
|
134
|
+
* must have got control back, so its unreturned calls end too. Asynchronous work has no caller and
|
|
135
|
+
* ends when its lifeline next receives a message while idle. Each `alt` operand restarts from the
|
|
136
|
+
* activations open when the fragment began, since only one operand runs.
|
|
137
|
+
*/
|
|
138
|
+
static replay(steps, lanes, frames) {
|
|
139
|
+
const calls = [];
|
|
140
|
+
const entry = /* @__PURE__ */ new Map();
|
|
141
|
+
let open = [];
|
|
142
|
+
let roots = 0;
|
|
143
|
+
const top = (lane) => open.findLast((call) => call.lane === lane);
|
|
144
|
+
const begin = (lane, first, caller, hidden = false, leaf = false) => {
|
|
145
|
+
const depth = open.filter((call) => call.lane === lane && !call.hidden).length;
|
|
146
|
+
const call = {
|
|
147
|
+
lane,
|
|
148
|
+
depth: hidden ? 0 : depth,
|
|
149
|
+
first,
|
|
150
|
+
last: first,
|
|
151
|
+
returned: false,
|
|
152
|
+
leaf,
|
|
153
|
+
caller,
|
|
154
|
+
hidden,
|
|
155
|
+
decimal: "",
|
|
156
|
+
calls: 0
|
|
157
|
+
};
|
|
158
|
+
calls.push(call);
|
|
159
|
+
open.push(call);
|
|
160
|
+
return call;
|
|
161
|
+
};
|
|
162
|
+
const end = (call) => {
|
|
163
|
+
open = open.filter((other) => other !== call);
|
|
164
|
+
if (call.caller) call.caller.last = Math.max(call.caller.last, call.last);
|
|
165
|
+
};
|
|
166
|
+
const release = (call) => {
|
|
167
|
+
for (const callee of open.filter((other) => other.caller === call).reverse()) {
|
|
168
|
+
release(callee);
|
|
169
|
+
end(callee);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
const seen = (call) => call && !call.hidden ? call.depth : null;
|
|
173
|
+
steps.forEach((step, index) => {
|
|
174
|
+
for (const frame of frames.filter((other) => other.branches.some((branch) => branch.first === index)).sort((a, b) => a.depth - b.depth)) {
|
|
175
|
+
for (const call of [...open].reverse()) if (!entry.get(frame).includes(call)) end(call);
|
|
176
|
+
open = [...entry.get(frame)];
|
|
177
|
+
}
|
|
178
|
+
for (const frame of frames) if (frame.first === index) entry.set(frame, [...open]);
|
|
179
|
+
const sender = top(step.from) ?? begin(step.from, index, null, lanes[step.from].actor);
|
|
180
|
+
release(sender);
|
|
181
|
+
step.fromDepth = seen(sender);
|
|
182
|
+
sender.last = index;
|
|
183
|
+
const number = () => sender.decimal ? `${sender.decimal}.${++sender.calls}` : String(++roots);
|
|
184
|
+
if (step.kind === SequenceMessageKind.Return) {
|
|
185
|
+
if (step.from === step.to) {
|
|
186
|
+
step.toDepth = step.fromDepth;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!sender.hidden) {
|
|
190
|
+
sender.returned = true;
|
|
191
|
+
end(sender);
|
|
192
|
+
}
|
|
193
|
+
const receiver = top(step.to);
|
|
194
|
+
if (receiver) receiver.last = index;
|
|
195
|
+
step.toDepth = seen(receiver);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
step.decimal = number();
|
|
199
|
+
if (step.from === step.to) {
|
|
200
|
+
const leaf = begin(step.to, index, null, false, true);
|
|
201
|
+
step.toDepth = leaf.depth;
|
|
202
|
+
end(leaf);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const current = top(step.to);
|
|
206
|
+
if (current && !current.hidden && !current.caller && !open.some((other) => other.caller === current)) end(current);
|
|
207
|
+
const callee = begin(step.to, index, step.kind === SequenceMessageKind.Sync ? sender : null);
|
|
208
|
+
callee.decimal = step.decimal;
|
|
209
|
+
step.toDepth = callee.depth;
|
|
210
|
+
});
|
|
211
|
+
return calls.filter((call) => !call.hidden).map(({ lane, depth, first, last, returned, leaf }) => ({
|
|
212
|
+
lane,
|
|
213
|
+
depth,
|
|
214
|
+
first,
|
|
215
|
+
last,
|
|
216
|
+
returned,
|
|
217
|
+
leaf
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
export { SequenceModel };
|