@trebco/treb 30.6.2 → 30.8.2
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 +9 -9
- package/dist/treb.d.ts +1 -1
- package/esbuild-utils.mjs +3 -1
- package/package.json +1 -1
- package/treb-calculator/src/calculator.ts +29 -6
- package/treb-calculator/src/expression-calculator.ts +107 -3
- package/treb-calculator/src/functions/base-functions.ts +78 -2
- package/treb-calculator/src/functions/sparkline.ts +20 -0
- package/treb-calculator/src/functions/statistics-functions.ts +2 -0
- package/treb-charts/src/chart-functions.ts +1 -0
- package/treb-charts/src/chart-utils.ts +81 -2
- package/treb-charts/src/quicksort.ts +54 -0
- package/treb-data-model/src/conditional_format.ts +4 -1
- package/treb-data-model/src/sheet.ts +4 -3
- package/treb-embed/src/embedded-spreadsheet.ts +58 -2
- package/treb-export/src/import2.ts +75 -6
- package/treb-export/src/workbook2.ts +5 -0
- package/treb-grid/src/editors/editor.ts +1 -1
- package/treb-parser/src/parser.ts +14 -4
package/dist/treb.d.ts
CHANGED
package/esbuild-utils.mjs
CHANGED
|
@@ -74,7 +74,7 @@ export const FormatSize = (size, precision = 1) => {
|
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* @function
|
|
77
|
-
* @param {{verbose?: boolean, minify?: boolean}} [options]
|
|
77
|
+
* @param {{verbose?: boolean, minify?: boolean, define?: Record<string, string>}} [options]
|
|
78
78
|
* @returns {esbuild.Plugin}
|
|
79
79
|
*
|
|
80
80
|
* inlining the worker build. this works out well with one limitation:
|
|
@@ -147,6 +147,8 @@ export const WorkerPlugin = (options) => ({
|
|
|
147
147
|
bundle: true,
|
|
148
148
|
format: 'esm',
|
|
149
149
|
|
|
150
|
+
define: options?.define,
|
|
151
|
+
|
|
150
152
|
// don't write to filesystem
|
|
151
153
|
write: false,
|
|
152
154
|
|
package/package.json
CHANGED
|
@@ -2383,6 +2383,9 @@ export class Calculator extends Graph {
|
|
|
2383
2383
|
// is it also (3) adding unecessary calculations (building the expression,
|
|
2384
2384
|
// below)?
|
|
2385
2385
|
|
|
2386
|
+
// NOTE: moving all conditional formats into EN-US (i.e. dot-separated).
|
|
2387
|
+
// make sure to evaluate them in this format.
|
|
2388
|
+
|
|
2386
2389
|
if (!list) {
|
|
2387
2390
|
|
|
2388
2391
|
// we could in theory remove all of the leaves (the ones we know to
|
|
@@ -2416,8 +2419,15 @@ export class Calculator extends Graph {
|
|
|
2416
2419
|
let expression = '';
|
|
2417
2420
|
|
|
2418
2421
|
switch (entry.type) {
|
|
2422
|
+
|
|
2419
2423
|
case 'cell-match':
|
|
2420
|
-
|
|
2424
|
+
if (entry.between) {
|
|
2425
|
+
const addr = this.Unresolve(entry.area, context, true, false);
|
|
2426
|
+
expression = `BETWEEN(${[addr, ...entry.between].join(', ')})`;
|
|
2427
|
+
}
|
|
2428
|
+
else {
|
|
2429
|
+
expression = this.Unresolve(entry.area, context, true, false) + ' ' + entry.expression;
|
|
2430
|
+
}
|
|
2421
2431
|
break;
|
|
2422
2432
|
|
|
2423
2433
|
case 'expression':
|
|
@@ -2440,7 +2450,7 @@ export class Calculator extends Graph {
|
|
|
2440
2450
|
entry.min ?? '',
|
|
2441
2451
|
entry.max ?? '',
|
|
2442
2452
|
|
|
2443
|
-
].join(
|
|
2453
|
+
].join(',')
|
|
2444
2454
|
})`;
|
|
2445
2455
|
break;
|
|
2446
2456
|
|
|
@@ -2464,13 +2474,17 @@ export class Calculator extends Graph {
|
|
|
2464
2474
|
|
|
2465
2475
|
entry.internal.vertex = vertex;
|
|
2466
2476
|
|
|
2467
|
-
let options: EvaluateOptions
|
|
2477
|
+
let options: EvaluateOptions = {
|
|
2478
|
+
argument_separator: ',',
|
|
2479
|
+
};
|
|
2480
|
+
|
|
2468
2481
|
if (entry.type !== 'gradient' && entry.type !== 'duplicate-values') {
|
|
2469
|
-
options = entry.options;
|
|
2482
|
+
options = {...entry.options, ...options};
|
|
2470
2483
|
}
|
|
2471
2484
|
|
|
2472
2485
|
// first pass, run the calculation
|
|
2473
2486
|
const check = this.Evaluate(expression, context, options, true);
|
|
2487
|
+
|
|
2474
2488
|
entry.internal.vertex.result = check;
|
|
2475
2489
|
entry.internal.vertex.updated = true;
|
|
2476
2490
|
|
|
@@ -2478,7 +2492,7 @@ export class Calculator extends Graph {
|
|
|
2478
2492
|
|
|
2479
2493
|
const vertex = entry.internal.vertex as LeafVertex;
|
|
2480
2494
|
this.AddLeafVertex(vertex);
|
|
2481
|
-
this.UpdateLeafVertex(vertex, expression, context);
|
|
2495
|
+
this.UpdateLeafVertex(vertex, expression, context, DecimalMarkType.Period); // force en-us
|
|
2482
2496
|
|
|
2483
2497
|
}
|
|
2484
2498
|
|
|
@@ -2846,10 +2860,15 @@ export class Calculator extends Graph {
|
|
|
2846
2860
|
return dependencies;
|
|
2847
2861
|
}
|
|
2848
2862
|
|
|
2849
|
-
protected UpdateLeafVertex(vertex: LeafVertex, formula: string, context: Sheet): void {
|
|
2863
|
+
protected UpdateLeafVertex(vertex: LeafVertex, formula: string, context: Sheet, decimal_mark?: DecimalMarkType): void {
|
|
2850
2864
|
|
|
2851
2865
|
vertex.Reset();
|
|
2852
2866
|
|
|
2867
|
+
if (decimal_mark) {
|
|
2868
|
+
this.parser.Save();
|
|
2869
|
+
this.parser.SetLocaleSettings(decimal_mark);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2853
2872
|
const parse_result = this.parser.Parse(formula);
|
|
2854
2873
|
if (parse_result.expression) {
|
|
2855
2874
|
const dependencies =
|
|
@@ -2895,6 +2914,10 @@ export class Calculator extends Graph {
|
|
|
2895
2914
|
|
|
2896
2915
|
// vertex.UpdateState();
|
|
2897
2916
|
|
|
2917
|
+
if (decimal_mark) {
|
|
2918
|
+
this.parser.Restore();
|
|
2919
|
+
}
|
|
2920
|
+
|
|
2898
2921
|
}
|
|
2899
2922
|
|
|
2900
2923
|
/**
|
|
@@ -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, SpillError } from './function-error';
|
|
34
|
+
import { NameError, ReferenceError, ExpressionError, UnknownError, SpillError, ValueError } from './function-error';
|
|
35
35
|
|
|
36
36
|
import * as Primitives from './primitives';
|
|
37
37
|
|
|
@@ -633,7 +633,7 @@ export class ExpressionCalculator {
|
|
|
633
633
|
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
-
protected UnaryExpression(x: UnitUnary): (expr: UnitUnary) => UnionValue /*UnionOrArray*/ { // operator: string, operand: any){
|
|
636
|
+
protected UnaryExpression(x: UnitUnary, return_reference = false): (expr: UnitUnary) => UnionValue /*UnionOrArray*/ { // operator: string, operand: any){
|
|
637
637
|
|
|
638
638
|
// there are basically three code paths here: negate, identity, and error.
|
|
639
639
|
// they have very different semantics so we're going to do them completely
|
|
@@ -663,6 +663,110 @@ export class ExpressionCalculator {
|
|
|
663
663
|
|
|
664
664
|
}
|
|
665
665
|
|
|
666
|
+
case '@':
|
|
667
|
+
return (expr: UnitUnary) => {
|
|
668
|
+
|
|
669
|
+
// if the operand is a range, then we need to do implicit intersection.
|
|
670
|
+
// otherwise, calculate the expression and return the first value
|
|
671
|
+
// if it's an array.
|
|
672
|
+
|
|
673
|
+
let address: UnitAddress|undefined;
|
|
674
|
+
|
|
675
|
+
switch (expr.operand.type) {
|
|
676
|
+
case 'address':
|
|
677
|
+
if (expr.operand.spill) {
|
|
678
|
+
|
|
679
|
+
// we need to calculate the result so we know how large the
|
|
680
|
+
// range is... perhaps there's a way to look this up without
|
|
681
|
+
// calculating? (TODO/FIXME)
|
|
682
|
+
|
|
683
|
+
const calculated = this.CellFunction2(expr.operand)();
|
|
684
|
+
if (calculated.type === ValueType.array) {
|
|
685
|
+
|
|
686
|
+
const row = this.context.address.row ?? -1;
|
|
687
|
+
const column = this.context.address.column ?? -1;
|
|
688
|
+
|
|
689
|
+
// for this verison we already have the result, so unless
|
|
690
|
+
// we're trying to preserve the address, we could just
|
|
691
|
+
// return it
|
|
692
|
+
|
|
693
|
+
if (row >= expr.operand.row && row < expr.operand.row + calculated.value[0]?.length && calculated.value.length === 1) {
|
|
694
|
+
|
|
695
|
+
if (!return_reference) {
|
|
696
|
+
return calculated.value[0][row - expr.operand.row];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
address = {
|
|
700
|
+
...expr.operand,
|
|
701
|
+
row,
|
|
702
|
+
spill: false,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
else if (column >= expr.operand.column && column < expr.operand.column + calculated.value.length && calculated.value[0]?.length === 1) {
|
|
706
|
+
|
|
707
|
+
if (!return_reference) {
|
|
708
|
+
return calculated.value[column - expr.operand.column][0];
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
address = {
|
|
712
|
+
...expr.operand,
|
|
713
|
+
column,
|
|
714
|
+
spill: false,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
return ValueError(); // out of range
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// return { type: ValueType.string, value: 'implicit (spill)' };
|
|
724
|
+
}
|
|
725
|
+
break;
|
|
726
|
+
|
|
727
|
+
case 'range':
|
|
728
|
+
{
|
|
729
|
+
// how do we intersect, if at all?
|
|
730
|
+
|
|
731
|
+
const row = this.context.address.row ?? -1;
|
|
732
|
+
const column = this.context.address.column ?? -1;
|
|
733
|
+
if (row >= expr.operand.start.row && row <= expr.operand.end.row && expr.operand.start.column === expr.operand.end.column) {
|
|
734
|
+
address = {
|
|
735
|
+
...expr.operand.start,
|
|
736
|
+
row,
|
|
737
|
+
spill: false,
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
else if (column >= expr.operand.start.column && column <= expr.operand.end.column && expr.operand.start.row === expr.operand.end.row) {
|
|
741
|
+
address = {
|
|
742
|
+
...expr.operand.start,
|
|
743
|
+
column,
|
|
744
|
+
spill: false,
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
return ValueError(); // out of range
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (address) {
|
|
754
|
+
if (return_reference) {
|
|
755
|
+
return { type: ValueType.object, value: address,
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return this.CellFunction2(address)();
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const operand = this.CalculateExpression(expr.operand as ExtendedExpressionUnit);
|
|
762
|
+
|
|
763
|
+
if (operand.type === ValueType.array) {
|
|
764
|
+
return operand.value[0][0];
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
return operand;
|
|
768
|
+
};
|
|
769
|
+
|
|
666
770
|
default:
|
|
667
771
|
return () => {
|
|
668
772
|
console.warn('unexpected unary operator:', x.operator);
|
|
@@ -977,7 +1081,7 @@ export class ExpressionCalculator {
|
|
|
977
1081
|
return (expr.fn = this.BinaryExpression(expr))(expr); // check
|
|
978
1082
|
|
|
979
1083
|
case 'unary':
|
|
980
|
-
return (expr.fn = this.UnaryExpression(expr))(expr); // check
|
|
1084
|
+
return (expr.fn = this.UnaryExpression(expr, return_reference))(expr); // check
|
|
981
1085
|
|
|
982
1086
|
case 'identifier':
|
|
983
1087
|
return (expr.fn = this.Identifier(expr))(); // check
|
|
@@ -244,6 +244,23 @@ const ZLookup = (value: number|string|boolean|undefined, table: (number|string|b
|
|
|
244
244
|
|
|
245
245
|
};
|
|
246
246
|
|
|
247
|
+
const NumberArgument = (argument?: UnionValue, default_value: number|false = false) => {
|
|
248
|
+
|
|
249
|
+
if (!argument) {
|
|
250
|
+
return default_value;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
switch (argument.type) {
|
|
254
|
+
case ValueType.number:
|
|
255
|
+
return argument.value;
|
|
256
|
+
case ValueType.undefined:
|
|
257
|
+
return default_value;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return false;
|
|
261
|
+
|
|
262
|
+
};
|
|
263
|
+
|
|
247
264
|
/**
|
|
248
265
|
* alternate functions. these are used (atm) only for changing complex
|
|
249
266
|
* behavior.
|
|
@@ -534,7 +551,6 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
534
551
|
value: delta / divisor,
|
|
535
552
|
};
|
|
536
553
|
|
|
537
|
-
return NAError();
|
|
538
554
|
},
|
|
539
555
|
},
|
|
540
556
|
|
|
@@ -1924,6 +1940,17 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
1924
1940
|
},
|
|
1925
1941
|
},
|
|
1926
1942
|
|
|
1943
|
+
Ceiling: {
|
|
1944
|
+
arguments: [ { unroll: true }, { unroll: true } ], // FIXME: lazy
|
|
1945
|
+
|
|
1946
|
+
fn: (a: number) => {
|
|
1947
|
+
return {
|
|
1948
|
+
type: ValueType.number,
|
|
1949
|
+
value: Math.ceil(a),
|
|
1950
|
+
};
|
|
1951
|
+
},
|
|
1952
|
+
},
|
|
1953
|
+
|
|
1927
1954
|
Round: {
|
|
1928
1955
|
arguments: [ { unroll: true }, { unroll: true } ], // FIXME: lazy
|
|
1929
1956
|
|
|
@@ -2359,6 +2386,21 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
2359
2386
|
},
|
|
2360
2387
|
},
|
|
2361
2388
|
|
|
2389
|
+
Between: {
|
|
2390
|
+
arguments: [
|
|
2391
|
+
{ name: 'target', boxed: true, unroll: true },
|
|
2392
|
+
{ name: 'min' },
|
|
2393
|
+
{ name: 'max' },
|
|
2394
|
+
],
|
|
2395
|
+
visibility: 'internal',
|
|
2396
|
+
fn: (target: UnionValue, min = 0, max = 1) => {
|
|
2397
|
+
return {
|
|
2398
|
+
type: ValueType.boolean,
|
|
2399
|
+
value: (target.type === ValueType.number) && (target.value >= min && target.value <= max),
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
},
|
|
2403
|
+
|
|
2362
2404
|
Gradient: {
|
|
2363
2405
|
arguments: [
|
|
2364
2406
|
{ name: 'range', boxed: true },
|
|
@@ -2510,7 +2552,41 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
2510
2552
|
return ArgumentError();
|
|
2511
2553
|
|
|
2512
2554
|
},
|
|
2513
|
-
}
|
|
2555
|
+
},
|
|
2556
|
+
|
|
2557
|
+
Sequence: {
|
|
2558
|
+
arguments:[
|
|
2559
|
+
{ name: 'rows', boxed: true },
|
|
2560
|
+
{ name: 'columns', default: 1, boxed: true },
|
|
2561
|
+
{ name: 'start', default: 1, boxed: true },
|
|
2562
|
+
{ name: 'step', default: 1, boxed: true }
|
|
2563
|
+
],
|
|
2564
|
+
fn: (rows: UnionValue, columns: UnionValue, start: UnionValue, step: UnionValue) => {
|
|
2565
|
+
|
|
2566
|
+
const rx = NumberArgument(rows, 1);
|
|
2567
|
+
const cx = NumberArgument(columns, 1);
|
|
2568
|
+
const step_ = NumberArgument(step, 1);
|
|
2569
|
+
const start_ = NumberArgument(start, 1);
|
|
2570
|
+
|
|
2571
|
+
if (rx === false || cx === false || step_ === false || start_ === false) {
|
|
2572
|
+
return ArgumentError();
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
const value: UnionValue[][] = [];
|
|
2576
|
+
for (let c = 0; c < cx; c++) {
|
|
2577
|
+
const col: UnionValue[] = [];
|
|
2578
|
+
for (let r = 0; r < rx; r++) {
|
|
2579
|
+
col.push({ type: ValueType.number, value: start_ + r * step_ * cx + c * step_ });
|
|
2580
|
+
}
|
|
2581
|
+
value.push(col);
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
return { type: ValueType.array, value };
|
|
2585
|
+
|
|
2586
|
+
},
|
|
2587
|
+
|
|
2588
|
+
},
|
|
2589
|
+
|
|
2514
2590
|
|
|
2515
2591
|
};
|
|
2516
2592
|
|
|
@@ -173,6 +173,16 @@ export class Sparkline {
|
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
if (min === max) {
|
|
177
|
+
if (min) {
|
|
178
|
+
min -= 1;
|
|
179
|
+
max += 1;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
max += 1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
176
186
|
if (min !== max) {
|
|
177
187
|
|
|
178
188
|
const step = (width * (1 - 2 * x_margin)) / (values.length - 1);
|
|
@@ -251,6 +261,16 @@ export class Sparkline {
|
|
|
251
261
|
}
|
|
252
262
|
}
|
|
253
263
|
|
|
264
|
+
if (min === max) {
|
|
265
|
+
if (min) {
|
|
266
|
+
min -= 1;
|
|
267
|
+
max += 1;
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
max += 1;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
254
274
|
if (values.length) {
|
|
255
275
|
|
|
256
276
|
// const step = (width - 2 * x_margin - 2) / (values.length-1);
|
|
@@ -685,6 +685,8 @@ export const StatisticsFunctionLibrary: FunctionMap = {
|
|
|
685
685
|
export const StatisticsFunctionAliases: {[index: string]: string} = {
|
|
686
686
|
Mean: 'Average',
|
|
687
687
|
'StDev': 'StDev.S',
|
|
688
|
+
'StDevA': 'StDev.S',
|
|
689
|
+
'StDevPA': 'StDev.P',
|
|
688
690
|
'Var': 'Var.S',
|
|
689
691
|
'Quartile': 'Quartile.Inc',
|
|
690
692
|
};
|
|
@@ -196,6 +196,7 @@ export const ChartFunctions: Record<ChartFunction|SupportFunction, CompositeFunc
|
|
|
196
196
|
arguments: [
|
|
197
197
|
{ name: 'Data', metadata: true, },
|
|
198
198
|
{ name: 'Chart Title' },
|
|
199
|
+
{ name: 'Min/Max Style' }
|
|
199
200
|
],
|
|
200
201
|
fn: Identity,
|
|
201
202
|
category: ['chart functions'],
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of TREB.
|
|
3
|
+
*
|
|
4
|
+
* TREB is free software: you can redistribute it and/or modify it under the
|
|
5
|
+
* terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
* Foundation, either version 3 of the License, or (at your option) any
|
|
7
|
+
* later version.
|
|
8
|
+
*
|
|
9
|
+
* TREB is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
10
|
+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
11
|
+
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
12
|
+
* details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License along
|
|
15
|
+
* with TREB. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*
|
|
17
|
+
* Copyright 2022-2024 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
1
21
|
|
|
2
22
|
import { type UnionValue, ValueType, type ArrayUnion, IsComplex, type CellValue } from 'treb-base-types';
|
|
3
23
|
import { IsArrayUnion, IsMetadata, IsSeries, LegendStyle } from './chart-types';
|
|
@@ -7,6 +27,8 @@ import { Util } from './util';
|
|
|
7
27
|
import type { ReferenceSeries } from './chart-types';
|
|
8
28
|
import type { RangeScale } from 'treb-utils';
|
|
9
29
|
|
|
30
|
+
import { QuickSort } from './quicksort';
|
|
31
|
+
|
|
10
32
|
/**
|
|
11
33
|
* this file is the concrete translation from function arguments
|
|
12
34
|
* to chart data. chart data is a (somewhat complicated) type with
|
|
@@ -148,6 +170,48 @@ export const ArrayToSeries = (array_data: ArrayUnion): SeriesType => {
|
|
|
148
170
|
|
|
149
171
|
// series.y.data = flat.map(item => typeof item.value === 'number' ? item.value : undefined);
|
|
150
172
|
|
|
173
|
+
// console.trace();
|
|
174
|
+
|
|
175
|
+
const values: number[] = []; // filter any undefineds
|
|
176
|
+
|
|
177
|
+
for (const [index, item] of flat.entries()) {
|
|
178
|
+
|
|
179
|
+
let value = 0;
|
|
180
|
+
|
|
181
|
+
// why is this testing type instead of using the union type?
|
|
182
|
+
|
|
183
|
+
if (typeof item.value === 'number') {
|
|
184
|
+
value = item.value;
|
|
185
|
+
// series.y.data[index] = item.value;
|
|
186
|
+
values.push(item.value);
|
|
187
|
+
}
|
|
188
|
+
else if (IsMetadata(item)) {
|
|
189
|
+
if (IsComplex(item.value.value)) {
|
|
190
|
+
series.x.data[index] = item.value.value.real;
|
|
191
|
+
// series.y.data[index] = item.value.value.imaginary;
|
|
192
|
+
// values.push(item.value.value.imaginary);
|
|
193
|
+
value = item.value.value.imaginary;
|
|
194
|
+
}
|
|
195
|
+
else if (typeof item.value.value === 'number') {
|
|
196
|
+
// series.y.data[index] = item.value.value;
|
|
197
|
+
// values.push(item.value.value);
|
|
198
|
+
value = item.value.value;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
// series.y.data[index] = undefined;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
series.y.data[index] = value;
|
|
210
|
+
values.push(value);
|
|
211
|
+
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/*
|
|
151
215
|
series.y.data = flat.map((item, index) => {
|
|
152
216
|
|
|
153
217
|
// if the data is passed in from the output of a function, it will not
|
|
@@ -178,6 +242,7 @@ export const ArrayToSeries = (array_data: ArrayUnion): SeriesType => {
|
|
|
178
242
|
return undefined;
|
|
179
243
|
|
|
180
244
|
});
|
|
245
|
+
*/
|
|
181
246
|
|
|
182
247
|
let first_format = '';
|
|
183
248
|
if (IsMetadata(flat[0])) {
|
|
@@ -190,7 +255,9 @@ export const ArrayToSeries = (array_data: ArrayUnion): SeriesType => {
|
|
|
190
255
|
series.y.labels = series.y.data.map(value => (value === undefined) ? undefined : format.Format(value));
|
|
191
256
|
}
|
|
192
257
|
|
|
193
|
-
|
|
258
|
+
// moved up, integrated loops
|
|
259
|
+
// const values = series.y.data.filter(value => value || value === 0) as number[];
|
|
260
|
+
|
|
194
261
|
series.y.range = ArrayMinMax(values);
|
|
195
262
|
|
|
196
263
|
// experimenting with complex... this should only be set if we populated
|
|
@@ -589,19 +656,31 @@ export const CreateBoxPlot = (args: UnionValue[]): ChartData => {
|
|
|
589
656
|
|
|
590
657
|
let max_n = 0;
|
|
591
658
|
|
|
659
|
+
// change to min-max style
|
|
660
|
+
const minmax = !!args[2];
|
|
661
|
+
|
|
592
662
|
const stats: BoxPlotData['data'] = series.map(series => {
|
|
593
663
|
// const data = series.y.data.slice(0).filter((test): test is number => test !== undefined).sort((a, b) => a - b);
|
|
594
664
|
const data: number[] = [];
|
|
595
665
|
for (const entry of series.y.data) {
|
|
596
666
|
if (entry !== undefined) { data.push(entry); }
|
|
597
667
|
}
|
|
598
|
-
|
|
668
|
+
|
|
669
|
+
// data.sort((a, b) => a - b);
|
|
670
|
+
QuickSort(data);
|
|
599
671
|
|
|
600
672
|
const result = BoxStats(data);
|
|
601
673
|
max_n = Math.max(max_n, result.n);
|
|
674
|
+
|
|
675
|
+
if (minmax) {
|
|
676
|
+
result.whiskers[0] = result.min;
|
|
677
|
+
result.whiskers[1] = result.max;
|
|
678
|
+
}
|
|
679
|
+
|
|
602
680
|
return result;
|
|
603
681
|
});
|
|
604
682
|
|
|
683
|
+
|
|
605
684
|
const title = args[1]?.toString() || undefined;
|
|
606
685
|
const x_labels: string[] = [];
|
|
607
686
|
const series_names: string[] = [];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file is part of TREB.
|
|
3
|
+
*
|
|
4
|
+
* TREB is free software: you can redistribute it and/or modify it under the
|
|
5
|
+
* terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
* Foundation, either version 3 of the License, or (at your option) any
|
|
7
|
+
* later version.
|
|
8
|
+
*
|
|
9
|
+
* TREB is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
10
|
+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
11
|
+
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
12
|
+
* details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License along
|
|
15
|
+
* with TREB. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
*
|
|
17
|
+
* Copyright 2022-2024 trebco, llc.
|
|
18
|
+
* info@treb.app
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// FIXME: move
|
|
23
|
+
|
|
24
|
+
export const QuickSort = (data: number[]) => {
|
|
25
|
+
Sort(data, 0, data.length);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const Partition = (data: number[], left: number, right: number) => {
|
|
29
|
+
const compare = data[right - 1];
|
|
30
|
+
let min_end = left;
|
|
31
|
+
for (let max_end = left; max_end < right - 1; max_end += 1) {
|
|
32
|
+
if (data[max_end] <= compare) {
|
|
33
|
+
Swap(data, max_end, min_end);
|
|
34
|
+
min_end += 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
Swap(data, min_end, right - 1);
|
|
38
|
+
return min_end;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const Swap = (data: number[], i: number, j: number) => {
|
|
42
|
+
const temp = data[i];
|
|
43
|
+
data[i] = data[j];
|
|
44
|
+
data[j] = temp;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const Sort = (data: number[], left: number, right: number) => {
|
|
48
|
+
if (left < right) {
|
|
49
|
+
const p = Partition(data, left, right);
|
|
50
|
+
Sort(data, left, p);
|
|
51
|
+
Sort(data, p + 1, right);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
@@ -139,6 +139,7 @@ export interface ConditionalFormatCellMatch extends ConditionalFormatCellMatchOp
|
|
|
139
139
|
export interface ConditionalFormatCellMatchOptions {
|
|
140
140
|
style: CellStyle;
|
|
141
141
|
expression: string;
|
|
142
|
+
between?: [number, number];
|
|
142
143
|
options?: EvaluateOptions;
|
|
143
144
|
}
|
|
144
145
|
|
|
@@ -178,8 +179,10 @@ export interface ConditionalFormatDuplicateValues extends ConditionalFormatDupli
|
|
|
178
179
|
*
|
|
179
180
|
* ...everybody has a vertex now, we could standardize it
|
|
180
181
|
*
|
|
182
|
+
* update: adding a priority field, optional
|
|
183
|
+
*
|
|
181
184
|
*/
|
|
182
|
-
export type ConditionalFormat = { internal?: unknown } & (
|
|
185
|
+
export type ConditionalFormat = { internal?: unknown, priority?: number } & (
|
|
183
186
|
ConditionalFormatDuplicateValues |
|
|
184
187
|
ConditionalFormatExpression |
|
|
185
188
|
ConditionalFormatCellMatch |
|
|
@@ -3338,9 +3338,6 @@ export class Sheet {
|
|
|
3338
3338
|
for (const format of this.conditional_formats) {
|
|
3339
3339
|
|
|
3340
3340
|
if (format.internal?.vertex?.updated) {
|
|
3341
|
-
|
|
3342
|
-
// console.info('updated');
|
|
3343
|
-
|
|
3344
3341
|
format.internal.vertex.updated = false;
|
|
3345
3342
|
}
|
|
3346
3343
|
|
|
@@ -3354,6 +3351,10 @@ export class Sheet {
|
|
|
3354
3351
|
// stop rule. if you go forwards, you need some sort of indicator
|
|
3355
3352
|
// or flag).
|
|
3356
3353
|
|
|
3354
|
+
// there's more to this, because there are rules that apply to areas,
|
|
3355
|
+
// which might stop, and there's priority. so we probably need those
|
|
3356
|
+
// flags eventually.
|
|
3357
|
+
|
|
3357
3358
|
const area = JSON.parse(JSON.stringify(format.area));
|
|
3358
3359
|
|
|
3359
3360
|
if (area.start.row === null || area.end.row === null) {
|