@trebco/treb 28.5.2 → 28.7.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.
Files changed (162) hide show
  1. package/dist/treb-spreadsheet-light.mjs +15 -15
  2. package/dist/treb-spreadsheet.mjs +15 -15
  3. package/dist/treb.d.ts +1 -1
  4. package/package.json +1 -1
  5. package/treb-base-types/src/api_types.ts +1 -1
  6. package/treb-base-types/src/area.ts +1 -1
  7. package/treb-base-types/src/basic_types.ts +21 -21
  8. package/treb-base-types/src/cell.ts +1 -1
  9. package/treb-base-types/src/cells.ts +1 -1
  10. package/treb-base-types/src/color.ts +1 -1
  11. package/treb-base-types/src/dom-utilities.ts +1 -1
  12. package/treb-base-types/src/import.ts +1 -1
  13. package/treb-base-types/src/index-standalone.ts +9 -9
  14. package/treb-base-types/src/index.ts +1 -1
  15. package/treb-base-types/src/layout.ts +21 -21
  16. package/treb-base-types/src/localization.ts +21 -21
  17. package/treb-base-types/src/rectangle.ts +1 -1
  18. package/treb-base-types/src/render_text.ts +1 -1
  19. package/treb-base-types/src/style.ts +1 -1
  20. package/treb-base-types/src/table.ts +1 -1
  21. package/treb-base-types/src/text_part.ts +21 -21
  22. package/treb-base-types/src/theme.ts +1 -1
  23. package/treb-base-types/src/union.ts +1 -1
  24. package/treb-base-types/src/value-type.ts +1 -1
  25. package/treb-base-types/style/resizable.css +21 -21
  26. package/treb-calculator/src/calculator.ts +1 -1
  27. package/treb-calculator/src/complex-math.ts +1 -1
  28. package/treb-calculator/src/dag/array-vertex.ts +1 -1
  29. package/treb-calculator/src/dag/calculation_leaf_vertex.ts +1 -1
  30. package/treb-calculator/src/dag/graph.ts +1 -1
  31. package/treb-calculator/src/dag/spreadsheet_vertex.ts +1 -1
  32. package/treb-calculator/src/dag/spreadsheet_vertex_base.ts +21 -21
  33. package/treb-calculator/src/dag/state_leaf_vertex.ts +1 -1
  34. package/treb-calculator/src/dag/vertex.ts +21 -21
  35. package/treb-calculator/src/descriptors.ts +1 -1
  36. package/treb-calculator/src/expression-calculator.ts +1 -1
  37. package/treb-calculator/src/function-error.ts +1 -1
  38. package/treb-calculator/src/function-library.ts +1 -1
  39. package/treb-calculator/src/functions/base-functions.ts +1 -1
  40. package/treb-calculator/src/functions/checkbox.ts +1 -1
  41. package/treb-calculator/src/functions/complex-functions.ts +1 -1
  42. package/treb-calculator/src/functions/finance-functions.ts +1 -1
  43. package/treb-calculator/src/functions/information-functions.ts +1 -1
  44. package/treb-calculator/src/functions/matrix-functions.ts +1 -1
  45. package/treb-calculator/src/functions/sparkline.ts +1 -1
  46. package/treb-calculator/src/functions/statistics-functions.ts +1 -1
  47. package/treb-calculator/src/functions/text-functions.ts +1 -1
  48. package/treb-calculator/src/index.ts +1 -1
  49. package/treb-calculator/src/notifier-types.ts +1 -1
  50. package/treb-calculator/src/primitives.ts +1 -1
  51. package/treb-calculator/src/utilities.ts +1 -1
  52. package/treb-charts/src/chart-functions.ts +1 -1
  53. package/treb-charts/src/chart-types.ts +21 -21
  54. package/treb-charts/src/chart-utils.ts +696 -0
  55. package/treb-charts/src/chart.ts +92 -1291
  56. package/treb-charts/src/default-chart-renderer.ts +535 -0
  57. package/treb-charts/src/index.ts +5 -4
  58. package/treb-charts/src/main.ts +17 -17
  59. package/treb-charts/src/rectangle.ts +21 -21
  60. package/treb-charts/src/renderer-type.ts +32 -0
  61. package/treb-charts/src/renderer.ts +1 -1
  62. package/treb-charts/src/util.ts +1 -1
  63. package/treb-charts/style/charts.scss +1 -1
  64. package/treb-charts/style/old-charts.scss +21 -21
  65. package/treb-embed/src/embedded-spreadsheet.ts +15 -12
  66. package/treb-embed/src/language-model.ts +1 -1
  67. package/treb-embed/src/options.ts +30 -1
  68. package/treb-embed/src/progress-dialog.ts +1 -1
  69. package/treb-embed/src/spinner.ts +1 -1
  70. package/treb-embed/src/types.ts +1 -1
  71. package/treb-embed/style/autocomplete.scss +1 -1
  72. package/treb-embed/style/dark-theme.scss +1 -1
  73. package/treb-embed/style/defaults.scss +1 -1
  74. package/treb-embed/style/dialog.scss +1 -1
  75. package/treb-embed/style/dropdown-select.scss +1 -1
  76. package/treb-embed/style/formula-bar.scss +1 -1
  77. package/treb-embed/style/grid.scss +1 -1
  78. package/treb-embed/style/mouse-mask.scss +1 -1
  79. package/treb-embed/style/note.scss +1 -1
  80. package/treb-embed/style/overlay-editor.scss +1 -1
  81. package/treb-embed/style/spinner.scss +1 -1
  82. package/treb-embed/style/tab-bar.scss +1 -1
  83. package/treb-embed/style/table.scss +1 -1
  84. package/treb-embed/style/theme-defaults.scss +1 -1
  85. package/treb-embed/style/tooltip.scss +1 -1
  86. package/treb-embed/style/z-index.scss +1 -1
  87. package/treb-export/src/address-type.ts +21 -21
  88. package/treb-export/src/base-template.ts +1 -1
  89. package/treb-export/src/column-width.ts +1 -1
  90. package/treb-export/src/drawing2/chart-template-components2.ts +1 -1
  91. package/treb-export/src/drawing2/chart2.ts +1 -1
  92. package/treb-export/src/drawing2/column-chart-template2.ts +1 -1
  93. package/treb-export/src/drawing2/donut-chart-template2.ts +1 -1
  94. package/treb-export/src/drawing2/drawing2.ts +1 -1
  95. package/treb-export/src/drawing2/embedded-image.ts +1 -1
  96. package/treb-export/src/drawing2/scatter-chart-template2.ts +1 -1
  97. package/treb-export/src/export-worker/export-worker.ts +1 -1
  98. package/treb-export/src/export-worker/index.worker.ts +1 -1
  99. package/treb-export/src/export2.ts +1 -1
  100. package/treb-export/src/import2.ts +1 -1
  101. package/treb-export/src/relationship.ts +1 -1
  102. package/treb-export/src/shared-strings2.ts +1 -1
  103. package/treb-export/src/template-2.ts +2 -2
  104. package/treb-export/src/workbook-sheet2.ts +1 -1
  105. package/treb-export/src/workbook-style2.ts +1 -1
  106. package/treb-export/src/workbook-theme2.ts +1 -1
  107. package/treb-export/src/workbook2.ts +1 -1
  108. package/treb-export/src/xml-utils.ts +1 -1
  109. package/treb-format/src/format.test.ts +21 -21
  110. package/treb-format/src/format.ts +1 -1
  111. package/treb-format/src/format_cache.ts +21 -21
  112. package/treb-format/src/format_parser.ts +1 -1
  113. package/treb-format/src/index.ts +4 -4
  114. package/treb-format/src/number_format_section.ts +21 -21
  115. package/treb-format/src/value_parser.ts +1 -1
  116. package/treb-grid/src/editors/autocomplete.ts +1 -1
  117. package/treb-grid/src/editors/autocomplete_matcher.ts +21 -21
  118. package/treb-grid/src/editors/editor.ts +1 -1
  119. package/treb-grid/src/editors/formula_bar.ts +1 -1
  120. package/treb-grid/src/editors/overlay_editor.ts +1 -1
  121. package/treb-grid/src/index.ts +1 -1
  122. package/treb-grid/src/layout/base_layout.ts +1 -1
  123. package/treb-grid/src/layout/grid_layout.ts +1 -1
  124. package/treb-grid/src/layout/rectangle_cache.ts +21 -21
  125. package/treb-grid/src/render/selection-renderer.ts +1 -1
  126. package/treb-grid/src/render/svg_header_overlay.ts +1 -1
  127. package/treb-grid/src/render/svg_selection_block.ts +1 -1
  128. package/treb-grid/src/render/tile_renderer.ts +1 -1
  129. package/treb-grid/src/types/annotation.ts +1 -1
  130. package/treb-grid/src/types/border_constants.ts +1 -1
  131. package/treb-grid/src/types/clipboard_data.ts +1 -1
  132. package/treb-grid/src/types/data_model.ts +1 -1
  133. package/treb-grid/src/types/drag_mask.ts +21 -21
  134. package/treb-grid/src/types/grid.ts +1 -1
  135. package/treb-grid/src/types/grid_base.ts +1 -1
  136. package/treb-grid/src/types/grid_command.ts +1 -1
  137. package/treb-grid/src/types/grid_events.ts +1 -1
  138. package/treb-grid/src/types/grid_options.ts +1 -1
  139. package/treb-grid/src/types/grid_selection.ts +1 -1
  140. package/treb-grid/src/types/named_range.ts +1 -1
  141. package/treb-grid/src/types/scale-control.ts +1 -1
  142. package/treb-grid/src/types/serialize_options.ts +1 -1
  143. package/treb-grid/src/types/set_range_options.ts +1 -1
  144. package/treb-grid/src/types/sheet.ts +1 -1
  145. package/treb-grid/src/types/sheet_types.ts +1 -1
  146. package/treb-grid/src/types/tab_bar.ts +1 -1
  147. package/treb-grid/src/types/tile.ts +21 -21
  148. package/treb-grid/src/types/update_flags.ts +1 -1
  149. package/treb-grid/src/util/fontmetrics2.ts +1 -1
  150. package/treb-grid/src/util/ua.ts +21 -21
  151. package/treb-parser/src/csv-parser.ts +21 -21
  152. package/treb-parser/src/index.ts +5 -5
  153. package/treb-parser/src/md-parser.ts +1 -1
  154. package/treb-parser/src/parser-types.ts +1 -1
  155. package/treb-parser/src/parser.test.ts +21 -21
  156. package/treb-parser/src/parser.ts +1 -1
  157. package/treb-utils/src/event_source.ts +1 -1
  158. package/treb-utils/src/ievent_source.ts +13 -13
  159. package/treb-utils/src/index.ts +1 -1
  160. package/treb-utils/src/measurement.ts +1 -1
  161. package/treb-utils/src/scale.ts +21 -21
  162. package/treb-utils/src/serialize_html.ts +1 -1
@@ -0,0 +1,696 @@
1
+
2
+ import { type UnionValue, ValueType, type ArrayUnion } from 'treb-base-types';
3
+ import { LegendStyle } from './chart-types';
4
+ import type { SubSeries, SeriesType, BarData, ChartDataBaseType, ChartData, ScatterData2, LineData, DonutSlice } from './chart-types';
5
+ import { NumberFormatCache } from 'treb-format';
6
+ import { Util } from './util';
7
+
8
+ /**
9
+ * this file is the concrete translation from function arguments
10
+ * to chart data. chart data is a (somewhat complicated) type with
11
+ * specializations for various chart types. we're splitting the
12
+ * generation of that data from the actual layout/rendering with
13
+ * a view towards building a new (or several new) renderers.
14
+ */
15
+
16
+ const DEFAULT_FORMAT = '#,##0.00'; // why not use "general", or whatever the usual default is?
17
+
18
+ export const ReadSeries = (data: Array<any>): SeriesType => {
19
+
20
+ // in this case it's (label, X, Y)
21
+ const series: SeriesType = {
22
+ x: { data: [] },
23
+ y: { data: [] },
24
+ };
25
+
26
+ if (data[3] && typeof data[3] === 'number') {
27
+ series.index = data[3];
28
+ }
29
+ if (data[4]) {
30
+ series.subtype = data[4].toString();
31
+ }
32
+
33
+ if (data[0]) {
34
+
35
+ const flat = Util.Flatten(data[0]);
36
+
37
+ // this could be a string, if it's a literal, or metadata
38
+ // [why would we want metadata?]
39
+ //
40
+ // OK, check that, should be a string (or other literal)
41
+
42
+ if (typeof flat[0] === 'object') {
43
+ series.label = flat[0]?.value?.toString() || '';
44
+ }
45
+ else {
46
+ series.label = flat[0].toString();
47
+ }
48
+ }
49
+
50
+ // read [2] first, so we can default for [1] if necessary
51
+
52
+ if (!!data[2] && (typeof data[2] === 'object') && data[2].type === ValueType.array) {
53
+ const flat = Util.Flatten(data[2].value);
54
+ series.y.data = flat.map(item => typeof item.value.value === 'number' ? item.value.value : undefined);
55
+ if (flat[0].value?.format) {
56
+ series.y.format = flat[0].value?.format as string;
57
+ const format = NumberFormatCache.Get(series.y.format);
58
+ series.y.labels = series.y.data.map(value => (value === undefined) ? undefined : format.Format(value));
59
+ }
60
+ }
61
+
62
+ if (!!data[1] && (typeof data[1] === 'object') && data[1].type === ValueType.array) {
63
+ const flat = Util.Flatten(data[1].value);
64
+ series.x.data = flat.map(item => typeof item.value.value === 'number' ? item.value.value : undefined);
65
+ if (flat[0].value.format) {
66
+ series.x.format = flat[0].value.format;
67
+ }
68
+ }
69
+
70
+ for (const subseries of [series.x, series.y]) {
71
+
72
+ // in case of no values
73
+ if (subseries.data.length) {
74
+ const values = subseries.data.filter(value => value || value === 0) as number[];
75
+ subseries.range = {
76
+ min: Math.min.apply(0, values),
77
+ max: Math.max.apply(0, values),
78
+ };
79
+ }
80
+ }
81
+
82
+ return series;
83
+
84
+ };
85
+
86
+ export const ArrayToSeries = (array_data: ArrayUnion): SeriesType => {
87
+
88
+ // this is an array of Y, X not provided
89
+
90
+ const series: SeriesType = { x: { data: [] }, y: { data: [] }, };
91
+ const flat = Util.Flatten(array_data.value);
92
+
93
+ // series.y.data = flat.map(item => typeof item.value === 'number' ? item.value : undefined);
94
+
95
+ series.y.data = flat.map((item, index) => {
96
+
97
+ // if the data is passed in from the output of a function, it will not
98
+ // be inside a metadata structure
99
+
100
+ if (typeof item.value === 'number') { return item.value; }
101
+
102
+ // ... ok, it's metadata (why not just test?) ...
103
+
104
+ // experimenting with complex... put real in X axis and imaginary in Y axis
105
+ // note should also function w/ complex not in a metadata structure
106
+
107
+ if (typeof item.value.value?.real === 'number') {
108
+ series.x.data[index] = item.value.value.real;
109
+ return item.value.value.imaginary;
110
+ }
111
+
112
+ return typeof item.value.value === 'number' ? item.value.value : undefined;
113
+
114
+ });
115
+
116
+ if (flat[0].value.format) {
117
+ series.y.format = flat[0].value.format || '';
118
+ const format = NumberFormatCache.Get(series.y.format || '');
119
+ series.y.labels = series.y.data.map(value => (value === undefined) ? undefined : format.Format(value));
120
+ }
121
+
122
+ const values = series.y.data.filter(value => value || value === 0) as number[];
123
+ series.y.range = {
124
+ min: Math.min.apply(0, values),
125
+ max: Math.max.apply(0, values),
126
+ };
127
+
128
+ // experimenting with complex... this should only be set if we populated
129
+ // it from complex values
130
+
131
+ if (series.x.data.length) {
132
+
133
+ const filtered: number[] = series.x.data.filter(test => typeof test === 'number') as number[];
134
+ series.x.range = {
135
+ min: Math.min.apply(0, filtered),
136
+ max: Math.max.apply(0, filtered),
137
+ }
138
+
139
+ if (flat[0].value.format) {
140
+ series.x.format = flat[0].value.format || '';
141
+ const format = NumberFormatCache.Get(series.x.format || '');
142
+ series.x.labels = series.x.data.map(value => (value === undefined) ? undefined : format.Format(value));
143
+ }
144
+
145
+ }
146
+
147
+ return series;
148
+
149
+ };
150
+
151
+ /**
152
+ * composite data -> series. composite data can be
153
+ *
154
+ * (1) set of Y values, with X not provided;
155
+ * (2) SERIES(label, X, Y) with Y required, others optional
156
+ * (3) GROUP(a, b, ...), where entries are either arrays as (1) or SERIES as (2)
157
+ *
158
+ * FIXME: consider supporting GROUP(SERIES, [y], ...)
159
+ *
160
+ * NOTE: (1) could be an array of boxed (union) values...
161
+ *
162
+ */
163
+ export const TransformSeriesData = (raw_data?: UnionValue, default_x?: UnionValue): SeriesType[] => {
164
+
165
+ if (!raw_data) { return []; }
166
+
167
+ const list: SeriesType[] = [];
168
+
169
+ if (raw_data.type === ValueType.object) {
170
+ if (raw_data.key === 'group') {
171
+ if (Array.isArray(raw_data.value)) {
172
+ for (const entry of raw_data.value) {
173
+ if (!!entry && (typeof entry === 'object')) {
174
+ if (entry.key === 'series') {
175
+ const series = ReadSeries(entry.value);
176
+ list.push(series);
177
+ }
178
+ else if (entry.type === ValueType.array) {
179
+ list.push(ArrayToSeries(entry));
180
+ }
181
+ }
182
+ }
183
+ }
184
+ }
185
+ else if (raw_data.key === 'series') {
186
+ const series = ReadSeries(raw_data.value);
187
+ list.push(series);
188
+ }
189
+ }
190
+ else if (raw_data.type === ValueType.array) {
191
+ list.push(ArrayToSeries(raw_data));
192
+ }
193
+
194
+ // now we may or may not have X for each series, so we need
195
+ // to patch. it's also possible (as with older chart functions)
196
+ // that there's a common X -- not sure if we want to continue
197
+ // to support that or not...
198
+
199
+ let baseline_x: SubSeries | undefined;
200
+ let max_y_length = 0;
201
+
202
+ // if we have a default, use that (and range it)
203
+
204
+ if (default_x?.type === ValueType.array) {
205
+
206
+ const values = Util.Flatten(default_x.value);
207
+
208
+ let format = '0.00###';
209
+
210
+ if (values[0] && values[0].type === ValueType.object) { // UnionIs.Extended(values[0])) {
211
+ format = values[0].value.format;
212
+ }
213
+
214
+ const data = values.map(x => {
215
+ if (x.type === ValueType.number) { return x.value; }
216
+ if (x.type === ValueType.object) { // ??
217
+ // if (UnionIs.Extended(x)) { // ?
218
+ return x.value.value;
219
+ }
220
+ return undefined;
221
+ }) as Array<number | undefined>;
222
+
223
+ const filtered = data.filter(x => typeof x === 'number') as number[];
224
+
225
+ baseline_x = {
226
+ data,
227
+ format,
228
+ range: {
229
+ min: Math.min.apply(0, filtered),
230
+ max: Math.max.apply(0, filtered),
231
+ }
232
+ }
233
+ }
234
+
235
+ // look for the first set that has values. at the same time, get max len
236
+
237
+ for (const entry of list) {
238
+ max_y_length = Math.max(max_y_length, entry.y.data.length);
239
+ if (entry.x.data.length) {
240
+ if (!baseline_x) {
241
+ baseline_x = entry.x;
242
+ }
243
+ }
244
+ }
245
+
246
+ // now default for any series missing X
247
+
248
+ if (!baseline_x) {
249
+ baseline_x = {
250
+ data: [],
251
+ range: {
252
+ min: 0,
253
+ max: Math.max(0, max_y_length - 1),
254
+ }
255
+ }
256
+ for (let i = 0; i < max_y_length; i++) { baseline_x.data.push(i); }
257
+ }
258
+
259
+ for (const entry of list) {
260
+ if (!entry.x.data.length) {
261
+ entry.x = baseline_x;
262
+ }
263
+ }
264
+
265
+ return list;
266
+ };
267
+
268
+ /** get a unified scale, and formats */
269
+ export const CommonData = (series: SeriesType[], y_floor?: number, y_ceiling?: number) => {
270
+
271
+ let x_format = '';
272
+ let y_format = '';
273
+
274
+ for (const entry of series) {
275
+ if (entry.y.format && !y_format) { y_format = entry.y.format; }
276
+ if (entry.x.format && !x_format) { x_format = entry.x.format; }
277
+ }
278
+
279
+ let legend: Array<{ label: string, index?: number }> | undefined; // string[]|undefined;
280
+ if (series.some(test => test.label && (test.label.length > 0))) {
281
+ legend = series.map((entry, i) => ({
282
+ label: entry.label || `Series ${i + 1}`,
283
+ index: typeof entry.index === 'number' ? entry.index : i + 1,
284
+ }));
285
+ }
286
+
287
+ const x = series.filter(test => test.x.range);
288
+ const x_min = Math.min.apply(0, x.map(test => test.x.range?.min || 0));
289
+ const x_max = Math.max.apply(0, x.map(test => test.x.range?.max || 0));
290
+
291
+ const y = series.filter(test => test.y.range);
292
+ let y_min = Math.min.apply(0, x.map(test => test.y.range?.min || 0));
293
+ let y_max = Math.max.apply(0, x.map(test => test.y.range?.max || 0));
294
+
295
+ if (typeof y_floor !== 'undefined') {
296
+ y_min = Math.min(y_min, y_floor);
297
+ }
298
+ if (typeof y_ceiling !== 'undefined') {
299
+ y_max = Math.max(y_max, y_ceiling);
300
+ }
301
+
302
+ const x_scale = Util.Scale(x_min, x_max, 7);
303
+ const y_scale = Util.Scale(y_min, y_max, 7);
304
+
305
+ let x_labels: string[] | undefined;
306
+ let y_labels: string[] | undefined;
307
+
308
+ if (x_format) {
309
+ x_labels = [];
310
+ const format = NumberFormatCache.Get(x_format);
311
+ for (let i = 0; i <= x_scale.count; i++) {
312
+ x_labels.push(format.Format(x_scale.min + i * x_scale.step));
313
+ }
314
+ }
315
+
316
+ if (y_format) {
317
+ y_labels = [];
318
+ const format = NumberFormatCache.Get(y_format);
319
+ for (let i = 0; i <= y_scale.count; i++) {
320
+ y_labels.push(format.Format(y_scale.min + i * y_scale.step));
321
+ }
322
+ }
323
+
324
+ return {
325
+ x: {
326
+ format: x_format,
327
+ scale: x_scale,
328
+ labels: x_labels,
329
+ },
330
+ y: {
331
+ format: y_format,
332
+ scale: y_scale,
333
+ labels: y_labels,
334
+ },
335
+ legend,
336
+ };
337
+
338
+ };
339
+
340
+ const ApplyLabels = (series_list: SeriesType[], pattern: string, category_labels?: string[]): void => {
341
+
342
+ for (const series of series_list) {
343
+
344
+ const format = {
345
+ x: NumberFormatCache.Get(series.x.format || ''),
346
+ y: NumberFormatCache.Get(series.y.format || ''),
347
+ };
348
+
349
+ series.y.labels = [];
350
+
351
+ for (let i = 0; i < series.y.data.length; i++) {
352
+
353
+ const x = category_labels ? category_labels[i] :
354
+ (typeof series.x.data[i] === 'number' ? format.x.Format(series.x.data[i]) : '');
355
+ const y = typeof series.y.data[i] === 'number' ? format.y.Format(series.y.data[i]) : '';
356
+
357
+ series.y.labels[i] = pattern.replace(/\bx\b/g, x).replace(/\by\b/g, y);
358
+
359
+ }
360
+
361
+ }
362
+
363
+ };
364
+
365
+ //------------------------------------------------------------------------------
366
+
367
+
368
+ /**
369
+ * args is [data, title, options]
370
+ *
371
+ * args[0] is the scatter data. this can be
372
+ *
373
+ * (1) set of Y values, with X not provided;
374
+ * (2) SERIES(label, X, Y) with Y required, others optional
375
+ * (3) GROUP(SERIES(label, X, Y), SERIES(label, X, Y), ...), with same rule for each series
376
+ *
377
+ * @param args
378
+ */
379
+ export const CreateScatterChart = (args: any[], style: 'plot' | 'line' = 'plot'): ChartData => {
380
+
381
+ // FIXME: transform the data, then have this function
382
+ // operate on clean data. that way the transform can
383
+ // be reused (and the function can be reused without the
384
+ // transform).
385
+
386
+ const series: SeriesType[] = TransformSeriesData(args[0]);
387
+
388
+ const common = CommonData(series);
389
+
390
+ const title = args[1]?.toString() || undefined;
391
+ const options = args[2]?.toString() || undefined;
392
+
393
+ const chart_data: ScatterData2 = {
394
+ legend: common.legend,
395
+ style,
396
+ type: 'scatter2',
397
+ series, // : [{x, y}],
398
+ title,
399
+
400
+ x_scale: common.x.scale,
401
+ x_labels: common.x.labels,
402
+
403
+ y_scale: common.y.scale,
404
+ y_labels: common.y.labels,
405
+
406
+ lines: style === 'line', // true,
407
+ points: style === 'plot',
408
+
409
+ };
410
+
411
+ if (options) {
412
+
413
+ chart_data.markers = /marker/i.test(options);
414
+ chart_data.smooth = /smooth/i.test(options);
415
+ chart_data.data_labels = /labels/i.test(options);
416
+
417
+ let match = options.match(/labels="(.*?)"/);
418
+ if (match && chart_data.series) {
419
+ ApplyLabels(chart_data.series, match[1]);
420
+ }
421
+ else {
422
+ match = options.match(/labels=([^\s\r\n,]+)(?:\W|$)/);
423
+ if (match && chart_data.series) {
424
+ ApplyLabels(chart_data.series, match[1]);
425
+ }
426
+ }
427
+
428
+ match = options.match(/class=([\w_-]+)(?:\W|$)/);
429
+ if (match) {
430
+ chart_data.class_name = match[1];
431
+ }
432
+
433
+ }
434
+
435
+ return chart_data;
436
+
437
+ };
438
+
439
+
440
+ /**
441
+ * column/bar chart, now using common Series data and routines
442
+ *
443
+ * @param args arguments: data, categories, title, options
444
+ * @param type
445
+ */
446
+ export const CreateColumnChart = (args: [UnionValue?, UnionValue?, string?, string?], type: 'bar' | 'column'): ChartData => {
447
+
448
+ const series: SeriesType[] = TransformSeriesData(args[0]);
449
+ const common = CommonData(series);
450
+
451
+ let category_labels: string[] | undefined;
452
+
453
+ if (args[1]) {
454
+
455
+ const values = args[1].type === ValueType.array ? Util.Flatten(args[1].value) : Util.Flatten(args[1]);
456
+ category_labels = values.map((cell) => {
457
+ if (!cell) { return ''; }
458
+
459
+ if (cell.type === ValueType.object && cell.value.type === 'metadata') {
460
+ if (typeof cell.value.value === 'number') {
461
+ const format = NumberFormatCache.Get(cell.value.format || DEFAULT_FORMAT);
462
+ return format.Format(cell.value.value);
463
+ }
464
+ return cell.value.value;
465
+ }
466
+
467
+ if (typeof cell.value === 'number') {
468
+ const format = NumberFormatCache.Get(cell.format || DEFAULT_FORMAT);
469
+ return format.Format(cell.value);
470
+ }
471
+ return cell.value;
472
+ });
473
+
474
+ const count = series.reduce((a, entry) => Math.max(a, entry.y.data.length), 0);
475
+
476
+ if (count < category_labels.length) {
477
+ category_labels = category_labels.slice(0, count);
478
+ }
479
+
480
+ while (count > category_labels.length) { category_labels.push(''); }
481
+
482
+ }
483
+
484
+ const title = args[2]?.toString() || undefined;
485
+ const options = args[3]?.toString() || undefined;
486
+
487
+ const chart_data = {
488
+ type,
489
+ legend: common.legend,
490
+ // legend_position: LegendPosition.right,
491
+ legend_style: LegendStyle.marker,
492
+ series2: series,
493
+ scale: common.y.scale,
494
+ title,
495
+ y_labels: type === 'bar' ? category_labels : common.y.labels, // swapped
496
+ x_labels: type === 'bar' ? common.y.labels : category_labels, // swapped
497
+ };
498
+
499
+ if (options) {
500
+ (chart_data as BarData).round = /round/i.test(options);
501
+ (chart_data as ChartDataBaseType).data_labels = /labels/i.test(options);
502
+
503
+ let match = options.match(/labels="(.*?)"/);
504
+ if (match && series) {
505
+ ApplyLabels(series, match[1], category_labels);
506
+ }
507
+ else {
508
+ match = options.match(/labels=([^\s\r\n,]+)(?:\W|$)/);
509
+ if (match && series) {
510
+ ApplyLabels(series, match[1], category_labels);
511
+ }
512
+
513
+ }
514
+
515
+ match = options.match(/class=([\w_-]+)(?:\W|$)/);
516
+ if (match) {
517
+ (chart_data as ChartDataBaseType).class_name = match[1];
518
+ }
519
+
520
+ }
521
+
522
+ return chart_data;
523
+
524
+ };
525
+
526
+
527
+ /**
528
+ * args: data, labels, title, callouts, "smooth"
529
+ */
530
+ export const CreateLineChart = (args: any[], type: 'line' | 'area'): ChartData => {
531
+
532
+ const series: SeriesType[] = TransformSeriesData(args[0], args[1]);
533
+ const common = CommonData(series, 0, 0);
534
+
535
+ const title = args[2]?.toString() || undefined;
536
+ const options = args[3]?.toString() || undefined;
537
+
538
+ const chart_data: ChartData = {
539
+ legend: common.legend,
540
+ // style: type, // 'line',
541
+ type: 'scatter2',
542
+ series, // : [{x, y}],
543
+ title,
544
+
545
+ x_scale: common.x.scale,
546
+ x_labels: common.x.labels,
547
+
548
+ y_scale: common.y.scale,
549
+ y_labels: common.y.labels,
550
+
551
+ lines: true,
552
+ filled: type === 'area',
553
+
554
+ };
555
+
556
+ if (options) {
557
+ // this.chart_data.markers = /marker/i.test(options);
558
+ chart_data.smooth = /smooth/i.test(options);
559
+ // this.chart_data.data_labels = /labels/i.test(options);
560
+
561
+ const match = options.match(/class=([\w_-]+)(?:\W|$)/);
562
+ if (match) {
563
+ chart_data.class_name = match[1];
564
+ }
565
+
566
+ }
567
+
568
+ return chart_data;
569
+
570
+ };
571
+
572
+ /**
573
+ * arguments are values, labels, title, sort, label option, ...
574
+ */
575
+ export const CreateDonut = (args: [UnionValue?, UnionValue?, string?, string?, string?], pie_chart = false): ChartData => {
576
+
577
+ const raw_data = args[0]?.type === ValueType.array ? args[0].value : args[0];
578
+
579
+ // we're now expecting this to be metadata (including value).
580
+ // so we need to unpack. could be an array... could be deep...
581
+ const flat = Util.Flatten(raw_data);
582
+
583
+ // we still need the aggregate for range, scale
584
+ let data = flat.map((x) => (typeof x.value.value === 'number') ? x.value.value : undefined) as number[];
585
+
586
+
587
+ // if labels are strings, just pass them in. if they're numbers then
588
+ // use the format (we're collecting metadata for this field now)
589
+
590
+ const raw_labels = args[1]?.type === ValueType.array ? args[1].value : args[1];
591
+
592
+ const labels = Util.Flatten(raw_labels).map((label) => {
593
+ if (label && typeof label === 'object') {
594
+ const value = label.value?.value;
595
+ if (typeof value === 'number' && label.value?.format) {
596
+ return NumberFormatCache.Get(label.value?.format).Format(value);
597
+ }
598
+ else return value ? value.toString() : '';
599
+ }
600
+ else return label ? label.toString() : '';
601
+ });
602
+
603
+ // no negative numbers
604
+
605
+ data = data.map((check) => {
606
+ if (check < 0) {
607
+ console.warn('pie/donut chart does not support negative values (omitted)');
608
+ return 0;
609
+ }
610
+ return check;
611
+ });
612
+
613
+ const title = args[2] || '';
614
+
615
+ let sum = 0;
616
+
617
+ const slices: DonutSlice[] = data.map((value, i) => {
618
+ if (typeof value !== 'undefined') sum += value;
619
+ return { value, label: labels[i] || '', index: i + 1, percent: 0 };
620
+ });
621
+
622
+ if (sum) {
623
+ for (const slice of slices) {
624
+ slice.percent = (slice.value || 0) / sum;
625
+ }
626
+ }
627
+
628
+ // titles? label/value/percent
629
+ // FIXME: number format(s)
630
+
631
+ const format_pattern = (flat.length && flat[0].value?.format) ? flat[0].value.format : '';
632
+ const format = NumberFormatCache.Get(format_pattern || DEFAULT_FORMAT);
633
+ const percent_format = NumberFormatCache.Get('percent');
634
+
635
+ // ensure label if we have labels array but no label format string
636
+
637
+ if (typeof args[4] === 'undefined' && args[1]) {
638
+ args[4] = 'label';
639
+ }
640
+
641
+ const slice_title = (args[4] || '');
642
+ if (slice_title) {
643
+ for (const slice of slices) {
644
+ const value = /*NumberFormatCache.Get('general')*/ format.Format(slice.value || 0);
645
+ const percent = percent_format.Format(slice.percent);
646
+ slice.title = slice_title
647
+ .replace(/value%/ig, percent_format.Format(slice.value || 0))
648
+ .replace(/value/ig, value)
649
+ .replace(/percent/ig, percent)
650
+ .replace(/label/ig, slice.label || '')
651
+ .trim();
652
+ }
653
+ }
654
+
655
+ // optionally sort...
656
+
657
+ const options = (args[3] || '').toString().trim();
658
+
659
+ // old-style...
660
+
661
+ let sort = options.toUpperCase();
662
+ if (sort === 'ASC' || sort === 'ASCENDING' || sort === 'INC') {
663
+ slices.sort((a, b) => { return (a.value || 0) - (b.value || 0); });
664
+ }
665
+ else if (sort === 'DESC' || sort === 'DESCENDING' || sort === 'DEC') {
666
+ slices.sort((a, b) => { return (b.value || 0) - (a.value || 0); });
667
+ }
668
+ else {
669
+ const match = options.match(/sort=([\w]+)(?:\W|$)/i);
670
+ if (match) {
671
+ sort = match[1];
672
+ if (/^(asc|inc)/i.test(sort)) {
673
+ slices.sort((a, b) => { return (a.value || 0) - (b.value || 0); });
674
+ }
675
+ else if (/^(desc|dec)/i.test(sort)) {
676
+ slices.sort((a, b) => { return (b.value || 0) - (a.value || 0); });
677
+ }
678
+ }
679
+ }
680
+
681
+ const chart_data: ChartData = {
682
+ type: pie_chart ? 'pie' : 'donut',
683
+ slices,
684
+ title,
685
+ };
686
+
687
+ if (options) {
688
+ const match = options.match(/class=([_-\w]+)(?:\W|$)/);
689
+ if (match) {
690
+ chart_data.class_name = match[1];
691
+ }
692
+ }
693
+
694
+ return chart_data;
695
+
696
+ };