@trebco/treb 32.5.0 → 32.6.6
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-spreadsheet.mjs +12 -12
- package/package.json +1 -1
- package/treb-base-types/src/import.ts +14 -10
- package/treb-base-types/src/value-type.ts +6 -7
- package/treb-calculator/src/calculator.ts +5 -0
- package/treb-calculator/src/functions/base-functions.ts +5 -5
- package/treb-calculator/src/functions/statistics-functions.ts +1 -1
- package/treb-charts/src/chart-types.ts +2 -0
- package/treb-charts/src/chart-utils.ts +45 -4
- package/treb-charts/src/default-chart-renderer.ts +84 -24
- package/treb-data-model/src/data_model.ts +8 -1
- package/treb-data-model/src/named.ts +5 -2
- package/treb-data-model/src/sheet.ts +9 -0
- package/treb-embed/style/tab-bar.scss +6 -0
- package/treb-export/src/import.ts +147 -13
- package/treb-export/src/metadata.ts +187 -0
- package/treb-export/src/workbook.ts +151 -5
package/package.json
CHANGED
|
@@ -27,18 +27,22 @@ import type { Table } from './table';
|
|
|
27
27
|
import type { DataValidation, AnnotationType, ConditionalFormat } from 'treb-data-model';
|
|
28
28
|
|
|
29
29
|
export interface CellParseResult {
|
|
30
|
-
row: number
|
|
31
|
-
column: number
|
|
30
|
+
row: number;
|
|
31
|
+
column: number;
|
|
32
32
|
type: SerializedValueType; // ValueType,
|
|
33
|
-
value: number|string|undefined|boolean
|
|
34
|
-
calculated?: number|string|undefined|boolean
|
|
33
|
+
value: number|string|undefined|boolean;
|
|
34
|
+
calculated?: number|string|undefined|boolean;
|
|
35
35
|
calculated_type?: SerializedValueType; // ValueType,
|
|
36
|
-
style_ref?: number
|
|
37
|
-
hyperlink?: string
|
|
38
|
-
validation?: DataValidation
|
|
39
|
-
merge_area?: IArea
|
|
40
|
-
area?: IArea
|
|
41
|
-
|
|
36
|
+
style_ref?: number;
|
|
37
|
+
hyperlink?: string;
|
|
38
|
+
validation?: DataValidation;
|
|
39
|
+
merge_area?: IArea;
|
|
40
|
+
area?: IArea;
|
|
41
|
+
|
|
42
|
+
/** dynamic arrays */
|
|
43
|
+
spill?: IArea;
|
|
44
|
+
|
|
45
|
+
table?: Table;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
export interface AnchoredAnnotation {
|
|
@@ -166,23 +166,22 @@ export enum ValueType {
|
|
|
166
166
|
// OK we use it all the time now
|
|
167
167
|
object = 5,
|
|
168
168
|
|
|
169
|
+
function = 6, // why was this inserted in the middle?
|
|
170
|
+
|
|
169
171
|
// error is a STRING VALUE... object errors are layered on top? is that
|
|
170
172
|
// correct? (...) it sort of makes sense... since we have separate typing
|
|
171
|
-
error =
|
|
173
|
+
error = 7,
|
|
172
174
|
|
|
173
175
|
// complex is pretty stable by now
|
|
174
|
-
complex =
|
|
176
|
+
complex = 8,
|
|
175
177
|
|
|
176
178
|
// this is new though. this is not a cell value, it's
|
|
177
179
|
// only for union types. perhaps we should move or rename
|
|
178
180
|
// this array, and then cells could have a subset?
|
|
179
|
-
array =
|
|
181
|
+
array = 9,
|
|
180
182
|
|
|
181
183
|
// adding DQ to union
|
|
182
|
-
dimensioned_quantity =
|
|
183
|
-
|
|
184
|
-
// new for lambdas
|
|
185
|
-
function = 10,
|
|
184
|
+
dimensioned_quantity = 10,
|
|
186
185
|
|
|
187
186
|
}
|
|
188
187
|
|
|
@@ -1221,11 +1221,16 @@ export class Calculator extends Graph {
|
|
|
1221
1221
|
public AttachSpillData(area: Area, cells?: Cells) {
|
|
1222
1222
|
|
|
1223
1223
|
if (!cells) {
|
|
1224
|
+
|
|
1225
|
+
// can we assume active sheet here? actually I guess not, we'll
|
|
1226
|
+
// need to set that...
|
|
1227
|
+
|
|
1224
1228
|
const sheet = area.start.sheet_id ? this.model.sheets.Find(area.start.sheet_id) : undefined;
|
|
1225
1229
|
cells = sheet?.cells;
|
|
1226
1230
|
}
|
|
1227
1231
|
|
|
1228
1232
|
if (!cells) {
|
|
1233
|
+
console.info({area, cells});
|
|
1229
1234
|
throw new Error('invalid sheet ID in attach spill data');
|
|
1230
1235
|
}
|
|
1231
1236
|
|
|
@@ -366,9 +366,9 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
366
366
|
|
|
367
367
|
if (integer) {
|
|
368
368
|
const range = max - min + 1;
|
|
369
|
-
for (let i = 0; i <
|
|
369
|
+
for (let i = 0; i < columns; i++) {
|
|
370
370
|
const row: UnionValue[] = [];
|
|
371
|
-
for (let j = 0; j <
|
|
371
|
+
for (let j = 0; j < rows; j++) {
|
|
372
372
|
row.push({
|
|
373
373
|
type: ValueType.number,
|
|
374
374
|
value: Math.floor(Math.random() * range + min),
|
|
@@ -380,9 +380,9 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
380
380
|
else {
|
|
381
381
|
const range = max - min;
|
|
382
382
|
|
|
383
|
-
for (let i = 0; i <
|
|
383
|
+
for (let i = 0; i < columns; i++) {
|
|
384
384
|
const row: UnionValue[] = [];
|
|
385
|
-
for (let j = 0; j <
|
|
385
|
+
for (let j = 0; j < rows; j++) {
|
|
386
386
|
row.push({
|
|
387
387
|
type: ValueType.number,
|
|
388
388
|
value: Math.random() * range + min,
|
|
@@ -391,7 +391,7 @@ export const BaseFunctionLibrary: FunctionMap = {
|
|
|
391
391
|
value.push(row);
|
|
392
392
|
}
|
|
393
393
|
}
|
|
394
|
-
|
|
394
|
+
|
|
395
395
|
return {
|
|
396
396
|
type: ValueType.array,
|
|
397
397
|
value,
|
|
@@ -486,7 +486,7 @@ export const StatisticsFunctionLibrary: FunctionMap = {
|
|
|
486
486
|
'Norm.S.Inv': {
|
|
487
487
|
description: 'Inverse of the standard normal cumulative distribution',
|
|
488
488
|
arguments: [
|
|
489
|
-
{name: 'probability'},
|
|
489
|
+
{name: 'probability', unroll: true },
|
|
490
490
|
],
|
|
491
491
|
xlfn: true,
|
|
492
492
|
fn: (q: number): UnionValue => {
|
|
@@ -207,6 +207,7 @@ export interface HistogramData extends ColumnDataBaseType {
|
|
|
207
207
|
export interface LineBaseData extends ChartDataBaseType {
|
|
208
208
|
series?: NumberOrUndefinedArray[];
|
|
209
209
|
series2?: SeriesType[];
|
|
210
|
+
stacked_series?: SeriesType[];
|
|
210
211
|
scale: RangeScale;
|
|
211
212
|
x_scale?: RangeScale;
|
|
212
213
|
titles?: string[];
|
|
@@ -262,6 +263,7 @@ export interface BarData extends LineBaseData {
|
|
|
262
263
|
type: 'bar';
|
|
263
264
|
round?: boolean;
|
|
264
265
|
space?: number;
|
|
266
|
+
stacked?: boolean;
|
|
265
267
|
}
|
|
266
268
|
|
|
267
269
|
export interface DonutDataBaseType extends ChartDataBaseType {
|
|
@@ -1001,8 +1001,11 @@ export const CreateColumnChart = (
|
|
|
1001
1001
|
|
|
1002
1002
|
const [data, labels, args_title, args_options] = args;
|
|
1003
1003
|
|
|
1004
|
-
const
|
|
1005
|
-
const
|
|
1004
|
+
const options = args_options?.toString() || undefined;
|
|
1005
|
+
const stacked = /stacked/i.test(options || '');
|
|
1006
|
+
|
|
1007
|
+
let series: SeriesType[] = TransformSeriesData(data);
|
|
1008
|
+
let common = CommonData(series);
|
|
1006
1009
|
|
|
1007
1010
|
let category_labels: string[] | undefined;
|
|
1008
1011
|
|
|
@@ -1041,15 +1044,50 @@ export const CreateColumnChart = (
|
|
|
1041
1044
|
|
|
1042
1045
|
}
|
|
1043
1046
|
|
|
1047
|
+
let stacked_series: SeriesType[]|undefined = undefined;
|
|
1048
|
+
const legend = common.legend; // in case we munge it
|
|
1049
|
+
|
|
1050
|
+
if (stacked) {
|
|
1051
|
+
stacked_series = series;
|
|
1052
|
+
|
|
1053
|
+
const map: Map<number, number> = new Map();
|
|
1054
|
+
// const label_map: Map<number, string> = new Map();
|
|
1055
|
+
|
|
1056
|
+
for (const entry of series) {
|
|
1057
|
+
for (const [index, key] of entry.x.data.entries()) {
|
|
1058
|
+
if (key !== undefined) {
|
|
1059
|
+
const value = entry.y.data[index] || 0;
|
|
1060
|
+
map.set(key, value + (map.get(key) || 0));
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
const x_data = Array.from(map.keys()).sort((a, b) => a - b);
|
|
1066
|
+
const y_data = x_data.map(key => map.get(key) || 0);
|
|
1067
|
+
|
|
1068
|
+
// console.info({stacked_series, map, x_data, y_data});
|
|
1069
|
+
|
|
1070
|
+
series = [{
|
|
1071
|
+
x: { data: x_data, format: stacked_series[0]?.x?.format },
|
|
1072
|
+
y: { data: y_data, format: stacked_series[0]?.y?.format },
|
|
1073
|
+
}];
|
|
1074
|
+
|
|
1075
|
+
series[0].x.range = ArrayMinMax(x_data);
|
|
1076
|
+
series[0].y.range = ArrayMinMax(y_data);
|
|
1077
|
+
|
|
1078
|
+
common = CommonData(series, Math.min(0, ...y_data));
|
|
1079
|
+
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1044
1082
|
const title = args_title?.toString() || undefined;
|
|
1045
|
-
const options = args_options?.toString() || undefined;
|
|
1046
1083
|
|
|
1047
1084
|
const chart_data = {
|
|
1048
1085
|
type,
|
|
1049
|
-
legend: common.legend,
|
|
1086
|
+
legend, // legend: common.legend,
|
|
1050
1087
|
// legend_position: LegendPosition.right,
|
|
1051
1088
|
legend_style: LegendStyle.marker,
|
|
1052
1089
|
series2: series,
|
|
1090
|
+
stacked_series,
|
|
1053
1091
|
scale: common.y.scale,
|
|
1054
1092
|
title,
|
|
1055
1093
|
y_labels: type === 'bar' ? category_labels : common.y.labels, // swapped
|
|
@@ -1057,6 +1095,9 @@ export const CreateColumnChart = (
|
|
|
1057
1095
|
};
|
|
1058
1096
|
|
|
1059
1097
|
if (options) {
|
|
1098
|
+
|
|
1099
|
+
(chart_data as BarData).stacked = stacked;
|
|
1100
|
+
|
|
1060
1101
|
(chart_data as BarData).round = /round/i.test(options);
|
|
1061
1102
|
(chart_data as ChartDataBaseType).data_labels = /labels/i.test(options);
|
|
1062
1103
|
|
|
@@ -599,23 +599,33 @@ export class DefaultChartRenderer implements ChartRendererType {
|
|
|
599
599
|
corners = [half_width, half_width, 0, 0];
|
|
600
600
|
}
|
|
601
601
|
|
|
602
|
-
|
|
603
|
-
const series = chart_data.series2[s];
|
|
604
|
-
const color_index = typeof series.index === 'number' ? series.index : s + 1;
|
|
602
|
+
if (chart_data.stacked_series) {
|
|
605
603
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
// const format = NumberFormatCache.Get(series.y.format || '0.00');
|
|
604
|
+
// step by group (stacked series)
|
|
605
|
+
for (let s = 0; s < chart_data.stacked_series[0].y.data.length; s++ ){
|
|
609
606
|
|
|
610
|
-
|
|
607
|
+
const x = (area.left + s * column_width + space) ; // + s * width;
|
|
608
|
+
let y = 0;
|
|
609
|
+
|
|
610
|
+
// now check each series, grab s-th value
|
|
611
|
+
for (let t = 0; t < chart_data.stacked_series.length; t++) {
|
|
612
|
+
|
|
613
|
+
const series = chart_data.stacked_series[t];
|
|
614
|
+
const color_index = typeof series.index === 'number' ? series.index : t + 1;
|
|
611
615
|
|
|
612
|
-
// const x = Math.round(area.left + i * column_width + space) + s * width;
|
|
613
|
-
const x = (area.left + i * column_width + space) + s * width;
|
|
614
|
-
|
|
615
616
|
let height = 0;
|
|
616
|
-
let y = 0;
|
|
617
617
|
// let negative = false;
|
|
618
618
|
|
|
619
|
+
const value = (series.y.data[s] || 0);
|
|
620
|
+
|
|
621
|
+
if (t === 0) {
|
|
622
|
+
y = Math.min(chart_data.scale.min || 0, value);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
height = Util.ApplyScale(value, area.height, chart_data.scale);
|
|
626
|
+
const base = area.bottom - height - Util.ApplyScale(y, area.height, chart_data.scale);
|
|
627
|
+
|
|
628
|
+
/*
|
|
619
629
|
if (zero) {
|
|
620
630
|
if (value > 0) {
|
|
621
631
|
height = Util.ApplyScale(value + chart_data.scale.min, area.height, chart_data.scale);
|
|
@@ -631,27 +641,77 @@ export class DefaultChartRenderer implements ChartRendererType {
|
|
|
631
641
|
height = Util.ApplyScale(value, area.height, chart_data.scale);
|
|
632
642
|
y = area.bottom - height;
|
|
633
643
|
}
|
|
644
|
+
*/
|
|
634
645
|
|
|
635
|
-
|
|
636
|
-
|
|
646
|
+
let bar_title = undefined;
|
|
647
|
+
let label = undefined;
|
|
648
|
+
let label_point = undefined;
|
|
637
649
|
|
|
638
|
-
|
|
650
|
+
this.renderer.RenderRectangle(new Area(
|
|
651
|
+
x, base, x + width, base + height,
|
|
652
|
+
), corners, ['chart-column', `series-${color_index}`], bar_title || undefined, label, label_point);
|
|
639
653
|
|
|
640
|
-
|
|
641
|
-
const label_point = {
|
|
642
|
-
x: Math.round(x + width / 2),
|
|
643
|
-
y: Math.round(y - 10),
|
|
644
|
-
};
|
|
654
|
+
y += value;
|
|
645
655
|
|
|
646
|
-
this.renderer.RenderRectangle(new Area(
|
|
647
|
-
x, y, x + width, y + height,
|
|
648
|
-
), corners, ['chart-column', `series-${color_index}`], bar_title || undefined, label, label_point);
|
|
649
|
-
}
|
|
650
656
|
}
|
|
657
|
+
|
|
658
|
+
|
|
651
659
|
}
|
|
660
|
+
}
|
|
661
|
+
else {
|
|
662
|
+
for (let s = 0; s < series_count; s++) {
|
|
663
|
+
const series = chart_data.series2[s];
|
|
664
|
+
const color_index = typeof series.index === 'number' ? series.index : s + 1;
|
|
665
|
+
|
|
666
|
+
for (let i = 0; i < series.y.data.length; i++ ){
|
|
667
|
+
const value = series.y.data[i];
|
|
668
|
+
|
|
669
|
+
if (typeof value === 'number') {
|
|
670
|
+
|
|
671
|
+
// const x = Math.round(area.left + i * column_width + space) + s * width;
|
|
672
|
+
const x = (area.left + i * column_width + space) + s * width;
|
|
673
|
+
|
|
674
|
+
let height = 0;
|
|
675
|
+
let y = 0;
|
|
676
|
+
// let negative = false;
|
|
677
|
+
|
|
678
|
+
if (zero) {
|
|
679
|
+
if (value > 0) {
|
|
680
|
+
height = Util.ApplyScale(value + chart_data.scale.min, area.height, chart_data.scale);
|
|
681
|
+
y = area.bottom - height - zero;
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
height = Util.ApplyScale(chart_data.scale.min - value, area.height, chart_data.scale);
|
|
685
|
+
y = area.bottom - zero; // // area.bottom - height - zero;
|
|
686
|
+
// negative = true;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
height = Util.ApplyScale(value, area.height, chart_data.scale);
|
|
691
|
+
y = area.bottom - height;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// const bar_title = chart_data.titles ? chart_data.titles[i] : undefined;
|
|
695
|
+
const bar_title = undefined;
|
|
696
|
+
|
|
697
|
+
if (height) {
|
|
652
698
|
|
|
699
|
+
const label = (chart_data.data_labels && !!series.y.labels) ? series.y.labels[i] : '';
|
|
700
|
+
const label_point = {
|
|
701
|
+
x: Math.round(x + width / 2),
|
|
702
|
+
y: Math.round(y - 10),
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
this.renderer.RenderRectangle(new Area(
|
|
706
|
+
x, y, x + width, y + height,
|
|
707
|
+
), corners, ['chart-column', `series-${color_index}`], bar_title || undefined, label, label_point);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
}
|
|
653
713
|
}
|
|
654
|
-
|
|
714
|
+
|
|
655
715
|
}
|
|
656
716
|
|
|
657
717
|
}
|
|
@@ -105,7 +105,14 @@ export class DataModel {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
const sheet = this.sheets.ID(sheet_name);
|
|
108
|
-
|
|
108
|
+
|
|
109
|
+
// Q: why require scope in this case? if there _is_ a global name,
|
|
110
|
+
// and no scoped name, why not return that? that seems to be the
|
|
111
|
+
// behavior in excel, although I can't be sure
|
|
112
|
+
|
|
113
|
+
// test: default false
|
|
114
|
+
|
|
115
|
+
return this.named.Get_(parts[1], sheet || 0, false); // true); // require scope in this case
|
|
109
116
|
|
|
110
117
|
|
|
111
118
|
}
|
|
@@ -201,14 +201,17 @@ export class NamedRangeManager {
|
|
|
201
201
|
* that implies that if there are both, we'll prefer the scoped name.
|
|
202
202
|
*
|
|
203
203
|
* now possible to require scope, for qualified scoped names
|
|
204
|
+
*
|
|
205
|
+
* Q: why require scope? what's the benefit of that? (...)
|
|
206
|
+
*
|
|
204
207
|
*/
|
|
205
208
|
public Get_(name: string, scope: number, require_scope = false) {
|
|
206
|
-
|
|
209
|
+
|
|
207
210
|
if (require_scope) {
|
|
208
211
|
return this.named.get(this.ScopedName(name, scope));
|
|
209
212
|
}
|
|
210
213
|
|
|
211
|
-
return this.named.get(this.ScopedName(name, scope))
|
|
214
|
+
return this.named.get(this.ScopedName(name, scope)) ?? this.named.get(name.toLowerCase());
|
|
212
215
|
}
|
|
213
216
|
|
|
214
217
|
/**
|
|
@@ -3011,6 +3011,15 @@ export class Sheet {
|
|
|
3011
3011
|
this.name = data.name || ''; // wtf is this?
|
|
3012
3012
|
}
|
|
3013
3013
|
|
|
3014
|
+
// patching from import
|
|
3015
|
+
for (const cell of this.cells.Iterate()) {
|
|
3016
|
+
if (cell.spill) {
|
|
3017
|
+
if (!cell.spill.start.sheet_id) {
|
|
3018
|
+
cell.spill.SetSheetID(this.id);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3014
3023
|
if (data.tab_color) {
|
|
3015
3024
|
this.tab_color = data.tab_color;
|
|
3016
3025
|
}
|