@trebco/treb 32.1.1 → 32.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,143 @@
1
+ /**
2
+ * functions and constants for gamma distribution. we're
3
+ * returning false instead of throwing so we can return
4
+ * a spreadsheet-style eror. TODO: optional?
5
+ */
6
+
7
+ const max_iterations = 1000;
8
+ const epsilon = 3.0e-9;
9
+ const min_value = 1.0e-30;
10
+
11
+ /**
12
+ * faster approximation for real numbers
13
+ */
14
+ export const gamma_ln = (z: number): number => {
15
+
16
+ let x = z - 1.0;
17
+ let y = x + 5.5;
18
+ y -= (x + 0.5) * Math.log(y);
19
+ let a = 1.0;
20
+
21
+ const coefficients = [
22
+ 76.18009173,
23
+ -86.50532033,
24
+ 24.01409822,
25
+ -1.231739516,
26
+ 0.120858003e-2,
27
+ -0.536382e-5,
28
+ ];
29
+
30
+ for (const coeff of coefficients) {
31
+ a += coeff / (++x);
32
+ }
33
+
34
+ return (-y + Math.log(2.50662827465 * a));
35
+
36
+ };
37
+
38
+ /**
39
+ * series representation
40
+ */
41
+ export const gamma_series = (a: number, x: number): number|false => {
42
+
43
+ const gamma_ln_a = gamma_ln(a);
44
+
45
+ if (x === 0) {
46
+ return 0;
47
+ }
48
+
49
+ let ap = a;
50
+ let sum = 1.0 / a;
51
+ let del = sum;
52
+ for (let n = 1; n < max_iterations; n++) {
53
+ ++ap;
54
+ del *= x / ap;
55
+ sum += del;
56
+ if (Math.abs(del) < Math.abs(sum) * epsilon) {
57
+ return sum * Math.exp(-x + a * Math.log(x) - (gamma_ln_a));
58
+ }
59
+ }
60
+
61
+ // throw new Error('too many iterations');
62
+ console.error('too many iterations');
63
+ return false;
64
+
65
+ };
66
+
67
+ /**
68
+ * continued fraction
69
+ */
70
+ export const gamma_cf = (a: number, x: number): number|false => {
71
+
72
+ const gamma_ln_a = gamma_ln(a);
73
+
74
+ let b = x + 1.0 - a;
75
+ let c = 1.0 / min_value;
76
+ let d = 1.0 / b;
77
+ let h = d;
78
+
79
+ for (let i = 1; i <= max_iterations; i++) {
80
+
81
+ const an = -i * (i - a); b += 2.0; d = an * d + b;
82
+
83
+ if (Math.abs(d) < min_value) d = min_value;
84
+ c = b + an / c;
85
+
86
+ if (Math.abs(c) < min_value) c = min_value;
87
+
88
+ d = 1.0 / d;
89
+ const del = d * c;
90
+ h *= del;
91
+
92
+ if (Math.abs(del - 1.0) < epsilon) {
93
+ return Math.exp(-x + a * Math.log(x) - (gamma_ln_a)) * h;
94
+ }
95
+
96
+ }
97
+
98
+ // throw new Error('too many iterations');
99
+ console.error('too many iterations');
100
+ return false;
101
+
102
+ };
103
+
104
+ /**
105
+ * regularized gamma function; CDF of gamma distribution with scale 1
106
+ */
107
+ export const gamma_p = (a: number, x: number) => {
108
+
109
+ if (x < 0.0 || a <= 0.0) {
110
+ // throw new Error('invalid parameter');
111
+ console.error('invalid parameter');
112
+ return false;
113
+ }
114
+ if (x < (a + 1.0)) {
115
+ return gamma_series(a, x);
116
+ }
117
+ else {
118
+ const cf = gamma_cf(a, x);
119
+ if (cf === false) { return false; }
120
+ return 1 - cf;
121
+ }
122
+
123
+ }
124
+
125
+ /**
126
+ * regularized gamma function
127
+ */
128
+ export const gamma_q = (a: number, x: number) => {
129
+
130
+ if (x < 0.0 || a <= 0.0) {
131
+ // throw new Error('invalid parameter');
132
+ console.error('invalid parameter');
133
+ return false;
134
+ }
135
+ if (x < (a + 1.0)) {
136
+ const series = gamma_series(a, x);
137
+ if (series === false) { return false; }
138
+ return 1 - series;
139
+ }
140
+ else {
141
+ return gamma_cf(a, x);
142
+ }
143
+ }
@@ -26,6 +26,7 @@ import { type Complex, type UnionValue, ValueType, type CellValue, ComplexOrReal
26
26
  import * as ComplexMath from '../complex-math';
27
27
 
28
28
  import { BetaCDF, BetaPDF, InverseBeta, LnGamma } from './beta';
29
+ import { gamma_p } from './gamma';
29
30
 
30
31
  /** error function (for gaussian distribution) */
31
32
  const erf = (x: number): number => {
@@ -172,6 +173,46 @@ const Gamma = (z: Complex): Complex => {
172
173
 
173
174
  };
174
175
 
176
+ const GammaPDF = (x: number, alpha: number, beta: number): number => {
177
+ const gamma_alpha = Gamma({real: alpha, imaginary: 0}).real;
178
+ return (Math.pow(x, alpha - 1) * Math.exp(-x / beta)) / (gamma_alpha * Math.pow(beta, alpha));
179
+ };
180
+
181
+ /** bisection */
182
+ const InverseGamma = (p: number, alpha: number, beta: number): number|false => {
183
+
184
+ let lower = 0;
185
+ let upper = alpha * 10;
186
+
187
+ const tolerance = 1e-6;
188
+
189
+ let iterations = 0;
190
+
191
+ while (upper - lower > tolerance) {
192
+ iterations++;
193
+
194
+ const mid = (upper + lower) / 2;
195
+
196
+ const f_lower = gamma_p(alpha, lower/beta);
197
+ const f_mid = gamma_p(alpha, mid/beta);
198
+
199
+ if (f_lower === false || f_mid === false) {
200
+ return false;
201
+ }
202
+
203
+ if ((f_mid - p) * (f_lower - p) < 0) {
204
+ upper = mid;
205
+ }
206
+ else {
207
+ lower = mid;
208
+ }
209
+
210
+ }
211
+
212
+ return (lower + upper) / 2;
213
+
214
+ };
215
+
175
216
  export const Variance = (data: number[], sample = false) => {
176
217
 
177
218
  const len = data.length;
@@ -743,6 +784,53 @@ export const StatisticsFunctionLibrary: FunctionMap = {
743
784
  },
744
785
  },
745
786
 
787
+ 'Gamma.Inv': {
788
+ description: 'Returns the inverse of the gamma distribution',
789
+ arguments: [
790
+ {name: 'probability', unroll: true},
791
+ {name: 'alpha', },
792
+ {name: 'beta', },
793
+ ],
794
+ fn: (p: number, alpha: number, beta: number) => {
795
+
796
+ if (p < 0 || p > 1) {
797
+ return ArgumentError();
798
+ }
799
+
800
+ const value = InverseGamma(p, alpha, beta);
801
+
802
+ if (value === false) {
803
+ return ValueError();
804
+ }
805
+
806
+ return {
807
+ type: ValueType.number,
808
+ value,
809
+ }
810
+ },
811
+ },
812
+
813
+ 'Gamma.Dist': {
814
+ fn: (x: number, alpha: number, beta: number, cumulative?: boolean) => {
815
+
816
+ if (x < 0 || alpha <= 0 || beta <= 0) {
817
+ return ArgumentError();
818
+ }
819
+
820
+ const value = cumulative ? gamma_p(alpha, x/beta) : GammaPDF(x, alpha, beta);
821
+
822
+ if (value === false) {
823
+ return ValueError();
824
+ }
825
+
826
+ return {
827
+ type: ValueType.number,
828
+ value,
829
+ };
830
+
831
+ }
832
+ },
833
+
746
834
  GammaLn: {
747
835
  description: 'Returns the natural log of the gamma function',
748
836
  arguments: [{ name: 'value', boxed: true, unroll: true }],
@@ -2556,7 +2556,7 @@ export class Sheet {
2556
2556
  cell_reference_map[c] = [];
2557
2557
  for (let r = 0; r < column.length; r++) {
2558
2558
  if (column[r]) {
2559
- const style_as_json = JSON.stringify(column[r]);
2559
+ const style_as_json = Style.Serialize(column[r]); // JSON.stringify(column[r]);
2560
2560
  if (style_as_json !== empty_json) {
2561
2561
  let reference_index = cell_style_map[style_as_json];
2562
2562
  if (typeof reference_index !== 'number') {
@@ -2579,7 +2579,7 @@ export class Sheet {
2579
2579
  */
2580
2580
  const StyleToRef = (style: CellStyle) => {
2581
2581
 
2582
- const style_as_json = JSON.stringify(style);
2582
+ const style_as_json = Style.Serialize(style); // JSON.stringify(style);
2583
2583
  if (style_as_json === empty_json) {
2584
2584
  return 0;
2585
2585
  }
@@ -2242,8 +2242,11 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
2242
2242
 
2243
2243
  // console.info({events});
2244
2244
 
2245
+ // FIXME: composite? (...)
2246
+
2245
2247
  for (const event of events) {
2246
2248
  switch (event.type) {
2249
+ case 'composite':
2247
2250
  case 'data':
2248
2251
  recalc = true;
2249
2252
  break;
@@ -4000,8 +4003,13 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
4000
4003
  // API v1 OK
4001
4004
 
4002
4005
  let area: Area | undefined;
4003
- if (event && event.type === 'data' && event.area) {
4004
- area = event.area;
4006
+ switch (event?.type) {
4007
+ case 'data':
4008
+ area = event.area;
4009
+ break;
4010
+ case 'composite':
4011
+ area = event.data_area;
4012
+ break;
4005
4013
  }
4006
4014
 
4007
4015
  this.calculator.Calculate(area);
@@ -1036,11 +1036,25 @@ export class Editor<E = FormulaEditorEvent> extends EventSource<E|FormulaEditorE
1036
1036
  if (matcher) {
1037
1037
  Promise.resolve().then(() => {
1038
1038
  const exec_result = matcher.Exec({ text, cursor: substring_end.length });
1039
+
1040
+ // fix behavior around "f16round", which is a function we accidentally
1041
+ // inherit from Math in Safari and Firefox. it breaks arrow-selection
1042
+ // because it pops up an AC window. we don't want to break AC behavior
1043
+ // on tooltips, though, so we still want to call AC methods. just block
1044
+ // the completion list.
1045
+
1046
+ if (this.selecting && exec_result.completions?.length) {
1047
+ // console.info("Blocking completion list", JSON.stringify(exec_result.completions));
1048
+ exec_result.completions = undefined;
1049
+ }
1050
+
1039
1051
  const node =
1040
1052
  this.NodeAtIndex(exec_result.completions?.length ?
1041
1053
  (exec_result.position || 0) :
1042
1054
  (exec_result.function_position || 0));
1055
+
1043
1056
  this.Autocomplete(exec_result, node);
1057
+
1044
1058
  });
1045
1059
  }
1046
1060
 
@@ -2041,6 +2041,7 @@ export class Grid extends GridBase {
2041
2041
  command.expression = parse_result.expression;
2042
2042
  }
2043
2043
  else {
2044
+ console.info({expression, parse_result});
2044
2045
  throw new Error('invalid expression');
2045
2046
  }
2046
2047
  }
@@ -4554,20 +4555,8 @@ export class Grid extends GridBase {
4554
4555
  if (selecting_argument) {
4555
4556
  this.UpdateSelectedArgument(selection);
4556
4557
  }
4557
- else if (!selection.empty && !selection.area.entire_sheet) {
4558
- if (selection.area.entire_column) {
4559
- this.UpdateAddressLabel(undefined, selection.area.columns + 'C');
4560
- }
4561
- else if (selection.area.entire_row) {
4562
- this.UpdateAddressLabel(undefined, selection.area.rows + 'R');
4563
- }
4564
- else if (selection.area.count > 1) {
4565
- this.UpdateAddressLabel(undefined, selection.area.rows + 'R x ' +
4566
- selection.area.columns + 'C');
4567
- }
4568
- else {
4569
- this.UpdateAddressLabel(selection);
4570
- }
4558
+ else {
4559
+ this.UpdateAddressLabelArea(selection); // 3R x 2C
4571
4560
  }
4572
4561
  }
4573
4562
  }, () => {
@@ -5114,6 +5103,9 @@ export class Grid extends GridBase {
5114
5103
 
5115
5104
  if (!selection.empty && (delta.columns || delta.rows)) {
5116
5105
  if (this.BlockSelection(selection, !!event.shiftKey, delta.columns, delta.rows)) {
5106
+ if (event.shiftKey && !selecting_argument) {
5107
+ this.UpdateAddressLabelArea(selection);
5108
+ }
5117
5109
  return;
5118
5110
  }
5119
5111
  }
@@ -5280,6 +5272,9 @@ export class Grid extends GridBase {
5280
5272
 
5281
5273
  if (delta.rows || delta.columns) {
5282
5274
  this.AdvanceSelection(delta, selection, within_selection, expand_selection, !editor_open);
5275
+ if (event.shiftKey && !selecting_argument) {
5276
+ this.UpdateAddressLabelArea(selection);
5277
+ }
5283
5278
  }
5284
5279
 
5285
5280
  }
@@ -5660,7 +5655,13 @@ export class Grid extends GridBase {
5660
5655
  }
5661
5656
  }
5662
5657
  }
5663
- if (formula_parse_result.dependencies) {
5658
+ if (formula_parse_result.expression) {
5659
+
5660
+ // we were previously looking at address arguments, and ignoring
5661
+ // ranges. this leads to inconsistent behavior. we'll now look at
5662
+ // ranges when inferring number format.
5663
+
5664
+ // there was also an issue with using the correct sheet, also fixed.
5664
5665
 
5665
5666
  // this was set up to just use the first format we found.
5666
5667
  // updating to change priority -- if the first one is a
@@ -5669,14 +5670,10 @@ export class Grid extends GridBase {
5669
5670
 
5670
5671
  let found_number_format: string|undefined = undefined;
5671
5672
 
5672
- const list = formula_parse_result.dependencies;
5673
- for (const key of Object.keys(list.addresses)) {
5674
- const address = list.addresses[key];
5675
- if (this.active_sheet.HasCellStyle({ ...address })) {
5676
-
5677
- // FIXME: this should not be active_sheet
5678
-
5679
- const test = this.active_sheet.CellData({ ...address });
5673
+ /** returns true if we've found a non-% number format */
5674
+ const Check = (address: ICellAddress, sheet = this.active_sheet) => {
5675
+ if (sheet.HasCellStyle({...address})) {
5676
+ const test = sheet.CellData({ ...address });
5680
5677
  if (test.style && test.style.number_format) {
5681
5678
  if (!found_number_format || /%/.test(found_number_format)) {
5682
5679
 
@@ -5686,13 +5683,43 @@ export class Grid extends GridBase {
5686
5683
 
5687
5684
  found_number_format = NumberFormatCache.Translate(test.style.number_format);
5688
5685
  if (!/%/.test(found_number_format)) {
5689
- break;
5686
+ return true;
5690
5687
  }
5691
5688
 
5692
5689
  }
5693
5690
  }
5694
5691
  }
5695
- }
5692
+ return false;
5693
+ };
5694
+
5695
+ let finished = false;
5696
+ let sheet: Sheet|undefined = this.active_sheet;
5697
+
5698
+ this.parser.Walk(formula_parse_result.expression, unit => {
5699
+
5700
+ if (finished) {
5701
+ return false;
5702
+ }
5703
+
5704
+ switch (unit.type) {
5705
+ case 'address':
5706
+ this.model.ResolveSheetID(unit);
5707
+ sheet = this.model.sheets.Find(unit.sheet_id || 0);
5708
+ finished = finished || Check(unit, sheet);
5709
+ break;
5710
+
5711
+ case 'range':
5712
+ this.model.ResolveSheetID(unit);
5713
+ sheet = this.model.sheets.Find(unit.start.sheet_id || 0);
5714
+ for (const address of new Area(unit.start, unit.end)) {
5715
+ finished = finished || Check(address, sheet);
5716
+ if (finished) { break; }
5717
+ }
5718
+ break;
5719
+ }
5720
+ return !finished;
5721
+
5722
+ });
5696
5723
 
5697
5724
  if (found_number_format) {
5698
5725
 
@@ -7025,6 +7052,24 @@ export class Grid extends GridBase {
7025
7052
  }
7026
7053
  }
7027
7054
 
7055
+ private UpdateAddressLabelArea(selection: GridSelection) {
7056
+ if (!selection.empty && !selection.area.entire_sheet) {
7057
+ if (selection.area.entire_column) {
7058
+ this.UpdateAddressLabel(undefined, selection.area.columns + 'C');
7059
+ }
7060
+ else if (selection.area.entire_row) {
7061
+ this.UpdateAddressLabel(undefined, selection.area.rows + 'R');
7062
+ }
7063
+ else if (selection.area.count > 1) {
7064
+ this.UpdateAddressLabel(undefined, selection.area.rows + 'R x ' +
7065
+ selection.area.columns + 'C');
7066
+ }
7067
+ else {
7068
+ this.UpdateAddressLabel(selection);
7069
+ }
7070
+ }
7071
+ }
7072
+
7028
7073
  private UpdateAddressLabel(selection = this.primary_selection, text?: string) {
7029
7074
 
7030
7075
  if (!this.formula_bar) { return; }
@@ -445,6 +445,9 @@ export interface RenderOptions {
445
445
  /** base for offsetting relative R1C1 addresses */
446
446
  r1c1_base?: UnitAddress;
447
447
 
448
+ /** force addresses to be relative */
449
+ r1c1_force_relative?: boolean;
450
+
448
451
  /** if we're just translating, don't have to render addresses */
449
452
  pass_through_addresses?: boolean;
450
453
 
@@ -602,14 +602,15 @@ export class Parser {
602
602
  if (options.pass_through_addresses) {
603
603
  return unit.label;
604
604
  }
605
- return options.r1c1 ? this.R1C1Label(unit, options.r1c1_base) : this.AddressLabel(unit, offset);
605
+ return options.r1c1 ? this.R1C1Label(unit, options.r1c1_base, options.r1c1_force_relative) : this.AddressLabel(unit, offset);
606
606
 
607
607
  case 'range':
608
608
  if (options.pass_through_addresses) {
609
609
  return unit.label;
610
610
  }
611
611
  return options.r1c1 ?
612
- this.R1C1Label(unit.start, options.r1c1_base) + ':' + this.R1C1Label(unit.end, options.r1c1_base) :
612
+ this.R1C1Label(unit.start, options.r1c1_base, options.r1c1_force_relative) + ':' +
613
+ this.R1C1Label(unit.end, options.r1c1_base, options.r1c1_force_relative) :
613
614
  this.AddressLabel(unit.start, offset) + ':' + this.AddressLabel(unit.end, offset);
614
615
 
615
616
  case 'missing':
@@ -955,10 +956,14 @@ export class Parser {
955
956
 
956
957
  /**
957
958
  * generates absolute or relative R1C1 address
959
+ *
960
+ * FIXME: not supporting relative (offset) addresses atm? I'd like to
961
+ * change this but I don't want to break anything...
958
962
  */
959
963
  protected R1C1Label(
960
964
  address: UnitAddress,
961
965
  base?: UnitAddress,
966
+ force_relative = false,
962
967
  ): string {
963
968
 
964
969
  let label = '';
@@ -968,8 +973,25 @@ export class Parser {
968
973
  '\'' + address.sheet + '\'' : address.sheet) + '!';
969
974
  }
970
975
 
971
- const row = address.offset_row ? `[${address.row}]` : address.row + 1;
972
- const column = address.offset_column ? `[${address.column}]` : address.column + 1;
976
+ let row = '';
977
+ let column = '';
978
+
979
+ if (force_relative && base) {
980
+ const delta_row = address.row - base.row;
981
+ const delta_column = address.column - base.column;
982
+
983
+ if (delta_row) {
984
+ row = `[${delta_row}]`;
985
+ }
986
+ if (delta_column) {
987
+ column = `[${delta_column}]`;
988
+ }
989
+
990
+ }
991
+ else {
992
+ row = address.offset_row ? `[${address.row}]` : (address.row + 1).toString();
993
+ column = address.offset_column ? `[${address.column}]` : (address.column + 1).toString();
994
+ }
973
995
 
974
996
  /*
975
997
  const row = (address.absolute_row || !base) ? (address.row + 1).toString() : `[${address.row - base.row}]`;
@@ -1063,14 +1085,18 @@ export class Parser {
1063
1085
 
1064
1086
  // skip nulls
1065
1087
 
1066
- if (group) {
1088
+ // ...don't skip nulls? don't know what the rationale was
1089
+ // but for implicit calls we will need to support empty arguments
1090
+
1091
+ // if (group) {
1067
1092
  stream.push({
1068
1093
  type: 'group',
1069
1094
  id: this.id_counter++,
1070
- elements: [group],
1095
+ elements: group? [group] : [],
1071
1096
  explicit: true,
1072
1097
  });
1073
- }
1098
+ //}
1099
+
1074
1100
  }
1075
1101
  else {
1076
1102
  // this can probably move to PNext? except for the test
@@ -1,7 +0,0 @@
1
- {
2
- "extends": "../tsproject.json",
3
- "include": [
4
- "../treb-base-types/**/*.ts"
5
- ]
6
- }
7
-