@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.
- package/dist/treb-spreadsheet-light.mjs +15 -15
- package/dist/treb-spreadsheet.mjs +15 -15
- package/dist/treb.d.ts +1 -1
- package/package.json +1 -1
- package/treb-base-types/src/api_types.ts +1 -1
- package/treb-base-types/src/area.ts +1 -1
- package/treb-base-types/src/basic_types.ts +21 -21
- package/treb-base-types/src/cell.ts +1 -1
- package/treb-base-types/src/cells.ts +1 -1
- package/treb-base-types/src/color.ts +1 -1
- package/treb-base-types/src/dom-utilities.ts +1 -1
- package/treb-base-types/src/import.ts +1 -1
- package/treb-base-types/src/index-standalone.ts +9 -9
- package/treb-base-types/src/index.ts +1 -1
- package/treb-base-types/src/layout.ts +21 -21
- package/treb-base-types/src/localization.ts +21 -21
- package/treb-base-types/src/rectangle.ts +1 -1
- package/treb-base-types/src/render_text.ts +1 -1
- package/treb-base-types/src/style.ts +1 -1
- package/treb-base-types/src/table.ts +1 -1
- package/treb-base-types/src/text_part.ts +21 -21
- package/treb-base-types/src/theme.ts +1 -1
- package/treb-base-types/src/union.ts +1 -1
- package/treb-base-types/src/value-type.ts +1 -1
- package/treb-base-types/style/resizable.css +21 -21
- package/treb-calculator/src/calculator.ts +1 -1
- package/treb-calculator/src/complex-math.ts +1 -1
- package/treb-calculator/src/dag/array-vertex.ts +1 -1
- package/treb-calculator/src/dag/calculation_leaf_vertex.ts +1 -1
- package/treb-calculator/src/dag/graph.ts +1 -1
- package/treb-calculator/src/dag/spreadsheet_vertex.ts +1 -1
- package/treb-calculator/src/dag/spreadsheet_vertex_base.ts +21 -21
- package/treb-calculator/src/dag/state_leaf_vertex.ts +1 -1
- package/treb-calculator/src/dag/vertex.ts +21 -21
- package/treb-calculator/src/descriptors.ts +1 -1
- package/treb-calculator/src/expression-calculator.ts +1 -1
- package/treb-calculator/src/function-error.ts +1 -1
- package/treb-calculator/src/function-library.ts +1 -1
- package/treb-calculator/src/functions/base-functions.ts +1 -1
- package/treb-calculator/src/functions/checkbox.ts +1 -1
- package/treb-calculator/src/functions/complex-functions.ts +1 -1
- package/treb-calculator/src/functions/finance-functions.ts +1 -1
- package/treb-calculator/src/functions/information-functions.ts +1 -1
- package/treb-calculator/src/functions/matrix-functions.ts +1 -1
- package/treb-calculator/src/functions/sparkline.ts +1 -1
- package/treb-calculator/src/functions/statistics-functions.ts +1 -1
- package/treb-calculator/src/functions/text-functions.ts +1 -1
- package/treb-calculator/src/index.ts +1 -1
- package/treb-calculator/src/notifier-types.ts +1 -1
- package/treb-calculator/src/primitives.ts +1 -1
- package/treb-calculator/src/utilities.ts +1 -1
- package/treb-charts/src/chart-functions.ts +1 -1
- package/treb-charts/src/chart-types.ts +21 -21
- package/treb-charts/src/chart-utils.ts +696 -0
- package/treb-charts/src/chart.ts +92 -1291
- package/treb-charts/src/default-chart-renderer.ts +535 -0
- package/treb-charts/src/index.ts +5 -4
- package/treb-charts/src/main.ts +17 -17
- package/treb-charts/src/rectangle.ts +21 -21
- package/treb-charts/src/renderer-type.ts +32 -0
- package/treb-charts/src/renderer.ts +1 -1
- package/treb-charts/src/util.ts +1 -1
- package/treb-charts/style/charts.scss +1 -1
- package/treb-charts/style/old-charts.scss +21 -21
- package/treb-embed/src/embedded-spreadsheet.ts +15 -12
- package/treb-embed/src/language-model.ts +1 -1
- package/treb-embed/src/options.ts +30 -1
- package/treb-embed/src/progress-dialog.ts +1 -1
- package/treb-embed/src/spinner.ts +1 -1
- package/treb-embed/src/types.ts +1 -1
- package/treb-embed/style/autocomplete.scss +1 -1
- package/treb-embed/style/dark-theme.scss +1 -1
- package/treb-embed/style/defaults.scss +1 -1
- package/treb-embed/style/dialog.scss +1 -1
- package/treb-embed/style/dropdown-select.scss +1 -1
- package/treb-embed/style/formula-bar.scss +1 -1
- package/treb-embed/style/grid.scss +1 -1
- package/treb-embed/style/mouse-mask.scss +1 -1
- package/treb-embed/style/note.scss +1 -1
- package/treb-embed/style/overlay-editor.scss +1 -1
- package/treb-embed/style/spinner.scss +1 -1
- package/treb-embed/style/tab-bar.scss +1 -1
- package/treb-embed/style/table.scss +1 -1
- package/treb-embed/style/theme-defaults.scss +1 -1
- package/treb-embed/style/tooltip.scss +1 -1
- package/treb-embed/style/z-index.scss +1 -1
- package/treb-export/src/address-type.ts +21 -21
- package/treb-export/src/base-template.ts +1 -1
- package/treb-export/src/column-width.ts +1 -1
- package/treb-export/src/drawing2/chart-template-components2.ts +1 -1
- package/treb-export/src/drawing2/chart2.ts +1 -1
- package/treb-export/src/drawing2/column-chart-template2.ts +1 -1
- package/treb-export/src/drawing2/donut-chart-template2.ts +1 -1
- package/treb-export/src/drawing2/drawing2.ts +1 -1
- package/treb-export/src/drawing2/embedded-image.ts +1 -1
- package/treb-export/src/drawing2/scatter-chart-template2.ts +1 -1
- package/treb-export/src/export-worker/export-worker.ts +1 -1
- package/treb-export/src/export-worker/index.worker.ts +1 -1
- package/treb-export/src/export2.ts +1 -1
- package/treb-export/src/import2.ts +1 -1
- package/treb-export/src/relationship.ts +1 -1
- package/treb-export/src/shared-strings2.ts +1 -1
- package/treb-export/src/template-2.ts +2 -2
- package/treb-export/src/workbook-sheet2.ts +1 -1
- package/treb-export/src/workbook-style2.ts +1 -1
- package/treb-export/src/workbook-theme2.ts +1 -1
- package/treb-export/src/workbook2.ts +1 -1
- package/treb-export/src/xml-utils.ts +1 -1
- package/treb-format/src/format.test.ts +21 -21
- package/treb-format/src/format.ts +1 -1
- package/treb-format/src/format_cache.ts +21 -21
- package/treb-format/src/format_parser.ts +1 -1
- package/treb-format/src/index.ts +4 -4
- package/treb-format/src/number_format_section.ts +21 -21
- package/treb-format/src/value_parser.ts +1 -1
- package/treb-grid/src/editors/autocomplete.ts +1 -1
- package/treb-grid/src/editors/autocomplete_matcher.ts +21 -21
- package/treb-grid/src/editors/editor.ts +1 -1
- package/treb-grid/src/editors/formula_bar.ts +1 -1
- package/treb-grid/src/editors/overlay_editor.ts +1 -1
- package/treb-grid/src/index.ts +1 -1
- package/treb-grid/src/layout/base_layout.ts +1 -1
- package/treb-grid/src/layout/grid_layout.ts +1 -1
- package/treb-grid/src/layout/rectangle_cache.ts +21 -21
- package/treb-grid/src/render/selection-renderer.ts +1 -1
- package/treb-grid/src/render/svg_header_overlay.ts +1 -1
- package/treb-grid/src/render/svg_selection_block.ts +1 -1
- package/treb-grid/src/render/tile_renderer.ts +1 -1
- package/treb-grid/src/types/annotation.ts +1 -1
- package/treb-grid/src/types/border_constants.ts +1 -1
- package/treb-grid/src/types/clipboard_data.ts +1 -1
- package/treb-grid/src/types/data_model.ts +1 -1
- package/treb-grid/src/types/drag_mask.ts +21 -21
- package/treb-grid/src/types/grid.ts +1 -1
- package/treb-grid/src/types/grid_base.ts +1 -1
- package/treb-grid/src/types/grid_command.ts +1 -1
- package/treb-grid/src/types/grid_events.ts +1 -1
- package/treb-grid/src/types/grid_options.ts +1 -1
- package/treb-grid/src/types/grid_selection.ts +1 -1
- package/treb-grid/src/types/named_range.ts +1 -1
- package/treb-grid/src/types/scale-control.ts +1 -1
- package/treb-grid/src/types/serialize_options.ts +1 -1
- package/treb-grid/src/types/set_range_options.ts +1 -1
- package/treb-grid/src/types/sheet.ts +1 -1
- package/treb-grid/src/types/sheet_types.ts +1 -1
- package/treb-grid/src/types/tab_bar.ts +1 -1
- package/treb-grid/src/types/tile.ts +21 -21
- package/treb-grid/src/types/update_flags.ts +1 -1
- package/treb-grid/src/util/fontmetrics2.ts +1 -1
- package/treb-grid/src/util/ua.ts +21 -21
- package/treb-parser/src/csv-parser.ts +21 -21
- package/treb-parser/src/index.ts +5 -5
- package/treb-parser/src/md-parser.ts +1 -1
- package/treb-parser/src/parser-types.ts +1 -1
- package/treb-parser/src/parser.test.ts +21 -21
- package/treb-parser/src/parser.ts +1 -1
- package/treb-utils/src/event_source.ts +1 -1
- package/treb-utils/src/ievent_source.ts +13 -13
- package/treb-utils/src/index.ts +1 -1
- package/treb-utils/src/measurement.ts +1 -1
- package/treb-utils/src/scale.ts +21 -21
- package/treb-utils/src/serialize_html.ts +1 -1
package/treb-charts/src/chart.ts
CHANGED
|
@@ -1,1291 +1,92 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
break;
|
|
94
|
-
|
|
95
|
-
default:
|
|
96
|
-
this.Clear();
|
|
97
|
-
break;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
public Clear() {
|
|
103
|
-
this.chart_data = { type: 'null' };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* column/bar chart, now using common Series data and routines
|
|
108
|
-
*
|
|
109
|
-
* @param args arguments: data, categories, title, options
|
|
110
|
-
* @param type
|
|
111
|
-
*/
|
|
112
|
-
public CreateColumnChart(args: [UnionValue?, UnionValue?, string?, string?], type: 'bar'|'column'): void {
|
|
113
|
-
|
|
114
|
-
const series: SeriesType[] = this.TransformSeriesData(args[0]);
|
|
115
|
-
|
|
116
|
-
const common = this.CommonData(series);
|
|
117
|
-
|
|
118
|
-
let category_labels: string[] | undefined;
|
|
119
|
-
|
|
120
|
-
if (args[1]) {
|
|
121
|
-
|
|
122
|
-
const values = args[1].type === ValueType.array ? Util.Flatten(args[1].value) : Util.Flatten(args[1]);
|
|
123
|
-
category_labels = values.map((cell) => {
|
|
124
|
-
if (!cell) { return ''; }
|
|
125
|
-
|
|
126
|
-
if (cell.type === ValueType.object && cell.value.type === 'metadata') {
|
|
127
|
-
if (typeof cell.value.value === 'number') {
|
|
128
|
-
const format = NumberFormatCache.Get(cell.value.format || DEFAULT_FORMAT);
|
|
129
|
-
return format.Format(cell.value.value);
|
|
130
|
-
}
|
|
131
|
-
return cell.value.value;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (typeof cell.value === 'number') {
|
|
135
|
-
const format = NumberFormatCache.Get(cell.format || DEFAULT_FORMAT);
|
|
136
|
-
return format.Format(cell.value);
|
|
137
|
-
}
|
|
138
|
-
return cell.value;
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const count = series.reduce((a, entry) => Math.max(a, entry.y.data.length), 0);
|
|
142
|
-
|
|
143
|
-
if(count < category_labels.length) {
|
|
144
|
-
category_labels = category_labels.slice(0, count);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
while (count > category_labels.length) { category_labels.push(''); }
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const title = args[2]?.toString() || undefined;
|
|
152
|
-
const options = args[3]?.toString() || undefined;
|
|
153
|
-
|
|
154
|
-
this.chart_data = {
|
|
155
|
-
type,
|
|
156
|
-
legend: common.legend,
|
|
157
|
-
// legend_position: LegendPosition.right,
|
|
158
|
-
legend_style: LegendStyle.marker,
|
|
159
|
-
series2: series,
|
|
160
|
-
scale: common.y.scale,
|
|
161
|
-
title,
|
|
162
|
-
y_labels: type === 'bar' ? category_labels : common.y.labels, // swapped
|
|
163
|
-
x_labels: type === 'bar' ? common.y.labels : category_labels, // swapped
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
if (options) {
|
|
167
|
-
(this.chart_data as BarData).round = /round/i.test(options);
|
|
168
|
-
this.chart_data.data_labels = /labels/i.test(options);
|
|
169
|
-
|
|
170
|
-
let match = options.match(/labels="(.*?)"/);
|
|
171
|
-
if (match && series) {
|
|
172
|
-
this.ApplyLabels(series, match[1], category_labels);
|
|
173
|
-
}
|
|
174
|
-
else {
|
|
175
|
-
match = options.match(/labels=([^\s\r\n,]+)(?:\W|$)/);
|
|
176
|
-
if (match && series) {
|
|
177
|
-
this.ApplyLabels(series, match[1], category_labels);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
match = options.match(/class=([\w_-]+)(?:\W|$)/);
|
|
183
|
-
if (match) {
|
|
184
|
-
this.chart_data.class_name = match[1];
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
public ReadSeries(data: Array<any>): SeriesType {
|
|
192
|
-
|
|
193
|
-
// in this case it's (label, X, Y)
|
|
194
|
-
const series: SeriesType = {
|
|
195
|
-
x: { data: [] },
|
|
196
|
-
y: { data: [] },
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
if (data[3] && typeof data[3] === 'number') {
|
|
200
|
-
series.index = data[3];
|
|
201
|
-
}
|
|
202
|
-
if (data[4]) {
|
|
203
|
-
series.subtype = data[4].toString();
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (data[0]) {
|
|
207
|
-
|
|
208
|
-
const flat = Util.Flatten(data[0]);
|
|
209
|
-
|
|
210
|
-
// this could be a string, if it's a literal, or metadata
|
|
211
|
-
// [why would we want metadata?]
|
|
212
|
-
//
|
|
213
|
-
// OK, check that, should be a string (or other literal)
|
|
214
|
-
|
|
215
|
-
if (typeof flat[0] === 'object') {
|
|
216
|
-
series.label = flat[0]?.value?.toString() || '';
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
series.label = flat[0].toString();
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// read [2] first, so we can default for [1] if necessary
|
|
224
|
-
|
|
225
|
-
if (!!data[2] && (typeof data[2] === 'object') && data[2].type === ValueType.array) {
|
|
226
|
-
const flat = Util.Flatten(data[2].value);
|
|
227
|
-
series.y.data = flat.map(item => typeof item.value.value === 'number' ? item.value.value : undefined);
|
|
228
|
-
if (flat[0].value?.format) {
|
|
229
|
-
series.y.format = flat[0].value?.format as string;
|
|
230
|
-
const format = NumberFormatCache.Get(series.y.format);
|
|
231
|
-
series.y.labels = series.y.data.map(value => (value === undefined) ? undefined : format.Format(value));
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (!!data[1] && (typeof data[1] === 'object') && data[1].type === ValueType.array) {
|
|
236
|
-
const flat = Util.Flatten(data[1].value);
|
|
237
|
-
series.x.data = flat.map(item => typeof item.value.value === 'number' ? item.value.value : undefined);
|
|
238
|
-
if (flat[0].value.format) {
|
|
239
|
-
series.x.format = flat[0].value.format;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
for (const subseries of [series.x, series.y]) {
|
|
244
|
-
|
|
245
|
-
// in case of no values
|
|
246
|
-
if (subseries.data.length) {
|
|
247
|
-
const values = subseries.data.filter(value => value || value === 0) as number[];
|
|
248
|
-
subseries.range = {
|
|
249
|
-
min: Math.min.apply(0, values),
|
|
250
|
-
max: Math.max.apply(0, values),
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return series;
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
public ArrayToSeries(array_data: ArrayUnion): SeriesType {
|
|
260
|
-
|
|
261
|
-
// this is an array of Y, X not provided
|
|
262
|
-
|
|
263
|
-
const series: SeriesType = { x: { data: [] }, y: { data: [] }, };
|
|
264
|
-
const flat = Util.Flatten(array_data.value);
|
|
265
|
-
|
|
266
|
-
// series.y.data = flat.map(item => typeof item.value === 'number' ? item.value : undefined);
|
|
267
|
-
|
|
268
|
-
series.y.data = flat.map((item, index) => {
|
|
269
|
-
|
|
270
|
-
// if the data is passed in from the output of a function, it will not
|
|
271
|
-
// be inside a metadata structure
|
|
272
|
-
|
|
273
|
-
if (typeof item.value === 'number') { return item.value; }
|
|
274
|
-
|
|
275
|
-
// ... ok, it's metadata (why not just test?) ...
|
|
276
|
-
|
|
277
|
-
// experimenting with complex... put real in X axis and imaginary in Y axis
|
|
278
|
-
// note should also function w/ complex not in a metadata structure
|
|
279
|
-
|
|
280
|
-
if (typeof item.value.value?.real === 'number') {
|
|
281
|
-
series.x.data[index] = item.value.value.real;
|
|
282
|
-
return item.value.value.imaginary;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return typeof item.value.value === 'number' ? item.value.value : undefined;
|
|
286
|
-
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
if (flat[0].value.format) {
|
|
290
|
-
series.y.format = flat[0].value.format || '';
|
|
291
|
-
const format = NumberFormatCache.Get(series.y.format || '');
|
|
292
|
-
series.y.labels = series.y.data.map(value => (value === undefined) ? undefined : format.Format(value));
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const values = series.y.data.filter(value => value || value === 0) as number[];
|
|
296
|
-
series.y.range = {
|
|
297
|
-
min: Math.min.apply(0, values),
|
|
298
|
-
max: Math.max.apply(0, values),
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
// experimenting with complex... this should only be set if we populated
|
|
302
|
-
// it from complex values
|
|
303
|
-
|
|
304
|
-
if (series.x.data.length) {
|
|
305
|
-
|
|
306
|
-
const filtered: number[] = series.x.data.filter(test => typeof test === 'number') as number[];
|
|
307
|
-
series.x.range = {
|
|
308
|
-
min: Math.min.apply(0, filtered),
|
|
309
|
-
max: Math.max.apply(0, filtered),
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (flat[0].value.format) {
|
|
313
|
-
series.x.format = flat[0].value.format || '';
|
|
314
|
-
const format = NumberFormatCache.Get(series.x.format || '');
|
|
315
|
-
series.x.labels = series.x.data.map(value => (value === undefined) ? undefined : format.Format(value));
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return series;
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* composite data -> series. composite data can be
|
|
326
|
-
*
|
|
327
|
-
* (1) set of Y values, with X not provided;
|
|
328
|
-
* (2) SERIES(label, X, Y) with Y required, others optional
|
|
329
|
-
* (3) GROUP(a, b, ...), where entries are either arrays as (1) or SERIES as (2)
|
|
330
|
-
*
|
|
331
|
-
* FIXME: consider supporting GROUP(SERIES, [y], ...)
|
|
332
|
-
*
|
|
333
|
-
* NOTE: (1) could be an array of boxed (union) values...
|
|
334
|
-
*
|
|
335
|
-
*/
|
|
336
|
-
public TransformSeriesData(raw_data?: UnionValue, default_x?: UnionValue): SeriesType[] {
|
|
337
|
-
|
|
338
|
-
if (!raw_data) { return []; }
|
|
339
|
-
|
|
340
|
-
const list: SeriesType[] = [];
|
|
341
|
-
|
|
342
|
-
if (raw_data.type === ValueType.object) {
|
|
343
|
-
if (raw_data.key === 'group') {
|
|
344
|
-
if (Array.isArray(raw_data.value)) {
|
|
345
|
-
for (const entry of raw_data.value) {
|
|
346
|
-
if (!!entry && (typeof entry === 'object')) {
|
|
347
|
-
if (entry.key === 'series') {
|
|
348
|
-
const series = this.ReadSeries(entry.value);
|
|
349
|
-
list.push(series);
|
|
350
|
-
}
|
|
351
|
-
else if (entry.type === ValueType.array) {
|
|
352
|
-
list.push(this.ArrayToSeries(entry));
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
else if (raw_data.key === 'series') {
|
|
359
|
-
const series = this.ReadSeries(raw_data.value);
|
|
360
|
-
list.push(series);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
else if (raw_data.type === ValueType.array) {
|
|
364
|
-
list.push(this.ArrayToSeries(raw_data));
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// now we may or may not have X for each series, so we need
|
|
368
|
-
// to patch. it's also possible (as with older chart functions)
|
|
369
|
-
// that there's a common X -- not sure if we want to continue
|
|
370
|
-
// to support that or not...
|
|
371
|
-
|
|
372
|
-
let baseline_x: SubSeries|undefined;
|
|
373
|
-
let max_y_length = 0;
|
|
374
|
-
|
|
375
|
-
// if we have a default, use that (and range it)
|
|
376
|
-
|
|
377
|
-
if (default_x?.type === ValueType.array) {
|
|
378
|
-
|
|
379
|
-
const values = Util.Flatten(default_x.value);
|
|
380
|
-
|
|
381
|
-
let format = '0.00###';
|
|
382
|
-
|
|
383
|
-
if (values[0] && values[0].type === ValueType.object) { // UnionIs.Extended(values[0])) {
|
|
384
|
-
format = values[0].value.format;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const data = values.map(x => {
|
|
388
|
-
if (x.type === ValueType.number) { return x.value; }
|
|
389
|
-
if (x.type === ValueType.object) { // ??
|
|
390
|
-
// if (UnionIs.Extended(x)) { // ?
|
|
391
|
-
return x.value.value;
|
|
392
|
-
}
|
|
393
|
-
return undefined;
|
|
394
|
-
}) as Array<number|undefined>;
|
|
395
|
-
|
|
396
|
-
const filtered = data.filter(x => typeof x === 'number') as number[];
|
|
397
|
-
|
|
398
|
-
baseline_x = {
|
|
399
|
-
data,
|
|
400
|
-
format,
|
|
401
|
-
range: {
|
|
402
|
-
min: Math.min.apply(0, filtered),
|
|
403
|
-
max: Math.max.apply(0, filtered),
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// look for the first set that has values. at the same time, get max len
|
|
409
|
-
|
|
410
|
-
for (const entry of list) {
|
|
411
|
-
max_y_length = Math.max(max_y_length, entry.y.data.length);
|
|
412
|
-
if (entry.x.data.length) {
|
|
413
|
-
if (!baseline_x) {
|
|
414
|
-
baseline_x = entry.x;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// now default for any series missing X
|
|
420
|
-
|
|
421
|
-
if (!baseline_x) {
|
|
422
|
-
baseline_x = {
|
|
423
|
-
data: [],
|
|
424
|
-
range: {
|
|
425
|
-
min: 0,
|
|
426
|
-
max: Math.max(0, max_y_length - 1),
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
for (let i = 0; i < max_y_length; i++) { baseline_x.data.push(i); }
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
for (const entry of list) {
|
|
433
|
-
if (!entry.x.data.length) {
|
|
434
|
-
entry.x = baseline_x;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
return list;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/** get a unified scale, and formats */
|
|
442
|
-
public CommonData(series: SeriesType[], y_floor?: number, y_ceiling?: number) {
|
|
443
|
-
|
|
444
|
-
let x_format = '';
|
|
445
|
-
let y_format = '';
|
|
446
|
-
|
|
447
|
-
for (const entry of series) {
|
|
448
|
-
if (entry.y.format && !y_format) { y_format = entry.y.format; }
|
|
449
|
-
if (entry.x.format && !x_format) { x_format = entry.x.format; }
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
let legend: Array<{label: string, index?: number}>|undefined; // string[]|undefined;
|
|
453
|
-
if (series.some(test => test.label && (test.label.length > 0))) {
|
|
454
|
-
legend = series.map((entry, i) => ({
|
|
455
|
-
label: entry.label || `Series ${i + 1}`,
|
|
456
|
-
index: typeof entry.index === 'number' ? entry.index : i + 1,
|
|
457
|
-
}));
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const x = series.filter(test => test.x.range);
|
|
461
|
-
const x_min = Math.min.apply(0, x.map(test => test.x.range?.min || 0));
|
|
462
|
-
const x_max = Math.max.apply(0, x.map(test => test.x.range?.max || 0));
|
|
463
|
-
|
|
464
|
-
const y = series.filter(test => test.y.range);
|
|
465
|
-
let y_min = Math.min.apply(0, x.map(test => test.y.range?.min || 0));
|
|
466
|
-
let y_max = Math.max.apply(0, x.map(test => test.y.range?.max || 0));
|
|
467
|
-
|
|
468
|
-
if (typeof y_floor !== 'undefined') {
|
|
469
|
-
y_min = Math.min(y_min, y_floor);
|
|
470
|
-
}
|
|
471
|
-
if (typeof y_ceiling !== 'undefined') {
|
|
472
|
-
y_max = Math.max(y_max, y_ceiling);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const x_scale = Util.Scale(x_min, x_max, 7);
|
|
476
|
-
const y_scale = Util.Scale(y_min, y_max, 7);
|
|
477
|
-
|
|
478
|
-
let x_labels: string[]|undefined;
|
|
479
|
-
let y_labels: string[]|undefined;
|
|
480
|
-
|
|
481
|
-
if (x_format) {
|
|
482
|
-
x_labels = [];
|
|
483
|
-
const format = NumberFormatCache.Get(x_format);
|
|
484
|
-
for (let i = 0; i <= x_scale.count; i++) {
|
|
485
|
-
x_labels.push(format.Format(x_scale.min + i * x_scale.step));
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
if (y_format) {
|
|
490
|
-
y_labels = [];
|
|
491
|
-
const format = NumberFormatCache.Get(y_format);
|
|
492
|
-
for (let i = 0; i <= y_scale.count; i++) {
|
|
493
|
-
y_labels.push(format.Format(y_scale.min + i * y_scale.step));
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
return {
|
|
498
|
-
x: {
|
|
499
|
-
format: x_format,
|
|
500
|
-
scale: x_scale,
|
|
501
|
-
labels: x_labels,
|
|
502
|
-
},
|
|
503
|
-
y: {
|
|
504
|
-
format: y_format,
|
|
505
|
-
scale: y_scale,
|
|
506
|
-
labels: y_labels,
|
|
507
|
-
},
|
|
508
|
-
legend,
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
/**
|
|
514
|
-
* args is [data, title, options]
|
|
515
|
-
*
|
|
516
|
-
* args[0] is the scatter data. this can be
|
|
517
|
-
*
|
|
518
|
-
* (1) set of Y values, with X not provided;
|
|
519
|
-
* (2) SERIES(label, X, Y) with Y required, others optional
|
|
520
|
-
* (3) GROUP(SERIES(label, X, Y), SERIES(label, X, Y), ...), with same rule for each series
|
|
521
|
-
*
|
|
522
|
-
* @param args
|
|
523
|
-
*/
|
|
524
|
-
public CreateScatterChart(args: any[], style: 'plot'|'line' = 'plot'): void {
|
|
525
|
-
|
|
526
|
-
// FIXME: transform the data, then have this function
|
|
527
|
-
// operate on clean data. that way the transform can
|
|
528
|
-
// be reused (and the function can be reused without the
|
|
529
|
-
// transform).
|
|
530
|
-
|
|
531
|
-
const series: SeriesType[] = this.TransformSeriesData(args[0]);
|
|
532
|
-
|
|
533
|
-
const common = this.CommonData(series);
|
|
534
|
-
|
|
535
|
-
const title = args[1]?.toString() || undefined;
|
|
536
|
-
const options = args[2]?.toString() || undefined;
|
|
537
|
-
|
|
538
|
-
this.chart_data = {
|
|
539
|
-
legend: common.legend,
|
|
540
|
-
style,
|
|
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: style === 'line', // true,
|
|
552
|
-
points: style === 'plot',
|
|
553
|
-
|
|
554
|
-
};
|
|
555
|
-
|
|
556
|
-
if (options) {
|
|
557
|
-
|
|
558
|
-
this.chart_data.markers = /marker/i.test(options);
|
|
559
|
-
this.chart_data.smooth = /smooth/i.test(options);
|
|
560
|
-
this.chart_data.data_labels = /labels/i.test(options);
|
|
561
|
-
|
|
562
|
-
let match = options.match(/labels="(.*?)"/);
|
|
563
|
-
if (match && this.chart_data.series) {
|
|
564
|
-
this.ApplyLabels(this.chart_data.series, match[1]);
|
|
565
|
-
}
|
|
566
|
-
else {
|
|
567
|
-
match = options.match(/labels=([^\s\r\n,]+)(?:\W|$)/);
|
|
568
|
-
if (match && this.chart_data.series) {
|
|
569
|
-
this.ApplyLabels(this.chart_data.series, match[1]);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
match = options.match(/class=([\w_-]+)(?:\W|$)/);
|
|
574
|
-
if (match) {
|
|
575
|
-
this.chart_data.class_name = match[1];
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
public ApplyLabels(series_list: SeriesType[], pattern: string, category_labels?: string[]): void {
|
|
583
|
-
|
|
584
|
-
for (const series of series_list) {
|
|
585
|
-
|
|
586
|
-
const format = {
|
|
587
|
-
x: NumberFormatCache.Get(series.x.format || ''),
|
|
588
|
-
y: NumberFormatCache.Get(series.y.format || ''),
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
series.y.labels = [];
|
|
592
|
-
|
|
593
|
-
for (let i = 0; i < series.y.data.length; i++) {
|
|
594
|
-
|
|
595
|
-
const x = category_labels ? category_labels[i] :
|
|
596
|
-
(typeof series.x.data[i] === 'number' ? format.x.Format(series.x.data[i]) : '');
|
|
597
|
-
const y = typeof series.y.data[i] === 'number' ? format.y.Format(series.y.data[i]) : '';
|
|
598
|
-
|
|
599
|
-
series.y.labels[i] = pattern.replace(/\bx\b/g, x).replace(/\by\b/g, y);
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* args: data, labels, title, callouts, "smooth"
|
|
609
|
-
*/
|
|
610
|
-
public CreateLineChart(args: any[], type: 'line'|'area'): void { // |'bar'|'column') {
|
|
611
|
-
|
|
612
|
-
const series: SeriesType[] = this.TransformSeriesData(args[0], args[1]);
|
|
613
|
-
|
|
614
|
-
const common = this.CommonData(series, 0, 0);
|
|
615
|
-
|
|
616
|
-
const title = args[2]?.toString() || undefined;
|
|
617
|
-
const options = args[3]?.toString() || undefined;
|
|
618
|
-
|
|
619
|
-
this.chart_data = {
|
|
620
|
-
legend: common.legend,
|
|
621
|
-
// style: type, // 'line',
|
|
622
|
-
type: 'scatter2',
|
|
623
|
-
series, // : [{x, y}],
|
|
624
|
-
title,
|
|
625
|
-
|
|
626
|
-
x_scale: common.x.scale,
|
|
627
|
-
x_labels: common.x.labels,
|
|
628
|
-
|
|
629
|
-
y_scale: common.y.scale,
|
|
630
|
-
y_labels: common.y.labels,
|
|
631
|
-
|
|
632
|
-
lines: true,
|
|
633
|
-
filled: type === 'area',
|
|
634
|
-
|
|
635
|
-
};
|
|
636
|
-
|
|
637
|
-
if (options) {
|
|
638
|
-
// this.chart_data.markers = /marker/i.test(options);
|
|
639
|
-
this.chart_data.smooth = /smooth/i.test(options);
|
|
640
|
-
// this.chart_data.data_labels = /labels/i.test(options);
|
|
641
|
-
|
|
642
|
-
const match = options.match(/class=([\w_-]+)(?:\W|$)/);
|
|
643
|
-
if (match) {
|
|
644
|
-
this.chart_data.class_name = match[1];
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
/**
|
|
652
|
-
* arguments are values, labels, title, sort, label option, ...
|
|
653
|
-
*/
|
|
654
|
-
public CreateDonut(args: [UnionValue?, UnionValue?, string?, string?, string?], pie_chart = false): void {
|
|
655
|
-
|
|
656
|
-
const raw_data = args[0]?.type === ValueType.array ? args[0].value : args[0];
|
|
657
|
-
|
|
658
|
-
// we're now expecting this to be metadata (including value).
|
|
659
|
-
// so we need to unpack. could be an array... could be deep...
|
|
660
|
-
const flat = Util.Flatten(raw_data);
|
|
661
|
-
|
|
662
|
-
// we still need the aggregate for range, scale
|
|
663
|
-
let data = flat.map((x) => (typeof x.value.value === 'number') ? x.value.value : undefined) as number[];
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
// if labels are strings, just pass them in. if they're numbers then
|
|
667
|
-
// use the format (we're collecting metadata for this field now)
|
|
668
|
-
|
|
669
|
-
const raw_labels = args[1]?.type === ValueType.array ? args[1].value : args[1];
|
|
670
|
-
|
|
671
|
-
const labels = Util.Flatten(raw_labels).map((label) => {
|
|
672
|
-
if (label && typeof label === 'object') {
|
|
673
|
-
const value = label.value?.value;
|
|
674
|
-
if (typeof value === 'number' && label.value?.format) {
|
|
675
|
-
return NumberFormatCache.Get(label.value?.format).Format(value);
|
|
676
|
-
}
|
|
677
|
-
else return value ? value.toString() : '';
|
|
678
|
-
}
|
|
679
|
-
else return label ? label.toString() : '';
|
|
680
|
-
});
|
|
681
|
-
|
|
682
|
-
// no negative numbers
|
|
683
|
-
|
|
684
|
-
data = data.map((check) => {
|
|
685
|
-
if (check < 0) {
|
|
686
|
-
console.warn('pie/donut chart does not support negative values (omitted)');
|
|
687
|
-
return 0;
|
|
688
|
-
}
|
|
689
|
-
return check;
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
const title = args[2] || '';
|
|
693
|
-
|
|
694
|
-
let sum = 0;
|
|
695
|
-
|
|
696
|
-
const slices: DonutSlice[] = data.map((value, i) => {
|
|
697
|
-
if (typeof value !== 'undefined') sum += value;
|
|
698
|
-
return { value, label: labels[i] || '', index: i + 1, percent: 0 };
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
if (sum) {
|
|
702
|
-
for (const slice of slices) {
|
|
703
|
-
slice.percent = (slice.value || 0) / sum;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// titles? label/value/percent
|
|
708
|
-
// FIXME: number format(s)
|
|
709
|
-
|
|
710
|
-
const format_pattern = (flat.length && flat[0].value?.format) ? flat[0].value.format : '';
|
|
711
|
-
const format = NumberFormatCache.Get(format_pattern || DEFAULT_FORMAT);
|
|
712
|
-
const percent_format = NumberFormatCache.Get('percent');
|
|
713
|
-
|
|
714
|
-
// ensure label if we have labels array but no label format string
|
|
715
|
-
|
|
716
|
-
if (typeof args[4] === 'undefined' && args[1]) {
|
|
717
|
-
args[4] = 'label';
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
const slice_title = (args[4] || '');
|
|
721
|
-
if (slice_title) {
|
|
722
|
-
for (const slice of slices) {
|
|
723
|
-
const value = /*NumberFormatCache.Get('general')*/ format.Format(slice.value || 0);
|
|
724
|
-
const percent = percent_format.Format(slice.percent);
|
|
725
|
-
slice.title = slice_title
|
|
726
|
-
.replace(/value%/ig, percent_format.Format(slice.value || 0))
|
|
727
|
-
.replace(/value/ig, value)
|
|
728
|
-
.replace(/percent/ig, percent)
|
|
729
|
-
.replace(/label/ig, slice.label || '')
|
|
730
|
-
.trim();
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
// optionally sort...
|
|
735
|
-
|
|
736
|
-
const options = (args[3] || '').toString().trim();
|
|
737
|
-
|
|
738
|
-
// old-style...
|
|
739
|
-
|
|
740
|
-
let sort = options.toUpperCase();
|
|
741
|
-
if (sort === 'ASC' || sort === 'ASCENDING' || sort === 'INC') {
|
|
742
|
-
slices.sort((a, b) => { return (a.value || 0) - (b.value || 0); });
|
|
743
|
-
}
|
|
744
|
-
else if (sort === 'DESC' || sort === 'DESCENDING' || sort === 'DEC') {
|
|
745
|
-
slices.sort((a, b) => { return (b.value || 0) - (a.value || 0); });
|
|
746
|
-
}
|
|
747
|
-
else {
|
|
748
|
-
const match = options.match(/sort=([\w]+)(?:\W|$)/i);
|
|
749
|
-
if (match) {
|
|
750
|
-
sort = match[1];
|
|
751
|
-
if (/^(asc|inc)/i.test(sort)) {
|
|
752
|
-
slices.sort((a, b) => { return (a.value || 0) - (b.value || 0); });
|
|
753
|
-
}
|
|
754
|
-
else if (/^(desc|dec)/i.test(sort)) {
|
|
755
|
-
slices.sort((a, b) => { return (b.value || 0) - (a.value || 0); });
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
this.chart_data = {
|
|
761
|
-
type: pie_chart ? 'pie' : 'donut',
|
|
762
|
-
slices,
|
|
763
|
-
title,
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
if (options) {
|
|
767
|
-
const match = options.match(/class=([_-\w]+)(?:\W|$)/);
|
|
768
|
-
if (match) {
|
|
769
|
-
this.chart_data.class_name = match[1];
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
/** pass-through */
|
|
777
|
-
public Resize() {
|
|
778
|
-
this.renderer.Resize();
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
/**
|
|
782
|
-
* redraw
|
|
783
|
-
*/
|
|
784
|
-
public Update() {
|
|
785
|
-
|
|
786
|
-
// reset
|
|
787
|
-
this.renderer.Resize(); // just too many problems
|
|
788
|
-
this.renderer.Prerender();
|
|
789
|
-
this.renderer.Clear(this.chart_data.class_name);
|
|
790
|
-
|
|
791
|
-
// get usable area [FIXME: method]
|
|
792
|
-
const area = new Area(0, 0, this.renderer.size.width, this.renderer.size.height);
|
|
793
|
-
|
|
794
|
-
// chart margin
|
|
795
|
-
const chart_margin = {
|
|
796
|
-
top: Math.round(area.height) * this.margin.top,
|
|
797
|
-
bottom: Math.round(area.height) * this.margin.bottom,
|
|
798
|
-
left: Math.round(area.width) * this.margin.left,
|
|
799
|
-
right: Math.round(area.width) * this.margin.right,
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
// title, top or bottom
|
|
803
|
-
const title = this.chart_data.title;
|
|
804
|
-
|
|
805
|
-
if (title) {
|
|
806
|
-
this.renderer.RenderTitle(title, area, chart_margin.top,
|
|
807
|
-
this.chart_data.title_layout||'top');
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
// pad
|
|
811
|
-
area.top += chart_margin.top;
|
|
812
|
-
area.left += chart_margin.left;
|
|
813
|
-
area.bottom -= chart_margin.bottom;
|
|
814
|
-
area.right -= chart_margin.right;
|
|
815
|
-
|
|
816
|
-
if (this.chart_data.legend && this.chart_data.legend.length) {
|
|
817
|
-
|
|
818
|
-
let default_position = LegendPosition.top;
|
|
819
|
-
if (this.chart_data.title) {
|
|
820
|
-
if (!this.chart_data.title_layout || this.chart_data.title_layout === 'top') {
|
|
821
|
-
default_position = LegendPosition.bottom;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
const position = this.chart_data.legend_position || default_position;
|
|
826
|
-
|
|
827
|
-
this.renderer.Legend({
|
|
828
|
-
labels: this.chart_data.legend,
|
|
829
|
-
position,
|
|
830
|
-
style: this.chart_data.legend_style,
|
|
831
|
-
layout: (position === LegendPosition.top || position === LegendPosition.bottom) ?
|
|
832
|
-
LegendLayout.horizontal : LegendLayout.vertical,
|
|
833
|
-
area,
|
|
834
|
-
});
|
|
835
|
-
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
if (this.chart_data.type === 'histogram'
|
|
839
|
-
|| this.chart_data.type === 'line'
|
|
840
|
-
|| this.chart_data.type === 'area'
|
|
841
|
-
|| this.chart_data.type === 'column'
|
|
842
|
-
|| this.chart_data.type === 'histogram2'
|
|
843
|
-
|| this.chart_data.type === 'bar'
|
|
844
|
-
|| this.chart_data.type === 'scatter2'
|
|
845
|
-
) {
|
|
846
|
-
|
|
847
|
-
// we need to measure first, then lay out the other axis, then we
|
|
848
|
-
// can come back and render. it doesn't really matter which one you
|
|
849
|
-
// do first.
|
|
850
|
-
|
|
851
|
-
// measure x axis (height)
|
|
852
|
-
|
|
853
|
-
let x_metrics: Metrics[] = [];
|
|
854
|
-
let max_x_height = 0;
|
|
855
|
-
|
|
856
|
-
if (this.chart_data.x_labels && this.chart_data.x_labels.length) {
|
|
857
|
-
x_metrics = this.chart_data.x_labels.map((text) => {
|
|
858
|
-
const metrics = this.renderer.MeasureText(text, ['axis-label', 'x-axis-label'], true);
|
|
859
|
-
max_x_height = Math.max(max_x_height, metrics.height);
|
|
860
|
-
return metrics;
|
|
861
|
-
});
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
// measure & render y axis
|
|
865
|
-
|
|
866
|
-
if (this.chart_data.y_labels && this.chart_data.y_labels.length) {
|
|
867
|
-
|
|
868
|
-
const y_labels: Array<{label: string; metrics: Metrics}> = [];
|
|
869
|
-
let max_width = 0;
|
|
870
|
-
let max_height = 0;
|
|
871
|
-
|
|
872
|
-
const scale = (this.chart_data.type === 'scatter2') ? this.chart_data.y_scale : this.chart_data.scale;
|
|
873
|
-
|
|
874
|
-
const count = (this.chart_data.type === 'bar') ?
|
|
875
|
-
this.chart_data.y_labels.length :
|
|
876
|
-
/* this.chart_data. */
|
|
877
|
-
scale.count + 1;
|
|
878
|
-
|
|
879
|
-
for (let i = 0; i < count; i++ ){
|
|
880
|
-
const metrics = this.renderer.MeasureText(this.chart_data.y_labels[i], ['axis-label', 'y-axis-label']);
|
|
881
|
-
y_labels.push({ label: this.chart_data.y_labels[i], metrics });
|
|
882
|
-
max_width = Math.max(max_width, metrics.width);
|
|
883
|
-
max_height = Math.max(max_height, metrics.height);
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
area.bottom = Math.round(area.bottom - max_height / 2);
|
|
887
|
-
area.top = Math.round(area.top + max_height / 2);
|
|
888
|
-
|
|
889
|
-
if (x_metrics.length) {
|
|
890
|
-
area.bottom -= (max_x_height + chart_margin.bottom);
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
if (this.chart_data.type === 'bar') {
|
|
894
|
-
this.renderer.RenderYAxisBar(area, area.left + max_width, y_labels, ['axis-label', 'y-axis-label']);
|
|
895
|
-
}
|
|
896
|
-
else {
|
|
897
|
-
this.renderer.RenderYAxis(area, area.left + max_width, y_labels, ['axis-label', 'y-axis-label']);
|
|
898
|
-
}
|
|
899
|
-
area.left += (max_width + chart_margin.left);
|
|
900
|
-
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
// now render x axis
|
|
904
|
-
|
|
905
|
-
if (x_metrics.length && this.chart_data.x_labels && this.chart_data.x_labels.length) {
|
|
906
|
-
|
|
907
|
-
const tick = (this.chart_data.type === 'histogram2');
|
|
908
|
-
const offset_tick = (
|
|
909
|
-
this.chart_data.type !== 'line' &&
|
|
910
|
-
this.chart_data.type !== 'area' &&
|
|
911
|
-
this.chart_data.type !== 'bar' &&
|
|
912
|
-
this.chart_data.type !== 'scatter2' &&
|
|
913
|
-
this.chart_data.type !== 'histogram2'
|
|
914
|
-
);
|
|
915
|
-
|
|
916
|
-
// do this before you fix the offset
|
|
917
|
-
|
|
918
|
-
if (tick) {
|
|
919
|
-
this.renderer.RenderXAxisTicks(area, offset_tick, this.chart_data.x_labels.length);
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
if (this.chart_data.y_labels) {
|
|
924
|
-
// undo, temp
|
|
925
|
-
area.bottom += (max_x_height + chart_margin.bottom);
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
// render
|
|
929
|
-
this.renderer.RenderXAxis(area,
|
|
930
|
-
offset_tick,
|
|
931
|
-
this.chart_data.x_labels,
|
|
932
|
-
x_metrics,
|
|
933
|
-
['axis-label', 'x-axis-label']);
|
|
934
|
-
|
|
935
|
-
// update bottom (either we unwound for labels, or we need to do it the first time)
|
|
936
|
-
area.bottom -= (max_x_height + chart_margin.bottom);
|
|
937
|
-
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// now do type-specific rendering
|
|
943
|
-
|
|
944
|
-
switch (this.chart_data.type) {
|
|
945
|
-
case 'scatter':
|
|
946
|
-
this.renderer.RenderPoints(area, this.chart_data.x, this.chart_data.y, 'mc mc-correlation series-1');
|
|
947
|
-
break;
|
|
948
|
-
|
|
949
|
-
case 'scatter2':
|
|
950
|
-
|
|
951
|
-
this.renderer.RenderGrid(area,
|
|
952
|
-
this.chart_data.y_scale.count,
|
|
953
|
-
this.chart_data.x_scale.count + 1, // (sigh)
|
|
954
|
-
'chart-grid');
|
|
955
|
-
|
|
956
|
-
if (this.chart_data.series) {
|
|
957
|
-
for (let i = 0; i < this.chart_data.series.length; i++) {
|
|
958
|
-
const series = this.chart_data.series[i];
|
|
959
|
-
|
|
960
|
-
let lines = !!this.chart_data.lines;
|
|
961
|
-
let points = !!this.chart_data.points;
|
|
962
|
-
|
|
963
|
-
if (series.subtype === 'plot') {
|
|
964
|
-
points = true;
|
|
965
|
-
lines = false;
|
|
966
|
-
}
|
|
967
|
-
else if (series.subtype === 'line') {
|
|
968
|
-
points = false;
|
|
969
|
-
lines = true;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
const index = typeof series.index === 'number' ? series.index : i + 1;
|
|
973
|
-
this.renderer.RenderScatterSeries(area,
|
|
974
|
-
series.x.data,
|
|
975
|
-
series.y.data,
|
|
976
|
-
this.chart_data.x_scale,
|
|
977
|
-
this.chart_data.y_scale,
|
|
978
|
-
lines,
|
|
979
|
-
points,
|
|
980
|
-
!!this.chart_data.filled,
|
|
981
|
-
!!this.chart_data.markers,
|
|
982
|
-
!!this.chart_data.smooth,
|
|
983
|
-
`scatter-plot series-${index}`);
|
|
984
|
-
}
|
|
985
|
-
if (this.chart_data.data_labels) {
|
|
986
|
-
for (let i = 0; i < this.chart_data.series.length; i++) {
|
|
987
|
-
const series = this.chart_data.series[i];
|
|
988
|
-
if (series.y.labels) {
|
|
989
|
-
this.renderer.RenderDataLabels(
|
|
990
|
-
area,
|
|
991
|
-
series.x.data,
|
|
992
|
-
series.y.data,
|
|
993
|
-
this.chart_data.x_scale,
|
|
994
|
-
this.chart_data.y_scale,
|
|
995
|
-
series.y.labels,
|
|
996
|
-
i + 1);
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
break;
|
|
1002
|
-
|
|
1003
|
-
case 'pie':
|
|
1004
|
-
case 'donut':
|
|
1005
|
-
{
|
|
1006
|
-
const outer = (Math.min(area.height, area.width) / 2) * .9;
|
|
1007
|
-
const inner = this.chart_data.type === 'pie' ? 0 : outer * .8;
|
|
1008
|
-
this.renderer.RenderDonut(this.chart_data.slices, area.center, outer, inner, area,
|
|
1009
|
-
true, 'donut');
|
|
1010
|
-
}
|
|
1011
|
-
break;
|
|
1012
|
-
|
|
1013
|
-
case 'line':
|
|
1014
|
-
case 'area':
|
|
1015
|
-
{
|
|
1016
|
-
const scale = this.chart_data.scale;
|
|
1017
|
-
if (this.chart_data.series) {
|
|
1018
|
-
|
|
1019
|
-
const points = this.chart_data.x_scale ?
|
|
1020
|
-
this.chart_data.x_scale.max :
|
|
1021
|
-
Math.max.apply(0, this.chart_data.series.map(x => x.length));
|
|
1022
|
-
|
|
1023
|
-
const func = this.chart_data.smooth ?
|
|
1024
|
-
this.renderer.RenderSmoothLine : this.renderer.RenderLine;
|
|
1025
|
-
|
|
1026
|
-
// gridlines
|
|
1027
|
-
this.renderer.RenderGrid(area,
|
|
1028
|
-
this.chart_data.scale.count,
|
|
1029
|
-
this.chart_data.x_scale ? this.chart_data.x_scale.count + 1 : points,
|
|
1030
|
-
'chart-grid');
|
|
1031
|
-
|
|
1032
|
-
// series
|
|
1033
|
-
let series_index = 0;
|
|
1034
|
-
for (const series of this.chart_data.series) {
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
const y = series.map((point) => {
|
|
1039
|
-
if (typeof point === 'undefined') { return undefined; }
|
|
1040
|
-
return Util.ApplyScale(point, area.height, scale);
|
|
1041
|
-
});
|
|
1042
|
-
|
|
1043
|
-
if (y.length < points) {
|
|
1044
|
-
for (let i = y.length; i < points; i++) {
|
|
1045
|
-
y.push(undefined);
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
const styles = [
|
|
1050
|
-
this.chart_data.type === 'area' ? 'chart-area' : 'chart-line',
|
|
1051
|
-
`series-${series_index + 1}`]
|
|
1052
|
-
|
|
1053
|
-
func.call(this.renderer, area, y, (this.chart_data.type === 'area'), this.chart_data.titles, styles);
|
|
1054
|
-
series_index++;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
// TODO: callouts
|
|
1059
|
-
|
|
1060
|
-
}
|
|
1061
|
-
break;
|
|
1062
|
-
|
|
1063
|
-
case 'bar':
|
|
1064
|
-
{
|
|
1065
|
-
let corners: number[]|undefined;
|
|
1066
|
-
|
|
1067
|
-
// gridlines
|
|
1068
|
-
this.renderer.RenderBarGrid(area, this.chart_data.scale.count, 'chart-grid');
|
|
1069
|
-
if (this.chart_data.series2) {
|
|
1070
|
-
|
|
1071
|
-
let count = 0;
|
|
1072
|
-
const series_count = this.chart_data.series2.length;
|
|
1073
|
-
|
|
1074
|
-
for (const series of this.chart_data.series2) {
|
|
1075
|
-
count = Math.max(count, series.y.data.length);
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
const row_height = area.height / count;
|
|
1079
|
-
let row_pct = .7;
|
|
1080
|
-
if (typeof this.chart_data.space === 'number') {
|
|
1081
|
-
row_pct = Math.max(0, Math.min(1, 1 - (this.chart_data.space)));
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
const space = row_height * (1 - row_pct) / 2;
|
|
1085
|
-
const height = (row_height - space * 2) / series_count;
|
|
1086
|
-
|
|
1087
|
-
let zero = 0;
|
|
1088
|
-
if (this.chart_data.scale.min < 0) { // && this.chart_data.scale.max >= 0) {
|
|
1089
|
-
zero = Util.ApplyScale(0, area.width, this.chart_data.scale);
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
if (this.chart_data.round) {
|
|
1093
|
-
const half_height = Math.floor(height / 2);
|
|
1094
|
-
corners = [0, half_height, half_height, 0];
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
for (let s = 0; s < series_count; s++) {
|
|
1098
|
-
const series = this.chart_data.series2[s];
|
|
1099
|
-
const color_index = typeof series.index === 'number' ? series.index : s + 1;
|
|
1100
|
-
|
|
1101
|
-
for (let i = 0; i < series.y.data.length; i++ ){
|
|
1102
|
-
const value = series.y.data[i];
|
|
1103
|
-
if (typeof value === 'number') {
|
|
1104
|
-
|
|
1105
|
-
const y = Math.round(area.top + i * row_height + space) + s * height;
|
|
1106
|
-
|
|
1107
|
-
let x = 0;
|
|
1108
|
-
let width = 0;
|
|
1109
|
-
let negative = false;
|
|
1110
|
-
|
|
1111
|
-
if (zero) {
|
|
1112
|
-
if (value > 0) {
|
|
1113
|
-
width = Util.ApplyScale(value + this.chart_data.scale.min, area.width, this.chart_data.scale);
|
|
1114
|
-
x = area.left + zero;
|
|
1115
|
-
}
|
|
1116
|
-
else {
|
|
1117
|
-
width = Util.ApplyScale(this.chart_data.scale.min - value, area.width, this.chart_data.scale);
|
|
1118
|
-
x = area.left + zero - width;
|
|
1119
|
-
negative = true;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
else {
|
|
1123
|
-
width = Util.ApplyScale(value, area.width, this.chart_data.scale);
|
|
1124
|
-
x = area.left;
|
|
1125
|
-
}
|
|
1126
|
-
|
|
1127
|
-
// const bar_title = this.chart_data.titles ? this.chart_data.titles[i] : undefined;
|
|
1128
|
-
const bar_title = undefined;
|
|
1129
|
-
|
|
1130
|
-
if (width) {
|
|
1131
|
-
this.renderer.RenderRectangle(new Area(
|
|
1132
|
-
x, y, x + width, y + height,
|
|
1133
|
-
), corners, ['chart-column', `series-${color_index}`], bar_title || undefined);
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
}
|
|
1142
|
-
break;
|
|
1143
|
-
|
|
1144
|
-
case 'column':
|
|
1145
|
-
case 'histogram2':
|
|
1146
|
-
{
|
|
1147
|
-
|
|
1148
|
-
// gridlines
|
|
1149
|
-
this.renderer.RenderGrid(area, this.chart_data.scale.count, 0, 'chart-grid');
|
|
1150
|
-
|
|
1151
|
-
if (this.chart_data.series2) {
|
|
1152
|
-
|
|
1153
|
-
let count = 0;
|
|
1154
|
-
const series_count = this.chart_data.series2.length;
|
|
1155
|
-
|
|
1156
|
-
for (const series of this.chart_data.series2) {
|
|
1157
|
-
count = Math.max(count, series.y.data.length);
|
|
1158
|
-
}
|
|
1159
|
-
|
|
1160
|
-
// columns
|
|
1161
|
-
const column_width = area.width / count;
|
|
1162
|
-
let column_pct = .7;
|
|
1163
|
-
if (typeof this.chart_data.space === 'number') {
|
|
1164
|
-
column_pct = Math.max(0, Math.min(1, 1 - (this.chart_data.space)));
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
const space = column_width * (1 - column_pct) / 2;
|
|
1168
|
-
const width = (column_width - space * 2) / series_count;
|
|
1169
|
-
|
|
1170
|
-
let zero = 0;
|
|
1171
|
-
if (this.chart_data.scale.min < 0) { // && this.chart_data.scale.max >= 0) {
|
|
1172
|
-
zero = Util.ApplyScale(0, area.height, this.chart_data.scale);
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
if (this.chart_data.callouts && this.chart_data.x_scale) {
|
|
1176
|
-
const scale = this.chart_data.x_scale;
|
|
1177
|
-
const lines = this.chart_data.callouts.map((callout, index) => {
|
|
1178
|
-
const x = Math.round(area.left + Util.ApplyScale(callout.value, area.width, scale)) + .5;
|
|
1179
|
-
return {
|
|
1180
|
-
x1: x, y1: area.bottom - area.height, x2: x, y2: area.bottom,
|
|
1181
|
-
classes: `callout-${index + 1}`,
|
|
1182
|
-
}
|
|
1183
|
-
});
|
|
1184
|
-
this.renderer.RenderCalloutLines(lines);
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
let corners: number[]|undefined;
|
|
1188
|
-
|
|
1189
|
-
if (this.chart_data.round) {
|
|
1190
|
-
const half_width = Math.floor(width/2);
|
|
1191
|
-
corners = [half_width, half_width, 0, 0];
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
for (let s = 0; s < series_count; s++) {
|
|
1195
|
-
const series = this.chart_data.series2[s];
|
|
1196
|
-
const color_index = typeof series.index === 'number' ? series.index : s + 1;
|
|
1197
|
-
|
|
1198
|
-
for (let i = 0; i < series.y.data.length; i++ ){
|
|
1199
|
-
const value = series.y.data[i];
|
|
1200
|
-
// const format = NumberFormatCache.Get(series.y.format || '0.00');
|
|
1201
|
-
|
|
1202
|
-
if (typeof value === 'number') {
|
|
1203
|
-
|
|
1204
|
-
// const x = Math.round(area.left + i * column_width + space) + s * width;
|
|
1205
|
-
const x = (area.left + i * column_width + space) + s * width;
|
|
1206
|
-
|
|
1207
|
-
let height = 0;
|
|
1208
|
-
let y = 0;
|
|
1209
|
-
let negative = false;
|
|
1210
|
-
|
|
1211
|
-
if (zero) {
|
|
1212
|
-
if (value > 0) {
|
|
1213
|
-
height = Util.ApplyScale(value + this.chart_data.scale.min, area.height, this.chart_data.scale);
|
|
1214
|
-
y = area.bottom - height - zero;
|
|
1215
|
-
}
|
|
1216
|
-
else {
|
|
1217
|
-
height = Util.ApplyScale(this.chart_data.scale.min - value, area.height, this.chart_data.scale);
|
|
1218
|
-
y = area.bottom - zero; // // area.bottom - height - zero;
|
|
1219
|
-
negative = true;
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
else {
|
|
1223
|
-
height = Util.ApplyScale(value, area.height, this.chart_data.scale);
|
|
1224
|
-
y = area.bottom - height;
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
// const bar_title = this.chart_data.titles ? this.chart_data.titles[i] : undefined;
|
|
1228
|
-
const bar_title = undefined;
|
|
1229
|
-
|
|
1230
|
-
if (height) {
|
|
1231
|
-
|
|
1232
|
-
const label = (this.chart_data.data_labels && !!series.y.labels) ? series.y.labels[i] : '';
|
|
1233
|
-
const label_point = {
|
|
1234
|
-
x: Math.round(x + width / 2),
|
|
1235
|
-
y: Math.round(y - 10),
|
|
1236
|
-
};
|
|
1237
|
-
|
|
1238
|
-
this.renderer.RenderRectangle(new Area(
|
|
1239
|
-
x, y, x + width, y + height,
|
|
1240
|
-
), corners, ['chart-column', `series-${color_index}`], bar_title || undefined, label, label_point);
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
}
|
|
1250
|
-
break;
|
|
1251
|
-
|
|
1252
|
-
case 'histogram':
|
|
1253
|
-
{
|
|
1254
|
-
// gridlines
|
|
1255
|
-
this.renderer.RenderGrid(area, this.chart_data.scale.count, 0, 'chart-grid');
|
|
1256
|
-
|
|
1257
|
-
// columns
|
|
1258
|
-
const column_width = area.width / this.chart_data.count;
|
|
1259
|
-
const column_pct = this.chart_data.column_width;
|
|
1260
|
-
|
|
1261
|
-
const space = column_width * (1 - column_pct) / 2;
|
|
1262
|
-
|
|
1263
|
-
for (let i = 0; i < this.chart_data.count; i++ ){
|
|
1264
|
-
const x = Math.round(area.left + i * column_width + space);
|
|
1265
|
-
const width = column_width - space * 2;
|
|
1266
|
-
const height = Util.ApplyScale(this.chart_data.bins[i], area.height, this.chart_data.scale);
|
|
1267
|
-
const y = area.bottom - height;
|
|
1268
|
-
const bar_title = this.chart_data.titles ? this.chart_data.titles[i] : undefined;
|
|
1269
|
-
|
|
1270
|
-
this.renderer.RenderRectangle(new Area(
|
|
1271
|
-
x, y, x + width, y + height,
|
|
1272
|
-
), undefined, 'chart-column series-1', bar_title || undefined);
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
}
|
|
1276
|
-
break;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
/** type guard */
|
|
1282
|
-
protected IsCellData(candidate: any): candidate is CellData {
|
|
1283
|
-
return (
|
|
1284
|
-
typeof candidate === 'object' &&
|
|
1285
|
-
typeof candidate.address === 'object' &&
|
|
1286
|
-
typeof candidate.address.row === 'number' &&
|
|
1287
|
-
typeof candidate.address.column === 'number');
|
|
1288
|
-
}
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
}
|
|
1
|
+
|
|
2
|
+
import type { ChartRenderer } from './renderer-type';
|
|
3
|
+
import type { ChartData } from './chart-types';
|
|
4
|
+
import type { ExtendedUnion, UnionValue } from 'treb-base-types';
|
|
5
|
+
import * as ChartUtils from './chart-utils';
|
|
6
|
+
import { DefaultChartRenderer } from './default-chart-renderer';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* transitioning to new structure, this should mirror the old chart
|
|
10
|
+
* interface (at least the public interface)
|
|
11
|
+
*/
|
|
12
|
+
export class Chart {
|
|
13
|
+
|
|
14
|
+
/** flag indicating we've registered at least once */
|
|
15
|
+
public static functions_registered = false;
|
|
16
|
+
|
|
17
|
+
// always exists; default null type, no title
|
|
18
|
+
|
|
19
|
+
protected chart_data: ChartData = {type: 'null'};
|
|
20
|
+
|
|
21
|
+
protected node?: HTMLElement;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
public renderer: ChartRenderer = new DefaultChartRenderer()) {
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public Initialize(node: HTMLElement) {
|
|
28
|
+
this.node = node;
|
|
29
|
+
this.renderer.Initialize(node);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public Exec(func: string, union: ExtendedUnion) {
|
|
33
|
+
|
|
34
|
+
const args: any[] = union?.value || [];
|
|
35
|
+
|
|
36
|
+
switch (func.toLowerCase()) {
|
|
37
|
+
|
|
38
|
+
case 'column.chart':
|
|
39
|
+
this.chart_data = ChartUtils.CreateColumnChart(args as [UnionValue?, UnionValue?, string?, string?], 'column');
|
|
40
|
+
break;
|
|
41
|
+
|
|
42
|
+
case 'bar.chart':
|
|
43
|
+
this.chart_data = ChartUtils.CreateColumnChart(args as [UnionValue?, UnionValue?, string?, string?], 'bar');
|
|
44
|
+
break;
|
|
45
|
+
|
|
46
|
+
case 'line.chart':
|
|
47
|
+
this.chart_data = ChartUtils.CreateLineChart(args, 'line');
|
|
48
|
+
break;
|
|
49
|
+
|
|
50
|
+
case 'area.chart':
|
|
51
|
+
this.chart_data = ChartUtils.CreateLineChart(args, 'area');
|
|
52
|
+
break;
|
|
53
|
+
|
|
54
|
+
case 'donut.chart':
|
|
55
|
+
case 'pie.chart':
|
|
56
|
+
this.chart_data = ChartUtils.CreateDonut(args as [UnionValue?, UnionValue?, string?, string?, string?], func.toLowerCase() === 'pie.chart');
|
|
57
|
+
break;
|
|
58
|
+
|
|
59
|
+
case 'scatter.plot':
|
|
60
|
+
this.chart_data = ChartUtils.CreateScatterChart(args, 'plot');
|
|
61
|
+
break;
|
|
62
|
+
|
|
63
|
+
case 'scatter.line':
|
|
64
|
+
this.chart_data = ChartUtils.CreateScatterChart(args, 'line');
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
default:
|
|
68
|
+
this.Clear();
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public Clear() {
|
|
75
|
+
this.chart_data = { type: 'null' };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** pass through */
|
|
79
|
+
public Resize() {
|
|
80
|
+
if (this.node) {
|
|
81
|
+
this.renderer.Resize(this.node, this.chart_data);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** pass through */
|
|
86
|
+
public Update() {
|
|
87
|
+
if (this.node) {
|
|
88
|
+
this.renderer.Update(this.node, this.chart_data);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
}
|