bare-script 2.3.2 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -4
- package/bin/bare.js +3 -16
- package/bin/baredoc.js +3 -16
- package/lib/bare.js +87 -84
- package/lib/baredoc.js +133 -59
- package/lib/data.js +155 -158
- package/lib/library.js +794 -291
- package/lib/model.js +3 -3
- package/lib/options.js +72 -0
- package/lib/optionsNode.js +70 -0
- package/lib/parser.js +72 -70
- package/lib/runtime.js +107 -88
- package/lib/runtimeAsync.js +112 -87
- package/lib/value.js +287 -0
- package/package.json +2 -2
package/lib/data.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
/** @module lib/data */
|
|
5
5
|
|
|
6
|
+
import {valueBoolean, valueCompare, valueJSON, valueParseDatetime, valueParseNumber} from './value.js';
|
|
6
7
|
import {evaluateExpression} from './runtime.js';
|
|
7
|
-
import {jsonStringifySortKeys} from 'schema-markdown/lib/encode.js';
|
|
8
8
|
import {parseExpression} from './parser.js';
|
|
9
9
|
import {parseSchemaMarkdown} from 'schema-markdown/lib/parser.js';
|
|
10
10
|
import {validateType} from 'schema-markdown/lib/schema.js';
|
|
@@ -31,7 +31,7 @@ export function parseCSV(text) {
|
|
|
31
31
|
const rows = lines.filter((line) => !line.match(rCSVBlankLine)).map((line) => {
|
|
32
32
|
const row = [];
|
|
33
33
|
let linePart = line;
|
|
34
|
-
while (linePart !==
|
|
34
|
+
while (linePart !== null) {
|
|
35
35
|
// Quoted field?
|
|
36
36
|
const mQuoted = linePart.match(rCSVQuotedField) ?? linePart.match(rCSVQuotedFieldEnd);
|
|
37
37
|
if (mQuoted !== null) {
|
|
@@ -43,7 +43,7 @@ export function parseCSV(text) {
|
|
|
43
43
|
// Non-quoted field
|
|
44
44
|
const ixComma = linePart.indexOf(',');
|
|
45
45
|
row.push(ixComma !== -1 ? linePart.slice(0, ixComma) : linePart);
|
|
46
|
-
linePart = (ixComma !== -1 ? linePart.slice(ixComma + 1) :
|
|
46
|
+
linePart = (ixComma !== -1 ? linePart.slice(ixComma + 1) : null);
|
|
47
47
|
}
|
|
48
48
|
return row;
|
|
49
49
|
});
|
|
@@ -51,11 +51,11 @@ export function parseCSV(text) {
|
|
|
51
51
|
// Assemble the data rows
|
|
52
52
|
const result = [];
|
|
53
53
|
if (rows.length >= 2) {
|
|
54
|
-
const
|
|
54
|
+
const fields = rows[0].map((field) => field.trim());
|
|
55
55
|
for (let ixLine = 1; ixLine < rows.length; ixLine += 1) {
|
|
56
56
|
const row = rows[ixLine];
|
|
57
57
|
result.push(Object.fromEntries(fields.map(
|
|
58
|
-
(field, ixField) => [field, ixField < row.length ? row[ixField] :
|
|
58
|
+
(field, ixField) => [field, ixField < row.length ? row[ixField] : null]
|
|
59
59
|
)));
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -83,15 +83,28 @@ export function validateData(data, csv = false) {
|
|
|
83
83
|
const types = {};
|
|
84
84
|
for (const row of data) {
|
|
85
85
|
for (const [field, value] of Object.entries(row)) {
|
|
86
|
-
if (
|
|
87
|
-
if (typeof value === '
|
|
86
|
+
if ((types[field] ?? null) === null) {
|
|
87
|
+
if (typeof value === 'boolean') {
|
|
88
|
+
types[field] = 'boolean';
|
|
89
|
+
} if (typeof value === 'number') {
|
|
88
90
|
types[field] = 'number';
|
|
89
91
|
} else if (value instanceof Date) {
|
|
90
92
|
types[field] = 'datetime';
|
|
91
|
-
} else if (typeof value === 'string'
|
|
92
|
-
|
|
93
|
+
} else if (typeof value === 'string') {
|
|
94
|
+
// If we aren't parsing CSV strings, its just a string
|
|
95
|
+
if (!csv) {
|
|
96
|
+
types[field] = 'string';
|
|
97
|
+
|
|
98
|
+
// If its the null string we can't determine the type yet
|
|
99
|
+
} else if (value === '' || value === 'null') {
|
|
100
|
+
types[field] = null;
|
|
101
|
+
|
|
102
|
+
// Can the string be parsed into another type?
|
|
103
|
+
} else if (valueParseDatetime(value) !== null) {
|
|
93
104
|
types[field] = 'datetime';
|
|
94
|
-
} else if (
|
|
105
|
+
} else if (value === 'true' || value === 'false') {
|
|
106
|
+
types[field] = 'boolean';
|
|
107
|
+
} else if (valueParseNumber(value) !== null) {
|
|
95
108
|
types[field] = 'number';
|
|
96
109
|
} else {
|
|
97
110
|
types[field] = 'string';
|
|
@@ -101,13 +114,25 @@ export function validateData(data, csv = false) {
|
|
|
101
114
|
}
|
|
102
115
|
}
|
|
103
116
|
|
|
104
|
-
//
|
|
117
|
+
// Set the type for fields with undetermined type
|
|
118
|
+
for (const [field, fieldType] of Object.entries(types)) {
|
|
119
|
+
if (fieldType === null) {
|
|
120
|
+
types[field] = 'string';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Helper to format and raise validation errors
|
|
105
125
|
const throwFieldError = (field, fieldType, fieldValue) => {
|
|
106
|
-
throw new Error(`Invalid "${field}" field value ${
|
|
126
|
+
throw new Error(`Invalid "${field}" field value ${valueJSON(fieldValue)}, expected type ${fieldType}`);
|
|
107
127
|
};
|
|
128
|
+
|
|
129
|
+
// Validate field values
|
|
108
130
|
for (const row of data) {
|
|
109
131
|
for (const [field, value] of Object.entries(row)) {
|
|
110
|
-
const fieldType = types[field];
|
|
132
|
+
const fieldType = types[field] ?? null;
|
|
133
|
+
if (fieldType === null) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
111
136
|
|
|
112
137
|
// Null string?
|
|
113
138
|
if (csv && value === 'null') {
|
|
@@ -116,9 +141,14 @@ export function validateData(data, csv = false) {
|
|
|
116
141
|
// Number field
|
|
117
142
|
} else if (fieldType === 'number') {
|
|
118
143
|
if (csv && typeof value === 'string') {
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
|
|
144
|
+
let numberValue;
|
|
145
|
+
if (value === '') {
|
|
146
|
+
numberValue = null;
|
|
147
|
+
} else {
|
|
148
|
+
numberValue = valueParseNumber(value);
|
|
149
|
+
if (numberValue === null) {
|
|
150
|
+
throwFieldError(field, fieldType, value);
|
|
151
|
+
}
|
|
122
152
|
}
|
|
123
153
|
row[field] = numberValue;
|
|
124
154
|
} else if (value !== null && typeof value !== 'number') {
|
|
@@ -127,16 +157,38 @@ export function validateData(data, csv = false) {
|
|
|
127
157
|
|
|
128
158
|
// Datetime field
|
|
129
159
|
} else if (fieldType === 'datetime') {
|
|
130
|
-
if (typeof value === 'string') {
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
|
|
160
|
+
if (csv && typeof value === 'string') {
|
|
161
|
+
let datetimeValue;
|
|
162
|
+
if (value === '') {
|
|
163
|
+
datetimeValue = null;
|
|
164
|
+
} else {
|
|
165
|
+
datetimeValue = valueParseDatetime(value);
|
|
166
|
+
if (datetimeValue === null) {
|
|
167
|
+
throwFieldError(field, fieldType, value);
|
|
168
|
+
}
|
|
134
169
|
}
|
|
135
170
|
row[field] = datetimeValue;
|
|
136
171
|
} else if (value !== null && !(value instanceof Date)) {
|
|
137
172
|
throwFieldError(field, fieldType, value);
|
|
138
173
|
}
|
|
139
174
|
|
|
175
|
+
// Boolean field
|
|
176
|
+
} else if (fieldType === 'boolean') {
|
|
177
|
+
if (csv && typeof value === 'string') {
|
|
178
|
+
let booleanValue;
|
|
179
|
+
if (value === '') {
|
|
180
|
+
booleanValue = null;
|
|
181
|
+
} else {
|
|
182
|
+
booleanValue = (value === 'true' ? true : (value === 'false' ? false : null));
|
|
183
|
+
if (booleanValue === null) {
|
|
184
|
+
throwFieldError(field, fieldType, value);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
row[field] = booleanValue;
|
|
188
|
+
} else if (value !== null && typeof value !== 'boolean') {
|
|
189
|
+
throwFieldError(field, fieldType, value);
|
|
190
|
+
}
|
|
191
|
+
|
|
140
192
|
// String field
|
|
141
193
|
} else {
|
|
142
194
|
if (value !== null && typeof value !== 'string') {
|
|
@@ -150,47 +202,23 @@ export function validateData(data, csv = false) {
|
|
|
150
202
|
}
|
|
151
203
|
|
|
152
204
|
|
|
153
|
-
function parseNumber(text) {
|
|
154
|
-
const value = Number.parseFloat(text);
|
|
155
|
-
if (Number.isNaN(value) || !Number.isFinite(value)) {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
return value;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
export function parseDatetime(text) {
|
|
163
|
-
const mDate = text.match(rDate);
|
|
164
|
-
if (mDate !== null) {
|
|
165
|
-
const year = Number.parseInt(mDate.groups.year, 10);
|
|
166
|
-
const month = Number.parseInt(mDate.groups.month, 10);
|
|
167
|
-
const day = Number.parseInt(mDate.groups.day, 10);
|
|
168
|
-
return new Date(year, month - 1, day);
|
|
169
|
-
} else if (rDatetime.test(text)) {
|
|
170
|
-
return new Date(text);
|
|
171
|
-
}
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const rDate = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})$/;
|
|
176
|
-
const rDatetime = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
177
|
-
|
|
178
|
-
|
|
179
205
|
/**
|
|
180
206
|
* Join two data arrays
|
|
181
207
|
*
|
|
182
208
|
* @param {Object} leftData - The left data array
|
|
183
209
|
* @param {Object} rightData - The left data array
|
|
184
|
-
* @param {string} joinExpr - The join [expression]
|
|
185
|
-
* @param {?string} [rightExpr = null] - The right join [expression]
|
|
210
|
+
* @param {string} joinExpr - The join [expression](./language/#expressions)
|
|
211
|
+
* @param {?string} [rightExpr = null] - The right join [expression](./language/#expressions)
|
|
186
212
|
* @param {boolean} [isLeftJoin = false] - If true, perform a left join (always include left row)
|
|
187
213
|
* @param {?Object} [variables = null] - Additional variables for expression evaluation
|
|
188
|
-
* @param {?Object} [options = null] - The [script execution options]{@link module:lib/
|
|
214
|
+
* @param {?Object} [options = null] - The [script execution options]{@link module:lib/options~ExecuteScriptOptions}
|
|
189
215
|
* @returns {Object[]} The joined data array
|
|
190
216
|
*/
|
|
191
217
|
export function joinData(leftData, rightData, joinExpr, rightExpr = null, isLeftJoin = false, variables = null, options = null) {
|
|
192
218
|
// Compute the map of row field name to joined row field name
|
|
193
219
|
const leftNames = {};
|
|
220
|
+
const rightNamesRaw = {};
|
|
221
|
+
const rightNames = {};
|
|
194
222
|
for (const row of leftData) {
|
|
195
223
|
for (const fieldName of Object.keys(row)) {
|
|
196
224
|
if (!(fieldName in leftNames)) {
|
|
@@ -198,24 +226,26 @@ export function joinData(leftData, rightData, joinExpr, rightExpr = null, isLeft
|
|
|
198
226
|
}
|
|
199
227
|
}
|
|
200
228
|
}
|
|
201
|
-
const rightNames = {};
|
|
202
229
|
for (const row of rightData) {
|
|
203
230
|
for (const fieldName of Object.keys(row)) {
|
|
204
231
|
if (!(fieldName in rightNames)) {
|
|
205
|
-
|
|
206
|
-
rightNames[fieldName] = fieldName;
|
|
207
|
-
} else {
|
|
208
|
-
let uniqueName = fieldName;
|
|
209
|
-
let ixUnique = 2;
|
|
210
|
-
do {
|
|
211
|
-
uniqueName = `${fieldName}${ixUnique}`;
|
|
212
|
-
ixUnique += 1;
|
|
213
|
-
} while (uniqueName in leftNames || uniqueName in rightNames);
|
|
214
|
-
rightNames[fieldName] = uniqueName;
|
|
215
|
-
}
|
|
232
|
+
rightNamesRaw[fieldName] = fieldName;
|
|
216
233
|
}
|
|
217
234
|
}
|
|
218
235
|
}
|
|
236
|
+
for (const fieldName of Object.keys(rightNamesRaw)) {
|
|
237
|
+
if (!(fieldName in leftNames)) {
|
|
238
|
+
rightNames[fieldName] = fieldName;
|
|
239
|
+
} else {
|
|
240
|
+
let ixUnique = 2;
|
|
241
|
+
let uniqueName = `${fieldName}${ixUnique}`;
|
|
242
|
+
while (uniqueName in leftNames || uniqueName in rightNames || uniqueName in rightNamesRaw) {
|
|
243
|
+
ixUnique += 1;
|
|
244
|
+
uniqueName = `${fieldName}${ixUnique}`;
|
|
245
|
+
}
|
|
246
|
+
rightNames[fieldName] = uniqueName;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
219
249
|
|
|
220
250
|
// Create the evaluation options object
|
|
221
251
|
let evalOptions = options;
|
|
@@ -235,7 +265,7 @@ export function joinData(leftData, rightData, joinExpr, rightExpr = null, isLeft
|
|
|
235
265
|
// Bucket the right rows by the right expression value
|
|
236
266
|
const rightCategoryRows = {};
|
|
237
267
|
for (const rightRow of rightData) {
|
|
238
|
-
const categoryKey =
|
|
268
|
+
const categoryKey = valueJSON(evaluateExpression(rightExpression, evalOptions, rightRow));
|
|
239
269
|
if (!(categoryKey in rightCategoryRows)) {
|
|
240
270
|
rightCategoryRows[categoryKey] = [];
|
|
241
271
|
}
|
|
@@ -245,7 +275,7 @@ export function joinData(leftData, rightData, joinExpr, rightExpr = null, isLeft
|
|
|
245
275
|
// Join the left with the right
|
|
246
276
|
const data = [];
|
|
247
277
|
for (const leftRow of leftData) {
|
|
248
|
-
const categoryKey =
|
|
278
|
+
const categoryKey = valueJSON(evaluateExpression(leftExpression, evalOptions, leftRow));
|
|
249
279
|
if (categoryKey in rightCategoryRows) {
|
|
250
280
|
for (const rightRow of rightCategoryRows[categoryKey]) {
|
|
251
281
|
const joinRow = {...leftRow};
|
|
@@ -270,7 +300,7 @@ export function joinData(leftData, rightData, joinExpr, rightExpr = null, isLeft
|
|
|
270
300
|
* @param {string} fieldName - The calculated field name
|
|
271
301
|
* @param {string} expr - The calculated field expression
|
|
272
302
|
* @param {?Object} [variables = null] - Additional variables for expression evaluation
|
|
273
|
-
* @param {?Object} [options = null] - The [script execution options]{@link module:lib/
|
|
303
|
+
* @param {?Object} [options = null] - The [script execution options]{@link module:lib/options~ExecuteScriptOptions}
|
|
274
304
|
* @returns {Object[]} The updated data array
|
|
275
305
|
*/
|
|
276
306
|
export function addCalculatedField(data, fieldName, expr, variables = null, options = null) {
|
|
@@ -292,6 +322,7 @@ export function addCalculatedField(data, fieldName, expr, variables = null, opti
|
|
|
292
322
|
for (const row of data) {
|
|
293
323
|
row[fieldName] = evaluateExpression(calcExpr, evalOptions, row);
|
|
294
324
|
}
|
|
325
|
+
|
|
295
326
|
return data;
|
|
296
327
|
}
|
|
297
328
|
|
|
@@ -300,9 +331,9 @@ export function addCalculatedField(data, fieldName, expr, variables = null, opti
|
|
|
300
331
|
* Filter data rows
|
|
301
332
|
*
|
|
302
333
|
* @param {Object[]} data - The data array
|
|
303
|
-
* @param {string} expr - The boolean filter [expression]
|
|
334
|
+
* @param {string} expr - The boolean filter [expression](./language/#expressions)
|
|
304
335
|
* @param {?Object} [variables = null] - Additional variables for expression evaluation
|
|
305
|
-
* @param {?Object} [options = null] - The [script execution options]{@link module:lib/
|
|
336
|
+
* @param {?Object} [options = null] - The [script execution options]{@link module:lib/options~ExecuteScriptOptions}
|
|
306
337
|
* @returns {Object[]} The filtered data array
|
|
307
338
|
*/
|
|
308
339
|
export function filterData(data, expr, variables = null, options = null) {
|
|
@@ -324,7 +355,7 @@ export function filterData(data, expr, variables = null, options = null) {
|
|
|
324
355
|
|
|
325
356
|
// Filter the data
|
|
326
357
|
for (const row of data) {
|
|
327
|
-
if (evaluateExpression(filterExpr, evalOptions, row)) {
|
|
358
|
+
if (valueBoolean(evaluateExpression(filterExpr, evalOptions, row))) {
|
|
328
359
|
result.push(row);
|
|
329
360
|
}
|
|
330
361
|
}
|
|
@@ -333,80 +364,16 @@ export function filterData(data, expr, variables = null, options = null) {
|
|
|
333
364
|
}
|
|
334
365
|
|
|
335
366
|
|
|
336
|
-
// The aggregation model
|
|
337
|
-
export const aggregationTypes = parseSchemaMarkdown(`\
|
|
338
|
-
group "Aggregation"
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
# A data aggregation specification
|
|
342
|
-
struct Aggregation
|
|
343
|
-
|
|
344
|
-
# The aggregation category fields
|
|
345
|
-
optional string[len > 0] categories
|
|
346
|
-
|
|
347
|
-
# The aggregation measures
|
|
348
|
-
AggregationMeasure[len > 0] measures
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
# An aggregation measure specification
|
|
352
|
-
struct AggregationMeasure
|
|
353
|
-
|
|
354
|
-
# The aggregation measure field
|
|
355
|
-
string field
|
|
356
|
-
|
|
357
|
-
# The aggregation function
|
|
358
|
-
AggregationFunction function
|
|
359
|
-
|
|
360
|
-
# The aggregated-measure field name
|
|
361
|
-
optional string name
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
# An aggregation function
|
|
365
|
-
enum AggregationFunction
|
|
366
|
-
|
|
367
|
-
# The average of the measure's values
|
|
368
|
-
average
|
|
369
|
-
|
|
370
|
-
# The count of the measure's values
|
|
371
|
-
count
|
|
372
|
-
|
|
373
|
-
# The greatest of the measure's values
|
|
374
|
-
max
|
|
375
|
-
|
|
376
|
-
# The least of the measure's values
|
|
377
|
-
min
|
|
378
|
-
|
|
379
|
-
# The standard deviation of the measure's values
|
|
380
|
-
stddev
|
|
381
|
-
|
|
382
|
-
# The sum of the measure's values
|
|
383
|
-
sum
|
|
384
|
-
`);
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Validate an aggregation model
|
|
389
|
-
*
|
|
390
|
-
* @param {Object} aggregation - The
|
|
391
|
-
* [aggregation model]{@link https://craigahobbs.github.io/bare-script/library/model.html#var.vName='Aggregation'}
|
|
392
|
-
* @returns {Object} The validated
|
|
393
|
-
* [aggregation model]{@link https://craigahobbs.github.io/bare-script/library/model.html#var.vName='Aggregation'}
|
|
394
|
-
* @throws [ValidationError]{@link https://craigahobbs.github.io/schema-markdown-js/module-lib_schema.ValidationError.html}
|
|
395
|
-
*/
|
|
396
|
-
export function validateAggregation(aggregation) {
|
|
397
|
-
return validateType(aggregationTypes, 'Aggregation', aggregation);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
|
|
401
367
|
/**
|
|
402
368
|
* Aggregate data rows
|
|
403
369
|
*
|
|
404
370
|
* @param {Object[]} data - The data array
|
|
405
|
-
* @param {Object} aggregation - The
|
|
406
|
-
* [aggregation model]{@link https://craigahobbs.github.io/bare-script/library/model.html#var.vName='Aggregation'}
|
|
371
|
+
* @param {Object} aggregation - The [aggregation model](./library/model.html#var.vName='Aggregation')
|
|
407
372
|
* @returns {Object[]} The aggregated data array
|
|
408
373
|
*/
|
|
409
374
|
export function aggregateData(data, aggregation) {
|
|
375
|
+
// Validate the aggregation model
|
|
376
|
+
validateType(aggregationTypes, 'Aggregation', aggregation);
|
|
410
377
|
const categories = aggregation.categories ?? null;
|
|
411
378
|
|
|
412
379
|
// Create the aggregate rows
|
|
@@ -417,7 +384,7 @@ export function aggregateData(data, aggregation) {
|
|
|
417
384
|
|
|
418
385
|
// Get or create the aggregate row
|
|
419
386
|
let aggregateRow;
|
|
420
|
-
const rowKey = (categoryValues !== null ?
|
|
387
|
+
const rowKey = (categoryValues !== null ? valueJSON(categoryValues) : '');
|
|
421
388
|
if (rowKey in categoryRows) {
|
|
422
389
|
aggregateRow = categoryRows[rowKey];
|
|
423
390
|
} else {
|
|
@@ -474,6 +441,57 @@ export function aggregateData(data, aggregation) {
|
|
|
474
441
|
}
|
|
475
442
|
|
|
476
443
|
|
|
444
|
+
// The aggregation model
|
|
445
|
+
export const aggregationTypes = parseSchemaMarkdown(`\
|
|
446
|
+
group "Aggregation"
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
# A data aggregation specification
|
|
450
|
+
struct Aggregation
|
|
451
|
+
|
|
452
|
+
# The aggregation category fields
|
|
453
|
+
optional string[len > 0] categories
|
|
454
|
+
|
|
455
|
+
# The aggregation measures
|
|
456
|
+
AggregationMeasure[len > 0] measures
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
# An aggregation measure specification
|
|
460
|
+
struct AggregationMeasure
|
|
461
|
+
|
|
462
|
+
# The aggregation measure field
|
|
463
|
+
string field
|
|
464
|
+
|
|
465
|
+
# The aggregation function
|
|
466
|
+
AggregationFunction function
|
|
467
|
+
|
|
468
|
+
# The aggregated-measure field name
|
|
469
|
+
optional string name
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
# An aggregation function
|
|
473
|
+
enum AggregationFunction
|
|
474
|
+
|
|
475
|
+
# The average of the measure's values
|
|
476
|
+
average
|
|
477
|
+
|
|
478
|
+
# The count of the measure's values
|
|
479
|
+
count
|
|
480
|
+
|
|
481
|
+
# The greatest of the measure's values
|
|
482
|
+
max
|
|
483
|
+
|
|
484
|
+
# The least of the measure's values
|
|
485
|
+
min
|
|
486
|
+
|
|
487
|
+
# The standard deviation of the measure's values
|
|
488
|
+
stddev
|
|
489
|
+
|
|
490
|
+
# The sum of the measure's values
|
|
491
|
+
sum
|
|
492
|
+
`);
|
|
493
|
+
|
|
494
|
+
|
|
477
495
|
/**
|
|
478
496
|
* Sort data rows
|
|
479
497
|
*
|
|
@@ -489,7 +507,7 @@ export function sortData(data, sorts) {
|
|
|
489
507
|
const [field, desc = false] = sort;
|
|
490
508
|
const value1 = row1[field] ?? null;
|
|
491
509
|
const value2 = row2[field] ?? null;
|
|
492
|
-
const compare =
|
|
510
|
+
const compare = valueCompare(value1, value2);
|
|
493
511
|
return desc ? -compare : compare;
|
|
494
512
|
}, 0));
|
|
495
513
|
}
|
|
@@ -509,7 +527,7 @@ export function topData(data, count, categoryFields = null) {
|
|
|
509
527
|
const categoryOrder = [];
|
|
510
528
|
for (const row of data) {
|
|
511
529
|
const categoryKey = categoryFields === null ? ''
|
|
512
|
-
:
|
|
530
|
+
: valueJSON(categoryFields.map((field) => (field in row ? row[field] : null)));
|
|
513
531
|
if (!(categoryKey in categoryRows)) {
|
|
514
532
|
categoryRows[categoryKey] = [];
|
|
515
533
|
categoryOrder.push(categoryKey);
|
|
@@ -528,24 +546,3 @@ export function topData(data, count, categoryFields = null) {
|
|
|
528
546
|
}
|
|
529
547
|
return dataTop;
|
|
530
548
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
/**
|
|
534
|
-
* Compare two data values
|
|
535
|
-
*
|
|
536
|
-
* @param {*} value1 - The first value
|
|
537
|
-
* @param {*} value2 - The second value
|
|
538
|
-
* @returns {number} -1 if the first value is less, 1 if the first value is greater, and 0 if they are equal
|
|
539
|
-
*/
|
|
540
|
-
export function compareValues(value1, value2) {
|
|
541
|
-
if (value1 === null) {
|
|
542
|
-
return value2 === null ? 0 : -1;
|
|
543
|
-
} else if (value2 === null) {
|
|
544
|
-
return 1;
|
|
545
|
-
} else if (value1 instanceof Date) {
|
|
546
|
-
const time1 = value1.getTime();
|
|
547
|
-
const time2 = value2.getTime();
|
|
548
|
-
return time1 < time2 ? -1 : (time1 === time2 ? 0 : 1);
|
|
549
|
-
}
|
|
550
|
-
return value1 < value2 ? -1 : (value1 === value2 ? 0 : 1);
|
|
551
|
-
}
|