@trebco/treb 28.10.5 → 28.11.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-spreadsheet-light.mjs +9 -9
- package/dist/treb-spreadsheet.mjs +15 -15
- package/dist/treb.d.ts +1 -1
- package/package.json +1 -1
- package/treb-charts/src/chart-utils.ts +24 -3
- package/treb-charts/src/default-chart-renderer.ts +10 -1
- package/treb-charts/src/renderer.ts +34 -3
- package/treb-charts/style/charts.scss +5 -0
- package/treb-export/src/drawing2/bubble-chart-template.ts +553 -0
- package/treb-export/src/drawing2/chart2.ts +84 -1
- package/treb-export/src/export2.ts +49 -10
- package/treb-export/src/import2.ts +21 -0
- package/treb-export/src/workbook2.ts +27 -4
|
@@ -27,6 +27,7 @@ import { static_title, ref_title, chart_template } from './chart-template-compon
|
|
|
27
27
|
import { column_json, column_series } from './column-chart-template2';
|
|
28
28
|
import { donut_json } from './donut-chart-template2';
|
|
29
29
|
import { scatter_json, scatter_series } from './scatter-chart-template2';
|
|
30
|
+
import { bubble_json, bubble_series } from './bubble-chart-template';
|
|
30
31
|
|
|
31
32
|
import { XMLUtils } from '../xml-utils';
|
|
32
33
|
|
|
@@ -44,11 +45,12 @@ import { Localization } from 'treb-base-types';
|
|
|
44
45
|
import type { RelationshipMap } from '../relationship';
|
|
45
46
|
|
|
46
47
|
export interface ChartOptions {
|
|
47
|
-
type: 'donut'|'column'|'bar'|'scatter'|'scatter2';
|
|
48
|
+
type: 'donut'|'column'|'bar'|'scatter'|'scatter2' | 'bubble';
|
|
48
49
|
title?: UnitLiteral | UnitAddress;
|
|
49
50
|
data: UnitRange[];
|
|
50
51
|
labels?: UnitRange;
|
|
51
52
|
labels2?: UnitRange[];
|
|
53
|
+
labels3?: UnitRange[];
|
|
52
54
|
names?: ExpressionUnit[];
|
|
53
55
|
smooth?: boolean;
|
|
54
56
|
}
|
|
@@ -110,6 +112,9 @@ export class Chart {
|
|
|
110
112
|
case 'scatter2':
|
|
111
113
|
return this.CreateScatterChart();
|
|
112
114
|
|
|
115
|
+
case 'bubble':
|
|
116
|
+
return this.CreateBubbleChart();
|
|
117
|
+
|
|
113
118
|
case 'donut':
|
|
114
119
|
return this.CreateDonutChart();
|
|
115
120
|
|
|
@@ -121,6 +126,84 @@ export class Chart {
|
|
|
121
126
|
|
|
122
127
|
}
|
|
123
128
|
|
|
129
|
+
public CreateBubbleChart() {
|
|
130
|
+
|
|
131
|
+
const template = JSON.parse(JSON.stringify(chart_template));
|
|
132
|
+
const chartspace = template['c:chartSpace'];
|
|
133
|
+
const bubble = JSON.parse(JSON.stringify(bubble_json));
|
|
134
|
+
|
|
135
|
+
chartspace['c:chart'] = bubble;
|
|
136
|
+
|
|
137
|
+
this.UpdateChartTitle(chartspace['c:chart']);
|
|
138
|
+
|
|
139
|
+
const cser = chartspace['c:chart']['c:plotArea']['c:bubbleChart']['c:ser']; // this.FindNode('c:ser', template);
|
|
140
|
+
|
|
141
|
+
let legend = false;
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < this.options.data.length; i++) {
|
|
144
|
+
|
|
145
|
+
const series = JSON.parse(JSON.stringify(bubble_series));
|
|
146
|
+
|
|
147
|
+
series['c:idx'] = { a$: { val: i.toString() }};
|
|
148
|
+
series['c:order'] = { a$: { val: i.toString() }};
|
|
149
|
+
|
|
150
|
+
if (this.options.names && this.options.names[i]) {
|
|
151
|
+
|
|
152
|
+
const name = this.options.names[i];
|
|
153
|
+
switch (name.type) {
|
|
154
|
+
case 'literal':
|
|
155
|
+
series['c:tx'] = {
|
|
156
|
+
'c:v': name.value.toString(),
|
|
157
|
+
};
|
|
158
|
+
legend = true;
|
|
159
|
+
break;
|
|
160
|
+
|
|
161
|
+
case 'range':
|
|
162
|
+
case 'address':
|
|
163
|
+
series['c:tx'] = {
|
|
164
|
+
'c:strRef': {
|
|
165
|
+
'c:f': name.label,
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
legend = true;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// "accent7" will break
|
|
174
|
+
|
|
175
|
+
if (i < 6) {
|
|
176
|
+
series['c:spPr']['a:solidFill']['a:schemeClr'].a$['val'] = `accent${i+1}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
series['c:yVal']['c:numRef']['c:f'] = this.options.data[i]?.label;
|
|
180
|
+
|
|
181
|
+
if (this.options.labels2 && this.options.labels2[i]) {
|
|
182
|
+
series['c:xVal']['c:numRef']['c:f'] = this.options.labels2[i]?.label;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (this.options.labels3 && this.options.labels3[i]) {
|
|
186
|
+
series['c:bubbleSize']['c:numRef']['c:f'] = this.options.labels3[i]?.label;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// console.info("SER", JSON.stringify(series, undefined, 2));
|
|
190
|
+
|
|
191
|
+
cser.push(series);
|
|
192
|
+
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (legend) {
|
|
196
|
+
chartspace['c:chart']['c:legend'] = {
|
|
197
|
+
'c:legendPos': { a$: {val: 'b'}, },
|
|
198
|
+
'c:overlay': { a$: {val: '0'}, },
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return template;
|
|
203
|
+
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
124
207
|
public CreateScatterChart() {
|
|
125
208
|
|
|
126
209
|
const template = JSON.parse(JSON.stringify(chart_template));
|
|
@@ -714,6 +714,9 @@ export class Exporter {
|
|
|
714
714
|
/** overload for return type */
|
|
715
715
|
public NormalizeAddress(unit: UnitRange, sheet: SerializedSheet): UnitRange;
|
|
716
716
|
|
|
717
|
+
/** extra overload */
|
|
718
|
+
public NormalizeAddress<UNIT = UnitAddress|UnitRange>(unit: UNIT, sheet: SerializedSheet): UNIT;
|
|
719
|
+
|
|
717
720
|
/**
|
|
718
721
|
* for charts we need addresses to be absolute ($) and ensure there's
|
|
719
722
|
* a sheet name -- use the active sheet if it's not explicitly referenced
|
|
@@ -738,6 +741,19 @@ export class Exporter {
|
|
|
738
741
|
|
|
739
742
|
}
|
|
740
743
|
|
|
744
|
+
public EnsureRange(unit: UnitAddress|UnitRange): UnitRange {
|
|
745
|
+
if (unit.type === 'range') {
|
|
746
|
+
return unit;
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
type: 'range',
|
|
750
|
+
start: unit,
|
|
751
|
+
end: unit,
|
|
752
|
+
label: unit.label,
|
|
753
|
+
id: unit.id,
|
|
754
|
+
position: unit.position,
|
|
755
|
+
};
|
|
756
|
+
}
|
|
741
757
|
|
|
742
758
|
/**
|
|
743
759
|
* new-style annotation layout (kind of a two-cell anchor) to two-cell anchor
|
|
@@ -909,10 +925,13 @@ export class Exporter {
|
|
|
909
925
|
}
|
|
910
926
|
else if (/series/i.test(arg.name)) {
|
|
911
927
|
|
|
912
|
-
const [label, x, y] = arg.args; // y is required
|
|
928
|
+
const [label, x, y, z] = arg.args; // y is required
|
|
913
929
|
|
|
914
|
-
|
|
915
|
-
|
|
930
|
+
// FIXME: could be address also [x, y]
|
|
931
|
+
|
|
932
|
+
if (y && (y.type === 'range' || y.type === 'address')) {
|
|
933
|
+
|
|
934
|
+
options.data.push(this.EnsureRange(this.NormalizeAddress(y, sheet_source)));
|
|
916
935
|
|
|
917
936
|
if (label) {
|
|
918
937
|
|
|
@@ -933,9 +952,16 @@ export class Exporter {
|
|
|
933
952
|
}
|
|
934
953
|
|
|
935
954
|
if (!options.labels2) { options.labels2 = []; }
|
|
936
|
-
|
|
937
|
-
|
|
955
|
+
|
|
956
|
+
if (x && (x.type === 'range' || x.type === 'address')) {
|
|
957
|
+
options.labels2[options.data.length - 1] = this.EnsureRange(this.NormalizeAddress(x, sheet_source));
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
if (z && (z.type === 'range' || z.type === 'address')) {
|
|
961
|
+
if (!options.labels3) { options.labels3 = []; }
|
|
962
|
+
options.labels3[options.data.length - 1] = this.EnsureRange(this.NormalizeAddress(z, sheet_source));
|
|
938
963
|
}
|
|
964
|
+
|
|
939
965
|
}
|
|
940
966
|
else {
|
|
941
967
|
console.info('invalid series missing Y', {y, arg, ref});
|
|
@@ -951,30 +977,39 @@ export class Exporter {
|
|
|
951
977
|
const parse_result = this.parser.Parse(annotation.formula || '');
|
|
952
978
|
if (parse_result.expression && parse_result.expression.type === 'call') {
|
|
953
979
|
|
|
954
|
-
let type = '';
|
|
980
|
+
let type = ''; // FIXME
|
|
981
|
+
|
|
955
982
|
switch (parse_result.expression.name.toLowerCase()) {
|
|
956
983
|
case 'line.chart':
|
|
957
984
|
type = 'scatter';
|
|
958
985
|
break;
|
|
986
|
+
|
|
987
|
+
case 'bubble.chart':
|
|
988
|
+
type = 'bubble';
|
|
989
|
+
break;
|
|
990
|
+
|
|
959
991
|
case 'scatter.line':
|
|
960
992
|
type = 'scatter2';
|
|
961
993
|
break;
|
|
994
|
+
|
|
962
995
|
case 'donut.chart':
|
|
963
996
|
type = 'donut';
|
|
964
997
|
break;
|
|
998
|
+
|
|
965
999
|
case 'bar.chart':
|
|
966
1000
|
type = 'bar';
|
|
967
1001
|
break;
|
|
1002
|
+
|
|
968
1003
|
case 'column.chart':
|
|
969
1004
|
type = 'column';
|
|
970
1005
|
break;
|
|
971
1006
|
}
|
|
972
1007
|
|
|
973
|
-
if (type === 'column' || type === 'donut' || type === 'bar' || type === 'scatter' || type === 'scatter2') {
|
|
1008
|
+
if (type === 'column' || type === 'donut' || type === 'bar' || type === 'scatter' || type === 'scatter2' || type === 'bubble') {
|
|
974
1009
|
|
|
975
1010
|
const options: ChartOptions = { type, data: [] };
|
|
976
1011
|
|
|
977
|
-
const title_index = (type === 'scatter2') ? 1 : 2;
|
|
1012
|
+
const title_index = (type === 'scatter2' || type === 'bubble') ? 1 : 2;
|
|
978
1013
|
const title_arg = parse_result.expression.args[title_index];
|
|
979
1014
|
|
|
980
1015
|
if (title_arg && title_arg.type === 'literal') {
|
|
@@ -1000,7 +1035,7 @@ export class Exporter {
|
|
|
1000
1035
|
|
|
1001
1036
|
if (parse_result.expression.args[0]) {
|
|
1002
1037
|
const arg0 = parse_result.expression.args[0];
|
|
1003
|
-
if (type === 'scatter2' || type === 'bar' || type === 'column' || type === 'scatter') {
|
|
1038
|
+
if (type === 'scatter2' || type === 'bar' || type === 'column' || type === 'scatter' || type === 'bubble') {
|
|
1004
1039
|
parse_series(arg0, options, sheet_source.name);
|
|
1005
1040
|
}
|
|
1006
1041
|
else if (arg0.type === 'range') {
|
|
@@ -1045,7 +1080,7 @@ export class Exporter {
|
|
|
1045
1080
|
*/
|
|
1046
1081
|
}
|
|
1047
1082
|
|
|
1048
|
-
if (type !== 'scatter2') {
|
|
1083
|
+
if (type !== 'scatter2' && type !== 'bubble') {
|
|
1049
1084
|
if (parse_result.expression.args[1] && parse_result.expression.args[1].type === 'range') {
|
|
1050
1085
|
options.labels = this.NormalizeAddress(parse_result.expression.args[1], sheet_source);
|
|
1051
1086
|
}
|
|
@@ -1064,6 +1099,10 @@ export class Exporter {
|
|
|
1064
1099
|
options.smooth = true;
|
|
1065
1100
|
}
|
|
1066
1101
|
}
|
|
1102
|
+
else if (type === 'bubble') {
|
|
1103
|
+
// ...
|
|
1104
|
+
// console.info({parse_result});
|
|
1105
|
+
}
|
|
1067
1106
|
|
|
1068
1107
|
// FIXME: fix this type (this happened when we switched from annotation
|
|
1069
1108
|
// class to a data interface)
|
|
@@ -917,6 +917,27 @@ export class Importer {
|
|
|
917
917
|
const series = descriptor.chart?.series;
|
|
918
918
|
|
|
919
919
|
switch(descriptor.chart.type) {
|
|
920
|
+
|
|
921
|
+
case ChartType.Bubble:
|
|
922
|
+
type = 'treb-chart';
|
|
923
|
+
func = 'Bubble.Chart';
|
|
924
|
+
|
|
925
|
+
if (series && series.length) {
|
|
926
|
+
args[0] = `Group(${series.map(s => `Series(${
|
|
927
|
+
[
|
|
928
|
+
s.title || '',
|
|
929
|
+
s.values || '',
|
|
930
|
+
s.categories || '',
|
|
931
|
+
s.bubble_size || '',
|
|
932
|
+
|
|
933
|
+
].join(', ')
|
|
934
|
+
})`).join(', ')})`;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
args[1] = descriptor.chart.title;
|
|
938
|
+
|
|
939
|
+
break;
|
|
940
|
+
|
|
920
941
|
case ChartType.Scatter:
|
|
921
942
|
type = 'treb-chart';
|
|
922
943
|
func = 'Scatter.Line';
|
|
@@ -69,12 +69,13 @@ export const ConditionalFormatOperators: Record<string, string> = {
|
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
export enum ChartType {
|
|
72
|
-
Unknown = 0, Column, Bar, Line, Scatter, Donut, Pie
|
|
72
|
+
Unknown = 0, Column, Bar, Line, Scatter, Donut, Pie, Bubble
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
export interface ChartSeries {
|
|
76
76
|
values?: string;
|
|
77
77
|
categories?: string;
|
|
78
|
+
bubble_size?: string;
|
|
78
79
|
title?: string;
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -411,7 +412,7 @@ export class Workbook {
|
|
|
411
412
|
|
|
412
413
|
}
|
|
413
414
|
|
|
414
|
-
const ParseSeries = (node: any,
|
|
415
|
+
const ParseSeries = (node: any, type?: ChartType): ChartSeries[] => {
|
|
415
416
|
|
|
416
417
|
const series: ChartSeries[] = [];
|
|
417
418
|
|
|
@@ -447,7 +448,7 @@ export class Workbook {
|
|
|
447
448
|
}
|
|
448
449
|
}
|
|
449
450
|
|
|
450
|
-
if (
|
|
451
|
+
if (type === ChartType.Scatter || type === ChartType.Bubble) {
|
|
451
452
|
const x = XMLUtils.FindChild(series_node, 'c:xVal/c:numRef/c:f');
|
|
452
453
|
if (x) {
|
|
453
454
|
series_data.categories = x; // .text?.toString();
|
|
@@ -456,6 +457,14 @@ export class Workbook {
|
|
|
456
457
|
if (y) {
|
|
457
458
|
series_data.values = y; // .text?.toString();
|
|
458
459
|
}
|
|
460
|
+
|
|
461
|
+
if (type === ChartType.Bubble) {
|
|
462
|
+
const z = XMLUtils.FindChild(series_node, 'c:bubbleSize/c:numRef/c:f');
|
|
463
|
+
if (z) {
|
|
464
|
+
series_data.bubble_size = z; // .text?.toString();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
459
468
|
}
|
|
460
469
|
else {
|
|
461
470
|
const value_node = XMLUtils.FindChild(series_node, 'c:val/c:numRef/c:f');
|
|
@@ -522,9 +531,23 @@ export class Workbook {
|
|
|
522
531
|
node = XMLUtils.FindChild(xml, 'c:chartSpace/c:chart/c:plotArea/c:scatterChart');
|
|
523
532
|
if (node) {
|
|
524
533
|
result.type = ChartType.Scatter;
|
|
525
|
-
result.series = ParseSeries(node,
|
|
534
|
+
result.series = ParseSeries(node, ChartType.Scatter);
|
|
526
535
|
}
|
|
527
536
|
}
|
|
537
|
+
|
|
538
|
+
if (!node) {
|
|
539
|
+
node = XMLUtils.FindChild(xml, 'c:chartSpace/c:chart/c:plotArea/c:bubbleChart');
|
|
540
|
+
if (node) {
|
|
541
|
+
result.type = ChartType.Bubble;
|
|
542
|
+
result.series = ParseSeries(node, ChartType.Bubble);
|
|
543
|
+
console.info("Bubble series?", result.series);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
if (!node) {
|
|
548
|
+
console.info("Chart type not handled");
|
|
549
|
+
}
|
|
550
|
+
|
|
528
551
|
// console.info("RX?", result);
|
|
529
552
|
|
|
530
553
|
return result;
|