@trebco/treb 30.3.2 → 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 -2
- package/dist/treb-spreadsheet.mjs +14 -14
- package/dist/treb.d.ts +1 -1
- package/package.json +1 -1
- 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/src/embedded-spreadsheet.ts +14 -14
- package/treb-embed/style/dark-theme.scss +1 -1
- package/treb-export/src/export.ts +22 -1
- package/treb-grid/src/editors/overlay_editor.ts +1 -1
- package/treb-grid/src/types/grid.ts +9 -1
|
@@ -25,6 +25,95 @@ import { ValueError, ArgumentError, NAError } from '../function-error';
|
|
|
25
25
|
import { type Complex, type UnionValue, ValueType, type CellValue } from 'treb-base-types';
|
|
26
26
|
import * as ComplexMath from '../complex-math';
|
|
27
27
|
|
|
28
|
+
const Median = (data: number[]) => {
|
|
29
|
+
const n = data.length;
|
|
30
|
+
if (n % 2) {
|
|
31
|
+
return data[Math.floor(n/2)];
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
return (data[n/2] + data[n/2 - 1])/2;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const InterpolatedQuartiles = (data: number[], include_median = true) => {
|
|
39
|
+
|
|
40
|
+
data.sort((a, b) => a - b);
|
|
41
|
+
const n = data.length;
|
|
42
|
+
|
|
43
|
+
const interp = (p: number, base: number, skip: number) => {
|
|
44
|
+
const index = base * p + skip;
|
|
45
|
+
const offset = index % 1;
|
|
46
|
+
if (offset) {
|
|
47
|
+
const a = data[Math.floor(index)];
|
|
48
|
+
const b = data[Math.ceil(index)];
|
|
49
|
+
return a + (b - a) * offset;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
return data[index];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (include_median) {
|
|
58
|
+
return [data[0], interp(.25, n - 1, 0), Median(data), interp(.75, n - 1, 0), data[n-1]];
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
if (n % 1) {
|
|
62
|
+
return [data[0], interp(.25, n - 2, 0), Median(data), interp(.75, n - 2, 1), data[n-1]];
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return [data[0], interp(.25, n - 3, 0), Median(data), interp(.75, n - 3, 2), data[n-1]];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const Gamma = (z: Complex): Complex => {
|
|
72
|
+
|
|
73
|
+
// this is a Lanczos approximation. could be cleaned up.
|
|
74
|
+
|
|
75
|
+
const coefficients = [
|
|
76
|
+
0.99999999999980993,
|
|
77
|
+
676.5203681218851,
|
|
78
|
+
-1259.1392167224028,
|
|
79
|
+
771.32342877765313,
|
|
80
|
+
-176.61502916214059,
|
|
81
|
+
12.507343278686905,
|
|
82
|
+
-0.13857109526572012,
|
|
83
|
+
9.9843695780195716e-6,
|
|
84
|
+
1.5056327351493116e-7
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
// generally speaking I'm against operator overloading but
|
|
88
|
+
// it would be a big help for complex math
|
|
89
|
+
|
|
90
|
+
const pi = Math.PI;
|
|
91
|
+
const sin = ComplexMath.Sin;
|
|
92
|
+
const div = ComplexMath.Divide;
|
|
93
|
+
const mul = ComplexMath.Multiply;
|
|
94
|
+
const cpx = (a: number) => ({ real: a, imaginary: 0 });
|
|
95
|
+
const add = (a: Complex, b: Complex): Complex => ({ real: a.real + b.real, imaginary: a.imaginary + b.imaginary });
|
|
96
|
+
const pow = ComplexMath.Power;
|
|
97
|
+
const exp = ComplexMath.Exp;
|
|
98
|
+
const inv = (a: Complex) => ({ real: -a.real, imaginary: -a.imaginary });
|
|
99
|
+
const prod = ComplexMath.Product;
|
|
100
|
+
|
|
101
|
+
if (z.real < 0.5) {
|
|
102
|
+
return div(cpx(pi), mul(sin(mul(cpx(pi), z)), Gamma({ real: 1 - z.real, imaginary: -z.imaginary })));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
z.real -= 1;
|
|
106
|
+
let x = cpx(coefficients[0]);
|
|
107
|
+
|
|
108
|
+
for (let i = 1; i < coefficients.length; i++) {
|
|
109
|
+
x = add(x, div(cpx(coefficients[i]), add(z, cpx(i))));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const t = add(z, cpx(7.5));
|
|
113
|
+
return prod(cpx(Math.sqrt(2 * pi)), pow(t, add(z, cpx(0.5))), exp(inv(t)), x);
|
|
114
|
+
|
|
115
|
+
};
|
|
116
|
+
|
|
28
117
|
export const Variance = (data: number[], sample = false) => {
|
|
29
118
|
|
|
30
119
|
const len = data.length;
|
|
@@ -307,6 +396,48 @@ export const StatisticsFunctionLibrary: FunctionMap = {
|
|
|
307
396
|
},
|
|
308
397
|
},
|
|
309
398
|
|
|
399
|
+
Gamma: {
|
|
400
|
+
description: 'Returns the gamma function for the given value',
|
|
401
|
+
arguments: [{ name: 'value', boxed: true }],
|
|
402
|
+
fn: (value: UnionValue) => {
|
|
403
|
+
|
|
404
|
+
let complex: Complex = { real: 0, imaginary: 0 };
|
|
405
|
+
|
|
406
|
+
if (value.type === ValueType.complex) {
|
|
407
|
+
complex = {...value.value};
|
|
408
|
+
}
|
|
409
|
+
else if (value.type === ValueType.number) {
|
|
410
|
+
complex.real = value.value;
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
return ArgumentError();
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (complex.imaginary === 0 && complex.real % 1 === 0 && complex.real <= 0) {
|
|
417
|
+
return ValueError();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const gamma = Gamma(complex);
|
|
421
|
+
|
|
422
|
+
if (Math.abs(gamma.imaginary) <= 1e-7) {
|
|
423
|
+
return { type: ValueType.number, value: gamma.real };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return {type: ValueType.complex, value: gamma};
|
|
427
|
+
|
|
428
|
+
},
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
Delta: {
|
|
432
|
+
arguments: [{ name: 'number', }, { name: 'number', default: 0 }],
|
|
433
|
+
fn: (a: CellValue, b: CellValue = 0) => {
|
|
434
|
+
if (typeof a !== 'number' || typeof b !== 'number') {
|
|
435
|
+
return ValueError();
|
|
436
|
+
}
|
|
437
|
+
return { type: ValueType.number, value: (a === b) ? 1 : 0 };
|
|
438
|
+
},
|
|
439
|
+
},
|
|
440
|
+
|
|
310
441
|
GCD: {
|
|
311
442
|
description: 'Finds the greatest common divisor of the arguments',
|
|
312
443
|
arguments: [{ boxed: true }],
|
|
@@ -457,6 +588,46 @@ export const StatisticsFunctionLibrary: FunctionMap = {
|
|
|
457
588
|
},
|
|
458
589
|
},
|
|
459
590
|
|
|
591
|
+
'Quartile.Inc': {
|
|
592
|
+
description: 'Returns the interpolated quartile of the data set (including median)',
|
|
593
|
+
arguments: [
|
|
594
|
+
{ name: 'range', },
|
|
595
|
+
{ name: 'quartile' },
|
|
596
|
+
],
|
|
597
|
+
xlfn: true,
|
|
598
|
+
fn: (data: CellValue[], quartile: CellValue) => {
|
|
599
|
+
|
|
600
|
+
if (typeof quartile !== 'number' || quartile < 0 || quartile > 4 || quartile % 1) {
|
|
601
|
+
return ArgumentError();
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const flat = Utils.FlattenNumbers(data);
|
|
605
|
+
const quartiles = InterpolatedQuartiles(flat, true);
|
|
606
|
+
|
|
607
|
+
return { type: ValueType.number, value: quartiles[quartile] };
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
|
|
611
|
+
'Quartile.Exc': {
|
|
612
|
+
description: 'Returns the interpolated quartile of the data set (excluding median)',
|
|
613
|
+
arguments: [
|
|
614
|
+
{ name: 'range', },
|
|
615
|
+
{ name: 'quartile' },
|
|
616
|
+
],
|
|
617
|
+
xlfn: true,
|
|
618
|
+
fn: (data: CellValue[], quartile: CellValue) => {
|
|
619
|
+
|
|
620
|
+
if (typeof quartile !== 'number' || quartile < 1 || quartile > 3 || quartile % 1) {
|
|
621
|
+
return ArgumentError();
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const flat = Utils.FlattenNumbers(data);
|
|
625
|
+
const quartiles = InterpolatedQuartiles(flat, false);
|
|
626
|
+
|
|
627
|
+
return { type: ValueType.number, value: quartiles[quartile] };
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
|
|
460
631
|
Median: {
|
|
461
632
|
description: 'Returns the median value of the range of data',
|
|
462
633
|
arguments: [
|
|
@@ -515,4 +686,5 @@ export const StatisticsFunctionAliases: {[index: string]: string} = {
|
|
|
515
686
|
Mean: 'Average',
|
|
516
687
|
'StDev': 'StDev.S',
|
|
517
688
|
'Var': 'Var.S',
|
|
689
|
+
'Quartile': 'Quartile.Inc',
|
|
518
690
|
};
|
|
@@ -301,6 +301,31 @@ export const TextFunctionLibrary: FunctionMap = {
|
|
|
301
301
|
},
|
|
302
302
|
},
|
|
303
303
|
|
|
304
|
+
Upper: {
|
|
305
|
+
description: 'Converts text to upper case',
|
|
306
|
+
arguments: [{ name: 'text', unroll: true }],
|
|
307
|
+
fn: (text?: string) => {
|
|
308
|
+
if (text === null || text === undefined) {
|
|
309
|
+
return { type: ValueType.undefined };
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
type: ValueType.string, value: text.toString().toUpperCase(),
|
|
313
|
+
};
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
Lower: {
|
|
317
|
+
description: 'Converts text to lower case',
|
|
318
|
+
arguments: [{ name: 'text', unroll: true }],
|
|
319
|
+
fn: (text?: string) => {
|
|
320
|
+
if (text === null || text === undefined) {
|
|
321
|
+
return { type: ValueType.undefined };
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
type: ValueType.string, value: text.toString().toLowerCase(),
|
|
325
|
+
};
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
|
|
304
329
|
/** canonical should be CONCAT; concatenate can be an alias */
|
|
305
330
|
Concat: {
|
|
306
331
|
description: 'Pastes strings together',
|
|
@@ -172,12 +172,12 @@ export const FlattenCellValues = (args: any[]): any[] => {
|
|
|
172
172
|
* to using any, although we're still kind of hacking at it. also we
|
|
173
173
|
* need to allow typed arrays (I think we've mostly gotten those out?)
|
|
174
174
|
*/
|
|
175
|
-
export const FlattenCellValues = (args: (CellValue|CellValue[]|CellValue[][]|Float32Array|Float64Array)[]): CellValue[] => {
|
|
175
|
+
export const FlattenCellValues = (args: (CellValue|CellValue[]|CellValue[][]|Float32Array|Float64Array)[], keep_undefined = false): CellValue[] => {
|
|
176
176
|
|
|
177
177
|
if (!Array.isArray(args)) { return [args]; } // special case
|
|
178
178
|
return args.reduce((a: CellValue[], b) => {
|
|
179
|
-
if (typeof b === 'undefined') return a;
|
|
180
|
-
if (Array.isArray(b)) return a.concat(FlattenCellValues(b));
|
|
179
|
+
if (typeof b === 'undefined' && !keep_undefined) return a;
|
|
180
|
+
if (Array.isArray(b)) return a.concat(FlattenCellValues(b, keep_undefined));
|
|
181
181
|
if (b instanceof Float32Array) return a.concat(Array.from(b));
|
|
182
182
|
if (b instanceof Float64Array) return a.concat(Array.from(b));
|
|
183
183
|
return a.concat([b]);
|
|
@@ -193,7 +193,21 @@ export const FlattenCellValues = (args: (CellValue|CellValue[]|CellValue[][]|Flo
|
|
|
193
193
|
export const FlattenNumbers = (args: Parameters<typeof FlattenCellValues>[0]) =>
|
|
194
194
|
(FlattenCellValues(args)).filter((value): value is number => typeof value === 'number');
|
|
195
195
|
|
|
196
|
-
export const FilterIntrinsics = (data: unknown[]): (string|number|boolean|undefined)[] => {
|
|
196
|
+
export const FilterIntrinsics = (data: unknown[], fill = false): (string|number|boolean|undefined)[] => {
|
|
197
|
+
|
|
198
|
+
if (fill) {
|
|
199
|
+
return data.map((value => {
|
|
200
|
+
switch (typeof value) {
|
|
201
|
+
case 'number':
|
|
202
|
+
case 'undefined':
|
|
203
|
+
case 'string':
|
|
204
|
+
case 'boolean':
|
|
205
|
+
return value;
|
|
206
|
+
}
|
|
207
|
+
return undefined;
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
210
|
+
|
|
197
211
|
return data.filter((value): value is number|boolean|undefined|string => {
|
|
198
212
|
switch (typeof value) {
|
|
199
213
|
case 'number':
|
|
@@ -17,6 +17,26 @@ import type { RangeScale } from 'treb-utils';
|
|
|
17
17
|
|
|
18
18
|
const DEFAULT_FORMAT = '#,##0.00'; // why not use "general", or whatever the usual default is?
|
|
19
19
|
|
|
20
|
+
export const ArrayMinMax = (data: number[]) => {
|
|
21
|
+
|
|
22
|
+
let min = data[0];
|
|
23
|
+
let max = data[0];
|
|
24
|
+
|
|
25
|
+
for (const entry of data) {
|
|
26
|
+
if (entry < min) { min = entry; }
|
|
27
|
+
if (entry > max) { max = entry; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { min, max };
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
const copy = data.slice(0);
|
|
34
|
+
copy.sort((a, b) => a - b);
|
|
35
|
+
return {min: copy[0], max: copy[copy.length - 1]};
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
};
|
|
39
|
+
|
|
20
40
|
export const ReadSeries = (data: ReferenceSeries['value']): SeriesType => {
|
|
21
41
|
|
|
22
42
|
const [label, x, y, z, index, subtype, data_labels] = data;
|
|
@@ -111,10 +131,7 @@ export const ReadSeries = (data: ReferenceSeries['value']): SeriesType => {
|
|
|
111
131
|
// in case of no values
|
|
112
132
|
if (subseries.data.length) {
|
|
113
133
|
const values = subseries.data.filter(value => value || value === 0) as number[];
|
|
114
|
-
subseries.range =
|
|
115
|
-
min: Math.min.apply(0, values),
|
|
116
|
-
max: Math.max.apply(0, values),
|
|
117
|
-
};
|
|
134
|
+
subseries.range = ArrayMinMax(values);
|
|
118
135
|
}
|
|
119
136
|
}
|
|
120
137
|
|
|
@@ -174,10 +191,7 @@ export const ArrayToSeries = (array_data: ArrayUnion): SeriesType => {
|
|
|
174
191
|
}
|
|
175
192
|
|
|
176
193
|
const values = series.y.data.filter(value => value || value === 0) as number[];
|
|
177
|
-
series.y.range =
|
|
178
|
-
min: Math.min.apply(0, values),
|
|
179
|
-
max: Math.max.apply(0, values),
|
|
180
|
-
};
|
|
194
|
+
series.y.range = ArrayMinMax(values);
|
|
181
195
|
|
|
182
196
|
// experimenting with complex... this should only be set if we populated
|
|
183
197
|
// it from complex values
|
|
@@ -185,10 +199,7 @@ export const ArrayToSeries = (array_data: ArrayUnion): SeriesType => {
|
|
|
185
199
|
if (series.x.data.length) {
|
|
186
200
|
|
|
187
201
|
const filtered: number[] = series.x.data.filter(test => typeof test === 'number') as number[];
|
|
188
|
-
series.x.range =
|
|
189
|
-
min: Math.min.apply(0, filtered),
|
|
190
|
-
max: Math.max.apply(0, filtered),
|
|
191
|
-
}
|
|
202
|
+
series.x.range = ArrayMinMax(filtered);
|
|
192
203
|
|
|
193
204
|
if (first_format) {
|
|
194
205
|
series.x.format = first_format;
|
|
@@ -281,10 +292,7 @@ export const TransformSeriesData = (raw_data?: UnionValue, default_x?: UnionValu
|
|
|
281
292
|
baseline_x = {
|
|
282
293
|
data,
|
|
283
294
|
format,
|
|
284
|
-
range:
|
|
285
|
-
min: Math.min.apply(0, filtered),
|
|
286
|
-
max: Math.max.apply(0, filtered),
|
|
287
|
-
}
|
|
295
|
+
range: ArrayMinMax(filtered),
|
|
288
296
|
}
|
|
289
297
|
}
|
|
290
298
|
|
|
@@ -511,26 +519,27 @@ const ApplyLabels = (series_list: SeriesType[], pattern: string, category_labels
|
|
|
511
519
|
*/
|
|
512
520
|
export const BoxStats = (data: number[]) => {
|
|
513
521
|
|
|
514
|
-
|
|
515
|
-
|
|
522
|
+
// removed copying. still has 3 loops though.
|
|
523
|
+
|
|
524
|
+
const median = (data: number[], start = 0, n = data.length) => {
|
|
516
525
|
if (n % 2) {
|
|
517
|
-
return data[Math.floor(n/2)];
|
|
526
|
+
return data[Math.floor(n/2) + start];
|
|
518
527
|
}
|
|
519
528
|
else {
|
|
520
|
-
return (data[n/2] + data[n/2 - 1])/2;
|
|
529
|
+
return (data[n/2 + start] + data[n/2 - 1 + start])/2;
|
|
521
530
|
}
|
|
522
531
|
};
|
|
523
532
|
|
|
524
533
|
const n = data.length;
|
|
525
534
|
const quartiles: [number, number, number] = [0, median(data), 0];
|
|
526
|
-
|
|
527
535
|
if (n % 2) {
|
|
528
|
-
|
|
529
|
-
quartiles[
|
|
536
|
+
const floor = Math.floor(n/2);
|
|
537
|
+
quartiles[0] = median(data, 0, Math.ceil(n/2));
|
|
538
|
+
quartiles[2] = median(data, floor, data.length - floor);
|
|
530
539
|
}
|
|
531
540
|
else {
|
|
532
|
-
quartiles[0] = median(data
|
|
533
|
-
quartiles[2] = median(data.
|
|
541
|
+
quartiles[0] = median(data, 0, n/2);
|
|
542
|
+
quartiles[2] = median(data, n/2, data.length - n/2);
|
|
534
543
|
}
|
|
535
544
|
|
|
536
545
|
const iqr = quartiles[2] - quartiles[0];
|
|
@@ -556,7 +565,7 @@ export const BoxStats = (data: number[]) => {
|
|
|
556
565
|
break;
|
|
557
566
|
}
|
|
558
567
|
}
|
|
559
|
-
|
|
568
|
+
|
|
560
569
|
return {
|
|
561
570
|
data,
|
|
562
571
|
quartiles,
|
|
@@ -581,7 +590,13 @@ export const CreateBoxPlot = (args: UnionValue[]): ChartData => {
|
|
|
581
590
|
let max_n = 0;
|
|
582
591
|
|
|
583
592
|
const stats: BoxPlotData['data'] = series.map(series => {
|
|
584
|
-
const data = series.y.data.slice(0).filter((test): test is number => test !== undefined).sort((a, b) => a - b);
|
|
593
|
+
// const data = series.y.data.slice(0).filter((test): test is number => test !== undefined).sort((a, b) => a - b);
|
|
594
|
+
const data: number[] = [];
|
|
595
|
+
for (const entry of series.y.data) {
|
|
596
|
+
if (entry !== undefined) { data.push(entry); }
|
|
597
|
+
}
|
|
598
|
+
data.sort((a, b) => a - b);
|
|
599
|
+
|
|
585
600
|
const result = BoxStats(data);
|
|
586
601
|
max_n = Math.max(max_n, result.n);
|
|
587
602
|
return result;
|
|
@@ -48,6 +48,16 @@ export interface SerializedNamedExpression {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
export interface SerializedModel {
|
|
51
|
+
|
|
52
|
+
// has this been superceded by TREBDocument?
|
|
53
|
+
// I can't tell why we're still using this.
|
|
54
|
+
|
|
55
|
+
// ...
|
|
56
|
+
|
|
57
|
+
// it seems like TREBDocument was a replacment,
|
|
58
|
+
// but it has some required fields that this type
|
|
59
|
+
// doesn't have.
|
|
60
|
+
|
|
51
61
|
sheet_data: SerializedSheet[];
|
|
52
62
|
active_sheet: number;
|
|
53
63
|
|
|
@@ -2846,7 +2846,7 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
2846
2846
|
*
|
|
2847
2847
|
* @internal
|
|
2848
2848
|
*/
|
|
2849
|
-
public async ExportBlob(): Promise<Blob> {
|
|
2849
|
+
public async ExportBlob(serialized?: SerializedModel): Promise<Blob> {
|
|
2850
2850
|
|
|
2851
2851
|
// this is inlined to ensure the code will be tree-shaken properly
|
|
2852
2852
|
if (!process.env.XLSX_SUPPORT) {
|
|
@@ -2870,19 +2870,19 @@ export class EmbeddedSpreadsheet<USER_DATA_TYPE = unknown> {
|
|
|
2870
2870
|
reject(event);
|
|
2871
2871
|
};
|
|
2872
2872
|
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
}
|
|
2885
|
-
|
|
2873
|
+
if (!serialized) {
|
|
2874
|
+
serialized = this.Serialize({
|
|
2875
|
+
rendered_values: true,
|
|
2876
|
+
expand_arrays: true,
|
|
2877
|
+
export_colors: true,
|
|
2878
|
+
decorated_cells: true,
|
|
2879
|
+
tables: true,
|
|
2880
|
+
share_resources: false,
|
|
2881
|
+
export_functions: true,
|
|
2882
|
+
apply_row_pattern: true, // if there's a row pattern, set it on rows so they export properly
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
2886
|
// why do _we_ put this in, instead of the grid method?
|
|
2887
2887
|
serialized.decimal_mark = Localization.decimal_separator;
|
|
2888
2888
|
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
--treb-grid-background: rgb(30,30,30);
|
|
98
98
|
--treb-grid-header-background: rgb(86,86,86);
|
|
99
99
|
--treb-grid-header-color: rgb(221,221,221);
|
|
100
|
-
--treb-tab-bar-active-tab-background:
|
|
100
|
+
--treb-tab-bar-active-tab-background: #444;
|
|
101
101
|
--treb-tab-bar-tab-background: transparent;
|
|
102
102
|
--treb-tab-bar-tab-border-color: #444;
|
|
103
103
|
--treb-ui-border-color: #aaa;
|
|
@@ -2076,12 +2076,33 @@ export class Exporter {
|
|
|
2076
2076
|
}
|
|
2077
2077
|
}
|
|
2078
2078
|
|
|
2079
|
+
const color_series: {
|
|
2080
|
+
rgb?: string;
|
|
2081
|
+
tint?: string;
|
|
2082
|
+
theme?: string;
|
|
2083
|
+
} = {
|
|
2084
|
+
rgb: 'FF376092' // default
|
|
2085
|
+
};
|
|
2086
|
+
|
|
2087
|
+
if (sparkline.style?.text) {
|
|
2088
|
+
if (IsHTMLColor(sparkline.style.text)) {
|
|
2089
|
+
color_series.rgb = sparkline.style.text.text;
|
|
2090
|
+
}
|
|
2091
|
+
else if (IsThemeColor(sparkline.style.text)) {
|
|
2092
|
+
color_series.rgb = undefined;
|
|
2093
|
+
color_series.theme = sparkline.style.text.theme.toString();
|
|
2094
|
+
color_series.tint = typeof sparkline.style.text.tint === 'number' ?
|
|
2095
|
+
sparkline.style.text.tint.toString() : undefined;
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2079
2099
|
return {
|
|
2080
2100
|
a$: {
|
|
2081
2101
|
displayEmptyCellsAs: 'gap',
|
|
2102
|
+
displayHidden: '1',
|
|
2082
2103
|
type: /column/i.test(sparkline.formula) ? 'column' : undefined,
|
|
2083
2104
|
},
|
|
2084
|
-
'x14:colorSeries': { a$: {
|
|
2105
|
+
'x14:colorSeries': { a$: { ...color_series }},
|
|
2085
2106
|
'x14:sparklines': {
|
|
2086
2107
|
'x14:sparkline': {
|
|
2087
2108
|
'xm:f': source,
|
|
@@ -4014,7 +4014,15 @@ export class Grid extends GridBase {
|
|
|
4014
4014
|
// unless we're selecting an argument, close the ICE
|
|
4015
4015
|
|
|
4016
4016
|
if (this.overlay_editor?.editing && !this.overlay_editor?.selecting) {
|
|
4017
|
-
|
|
4017
|
+
|
|
4018
|
+
// commit
|
|
4019
|
+
|
|
4020
|
+
if (this.overlay_editor?.selection) {
|
|
4021
|
+
const value = this.overlay_editor?.edit_node.textContent || undefined;
|
|
4022
|
+
this.SetInferredType(this.overlay_editor.selection, value, false);
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
this.DismissEditor();
|
|
4018
4026
|
}
|
|
4019
4027
|
|
|
4020
4028
|
const offset_point = {
|