@trebco/treb 27.0.1 → 27.2.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.
package/dist/treb.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /*! API v27.0. Copyright 2018-2023 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
1
+ /*! API v27.2. Copyright 2018-2023 trebco, llc. All rights reserved. LGPL: https://treb.app/license */
2
2
 
3
3
  /**
4
4
  * add our tag to the map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trebco/treb",
3
- "version": "27.0.1",
3
+ "version": "27.2.1",
4
4
  "license": "LGPL-3.0-or-later",
5
5
  "homepage": "https://treb.app",
6
6
  "repository": {
@@ -13,8 +13,8 @@
13
13
  "devDependencies": {
14
14
  "@types/html-minifier": "^4.0.2",
15
15
  "@types/node": "^20.4.0",
16
- "@typescript-eslint/eslint-plugin": "^5.30.4",
17
- "@typescript-eslint/parser": "^5.30.4",
16
+ "@typescript-eslint/eslint-plugin": "^6.1.0",
17
+ "@typescript-eslint/parser": "^6.1.0",
18
18
  "archiver": "^5.3.0",
19
19
  "cssnano": "^6.0.0",
20
20
  "esbuild": "^0.18.11",
@@ -811,6 +811,33 @@ export class Calculator extends Graph {
811
811
  },
812
812
  },
813
813
 
814
+
815
+ /** not sure when this one appeared, but it's what I was looking for */
816
+ FormulaText: {
817
+ description: 'Returns a formula as a string',
818
+ arguments: [
819
+ { name: 'reference', description: 'Cell reference', metadata: true, },
820
+ ],
821
+ fn: Utilities.ApplyAsArray((reference: UnionValue): UnionValue => {
822
+
823
+ if (!UnionIsMetadata(reference)) {
824
+ return ReferenceError();
825
+ }
826
+
827
+ const sheet = this.model.sheets.Find(reference.value?.address?.sheet_id || 0);
828
+ if (sheet) {
829
+ const cell = sheet.cells.GetCell(reference.value.address, false);
830
+ return {
831
+ type: ValueType.string,
832
+ value: cell?.value?.toString() || '',
833
+ };
834
+ }
835
+
836
+ return ReferenceError();
837
+
838
+ }),
839
+ },
840
+
814
841
  /**
815
842
  * this should be in the 'information' library but it needs reference
816
843
  * to the underlying cell (unresolved)
@@ -23,6 +23,8 @@ import type { FunctionMap } from '../descriptors';
23
23
  import { type CellValue, type UnionValue, ValueType } from 'treb-base-types';
24
24
  import { FlattenUnboxed } from '../utilities';
25
25
 
26
+ import { ArgumentError, ReferenceError, UnknownError, ValueError, ExpressionError, NAError, DivideByZeroError } from '../function-error';
27
+
26
28
  // use a single, static object for base functions
27
29
 
28
30
  /**
@@ -114,6 +116,189 @@ export const FinanceFunctionLibrary: FunctionMap = {
114
116
  }
115
117
  },
116
118
 
119
+ XNPV: {
120
+ description: 'returns the NPV of a nonperiodic stream of payments at a given rate',
121
+ arguments: [
122
+ { name: 'Discount rate', },
123
+ { name: 'Values', },
124
+ { name: 'Dates', },
125
+ ],
126
+ fn: (rate: UnionValue, input_values: CellValue[], input_dates: CellValue[]): UnionValue => {
127
+
128
+ if (typeof rate !== 'number') {
129
+ return ArgumentError();
130
+ }
131
+
132
+ input_values = FlattenUnboxed(input_values);
133
+ input_dates = FlattenUnboxed(input_dates);
134
+
135
+ // some validation...
136
+
137
+ if (input_values.length !== input_dates.length) {
138
+ return ArgumentError();
139
+ }
140
+
141
+ const values: number[] = [];
142
+
143
+ for (const value of input_values) {
144
+ if (typeof value !== 'number') {
145
+ return ArgumentError();
146
+ }
147
+ values.push(value);
148
+ }
149
+
150
+ const dates: number[] = [];
151
+
152
+ //
153
+ // "Numbers in dates are truncated to integers."
154
+ //
155
+ // https://support.microsoft.com/en-gb/office/xirr-function-de1242ec-6477-445b-b11b-a303ad9adc9d
156
+ //
157
+ // what does that mean? rounded? floored? going to assume the latter...
158
+
159
+ for (const date of input_dates) {
160
+ if (typeof date !== 'number') {
161
+ return ArgumentError();
162
+ }
163
+ dates.push(Math.floor(date));
164
+ }
165
+
166
+ let npv = 0;
167
+
168
+ for (let j = 0; j < values.length; j++) {
169
+ npv += (values[j] || 0) / Math.pow((1 + rate), (dates[j] - dates[0]) / 365);
170
+ }
171
+
172
+ return {
173
+ type: ValueType.number,
174
+ value: npv,
175
+ };
176
+
177
+ }
178
+ },
179
+
180
+ XIRR: {
181
+ description: 'returns the internal rate of return of a nonperiodic stream of payments',
182
+ arguments: [
183
+ { name: 'Values', },
184
+ { name: 'Dates', },
185
+ { name: 'Guess', default: .1 },
186
+ ],
187
+ fn: (input_values: CellValue[], input_dates: CellValue[], guess = .1): UnionValue => {
188
+
189
+ input_values = FlattenUnboxed(input_values);
190
+ input_dates = FlattenUnboxed(input_dates);
191
+
192
+ // some validation...
193
+
194
+ if (input_values.length !== input_dates.length) {
195
+ return ArgumentError();
196
+ }
197
+
198
+ let positive = 0;
199
+ let negative = 0;
200
+
201
+ const values: number[] = [];
202
+
203
+ for (const value of input_values) {
204
+ if (typeof value !== 'number') {
205
+ // console.info('value not number', value);
206
+ return ArgumentError();
207
+ }
208
+ if (value > 0) { positive++; }
209
+ if (value < 0) { negative++; }
210
+
211
+ values.push(value);
212
+ }
213
+
214
+ if (positive <= 0 || negative <= 0) {
215
+ // console.info('invalid -/+ count', positive, negative);
216
+ return ArgumentError();
217
+ }
218
+
219
+ const dates: number[] = [];
220
+
221
+ //
222
+ // "Numbers in dates are truncated to integers."
223
+ //
224
+ // https://support.microsoft.com/en-gb/office/xirr-function-de1242ec-6477-445b-b11b-a303ad9adc9d
225
+ //
226
+ // what does that mean? rounded? floored? going to assume the latter...
227
+
228
+ for (const date of input_dates) {
229
+ if (typeof date !== 'number') {
230
+ return ArgumentError();
231
+ }
232
+ dates.push(Math.floor(date));
233
+ }
234
+
235
+ const start = dates[0];
236
+ for (const date of dates) {
237
+ if (date < start) {
238
+ return ArgumentError();
239
+ }
240
+ }
241
+
242
+ // per the above reference link we have max steps = 100 and
243
+ // resolution threshold = 1e-8 ("0.000001 percent")
244
+
245
+ const step = .1; // initial step
246
+
247
+ const bounds = [
248
+ {found: false, value: 0},
249
+ {found: false, value: 0},
250
+ ];
251
+
252
+ const count = values.length;
253
+
254
+ for (let i = 0; i < 100; i++) {
255
+
256
+ // calculate npv
257
+ let npv = 0;
258
+
259
+ for (let j = 0; j < count; j++) {
260
+ npv += (values[j] || 0) / Math.pow((1 + guess), (dates[j] - dates[0]) / 365);
261
+ }
262
+
263
+ if (Math.abs(npv) <= 1e-6) { // resolution
264
+ // console.info(`** found in ${i + 1} steps`)
265
+ return {
266
+ type: ValueType.number,
267
+ value: guess,
268
+ }
269
+ }
270
+
271
+ // search space is unbounded, unfortunately. we can expand exponentially
272
+ // until we have bounds, at which point it's a standard bounded binary search
273
+
274
+ // ...or we can expand linearly, using a reasonable initial step size?
275
+
276
+ if (npv > 0) {
277
+ bounds[0].value = bounds[0].found ? Math.max(bounds[0].value, guess) : guess;
278
+ bounds[0].found = true;
279
+ if (!bounds[1].found) {
280
+ guess += step;
281
+ continue;
282
+ }
283
+ }
284
+ else {
285
+ bounds[1].value = bounds[1].found ? Math.min(bounds[1].value, guess) : guess;
286
+ bounds[1].found = true;
287
+ if (!bounds[0].found) {
288
+ guess -= step;
289
+ continue;
290
+ }
291
+ }
292
+
293
+ guess = bounds[0].value + (bounds[1].value - bounds[0].value) / 2;
294
+
295
+ }
296
+
297
+ return ValueError();
298
+
299
+ },
300
+ },
301
+
117
302
  IRR: {
118
303
  description: 'Calculates the internal rate of return of a series of cashflows',
119
304
  arguments: [
@@ -887,6 +887,9 @@ export class EmbeddedSpreadsheet {
887
887
  if (data) {
888
888
  this.LoadDocument(data as TREBDocument, { recalculate: !!this.options.recalculate, source});
889
889
  }
890
+ else {
891
+ this.UpdateDocumentStyles();
892
+ }
890
893
  }
891
894
  else if (!network_document) {
892
895
 
@@ -894,6 +897,7 @@ export class EmbeddedSpreadsheet {
894
897
  // and the calculator, which would otherwise happen on document load
895
898
 
896
899
  this.calculator.RebuildClean(true);
900
+ this.UpdateDocumentStyles();
897
901
 
898
902
  }
899
903
 
@@ -2410,17 +2414,7 @@ export class EmbeddedSpreadsheet {
2410
2414
  }
2411
2415
 
2412
2416
  if (blob) {
2413
-
2414
- /*
2415
- const a = document.createElement('a');
2416
- a.href = URL.createObjectURL(blob);
2417
- a.download = filename + '.xlsx';
2418
- a.click();
2419
- URL.revokeObjectURL(a.href);
2420
- // FileSaver.saveAs(blob, filename + '.xlsx', { autoBom: false });
2421
- */
2422
2417
  this.SaveAs(blob, filename + '.xlsx');
2423
-
2424
2418
  this.last_save_version = this.file_version; // even though it's an export, consider it clean
2425
2419
  }
2426
2420
 
@@ -796,13 +796,13 @@ export class Exporter {
796
796
  case 'png':
797
797
  case 'gif':
798
798
 
799
- if (annotation.rect) {
799
+ if (annotation.layout) {
800
800
  images.push({
801
- anchor: this.AnnotationRectToAnchor(annotation.rect, sheet_source), options});
801
+ anchor: this.AnnotationLayoutToAnchor(annotation.layout, sheet_source), options});
802
802
  }
803
- else if (annotation.layout) {
803
+ else if (annotation.rect) {
804
804
  images.push({
805
- anchor: this.AnnotationLayoutToAnchor(annotation.layout, sheet_source), options});
805
+ anchor: this.AnnotationRectToAnchor(annotation.rect, sheet_source), options});
806
806
  }
807
807
  else {
808
808
  console.warn('annotation missing layout');
@@ -1006,16 +1006,16 @@ export class Exporter {
1006
1006
 
1007
1007
  const rect = (annotation as AnnotationData & { rect?: Partial<Rectangle>}).rect;
1008
1008
 
1009
- if (rect) {
1010
- charts.push({
1011
- anchor: this.AnnotationRectToAnchor(rect, sheet_source), options});
1012
- // sheet.AddChart(this.AnnotationRectToAnchor(annotation.rect, sheet_source), options);
1013
- }
1014
- else if (annotation.layout) {
1009
+ if (annotation.layout) {
1015
1010
  charts.push({
1016
1011
  anchor: this.AnnotationLayoutToAnchor(annotation.layout, sheet_source), options});
1017
1012
  // sheet.AddChart(this.AnnotationLayoutToAnchor(annotation.layout, sheet_source), options);
1018
1013
  }
1014
+ else if (rect) {
1015
+ charts.push({
1016
+ anchor: this.AnnotationRectToAnchor(rect, sheet_source), options});
1017
+ // sheet.AddChart(this.AnnotationRectToAnchor(annotation.rect, sheet_source), options);
1018
+ }
1019
1019
  else {
1020
1020
  console.warn('annotation missing layout');
1021
1021
  }