@trebco/treb 23.6.5 → 25.0.0-rc1

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 (217) hide show
  1. package/.eslintignore +8 -0
  2. package/.eslintrc.js +164 -0
  3. package/README-shadow-DOM.md +88 -0
  4. package/README.md +37 -130
  5. package/api-config.json +29 -0
  6. package/api-generator/api-generator-types.ts +82 -0
  7. package/api-generator/api-generator.ts +1172 -0
  8. package/api-generator/package.json +3 -0
  9. package/build/treb-spreadsheet.mjs +14 -0
  10. package/{treb.d.ts → build/treb.d.ts} +285 -269
  11. package/esbuild-custom-element.mjs +336 -0
  12. package/esbuild.js +305 -0
  13. package/package.json +43 -14
  14. package/treb-base-types/package.json +5 -0
  15. package/treb-base-types/src/api_types.ts +36 -0
  16. package/treb-base-types/src/area.ts +583 -0
  17. package/treb-base-types/src/basic_types.ts +45 -0
  18. package/treb-base-types/src/cell.ts +612 -0
  19. package/treb-base-types/src/cells.ts +1066 -0
  20. package/treb-base-types/src/color.ts +124 -0
  21. package/treb-base-types/src/import.ts +71 -0
  22. package/treb-base-types/src/index-standalone.ts +29 -0
  23. package/treb-base-types/src/index.ts +42 -0
  24. package/treb-base-types/src/layout.ts +47 -0
  25. package/treb-base-types/src/localization.ts +187 -0
  26. package/treb-base-types/src/rectangle.ts +145 -0
  27. package/treb-base-types/src/render_text.ts +72 -0
  28. package/treb-base-types/src/style.ts +545 -0
  29. package/treb-base-types/src/table.ts +109 -0
  30. package/treb-base-types/src/text_part.ts +54 -0
  31. package/treb-base-types/src/theme.ts +608 -0
  32. package/treb-base-types/src/union.ts +152 -0
  33. package/treb-base-types/src/value-type.ts +164 -0
  34. package/treb-base-types/style/resizable.css +59 -0
  35. package/treb-calculator/modern.tsconfig.json +11 -0
  36. package/treb-calculator/package.json +5 -0
  37. package/treb-calculator/src/calculator.ts +2546 -0
  38. package/treb-calculator/src/complex-math.ts +558 -0
  39. package/treb-calculator/src/dag/array-vertex.ts +198 -0
  40. package/treb-calculator/src/dag/graph.ts +951 -0
  41. package/treb-calculator/src/dag/leaf_vertex.ts +118 -0
  42. package/treb-calculator/src/dag/spreadsheet_vertex.ts +327 -0
  43. package/treb-calculator/src/dag/spreadsheet_vertex_base.ts +44 -0
  44. package/treb-calculator/src/dag/vertex.ts +352 -0
  45. package/treb-calculator/src/descriptors.ts +162 -0
  46. package/treb-calculator/src/expression-calculator.ts +1069 -0
  47. package/treb-calculator/src/function-error.ts +103 -0
  48. package/treb-calculator/src/function-library.ts +103 -0
  49. package/treb-calculator/src/functions/base-functions.ts +1214 -0
  50. package/treb-calculator/src/functions/checkbox.ts +164 -0
  51. package/treb-calculator/src/functions/complex-functions.ts +253 -0
  52. package/treb-calculator/src/functions/finance-functions.ts +399 -0
  53. package/treb-calculator/src/functions/information-functions.ts +102 -0
  54. package/treb-calculator/src/functions/matrix-functions.ts +182 -0
  55. package/treb-calculator/src/functions/sparkline.ts +335 -0
  56. package/treb-calculator/src/functions/statistics-functions.ts +350 -0
  57. package/treb-calculator/src/functions/text-functions.ts +298 -0
  58. package/treb-calculator/src/index.ts +27 -0
  59. package/treb-calculator/src/notifier-types.ts +59 -0
  60. package/treb-calculator/src/primitives.ts +428 -0
  61. package/treb-calculator/src/utilities.ts +305 -0
  62. package/treb-charts/package.json +5 -0
  63. package/treb-charts/src/chart-functions.ts +156 -0
  64. package/treb-charts/src/chart-types.ts +230 -0
  65. package/treb-charts/src/chart.ts +1288 -0
  66. package/treb-charts/src/index.ts +24 -0
  67. package/treb-charts/src/main.ts +37 -0
  68. package/treb-charts/src/rectangle.ts +52 -0
  69. package/treb-charts/src/renderer.ts +1841 -0
  70. package/treb-charts/src/util.ts +122 -0
  71. package/treb-charts/style/charts.scss +221 -0
  72. package/treb-charts/style/old-charts.scss +250 -0
  73. package/treb-embed/markup/layout.html +137 -0
  74. package/treb-embed/markup/toolbar.html +175 -0
  75. package/treb-embed/modern.tsconfig.json +25 -0
  76. package/treb-embed/src/custom-element/content-types.d.ts +18 -0
  77. package/treb-embed/src/custom-element/global.d.ts +11 -0
  78. package/treb-embed/src/custom-element/spreadsheet-constructor.ts +1227 -0
  79. package/treb-embed/src/custom-element/treb-global.ts +44 -0
  80. package/treb-embed/src/custom-element/treb-spreadsheet-element.ts +52 -0
  81. package/treb-embed/src/embedded-spreadsheet.ts +5362 -0
  82. package/treb-embed/src/index.ts +16 -0
  83. package/treb-embed/src/language-model.ts +41 -0
  84. package/treb-embed/src/options.ts +320 -0
  85. package/treb-embed/src/progress-dialog.ts +228 -0
  86. package/treb-embed/src/selection-state.ts +16 -0
  87. package/treb-embed/src/spinner.ts +42 -0
  88. package/treb-embed/src/toolbar-message.ts +96 -0
  89. package/treb-embed/src/types.ts +167 -0
  90. package/treb-embed/style/autocomplete.scss +103 -0
  91. package/treb-embed/style/dark-theme.scss +114 -0
  92. package/treb-embed/style/defaults.scss +36 -0
  93. package/treb-embed/style/dialog.scss +181 -0
  94. package/treb-embed/style/dropdown-select.scss +101 -0
  95. package/treb-embed/style/formula-bar.scss +193 -0
  96. package/treb-embed/style/grid.scss +374 -0
  97. package/treb-embed/style/layout.scss +424 -0
  98. package/treb-embed/style/mouse-mask.scss +67 -0
  99. package/treb-embed/style/note.scss +92 -0
  100. package/treb-embed/style/overlay-editor.scss +102 -0
  101. package/treb-embed/style/spinner.scss +92 -0
  102. package/treb-embed/style/tab-bar.scss +228 -0
  103. package/treb-embed/style/table.scss +80 -0
  104. package/treb-embed/style/theme-defaults.scss +444 -0
  105. package/treb-embed/style/toolbar.scss +416 -0
  106. package/treb-embed/style/tooltip.scss +68 -0
  107. package/treb-embed/style/treb-icons.scss +130 -0
  108. package/treb-embed/style/treb-spreadsheet-element.scss +20 -0
  109. package/treb-embed/style/z-index.scss +43 -0
  110. package/treb-export/docs/charts.md +68 -0
  111. package/treb-export/modern.tsconfig.json +19 -0
  112. package/treb-export/package.json +4 -0
  113. package/treb-export/src/address-type.ts +77 -0
  114. package/treb-export/src/base-template.ts +22 -0
  115. package/treb-export/src/column-width.ts +85 -0
  116. package/treb-export/src/drawing2/chart-template-components2.ts +389 -0
  117. package/treb-export/src/drawing2/chart2.ts +282 -0
  118. package/treb-export/src/drawing2/column-chart-template2.ts +521 -0
  119. package/treb-export/src/drawing2/donut-chart-template2.ts +296 -0
  120. package/treb-export/src/drawing2/drawing2.ts +355 -0
  121. package/treb-export/src/drawing2/embedded-image.ts +71 -0
  122. package/treb-export/src/drawing2/scatter-chart-template2.ts +555 -0
  123. package/treb-export/src/export-worker/export-worker.ts +99 -0
  124. package/treb-export/src/export-worker/index-modern.ts +22 -0
  125. package/treb-export/src/export2.ts +2204 -0
  126. package/treb-export/src/import2.ts +882 -0
  127. package/treb-export/src/relationship.ts +36 -0
  128. package/treb-export/src/shared-strings2.ts +128 -0
  129. package/treb-export/src/template-2.ts +22 -0
  130. package/treb-export/src/unescape_xml.ts +47 -0
  131. package/treb-export/src/workbook-sheet2.ts +182 -0
  132. package/treb-export/src/workbook-style2.ts +1285 -0
  133. package/treb-export/src/workbook-theme2.ts +88 -0
  134. package/treb-export/src/workbook2.ts +491 -0
  135. package/treb-export/src/xml-utils.ts +201 -0
  136. package/treb-export/template/base/[Content_Types].xml +2 -0
  137. package/treb-export/template/base/_rels/.rels +2 -0
  138. package/treb-export/template/base/docProps/app.xml +2 -0
  139. package/treb-export/template/base/docProps/core.xml +12 -0
  140. package/treb-export/template/base/xl/_rels/workbook.xml.rels +2 -0
  141. package/treb-export/template/base/xl/sharedStrings.xml +2 -0
  142. package/treb-export/template/base/xl/styles.xml +2 -0
  143. package/treb-export/template/base/xl/theme/theme1.xml +2 -0
  144. package/treb-export/template/base/xl/workbook.xml +2 -0
  145. package/treb-export/template/base/xl/worksheets/sheet1.xml +2 -0
  146. package/treb-export/template/base.xlsx +0 -0
  147. package/treb-format/package.json +8 -0
  148. package/treb-format/src/format.test.ts +213 -0
  149. package/treb-format/src/format.ts +942 -0
  150. package/treb-format/src/format_cache.ts +199 -0
  151. package/treb-format/src/format_parser.ts +723 -0
  152. package/treb-format/src/index.ts +25 -0
  153. package/treb-format/src/number_format_section.ts +100 -0
  154. package/treb-format/src/value_parser.ts +337 -0
  155. package/treb-grid/package.json +5 -0
  156. package/treb-grid/src/editors/autocomplete.ts +394 -0
  157. package/treb-grid/src/editors/autocomplete_matcher.ts +260 -0
  158. package/treb-grid/src/editors/formula_bar.ts +473 -0
  159. package/treb-grid/src/editors/formula_editor_base.ts +910 -0
  160. package/treb-grid/src/editors/overlay_editor.ts +511 -0
  161. package/treb-grid/src/index.ts +37 -0
  162. package/treb-grid/src/layout/base_layout.ts +2618 -0
  163. package/treb-grid/src/layout/grid_layout.ts +299 -0
  164. package/treb-grid/src/layout/rectangle_cache.ts +86 -0
  165. package/treb-grid/src/render/selection-renderer.ts +414 -0
  166. package/treb-grid/src/render/svg_header_overlay.ts +93 -0
  167. package/treb-grid/src/render/svg_selection_block.ts +187 -0
  168. package/treb-grid/src/render/tile_renderer.ts +2122 -0
  169. package/treb-grid/src/types/annotation.ts +216 -0
  170. package/treb-grid/src/types/border_constants.ts +34 -0
  171. package/treb-grid/src/types/clipboard_data.ts +31 -0
  172. package/treb-grid/src/types/data_model.ts +334 -0
  173. package/treb-grid/src/types/drag_mask.ts +81 -0
  174. package/treb-grid/src/types/grid.ts +7743 -0
  175. package/treb-grid/src/types/grid_base.ts +3644 -0
  176. package/treb-grid/src/types/grid_command.ts +470 -0
  177. package/treb-grid/src/types/grid_events.ts +124 -0
  178. package/treb-grid/src/types/grid_options.ts +97 -0
  179. package/treb-grid/src/types/grid_selection.ts +60 -0
  180. package/treb-grid/src/types/named_range.ts +369 -0
  181. package/treb-grid/src/types/scale-control.ts +202 -0
  182. package/treb-grid/src/types/serialize_options.ts +72 -0
  183. package/treb-grid/src/types/set_range_options.ts +52 -0
  184. package/treb-grid/src/types/sheet.ts +3099 -0
  185. package/treb-grid/src/types/sheet_types.ts +95 -0
  186. package/treb-grid/src/types/tab_bar.ts +464 -0
  187. package/treb-grid/src/types/tile.ts +59 -0
  188. package/treb-grid/src/types/update_flags.ts +75 -0
  189. package/treb-grid/src/util/dom_utilities.ts +44 -0
  190. package/treb-grid/src/util/fontmetrics2.ts +179 -0
  191. package/treb-grid/src/util/ua.ts +104 -0
  192. package/treb-logo.svg +18 -0
  193. package/treb-parser/package.json +5 -0
  194. package/treb-parser/src/csv-parser.ts +122 -0
  195. package/treb-parser/src/index.ts +25 -0
  196. package/treb-parser/src/md-parser.ts +526 -0
  197. package/treb-parser/src/parser-types.ts +397 -0
  198. package/treb-parser/src/parser.test.ts +298 -0
  199. package/treb-parser/src/parser.ts +2673 -0
  200. package/treb-utils/package.json +5 -0
  201. package/treb-utils/src/dispatch.ts +57 -0
  202. package/treb-utils/src/event_source.ts +147 -0
  203. package/treb-utils/src/ievent_source.ts +33 -0
  204. package/treb-utils/src/index.ts +31 -0
  205. package/treb-utils/src/measurement.ts +174 -0
  206. package/treb-utils/src/resizable.ts +160 -0
  207. package/treb-utils/src/scale.ts +137 -0
  208. package/treb-utils/src/serialize_html.ts +124 -0
  209. package/treb-utils/src/template.ts +70 -0
  210. package/treb-utils/src/validate_uri.ts +61 -0
  211. package/tsconfig.json +10 -0
  212. package/tsproject.json +30 -0
  213. package/util/license-plugin-esbuild.js +86 -0
  214. package/util/list-css-vars.sh +46 -0
  215. package/README-esm.md +0 -37
  216. package/treb-bundle.css +0 -2
  217. package/treb-bundle.mjs +0 -15
@@ -0,0 +1,951 @@
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-2023 trebco, llc.
18
+ * info@treb.app
19
+ *
20
+ */
21
+
22
+ import { Vertex, Color } from './vertex';
23
+ import { SpreadsheetVertex } from './spreadsheet_vertex';
24
+ import { ArrayVertex } from './array-vertex';
25
+ import type { SpreadsheetVertexBase, CalculationResult, GraphCallbacks } from './spreadsheet_vertex_base';
26
+ import type { LeafVertex } from './leaf_vertex';
27
+ import { ICellAddress, ICellAddress2, Area, IArea, UnionValue } from 'treb-base-types';
28
+ import type { DataModel } from 'treb-grid';
29
+
30
+ // FIXME: this is a bad habit if you're testing on falsy for OK.
31
+
32
+ export enum GraphStatus {
33
+ OK = 0,
34
+ Loop,
35
+ CalculationError,
36
+ }
37
+
38
+ /**
39
+ * graph is now abstract, as we are extending it with the calculator.
40
+ */
41
+ export abstract class Graph implements GraphCallbacks {
42
+
43
+ /**
44
+ * list of vertices, indexed by address as [sheet id][column][row]
45
+ */
46
+ public vertices: Array<Array<Array<SpreadsheetVertex|undefined>>> = [[]];
47
+
48
+ public volatile_list: SpreadsheetVertexBase[] = [];
49
+
50
+ public calculation_list: SpreadsheetVertexBase[] = [];
51
+
52
+ // public cells_map: {[index: number]: Cells} = {};
53
+
54
+ protected abstract readonly model: DataModel;
55
+
56
+ /**
57
+ * where is the loop in the graph (or at least the first one we found)?
58
+ */
59
+ public loop_hint?: string;
60
+
61
+ // special
62
+ public leaf_vertices: LeafVertex[] = [];
63
+
64
+ /** lock down access */
65
+ private dirty_list: SpreadsheetVertexBase[] = [];
66
+
67
+ /** flag set on add edge */
68
+ private loop_check_required = false;
69
+
70
+ /*
71
+ public IsArrayVertex(vertex: Vertex): vertex is ArrayVertex {
72
+ return vertex.type === ArrayVertex.type;
73
+ }
74
+ */
75
+
76
+ public IsSpreadsheetVertex(vertex: Vertex): vertex is SpreadsheetVertex {
77
+ return vertex.type === SpreadsheetVertex.type;
78
+ }
79
+
80
+ /* *
81
+ * we used to attach the data model here, but it's now an instance
82
+ * property (and readonly). we map still need to rebuild the map,
83
+ * so we're retaining the method for the time being (but renamed and
84
+ * reparameterized).
85
+ *
86
+ * if model were a class we wouldn't have to do this...
87
+ * /
88
+ protected RebuildMap(): void {
89
+ this.cells_map = {};
90
+ for (const sheet of this.model.sheets.list) {
91
+ this.cells_map[sheet.id] = sheet.cells;
92
+ }
93
+ }
94
+ */
95
+
96
+ /**
97
+ * flush the graph, calculation tree and cells reference
98
+ */
99
+ public FlushTree(): void {
100
+ this.dirty_list = [];
101
+ this.volatile_list = [];
102
+ this.vertices = [[]];
103
+ this.leaf_vertices = [];
104
+ // this.cells_map = {};
105
+
106
+ /** array vertex maintains its own list */
107
+ ArrayVertex.Clear();
108
+
109
+ }
110
+
111
+ public ResolveArrayHead(address: ICellAddress): ICellAddress {
112
+
113
+ if (!address.sheet_id) { throw new Error('resolve array head with no sheet id'); }
114
+ //const cells = this.cells_map[address.sheet_id];
115
+ const cells = this.model.sheets.Find(address.sheet_id)?.cells;
116
+
117
+ if (!cells) {
118
+ throw new Error('no cells? sheet id ' + address.sheet_id);
119
+ }
120
+
121
+ const row = cells.data[address.row];
122
+ if (row) {
123
+ const cell = row[address.column];
124
+ if (cell && cell.area) {
125
+
126
+ const resolved = { row: cell.area.start.row, column: cell.area.start.column, sheet_id: address.sheet_id };
127
+ console.info('array head', address, resolved);
128
+
129
+ return resolved;
130
+
131
+ }
132
+ }
133
+
134
+ return address;
135
+
136
+ }
137
+
138
+ /** overload */
139
+ public GetVertex(address: ICellAddress, create: true): SpreadsheetVertex;
140
+
141
+ /** overload */
142
+ public GetVertex(address: ICellAddress, create?: boolean): SpreadsheetVertex | undefined;
143
+
144
+ /** returns the vertex at this address. creates it if necessary. */
145
+ public GetVertex(address: ICellAddress, create?: boolean): SpreadsheetVertex | undefined {
146
+
147
+ if (!address.sheet_id) {
148
+ console.info({address, create});
149
+ throw new Error('getvertex with no sheet id');
150
+ }
151
+
152
+ // if (!this.cells) return undefined;
153
+
154
+ //const cells = this.cells_map[address.sheet_id];
155
+ const cells = this.model.sheets.Find(address.sheet_id)?.cells;
156
+
157
+ if (!cells) {
158
+ throw new Error('no cells? sheet id ' + address.sheet_id);
159
+ return undefined;
160
+ }
161
+
162
+ if (!this.vertices[address.sheet_id]) {
163
+ if (!create) {
164
+ return undefined;
165
+ }
166
+ this.vertices[address.sheet_id] = [];
167
+ }
168
+
169
+ if (!this.vertices[address.sheet_id][address.column]) {
170
+ if (!create) {
171
+ return undefined;
172
+ }
173
+ this.vertices[address.sheet_id][address.column] = [];
174
+ }
175
+ else {
176
+ const existing_vertex = this.vertices[address.sheet_id][address.column][address.row];
177
+ if (existing_vertex) {
178
+ return existing_vertex;
179
+ }
180
+ if (!create) return undefined;
181
+ }
182
+
183
+ const vertex = new SpreadsheetVertex();
184
+ // vertex.address = { ...address };
185
+
186
+ // because we are passing in something other than an address, we're
187
+ // collecting a lot of extraneous data here. I am worried that someone
188
+ // is relying on it, so we will force it to be just the address props.
189
+ // see if something breaks.
190
+
191
+ vertex.address = {
192
+ row: address.row,
193
+ column: address.column,
194
+ absolute_row: address.absolute_row,
195
+ absolute_column: address.absolute_column,
196
+ sheet_id: address.sheet_id,
197
+ };
198
+
199
+ // this breaks if the cell reference does not point to a cell; that
200
+ // happens if a formula references an empty cell, and we run through
201
+ // a serialize/unserialize pass.
202
+
203
+ // FIXME: ensuring the cell will work, but that seems like unecessary
204
+ // work; is there a way we can just let this reference dangle? the only
205
+ // thing we need to worry about is maintaining the dependency, so if the
206
+ // cell _is_ created later we get the update. (...)
207
+
208
+ // FIXME: the above is not working. recall the BSM model. we had
209
+ //
210
+ // =IF(C3, C3, C4 + x)
211
+ //
212
+ // with no value in C3. as a result if you type something in, it won't
213
+ // update because there's no bound reference. we can ensure the cell,
214
+ // but maybe there's way to get it to work without that.
215
+
216
+ // I think the reason is because the reference lookup is closed by
217
+ // the calc routine; so dirty doesn't do it. let's ensure cell, for now.
218
+
219
+ /*
220
+ // works ok, maybe a little verbose
221
+
222
+ const row = cells.data2[address.row];
223
+ if (row) {
224
+ const cell = row[address.column];
225
+ if (cell) {
226
+ vertex.reference = cell;
227
+ }
228
+ }
229
+ */
230
+
231
+ vertex.reference = cells.EnsureCell(address);
232
+
233
+ this.vertices[address.sheet_id][address.column][address.row] = vertex;
234
+
235
+ // if there's an array that contains this cell, we need to create an edge
236
+
237
+ // this.CreateImplicitEdgeToArrays(vertex);
238
+
239
+ // this is back, in the new form
240
+
241
+ ArrayVertex.CreateImplicitEdges(vertex, address as ICellAddress2);
242
+
243
+ return vertex;
244
+
245
+ }
246
+
247
+ /** deletes the vertex at this address. */
248
+ public RemoveVertex(address: ICellAddress): void {
249
+
250
+ if (!address.sheet_id) { throw new Error('removevertex with no sheet id'); }
251
+
252
+ const vertex = this.GetVertex(address, false);
253
+ if (!vertex) return;
254
+
255
+ vertex.Reset();
256
+ this.vertices[address.sheet_id][address.column][address.row] = undefined;
257
+
258
+ // ArrayVertex2.CheckOutbound();
259
+
260
+ }
261
+
262
+ /** removes all edges, for rebuilding. leaves value/formula as-is. */
263
+ public ResetVertex(address: ICellAddress): void {
264
+ const vertex = this.GetVertex(address, false);
265
+ if (vertex) vertex.Reset();
266
+ }
267
+
268
+ public RIBcount = 0;
269
+
270
+ /**
271
+ * resets the vertex by removing inbound edges and clearing formula flag.
272
+ * we have an option to set dirty because they get called together
273
+ * frequently, saves a lookup.
274
+ */
275
+ public ResetInbound(address: ICellAddress, set_dirty = false, create = true, remove = false): void {
276
+
277
+ this.RIBcount++;
278
+
279
+ const vertex = this.GetVertex(address, create);
280
+
281
+ // console.info("RIB", address.row, address.column, 'd?', set_dirty, vertex, 'R?', remove);
282
+
283
+ if (!vertex) {
284
+ if (set_dirty) {
285
+ const list = ArrayVertex.GetContainingArrays(address as ICellAddress2);
286
+ for (const entry of list) {
287
+ this.SetVertexDirty(entry);
288
+ }
289
+ }
290
+ return;
291
+ }
292
+
293
+ // this vertexes' dependencies might only have one outbound edge
294
+ // (to this); in that case, we could remove the dependency vertex,
295
+ // since it is essentially orphaned
296
+
297
+ let dependencies: Vertex[] = [];
298
+
299
+ // do this conditionally so we avoid the slice if unecessary
300
+
301
+ if (remove) {
302
+ // dependencies = vertex.edges_in.slice(0);
303
+ dependencies = Array.from(vertex.edges_in);
304
+ }
305
+
306
+ vertex.ClearDependencies();
307
+
308
+ if (set_dirty) {
309
+ // this.dirty_list.push(vertex);
310
+ // vertex.SetDirty();
311
+ this.SetVertexDirty(vertex);
312
+ }
313
+
314
+ // vertex.expression = { type: 'missing', id: -1 };
315
+ // vertex.expression_error = false;
316
+
317
+ // this probably should not happen unless there are no dependents/outbound edges? (...)
318
+
319
+ if (remove) {
320
+
321
+ if (!vertex.has_outbound_edges) {
322
+ this.RemoveVertex(address);
323
+ }
324
+
325
+ for (const dependency of dependencies) {
326
+ if (!dependency.has_inbound_edges && !dependency.has_outbound_edges) {
327
+ const target = (dependency as SpreadsheetVertex);
328
+ if (target.address) {
329
+ this.RemoveVertex(target.address);
330
+ }
331
+ }
332
+ }
333
+
334
+ /*
335
+ if (vertex?.has_outbound_edges) {
336
+ console.info('(NOT) removing a vertex with outbound edges...')
337
+ }
338
+ else {
339
+ console.info('removing a vertex')
340
+ this.RemoveVertex(address);
341
+ }
342
+ */
343
+
344
+ }
345
+
346
+ }
347
+
348
+ /* * dev * /
349
+ public ForceClean() {
350
+ for (const l1 of this.vertices) {
351
+ if (l1) {
352
+ for (const l2 of l1) {
353
+ if (l2) {
354
+ for (const vertex of l2) {
355
+ if (vertex && vertex.dirty) {
356
+ vertex.dirty = false;
357
+ }
358
+ }
359
+ }
360
+ }
361
+ }
362
+ }
363
+
364
+ }
365
+
366
+ / * * dev, check if any vertices are dirtices * /
367
+ public CheckDirty() {
368
+
369
+ for (const l1 of this.vertices) {
370
+ if (l1) {
371
+ for (const l2 of l1) {
372
+ if (l2) {
373
+ for (const vertex of l2) {
374
+ if (vertex && vertex.dirty) {
375
+ console.info("DIRTY", `R${vertex.address?.row} C${vertex.address?.column}`, vertex);
376
+
377
+
378
+ }
379
+ }
380
+ }
381
+ }
382
+ }
383
+ }
384
+
385
+ }
386
+ */
387
+
388
+ /**
389
+ * reset all vertices. this method is used so we can run the loop check
390
+ * as part of the graph calculation, instead of requiring the separate call.
391
+ */
392
+ public ResetLoopState(): void {
393
+
394
+ for (const l1 of this.vertices) {
395
+ if (l1) {
396
+ for (const l2 of l1) {
397
+ if (l2) {
398
+ for (const vertex of l2) {
399
+ if (vertex) {
400
+ // vertex.color = Color.white;
401
+ vertex.color = vertex.edges_out.size ? Color.white : Color.black;
402
+ }
403
+ }
404
+ }
405
+ }
406
+ }
407
+ }
408
+
409
+ // this is unecessary
410
+
411
+ for (const vertex of this.leaf_vertices) {
412
+ vertex.color = Color.black;
413
+ }
414
+
415
+ }
416
+
417
+ /**
418
+ * global check returns true if there is any loop. this is more efficient
419
+ * than detecting loops on every call to AddEdge. uses the color algorithm
420
+ * from CLRS.
421
+ *
422
+ * UPDATE we switched to a stack-based check because we were hitting
423
+ * recursion limits, although that seemed to only happen in workers --
424
+ * perhaps they have different stack [in the malloc sense] sizes? in any
425
+ * event, I think the version below is now stable.
426
+ *
427
+ * @param force force a check, for dev/debug
428
+ */
429
+ public LoopCheck(force = false): boolean {
430
+
431
+ // this flag is only set on AddEdge, and only cleared when we successfully
432
+ // get through this function. so if there are no new edges, we can bypass.
433
+
434
+ if (!this.loop_check_required && !force) { return false; }
435
+
436
+ // vertices is array [][][]
437
+ // build a list so we can simplify the second loop (waste of space?)
438
+
439
+ const list: Vertex[] = [];
440
+
441
+ for (const l1 of this.vertices) {
442
+ if (l1) {
443
+ for (const l2 of l1) {
444
+ if (l2) {
445
+ for (const vertex of l2) {
446
+ if (vertex) {
447
+ vertex.color = vertex.edges_out.size ? Color.white : Color.black;
448
+ list.push(vertex);
449
+ }
450
+ }
451
+ }
452
+ }
453
+ }
454
+ }
455
+
456
+ // we were having problems with large calculation loops (basically long
457
+ // lists of x+1) using a recursive DFS. so we need to switch to a stack,
458
+ // just in case, hopefully it won't be too expensive.
459
+
460
+ // ---
461
+
462
+ // unwind recursion -> stack. seems to work OK. could we not just
463
+ // use the list as the initial stack? (...)
464
+
465
+ // NOTE: I think this method is bugged. I'm fixing it in the vertex
466
+ // version of the loop check routine (because we don't use this anymore)
467
+ // but if this ever comes back it needs to be fixed.
468
+
469
+ const stack: Vertex[] = [];
470
+
471
+ for (const vertex of list) {
472
+ if (vertex.color === Color.white) {
473
+
474
+ vertex.color = Color.gray; // testing
475
+ stack.push(vertex);
476
+
477
+ while (stack.length) {
478
+
479
+ // so leave it on the stack until we're done. we may "recurse", in
480
+ // which case we need to come back to this item when the children
481
+ // have been handled. we will wind up looping again, so there are
482
+ // some wasted checks, although I'm not sure how to deal with that
483
+ // without duplicating the edge list.
484
+
485
+ // concept: stack is a list of [edge, skip = 0]
486
+ // when processing an entry, do
487
+ //
488
+ // const x of (skip ? v.edges_out.slice(skip) : v.edges_out)
489
+ //
490
+ // or maybe be efficient and not fancy,
491
+ //
492
+ // for (let i = skip; i < v.edges_out.length; i++)
493
+ //
494
+ // or what you should actually do is use the stack field as the loop
495
+ // variable, so it persists. or put something in the vertex so it
496
+ // persists and applies to things that are placed on the stack more
497
+ // than once.
498
+
499
+ const v = stack[stack.length - 1];
500
+ let completed = true;
501
+
502
+ if (v.color !== Color.black) {
503
+
504
+ for (const edge of v.edges_out) {
505
+
506
+ if (edge.color === Color.gray) {
507
+ this.loop_hint = this.RenderAddress((vertex as SpreadsheetVertex).address);
508
+ console.info('loop detected @', this.loop_hint);
509
+ return true; // exit
510
+ }
511
+ else if (edge.color === Color.white) {
512
+
513
+ // here we're pushing onto the stack, so these will be handled
514
+ // next, but since v is still on the stack once those are done
515
+ // we will hit v again.
516
+
517
+ // edge.color = Color.gray;
518
+ stack.push(edge);
519
+ completed = false;
520
+ }
521
+
522
+ }
523
+
524
+ }
525
+
526
+ // if we have not pushed anything onto the stack (we have not
527
+ // recursed), then we can clean up; since the stack is still the
528
+ // same we can pop() now.
529
+
530
+ if (completed) {
531
+ stack.pop();
532
+ v.color = Color.black; // v is complete, just in case we test it again
533
+ }
534
+
535
+ }
536
+
537
+ // OK, tested and complete
538
+
539
+ vertex.color = Color.black;
540
+
541
+ }
542
+ }
543
+
544
+ /*
545
+
546
+ const tail = (vertex: Vertex): boolean => {
547
+ vertex.color = Color.gray;
548
+ for (const edge of vertex.edges_out) {
549
+ if (edge.color === Color.gray) {
550
+ this.loop_hint = this.RenderAddress((vertex as SpreadsheetVertex).address);
551
+ console.info('loop detected @', this.loop_hint);
552
+ return true; // loop
553
+ }
554
+ else if (edge.color === Color.white) {
555
+ if (tail(edge)) {
556
+ return true; // loop
557
+ }
558
+ }
559
+ }
560
+ vertex.color = Color.black;
561
+ return false;
562
+ };
563
+
564
+ for (const vertex of list) {
565
+ if (vertex.color === Color.white && tail(vertex)) { return true; }
566
+ }
567
+ */
568
+
569
+ this.loop_check_required = false;
570
+ this.loop_hint = undefined;
571
+
572
+ return false;
573
+
574
+ }
575
+
576
+ /**
577
+ * render address as string; this is for reporting loops
578
+ */
579
+ public RenderAddress(address?: ICellAddress): string {
580
+
581
+ if (!address) { return 'undefined'; }
582
+
583
+ let sheet_name = '';
584
+ if (address.sheet_id) {
585
+ const sheet = this.model.sheets.Find(address.sheet_id);
586
+ if (sheet) {
587
+ sheet_name = sheet.name + '!';
588
+ }
589
+
590
+ /*
591
+ for (const sheet of this.model.sheets.list) {
592
+ if (address.sheet_id === sheet.id) {
593
+ sheet_name = sheet.name + '!';
594
+ break;
595
+ }
596
+ }
597
+ */
598
+
599
+ }
600
+
601
+ const area = new Area(address);
602
+ return sheet_name + area.spreadsheet_label;
603
+
604
+ }
605
+
606
+ /**
607
+ * new array vertices
608
+ */
609
+ public AddArrayEdge(u: Area, v: ICellAddress): void {
610
+
611
+ // console.info('add array edge', u, v);
612
+
613
+ if (!u.start.sheet_id) {
614
+ throw new Error('AddArrayEdge called without sheet ID');
615
+ }
616
+
617
+ // this should have already been added...
618
+ const v_v = this.GetVertex(v, true);
619
+
620
+ // create or use existing
621
+ const [array_vertex, created] = ArrayVertex.GetVertex(u);
622
+
623
+ // add an edge
624
+ v_v.DependsOn(array_vertex);
625
+
626
+ // force a check on next calculation pass
627
+ this.loop_check_required = true;
628
+
629
+ if (!created) {
630
+ // console.info('reusing, so not adding edges');
631
+ return;
632
+ }
633
+
634
+ // now add edges from/to nodes THAT ALREADY EXIST
635
+
636
+ // range can't span sheets, so we only need one set to look up
637
+
638
+ const map = this.vertices[u.start.sheet_id];
639
+
640
+ // this might happen on create, we can let it go because the
641
+ // references will be added when the relevant sheet is added
642
+
643
+ if (!map) {
644
+ return;
645
+ }
646
+
647
+ // ...
648
+
649
+ if (u.entire_row) {
650
+ // console.group('entire row(s)')
651
+ for (let column = 0; column < map.length; column++) {
652
+ if (map[column]) {
653
+ for (let row = u.start.row; row <= u.end.row; row++ ) {
654
+ const vertex = map[column][row];
655
+ if (vertex) {
656
+ // console.info('add', column, row);
657
+ array_vertex.DependsOn(vertex);
658
+ }
659
+ }
660
+ }
661
+ }
662
+ // console.groupEnd();
663
+ }
664
+ else if (u.entire_column) {
665
+ // console.group('entire column(s)');
666
+ for (let column = u.start.column; column <= u.end.column; column++) {
667
+ if(map[column]) {
668
+ for (const vertex of map[column]) {
669
+ if (vertex?.address) {
670
+ // console.info('add', vertex.address);
671
+ array_vertex.DependsOn(vertex);
672
+ }
673
+ }
674
+ }
675
+ }
676
+ // console.groupEnd();
677
+ }
678
+ else {
679
+ for (let row = u.start.row; row <= u.end.row; row++) {
680
+ for (let column = u.start.column; column <= u.end.column; column++) {
681
+ const vertex = map[column][row];
682
+ if (vertex) {
683
+ array_vertex.DependsOn(vertex);
684
+ }
685
+ }
686
+ }
687
+ }
688
+
689
+ }
690
+
691
+ /** adds an edge from u -> v */
692
+ public AddEdge(u: ICellAddress, v: ICellAddress, tag?: string): void {
693
+
694
+ const v_u = this.GetVertex(u, true);
695
+ const v_v = this.GetVertex(v, true);
696
+
697
+ // seems pretty uncommon, not sure it's a useful optimization
698
+ // const already_connected = v_u.edges_out.includes(v_v);
699
+ // if (already_connected)
700
+
701
+ // console.info('add edge', u.sheet_id, u.row, u.column, '<-', v.sheet_id, v.row, v.column, tag||'')
702
+
703
+ // const status = this.LoopCheck(v_v, v_u);
704
+ // if (status === GraphStatus.Loop) { return status; }
705
+
706
+ v_v.DependsOn(v_u);
707
+
708
+ // add implicit edge to array head. this is required at start
709
+ // because the array isn't set implicitly (why not?)
710
+
711
+ // watch out for missing sheet ID!
712
+
713
+ if (v_u.reference && v_u.reference.area && !v_u.array_head) {
714
+
715
+ // console.info('add implicit edge -> array head (?), u', u, ', v', v);
716
+
717
+ // the old version added an implicit edge from array head -> array
718
+ // member, not sure why that was a good idea (or why it doesn't work);
719
+ // add an implicit edge -> v instead...
720
+ //
721
+ // maybe we thought it was a good idea because it would consolidate
722
+ // all the edges through the member? you still get edges, though...
723
+
724
+ this.AddEdge({
725
+ ...u,
726
+ row: v_u.reference.area.start.row,
727
+ column: v_u.reference.area.start.column,
728
+ }, v, 'implicit');
729
+ }
730
+
731
+ this.loop_check_required = true; // because new edges
732
+
733
+ }
734
+
735
+ /** removes edge from u -> v */
736
+ public RemoveEdge(u: ICellAddress, v: ICellAddress): void {
737
+
738
+ const v_u = this.GetVertex(u, false);
739
+ const v_v = this.GetVertex(v, false);
740
+
741
+ if (!v_u || !v_v) return;
742
+
743
+ v_u.RemoveDependent(v_v);
744
+ v_v.RemoveDependency(v_u);
745
+
746
+ }
747
+
748
+ /**
749
+ * not used? remove
750
+ * @deprecated
751
+ */
752
+ public SetAreaDirty(area: IArea): void {
753
+
754
+ // console.info("SAD");
755
+
756
+ if (area.start.column === Infinity
757
+ || area.end.column === Infinity
758
+ || area.start.row === Infinity
759
+ || area.end.row === Infinity ){
760
+ throw new Error('don\'t iterate over infinite area');
761
+ }
762
+
763
+ const sheet_id = area.start.sheet_id;
764
+ if (!sheet_id) {
765
+ throw new Error('invalid area, missing sheet id');
766
+ }
767
+
768
+ for (let column = area.start.column; column <= area.end.column; column++) {
769
+ for (let row = area.start.row; row <= area.end.row; row++) {
770
+ const address: ICellAddress = {row, column, sheet_id};
771
+ const vertex = this.GetVertex(address, false);
772
+ if (vertex) { this.SetDirty(address); }
773
+ // this.SetArraysDirty(address);
774
+ }
775
+ }
776
+
777
+ }
778
+
779
+ public SetVertexDirty(vertex: SpreadsheetVertexBase): void {
780
+
781
+ // console.info("SvD", vertex);
782
+
783
+ // see below re: concern about relying on this
784
+
785
+ if (vertex.dirty) { return; }
786
+
787
+ this.dirty_list.push(vertex);
788
+ vertex.dirty = true;
789
+
790
+ for (const edge of vertex.edges_out) {
791
+ this.SetVertexDirty(edge as SpreadsheetVertexBase);
792
+ }
793
+
794
+ }
795
+
796
+ /** sets dirty */
797
+ public SetDirty(address: ICellAddress): void {
798
+
799
+ // console.info("SD", address);
800
+
801
+ const vertex = this.GetVertex(address, true);
802
+ this.SetVertexDirty(vertex);
803
+
804
+ }
805
+
806
+ // --- leaf vertex api ---
807
+
808
+ /**
809
+ * adds a leaf vertex to the graph. this implies that someone else is
810
+ * managing and maintaining these vertices: we only need references.
811
+ */
812
+ public AddLeafVertex(vertex: LeafVertex): void {
813
+
814
+ // ... don't add more than once. this is expensive but
815
+ // the list should (generally speaking) be short, so not
816
+ // a serious problem atm
817
+
818
+ /*
819
+ if (this.leaf_vertices.some((test) => test === vertex)) {
820
+ return;
821
+ }
822
+ */
823
+ for (const test of this.leaf_vertices) {
824
+ if (test === vertex) {
825
+ return;
826
+ }
827
+ }
828
+
829
+ this.leaf_vertices.push(vertex);
830
+ }
831
+
832
+ /** removes vertex, by match */
833
+ public RemoveLeafVertex(vertex: LeafVertex): void {
834
+ this.leaf_vertices = this.leaf_vertices.filter((test) => test !== vertex);
835
+ }
836
+
837
+ /**
838
+ * adds an edge from u -> v where v is a leaf vertex. this doesn't use
839
+ * the normal semantics, and you must pass in the actual vertex instead
840
+ * of an address.
841
+ *
842
+ * there is no loop check (leaves are not allowed to have outbound
843
+ * edges).
844
+ */
845
+ public AddLeafVertexEdge(u: ICellAddress, v: LeafVertex): GraphStatus {
846
+ const v_u = this.GetVertex(u, true);
847
+ v.DependsOn(v_u);
848
+ return GraphStatus.OK;
849
+ }
850
+
851
+ /** removes edge from u -> v */
852
+ public RemoveLeafVertexEdge(u: ICellAddress, v: LeafVertex): void {
853
+ const v_u = this.GetVertex(u, false);
854
+
855
+ if (!v_u) return;
856
+
857
+ v_u.RemoveDependent(v);
858
+ v.RemoveDependency(v_u);
859
+
860
+ }
861
+
862
+ // --- for initial load ---
863
+
864
+ public InitializeGraph(): void {
865
+
866
+ for (const vertex of this.dirty_list) {
867
+
868
+ // take reference values for spreadsheet vertices
869
+
870
+ if (this.IsSpreadsheetVertex(vertex)) {
871
+ vertex.TakeReferenceValue();
872
+ if (this.CheckVolatile(vertex)) {
873
+ this.volatile_list.push(vertex);
874
+ }
875
+ }
876
+
877
+ // clear dirty flag on _all_ vertices
878
+
879
+ vertex.dirty = false;
880
+
881
+ }
882
+
883
+ // reset, essentially saying we're clean
884
+
885
+ this.dirty_list = [];
886
+
887
+ }
888
+
889
+ // --- calculation ---
890
+
891
+ /** runs calculation */
892
+ public Recalculate(): void {
893
+
894
+ /*
895
+ if (this.GlobalLoopCheck()) {
896
+ return GraphStatus.Loop;
897
+ }
898
+ */
899
+
900
+ // FIXME: volatiles should proabbly be caclucated first,
901
+ // not last, because they're probably primary.
902
+
903
+ // for (const vertex of this.volatile_list) {
904
+ // vertex.SetDirty();
905
+ // }
906
+
907
+ // const calculation_list = this.volatile_list.slice(0).concat(this.dirty_list);
908
+
909
+ // we do this using the local function so we can trace back arrays
910
+
911
+ for (const vertex of this.volatile_list) {
912
+ this.SetVertexDirty(vertex as SpreadsheetVertex);
913
+ }
914
+ // const calculation_list = this.dirty_list.slice(0);
915
+ this.calculation_list = this.dirty_list.slice(0);
916
+
917
+ this.volatile_list = [];
918
+ this.dirty_list = [];
919
+
920
+ if (this.loop_check_required) {
921
+ // console.info('reset loop state');
922
+
923
+ this.ResetLoopState();
924
+ this.loop_check_required = false;
925
+
926
+ }
927
+
928
+ // console.info("CL", calculation_list)
929
+
930
+ // recalculate everything that's dirty. FIXME: optimize path
931
+ // so we do fewer wasted checks of "are all my deps clean"?
932
+
933
+ // for (const vertex of calculation_list) {
934
+ // vertex.Calculate(this);
935
+ //}
936
+
937
+ for (let i = 0; i < this.calculation_list.length; i++) {
938
+ this.calculation_list[i].Calculate(this);
939
+ }
940
+
941
+ this.calculation_list = [];
942
+
943
+ }
944
+
945
+ public abstract CalculationCallback(vertex: SpreadsheetVertexBase): CalculationResult;
946
+
947
+ public abstract SpreadCallback(vertex: SpreadsheetVertexBase, value: UnionValue): void;
948
+
949
+ protected abstract CheckVolatile(vertex: SpreadsheetVertex): boolean;
950
+
951
+ }