@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.
@@ -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
- if (y && y.type === 'range') {
915
- options.data.push(this.NormalizeAddress(y, sheet_source));
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
- if (x && x.type === 'range') {
937
- options.labels2[options.data.length - 1] = this.NormalizeAddress(x, sheet_source);
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, scatter = false): ChartSeries[] => {
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 (scatter) {
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, true);
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;