@davidsouther/agathon 0.0.0-stage → 2026.11.1

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 (62) hide show
  1. package/package.json +19 -4
  2. package/src/assert.ts +67 -0
  3. package/src/geometry/box.ts +76 -0
  4. package/src/geometry/circle.ts +296 -0
  5. package/src/geometry/geometry.ts +318 -0
  6. package/src/geometry/path.ts +315 -0
  7. package/src/geometry/quadtree.ts +321 -0
  8. package/src/geometry/rectangle.ts +129 -0
  9. package/src/geometry/rngon.ts +132 -0
  10. package/src/geometry/types.ts +102 -0
  11. package/src/geometry.ts +61 -0
  12. package/src/highlight/highlight.ts +85 -0
  13. package/src/index.ts +5 -0
  14. package/src/layout/grid/manager.ts +55 -0
  15. package/src/layout/grid.ts +1 -0
  16. package/src/layout/hierarchic/README.md +21 -0
  17. package/src/layout/hierarchic/acyclic.ts +249 -0
  18. package/src/layout/hierarchic/children.ts +169 -0
  19. package/src/layout/hierarchic/debug.ts +186 -0
  20. package/src/layout/hierarchic/layout.ts +266 -0
  21. package/src/layout/hierarchic/manager.ts +65 -0
  22. package/src/layout/hierarchic/normalize.ts +177 -0
  23. package/src/layout/hierarchic/order.ts +321 -0
  24. package/src/layout/hierarchic/position.ts +514 -0
  25. package/src/layout/hierarchic/rank.ts +629 -0
  26. package/src/layout/hierarchic.ts +2 -0
  27. package/src/layout/hulls/arrange.ts +94 -0
  28. package/src/layout/hulls/hulls.ts +243 -0
  29. package/src/layout/hulls/segments.ts +174 -0
  30. package/src/layout/hulls.ts +3 -0
  31. package/src/layout/manager/child_manager.ts +42 -0
  32. package/src/layout/manager/drag_manager.ts +112 -0
  33. package/src/layout/manager/manager.ts +425 -0
  34. package/src/layout/manager/pan_manager.ts +146 -0
  35. package/src/layout/manager/visibility_manager.ts +84 -0
  36. package/src/layout/manager/zoom_manager.ts +263 -0
  37. package/src/layout/manager.ts +6 -0
  38. package/src/layout/properties/defaults.ts +99 -0
  39. package/src/layout/properties/symbol.ts +99 -0
  40. package/src/layout/properties/weak_map.ts +73 -0
  41. package/src/layout/properties.ts +111 -0
  42. package/src/layout/types.ts +470 -0
  43. package/src/layout/util.ts +444 -0
  44. package/src/layout.ts +6 -0
  45. package/src/samples/diff_graph.ts +15 -0
  46. package/src/samples/edge_cases.ts +168 -0
  47. package/src/samples/gansner_with_states.ts +67 -0
  48. package/src/samples/load_generator.ts +213 -0
  49. package/src/samples/search_graph.ts +35 -0
  50. package/src/tsabre/algo.ts +126 -0
  51. package/src/tsabre/builder.ts +97 -0
  52. package/src/tsabre/compress.ts +15 -0
  53. package/src/tsabre/dijkstra/dijkstra.ts +76 -0
  54. package/src/tsabre/dijkstra/heap.ts +83 -0
  55. package/src/tsabre/graph.ts +808 -0
  56. package/src/tsabre/parse.ts +634 -0
  57. package/src/tsabre/print.ts +188 -0
  58. package/src/tsabre/sourcebuilder/code_content_buffer.ts +275 -0
  59. package/src/tsabre/sourcebuilder/printable.ts +33 -0
  60. package/src/tsabre.ts +6 -0
  61. package/src/types.ts +424 -0
  62. package/README.md +0 -3
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @fileoverview A small utility to create DOT-compatible string reprentations
3
+ * of graphs.
4
+ */
5
+
6
+ import type { Edge, FullGraph, NodeId } from "../types.ts";
7
+
8
+ import { CodeContentBuffer, DelimitationStrategy } from "./sourcebuilder/code_content_buffer.ts";
9
+ import { Printable, PrintableString } from "./sourcebuilder/printable.ts";
10
+
11
+ /**
12
+ * DigraphAttributeWriters allow clients to provide functions to return a map
13
+ * of attribute key:value pairs to include with each item.
14
+ */
15
+ export interface DigraphAttributeWriters {
16
+ node: (id: NodeId) => Map<string, string>;
17
+ edge: (edge: Edge) => Map<string, string>;
18
+ graph: () => Map<string, string>;
19
+ }
20
+
21
+ function fillMissingWriters(
22
+ writers: Partial<DigraphAttributeWriters> = {},
23
+ ): DigraphAttributeWriters {
24
+ if (!writers.node) {
25
+ writers.node = () => new Map<string, string>();
26
+ }
27
+ if (!writers.edge) {
28
+ writers.edge = () => new Map<string, string>();
29
+ }
30
+ if (!writers.graph) {
31
+ writers.graph = () => new Map<string, string>();
32
+ }
33
+ return writers as DigraphAttributeWriters;
34
+ }
35
+
36
+ class Digraph extends Printable {
37
+ static fromGraph(g: FullGraph, partialWriters?: Partial<DigraphAttributeWriters>): Digraph {
38
+ const writers = fillMissingWriters(partialWriters);
39
+
40
+ const nodes = [...g.children()].map((id) =>
41
+ DigraphNode.fromGraphNode(g, id, (id) => AttributeWriter.fromMap(writers.node(id))),
42
+ );
43
+
44
+ const edges = [...g.allEdges].map((e) =>
45
+ DigraphEdge.fromGraphEdge(e, AttributeWriter.fromMap(writers.edge(e))),
46
+ );
47
+
48
+ const attributes = AttributeWriter.fromMap(writers.graph());
49
+
50
+ return new Digraph(nodes, edges, attributes);
51
+ }
52
+
53
+ private readonly nodes: DigraphNode[];
54
+ private readonly edges: DigraphEdge[];
55
+ private readonly attributes: AttributeWriter;
56
+ private constructor(nodes: DigraphNode[], edges: DigraphEdge[], attributes: AttributeWriter) {
57
+ super();
58
+ this.nodes = nodes;
59
+ this.edges = edges;
60
+ this.attributes = attributes;
61
+ }
62
+
63
+ print(buffer: CodeContentBuffer) {
64
+ buffer.write("digraph {").indentOnce();
65
+ if (this.attributes.hasAttributes) {
66
+ buffer.newline().write(this.attributes).newline();
67
+ }
68
+ buffer
69
+ .repeatedNewlineWrite(this.nodes)
70
+ .repeatedNewlineWrite(this.edges)
71
+ .unindentOnce()
72
+ .removeTrailingSpaces()
73
+ .write("}");
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Given a Node, possibly with children, print the tree of children:
79
+ * ```
80
+ * a {
81
+ * b {
82
+ * c;
83
+ * };
84
+ * };
85
+ * ```
86
+ *
87
+ */
88
+ class DigraphNode extends Printable {
89
+ static fromGraphNode(
90
+ g: FullGraph,
91
+ id: NodeId,
92
+ attributeWriter: (id: NodeId) => AttributeWriter,
93
+ ): DigraphNode {
94
+ return new DigraphNode(
95
+ id,
96
+ [...g.children(id)].map((n) => DigraphNode.fromGraphNode(g, n, attributeWriter)),
97
+ attributeWriter(id),
98
+ );
99
+ }
100
+
101
+ private readonly id: NodeId;
102
+ private readonly children: DigraphNode[];
103
+ private readonly attributes: AttributeWriter;
104
+ private constructor(id: NodeId, children: DigraphNode[], attributes: AttributeWriter) {
105
+ super();
106
+ this.id = id;
107
+ this.children = children;
108
+ this.attributes = attributes;
109
+ }
110
+
111
+ print(buffer: CodeContentBuffer) {
112
+ buffer.write(this.id);
113
+ if (this.attributes.hasAttributes) {
114
+ buffer.write(" ").write(this.attributes);
115
+ }
116
+ if (this.children.length > 0) {
117
+ buffer.write(" {").indentOnce().repeatedNewlineWrite(this.children).unindentOnce().write("}");
118
+ }
119
+ buffer.write(";");
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Printable for DigraphEdge; given an edge print the link `id1 -> id2;`.
125
+ */
126
+ class DigraphEdge extends Printable {
127
+ static fromGraphEdge(e: Edge, attributes: AttributeWriter): DigraphEdge {
128
+ return new DigraphEdge(e, attributes);
129
+ }
130
+
131
+ private readonly edge: Edge;
132
+ private readonly attributes: AttributeWriter;
133
+ private constructor(edge: Edge, attributes: AttributeWriter) {
134
+ super();
135
+ this.edge = edge;
136
+ this.attributes = attributes;
137
+ }
138
+
139
+ print(buffer: CodeContentBuffer) {
140
+ buffer.write(this.edge.source).write(" -> ").write(this.edge.target);
141
+ if (this.attributes.hasAttributes) {
142
+ buffer.write(" ").write(this.attributes);
143
+ }
144
+ buffer.write(";");
145
+ }
146
+ }
147
+
148
+ /**
149
+ * An AttributeWriter creates an attribute list in the written DOT file. It is
150
+ * created from a map of key/value strings, and writes the key unquoted and the
151
+ * value in quotes. No validation is performed on the key.
152
+ */
153
+ class AttributeWriter extends Printable {
154
+ static fromMap(map: Map<string, string>): AttributeWriter {
155
+ return new AttributeWriter(map);
156
+ }
157
+
158
+ get hasAttributes(): boolean {
159
+ return this.attributes.size > 0;
160
+ }
161
+
162
+ private readonly attributes: Map<string, string>;
163
+ private constructor(attributes: Map<string, string>) {
164
+ super();
165
+ this.attributes = attributes;
166
+ }
167
+
168
+ print(buffer: CodeContentBuffer) {
169
+ if (this.attributes.size === 0) return;
170
+ buffer
171
+ .write("[")
172
+ .repeatedWrite(
173
+ [...this.attributes].map(([k, v]) => `${k}="${v}";`).map(PrintableString.from),
174
+ " ",
175
+ DelimitationStrategy.ALL_BUT_LAST,
176
+ )
177
+ .write("]");
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Create a DOT text for a graph. The opposite of `builder.parse`.
183
+ */
184
+ export function stringGraph(g: FullGraph, writers?: Partial<DigraphAttributeWriters>): string {
185
+ const buffer = new CodeContentBuffer();
186
+ const digraph = Digraph.fromGraph(g, writers);
187
+ return buffer.write(digraph).content;
188
+ }
@@ -0,0 +1,275 @@
1
+ import type { Printable } from "./printable.ts";
2
+
3
+ const INDENT = " "; // Two spaces
4
+ const NEWLINE = "\n";
5
+
6
+ /**
7
+ * Used to specify how a list should be delimited.
8
+ *
9
+ * <p>example: If the delimiter was a comma, then this would be the result of
10
+ * delimiting the list [1, 2, 3].
11
+ *
12
+ * <p>All: 1,2,3, All_BUT_LAST: 1,2,3
13
+ */
14
+ export const DelimitationStrategy = {
15
+ ALL: 0,
16
+ ALL_BUT_LAST: 1,
17
+ } as const;
18
+ export type DelimitationStrategy = (typeof DelimitationStrategy)[keyof typeof DelimitationStrategy];
19
+
20
+ /** Used to specify where leading and trailing new lines should be placed. */
21
+ const NewlineStrategyEnum = {
22
+ BETWEEN_ITEMS_ONLY: 0,
23
+ LEADING: 1,
24
+ TRAILING: 2,
25
+ LEADING_AND_TRAILING: 3,
26
+ } as const;
27
+ type NewlineStrategyEnum = (typeof NewlineStrategyEnum)[keyof typeof NewlineStrategyEnum];
28
+
29
+ /**
30
+ * NewlineStrategy describes where to place newlines around repeated sections of
31
+ * code in a CodeContentBuffer.
32
+ */
33
+ export class NewlineStrategy {
34
+ static BETWEEN_ITEMS_ONLY = new NewlineStrategy(NewlineStrategyEnum.BETWEEN_ITEMS_ONLY);
35
+ static LEADING = new NewlineStrategy(NewlineStrategyEnum.LEADING);
36
+ static TRAILING = new NewlineStrategy(NewlineStrategyEnum.TRAILING);
37
+ static LEADING_AND_TRAILING = new NewlineStrategy(NewlineStrategyEnum.LEADING_AND_TRAILING);
38
+
39
+ private readonly strategy: NewlineStrategyEnum;
40
+ private constructor(strategy: NewlineStrategyEnum) {
41
+ this.strategy = strategy;
42
+ }
43
+
44
+ get leading(): boolean {
45
+ return (
46
+ this.strategy === NewlineStrategyEnum.LEADING ||
47
+ this.strategy === NewlineStrategyEnum.LEADING_AND_TRAILING
48
+ );
49
+ }
50
+
51
+ get trailing(): boolean {
52
+ return (
53
+ this.strategy === NewlineStrategyEnum.TRAILING ||
54
+ this.strategy === NewlineStrategyEnum.LEADING_AND_TRAILING
55
+ );
56
+ }
57
+ }
58
+
59
+ /**
60
+ * For sertain operations, we do a lot of removing at the end of a string. This
61
+ * Buffer class handled that in an efficient way.
62
+ */
63
+ class Buffer {
64
+ readonly strings: string[] = [""];
65
+
66
+ get tail(): string {
67
+ return this.strings[this.strings.length - 1];
68
+ }
69
+ set tail(value: string) {
70
+ this.strings[this.strings.length - 1] = value;
71
+ }
72
+
73
+ get isLastCharNewLine() {
74
+ this.shrink();
75
+ return this.tail.substr(-1) === NEWLINE;
76
+ }
77
+
78
+ get isLastCharSpace() {
79
+ this.shrink();
80
+ return [" ", "\t"].indexOf(this.tail.substr(-1)) > -1;
81
+ }
82
+
83
+ get isLastCharWhitespace() {
84
+ this.shrink();
85
+ return [" ", "\t", "\n"].indexOf(this.tail.substr(-1)) > -1;
86
+ }
87
+
88
+ append(s: string) {
89
+ if (this.tail.length > this.bufferSize) {
90
+ this.strings.push(s);
91
+ } else {
92
+ this.tail += s;
93
+ }
94
+ }
95
+
96
+ toString(): string {
97
+ return this.strings.join("");
98
+ }
99
+
100
+ removeTrailingNewlines() {
101
+ while (this.isLastCharNewLine) {
102
+ this.tail = this.tail.slice(0, -1);
103
+ }
104
+ }
105
+
106
+ removeTrailingSpaces() {
107
+ while (this.isLastCharSpace) {
108
+ this.tail = this.tail.slice(0, -1);
109
+ }
110
+ }
111
+
112
+ removeTrailingWhitespace() {
113
+ while (this.isLastCharWhitespace) {
114
+ this.tail = this.tail.slice(0, -1);
115
+ }
116
+ }
117
+
118
+ private shrink() {
119
+ while (this.tail.length === 0 && this.strings.length > 1) {
120
+ this.strings.pop();
121
+ }
122
+ }
123
+
124
+ private readonly bufferSize = 1024;
125
+ }
126
+
127
+ /**
128
+ * CodeContentBuffer provides a clean wrapper around a string that maintains an
129
+ * understanding of internal lines and its current indentation leve.
130
+ */
131
+ export class CodeContentBuffer {
132
+ private indentLevel = 0;
133
+ private newlinePrefix = "";
134
+ private readonly stringBuilder = new Buffer();
135
+
136
+ get content(): string {
137
+ return this.stringBuilder.toString();
138
+ }
139
+
140
+ indentOnce(): CodeContentBuffer {
141
+ this.indentLevel += 1;
142
+ return this;
143
+ }
144
+
145
+ unindentOnce(): CodeContentBuffer {
146
+ this.indentLevel -= 1;
147
+ return this;
148
+ }
149
+
150
+ indentTwice(): CodeContentBuffer {
151
+ return this.indentOnce().indentOnce();
152
+ }
153
+
154
+ unindentTwice(): CodeContentBuffer {
155
+ return this.unindentOnce().unindentOnce();
156
+ }
157
+
158
+ setNewlinePrefix(prefix: string): CodeContentBuffer {
159
+ this.newlinePrefix = prefix;
160
+ return this;
161
+ }
162
+
163
+ removeNewlinePrefix(): CodeContentBuffer {
164
+ this.newlinePrefix = "";
165
+ return this;
166
+ }
167
+
168
+ removeTrailingNewlines(): CodeContentBuffer {
169
+ this.stringBuilder.removeTrailingNewlines();
170
+ return this;
171
+ }
172
+
173
+ removeTrailingSpaces(): CodeContentBuffer {
174
+ this.stringBuilder.removeTrailingSpaces();
175
+ return this;
176
+ }
177
+
178
+ removeTrailingWhitespace(): CodeContentBuffer {
179
+ this.stringBuilder.removeTrailingWhitespace();
180
+ return this;
181
+ }
182
+
183
+ newline(n = 1): CodeContentBuffer {
184
+ while (--n >= 0) {
185
+ this.stringBuilder.removeTrailingSpaces();
186
+ this.append(NEWLINE);
187
+ this.write(this.newlinePrefix);
188
+ }
189
+ return this;
190
+ }
191
+
192
+ write(printable: string | Printable | null): CodeContentBuffer {
193
+ if (printable == null) {
194
+ return this;
195
+ }
196
+ if (typeof printable === "string") {
197
+ this.applyIndent().append(printable);
198
+ } else {
199
+ printable.print(this);
200
+ }
201
+ return this;
202
+ }
203
+
204
+ repeatedNewlineWrite<T extends Printable>(
205
+ printables: T[] | null,
206
+ newlineCount = 1,
207
+ newlineStrategy = NewlineStrategy.LEADING_AND_TRAILING,
208
+ delimiter = "",
209
+ deliminationStrategy: DelimitationStrategy = DelimitationStrategy.ALL,
210
+ ): CodeContentBuffer {
211
+ return this.repeatedWrite(
212
+ printables,
213
+ delimiter,
214
+ deliminationStrategy,
215
+ newlineCount,
216
+ newlineStrategy,
217
+ );
218
+ }
219
+
220
+ repeatedWrite<T extends Printable>(
221
+ printables: T[] | null,
222
+ delimiter = "",
223
+ deliminationStrategy: DelimitationStrategy = DelimitationStrategy.ALL,
224
+ newlineCount = 0,
225
+ newlineStrategy = NewlineStrategy.BETWEEN_ITEMS_ONLY,
226
+ ): CodeContentBuffer {
227
+ if (printables == null) {
228
+ return this;
229
+ }
230
+ const size = printables.length;
231
+ if (size === 0) return this; // Bail early.
232
+
233
+ if (newlineCount > 0 && newlineStrategy.leading) {
234
+ this.newline();
235
+ }
236
+
237
+ let i = 0;
238
+ for (const t of printables) {
239
+ t.print(this);
240
+
241
+ if (i < size - 1) {
242
+ this.write(delimiter);
243
+ this.newline(newlineCount);
244
+ } else if (DelimitationStrategy.ALL === deliminationStrategy) {
245
+ this.write(delimiter);
246
+ }
247
+
248
+ i++;
249
+ }
250
+
251
+ if (newlineCount > 0 && newlineStrategy.trailing) {
252
+ this.newline().removeTrailingSpaces();
253
+ }
254
+
255
+ return this;
256
+ }
257
+
258
+ toString(): string {
259
+ return this.content;
260
+ }
261
+
262
+ private append(value: string): CodeContentBuffer {
263
+ this.stringBuilder.append(value);
264
+ return this;
265
+ }
266
+
267
+ private applyIndent(): CodeContentBuffer {
268
+ if (this.stringBuilder.isLastCharNewLine) {
269
+ for (let i = 0; i < this.indentLevel; i++) {
270
+ this.append(INDENT);
271
+ }
272
+ }
273
+ return this;
274
+ }
275
+ }
@@ -0,0 +1,33 @@
1
+ import { CodeContentBuffer } from "./code_content_buffer.ts";
2
+
3
+ /** Represents something that will write to a passed in {@link codeBuffer}. */
4
+ export abstract class Printable {
5
+ /** Write this Printable into a CodeContentBuffer. */
6
+ abstract print(codeBuffer: CodeContentBuffer): void;
7
+
8
+ toString(): string {
9
+ const codeBuffer = new CodeContentBuffer();
10
+ this.print(codeBuffer);
11
+ return codeBuffer.content;
12
+ }
13
+ }
14
+
15
+ /** Converts a String to a {@link Printable}. */
16
+ export class PrintableString extends Printable {
17
+ static from(s: string | Printable): Printable {
18
+ if (typeof s === "string") {
19
+ s = new PrintableString(s);
20
+ }
21
+ return s;
22
+ }
23
+
24
+ readonly value: string;
25
+ private constructor(value: string) {
26
+ super();
27
+ this.value = value;
28
+ }
29
+
30
+ print(codeBuffer: CodeContentBuffer): void {
31
+ codeBuffer.write(this.value);
32
+ }
33
+ }
package/src/tsabre.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./tsabre/algo.ts";
2
+ export * from "./tsabre/builder.ts";
3
+ export * from "./tsabre/compress.ts";
4
+ export * from "./tsabre/graph.ts";
5
+ export * from "./tsabre/parse.ts";
6
+ export * from "./tsabre/print.ts";