@trebco/treb 29.8.3 → 30.1.0

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 +11 -4
  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 +72 -28
  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
@@ -30,6 +30,8 @@ import { Area } from 'treb-base-types';
30
30
  import type { DataModel } from 'treb-data-model';
31
31
  import { CalculationLeafVertex } from './calculation_leaf_vertex';
32
32
 
33
+ import { AreaUtils } from 'treb-base-types';
34
+
33
35
  export type LeafVertex = StateLeafVertex|CalculationLeafVertex;
34
36
  export type { StateLeafVertex };
35
37
 
@@ -55,6 +57,10 @@ export abstract class Graph implements GraphCallbacks {
55
57
 
56
58
  public calculation_list: SpreadsheetVertexBase[] = [];
57
59
 
60
+ // list of spills we have created
61
+ // public spills: IArea[] = [];
62
+ public spill_data: { area: IArea, vertex: StateLeafVertex }[] = [];
63
+
58
64
  // public cells_map: {[index: number]: Cells} = {};
59
65
 
60
66
  protected abstract readonly model: DataModel;
@@ -110,6 +116,9 @@ export abstract class Graph implements GraphCallbacks {
110
116
  this.leaf_vertices.clear();
111
117
  // this.cells_map = {};
112
118
 
119
+ // can we flush spills here without cleaning up? (...)
120
+ // this.spills = [];
121
+
113
122
  /** array vertex maintains its own list */
114
123
  ArrayVertex.Clear();
115
124
 
@@ -142,6 +151,24 @@ export abstract class Graph implements GraphCallbacks {
142
151
 
143
152
  }
144
153
 
154
+ /**
155
+ * iterate vertices
156
+ * @param area
157
+ */
158
+ public *IterateVertices(area: IArea, create = false): Generator<SpreadsheetVertex> {
159
+
160
+ // this is wasteful because it repeatedly gets the cells, but
161
+ // for a contiguous area we know they're in the same sheet. we
162
+ // cal also skip the repeated tests. FIXME
163
+
164
+ for (const address of AreaUtils.Iterate(area)) {
165
+ const vertex = this.GetVertex(address, create);
166
+ if (vertex) {
167
+ yield vertex;
168
+ }
169
+ }
170
+ }
171
+
145
172
  /** overload */
146
173
  public GetVertex(address: ICellAddress, create: true): SpreadsheetVertex;
147
174
 
@@ -717,77 +744,6 @@ export abstract class Graph implements GraphCallbacks {
717
744
 
718
745
  this.CompositeAddArrayEdge(u, v_v);
719
746
 
720
- /*
721
- // create or use existing
722
- const [array_vertex, created] = ArrayVertex.GetVertex(u);
723
-
724
- // add an edge
725
- v_v.DependsOn(array_vertex);
726
-
727
- // force a check on next calculation pass
728
- this.loop_check_required = true;
729
-
730
- if (!created) {
731
- // console.info('reusing, so not adding edges');
732
- return;
733
- }
734
-
735
- // now add edges from/to nodes THAT ALREADY EXIST
736
-
737
- // range can't span sheets, so we only need one set to look up
738
-
739
- const map = this.vertices[u.start.sheet_id];
740
-
741
- // this might happen on create, we can let it go because the
742
- // references will be added when the relevant sheet is added
743
-
744
- if (!map) {
745
- return;
746
- }
747
-
748
- // ...
749
-
750
- if (u.entire_row) {
751
- // console.group('entire row(s)')
752
- for (let column = 0; column < map.length; column++) {
753
- if (map[column]) {
754
- for (let row = u.start.row; row <= u.end.row; row++ ) {
755
- const vertex = map[column][row];
756
- if (vertex) {
757
- // console.info('add', column, row);
758
- array_vertex.DependsOn(vertex);
759
- }
760
- }
761
- }
762
- }
763
- // console.groupEnd();
764
- }
765
- else if (u.entire_column) {
766
- // console.group('entire column(s)');
767
- for (let column = u.start.column; column <= u.end.column; column++) {
768
- if(map[column]) {
769
- for (const vertex of map[column]) {
770
- if (vertex?.address) {
771
- // console.info('add', vertex.address);
772
- array_vertex.DependsOn(vertex);
773
- }
774
- }
775
- }
776
- }
777
- // console.groupEnd();
778
- }
779
- else {
780
- for (let row = u.start.row; row <= u.end.row; row++) {
781
- for (let column = u.start.column; column <= u.end.column; column++) {
782
- const vertex = map[column][row];
783
- if (vertex) {
784
- array_vertex.DependsOn(vertex);
785
- }
786
- }
787
- }
788
- }
789
- */
790
-
791
747
  }
792
748
 
793
749
  /** adds an edge from u -> v */
@@ -997,16 +953,101 @@ export abstract class Graph implements GraphCallbacks {
997
953
  // vertex.SetDirty();
998
954
  // }
999
955
 
1000
- // const calculation_list = this.volatile_list.slice(0).concat(this.dirty_list);
1001
-
1002
- // we do this using the local function so we can trace back arrays
956
+ // we do this using the local function so we can trace back arrays.
957
+ // be sure to do this _before_ checking spills
1003
958
 
1004
959
  for (const vertex of this.volatile_list) {
1005
960
  this.SetVertexDirty(vertex as SpreadsheetVertex);
1006
961
  }
1007
- // const calculation_list = this.dirty_list.slice(0);
962
+
963
+
964
+ ////////////////////////////////////////
965
+
966
+ // (moving this up so it comes before we slice the dirty list)
967
+
968
+ // the problem with flushing all the spills here
969
+ // is that if we're not calculating a range that
970
+ // intersects with the spill, it will disappear.
971
+ // options are (1) to dirty the spill root, so it
972
+ // calculates, or (2) to check for intersection.
973
+
974
+ // checking for intersection might work because
975
+ // we should have vertices and they should be marked
976
+ // as dirty...
977
+
978
+ // eh that's not going to work, because edges point
979
+ // the wrong way. if you edit a cell within a spill
980
+ // range, it won't dirty the spill source because
981
+ // the edge goes from spill source -> spill cell.
982
+
983
+ // we could create a special leaf vertex to watch the
984
+ // range. or we could just check here. vertices is
985
+ // more elegant (and more memory), this is clumsier (and
986
+ // more calc).
987
+
988
+ this.spill_data = this.spill_data.filter(({area, vertex}) => {
989
+ if (vertex.dirty) {
990
+ vertex.Reset();
991
+ const cells = area.start.sheet_id ? this.model.sheets.Find(area.start.sheet_id)?.cells : undefined;
992
+ if (cells) {
993
+ for (const {cell, row, column} of cells.IterateRC(new Area(area.start, area.end))) {
994
+ if (cell.spill) {
995
+ cell.spill = undefined;
996
+ if (typeof cell.value === 'undefined') {
997
+ cell.Reset();
998
+ }
999
+ }
1000
+
1001
+ // this is necessary for non-head cells in case the cell has deps
1002
+ this.SetDirty({row, column, sheet_id: area.start.sheet_id});
1003
+
1004
+ }
1005
+ // this.SetDirty(area.start);
1006
+ }
1007
+ return false; // drop
1008
+ }
1009
+ return true; // keep
1010
+ });
1011
+
1012
+ /*
1013
+ this.spills = this.spills.filter(spill => {
1014
+ let dirty = false;
1015
+ for (const vertex of this.IterateVertices(spill)) {
1016
+ if (vertex.dirty) {
1017
+ console.info("spill is dirty (it)");
1018
+ dirty = true;
1019
+ break;
1020
+ }
1021
+ }
1022
+
1023
+ if (dirty) {
1024
+ const cells = spill.start.sheet_id ? this.model.sheets.Find(spill.start.sheet_id)?.cells : undefined;
1025
+ if (cells) {
1026
+ for (const {cell, row, column} of cells.IterateRC(new Area(spill.start, spill.end))) {
1027
+ if (cell.spill) {
1028
+ cell.spill = undefined;
1029
+ if (typeof cell.value === 'undefined') {
1030
+ cell.Reset();
1031
+ }
1032
+ else {
1033
+ this.SetDirty({row, column, sheet_id: spill.start.sheet_id})
1034
+ }
1035
+ }
1036
+ }
1037
+ }
1038
+ return false; // drop
1039
+ }
1040
+
1041
+ return true; // keep
1042
+ });
1043
+ */
1044
+
1045
+ //////////////////////////////////////////
1046
+
1008
1047
  this.calculation_list = this.dirty_list.slice(0);
1009
1048
 
1049
+ // console.info("CL", this.calculation_list);
1050
+
1010
1051
  this.volatile_list = [];
1011
1052
  this.dirty_list = [];
1012
1053
 
@@ -1018,6 +1059,7 @@ export abstract class Graph implements GraphCallbacks {
1018
1059
 
1019
1060
  }
1020
1061
 
1062
+
1021
1063
  // console.info("CL", calculation_list)
1022
1064
 
1023
1065
  // recalculate everything that's dirty. FIXME: optimize path
@@ -1036,9 +1078,8 @@ export abstract class Graph implements GraphCallbacks {
1036
1078
  }
1037
1079
 
1038
1080
  public abstract CalculationCallback(vertex: SpreadsheetVertexBase): CalculationResult;
1039
-
1040
1081
  public abstract SpreadCallback(vertex: SpreadsheetVertexBase, value: UnionValue): void;
1041
-
1082
+ public abstract SpillCallback(vertex: SpreadsheetVertexBase, value: UnionValue): void;
1042
1083
  protected abstract CheckVolatile(vertex: SpreadsheetVertex): boolean;
1043
1084
 
1044
1085
  }
@@ -219,20 +219,49 @@ export class SpreadsheetVertex extends SpreadsheetVertexBase {
219
219
  }
220
220
  else if (this.reference.type === ValueType.formula) {
221
221
 
222
- // data should now be clean when it gets here (famous last words)
222
+ // adding check for spill, not withstanding the below
223
223
 
224
- // we're now sometimes getting 0-length arrays here. that's a
225
- // function of our new polynomial methods, BUT, we should probably
226
- // handle it properly regardless.
224
+ if (this.result.type === ValueType.array) {
227
225
 
228
- const single = (this.result.type === ValueType.array) ? this.result.value[0][0] : this.result;
226
+ // note array of length 1 should not trigger spill behavior
227
+ // (moved to callback method)
229
228
 
230
- // we are using object type in the returned value for sparklines...
231
- // so we can't drop it here. we could change rendering though. or
232
- // whitelist types. or blacklist types. or something.
229
+ const recalc = graph.SpillCallback.call(graph, this, this.result);
230
+ if (recalc) {
231
+ // console.info("RC", recalc);
232
+ for (const entry of (recalc as SpreadsheetVertex[])) {
233
233
 
234
- this.reference.SetCalculatedValue(single.value as CellValue, single.type);
234
+ // will this work properly with loops? (...)
235
235
 
236
+ for (const edge of entry.edges_out) {
237
+ (edge as SpreadsheetVertex).dirty = true;
238
+ (edge as SpreadsheetVertex).Calculate(graph);
239
+ }
240
+
241
+ }
242
+ }
243
+
244
+ }
245
+ else {
246
+
247
+ // ---
248
+
249
+
250
+ // data should now be clean when it gets here (famous last words)
251
+
252
+ // we're now sometimes getting 0-length arrays here. that's a
253
+ // function of our new polynomial methods, BUT, we should probably
254
+ // handle it properly regardless.
255
+
256
+ // neven // const single = (this.result.type === ValueType.array) ? this.result.value[0][0] : this.result;
257
+
258
+ // we are using object type in the returned value for sparklines...
259
+ // so we can't drop it here. we could change rendering though. or
260
+ // whitelist types. or blacklist types. or something.
261
+
262
+ this.reference.SetCalculatedValue(this.result.value as CellValue, this.result.type);
263
+
264
+ }
236
265
  }
237
266
 
238
267
  }
@@ -34,6 +34,7 @@ export interface CalculationResult {
34
34
  export interface GraphCallbacks {
35
35
  CalculationCallback: (vertex: SpreadsheetVertexBase) => CalculationResult;
36
36
  SpreadCallback: (vertex: SpreadsheetVertexBase, value: UnionValue) => void;
37
+ SpillCallback: (vertex: SpreadsheetVertexBase, value: UnionValue) => Vertex[]|void;
37
38
  volatile_list: SpreadsheetVertexBase[];
38
39
  calculation_list: SpreadsheetVertexBase[];
39
40
  }
@@ -31,7 +31,7 @@ import { ValueType, GetValueType, Area } from 'treb-base-types';
31
31
  import type { Parser, ExpressionUnit, UnitBinary, UnitIdentifier,
32
32
  UnitGroup, UnitUnary, UnitAddress, UnitRange, UnitCall, UnitDimensionedQuantity, UnitStructuredReference } from 'treb-parser';
33
33
  import type { DataModel, MacroFunction, Sheet } from 'treb-data-model';
34
- import { NameError, ReferenceError, ExpressionError, UnknownError } from './function-error';
34
+ import { NameError, ReferenceError, ExpressionError, UnknownError, SpillError } from './function-error';
35
35
  import { ReturnType } from './descriptors';
36
36
 
37
37
  import * as Primitives from './primitives';
@@ -136,7 +136,6 @@ export class ExpressionCalculator {
136
136
  }
137
137
  }
138
138
 
139
- // const cells = this.cells_map[expr.sheet_id];
140
139
  const cells = this.data_model.sheets.Find(expr.sheet_id)?.cells;
141
140
 
142
141
  if (!cells) {
@@ -156,6 +155,12 @@ export class ExpressionCalculator {
156
155
  };
157
156
  }
158
157
 
158
+ if (expr.spill && cell.spill && cell.spill.start.row === expr.row && cell.spill.start.column === expr.column) {
159
+ return () => {
160
+ return cell.spill ? cells.GetRange4(cell.spill.start, cell.spill.end, true) || ReferenceError() : SpillError();
161
+ }
162
+ }
163
+
159
164
  // close
160
165
  return () => cell.GetValue4();
161
166
 
@@ -34,6 +34,7 @@ export enum ErrorType {
34
34
  Div0 = 'DIV/0',
35
35
  NA = 'N/A',
36
36
  Loop = 'LOOP', // circular reference
37
+ Spill = 'SPILL',
37
38
  }
38
39
 
39
40
  export interface FunctionError {
@@ -74,6 +75,10 @@ export const NameError = (): UnionValue => {
74
75
  return { type: ValueType.error, value: ErrorType.Name };
75
76
  };
76
77
 
78
+ export const SpillError = (): UnionValue => {
79
+ return { type: ValueType.error, value: ErrorType.Spill };
80
+ };
81
+
77
82
  export const UnknownError = (): UnionValue => {
78
83
  return { type: ValueType.error, value: ErrorType.Unknown };
79
84
  };
@@ -94,6 +99,7 @@ export const IsError = (test: unknown): test is FunctionError => {
94
99
  (test as FunctionError).error === ErrorType.Unknown ||
95
100
  (test as FunctionError).error === ErrorType.NotImpl ||
96
101
  (test as FunctionError).error === ErrorType.Value ||
102
+ (test as FunctionError).error === ErrorType.Spill ||
97
103
  (test as FunctionError).error === ErrorType.Div0
98
104
  );
99
105
  };
@@ -20,7 +20,7 @@
20
20
  */
21
21
 
22
22
  import { type UnionValue, ValueType } from 'treb-base-types';
23
- import type { FunctionMap } from 'treb-calculator/src/descriptors';
23
+ import type { CompositeFunctionDescriptor } from 'treb-calculator/src/descriptors';
24
24
 
25
25
  /**
26
26
  * function returns its arguments
@@ -35,10 +35,25 @@ const Identity = (...args: unknown[]): UnionValue => {
35
35
  };
36
36
  };
37
37
 
38
+ export type ChartFunction
39
+ = 'Bar.Chart'
40
+ | 'Line.Chart'
41
+ | 'Area.Chart'
42
+ | 'Column.Chart'
43
+ | 'Bubble.Chart'
44
+ | 'Donut.Chart'
45
+ | 'Pie.Chart'
46
+ | 'Scatter.Line'
47
+ | 'Scatter.Plot'
48
+ | 'Box.Plot'
49
+ ;
50
+
51
+ type SupportFunction = 'Group'|'Series';
52
+
38
53
  /**
39
54
  * chart functions for registration
40
55
  */
41
- export const ChartFunctions: FunctionMap = {
56
+ export const ChartFunctions: Record<ChartFunction|SupportFunction, CompositeFunctionDescriptor> = {
42
57
 
43
58
  /* new: also helper */
44
59
  Group: {
@@ -52,7 +67,8 @@ export const ChartFunctions: FunctionMap = {
52
67
  key: 'group',
53
68
  };
54
69
 
55
- }
70
+ },
71
+ category: ['grouping'],
56
72
  },
57
73
 
58
74
  /**
@@ -81,8 +97,8 @@ export const ChartFunctions: FunctionMap = {
81
97
  value: args,
82
98
  key: 'series',
83
99
  };
84
- }
85
-
100
+ },
101
+ category: ['chart functions'],
86
102
  },
87
103
 
88
104
  'Bar.Chart': {
@@ -92,6 +108,7 @@ export const ChartFunctions: FunctionMap = {
92
108
  { name: 'ChartTitle' },
93
109
  ],
94
110
  fn: Identity,
111
+ category: ['chart functions'],
95
112
  },
96
113
 
97
114
  'Line.Chart': {
@@ -101,6 +118,7 @@ export const ChartFunctions: FunctionMap = {
101
118
  { name: 'ChartTitle' },
102
119
  ],
103
120
  fn: Identity,
121
+ category: ['chart functions'],
104
122
  },
105
123
 
106
124
  'Area.Chart': {
@@ -110,6 +128,7 @@ export const ChartFunctions: FunctionMap = {
110
128
  { name: 'ChartTitle' },
111
129
  ],
112
130
  fn: Identity,
131
+ category: ['chart functions'],
113
132
  },
114
133
 
115
134
  'Pie.Chart': {
@@ -121,6 +140,7 @@ export const ChartFunctions: FunctionMap = {
121
140
  { name: 'Label' },
122
141
  ],
123
142
  fn: Identity,
143
+ category: ['chart functions'],
124
144
  },
125
145
 
126
146
  'Donut.Chart': {
@@ -132,6 +152,7 @@ export const ChartFunctions: FunctionMap = {
132
152
  { name: 'Label' },
133
153
  ],
134
154
  fn: Identity,
155
+ category: ['chart functions'],
135
156
  },
136
157
 
137
158
  'Scatter.Line': {
@@ -140,6 +161,7 @@ export const ChartFunctions: FunctionMap = {
140
161
  { name: 'ChartTitle' },
141
162
  ],
142
163
  fn: Identity,
164
+ category: ['chart functions'],
143
165
  },
144
166
 
145
167
  'Scatter.Plot': {
@@ -148,6 +170,7 @@ export const ChartFunctions: FunctionMap = {
148
170
  { name: 'ChartTitle' },
149
171
  ],
150
172
  fn: Identity,
173
+ category: ['chart functions'],
151
174
  },
152
175
 
153
176
  'Column.Chart': {
@@ -157,6 +180,7 @@ export const ChartFunctions: FunctionMap = {
157
180
  { name: 'Chart Title' },
158
181
  ],
159
182
  fn: Identity,
183
+ category: ['chart functions'],
160
184
  },
161
185
 
162
186
  'Bubble.Chart': {
@@ -165,6 +189,16 @@ export const ChartFunctions: FunctionMap = {
165
189
  { name: 'Chart Title' },
166
190
  ],
167
191
  fn: Identity,
192
+ category: ['chart functions'],
193
+ },
194
+
195
+ 'Box.Plot': {
196
+ arguments: [
197
+ { name: 'Data', metadata: true, },
198
+ { name: 'Chart Title' },
199
+ ],
200
+ fn: Identity,
201
+ category: ['chart functions'],
168
202
  },
169
203
 
170
204
  };
@@ -199,6 +199,28 @@ export interface LineBaseData extends ChartDataBaseType {
199
199
  smooth?: boolean;
200
200
  }
201
201
 
202
+ export interface BoxPlotData extends ChartDataBaseType {
203
+ type: 'box';
204
+ series: SeriesType[];
205
+ x_labels?: string[];
206
+ series_names?: string[];
207
+ y_labels?: string[];
208
+ scale: RangeScale;
209
+ max_n: number,
210
+
211
+ data: {
212
+ data: number[],
213
+ quartiles: [number, number, number],
214
+ whiskers: [number, number],
215
+ iqr: number,
216
+ n: number,
217
+ min: number,
218
+ max: number,
219
+ mean: number,
220
+ }[];
221
+
222
+ }
223
+
202
224
  export interface LineData extends LineBaseData {
203
225
  type: 'line';
204
226
  }
@@ -253,6 +275,7 @@ export type ChartData
253
275
  | ColumnData
254
276
  | BarData
255
277
  | BubbleChartData
278
+ | BoxPlotData
256
279
  ;
257
280
 
258
281
  export enum LegendLayout {