kalfa 3.0.0.dev0__py3-none-any.whl

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 (148) hide show
  1. kalfa/__init__.py +19 -0
  2. kalfa/api.py +284 -0
  3. kalfa/board/__init__.py +1 -0
  4. kalfa/board/answers.py +419 -0
  5. kalfa/board/server.py +536 -0
  6. kalfa/board/static/api.js +176 -0
  7. kalfa/board/static/app.js +122 -0
  8. kalfa/board/static/board.css +228 -0
  9. kalfa/board/static/charts.css +46 -0
  10. kalfa/board/static/components/badge.js +14 -0
  11. kalfa/board/static/components/chart.js +204 -0
  12. kalfa/board/static/components/diagram.js +857 -0
  13. kalfa/board/static/components/error.js +17 -0
  14. kalfa/board/static/components/loading.js +7 -0
  15. kalfa/board/static/components/progress.js +25 -0
  16. kalfa/board/static/components/settings.js +418 -0
  17. kalfa/board/static/components/spark.js +43 -0
  18. kalfa/board/static/components/table.js +156 -0
  19. kalfa/board/static/diagram.css +76 -0
  20. kalfa/board/static/format.js +138 -0
  21. kalfa/board/static/index.html +34 -0
  22. kalfa/board/static/plot.js +594 -0
  23. kalfa/board/static/predictions.css +20 -0
  24. kalfa/board/static/store.js +215 -0
  25. kalfa/board/static/views/compare.js +185 -0
  26. kalfa/board/static/views/home.js +90 -0
  27. kalfa/board/static/views/record/config.js +81 -0
  28. kalfa/board/static/views/record/curves.js +137 -0
  29. kalfa/board/static/views/record/data.js +378 -0
  30. kalfa/board/static/views/record/files.js +128 -0
  31. kalfa/board/static/views/record/log.js +164 -0
  32. kalfa/board/static/views/record/model.js +286 -0
  33. kalfa/board/static/views/record/overview.js +216 -0
  34. kalfa/board/static/views/record/plots.js +103 -0
  35. kalfa/board/static/views/record/predictions.js +834 -0
  36. kalfa/board/static/views/record.js +179 -0
  37. kalfa/board/static/views/runs.js +124 -0
  38. kalfa/board/static/views/sweep.js +282 -0
  39. kalfa/board/watch.py +87 -0
  40. kalfa/cli.py +371 -0
  41. kalfa/commands/__init__.py +0 -0
  42. kalfa/commands/common.py +8 -0
  43. kalfa/commands/export.py +117 -0
  44. kalfa/commands/generate.py +31 -0
  45. kalfa/commands/guide/__init__.py +101 -0
  46. kalfa/commands/guide/about.py +93 -0
  47. kalfa/commands/guide/calls.py +102 -0
  48. kalfa/commands/guide/sections.py +186 -0
  49. kalfa/commands/guide/seen.py +307 -0
  50. kalfa/commands/guide/show.py +106 -0
  51. kalfa/commands/guide/training.py +333 -0
  52. kalfa/commands/guide/words.py +97 -0
  53. kalfa/commands/legos.py +508 -0
  54. kalfa/commands/plots.py +27 -0
  55. kalfa/commands/report/__init__.py +64 -0
  56. kalfa/commands/report/page.py +112 -0
  57. kalfa/commands/report/predictions.py +216 -0
  58. kalfa/commands/report/sections.py +356 -0
  59. kalfa/commands/stop.py +60 -0
  60. kalfa/commands/sweep/__init__.py +202 -0
  61. kalfa/commands/sweep/checks.py +98 -0
  62. kalfa/commands/sweep/plan.py +183 -0
  63. kalfa/commands/sweep/points.py +123 -0
  64. kalfa/commands/sweep/runner.py +100 -0
  65. kalfa/commands/sweep/space.py +67 -0
  66. kalfa/commands/sweep/strategies.py +228 -0
  67. kalfa/core/__init__.py +0 -0
  68. kalfa/core/aliases.py +229 -0
  69. kalfa/core/build.py +727 -0
  70. kalfa/core/check.py +550 -0
  71. kalfa/core/coerce.py +36 -0
  72. kalfa/core/context.py +149 -0
  73. kalfa/core/device.py +63 -0
  74. kalfa/core/errors.py +45 -0
  75. kalfa/core/graph.py +304 -0
  76. kalfa/core/history.py +70 -0
  77. kalfa/core/loop.py +710 -0
  78. kalfa/core/make.py +121 -0
  79. kalfa/core/plan.py +156 -0
  80. kalfa/core/predict.py +290 -0
  81. kalfa/core/protocols.py +122 -0
  82. kalfa/core/record.py +347 -0
  83. kalfa/core/refs.py +88 -0
  84. kalfa/core/rng.py +100 -0
  85. kalfa/core/rules.py +513 -0
  86. kalfa/core/run.py +568 -0
  87. kalfa/core/spec.py +413 -0
  88. kalfa/core/state.py +100 -0
  89. kalfa/core/wrap.py +671 -0
  90. kalfa/records/__init__.py +24 -0
  91. kalfa/records/compare.py +86 -0
  92. kalfa/records/reader.py +561 -0
  93. kalfa/records/stats/__init__.py +36 -0
  94. kalfa/records/stats/choices.py +282 -0
  95. kalfa/records/stats/classes.py +236 -0
  96. kalfa/records/stats/measures.py +144 -0
  97. kalfa/records/stats/pairs.py +225 -0
  98. kalfa/records/stats/tables.py +153 -0
  99. kalfa/records/sweep.py +244 -0
  100. kalfa/std/__init__.py +17 -0
  101. kalfa/std/actions.py +35 -0
  102. kalfa/std/common.py +56 -0
  103. kalfa/std/data/__init__.py +1 -0
  104. kalfa/std/data/base.py +45 -0
  105. kalfa/std/data/columns.py +190 -0
  106. kalfa/std/data/flow.py +317 -0
  107. kalfa/std/data/images.py +443 -0
  108. kalfa/std/data/options.py +147 -0
  109. kalfa/std/data/pre.py +84 -0
  110. kalfa/std/data/splits.py +140 -0
  111. kalfa/std/data/stream.py +294 -0
  112. kalfa/std/data/table.py +642 -0
  113. kalfa/std/data/text.py +436 -0
  114. kalfa/std/data/transforms.py +327 -0
  115. kalfa/std/data/weights.py +113 -0
  116. kalfa/std/data/window.py +135 -0
  117. kalfa/std/figure.py +37 -0
  118. kalfa/std/init.py +72 -0
  119. kalfa/std/loggers.py +105 -0
  120. kalfa/std/losses.py +161 -0
  121. kalfa/std/metrics.py +463 -0
  122. kalfa/std/models.py +122 -0
  123. kalfa/std/notes.py +1553 -0
  124. kalfa/std/objectives.py +360 -0
  125. kalfa/std/plots/__init__.py +75 -0
  126. kalfa/std/plots/classification.py +207 -0
  127. kalfa/std/plots/common.py +44 -0
  128. kalfa/std/plots/curves.py +56 -0
  129. kalfa/std/plots/images.py +188 -0
  130. kalfa/std/plots/pairs.py +66 -0
  131. kalfa/std/plots/predictions.py +154 -0
  132. kalfa/std/samplers.py +261 -0
  133. kalfa/std/samples.py +72 -0
  134. kalfa/std/schedules.py +74 -0
  135. kalfa/std/steps.py +133 -0
  136. kalfa/std/triggers.py +135 -0
  137. kalfa/summary.py +296 -0
  138. kalfa/tui/__init__.py +4 -0
  139. kalfa/tui/app.py +107 -0
  140. kalfa/tui/config.py +61 -0
  141. kalfa/tui/guide.py +100 -0
  142. kalfa/tui/jobs.py +97 -0
  143. kalfa/tui/legos.py +62 -0
  144. kalfa/tui/records.py +439 -0
  145. kalfa-3.0.0.dev0.dist-info/METADATA +261 -0
  146. kalfa-3.0.0.dev0.dist-info/RECORD +148 -0
  147. kalfa-3.0.0.dev0.dist-info/WHEEL +4 -0
  148. kalfa-3.0.0.dev0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,857 @@
1
+ const SIZE = { line: 14, padX: 10, padY: 7, minWidth: 64, chars: 30, head: 22 };
2
+ const SPACE = { rank: 46, order: 16, dummy: 8, port: 18, pad: 12 };
3
+ const CHAR = { label: 7.1, detail: 6.1, mono: 6.7, corner: 6.0, head: 6.7 };
4
+ const SWEEPS = 8;
5
+ const PASSES = 6;
6
+
7
+ function textOf(value) {
8
+ return value === null || value === undefined ? "" : String(value);
9
+ }
10
+
11
+ function clip(value, chars) {
12
+ const text = textOf(value);
13
+ return text.length > chars ? text.slice(0, Math.max(1, chars - 1)) + "…" : text;
14
+ }
15
+
16
+ function trimmed(value, digits) {
17
+ return value.toFixed(digits).replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1");
18
+ }
19
+
20
+ export function abbreviate(value) {
21
+ if (typeof value !== "number" || !Number.isFinite(value)) return "";
22
+ const size = Math.abs(value);
23
+ for (const [step, unit] of [[1e12, "T"], [1e9, "B"], [1e6, "M"], [1e3, "k"]]) {
24
+ if (size >= step) {
25
+ const scaled = value / step;
26
+ const digits = Math.abs(scaled) >= 100 ? 0 : Math.abs(scaled) >= 10 ? 1 : 2;
27
+ return trimmed(scaled, digits) + unit;
28
+ }
29
+ }
30
+ return Number.isInteger(value) ? String(value) : trimmed(value, 2);
31
+ }
32
+
33
+ export function shapeText(shape) {
34
+ return Array.isArray(shape) ? "[" + shape.join(", ") + "]" : "";
35
+ }
36
+
37
+ function isNumber(value) {
38
+ return typeof value === "number" && Number.isFinite(value);
39
+ }
40
+
41
+ function cornerOf(count, parameters) {
42
+ if (isNumber(count)) return abbreviate(count);
43
+ if (isNumber(parameters) && parameters > 0) return abbreviate(parameters);
44
+ return "";
45
+ }
46
+
47
+ function kindClass(kind) {
48
+ return "kind-" + (textOf(kind).toLowerCase().replace(/[^a-z0-9_-]/g, "") || "plain");
49
+ }
50
+
51
+ function present(value) {
52
+ return value !== undefined && value !== null;
53
+ }
54
+
55
+ function indexGraph(graph) {
56
+ const source = graph && typeof graph === "object" ? graph : {};
57
+ const nodes = (Array.isArray(source.nodes) ? source.nodes : []).filter(node => node && present(node.id));
58
+ const groups = new Map();
59
+ for (const group of Array.isArray(source.groups) ? source.groups : []) {
60
+ if (group && present(group.id)) groups.set(String(group.id), group);
61
+ }
62
+ const edges = (Array.isArray(source.edges) ? source.edges : []).filter(edge => edge && present(edge.from) && present(edge.to));
63
+ const chains = new Map();
64
+ const chain = (id) => {
65
+ const start = present(id) ? String(id) : null;
66
+ if (start === null || !groups.has(start)) return [];
67
+ if (chains.has(start)) return chains.get(start);
68
+ const found = [];
69
+ const seen = new Set();
70
+ let current = start;
71
+ while (current !== null && groups.has(current) && !seen.has(current)) {
72
+ seen.add(current);
73
+ found.unshift(current);
74
+ const parent = groups.get(current).parent;
75
+ current = present(parent) ? String(parent) : null;
76
+ }
77
+ chains.set(start, found);
78
+ return found;
79
+ };
80
+ return { nodes, groups, edges, chain };
81
+ }
82
+
83
+ function visibleOf(base, closed) {
84
+ const units = new Map();
85
+ const owner = new Map();
86
+ base.nodes.forEach((node, index) => {
87
+ const chain = base.chain(node.group);
88
+ const at = chain.findIndex(id => closed.has(id));
89
+ let key;
90
+ if (at >= 0) {
91
+ const gid = chain[at];
92
+ key = "group:" + gid;
93
+ if (!units.has(key)) {
94
+ units.set(key, { key, gid, collapsed: true, kind: "group", group: base.groups.get(gid), members: 0, index,
95
+ chain: chain.slice(0, at) });
96
+ }
97
+ units.get(key).members += 1;
98
+ } else {
99
+ key = "node:" + node.id;
100
+ units.set(key, { key, collapsed: false, kind: textOf(node.kind), node, index, chain });
101
+ }
102
+ owner.set(String(node.id), key);
103
+ });
104
+ const merged = new Map();
105
+ base.edges.forEach((edge) => {
106
+ const from = owner.get(String(edge.from));
107
+ const to = owner.get(String(edge.to));
108
+ if (!from || !to || from === to) return;
109
+ const key = from + ">" + to;
110
+ if (!merged.has(key)) merged.set(key, { key, from, to, count: 0, labels: new Set(), shapes: new Set() });
111
+ const item = merged.get(key);
112
+ item.count += 1;
113
+ item.labels.add(textOf(edge.label));
114
+ item.shapes.add(shapeText(edge.shape));
115
+ });
116
+ const edges = [...merged.values()].map(item => ({
117
+ key: item.key, from: item.from, to: item.to, count: item.count,
118
+ label: item.labels.size === 1 ? [...item.labels][0] : "",
119
+ shape: item.shapes.size === 1 ? [...item.shapes][0] : "",
120
+ }));
121
+ return { units, edges };
122
+ }
123
+
124
+ function unitText(unit) {
125
+ if (unit.collapsed) {
126
+ const group = unit.group || {};
127
+ const inside = unit.members === 1 ? "1 node inside" : unit.members + " nodes inside";
128
+ return { label: textOf(group.label || unit.gid), detail: [textOf(group.detail), inside].filter(Boolean).join(" · "),
129
+ shape: "", corner: cornerOf(undefined, group.parameters) };
130
+ }
131
+ const node = unit.node;
132
+ return { label: textOf(node.label || node.id), detail: textOf(node.detail), shape: shapeText(node.shape),
133
+ corner: cornerOf(node.count, node.parameters) };
134
+ }
135
+
136
+ function measure(text) {
137
+ const label = clip(text.label, SIZE.chars);
138
+ const detail = clip(text.detail, SIZE.chars + 8);
139
+ const shape = clip(text.shape, SIZE.chars + 8);
140
+ const head = label.length * CHAR.label + (text.corner ? text.corner.length * CHAR.corner + 12 : 0);
141
+ const widest = Math.max(head, detail.length * CHAR.detail, shape.length * CHAR.mono);
142
+ const rows = 1 + (detail ? 1 : 0) + (shape ? 1 : 0);
143
+ return { label, detail, shape, corner: text.corner, width: Math.max(SIZE.minWidth, Math.ceil(widest + 2 * SIZE.padX)),
144
+ height: rows * SIZE.line + 2 * SIZE.padY };
145
+ }
146
+
147
+ function boxesOf(base, units) {
148
+ const boxes = new Map([["", { key: "", gid: null, items: [], depth: 0, index: 0 }]]);
149
+ for (const unit of units.values()) {
150
+ let parent = "";
151
+ unit.chain.forEach((gid, position) => {
152
+ const key = "box:" + gid;
153
+ if (!boxes.has(key)) {
154
+ boxes.set(key, { key, gid, group: base.groups.get(gid) || {}, items: [], depth: position + 1, parent, index: unit.index });
155
+ boxes.get(parent).items.push(key);
156
+ }
157
+ const box = boxes.get(key);
158
+ box.index = Math.min(box.index, unit.index);
159
+ parent = key;
160
+ });
161
+ boxes.get(parent).items.push(unit.key);
162
+ unit.container = parent;
163
+ }
164
+ return boxes;
165
+ }
166
+
167
+ function ancestry(unit) {
168
+ return ["", ...unit.chain.map(gid => "box:" + gid)];
169
+ }
170
+
171
+ function plan(view) {
172
+ const wants = new Map();
173
+ const want = (container, from, to) => {
174
+ if (!wants.has(container)) wants.set(container, new Map());
175
+ const key = from + ">" + to;
176
+ if (!wants.get(container).has(key)) wants.get(container).set(key, { key, from, to });
177
+ return { container, from, to, key };
178
+ };
179
+ const routes = [];
180
+ for (const edge of view.edges) {
181
+ const up = ancestry(view.units.get(edge.from));
182
+ const down = ancestry(view.units.get(edge.to));
183
+ let low = 0;
184
+ while (low + 1 < up.length && low + 1 < down.length && up[low + 1] === down[low + 1]) low += 1;
185
+ const first = low + 1 < up.length ? up[low + 1] : edge.from;
186
+ const last = low + 1 < down.length ? down[low + 1] : edge.to;
187
+ const segments = [];
188
+ for (let level = up.length - 1; level > low; level -= 1) {
189
+ segments.push(want(up[level], level === up.length - 1 ? edge.from : up[level + 1], "out:" + last));
190
+ }
191
+ segments.push(want(up[low], first, last));
192
+ for (let level = low + 1; level < down.length; level += 1) {
193
+ segments.push(want(down[level], "in:" + first, level === down.length - 1 ? edge.to : down[level + 1]));
194
+ }
195
+ routes.push({ edge, segments });
196
+ }
197
+ return { wants, routes };
198
+ }
199
+
200
+ function isPort(key) {
201
+ return key.startsWith("in:") || key.startsWith("out:");
202
+ }
203
+
204
+ function layered(keys, links, indexOf) {
205
+ const succs = new Map(keys.map(key => [key, []]));
206
+ for (const link of links) if (succs.has(link.from) && succs.has(link.to)) succs.get(link.from).push(link.to);
207
+ const order = [...keys].sort((a, b) => indexOf(a) - indexOf(b));
208
+ const state = new Map();
209
+ const finished = [];
210
+ const back = new Set();
211
+ for (const start of order) {
212
+ if (state.has(start)) continue;
213
+ const stack = [[start, 0]];
214
+ state.set(start, 1);
215
+ while (stack.length) {
216
+ const top = stack[stack.length - 1];
217
+ const next = succs.get(top[0])[top[1]];
218
+ if (next === undefined) {
219
+ state.set(top[0], 2);
220
+ finished.push(top[0]);
221
+ stack.pop();
222
+ continue;
223
+ }
224
+ top[1] += 1;
225
+ if (!state.has(next)) {
226
+ state.set(next, 1);
227
+ stack.push([next, 0]);
228
+ } else if (state.get(next) === 1) {
229
+ back.add(top[0] + ">" + next);
230
+ }
231
+ }
232
+ }
233
+ const topo = finished.reverse();
234
+ const preds = new Map(keys.map(key => [key, []]));
235
+ const forward = new Map(keys.map(key => [key, []]));
236
+ for (const link of links) {
237
+ if (!succs.has(link.from) || !succs.has(link.to) || back.has(link.from + ">" + link.to)) continue;
238
+ preds.get(link.to).push(link.from);
239
+ forward.get(link.from).push(link.to);
240
+ }
241
+ const layer = new Map();
242
+ for (const key of topo) layer.set(key, Math.max(0, ...preds.get(key).map(other => layer.get(other) + 1)));
243
+ for (const key of [...topo].reverse()) {
244
+ if (preds.get(key).length || !forward.get(key).length) continue;
245
+ layer.set(key, Math.max(layer.get(key), Math.min(...forward.get(key).map(other => layer.get(other))) - 1));
246
+ }
247
+ return { layer, back };
248
+ }
249
+
250
+ function crossings(layers, succs) {
251
+ let count = 0;
252
+ for (let index = 0; index + 1 < layers.length; index += 1) {
253
+ const below = new Map(layers[index + 1].map((key, position) => [key, position]));
254
+ const pairs = [];
255
+ layers[index].forEach((key, position) => {
256
+ for (const other of succs.get(key) || []) if (below.has(other)) pairs.push([position, below.get(other)]);
257
+ });
258
+ for (let first = 0; first < pairs.length; first += 1) {
259
+ for (let second = first + 1; second < pairs.length; second += 1) {
260
+ const a = pairs[first];
261
+ const b = pairs[second];
262
+ if ((a[0] - b[0]) * (a[1] - b[1]) < 0) count += 1;
263
+ }
264
+ }
265
+ }
266
+ return count;
267
+ }
268
+
269
+ function reorder(layers, preds, succs) {
270
+ let current = layers.map(layer => layer.slice());
271
+ let best = current.map(layer => layer.slice());
272
+ let fewest = crossings(best, succs);
273
+ for (let sweep = 0; sweep < SWEEPS && fewest > 0; sweep += 1) {
274
+ const down = sweep % 2 === 0;
275
+ const indices = [...current.keys()];
276
+ const visit = down ? indices.slice(1) : indices.reverse().slice(1);
277
+ for (const index of visit) {
278
+ const reference = current[down ? index - 1 : index + 1];
279
+ const position = new Map(reference.map((key, place) => [key, place]));
280
+ const near = down ? preds : succs;
281
+ const scored = current[index].map((key, place) => {
282
+ const linked = (near.get(key) || []).filter(other => position.has(other));
283
+ const score = linked.length ? linked.reduce((sum, other) => sum + position.get(other), 0) / linked.length : place;
284
+ return { key, score, place };
285
+ });
286
+ scored.sort((a, b) => a.score - b.score || a.place - b.place);
287
+ current[index] = scored.map(item => item.key);
288
+ }
289
+ const count = crossings(current, succs);
290
+ if (count < fewest) {
291
+ fewest = count;
292
+ best = current.map(layer => layer.slice());
293
+ }
294
+ }
295
+ return best;
296
+ }
297
+
298
+ function centers(layers, extent, gap, preds, succs) {
299
+ const center = new Map();
300
+ for (const layer of layers) {
301
+ let cursor = 0;
302
+ layer.forEach((key, place) => {
303
+ if (place) cursor += gap(layer[place - 1], key);
304
+ center.set(key, cursor + extent(key) / 2);
305
+ cursor += extent(key);
306
+ });
307
+ for (const key of layer) center.set(key, center.get(key) - cursor / 2);
308
+ }
309
+ for (let pass = 0; pass < PASSES; pass += 1) {
310
+ const down = pass % 2 === 0;
311
+ const near = down ? preds : succs;
312
+ for (const layer of down ? layers : [...layers].reverse()) {
313
+ const wanted = layer.map((key) => {
314
+ const linked = (near.get(key) || []).filter(other => center.has(other));
315
+ return linked.length ? linked.reduce((sum, other) => sum + center.get(other), 0) / linked.length : center.get(key);
316
+ });
317
+ const placed = wanted.slice();
318
+ for (let place = 1; place < layer.length; place += 1) {
319
+ const least = placed[place - 1] + extent(layer[place - 1]) / 2 + gap(layer[place - 1], layer[place]) + extent(layer[place]) / 2;
320
+ if (placed[place] < least) placed[place] = least;
321
+ }
322
+ const shift = layer.length ? wanted.reduce((sum, value, place) => sum + value - placed[place], 0) / layer.length : 0;
323
+ layer.forEach((key, place) => center.set(key, placed[place] + shift));
324
+ }
325
+ }
326
+ return center;
327
+ }
328
+
329
+ function layoutBox(key, scene) {
330
+ if (scene.layouts.has(key)) return scene.layouts.get(key);
331
+ const box = scene.boxes.get(key);
332
+ const lr = scene.lr;
333
+ const root = key === "";
334
+ const pad = root ? 0 : SPACE.pad;
335
+ const head = root ? 0 : SIZE.head;
336
+ const requested = [...(scene.plan.wants.get(key) || new Map()).values()];
337
+ const items = new Map();
338
+ for (const item of box.items) {
339
+ const size = item.startsWith("box:") ? layoutBox(item, scene) : scene.sizes.get(item);
340
+ const index = item.startsWith("box:") ? scene.boxes.get(item).index : scene.units.get(item).index;
341
+ items.set(item, { key: item, w: size.width, h: size.height, index, kind: "item" });
342
+ }
343
+ for (const link of requested) {
344
+ for (const [end, other] of [[link.from, link.to], [link.to, link.from]]) {
345
+ if (!isPort(end)) continue;
346
+ const index = items.has(other) ? items.get(other).index : 0;
347
+ if (!items.has(end)) items.set(end, { key: end, w: 0, h: 0, index, kind: end.startsWith("in:") ? "in" : "out" });
348
+ else items.get(end).index = Math.min(items.get(end).index, index);
349
+ }
350
+ }
351
+ const real = [...items.values()].filter(item => item.kind === "item").map(item => item.key);
352
+ const indexOf = name => items.get(name).index;
353
+ const inner = requested.filter(link => items.has(link.from) && items.has(link.to) && !isPort(link.from) && !isPort(link.to));
354
+ const { layer, back } = layered(real, inner, indexOf);
355
+ const hasIn = requested.some(link => link.from.startsWith("in:"));
356
+ const hasOut = requested.some(link => link.to.startsWith("out:"));
357
+ if (hasIn) for (const name of real) layer.set(name, layer.get(name) + 1);
358
+ const deepest = Math.max(hasIn ? 0 : -1, ...real.map(name => layer.get(name)));
359
+ for (const item of items.values()) {
360
+ if (item.kind === "in") layer.set(item.key, 0);
361
+ if (item.kind === "out") layer.set(item.key, deepest + 1);
362
+ }
363
+ const count = Math.max(0, ...[...layer.values()].map(value => value + 1));
364
+ const layers = Array.from({ length: count }, () => []);
365
+ for (const item of [...items.values()].sort((a, b) => a.index - b.index)) layers[layer.get(item.key)].push(item.key);
366
+ const chains = new Map();
367
+ const preds = new Map();
368
+ const succs = new Map();
369
+ const join = (from, to) => {
370
+ if (!succs.has(from)) succs.set(from, []);
371
+ if (!preds.has(to)) preds.set(to, []);
372
+ succs.get(from).push(to);
373
+ preds.get(to).push(from);
374
+ };
375
+ for (const link of requested) {
376
+ if (!items.has(link.from) || !items.has(link.to)) continue;
377
+ const start = layer.get(link.from);
378
+ const end = layer.get(link.to);
379
+ const middle = [];
380
+ if (!back.has(link.from + ">" + link.to) && end > start) {
381
+ let previous = link.from;
382
+ for (let step = start + 1; step < end; step += 1) {
383
+ const dummy = link.key + "#" + step;
384
+ items.set(dummy, { key: dummy, w: 0, h: 2, index: items.get(link.from).index + 0.5, kind: "dummy" });
385
+ layer.set(dummy, step);
386
+ layers[step].push(dummy);
387
+ join(previous, dummy);
388
+ middle.push(dummy);
389
+ previous = dummy;
390
+ }
391
+ join(previous, link.to);
392
+ }
393
+ chains.set(link.key, middle);
394
+ }
395
+ const ordered = reorder(layers, preds, succs);
396
+ const extent = name => (lr ? items.get(name).h : items.get(name).w);
397
+ const length = name => (lr ? items.get(name).w : items.get(name).h);
398
+ const loose = name => items.get(name).kind !== "item";
399
+ const gap = (a, b) => (loose(a) || loose(b) ? SPACE.dummy : SPACE.order);
400
+ const center = centers(ordered, extent, gap, preds, succs);
401
+ const rankStart = pad + (lr ? 0 : head);
402
+ const orderStart = pad + (lr ? head : 0);
403
+ const starts = [];
404
+ const sizes = [];
405
+ let cursor = rankStart + (hasIn ? SPACE.port : 0);
406
+ let any = false;
407
+ ordered.forEach((names, index) => {
408
+ const ports = names.length && names.every(name => items.get(name).kind === "in" || items.get(name).kind === "out");
409
+ if (ports) {
410
+ starts[index] = 0;
411
+ sizes[index] = 0;
412
+ return;
413
+ }
414
+ starts[index] = cursor;
415
+ sizes[index] = Math.max(0, ...names.map(length));
416
+ cursor += sizes[index] + SPACE.rank;
417
+ any = true;
418
+ });
419
+ const contentEnd = any ? cursor - SPACE.rank : cursor;
420
+ let low = Infinity;
421
+ let high = -Infinity;
422
+ for (const [name, value] of center) {
423
+ low = Math.min(low, value - extent(name) / 2);
424
+ high = Math.max(high, value + extent(name) / 2);
425
+ }
426
+ if (!Number.isFinite(low)) {
427
+ low = 0;
428
+ high = 0;
429
+ }
430
+ const group = box.group || {};
431
+ const title = root ? 0 : textOf(group.label).length * CHAR.head + textOf(group.detail).length * CHAR.detail + 24 +
432
+ (cornerOf(undefined, group.parameters).length * CHAR.corner);
433
+ let rankSize = contentEnd + (hasOut ? SPACE.port : 0) + pad;
434
+ let orderSize = orderStart + (high - low) + pad;
435
+ const offset = orderStart - low;
436
+ if (lr) rankSize = Math.max(rankSize, title);
437
+ else orderSize = Math.max(orderSize, title);
438
+ const rects = new Map();
439
+ for (const [name, item] of items) {
440
+ if (item.kind === "dummy") continue;
441
+ const index = layer.get(name);
442
+ const middle = center.get(name) + offset;
443
+ let along;
444
+ if (item.kind === "in") along = 0;
445
+ else if (item.kind === "out") along = rankSize;
446
+ else along = starts[index] + (sizes[index] - length(name)) / 2;
447
+ rects.set(name, lr ? { x: along, y: middle - item.h / 2, w: item.w, h: item.h, kind: item.kind }
448
+ : { x: middle - item.w / 2, y: along, w: item.w, h: item.h, kind: item.kind });
449
+ }
450
+ const point = (along, across) => (lr ? { x: along, y: across } : { x: across, y: along });
451
+ const exit = (name) => {
452
+ const rect = rects.get(name);
453
+ if (rect.kind !== "item") return { x: rect.x, y: rect.y };
454
+ return lr ? { x: rect.x + rect.w, y: rect.y + rect.h / 2 } : { x: rect.x + rect.w / 2, y: rect.y + rect.h };
455
+ };
456
+ const entry = (name) => {
457
+ const rect = rects.get(name);
458
+ if (rect.kind !== "item") return { x: rect.x, y: rect.y };
459
+ return lr ? { x: rect.x, y: rect.y + rect.h / 2 } : { x: rect.x + rect.w / 2, y: rect.y };
460
+ };
461
+ const routes = new Map();
462
+ for (const link of requested) {
463
+ if (!rects.has(link.from) || !rects.has(link.to)) continue;
464
+ const points = [exit(link.from)];
465
+ for (const dummy of chains.get(link.key) || []) {
466
+ const index = layer.get(dummy);
467
+ const across = center.get(dummy) + offset;
468
+ points.push(point(starts[index], across), point(starts[index] + sizes[index], across));
469
+ }
470
+ points.push(entry(link.to));
471
+ routes.set(link.key, points);
472
+ }
473
+ const width = lr ? rankSize : orderSize;
474
+ const height = lr ? orderSize : rankSize;
475
+ const result = { width, height, rects, routes };
476
+ scene.layouts.set(key, result);
477
+ return result;
478
+ }
479
+
480
+ function placeBox(key, ox, oy, scene, out) {
481
+ const layout = scene.layouts.get(key);
482
+ scene.offsets.set(key, { x: ox, y: oy });
483
+ for (const [name, rect] of layout.rects) {
484
+ if (rect.kind !== "item") continue;
485
+ const x = ox + rect.x;
486
+ const y = oy + rect.y;
487
+ if (name.startsWith("box:")) {
488
+ const box = scene.boxes.get(name);
489
+ const group = box.group || {};
490
+ const corner = cornerOf(undefined, group.parameters);
491
+ const room = Math.max(4, Math.floor((rect.w - 24 - corner.length * CHAR.corner) / CHAR.head));
492
+ const label = clip(group.label || box.gid, room);
493
+ const detail = clip(group.detail, Math.max(0, room - label.length - 1));
494
+ out.boxes.push({ key: name, gid: box.gid, x, y, w: rect.w, h: rect.h, depth: box.depth, label, detail, corner,
495
+ title: [textOf(group.label || box.gid), textOf(group.detail),
496
+ isNumber(group.parameters) ? group.parameters.toLocaleString() + " parameters" : "",
497
+ "click the header to fold this group"].filter(Boolean).join("\n") });
498
+ placeBox(name, x, y, scene, out);
499
+ } else {
500
+ const unit = scene.units.get(name);
501
+ const shown = scene.texts.get(name);
502
+ out.nodes.push({ key: name, x, y, w: rect.w, h: rect.h, unit, kind: unit.kind, ...shown, title: titleOf(unit) });
503
+ }
504
+ }
505
+ }
506
+
507
+ function titleOf(unit) {
508
+ if (unit.collapsed) {
509
+ const group = unit.group || {};
510
+ return [textOf(group.label || unit.gid), textOf(group.detail), unit.members + " nodes inside",
511
+ isNumber(group.parameters) ? group.parameters.toLocaleString() + " parameters" : "",
512
+ "click to unfold"].filter(Boolean).join("\n");
513
+ }
514
+ const node = unit.node;
515
+ return [textOf(node.label || node.id), textOf(node.detail), shapeText(node.shape),
516
+ isNumber(node.count) ? "count " + node.count.toLocaleString() : "",
517
+ isNumber(node.parameters) ? node.parameters.toLocaleString() + " parameters" : ""].filter(Boolean).join("\n");
518
+ }
519
+
520
+ function shifted(points, offset) {
521
+ return points.map(item => ({ x: item.x + offset.x, y: item.y + offset.y }));
522
+ }
523
+
524
+ function joined(segments, scene) {
525
+ let points = [];
526
+ for (let index = 0; index < segments.length; index += 1) {
527
+ const segment = segments[index];
528
+ const route = scene.layouts.get(segment.container).routes.get(segment.key);
529
+ if (!route) return null;
530
+ let part = shifted(route, scene.offsets.get(segment.container));
531
+ if (index > 0 && segments[index - 1].to.startsWith("out:")) part = part.slice(1);
532
+ if (segment.from.startsWith("in:")) points = points.slice(0, -1);
533
+ points = points.concat(part);
534
+ }
535
+ return points.filter((item, index) => !index || Math.abs(item.x - points[index - 1].x) + Math.abs(item.y - points[index - 1].y) > 0.5);
536
+ }
537
+
538
+ function curve(points, lr) {
539
+ if (!points.length) return "";
540
+ let path = `M${points[0].x.toFixed(1)} ${points[0].y.toFixed(1)}`;
541
+ for (let index = 1; index < points.length; index += 1) {
542
+ const a = points[index - 1];
543
+ const b = points[index];
544
+ if (lr) {
545
+ const bend = Math.max(Math.abs(b.x - a.x) / 2, 12);
546
+ path += ` C${(a.x + bend).toFixed(1)} ${a.y.toFixed(1)} ${(b.x - bend).toFixed(1)} ${b.y.toFixed(1)} ${b.x.toFixed(1)} ${b.y.toFixed(1)}`;
547
+ } else {
548
+ const bend = Math.max(Math.abs(b.y - a.y) / 2, 12);
549
+ path += ` C${a.x.toFixed(1)} ${(a.y + bend).toFixed(1)} ${b.x.toFixed(1)} ${(b.y - bend).toFixed(1)} ${b.x.toFixed(1)} ${b.y.toFixed(1)}`;
550
+ }
551
+ }
552
+ return path;
553
+ }
554
+
555
+ export function layoutGraph(graph, closed, direction) {
556
+ const lr = direction !== "TB";
557
+ const base = indexGraph(graph);
558
+ const view = visibleOf(base, closed || new Set());
559
+ const boxes = boxesOf(base, view.units);
560
+ const texts = new Map();
561
+ const sizes = new Map();
562
+ for (const unit of view.units.values()) {
563
+ const shown = measure(unitText(unit));
564
+ texts.set(unit.key, shown);
565
+ sizes.set(unit.key, shown);
566
+ }
567
+ const scene = { lr, boxes, units: view.units, texts, sizes, plan: plan(view), layouts: new Map(), offsets: new Map() };
568
+ layoutBox("", scene);
569
+ const out = { nodes: [], boxes: [], edges: [] };
570
+ placeBox("", 0, 0, scene, out);
571
+ for (const route of scene.plan.routes) {
572
+ const points = joined(route.segments, scene);
573
+ if (!points || points.length < 2) continue;
574
+ const edge = route.edge;
575
+ const middle = Math.max(0, Math.floor((points.length - 2) / 2));
576
+ const a = points[middle];
577
+ const b = points[middle + 1];
578
+ const from = scene.texts.get(edge.from);
579
+ const to = scene.texts.get(edge.to);
580
+ out.edges.push({ key: edge.key, from: edge.from, to: edge.to, d: curve(points, lr), points, label: clip(edge.label, 24),
581
+ lx: (a.x + b.x) / 2, ly: (a.y + b.y) / 2 - 4,
582
+ title: [`${from.label} → ${to.label}`, edge.label, edge.shape,
583
+ edge.count > 1 ? edge.count + " edges" : ""].filter(Boolean).join("\n") });
584
+ }
585
+ let left = Infinity;
586
+ let top = Infinity;
587
+ let right = -Infinity;
588
+ let bottom = -Infinity;
589
+ const grow = (x, y) => {
590
+ left = Math.min(left, x);
591
+ top = Math.min(top, y);
592
+ right = Math.max(right, x);
593
+ bottom = Math.max(bottom, y);
594
+ };
595
+ for (const item of [...out.nodes, ...out.boxes]) {
596
+ grow(item.x, item.y);
597
+ grow(item.x + item.w, item.y + item.h);
598
+ }
599
+ for (const edge of out.edges) for (const item of edge.points) grow(item.x, item.y);
600
+ const bounds = Number.isFinite(left) ? { x: left, y: top, width: right - left, height: bottom - top } : { x: 0, y: 0, width: 0, height: 0 };
601
+ out.boxes.sort((a, b) => a.depth - b.depth);
602
+ return { ...out, bounds, groups: [...base.groups.keys()] };
603
+ }
604
+
605
+ const STYLED = ["fill", "fill-opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-opacity", "opacity",
606
+ "font-family", "font-size", "font-weight", "paint-order", "stroke-linejoin"];
607
+
608
+ function fileName(title) {
609
+ return (textOf(title).trim().replace(/[^\w.]+/g, "_").replace(/^_+|_+$/g, "") || "diagram") + ".svg";
610
+ }
611
+
612
+ export const Diagram = {
613
+ name: "KDiagram",
614
+ props: {
615
+ graph: { type: Object, default: null },
616
+ direction: { type: String, default: "LR" },
617
+ height: { type: Number, default: 460 },
618
+ collapsed: { type: Array, default: () => [] },
619
+ title: { type: String, default: "" },
620
+ },
621
+ emits: ["select"],
622
+ data() {
623
+ return {
624
+ dir: this.direction === "TB" ? "TB" : "LR",
625
+ closedIds: (this.collapsed || []).map(String),
626
+ zoom: 1,
627
+ pan: { x: 0, y: 0 },
628
+ drag: null,
629
+ dragged: false,
630
+ selected: null,
631
+ marker: "kdiagram-arrow-" + Math.random().toString(36).slice(2, 9),
632
+ };
633
+ },
634
+ computed: {
635
+ closedSet() { return new Set(this.closedIds); },
636
+ layout() { return layoutGraph(this.graph, this.closedSet, this.dir); },
637
+ signature() {
638
+ const graph = this.graph && typeof this.graph === "object" ? this.graph : {};
639
+ const ids = list => (Array.isArray(list) ? list.map(item => (item ? textOf(item.id) : "")).join(",") : "");
640
+ return ids(graph.nodes) + "|" + ids(graph.groups) + "|" + (Array.isArray(graph.edges) ? graph.edges.length : 0);
641
+ },
642
+ collapsedKey() { return (this.collapsed || []).map(String).join(","); },
643
+ transform() { return `translate(${this.pan.x} ${this.pan.y}) scale(${this.zoom})`; },
644
+ empty() { return !this.layout.nodes.length; },
645
+ groupCount() { return this.layout.groups.length; },
646
+ labeled() { return this.layout.edges.filter(edge => edge.label); },
647
+ },
648
+ watch: {
649
+ signature() {
650
+ this.closedIds = (this.collapsed || []).map(String);
651
+ this.selected = null;
652
+ this.later();
653
+ },
654
+ collapsedKey() {
655
+ this.closedIds = (this.collapsed || []).map(String);
656
+ this.later();
657
+ },
658
+ direction(value) {
659
+ this.dir = value === "TB" ? "TB" : "LR";
660
+ this.later();
661
+ },
662
+ },
663
+ mounted() {
664
+ this.later();
665
+ },
666
+ beforeUnmount() {
667
+ this.stopListening();
668
+ },
669
+ methods: {
670
+ later() {
671
+ this.$nextTick(() => this.fit());
672
+ },
673
+ fit() {
674
+ const svg = this.$refs.svg;
675
+ const bounds = this.layout.bounds;
676
+ if (!svg || !bounds.width || !bounds.height) {
677
+ this.zoom = 1;
678
+ this.pan = { x: 0, y: 0 };
679
+ return;
680
+ }
681
+ const width = svg.clientWidth || svg.getBoundingClientRect().width || 800;
682
+ const height = this.height;
683
+ const zoom = Math.max(0.05, Math.min(1.25, (width - 32) / bounds.width, (height - 32) / bounds.height));
684
+ this.zoom = zoom;
685
+ this.pan = { x: (width - bounds.width * zoom) / 2 - bounds.x * zoom, y: (height - bounds.height * zoom) / 2 - bounds.y * zoom };
686
+ },
687
+ turn(value) {
688
+ if (this.dir === value) return;
689
+ this.dir = value;
690
+ this.later();
691
+ },
692
+ toggle(gid) {
693
+ if (this.dragged) return;
694
+ const id = String(gid);
695
+ this.closedIds = this.closedSet.has(id) ? this.closedIds.filter(item => item !== id) : [...this.closedIds, id];
696
+ this.later();
697
+ },
698
+ openAll() {
699
+ this.closedIds = [];
700
+ this.later();
701
+ },
702
+ closeAll() {
703
+ this.closedIds = [...this.layout.groups];
704
+ this.later();
705
+ },
706
+ pick(node) {
707
+ if (this.dragged) return;
708
+ if (node.unit.collapsed) {
709
+ this.toggle(node.unit.gid);
710
+ return;
711
+ }
712
+ this.selected = node.key;
713
+ this.$emit("select", node.unit.node);
714
+ },
715
+ clear() {
716
+ if (this.dragged || this.selected === null) return;
717
+ this.selected = null;
718
+ this.$emit("select", null);
719
+ },
720
+ lit(edge) {
721
+ return this.selected !== null && (edge.from === this.selected || edge.to === this.selected);
722
+ },
723
+ nodeClass(node) {
724
+ return [kindClass(node.kind), { selected: node.key === this.selected, collapsed: node.unit.collapsed }];
725
+ },
726
+ wheel(event) {
727
+ const svg = this.$refs.svg;
728
+ if (!svg) return;
729
+ const rect = svg.getBoundingClientRect();
730
+ const cx = event.clientX - rect.left;
731
+ const cy = event.clientY - rect.top;
732
+ const zoom = Math.min(4, Math.max(0.05, this.zoom * Math.exp(-event.deltaY * 0.0015)));
733
+ const ratio = zoom / this.zoom;
734
+ this.pan = { x: cx - (cx - this.pan.x) * ratio, y: cy - (cy - this.pan.y) * ratio };
735
+ this.zoom = zoom;
736
+ },
737
+ press(event) {
738
+ if (event.button !== 0) return;
739
+ this.dragged = false;
740
+ this.drag = { x: event.clientX, y: event.clientY, pan: { ...this.pan }, moved: false };
741
+ window.addEventListener("mousemove", this.move);
742
+ window.addEventListener("mouseup", this.release);
743
+ },
744
+ move(event) {
745
+ const drag = this.drag;
746
+ if (!drag) return;
747
+ const dx = event.clientX - drag.x;
748
+ const dy = event.clientY - drag.y;
749
+ if (!drag.moved && Math.abs(dx) + Math.abs(dy) < 4) return;
750
+ drag.moved = true;
751
+ this.pan = { x: drag.pan.x + dx, y: drag.pan.y + dy };
752
+ },
753
+ release() {
754
+ this.stopListening();
755
+ this.dragged = Boolean(this.drag && this.drag.moved);
756
+ this.drag = null;
757
+ },
758
+ stopListening() {
759
+ window.removeEventListener("mousemove", this.move);
760
+ window.removeEventListener("mouseup", this.release);
761
+ },
762
+ download() {
763
+ const svg = this.$refs.svg;
764
+ const bounds = this.layout.bounds;
765
+ if (!svg || !bounds.width) return;
766
+ const margin = 16;
767
+ const copy = svg.cloneNode(true);
768
+ const originals = svg.querySelectorAll("*");
769
+ const copies = copy.querySelectorAll("*");
770
+ originals.forEach((element, index) => {
771
+ const target = copies[index];
772
+ if (!target) return;
773
+ const computed = window.getComputedStyle(element);
774
+ const style = STYLED.map(name => `${name}: ${computed.getPropertyValue(name)}`).join("; ");
775
+ target.setAttribute("style", style);
776
+ });
777
+ const width = Math.ceil(bounds.width + 2 * margin);
778
+ const height = Math.ceil(bounds.height + 2 * margin);
779
+ copy.setAttribute("xmlns", "http://www.w3.org/2000/svg");
780
+ copy.setAttribute("viewBox", `${bounds.x - margin} ${bounds.y - margin} ${width} ${height}`);
781
+ copy.setAttribute("width", String(width));
782
+ copy.setAttribute("height", String(height));
783
+ copy.removeAttribute("class");
784
+ const canvas = copy.querySelector("g.canvas");
785
+ if (canvas) canvas.removeAttribute("transform");
786
+ const back = document.createElementNS("http://www.w3.org/2000/svg", "rect");
787
+ back.setAttribute("x", String(bounds.x - margin));
788
+ back.setAttribute("y", String(bounds.y - margin));
789
+ back.setAttribute("width", String(width));
790
+ back.setAttribute("height", String(height));
791
+ back.setAttribute("fill", window.getComputedStyle(this.$el).getPropertyValue("--surface").trim() || "#ffffff");
792
+ copy.insertBefore(back, copy.firstChild);
793
+ const text = new XMLSerializer().serializeToString(copy);
794
+ const url = URL.createObjectURL(new Blob([text], { type: "image/svg+xml" }));
795
+ const link = document.createElement("a");
796
+ link.href = url;
797
+ link.download = fileName(this.title);
798
+ document.body.appendChild(link);
799
+ link.click();
800
+ link.remove();
801
+ window.setTimeout(() => URL.revokeObjectURL(url), 1000);
802
+ },
803
+ },
804
+ template: `
805
+ <div class="kdiagram">
806
+ <div class="kdiagram-tools">
807
+ <span class="muted small kdiagram-hint" v-if="!empty">drag to pan, wheel to zoom{{ groupCount ? ", click a group header to fold it" : "" }}</span>
808
+ <span class="spacer"></span>
809
+ <button type="button" class="tool" @click="fit" title="zoom and pan so the whole diagram is in view">fit</button>
810
+ <button type="button" class="tool" :class="{ on: dir === 'LR' }" @click="turn('LR')" title="lay the diagram out from left to right">LR</button>
811
+ <button type="button" class="tool" :class="{ on: dir === 'TB' }" @click="turn('TB')" title="lay the diagram out from top to bottom">TB</button>
812
+ <button type="button" class="tool" :disabled="!groupCount" @click="openAll" title="unfold every group">expand all</button>
813
+ <button type="button" class="tool" :disabled="!groupCount" @click="closeAll" title="fold every group">collapse all</button>
814
+ <button type="button" class="tool" :disabled="empty" @click="download" title="download the diagram as an SVG file">svg</button>
815
+ </div>
816
+ <div v-if="empty" class="empty">nothing to draw</div>
817
+ <svg v-else ref="svg" class="kdiagram-canvas" :class="{ panning: drag && drag.moved }" width="100%" :height="height"
818
+ @wheel.prevent="wheel" @mousedown="press" @click="clear">
819
+ <defs>
820
+ <marker :id="marker" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
821
+ <path d="M 0 0 L 10 5 L 0 10 z" class="kdiagram-arrow"></path>
822
+ </marker>
823
+ <marker :id="marker + '-on'" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
824
+ <path d="M 0 0 L 10 5 L 0 10 z" class="kdiagram-arrow on"></path>
825
+ </marker>
826
+ </defs>
827
+ <g class="canvas" :transform="transform">
828
+ <g v-for="box in layout.boxes" :key="box.key" class="kdiagram-group" :class="'depth-' + Math.min(box.depth, 4)">
829
+ <rect class="group-box" :x="box.x" :y="box.y" :width="box.w" :height="box.h" rx="8"></rect>
830
+ <g class="group-head" @click.stop="toggle(box.gid)">
831
+ <rect class="group-hit" :x="box.x" :y="box.y" :width="box.w" height="22" rx="8"></rect>
832
+ <text class="group-label" :x="box.x + 10" :y="box.y + 15">{{ box.label }}<tspan v-if="box.detail" class="group-detail" dx="6">{{ box.detail }}</tspan></text>
833
+ <text v-if="box.corner" class="group-corner" :x="box.x + box.w - 10" :y="box.y + 15" text-anchor="end">{{ box.corner }}</text>
834
+ <title>{{ box.title }}</title>
835
+ </g>
836
+ </g>
837
+ <g v-for="edge in layout.edges" :key="edge.key" class="kdiagram-edge" :class="{ on: lit(edge) }">
838
+ <path class="edge-line" :d="edge.d" :marker-end="'url(#' + marker + (lit(edge) ? '-on' : '') + ')'"></path>
839
+ <path class="edge-hit" :d="edge.d"></path>
840
+ <title>{{ edge.title }}</title>
841
+ </g>
842
+ <text v-for="edge in labeled" :key="'label:' + edge.key" class="edge-label"
843
+ :x="edge.lx" :y="edge.ly" text-anchor="middle">{{ edge.label }}</text>
844
+ <g v-for="node in layout.nodes" :key="node.key" class="kdiagram-node" :class="nodeClass(node)" @click.stop="pick(node)">
845
+ <rect class="node-box" :x="node.x" :y="node.y" :width="node.w" :height="node.h" rx="6"></rect>
846
+ <text class="node-label" :x="node.x + 10" :y="node.y + 18">{{ node.label }}</text>
847
+ <text v-if="node.corner" class="node-corner" :x="node.x + node.w - 8" :y="node.y + 17" text-anchor="end">{{ node.corner }}</text>
848
+ <text v-if="node.detail" class="node-detail" :x="node.x + 10" :y="node.y + 32">{{ node.detail }}</text>
849
+ <text v-if="node.shape" class="node-shape" :x="node.x + 10" :y="node.y + (node.detail ? 46 : 32)">{{ node.shape }}</text>
850
+ <title>{{ node.title }}</title>
851
+ </g>
852
+ </g>
853
+ </svg>
854
+ </div>`,
855
+ };
856
+
857
+ export default Diagram;