@trebco/treb 30.2.14 → 30.6.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.
- package/dist/treb-export-worker.mjs +2 -0
- package/dist/treb-spreadsheet.mjs +14 -14
- package/dist/treb.d.ts +1 -1
- package/{esbuild-custom-element.mjs → esbuild-composite.mjs} +6 -23
- package/esbuild-utils.mjs +28 -3
- package/eslint.config.js +2 -1
- package/package.json +7 -8
- package/treb-base-types/src/cells.ts +1 -1
- package/treb-calculator/src/calculator.ts +108 -69
- package/treb-calculator/src/complex-math.ts +48 -0
- package/treb-calculator/src/dag/graph.ts +4 -1
- package/treb-calculator/src/functions/base-functions.ts +106 -34
- package/treb-calculator/src/functions/date-utils.ts +64 -0
- package/treb-calculator/src/functions/finance-functions.ts +62 -0
- package/treb-calculator/src/functions/statistics-functions.ts +172 -0
- package/treb-calculator/src/functions/text-functions.ts +25 -0
- package/treb-calculator/src/utilities.ts +18 -4
- package/treb-charts/src/chart-utils.ts +42 -27
- package/treb-data-model/src/types.ts +10 -0
- package/treb-embed/modern.tsconfig.json +0 -1
- package/treb-embed/src/embedded-spreadsheet.ts +26 -51
- package/treb-embed/src/export-worker.ts +44 -0
- package/treb-embed/style/dark-theme.scss +1 -1
- package/treb-export/src/export.ts +22 -1
- package/treb-export/src/{export-worker/export-worker.ts → index.worker.ts} +2 -2
- package/treb-grid/src/editors/overlay_editor.ts +1 -1
- package/treb-grid/src/types/grid.ts +9 -1
- package/dist/treb-spreadsheet-light.mjs +0 -16
- package/treb-export/src/export-worker/index.worker.ts +0 -32
package/dist/treb.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import * as esbuild from 'esbuild';
|
|
6
6
|
|
|
7
|
-
import { SassPlugin, WorkerPlugin, NotifyPlugin, HTMLPlugin } from './esbuild-utils.mjs';
|
|
7
|
+
import { SassPlugin, WorkerPlugin, NotifyPlugin, HTMLPlugin, RewriteIgnoredImports } from './esbuild-utils.mjs';
|
|
8
8
|
import { promises as fs } from 'fs';
|
|
9
9
|
|
|
10
10
|
import pkg from './package.json' with { type: 'json' };
|
|
@@ -32,7 +32,7 @@ const options = {
|
|
|
32
32
|
minify: true,
|
|
33
33
|
verbose: false,
|
|
34
34
|
xlsx_support: true,
|
|
35
|
-
output_filename: 'treb-spreadsheet
|
|
35
|
+
output_filename: 'treb-spreadsheet',
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
//------------------------------------------------------------------------------
|
|
@@ -60,36 +60,19 @@ for (let i = 0; i < process.argv.length; i++) {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
/**
|
|
64
|
-
* thanks to
|
|
65
|
-
* https://github.com/evanw/esbuild/issues/3337#issuecomment-2085394950
|
|
66
|
-
*/
|
|
67
|
-
function RewriteIgnoredImports() {
|
|
68
|
-
return {
|
|
69
|
-
name: 'RewriteIgnoredImports',
|
|
70
|
-
setup(build) {
|
|
71
|
-
build.onEnd(async (result) => {
|
|
72
|
-
if (result.outputFiles) {
|
|
73
|
-
for (const file of result.outputFiles) {
|
|
74
|
-
const { path, text } = file;
|
|
75
|
-
await fs.writeFile(path, text.replace(/esbuild-ignore-import:/, ''));
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
63
|
|
|
83
64
|
/** @type esbuild.BuildOptions */
|
|
84
65
|
const build_options = {
|
|
85
66
|
entryPoints: [
|
|
86
|
-
'treb-embed/src/index.ts',
|
|
67
|
+
{ in: 'treb-embed/src/index.ts', out: options.output_filename },
|
|
68
|
+
{ in: 'treb-embed/src/export-worker.ts', out: 'treb-export-worker' },
|
|
87
69
|
],
|
|
88
70
|
banner: {
|
|
89
71
|
js: `/*! TREB v${pkg.version}. Copyright 2018-${new Date().getFullYear()} trebco, llc. All rights reserved. LGPL: https://treb.app/license */`
|
|
90
72
|
},
|
|
91
73
|
bundle: true,
|
|
92
|
-
|
|
74
|
+
outdir: 'dist',
|
|
75
|
+
// outfile: 'dist/' + options.output_filename,
|
|
93
76
|
outExtension: { '.js': '.mjs' },
|
|
94
77
|
minify: options.minify,
|
|
95
78
|
metafile: true,
|
package/esbuild-utils.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import * as esbuild from 'esbuild';
|
|
7
7
|
import { promises as fs } from 'fs';
|
|
8
|
-
import { minify } from 'html-minifier';
|
|
8
|
+
import { minify } from 'html-minifier-terser';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import * as sass from 'sass';
|
|
11
11
|
import cssnano from 'cssnano';
|
|
@@ -116,7 +116,7 @@ export const WorkerPlugin = (options) => ({
|
|
|
116
116
|
resolveDir: args.resolveDir,
|
|
117
117
|
});
|
|
118
118
|
return { path: result.path, namespace: 'worker', };
|
|
119
|
-
})
|
|
119
|
+
});
|
|
120
120
|
|
|
121
121
|
// for some reason I can't get the filter to work here, but using
|
|
122
122
|
// namespace works. as long as we don't collide with anybody else.
|
|
@@ -219,10 +219,14 @@ export const HTMLPlugin = (options) => ({
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
const text = await fs.readFile(args.path, 'utf8');
|
|
222
|
+
|
|
222
223
|
return {
|
|
223
|
-
contents: options?.minify ? minify(text, html_minifier_options) : text,
|
|
224
|
+
contents: options?.minify ? await minify(text, html_minifier_options) : text,
|
|
224
225
|
loader: 'text',
|
|
225
226
|
};
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
226
230
|
});
|
|
227
231
|
},
|
|
228
232
|
});
|
|
@@ -282,3 +286,24 @@ export const SassPlugin = (options) => ({
|
|
|
282
286
|
});
|
|
283
287
|
},
|
|
284
288
|
});
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* thanks to
|
|
292
|
+
* https://github.com/evanw/esbuild/issues/3337#issuecomment-2085394950
|
|
293
|
+
*/
|
|
294
|
+
export const RewriteIgnoredImports = () => {
|
|
295
|
+
return {
|
|
296
|
+
name: 'RewriteIgnoredImports',
|
|
297
|
+
setup(build) {
|
|
298
|
+
build.onEnd(async (result) => {
|
|
299
|
+
if (result.outputFiles) {
|
|
300
|
+
for (const file of result.outputFiles) {
|
|
301
|
+
const { path, text } = file;
|
|
302
|
+
await fs.writeFile(path, text.replace(/esbuild-ignore-import:/g, ''));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
};
|
|
309
|
+
|
package/eslint.config.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trebco/treb",
|
|
3
|
-
"version": "30.
|
|
3
|
+
"version": "30.6.0",
|
|
4
4
|
"license": "LGPL-3.0-or-later",
|
|
5
5
|
"homepage": "https://treb.app",
|
|
6
6
|
"repository": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"esbuild": "^0.23.0",
|
|
20
20
|
"eslint": "^9.8.0",
|
|
21
21
|
"fast-xml-parser": "^4.0.7",
|
|
22
|
-
"html-minifier": "^
|
|
22
|
+
"html-minifier-terser": "^7.2.0",
|
|
23
23
|
"sass": "^1.69.3",
|
|
24
24
|
"treb-base-types": "file:treb-base-types",
|
|
25
25
|
"treb-calculator": "file:treb-calculator",
|
|
@@ -37,17 +37,16 @@
|
|
|
37
37
|
"uzip": "^0.20201231.0"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
|
-
"build": "node esbuild-
|
|
41
|
-
"
|
|
42
|
-
"dev": "node esbuild-custom-element.mjs --dev",
|
|
40
|
+
"build": "node esbuild-composite.mjs",
|
|
41
|
+
"dev": "node esbuild-composite.mjs --dev",
|
|
43
42
|
"clean": "rm -fr build dist declaration",
|
|
44
|
-
"watch": "node --watch esbuild-
|
|
45
|
-
"watch-production": "node --watch esbuild-
|
|
43
|
+
"watch": "node --watch esbuild-composite.mjs --watch --dev",
|
|
44
|
+
"watch-production": "node --watch esbuild-composite.mjs --watch",
|
|
46
45
|
"tsc": "tsc -b treb-embed/modern.tsconfig.json",
|
|
47
46
|
"rebuild-tsc": "tsc -b --force treb-embed/modern.tsconfig.json",
|
|
48
47
|
"watch-tsc": "tsc -b treb-embed/modern.tsconfig.json -w",
|
|
49
48
|
"watch-api": "ts-node-dev --respawn api-generator/api-generator.ts --config api-config.json",
|
|
50
49
|
"generate-api": "ts-node-dev api-generator/api-generator.ts --config api-config.json",
|
|
51
|
-
"release": "npm run rebuild-tsc && npm run build && npm run generate-api
|
|
50
|
+
"release": "npm run rebuild-tsc && npm run build && npm run generate-api"
|
|
52
51
|
}
|
|
53
52
|
}
|
|
@@ -709,7 +709,7 @@ export class Cells {
|
|
|
709
709
|
typeof cell.calculated !== 'undefined') { // && cell.calculated_type !== ValueType.error) {
|
|
710
710
|
obj.calculated = cell.calculated;
|
|
711
711
|
if (cell.spill) {
|
|
712
|
-
obj.spill = cell.spill;
|
|
712
|
+
obj.spill = cell.spill.toJSON();
|
|
713
713
|
}
|
|
714
714
|
|
|
715
715
|
// always preserve error type, because we can't infer
|
|
@@ -253,6 +253,7 @@ export class Calculator extends Graph {
|
|
|
253
253
|
// special functions... need reference to the graph (this)
|
|
254
254
|
// moving countif here so we can reference it in COUNTIFS...
|
|
255
255
|
|
|
256
|
+
/*
|
|
256
257
|
const FlattenBooleans = (value: ArrayUnion) => {
|
|
257
258
|
const result: boolean[] = [];
|
|
258
259
|
for (const col of value.value) {
|
|
@@ -262,6 +263,58 @@ export class Calculator extends Graph {
|
|
|
262
263
|
}
|
|
263
264
|
return result;
|
|
264
265
|
};
|
|
266
|
+
*/
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* this is a function that does sumif/averageif/countif.
|
|
270
|
+
* args is one or more sets of [criteria_range, criteria]
|
|
271
|
+
*/
|
|
272
|
+
const XIf = (type: 'sum'|'count'|'average', value_range: CellValue[][], ...args: unknown[]): UnionValue => {
|
|
273
|
+
|
|
274
|
+
const filter: boolean[] = [];
|
|
275
|
+
|
|
276
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
277
|
+
if (Array.isArray(args[i])) {
|
|
278
|
+
const step = CountIfInternal(args[i] as CellValue[][], args[i+1] as CellValue);
|
|
279
|
+
if (step.type !== ValueType.array) {
|
|
280
|
+
return step;
|
|
281
|
+
}
|
|
282
|
+
for (const [r, cell] of step.value[0].entries()) {
|
|
283
|
+
filter[r] = (!!cell.value && (filter[r] !== false));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const values = Utilities.FlattenCellValues(value_range, true); // keep undefineds
|
|
289
|
+
|
|
290
|
+
let count = 0;
|
|
291
|
+
let sum = 0;
|
|
292
|
+
for (const [index, test] of filter.entries()) {
|
|
293
|
+
if (test) {
|
|
294
|
+
count++;
|
|
295
|
+
const value = values[index];
|
|
296
|
+
if (typeof value === 'number') {
|
|
297
|
+
sum += value;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
switch (type) {
|
|
303
|
+
case 'count':
|
|
304
|
+
return { type: ValueType.number, value: count };
|
|
305
|
+
|
|
306
|
+
case 'sum':
|
|
307
|
+
return { type: ValueType.number, value: sum };
|
|
308
|
+
|
|
309
|
+
case 'average':
|
|
310
|
+
if (count === 0) {
|
|
311
|
+
return DivideByZeroError();
|
|
312
|
+
}
|
|
313
|
+
return { type: ValueType.number, value: sum/count };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
}
|
|
265
318
|
|
|
266
319
|
const CountIfInternal = (range: CellValue[][], criteria: CellValue): UnionValue => {
|
|
267
320
|
|
|
@@ -271,7 +324,7 @@ export class Calculator extends Graph {
|
|
|
271
324
|
// in any event there are no dynamic dependencies with this
|
|
272
325
|
// function.
|
|
273
326
|
|
|
274
|
-
const data = Utilities.FlattenCellValues(range);
|
|
327
|
+
const data = Utilities.FlattenCellValues(range, true); // keep undefineds, important for mapping
|
|
275
328
|
|
|
276
329
|
let parse_result: ParseResult|undefined;
|
|
277
330
|
let expression: ExpressionUnit|undefined;
|
|
@@ -326,7 +379,7 @@ export class Calculator extends Graph {
|
|
|
326
379
|
}
|
|
327
380
|
|
|
328
381
|
if (expression?.type === 'call' && expression.args[0]?.type === 'array') {
|
|
329
|
-
expression.args[0].values = [Utilities.FilterIntrinsics(data)];
|
|
382
|
+
expression.args[0].values = [Utilities.FilterIntrinsics(data, true)];
|
|
330
383
|
}
|
|
331
384
|
|
|
332
385
|
}
|
|
@@ -377,7 +430,7 @@ export class Calculator extends Graph {
|
|
|
377
430
|
}
|
|
378
431
|
}
|
|
379
432
|
|
|
380
|
-
expression.left.values = [Utilities.FilterIntrinsics(data)];
|
|
433
|
+
expression.left.values = [Utilities.FilterIntrinsics(data, true)];
|
|
381
434
|
|
|
382
435
|
}
|
|
383
436
|
|
|
@@ -388,22 +441,6 @@ export class Calculator extends Graph {
|
|
|
388
441
|
const result = this.CalculateExpression(expression);
|
|
389
442
|
return result;
|
|
390
443
|
|
|
391
|
-
/*
|
|
392
|
-
// console.info({expression, result});
|
|
393
|
-
|
|
394
|
-
if (result.type === ValueType.array) {
|
|
395
|
-
let count = 0;
|
|
396
|
-
for (const column of (result as ArrayUnion).value) {
|
|
397
|
-
for (const cell of column) {
|
|
398
|
-
if (cell.value) { count++; }
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
return { type: ValueType.number, value: count };
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return result; // error?
|
|
405
|
-
*/
|
|
406
|
-
|
|
407
444
|
};
|
|
408
445
|
|
|
409
446
|
this.library.Register({
|
|
@@ -615,47 +652,63 @@ export class Calculator extends Graph {
|
|
|
615
652
|
*/
|
|
616
653
|
CountIfs: {
|
|
617
654
|
arguments: [
|
|
618
|
-
{ name: '
|
|
619
|
-
{ name: '
|
|
620
|
-
{ name: '
|
|
621
|
-
{ name: '
|
|
655
|
+
{ name: 'range', },
|
|
656
|
+
{ name: 'criteria', },
|
|
657
|
+
{ name: 'range', },
|
|
658
|
+
{ name: 'criteria', }
|
|
622
659
|
],
|
|
623
660
|
fn: (...args): UnionValue => {
|
|
661
|
+
return XIf('count', args[0], ...args);
|
|
662
|
+
},
|
|
663
|
+
},
|
|
624
664
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
if (args[i] && args[i + 1]) {
|
|
636
|
-
|
|
637
|
-
const result = CountIfInternal(args[i], args[i+1]);
|
|
638
|
-
if (result.type !== ValueType.array) {
|
|
639
|
-
return result;
|
|
640
|
-
}
|
|
665
|
+
/** @see CountIf */
|
|
666
|
+
AverageIf: {
|
|
667
|
+
arguments: [
|
|
668
|
+
{ name: 'range', },
|
|
669
|
+
{ name: 'criteria', },
|
|
670
|
+
],
|
|
671
|
+
fn: (range: CellValue[][], criteria: CellValue, average_range?: CellValue[][]) => {
|
|
672
|
+
return XIf('average', average_range||range, range, criteria);
|
|
673
|
+
},
|
|
674
|
+
},
|
|
641
675
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
676
|
+
/** @see CountIf */
|
|
677
|
+
AverageIfs: {
|
|
678
|
+
arguments: [
|
|
679
|
+
{ name: 'value range', },
|
|
680
|
+
{ name: 'criteria range', },
|
|
681
|
+
{ name: 'criteria', },
|
|
682
|
+
{ name: 'criteria range', },
|
|
683
|
+
{ name: 'criteria', },
|
|
684
|
+
],
|
|
685
|
+
fn: (range: CellValue[][], ...args) => {
|
|
686
|
+
return XIf('average', range, ...args);
|
|
687
|
+
},
|
|
688
|
+
},
|
|
649
689
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
690
|
+
/** @see CountIf */
|
|
691
|
+
SumIf: {
|
|
692
|
+
arguments: [
|
|
693
|
+
{ name: 'range', },
|
|
694
|
+
{ name: 'criteria', },
|
|
695
|
+
],
|
|
696
|
+
fn: (range: CellValue[][], criteria: CellValue, sum_range?: CellValue[][]) => {
|
|
697
|
+
return XIf('sum', sum_range||range, range, criteria);
|
|
698
|
+
},
|
|
699
|
+
},
|
|
658
700
|
|
|
701
|
+
/** @see CountIf */
|
|
702
|
+
SumIfs: {
|
|
703
|
+
arguments: [
|
|
704
|
+
{ name: 'value range', },
|
|
705
|
+
{ name: 'criteria range', },
|
|
706
|
+
{ name: 'criteria', },
|
|
707
|
+
{ name: 'criteria range', },
|
|
708
|
+
{ name: 'criteria', },
|
|
709
|
+
],
|
|
710
|
+
fn: (range: CellValue[][], ...args) => {
|
|
711
|
+
return XIf('sum', range, ...args);
|
|
659
712
|
},
|
|
660
713
|
},
|
|
661
714
|
|
|
@@ -680,21 +733,7 @@ export class Calculator extends Graph {
|
|
|
680
733
|
{ name: 'criteria', }
|
|
681
734
|
],
|
|
682
735
|
fn: (range, criteria): UnionValue => {
|
|
683
|
-
|
|
684
|
-
const result = CountIfInternal(range, criteria);
|
|
685
|
-
|
|
686
|
-
if (result.type === ValueType.array) {
|
|
687
|
-
let count = 0;
|
|
688
|
-
for (const column of (result as ArrayUnion).value) {
|
|
689
|
-
for (const cell of column) {
|
|
690
|
-
if (cell.value) { count++; }
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
return { type: ValueType.number, value: count };
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
return result; // error
|
|
697
|
-
|
|
736
|
+
return XIf('count', range, range, criteria);
|
|
698
737
|
},
|
|
699
738
|
},
|
|
700
739
|
|
|
@@ -99,6 +99,54 @@ export const RectangularToPolar = (value: Complex): { r: number, theta: number }
|
|
|
99
99
|
return { r, theta };
|
|
100
100
|
};
|
|
101
101
|
|
|
102
|
+
export const Tan = (z: Complex) => {
|
|
103
|
+
|
|
104
|
+
// tan(a+bi) = (tan(a) + i tanh(b)) / (1 - i tan(a) tanh(b))
|
|
105
|
+
|
|
106
|
+
return Divide({
|
|
107
|
+
real: Math.tan(z.real),
|
|
108
|
+
imaginary: Math.tanh(z.imaginary),
|
|
109
|
+
}, {
|
|
110
|
+
real: 1,
|
|
111
|
+
imaginary: -(Math.tan(z.real) * Math.tanh(z.imaginary)),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const Cos = (z: Complex) => {
|
|
117
|
+
|
|
118
|
+
// sin(a+bi) = cos(a) cosh(b) + i sin(a) sinh(b)
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
real: Math.cos(z.real) * Math.cosh(z.imaginary),
|
|
122
|
+
imaginary: Math.sin(z.real) * Math.sinh(z.imaginary),
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
export const Sin = (z: Complex) => {
|
|
129
|
+
|
|
130
|
+
// sin(a+bi) = sin(a) cosh(b) + i cos(a) sinh(b)
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
real: Math.sin(z.real) * Math.cosh(z.imaginary),
|
|
134
|
+
imaginary: Math.cos(z.real) * Math.sinh(z.imaginary),
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export const Product = (...args: Complex[]): Complex => {
|
|
140
|
+
let base = args.shift();
|
|
141
|
+
if (!base) {
|
|
142
|
+
return { real: 0, imaginary: 0 };
|
|
143
|
+
}
|
|
144
|
+
for (const arg of args) {
|
|
145
|
+
base = Multiply(base, arg);
|
|
146
|
+
}
|
|
147
|
+
return base;
|
|
148
|
+
};
|
|
149
|
+
|
|
102
150
|
export const Multiply = (a: Complex, b: Complex): Complex => {
|
|
103
151
|
return {
|
|
104
152
|
real: (a.real * b.real) - (a.imaginary * b.imaginary),
|
|
@@ -179,7 +179,10 @@ export abstract class Graph implements GraphCallbacks {
|
|
|
179
179
|
public GetVertex(address: ICellAddress, create?: boolean): SpreadsheetVertex | undefined {
|
|
180
180
|
|
|
181
181
|
if (!address.sheet_id) {
|
|
182
|
-
console.info({address, create});
|
|
182
|
+
console.info(JSON.stringify({address, create}));
|
|
183
|
+
console.trace();
|
|
184
|
+
|
|
185
|
+
|
|
183
186
|
throw new Error('getvertex with no sheet id');
|
|
184
187
|
}
|
|
185
188
|
|
|
@@ -35,8 +35,11 @@ import { ClickCheckbox, RenderCheckbox } from './checkbox';
|
|
|
35
35
|
import { UnionIsMetadata } from '../expression-calculator';
|
|
36
36
|
|
|
37
37
|
import { Exp as ComplexExp, Power as ComplexPower, Multiply as ComplexMultply } from '../complex-math';
|
|
38
|
+
import * as ComplexMath from '../complex-math';
|
|
39
|
+
|
|
38
40
|
import { CoerceComplex } from './function-utilities';
|
|
39
41
|
import type { UnitAddress, UnitRange } from 'treb-parser';
|
|
42
|
+
import { ConstructDate } from './date-utils';
|
|
40
43
|
|
|
41
44
|
/**
|
|
42
45
|
* BaseFunctionLibrary is a static object that has basic spreadsheet
|
|
@@ -77,6 +80,22 @@ const erf = (x: number): number => {
|
|
|
77
80
|
|
|
78
81
|
const sqrt2pi = Math.sqrt(2 * Math.PI);
|
|
79
82
|
|
|
83
|
+
const norm_dist = (x: number, mean: number, stdev: number, cumulative: boolean) => {
|
|
84
|
+
|
|
85
|
+
let value = 0;
|
|
86
|
+
|
|
87
|
+
if (cumulative) {
|
|
88
|
+
const sign = (x < mean) ? -1 : 1;
|
|
89
|
+
value = 0.5 * (1.0 + sign * erf((Math.abs(x - mean)) / (stdev * Math.sqrt(2))));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
value = Math.exp(-1/2 * Math.pow((x - mean) / stdev, 2)) / (stdev * sqrt2pi);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return value;
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
80
99
|
/** imprecise but reasonably fast normsinv function */
|
|
81
100
|
const inverse_normal = (q: number): number => {
|
|
82
101
|
|
|
@@ -93,6 +112,7 @@ const inverse_normal = (q: number): number => {
|
|
|
93
112
|
|
|
94
113
|
};
|
|
95
114
|
|
|
115
|
+
|
|
96
116
|
const edate_calc = (start: number, months: number) => {
|
|
97
117
|
|
|
98
118
|
let date = new Date(LotusDate(start));
|
|
@@ -519,36 +539,18 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
519
539
|
},
|
|
520
540
|
|
|
521
541
|
Date: {
|
|
522
|
-
description: 'Constructs a
|
|
542
|
+
description: 'Constructs a date from year/month/day',
|
|
523
543
|
arguments: [
|
|
524
544
|
{ name: 'year', unroll: true },
|
|
525
545
|
{ name: 'month', unroll: true },
|
|
526
546
|
{ name: 'day', unroll: true },
|
|
527
547
|
],
|
|
528
548
|
fn: (year: number, month: number, day: number) => {
|
|
529
|
-
const date =
|
|
530
|
-
date
|
|
531
|
-
date.setSeconds(0);
|
|
532
|
-
date.setMinutes(0);
|
|
533
|
-
date.setHours(0);
|
|
534
|
-
|
|
535
|
-
if (year < 0 || year > 10000) {
|
|
549
|
+
const date = ConstructDate(year, month, day);
|
|
550
|
+
if (date === false) {
|
|
536
551
|
return ArgumentError();
|
|
537
552
|
}
|
|
538
|
-
|
|
539
|
-
date.setFullYear(year);
|
|
540
|
-
|
|
541
|
-
if (month < 1 || month > 12) {
|
|
542
|
-
return ArgumentError();
|
|
543
|
-
}
|
|
544
|
-
date.setMonth(month - 1);
|
|
545
|
-
|
|
546
|
-
if (day < 1 || day > 31) {
|
|
547
|
-
return ArgumentError();
|
|
548
|
-
}
|
|
549
|
-
date.setDate(day);
|
|
550
|
-
|
|
551
|
-
return { type: ValueType.number, value: UnlotusDate(date.getTime()) };
|
|
553
|
+
return { type: ValueType.number, value: date };
|
|
552
554
|
},
|
|
553
555
|
},
|
|
554
556
|
|
|
@@ -2122,6 +2124,22 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
2122
2124
|
}
|
|
2123
2125
|
},
|
|
2124
2126
|
|
|
2127
|
+
'Norm.S.Inv': {
|
|
2128
|
+
description: 'Inverse of the normal cumulative distribution',
|
|
2129
|
+
arguments: [
|
|
2130
|
+
{name: 'probability'},
|
|
2131
|
+
{name: 'mean', default: 0},
|
|
2132
|
+
{name: 'standard deviation', default: 1},
|
|
2133
|
+
],
|
|
2134
|
+
xlfn: true,
|
|
2135
|
+
fn: (q: number, mean = 0, stdev = 1): UnionValue => {
|
|
2136
|
+
return {
|
|
2137
|
+
type: ValueType.number,
|
|
2138
|
+
value: inverse_normal(q) * stdev + mean,
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
},
|
|
2142
|
+
|
|
2125
2143
|
'Norm.Dist': {
|
|
2126
2144
|
|
|
2127
2145
|
description: 'Cumulative normal distribution',
|
|
@@ -2138,22 +2156,22 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
2138
2156
|
xlfn: true,
|
|
2139
2157
|
|
|
2140
2158
|
fn: (x: number, mean = 0, stdev = 1, cumulative = true): UnionValue => {
|
|
2159
|
+
return { type: ValueType.number, value: norm_dist(x, mean, stdev, cumulative) };
|
|
2160
|
+
},
|
|
2161
|
+
},
|
|
2141
2162
|
|
|
2142
|
-
|
|
2163
|
+
'Norm.S.Dist': {
|
|
2143
2164
|
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
|
-
value = Math.exp(-1/2 * Math.pow((x - mean) / stdev, 2)) / (stdev * sqrt2pi);
|
|
2150
|
-
}
|
|
2165
|
+
description: 'Cumulative normal distribution',
|
|
2166
|
+
arguments: [
|
|
2167
|
+
{name: 'value'},
|
|
2168
|
+
{name: 'cumulative', default: true},
|
|
2169
|
+
],
|
|
2151
2170
|
|
|
2152
|
-
|
|
2153
|
-
type: ValueType.number,
|
|
2154
|
-
value,
|
|
2155
|
-
};
|
|
2171
|
+
xlfn: true,
|
|
2156
2172
|
|
|
2173
|
+
fn: (x: number, cumulative = true): UnionValue => {
|
|
2174
|
+
return { type: ValueType.number, value: norm_dist(x, 0, 1, cumulative) };
|
|
2157
2175
|
},
|
|
2158
2176
|
},
|
|
2159
2177
|
|
|
@@ -2437,6 +2455,60 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
2437
2455
|
return ArgumentError();
|
|
2438
2456
|
}
|
|
2439
2457
|
|
|
2458
|
+
},
|
|
2459
|
+
},
|
|
2460
|
+
|
|
2461
|
+
Sin: {
|
|
2462
|
+
arguments: [
|
|
2463
|
+
{ name: 'angle in radians', boxed: true, unroll: true, }
|
|
2464
|
+
],
|
|
2465
|
+
fn: (a: UnionValue) => {
|
|
2466
|
+
|
|
2467
|
+
if (a.type === ValueType.number) {
|
|
2468
|
+
return { type: ValueType.number, value: Math.sin(a.value) };
|
|
2469
|
+
}
|
|
2470
|
+
if (a.type === ValueType.complex) {
|
|
2471
|
+
return { type: ValueType.complex, value: ComplexMath.Sin(a.value) };
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
return ArgumentError();
|
|
2475
|
+
|
|
2476
|
+
},
|
|
2477
|
+
},
|
|
2478
|
+
|
|
2479
|
+
Cos: {
|
|
2480
|
+
arguments: [
|
|
2481
|
+
{ name: 'angle in radians', boxed: true, unroll: true, }
|
|
2482
|
+
],
|
|
2483
|
+
fn: (a: UnionValue) => {
|
|
2484
|
+
|
|
2485
|
+
if (a.type === ValueType.number) {
|
|
2486
|
+
return { type: ValueType.number, value: Math.cos(a.value) };
|
|
2487
|
+
}
|
|
2488
|
+
if (a.type === ValueType.complex) {
|
|
2489
|
+
return { type: ValueType.complex, value: ComplexMath.Cos(a.value) };
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
return ArgumentError();
|
|
2493
|
+
|
|
2494
|
+
},
|
|
2495
|
+
},
|
|
2496
|
+
|
|
2497
|
+
Tan: {
|
|
2498
|
+
arguments: [
|
|
2499
|
+
{ name: 'angle in radians', boxed: true, unroll: true, }
|
|
2500
|
+
],
|
|
2501
|
+
fn: (a: UnionValue) => {
|
|
2502
|
+
|
|
2503
|
+
if (a.type === ValueType.number) {
|
|
2504
|
+
return { type: ValueType.number, value: Math.tan(a.value) };
|
|
2505
|
+
}
|
|
2506
|
+
if (a.type === ValueType.complex) {
|
|
2507
|
+
return { type: ValueType.complex, value: ComplexMath.Tan(a.value) };
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
return ArgumentError();
|
|
2511
|
+
|
|
2440
2512
|
},
|
|
2441
2513
|
}
|
|
2442
2514
|
|