@trebco/treb 23.6.2 → 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.
- package/.eslintignore +8 -0
- package/.eslintrc.js +164 -0
- package/README-shadow-DOM.md +88 -0
- package/README.md +37 -130
- package/api-config.json +29 -0
- package/api-generator/api-generator-types.ts +82 -0
- package/api-generator/api-generator.ts +1172 -0
- package/api-generator/package.json +3 -0
- package/build/treb-spreadsheet.mjs +14 -0
- package/{treb.d.ts → build/treb.d.ts} +293 -299
- package/esbuild-custom-element.mjs +336 -0
- package/esbuild.js +305 -0
- package/package.json +43 -14
- package/treb-base-types/package.json +5 -0
- package/treb-base-types/src/api_types.ts +36 -0
- package/treb-base-types/src/area.ts +583 -0
- package/treb-base-types/src/basic_types.ts +45 -0
- package/treb-base-types/src/cell.ts +612 -0
- package/treb-base-types/src/cells.ts +1066 -0
- package/treb-base-types/src/color.ts +124 -0
- package/treb-base-types/src/import.ts +71 -0
- package/treb-base-types/src/index-standalone.ts +29 -0
- package/treb-base-types/src/index.ts +42 -0
- package/treb-base-types/src/layout.ts +47 -0
- package/treb-base-types/src/localization.ts +187 -0
- package/treb-base-types/src/rectangle.ts +145 -0
- package/treb-base-types/src/render_text.ts +72 -0
- package/treb-base-types/src/style.ts +545 -0
- package/treb-base-types/src/table.ts +109 -0
- package/treb-base-types/src/text_part.ts +54 -0
- package/treb-base-types/src/theme.ts +608 -0
- package/treb-base-types/src/union.ts +152 -0
- package/treb-base-types/src/value-type.ts +164 -0
- package/treb-base-types/style/resizable.css +59 -0
- package/treb-calculator/modern.tsconfig.json +11 -0
- package/treb-calculator/package.json +5 -0
- package/treb-calculator/src/calculator.ts +2546 -0
- package/treb-calculator/src/complex-math.ts +558 -0
- package/treb-calculator/src/dag/array-vertex.ts +198 -0
- package/treb-calculator/src/dag/graph.ts +951 -0
- package/treb-calculator/src/dag/leaf_vertex.ts +118 -0
- package/treb-calculator/src/dag/spreadsheet_vertex.ts +327 -0
- package/treb-calculator/src/dag/spreadsheet_vertex_base.ts +44 -0
- package/treb-calculator/src/dag/vertex.ts +352 -0
- package/treb-calculator/src/descriptors.ts +162 -0
- package/treb-calculator/src/expression-calculator.ts +1069 -0
- package/treb-calculator/src/function-error.ts +103 -0
- package/treb-calculator/src/function-library.ts +103 -0
- package/treb-calculator/src/functions/base-functions.ts +1214 -0
- package/treb-calculator/src/functions/checkbox.ts +164 -0
- package/treb-calculator/src/functions/complex-functions.ts +253 -0
- package/treb-calculator/src/functions/finance-functions.ts +399 -0
- package/treb-calculator/src/functions/information-functions.ts +102 -0
- package/treb-calculator/src/functions/matrix-functions.ts +182 -0
- package/treb-calculator/src/functions/sparkline.ts +335 -0
- package/treb-calculator/src/functions/statistics-functions.ts +350 -0
- package/treb-calculator/src/functions/text-functions.ts +298 -0
- package/treb-calculator/src/index.ts +27 -0
- package/treb-calculator/src/notifier-types.ts +59 -0
- package/treb-calculator/src/primitives.ts +428 -0
- package/treb-calculator/src/utilities.ts +305 -0
- package/treb-charts/package.json +5 -0
- package/treb-charts/src/chart-functions.ts +156 -0
- package/treb-charts/src/chart-types.ts +230 -0
- package/treb-charts/src/chart.ts +1288 -0
- package/treb-charts/src/index.ts +24 -0
- package/treb-charts/src/main.ts +37 -0
- package/treb-charts/src/rectangle.ts +52 -0
- package/treb-charts/src/renderer.ts +1841 -0
- package/treb-charts/src/util.ts +122 -0
- package/treb-charts/style/charts.scss +221 -0
- package/treb-charts/style/old-charts.scss +250 -0
- package/treb-embed/markup/layout.html +137 -0
- package/treb-embed/markup/toolbar.html +175 -0
- package/treb-embed/modern.tsconfig.json +25 -0
- package/treb-embed/src/custom-element/content-types.d.ts +18 -0
- package/treb-embed/src/custom-element/global.d.ts +11 -0
- package/treb-embed/src/custom-element/spreadsheet-constructor.ts +1227 -0
- package/treb-embed/src/custom-element/treb-global.ts +44 -0
- package/treb-embed/src/custom-element/treb-spreadsheet-element.ts +52 -0
- package/treb-embed/src/embedded-spreadsheet.ts +5362 -0
- package/treb-embed/src/index.ts +16 -0
- package/treb-embed/src/language-model.ts +41 -0
- package/treb-embed/src/options.ts +320 -0
- package/treb-embed/src/progress-dialog.ts +228 -0
- package/treb-embed/src/selection-state.ts +16 -0
- package/treb-embed/src/spinner.ts +42 -0
- package/treb-embed/src/toolbar-message.ts +96 -0
- package/treb-embed/src/types.ts +167 -0
- package/treb-embed/style/autocomplete.scss +103 -0
- package/treb-embed/style/dark-theme.scss +114 -0
- package/treb-embed/style/defaults.scss +36 -0
- package/treb-embed/style/dialog.scss +181 -0
- package/treb-embed/style/dropdown-select.scss +101 -0
- package/treb-embed/style/formula-bar.scss +193 -0
- package/treb-embed/style/grid.scss +374 -0
- package/treb-embed/style/layout.scss +424 -0
- package/treb-embed/style/mouse-mask.scss +67 -0
- package/treb-embed/style/note.scss +92 -0
- package/treb-embed/style/overlay-editor.scss +102 -0
- package/treb-embed/style/spinner.scss +92 -0
- package/treb-embed/style/tab-bar.scss +228 -0
- package/treb-embed/style/table.scss +80 -0
- package/treb-embed/style/theme-defaults.scss +444 -0
- package/treb-embed/style/toolbar.scss +416 -0
- package/treb-embed/style/tooltip.scss +68 -0
- package/treb-embed/style/treb-icons.scss +130 -0
- package/treb-embed/style/treb-spreadsheet-element.scss +20 -0
- package/treb-embed/style/z-index.scss +43 -0
- package/treb-export/docs/charts.md +68 -0
- package/treb-export/modern.tsconfig.json +19 -0
- package/treb-export/package.json +4 -0
- package/treb-export/src/address-type.ts +77 -0
- package/treb-export/src/base-template.ts +22 -0
- package/treb-export/src/column-width.ts +85 -0
- package/treb-export/src/drawing2/chart-template-components2.ts +389 -0
- package/treb-export/src/drawing2/chart2.ts +282 -0
- package/treb-export/src/drawing2/column-chart-template2.ts +521 -0
- package/treb-export/src/drawing2/donut-chart-template2.ts +296 -0
- package/treb-export/src/drawing2/drawing2.ts +355 -0
- package/treb-export/src/drawing2/embedded-image.ts +71 -0
- package/treb-export/src/drawing2/scatter-chart-template2.ts +555 -0
- package/treb-export/src/export-worker/export-worker.ts +99 -0
- package/treb-export/src/export-worker/index-modern.ts +22 -0
- package/treb-export/src/export2.ts +2204 -0
- package/treb-export/src/import2.ts +882 -0
- package/treb-export/src/relationship.ts +36 -0
- package/treb-export/src/shared-strings2.ts +128 -0
- package/treb-export/src/template-2.ts +22 -0
- package/treb-export/src/unescape_xml.ts +47 -0
- package/treb-export/src/workbook-sheet2.ts +182 -0
- package/treb-export/src/workbook-style2.ts +1285 -0
- package/treb-export/src/workbook-theme2.ts +88 -0
- package/treb-export/src/workbook2.ts +491 -0
- package/treb-export/src/xml-utils.ts +201 -0
- package/treb-export/template/base/[Content_Types].xml +2 -0
- package/treb-export/template/base/_rels/.rels +2 -0
- package/treb-export/template/base/docProps/app.xml +2 -0
- package/treb-export/template/base/docProps/core.xml +12 -0
- package/treb-export/template/base/xl/_rels/workbook.xml.rels +2 -0
- package/treb-export/template/base/xl/sharedStrings.xml +2 -0
- package/treb-export/template/base/xl/styles.xml +2 -0
- package/treb-export/template/base/xl/theme/theme1.xml +2 -0
- package/treb-export/template/base/xl/workbook.xml +2 -0
- package/treb-export/template/base/xl/worksheets/sheet1.xml +2 -0
- package/treb-export/template/base.xlsx +0 -0
- package/treb-format/package.json +8 -0
- package/treb-format/src/format.test.ts +213 -0
- package/treb-format/src/format.ts +942 -0
- package/treb-format/src/format_cache.ts +199 -0
- package/treb-format/src/format_parser.ts +723 -0
- package/treb-format/src/index.ts +25 -0
- package/treb-format/src/number_format_section.ts +100 -0
- package/treb-format/src/value_parser.ts +337 -0
- package/treb-grid/package.json +5 -0
- package/treb-grid/src/editors/autocomplete.ts +394 -0
- package/treb-grid/src/editors/autocomplete_matcher.ts +260 -0
- package/treb-grid/src/editors/formula_bar.ts +473 -0
- package/treb-grid/src/editors/formula_editor_base.ts +910 -0
- package/treb-grid/src/editors/overlay_editor.ts +511 -0
- package/treb-grid/src/index.ts +37 -0
- package/treb-grid/src/layout/base_layout.ts +2618 -0
- package/treb-grid/src/layout/grid_layout.ts +299 -0
- package/treb-grid/src/layout/rectangle_cache.ts +86 -0
- package/treb-grid/src/render/selection-renderer.ts +414 -0
- package/treb-grid/src/render/svg_header_overlay.ts +93 -0
- package/treb-grid/src/render/svg_selection_block.ts +187 -0
- package/treb-grid/src/render/tile_renderer.ts +2122 -0
- package/treb-grid/src/types/annotation.ts +216 -0
- package/treb-grid/src/types/border_constants.ts +34 -0
- package/treb-grid/src/types/clipboard_data.ts +31 -0
- package/treb-grid/src/types/data_model.ts +334 -0
- package/treb-grid/src/types/drag_mask.ts +81 -0
- package/treb-grid/src/types/grid.ts +7743 -0
- package/treb-grid/src/types/grid_base.ts +3644 -0
- package/treb-grid/src/types/grid_command.ts +470 -0
- package/treb-grid/src/types/grid_events.ts +124 -0
- package/treb-grid/src/types/grid_options.ts +97 -0
- package/treb-grid/src/types/grid_selection.ts +60 -0
- package/treb-grid/src/types/named_range.ts +369 -0
- package/treb-grid/src/types/scale-control.ts +202 -0
- package/treb-grid/src/types/serialize_options.ts +72 -0
- package/treb-grid/src/types/set_range_options.ts +52 -0
- package/treb-grid/src/types/sheet.ts +3099 -0
- package/treb-grid/src/types/sheet_types.ts +95 -0
- package/treb-grid/src/types/tab_bar.ts +464 -0
- package/treb-grid/src/types/tile.ts +59 -0
- package/treb-grid/src/types/update_flags.ts +75 -0
- package/treb-grid/src/util/dom_utilities.ts +44 -0
- package/treb-grid/src/util/fontmetrics2.ts +179 -0
- package/treb-grid/src/util/ua.ts +104 -0
- package/treb-logo.svg +18 -0
- package/treb-parser/package.json +5 -0
- package/treb-parser/src/csv-parser.ts +122 -0
- package/treb-parser/src/index.ts +25 -0
- package/treb-parser/src/md-parser.ts +526 -0
- package/treb-parser/src/parser-types.ts +397 -0
- package/treb-parser/src/parser.test.ts +298 -0
- package/treb-parser/src/parser.ts +2673 -0
- package/treb-utils/package.json +5 -0
- package/treb-utils/src/dispatch.ts +57 -0
- package/treb-utils/src/event_source.ts +147 -0
- package/treb-utils/src/ievent_source.ts +33 -0
- package/treb-utils/src/index.ts +31 -0
- package/treb-utils/src/measurement.ts +174 -0
- package/treb-utils/src/resizable.ts +160 -0
- package/treb-utils/src/scale.ts +137 -0
- package/treb-utils/src/serialize_html.ts +124 -0
- package/treb-utils/src/template.ts +70 -0
- package/treb-utils/src/validate_uri.ts +61 -0
- package/tsconfig.json +10 -0
- package/tsproject.json +30 -0
- package/util/license-plugin-esbuild.js +86 -0
- package/util/list-css-vars.sh +46 -0
- package/README-esm.md +0 -37
- package/treb-bundle.css +0 -2
- 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
|
+
}
|