@trebco/treb 29.8.4 → 30.1.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 (38) hide show
  1. package/dist/treb-spreadsheet-light.mjs +11 -11
  2. package/dist/treb-spreadsheet.mjs +15 -15
  3. package/dist/treb.d.ts +11 -1
  4. package/eslint.config.js +9 -0
  5. package/package.json +1 -1
  6. package/treb-base-types/src/area-utils.ts +60 -0
  7. package/treb-base-types/src/area.ts +11 -0
  8. package/treb-base-types/src/cell.ts +6 -1
  9. package/treb-base-types/src/cells.ts +38 -7
  10. package/treb-base-types/src/index.ts +2 -0
  11. package/treb-calculator/src/calculator.ts +274 -4
  12. package/treb-calculator/src/dag/array-vertex.ts +0 -10
  13. package/treb-calculator/src/dag/graph.ts +118 -77
  14. package/treb-calculator/src/dag/spreadsheet_vertex.ts +38 -9
  15. package/treb-calculator/src/dag/spreadsheet_vertex_base.ts +1 -0
  16. package/treb-calculator/src/expression-calculator.ts +7 -2
  17. package/treb-calculator/src/function-error.ts +6 -0
  18. package/treb-charts/src/chart-functions.ts +39 -5
  19. package/treb-charts/src/chart-types.ts +23 -0
  20. package/treb-charts/src/chart-utils.ts +165 -2
  21. package/treb-charts/src/chart.ts +6 -1
  22. package/treb-charts/src/default-chart-renderer.ts +70 -1
  23. package/treb-charts/src/index.ts +1 -0
  24. package/treb-charts/src/renderer.ts +95 -2
  25. package/treb-charts/style/charts.scss +41 -0
  26. package/treb-embed/src/embedded-spreadsheet.ts +12 -5
  27. package/treb-embed/src/options.ts +8 -0
  28. package/treb-embed/style/dark-theme.scss +4 -0
  29. package/treb-embed/style/grid.scss +15 -0
  30. package/treb-embed/style/z-index.scss +3 -0
  31. package/treb-export/src/import2.ts +9 -0
  32. package/treb-export/src/workbook2.ts +67 -3
  33. package/treb-grid/src/editors/editor.ts +12 -5
  34. package/treb-grid/src/layout/base_layout.ts +41 -0
  35. package/treb-grid/src/types/grid.ts +61 -25
  36. package/treb-parser/src/parser-types.ts +3 -0
  37. package/treb-parser/src/parser.ts +21 -2
  38. package/treb-utils/src/serialize_html.ts +35 -10
package/dist/treb.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v29.8. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v30.1. Copyright 2018-2024 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
2
2
 
3
3
  /**
4
4
  * add our tag to the map
@@ -276,6 +276,12 @@ export interface EmbeddedSpreadsheetOptions {
276
276
  * indent/outdent buttons; default false
277
277
  */
278
278
  indent_buttons?: boolean;
279
+
280
+ /**
281
+ * enable spill arrays and spill references. this is on by default
282
+ * starting in 30.1.0. set to false to disable.
283
+ */
284
+ spill?: boolean;
279
285
  }
280
286
 
281
287
  /**
@@ -291,6 +297,9 @@ export interface ICellAddress {
291
297
  absolute_row?: boolean;
292
298
  absolute_column?: boolean;
293
299
  sheet_id?: number;
300
+
301
+ /** spill reference */
302
+ spill?: boolean;
294
303
  }
295
304
 
296
305
  /**
@@ -1794,6 +1803,7 @@ export interface BaseCellData {
1794
1803
  hyperlink?: string;
1795
1804
  type?: SerializedValueType;
1796
1805
  sheet_id?: number;
1806
+ spill?: IArea;
1797
1807
  }
1798
1808
 
1799
1809
  /**
package/eslint.config.js CHANGED
@@ -9,6 +9,15 @@ export default tseslint.config(
9
9
  {
10
10
  rules: {
11
11
 
12
+ // allow destructuring to use let if some vars are changed
13
+
14
+ "prefer-const": [
15
+ "error", {
16
+ "destructuring": "all",
17
+ },
18
+ ],
19
+
20
+
12
21
  // allow destructuring to use garbage variables. prefix name with
13
22
  // underscore (or just use underscore).
14
23
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "29.8.4",
3
+ "version": "30.1.1",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -0,0 +1,60 @@
1
+ /*
2
+ * This file is part of TREB.
3
+ *
4
+ * TREB is free software: you can redistribute it and/or modify it under the
5
+ * terms of the GNU General Public License as published by the Free Software
6
+ * Foundation, either version 3 of the License, or (at your option) any
7
+ * later version.
8
+ *
9
+ * TREB is distributed in the hope that it will be useful, but WITHOUT ANY
10
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
+ * details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License along
15
+ * with TREB. If not, see <https://www.gnu.org/licenses/>.
16
+ *
17
+ * Copyright 2022-2024 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+
22
+ import type { IArea } from './area';
23
+ import type { ICellAddress } from './area';
24
+
25
+ /**
26
+ * area being a class is a mistake, but it will take a while
27
+ * to undo that (if we do it at all). for now we'll start creating
28
+ * utilities that can operate on the interface type, and maybe over
29
+ * time the class will wither.
30
+ */
31
+
32
+ export function* Iterate(area: IArea): Generator<ICellAddress> {
33
+
34
+ /**
35
+ * this doesn't serialize. perhaps we should switch to -1,
36
+ * which is obviously invalid. although I think we may from
37
+ * time to time use that as a flag. which is bad, obviously.
38
+ * if we do that we'll need to define a new interface. which
39
+ * might be good.
40
+ */
41
+ if (area.start.row === Infinity || area.end.row === Infinity) {
42
+ throw new Error(`don't iterate infinite area`);
43
+ }
44
+
45
+ /*
46
+ if (area.entire_row || area.entire_column) {
47
+ throw new Error(`don't iterate infinite area`);
48
+ }
49
+ */
50
+
51
+ const sheet_id = area.start.sheet_id;
52
+
53
+ for (let row = area.start.row; row <= area.end.row; row++) {
54
+ for (let column = area.start.column; column <= area.end.column; column++) {
55
+ yield { row, column, sheet_id };
56
+ }
57
+ }
58
+
59
+ }
60
+
@@ -33,6 +33,10 @@ export interface ICellAddress {
33
33
  absolute_row?: boolean;
34
34
  absolute_column?: boolean;
35
35
  sheet_id?: number;
36
+
37
+ /** spill reference */
38
+ spill?: boolean;
39
+
36
40
  }
37
41
 
38
42
  /**
@@ -659,6 +663,13 @@ export class Area implements IArea {
659
663
  }
660
664
  }
661
665
 
666
+ /** Resizes range in place to be the requested shape */
667
+ public Reshape(rows: number, columns: number): Area {
668
+ this.end_.row = this.start_.row + rows - 1;
669
+ this.end_.column = this.start_.column + columns - 1;
670
+ return this; // fluent
671
+ }
672
+
662
673
  /** Resizes range in place so that it includes the given area (merge) */
663
674
  public ConsumeArea(area: IArea): void {
664
675
  this.ConsumeAddress(area.start);
@@ -236,6 +236,9 @@ export class Cell {
236
236
  /** if this cell is part of an array, pointer to the area. */
237
237
  public area?: Area;
238
238
 
239
+ /** testing spill */
240
+ public spill?: Area;
241
+
239
242
  /**
240
243
  * if this cell is merged, pointer to the area
241
244
  */
@@ -354,6 +357,7 @@ export class Cell {
354
357
  public Reset(): void{
355
358
  this.type = ValueType.undefined;
356
359
  this.value
360
+ = this.spill
357
361
  = this.note
358
362
  = this.hyperlink
359
363
  = this.formatted
@@ -372,7 +376,8 @@ export class Cell {
372
376
  public Set(value: CellValue, type = GetValueType(value)): void {
373
377
  this.value = value;
374
378
  this.type = type;
375
- this.formatted =
379
+ this.spill = // added
380
+ this.formatted =
376
381
  this.rendered_type =
377
382
  this.style =
378
383
  this.calculated =
@@ -87,6 +87,7 @@ export interface BaseCellData {
87
87
  hyperlink?: string;
88
88
  type?: SerializedValueType; // ValueType;
89
89
  sheet_id?: number;
90
+ spill?: IArea;
90
91
  // locked?: boolean;
91
92
  }
92
93
 
@@ -507,6 +508,9 @@ export class Cells {
507
508
  // cell.calculated = obj.calculated;
508
509
  // cell.calculated_type = obj.calculated_type;
509
510
  cell.SetCalculatedValue(obj.calculated, this.SerializedTypeToValueType(obj.calculated_type));
511
+ if (obj.spill) {
512
+ cell.spill = new Area(obj.spill.start, obj.spill.end);
513
+ }
510
514
  }
511
515
 
512
516
  if (style_refs) {
@@ -704,6 +708,9 @@ export class Cells {
704
708
  if (options.calculated_value &&
705
709
  typeof cell.calculated !== 'undefined') { // && cell.calculated_type !== ValueType.error) {
706
710
  obj.calculated = cell.calculated;
711
+ if (cell.spill) {
712
+ obj.spill = cell.spill;
713
+ }
707
714
 
708
715
  // always preserve error type, because we can't infer
709
716
  if (options.preserve_type || cell.calculated_type === ValueType.error) {
@@ -1216,16 +1223,40 @@ export class Cells {
1216
1223
  *
1217
1224
  * UPDATE: adding area parameter; not shrinking it (don't call w/ infinities)
1218
1225
  */
1219
- public *IterateRC(area?: IArea) {
1226
+ public *IterateRC(area?: IArea, create_missing_cells = false) {
1227
+
1228
+ if (!area && create_missing_cells) {
1229
+ area = new Area({
1230
+ row: 0,
1231
+ column: 0,
1232
+ }, {
1233
+ row: this.rows_ - 1,
1234
+ column: this.columns - 1,
1235
+ });
1236
+ }
1220
1237
 
1221
1238
  if (area) {
1222
- for (let row = area.start.row; row <= area.end.row; row++) {
1223
- const block = this.data[row];
1224
- if (block) {
1239
+ if (create_missing_cells) {
1240
+ for (let row = area.start.row; row <= area.end.row; row++) {
1241
+ if (!this.data[row]) this.data[row] = [];
1242
+ const block = this.data[row];
1225
1243
  for (let column = area.start.column; column <= area.end.column; column++) {
1226
- const cell = block[column];
1227
- if (cell) {
1228
- yield { cell, row, column };
1244
+ if (!block[column]) {
1245
+ block[column] = new Cell();
1246
+ }
1247
+ yield { cell: block[column], row, column };
1248
+ }
1249
+ }
1250
+ }
1251
+ else {
1252
+ for (let row = area.start.row; row <= area.end.row; row++) {
1253
+ const block = this.data[row];
1254
+ if (block) {
1255
+ for (let column = area.start.column; column <= area.end.column; column++) {
1256
+ const cell = block[column];
1257
+ if (cell) {
1258
+ yield { cell, row, column };
1259
+ }
1229
1260
  }
1230
1261
  }
1231
1262
  }
@@ -40,6 +40,8 @@ export * from './gradient';
40
40
  export * from './evaluate-options';
41
41
  export * from './dom-utilities';
42
42
 
43
+ export * as AreaUtils from './area-utils';
44
+
43
45
  // import * as Style from './style';
44
46
  // export { Style };
45
47
 
@@ -20,7 +20,8 @@
20
20
  */
21
21
 
22
22
  import type { Cell, ICellAddress, ICellAddress2, UnionValue, EvaluateOptions,
23
- ArrayUnion, IArea, CellDataWithAddress, CellValue} from 'treb-base-types';
23
+ ArrayUnion, IArea, CellDataWithAddress, CellValue,
24
+ Cells} from 'treb-base-types';
24
25
  import { Localization, Area, ValueType, IsCellAddress} from 'treb-base-types';
25
26
 
26
27
  import type { ExpressionUnit, DependencyList, UnitRange, UnitAddress, UnitIdentifier, ParseResult } from 'treb-parser';
@@ -140,10 +141,14 @@ export interface CalculatorOptions {
140
141
  */
141
142
  complex_numbers: 'on'|'off';
142
143
 
144
+ /** enable spill arrays */
145
+ spill?: boolean;
146
+
143
147
  }
144
148
 
145
149
  const default_calculator_options: CalculatorOptions = {
146
150
  complex_numbers: 'off',
151
+ spill: false,
147
152
  };
148
153
 
149
154
  /**
@@ -181,6 +186,15 @@ export class Calculator extends Graph {
181
186
  /** the next calculation must do a full rebuild -- set on reset */
182
187
  protected full_rebuild_required = false;
183
188
 
189
+ protected options: CalculatorOptions;
190
+
191
+ /**
192
+ * this is a flag we're using to communicate back to the embedded
193
+ * sheet, when the grid has expanded as a result of a calculation
194
+ * (because of a spill).
195
+ */
196
+ public grid_expanded = false;
197
+
184
198
  constructor(protected readonly model: DataModel, calculator_options: Partial<CalculatorOptions> = {}) {
185
199
 
186
200
  super();
@@ -191,12 +205,12 @@ export class Calculator extends Graph {
191
205
  // at the moment options are only used here; in the future
192
206
  // we may need to extend handling.
193
207
 
194
- const options: CalculatorOptions = {
208
+ this.options = {
195
209
  ...default_calculator_options,
196
210
  ...calculator_options,
197
211
  };
198
212
 
199
- if (options.complex_numbers === 'on') {
213
+ if (this.options.complex_numbers === 'on') {
200
214
 
201
215
  // complex number handling: we need to change SQRT, POWER and ^
202
216
 
@@ -1065,6 +1079,244 @@ export class Calculator extends Graph {
1065
1079
  }
1066
1080
  }
1067
1081
 
1082
+ public AttachSpillData(area: Area, cells?: Cells) {
1083
+
1084
+ if (!cells) {
1085
+ const sheet = area.start.sheet_id ? this.model.sheets.Find(area.start.sheet_id) : undefined;
1086
+ cells = sheet?.cells;
1087
+ }
1088
+
1089
+ if (!cells) {
1090
+ throw new Error('invalid sheet ID in attach spill data');
1091
+ }
1092
+
1093
+ const vertex = new StateLeafVertex();
1094
+ let counter = 0;
1095
+ let error = false;
1096
+
1097
+ for (const {cell, row, column} of cells.IterateRC(area, true)) {
1098
+ if (counter++ && (cell.type !== ValueType.undefined || cell.area || cell.merge_area || cell.table)) {
1099
+ error = true; // spill error.
1100
+ }
1101
+ this.AddLeafVertexEdge({row, column, sheet_id: area.start.sheet_id}, vertex);
1102
+ }
1103
+
1104
+ // console.info("storing spill data");
1105
+ this.spill_data.push({area, vertex});
1106
+
1107
+ return { vertex, area, error };
1108
+
1109
+ }
1110
+
1111
+ public SpillCallback(vertex: SpreadsheetVertex, result: ArrayUnion): SpreadsheetVertex[] {
1112
+
1113
+ const { reference, address } = vertex;
1114
+ const { value } = result;
1115
+
1116
+ const recalculate_list: SpreadsheetVertex[] = [];
1117
+
1118
+ if (!reference) {
1119
+ // should throw but this is new and I don't want to break stuff rn
1120
+ console.error("invalid reference in spill callback");
1121
+ return recalculate_list;
1122
+ }
1123
+
1124
+ if (!address || !address.sheet_id) {
1125
+ // should throw but this is new and I don't want to break stuff rn
1126
+ console.error("invalid address in spill callback");
1127
+ return recalculate_list;
1128
+ }
1129
+
1130
+ // I guess we could do the one-cell version here
1131
+
1132
+ if (value.length === 1 && value[0].length === 1) {
1133
+ reference.SetCalculatedValue(value[0][0].value as CellValue);
1134
+ return recalculate_list;
1135
+ }
1136
+
1137
+ if (!this.options.spill) {
1138
+ reference.SetCalculatedValue(value[0][0].value as CellValue);
1139
+ return recalculate_list;
1140
+ }
1141
+
1142
+ // console.info("SPILLING");
1143
+
1144
+ const sheet = this.model.sheets.Find(address.sheet_id);
1145
+ const cells = sheet?.cells;
1146
+
1147
+ if (cells) {
1148
+
1149
+ // first thing we do is check for empty. if !empty, that's a
1150
+ // spill error and we can stop. also check for area, spill and
1151
+ // merge (and table).
1152
+
1153
+ const columns = result.value.length;
1154
+ const rows = result.value[0].length;
1155
+ const area = new Area(address).Reshape(rows, columns);
1156
+
1157
+
1158
+ /*
1159
+ let counter = 0;
1160
+ let error = false;
1161
+ const leaf = new StateLeafVertex();
1162
+
1163
+ for (const {cell, row, column} of cells.IterateRC(area, true)) {
1164
+ if (counter++ && (cell.type !== ValueType.undefined || cell.area || cell.merge_area || cell.table)) {
1165
+ error = true; // spill error.
1166
+ }
1167
+ this.AddLeafVertexEdge({row, column, sheet_id: area.start.sheet_id}, leaf);
1168
+ }
1169
+
1170
+ console.info("storing spill data");
1171
+
1172
+ // this.spills.push(new Area(area.start, area.end));
1173
+ this.spill_data.push({area, vertex: leaf});
1174
+ */
1175
+
1176
+ const { error } = this.AttachSpillData(area, cells);
1177
+
1178
+ if (error) {
1179
+ // console.info("returning error");
1180
+ reference.SetCalculationError('SPILL');
1181
+ return recalculate_list;
1182
+ }
1183
+
1184
+ // expand the sheet, if necessary (+1)
1185
+
1186
+ if (sheet.rows < area.end.row + 1) {
1187
+ sheet.cells.EnsureRow(area.end.row + 1);
1188
+ this.grid_expanded = true;
1189
+ }
1190
+ if (sheet.columns < area.end.column + 1) {
1191
+ sheet.cells.EnsureColumn(area.end.column + 1);
1192
+ this.grid_expanded = true;
1193
+ }
1194
+
1195
+ // hmmm... we need the grid to update... how can we ensure that?
1196
+ // we could use a flag that the embedded sheet checks after
1197
+ // calculation... which is kind of sloppy but I don't have a better
1198
+ // idea
1199
+
1200
+
1201
+
1202
+ const sheet_id = address.sheet_id;
1203
+ // let dirty = false;
1204
+
1205
+ for (const {row, column} of cells.IterateRC(area)) {
1206
+ if (row === address.row && column === address.column) { continue; }
1207
+
1208
+ const vertex = this.GetVertex({sheet_id, row, column}, false) as SpreadsheetVertex;
1209
+
1210
+ if (vertex) {
1211
+ // onsole.info("Have vertex @", row, column, "dirty?", vertex.dirty);
1212
+
1213
+ if (!(vertex as SpreadsheetVertex).dirty) {
1214
+ recalculate_list.push(vertex);
1215
+ }
1216
+ }
1217
+
1218
+ // do we need these edges? if so, what for? (...)
1219
+ // I guess to propagate dirty if there's a dependent?
1220
+ // apparently not, although I'm not sure why...
1221
+
1222
+
1223
+ // this.AddEdge(address, {sheet_id, row, column});
1224
+
1225
+ }
1226
+
1227
+ /*
1228
+ // ok, now we can go on: copying a little from dynamic dependencies,
1229
+ // we're going to add vertices and check for dirty:
1230
+
1231
+ const sheet_id = address.sheet_id;
1232
+ let dirty = false;
1233
+
1234
+ for (const {row, column} of cells.IterateRC(area)) {
1235
+
1236
+ if (row === address.row && column === address.column) { continue; }
1237
+
1238
+ const vertex = this.GetVertex({sheet_id, row, column}, true);
1239
+ if (vertex && vertex.dirty) {
1240
+
1241
+ console.info(`Adding edge from ${{row: address.row, column: address.column}} -> ${{row, column}}`)
1242
+
1243
+ // see comments in DynamicDependencies()
1244
+
1245
+ this.AddEdge(address, {row, column, sheet_id});
1246
+ dirty = true;
1247
+
1248
+ }
1249
+
1250
+ }
1251
+
1252
+ console.info("DIRTY?", dirty);
1253
+
1254
+ if (dirty) {
1255
+ const current_vertex = this.GetVertex(address, true) as SpreadsheetVertex;
1256
+ current_vertex.short_circuit = true;
1257
+ return;
1258
+ }
1259
+ */
1260
+
1261
+ //
1262
+
1263
+
1264
+ // maybe we could use a vertex here?
1265
+ // actually we also need to do a loop check
1266
+
1267
+ // so I think the approach is
1268
+ //
1269
+ // 1 - create a vertex (spill -- array vertex?)
1270
+ // 2 - check for loops
1271
+ // 3 - if no loop, check for empty
1272
+ // 4 - if empty, fill in values
1273
+ //
1274
+
1275
+ // and then we need to flush spill vertices at
1276
+ // some point, either always on recalc, or on
1277
+ // recalc if something is dirty. flushing spill
1278
+ // vertices implies removing all spilled values
1279
+ // so they will be empty if something changes
1280
+
1281
+ // PLAN: start by flushing all spill vertices on
1282
+ // every recalc, and then we can trim it back
1283
+
1284
+ // spill ok, set values
1285
+
1286
+
1287
+ for (let {cell, row, column} of cells.IterateRC(area)) {
1288
+
1289
+ cell.spill = area;
1290
+
1291
+ row -= address.row;
1292
+ column -= address.column;
1293
+
1294
+ const v = result.value[column][row];
1295
+
1296
+ switch (v.type) {
1297
+ case ValueType.object:
1298
+ case ValueType.array:
1299
+ break;
1300
+ default:
1301
+ cell.SetCalculatedValue(v.value, v.type);
1302
+ break;
1303
+ }
1304
+
1305
+ }
1306
+
1307
+ return recalculate_list;
1308
+
1309
+ }
1310
+
1311
+ //
1312
+
1313
+ console.error("invalid cell reference in spill callback");
1314
+ reference.SetCalculationError('SPILL');
1315
+
1316
+ return [];
1317
+
1318
+ }
1319
+
1068
1320
  /**
1069
1321
  * this is a mess [not as bad as it used to be]
1070
1322
  */
@@ -1507,6 +1759,8 @@ export class Calculator extends Graph {
1507
1759
 
1508
1760
  this.RebuildGraph(subset);
1509
1761
 
1762
+ this.grid_expanded = false; // unset
1763
+
1510
1764
  try {
1511
1765
  this.Recalculate();
1512
1766
  }
@@ -2567,7 +2821,23 @@ export class Calculator extends Graph {
2567
2821
  */
2568
2822
  protected RebuildGraphCell(cell: Cell, address: ICellAddress2): void {
2569
2823
 
2570
- // console.info("RGC", cell, address);
2824
+ // FIXME/TODO: if spill is not enabled, we'll need to clean up
2825
+ // rendered values from the spill here
2826
+
2827
+ if (cell.spill) {
2828
+ if (this.options.spill) {
2829
+ if (cell.spill.start.row === address.row && cell.spill.start.column === address.column) {
2830
+
2831
+ // this.spills.push(new Area(cell.spill.start, cell.spill.end));
2832
+ this.AttachSpillData(new Area(cell.spill.start, cell.spill.end));
2833
+
2834
+ }
2835
+ else {
2836
+ // ...
2837
+ this.AddEdge(cell.spill.start, address);
2838
+ }
2839
+ }
2840
+ }
2571
2841
 
2572
2842
  // array head
2573
2843
  if (cell.area && cell.area.start.column === address.column && cell.area.start.row === address.row) {
@@ -66,16 +66,6 @@ export class ArrayVertex extends SpreadsheetVertexBase {
66
66
  /** the target area */
67
67
  public area: Area;
68
68
 
69
- /* * temporary method, we should clean up explicitly * /
70
- public static CheckOutbound(): void {
71
- for (const vertex of this.list) {
72
- if (vertex.edges_out.length === 0) {
73
- console.info('no outbound edges', vertex);
74
- }
75
- }
76
- }
77
- */
78
-
79
69
  /**
80
70
  * factory/lookup method: creates a vertex if it does not exist, or
81
71
  * returns existing vertex. returns the vertex and a flag indicating