jupyter-ijavascript-utils 1.18.0 → 1.19.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/DOCS.md +3 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/src/TableGenerator.js +1 -1
- package/src/describe.js +687 -0
- package/src/format.js +77 -0
- package/src/hashMap.js +218 -0
- package/src/index.js +21 -15
package/DOCS.md
CHANGED
|
@@ -46,6 +46,7 @@ This is not intended to be the only way to accomplish many of these tasks, and a
|
|
|
46
46
|
|
|
47
47
|
## What's New
|
|
48
48
|
|
|
49
|
+
* 1.19 - add in {@link module:describe|describe} and {@link module:hashMap|hashMap} modules, along with {@link module:format.limitLines|format.limitLines}
|
|
49
50
|
* 1.18 - tie to vega-datasets avoiding esmodules until ijavascript can support them
|
|
50
51
|
* 1.17 - provide object.propertyValueSample - as a way to list 'non-empty' property values
|
|
51
52
|
* 1.16 - provide file.matchFiles - as a way to find files or directories
|
|
@@ -73,9 +74,11 @@ This is not intended to be the only way to accomplish many of these tasks, and a
|
|
|
73
74
|
| {@link module:array} | Massage, sort, reshape arrays. |
|
|
74
75
|
| {@link module:base64} | Convert to and from base64 encoding of strings |
|
|
75
76
|
| {@link module:datasets} | Load example <a href="https://github.com/vega/vega-datasets">datasets provided by the vega team</a> |
|
|
77
|
+
| {@link module:describe} | Similar to Pandas describe, provides statistics on a set of values / objects |
|
|
76
78
|
| {@link module:file} | Read and write data/text to files. |
|
|
77
79
|
| {@link module:format} | Formatting and massage data to be legible. |
|
|
78
80
|
| {@link module:group} | Group/Reduce Hierarchies of Object - generating Maps of records ({@link SourceMap}) |
|
|
81
|
+
| {@link module:hashMap} | Modify JavaScript HashMaps (ex new Map()) |
|
|
79
82
|
| {@link module:ijs} | Extend iJavaScript to support await, and new types of rendering - like {@tutorial htmlScript} and markdown|
|
|
80
83
|
| {@link module:latex} | Render Math Notation with <a href="www.latex-project.org">LaTeX<a> and <a href="katex.org">KaTeX</a>|
|
|
81
84
|
| {@link module:leaflet} | Render maps with <a href="leaflet.org">Leaflet</a> |
|
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ This is not intended to be the only way to accomplish many of these tasks, and a
|
|
|
46
46
|
|
|
47
47
|
# What's New
|
|
48
48
|
|
|
49
|
+
* 1.19 - add in describe and hashMap modules, along with format.limitLines
|
|
49
50
|
* 1.18 - tie to vega-datasets avoiding esmodules until ijavascript can support them
|
|
50
51
|
* 1.17 - provide object.propertyValueSample - as a way to list 'non-empty' property values
|
|
51
52
|
* 1.16 - provide file.matchFiles - as a way to find files or directories
|
package/package.json
CHANGED
package/src/TableGenerator.js
CHANGED
|
@@ -1008,7 +1008,7 @@ class TableGenerator {
|
|
|
1008
1008
|
keys = keys.filter((key) => this.#columnsToExclude.indexOf(key) === -1);
|
|
1009
1009
|
|
|
1010
1010
|
//-- identify the formatter to use
|
|
1011
|
-
const cleanFormatter = this.#formatterFn ? this.#formatterFn : ({ value }) => value;
|
|
1011
|
+
const cleanFormatter = this.#formatterFn ? this.#formatterFn : ({ value }) => value === undefined ? '' : value;
|
|
1012
1012
|
|
|
1013
1013
|
const translateHeader = (key) => {
|
|
1014
1014
|
if (Object.prototype.hasOwnProperty.call(this.#labels, key)) {
|
package/src/describe.js
ADDED
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
/* eslint-disable max-classes-per-file, class-methods-use-this */
|
|
2
|
+
|
|
3
|
+
const FormatUtils = require('./format');
|
|
4
|
+
const ObjectUtils = require('./object');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Module to describe objects or sets of data
|
|
8
|
+
*
|
|
9
|
+
* Describe an array of objects
|
|
10
|
+
* * {@link module:describe.describeObjects|describeObjects(collection, options)} - given a list of objects, describes each of the fields
|
|
11
|
+
* Describe an array of values (assuming all are the same type)
|
|
12
|
+
* * {@link module:describe.describeBoolean|describeBoolean(collection, options)} - describes a series of booleans
|
|
13
|
+
* * {@link module:describe.describeStrings|describeStrings(collection, options)} - describes a series of strings
|
|
14
|
+
* * {@link module:describe.describeNumbers|describeNumbers(collection, options)} - describes a series of numbers
|
|
15
|
+
* * {@link module:describe.describeDates|describeDates(collection, options)} - describes a series of dates
|
|
16
|
+
*
|
|
17
|
+
* @module describe
|
|
18
|
+
* @exports describe
|
|
19
|
+
*/
|
|
20
|
+
module.exports = {};
|
|
21
|
+
const DescribeUtil = module.exports;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} DescribeOptions
|
|
25
|
+
* @property {Boolean} uniqueStrings - whether unique strings / frequency should be captured
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Base Description for a series of values
|
|
30
|
+
* @class
|
|
31
|
+
*/
|
|
32
|
+
class SeriesDescription {
|
|
33
|
+
/**
|
|
34
|
+
* Constructor
|
|
35
|
+
* @param {String} what - description of what is being described
|
|
36
|
+
* @param {DescribeOptions} options - options for how things are described
|
|
37
|
+
*/
|
|
38
|
+
constructor(what, type, options) {
|
|
39
|
+
this.reset();
|
|
40
|
+
this.what = what;
|
|
41
|
+
this.type = type;
|
|
42
|
+
// this.options = options || {};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Options used for describing
|
|
47
|
+
* @type {DescribeOptions}
|
|
48
|
+
*/
|
|
49
|
+
// options;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* What is being described
|
|
53
|
+
* @type {String}
|
|
54
|
+
*/
|
|
55
|
+
what;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The type of thing being described
|
|
59
|
+
* @type {String}
|
|
60
|
+
*/
|
|
61
|
+
type;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The number of entries reviewed
|
|
65
|
+
* @type {Number}
|
|
66
|
+
*/
|
|
67
|
+
count;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The minimum value found;
|
|
71
|
+
* @type {any}
|
|
72
|
+
*/
|
|
73
|
+
min;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The maximum value found
|
|
77
|
+
* @type {any}
|
|
78
|
+
*/
|
|
79
|
+
max;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Resets the Description to the initial state
|
|
83
|
+
*/
|
|
84
|
+
reset() {
|
|
85
|
+
this.count = 0;
|
|
86
|
+
this.max = null;
|
|
87
|
+
this.min = null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Validates a value is the type expected
|
|
92
|
+
* or throws an error if the type is not
|
|
93
|
+
* or throws false if the value is 'empty'
|
|
94
|
+
* @param {any} value - value to be checked
|
|
95
|
+
* @param {String} expectedTypeOf - the type of the value
|
|
96
|
+
* @returns {Boolean} - true if found and the right type, false if empty
|
|
97
|
+
* @throws {Error} if the value is the wrong type
|
|
98
|
+
*/
|
|
99
|
+
check(value, expectedType) {
|
|
100
|
+
if (FormatUtils.isEmptyValue(value)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const valueType = typeof value;
|
|
105
|
+
if (expectedType && valueType !== expectedType) {
|
|
106
|
+
throw Error(`describe: Value passed(${value}) expected to be:${expectedType}, but was: ${valueType}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.count += 1;
|
|
110
|
+
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Checks for minimum and maximum values
|
|
116
|
+
* @param {any} value
|
|
117
|
+
*/
|
|
118
|
+
checkMinMax(value) {
|
|
119
|
+
if (this.min === null || value < this.min) {
|
|
120
|
+
this.min = value;
|
|
121
|
+
}
|
|
122
|
+
if (this.max === null || value > this.max) {
|
|
123
|
+
this.max = value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Finalizes the review
|
|
129
|
+
*/
|
|
130
|
+
finalize() { // eslint-disable-line
|
|
131
|
+
const result = { ...this };
|
|
132
|
+
// delete result.options;
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Describes a series of Boolean Values
|
|
139
|
+
* @augments SeriesDescription
|
|
140
|
+
* @class
|
|
141
|
+
*/
|
|
142
|
+
class BooleanDescription extends SeriesDescription {
|
|
143
|
+
/**
|
|
144
|
+
* Mean sum as expressed
|
|
145
|
+
* @type {number}
|
|
146
|
+
*/
|
|
147
|
+
mean;
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
*
|
|
151
|
+
* @param {String} what - what is being described
|
|
152
|
+
* @param {DescribeOptions} options - options used for describing
|
|
153
|
+
*/
|
|
154
|
+
constructor(what, options) {
|
|
155
|
+
super(what, 'boolean', options);
|
|
156
|
+
this.reset();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
reset() {
|
|
160
|
+
super.reset();
|
|
161
|
+
this.mean = 0.0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Whether the value can be described with this
|
|
166
|
+
* @param {any} value - value to check
|
|
167
|
+
* @returns {Boolean} - true if the value matches
|
|
168
|
+
*/
|
|
169
|
+
static matchesType(value) {
|
|
170
|
+
return FormatUtils.parseBoolean(value);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
check(value) {
|
|
174
|
+
if (FormatUtils.isEmptyValue(value)) return;
|
|
175
|
+
|
|
176
|
+
this.count += 1;
|
|
177
|
+
const cleanValue = FormatUtils.parseBoolean(value)
|
|
178
|
+
? 1 : 0;
|
|
179
|
+
|
|
180
|
+
const oldMean = this.mean;
|
|
181
|
+
this.mean += (cleanValue - oldMean) / this.count;
|
|
182
|
+
|
|
183
|
+
if (this.max === null && cleanValue === 1) this.max = 1;
|
|
184
|
+
if (this.min === null && cleanValue === 0) this.min = 0;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
finalize() {
|
|
188
|
+
const result = super.finalize();
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Describes a series of Numbers
|
|
195
|
+
*/
|
|
196
|
+
class NumberDescription extends SeriesDescription {
|
|
197
|
+
/**
|
|
198
|
+
* Mean sum as expressed
|
|
199
|
+
* @type {number}
|
|
200
|
+
*/
|
|
201
|
+
mean;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* M2 - sum of squared deviation
|
|
205
|
+
*/
|
|
206
|
+
m2;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Standard deviation of the numbers
|
|
210
|
+
*/
|
|
211
|
+
stdDeviation;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Constructor
|
|
215
|
+
* @param {String} what - What is being described
|
|
216
|
+
* @param {DescribeOptions} options -
|
|
217
|
+
*/
|
|
218
|
+
constructor(what, options) {
|
|
219
|
+
super(what, 'number', options);
|
|
220
|
+
this.reset();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
reset() {
|
|
224
|
+
super.reset();
|
|
225
|
+
this.mean = 0.0;
|
|
226
|
+
this.m2 = 0.0;
|
|
227
|
+
this.stdDeviation = 0.0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Whether the value can be described with this
|
|
232
|
+
* @param {any} value - value to check
|
|
233
|
+
* @returns {Boolean} - true if the value matches
|
|
234
|
+
*/
|
|
235
|
+
static matchesType(value) {
|
|
236
|
+
return (typeof value) === 'number';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
check(value) {
|
|
240
|
+
if (!super.check(value, 'number')) return;
|
|
241
|
+
super.checkMinMax(value);
|
|
242
|
+
|
|
243
|
+
/*
|
|
244
|
+
@see Welford's algorithm
|
|
245
|
+
@see https://stackoverflow.com/a/1348615
|
|
246
|
+
@see https://lingpipe-blog.com/2009/03/19/computing-sample-mean-variance-online-one-pass/
|
|
247
|
+
@see https://lingpipe-blog.com/2009/07/07/welford-s-algorithm-delete-online-mean-variance-deviation/
|
|
248
|
+
@see https://www.calculator.net/standard-deviation-calculator.html
|
|
249
|
+
*/
|
|
250
|
+
const oldMean = this.mean;
|
|
251
|
+
this.mean += (value - oldMean) / this.count;
|
|
252
|
+
this.m2 += (value - oldMean) * (value - this.mean);
|
|
253
|
+
// console.log(`value:${value}, this.mean:${this.mean}, oldMean:${oldMean}, stdDeviation:${this.stdDeviation}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
finalize() {
|
|
257
|
+
let newDeviation;
|
|
258
|
+
if (this.count > 1) {
|
|
259
|
+
newDeviation = Math.sqrt(this.m2 / this.count);
|
|
260
|
+
} else {
|
|
261
|
+
newDeviation = 0.0;
|
|
262
|
+
}
|
|
263
|
+
// console.log(`updated m2:${this.m2}, stdDeviation:${this.stdDeviation}, count:${this.count}, newDeviation:${newDeviation}`);
|
|
264
|
+
this.stdDeviation = newDeviation;
|
|
265
|
+
|
|
266
|
+
const result = super.finalize();
|
|
267
|
+
delete result.m2;
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Describes a series of string values
|
|
274
|
+
*/
|
|
275
|
+
class StringDescription extends SeriesDescription {
|
|
276
|
+
/**
|
|
277
|
+
* Map of unique values
|
|
278
|
+
* @type {Map<String,Number>}
|
|
279
|
+
*/
|
|
280
|
+
uniqueMap;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Number of unique values;
|
|
284
|
+
* @type {Number}
|
|
285
|
+
*/
|
|
286
|
+
unique;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* The most common string
|
|
290
|
+
* @type {String}
|
|
291
|
+
*/
|
|
292
|
+
top;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* The frequency of the most common string
|
|
296
|
+
* @type {Number}
|
|
297
|
+
*/
|
|
298
|
+
topFrequency;
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Constructor
|
|
302
|
+
* @param {String} what - What is being described
|
|
303
|
+
* @param {DescribeOptions} options -
|
|
304
|
+
*/
|
|
305
|
+
constructor(what, options) {
|
|
306
|
+
super(what, 'string', options);
|
|
307
|
+
this.uniqueMap = null;
|
|
308
|
+
this.reset();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
reset() {
|
|
312
|
+
super.reset();
|
|
313
|
+
this.uniqueMap = new Map();
|
|
314
|
+
this.unique = null;
|
|
315
|
+
this.top = null;
|
|
316
|
+
this.topFrequency = null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Whether the value can be described with this
|
|
321
|
+
* @param {any} value - value to check
|
|
322
|
+
* @returns {Boolean} - true if the value matches
|
|
323
|
+
*/
|
|
324
|
+
static matchesType(value) {
|
|
325
|
+
return (typeof value) === 'string';
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
check(value) {
|
|
329
|
+
if (!super.check(value, 'string')) return;
|
|
330
|
+
|
|
331
|
+
if (this.uniqueMap.has(value)) {
|
|
332
|
+
this.uniqueMap.set(value, this.uniqueMap.get(value) + 1);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
this.uniqueMap.set(value, 1);
|
|
337
|
+
|
|
338
|
+
const len = value.length;
|
|
339
|
+
if (this.min === null || len < this.min.length) this.min = value;
|
|
340
|
+
if (this.max === null || len > this.max.length) this.max = value;
|
|
341
|
+
// console.log(`len:${len}, min:${this.min}, max:${this.max}`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
finalize() {
|
|
345
|
+
super.finalize();
|
|
346
|
+
|
|
347
|
+
let currentTop = null;
|
|
348
|
+
let currentTopFrequency = null;
|
|
349
|
+
for (const [key, count] of this.uniqueMap.entries()) {
|
|
350
|
+
if (currentTopFrequency == null || count > currentTopFrequency) {
|
|
351
|
+
currentTop = key;
|
|
352
|
+
currentTopFrequency = count;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
this.top = currentTop;
|
|
356
|
+
this.topFrequency = currentTopFrequency;
|
|
357
|
+
this.unique = this.uniqueMap.size;
|
|
358
|
+
|
|
359
|
+
this.uniqueMap = null;
|
|
360
|
+
|
|
361
|
+
const result = super.finalize();
|
|
362
|
+
delete result.uniqueMap;
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Describes a series of Dates
|
|
369
|
+
*/
|
|
370
|
+
class DateDescription extends SeriesDescription {
|
|
371
|
+
/**
|
|
372
|
+
* Mean sum as expressed
|
|
373
|
+
* @type {number}
|
|
374
|
+
*/
|
|
375
|
+
mean;
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Constructor
|
|
379
|
+
* @param {String} what - What is being described
|
|
380
|
+
* @param {DescribeOptions} options -
|
|
381
|
+
*/
|
|
382
|
+
constructor(what, options) {
|
|
383
|
+
super(what, 'Date', options);
|
|
384
|
+
this.reset();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
reset() {
|
|
388
|
+
super.reset();
|
|
389
|
+
this.mean = null;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Whether the value can be described with this
|
|
394
|
+
* @param {any} value - value to check
|
|
395
|
+
* @returns {Boolean} - true if the value matches
|
|
396
|
+
*/
|
|
397
|
+
static matchesType(value) {
|
|
398
|
+
return (value instanceof Date); // || (typeof value) === 'number';
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
check(value) {
|
|
402
|
+
if (FormatUtils.isEmptyValue(value)) return;
|
|
403
|
+
|
|
404
|
+
let cleanValue;
|
|
405
|
+
if (value instanceof Date) {
|
|
406
|
+
cleanValue = value.getTime();
|
|
407
|
+
} else if (typeof value === 'number') {
|
|
408
|
+
cleanValue = value;
|
|
409
|
+
} else {
|
|
410
|
+
throw Error(`describe: Value passed(${value}) - expected to be type:Date`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
this.count += 1;
|
|
414
|
+
|
|
415
|
+
const oldMean = this.mean;
|
|
416
|
+
this.mean += (cleanValue - oldMean) / this.count;
|
|
417
|
+
|
|
418
|
+
super.checkMinMax(cleanValue);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
finalize() {
|
|
422
|
+
if (!FormatUtils.isEmptyValue(this.min)) this.min = new Date(this.min);
|
|
423
|
+
if (!FormatUtils.isEmptyValue(this.max)) this.max = new Date(this.max);
|
|
424
|
+
if (!FormatUtils.isEmptyValue(this.mean)) this.mean = new Date(this.mean);
|
|
425
|
+
|
|
426
|
+
const result = super.finalize();
|
|
427
|
+
return result;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Describes a collection of objects.
|
|
433
|
+
*
|
|
434
|
+
* For example, given the following collection:
|
|
435
|
+
*
|
|
436
|
+
* ```
|
|
437
|
+
* collection = [{
|
|
438
|
+
* first: 'john',
|
|
439
|
+
* last: 'doe',
|
|
440
|
+
* age: 23,
|
|
441
|
+
* enrolled: new Date('2022-01-01')
|
|
442
|
+
* }, {
|
|
443
|
+
* first: 'john',
|
|
444
|
+
* last: 'doe',
|
|
445
|
+
* age: 24,
|
|
446
|
+
* enrolled: new Date('2022-01-03')
|
|
447
|
+
* }, {
|
|
448
|
+
* first: 'jan',
|
|
449
|
+
* last: 'doe',
|
|
450
|
+
* age: 25,
|
|
451
|
+
* enrolled: new Date('2022-01-05')
|
|
452
|
+
* }];
|
|
453
|
+
* ```
|
|
454
|
+
*
|
|
455
|
+
* Running `utils.describe.describeObjects(collection);` gives:
|
|
456
|
+
*
|
|
457
|
+
* ```
|
|
458
|
+
* [{
|
|
459
|
+
* "count": 3,
|
|
460
|
+
* "max": "john",
|
|
461
|
+
* "min": "jan",
|
|
462
|
+
* "top": "john",
|
|
463
|
+
* "topFrequency": 2,
|
|
464
|
+
* "type": "string",
|
|
465
|
+
* "unique": 2,
|
|
466
|
+
* "what": "first"
|
|
467
|
+
* }, {
|
|
468
|
+
* "count": 3,
|
|
469
|
+
* "max": "doe",
|
|
470
|
+
* "min": "doe",
|
|
471
|
+
* "top": "doe",
|
|
472
|
+
* "topFrequency": 3,
|
|
473
|
+
* "type": "string",
|
|
474
|
+
* "unique": 1,
|
|
475
|
+
* "what": "last"
|
|
476
|
+
* }, {
|
|
477
|
+
* "count": 3,
|
|
478
|
+
* "max": 25,
|
|
479
|
+
* "min": 23,
|
|
480
|
+
* "mean": 24,
|
|
481
|
+
* "stdDeviation": 0.816496580927726,
|
|
482
|
+
* "type": "number",
|
|
483
|
+
* "what": "age"
|
|
484
|
+
* }, {
|
|
485
|
+
* "count": 3,
|
|
486
|
+
* "max": "2022-01-05T00:00:00.000Z",
|
|
487
|
+
* "min": "2022-01-01T00:00:00.000Z",
|
|
488
|
+
* "mean": "2022-01-03T00:00:00.000Z",
|
|
489
|
+
* "type": "Date",
|
|
490
|
+
* "what": "enrolled"
|
|
491
|
+
* }]
|
|
492
|
+
* ```
|
|
493
|
+
*
|
|
494
|
+
* Or Rendered to a table: `utils.table(results).render()`:
|
|
495
|
+
*
|
|
496
|
+
* what |type |count|max |min |mean |top |topFrequency|unique
|
|
497
|
+
* -- |-- |-- |-- |-- |-- |-- |-- |--
|
|
498
|
+
* first |string|3 |john |jan | |john|2 |2
|
|
499
|
+
* last |string|3 |doe |doe | |doe |3 |1
|
|
500
|
+
* age |number|3 |25 |23 |24 | | |
|
|
501
|
+
* enrolled|Date |3 |2022-01-05T00:00:00.000Z|2022-01-01T00:00:00.000Z|2022-01-03T00:00:00.000Z| | |
|
|
502
|
+
*
|
|
503
|
+
* @param {Object[]} collection - Collection of objects to be described
|
|
504
|
+
* @param {Object} options - options to be used
|
|
505
|
+
* @param {String[]} options.include - string list of fields to include in the description
|
|
506
|
+
* @param {String[]} options.exclude - string list of fields to exclude in the description
|
|
507
|
+
* @param {Object} options.overridePropertyType - object with property:type values (string|number|date|boolean)
|
|
508
|
+
* - that will override how that property is parsed.
|
|
509
|
+
* @param {Number} maxRows - max rows to consider before halting
|
|
510
|
+
* @returns {SeriesDescription[]} - collection of descriptions - one for each property
|
|
511
|
+
*/
|
|
512
|
+
module.exports.describeObjects = function describeObjects(collection, options) {
|
|
513
|
+
const cleanCollection = Array.isArray(collection) ? collection : [collection];
|
|
514
|
+
|
|
515
|
+
const cleanOptions = options ? options : {};
|
|
516
|
+
|
|
517
|
+
cleanOptions.include = cleanOptions.include ? new Set(cleanOptions.include) : null;
|
|
518
|
+
cleanOptions.exclude = new Set(cleanOptions.exclude || []);
|
|
519
|
+
cleanOptions.maxRows = cleanOptions.maxRows || -1;
|
|
520
|
+
|
|
521
|
+
// cleanOptions.prepareFn = typeof cleanOptions.prepareFn === 'function'
|
|
522
|
+
// ? cleanOptions.prepareFn
|
|
523
|
+
// : (val) => val;
|
|
524
|
+
|
|
525
|
+
const results = new Map();
|
|
526
|
+
if (cleanOptions.overridePropertyType) {
|
|
527
|
+
ObjectUtils.keys(cleanOptions.overridePropertyType)
|
|
528
|
+
.forEach((key) => {
|
|
529
|
+
const keyValue = cleanOptions.overridePropertyType[key];
|
|
530
|
+
if (keyValue === 'string') {
|
|
531
|
+
results.set(key, new StringDescription(key, cleanOptions));
|
|
532
|
+
} else if (keyValue === 'number') {
|
|
533
|
+
results.set(key, new NumberDescription(key, cleanOptions));
|
|
534
|
+
} else if (keyValue === 'date') {
|
|
535
|
+
results.set(key, new DateDescription(key, cleanOptions));
|
|
536
|
+
} else if (keyValue === 'boolean' || keyValue === 'bool') {
|
|
537
|
+
results.set(key, new StringDescription(key, cleanOptions));
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
let val;
|
|
542
|
+
// let describer;
|
|
543
|
+
|
|
544
|
+
cleanCollection.every((obj, index) => {
|
|
545
|
+
if (cleanOptions.maxRows > 0 && index >= cleanOptions.maxRows) {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
//-- handles null objects
|
|
549
|
+
// obj = cleanOptions.prepareFn(obj);
|
|
550
|
+
ObjectUtils.keys(obj)
|
|
551
|
+
.forEach((key) => {
|
|
552
|
+
val = obj[key];
|
|
553
|
+
|
|
554
|
+
if (cleanOptions.include && !cleanOptions.include.has(key)) {
|
|
555
|
+
//-- ignore
|
|
556
|
+
} else if (cleanOptions.exclude.has(key)) {
|
|
557
|
+
//-- ignore
|
|
558
|
+
} else if (FormatUtils.isEmptyValue(val)) {
|
|
559
|
+
//-- do nothing
|
|
560
|
+
} else {
|
|
561
|
+
if (Object.prototype.hasOwnProperty.call(results, key)) {
|
|
562
|
+
//-- describer already found
|
|
563
|
+
} else if (StringDescription.matchesType(val)) {
|
|
564
|
+
results[key] = new StringDescription(key);
|
|
565
|
+
} else if (DateDescription.matchesType(val)) {
|
|
566
|
+
results[key] = new DateDescription(key);
|
|
567
|
+
} else if (NumberDescription.matchesType(val)) {
|
|
568
|
+
results[key] = new NumberDescription(key);
|
|
569
|
+
} else if (BooleanDescription.matchesType(val)) {
|
|
570
|
+
results[key] = new BooleanDescription(key);
|
|
571
|
+
} else {
|
|
572
|
+
//-- ignore?
|
|
573
|
+
results[key] = new SeriesDescription(key, typeof val);
|
|
574
|
+
}
|
|
575
|
+
results[key].check(val);
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
return true;
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
const resultArray = ObjectUtils.keys(results)
|
|
582
|
+
.map((key) => results[key].finalize());
|
|
583
|
+
|
|
584
|
+
return resultArray;
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Describes a series of numbers
|
|
589
|
+
* @param {String[]} collection - collection of string values to describe
|
|
590
|
+
* @param {Object} options - options for describing strings
|
|
591
|
+
* @returns {StringDescription} - Description of the list of strings
|
|
592
|
+
*/
|
|
593
|
+
module.exports.describeStrings = function describeStrings(collection, options) {
|
|
594
|
+
const cleanCollection = Array.isArray(collection) ? collection : [collection];
|
|
595
|
+
|
|
596
|
+
const result = new StringDescription(null, options);
|
|
597
|
+
cleanCollection.forEach((value) => result.check(value));
|
|
598
|
+
return result.finalize();
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Describes a series of numbers
|
|
603
|
+
* @param {Number[]} collection - Array of numbers
|
|
604
|
+
* @param {Object} options - options for describing numbers
|
|
605
|
+
* @returns {NumberDescription}
|
|
606
|
+
*/
|
|
607
|
+
module.exports.describeNumbers = function describeNumbers(collection, options) {
|
|
608
|
+
const cleanCollection = Array.isArray(collection) ? collection : [collection];
|
|
609
|
+
|
|
610
|
+
const result = new NumberDescription(null, options);
|
|
611
|
+
cleanCollection.forEach((value) => result.check(value));
|
|
612
|
+
return result.finalize();
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Describes a series of boolean values.
|
|
617
|
+
*
|
|
618
|
+
* Note, that the following are considered TRUE:
|
|
619
|
+
*
|
|
620
|
+
* * Boolean true
|
|
621
|
+
* * Number 1
|
|
622
|
+
* * String TRUE
|
|
623
|
+
* * String True
|
|
624
|
+
* * String true
|
|
625
|
+
*
|
|
626
|
+
* @param {Boolean[] | String[] | Number[]} collection - Array of Boolean Values
|
|
627
|
+
* @param {Object} options - options for describing boolean values
|
|
628
|
+
* @returns {BooleanDescription}
|
|
629
|
+
* @see {@link module:format.parseBooleanValue}
|
|
630
|
+
*/
|
|
631
|
+
module.exports.describeBoolean = function describeBoolean(collection, options) {
|
|
632
|
+
const cleanCollection = Array.isArray(collection) ? collection : [collection];
|
|
633
|
+
|
|
634
|
+
const result = new BooleanDescription(null, options);
|
|
635
|
+
cleanCollection.forEach((value) => result.check(value));
|
|
636
|
+
return result.finalize();
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Describes a series of Date / Epoch Numbers
|
|
641
|
+
*
|
|
642
|
+
* @param {Date[] | Number[]} collection - Array of Dates / Epoch Numbers
|
|
643
|
+
* @param {Object} options - options for describing dates
|
|
644
|
+
* @returns {DateDescription}
|
|
645
|
+
*/
|
|
646
|
+
module.exports.describeDates = function describeDates(collection, options) {
|
|
647
|
+
const cleanCollection = Array.isArray(collection) ? collection : [collection];
|
|
648
|
+
|
|
649
|
+
const result = new DateDescription(null, options);
|
|
650
|
+
cleanCollection.forEach((value) => result.check(value));
|
|
651
|
+
return result.finalize();
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
//-- Testing Internal items
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Sanity check for standard deviation
|
|
658
|
+
* @param {Number[]} series - collection of numbers
|
|
659
|
+
* @returns {Number} - standard deviation of the numbers
|
|
660
|
+
* @private
|
|
661
|
+
*/
|
|
662
|
+
DescribeUtil.stdDeviation = function stdDeviation(series) {
|
|
663
|
+
let avg = 0;
|
|
664
|
+
|
|
665
|
+
if (series.length < 2) return 0.0;
|
|
666
|
+
|
|
667
|
+
const sum = series.reduce((result, val) => result + val, 0);
|
|
668
|
+
avg = sum / series.length;
|
|
669
|
+
|
|
670
|
+
const s1 = series.reduce((result, val) => result + ((val - avg) ** 2), 0);
|
|
671
|
+
// console.log(`s1:${s1}`);
|
|
672
|
+
const s2 = Math.sqrt(s1 / series.length);
|
|
673
|
+
|
|
674
|
+
return s2;
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Number Description - used for testing
|
|
679
|
+
* @private
|
|
680
|
+
*/
|
|
681
|
+
DescribeUtil.NumberDescription = NumberDescription;
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* String Description - used for testing
|
|
685
|
+
* @private
|
|
686
|
+
*/
|
|
687
|
+
DescribeUtil.StringDescription = StringDescription;
|
package/src/format.js
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* * {@link module:format.capitalize|format.capitalize} - Capitalizes only the first character in the string (ex: 'John paul');
|
|
16
16
|
* * {@link module:format.capitalizeAll|format.capitalizeAll} - Capitalizes all the words in a string (ex: 'John Paul')
|
|
17
17
|
* * {@link module:format.ellipsify|format.ellipsify} - Truncates a string if the length is 'too long'
|
|
18
|
+
* * {@link module:format.limitLines|format.limitLines(string, toLine, fromLine, lineSeparator)} - selects only a subset of lines in a string
|
|
19
|
+
* * {@link module:format.consoleLines|format.consoleLines(...)} - same as limit lines, only console.logs the string out.
|
|
18
20
|
* * Formatting Time
|
|
19
21
|
* * {@link module:format.millisecondDuration|format.millisecondDuration}
|
|
20
22
|
* * Mapping Values
|
|
@@ -844,3 +846,78 @@ module.exports.isEmptyValue = (val) =>
|
|
|
844
846
|
//-- allow for 0s
|
|
845
847
|
val === null || val === undefined || val === ''
|
|
846
848
|
|| (Array.isArray(val) && val.length === 0);
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Determines if a value is a boolean true value.
|
|
852
|
+
*
|
|
853
|
+
* Matches for:
|
|
854
|
+
*
|
|
855
|
+
* * boolean TRUE
|
|
856
|
+
* * number 1
|
|
857
|
+
* * string 'TRUE'
|
|
858
|
+
* * string 'True'
|
|
859
|
+
* * string 'true'
|
|
860
|
+
*
|
|
861
|
+
* @param {any} val - the value to be tested
|
|
862
|
+
* @returns {Boolean} - TRUE if the value matches
|
|
863
|
+
*/
|
|
864
|
+
module.exports.parseBoolean = function parseBoolean(val) {
|
|
865
|
+
return val === true
|
|
866
|
+
|| val === 1
|
|
867
|
+
|| val === 'TRUE'
|
|
868
|
+
|| val === 'True'
|
|
869
|
+
|| val === 'true';
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
/**
|
|
873
|
+
* Narrows to only fromLine - toLine (inclusive) within a string.
|
|
874
|
+
*
|
|
875
|
+
* @see {@link module:format.consoleLines|format.consoleLines()} - to console the values out
|
|
876
|
+
* @param {String|Object} str - string to be limited, or object to be json.stringify-ied
|
|
877
|
+
* @param {Number} toLine
|
|
878
|
+
* @param {Number} [fromLine=0] - starting line number (starts at 0)
|
|
879
|
+
* @param {String} [lineSeparator='\n'] - separator for lines
|
|
880
|
+
* @returns {String}
|
|
881
|
+
* @example
|
|
882
|
+
* str = '1\n2\n\3';
|
|
883
|
+
* utils.format.limitLines(str, 2); // '1\n2'
|
|
884
|
+
*
|
|
885
|
+
* str = '1\n2\n3';
|
|
886
|
+
* utils.format.limitLines(str, 3, 2); // '2\n3'
|
|
887
|
+
*
|
|
888
|
+
* str = '1\n2\n3';
|
|
889
|
+
* utils.format.limitLines(str, undefined, 2); // '2\n3'
|
|
890
|
+
*/
|
|
891
|
+
module.exports.limitLines = function limitLines(str, toLine, fromLine, lineSeparator) {
|
|
892
|
+
const cleanStr = typeof str === 'string'
|
|
893
|
+
? str
|
|
894
|
+
: JSON.stringify(str || '', FormatUtils.mapReplacer, 2);
|
|
895
|
+
const cleanLine = lineSeparator || '\n';
|
|
896
|
+
|
|
897
|
+
return cleanStr.split(cleanLine)
|
|
898
|
+
.slice(fromLine || 0, toLine)
|
|
899
|
+
.join(cleanLine);
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* Same as {@link module:format.limitLines|limitLines()} - only prints to the console.
|
|
904
|
+
*
|
|
905
|
+
* @see {@link module:format.limitLines|format.limitLines}
|
|
906
|
+
* @param {String|Object} str - string to be limited, or object to be json.stringify-ied
|
|
907
|
+
* @param {Number} toLine
|
|
908
|
+
* @param {Number} [fromLine=0] - starting line number (starts at 0)
|
|
909
|
+
* @param {String} [lineSeparator='\n'] - separator for lines
|
|
910
|
+
* @returns {String}
|
|
911
|
+
* @example
|
|
912
|
+
* str = '1\n2\n\3';
|
|
913
|
+
* utils.format.limitLines(str, 2); // '1\n2'
|
|
914
|
+
*
|
|
915
|
+
* str = '1\n2\n3';
|
|
916
|
+
* utils.format.limitLines(str, 3, 2); // '2\n3'
|
|
917
|
+
*
|
|
918
|
+
* str = '1\n2\n3';
|
|
919
|
+
* utils.format.limitLines(str, undefined, 2); // '2\n3'
|
|
920
|
+
*/
|
|
921
|
+
module.exports.consoleLines = function consoleLines(str, toLine, fromLine, lineSeparator) {
|
|
922
|
+
console.log(FormatUtils.limitLines(str, toLine, fromLine, lineSeparator));
|
|
923
|
+
};
|
package/src/hashMap.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
const FormatUtils = require('./format');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Library for working with JavaScript hashmaps.
|
|
5
|
+
*
|
|
6
|
+
* * Modifying
|
|
7
|
+
* * {@link module:hashMap.add|hashMap.add(map, key, value):Map} - Add a value to a map and return the Map
|
|
8
|
+
* * {@link module:hashMap.union|hashMap.union(targetMap, additionalMap, canOverwrite)} - merges two maps and ignores or overwrites with conflicts
|
|
9
|
+
* * Cloning
|
|
10
|
+
* * {@link module:hashMap.clone|hashMap.clone(map):Map} - Clones a given Map
|
|
11
|
+
* * Conversion
|
|
12
|
+
* * {@link module:hashMap.stringify|hashMap.stringify(map, indent)} - converts a Map to a string representation
|
|
13
|
+
* * {@link module:hashMap.toObject|hashMap.toObject(map)} - converts a hashMap to an Object
|
|
14
|
+
* * {@link module:hashMap.fromObject|hashMap.fromObject(object)} - converts an object's properties to hashMap keys
|
|
15
|
+
*
|
|
16
|
+
* Note: JavaScript Maps can sometimes be faster than using Objects,
|
|
17
|
+
* and sometimes slower.
|
|
18
|
+
*
|
|
19
|
+
* (Current understanding is that Maps do better with more updates made)
|
|
20
|
+
*
|
|
21
|
+
* There are many searches such as `javascript map vs object performance`
|
|
22
|
+
* with many interesting links to come across.
|
|
23
|
+
*
|
|
24
|
+
* @module hashMap
|
|
25
|
+
* @exports hashMap
|
|
26
|
+
*/
|
|
27
|
+
module.exports = {};
|
|
28
|
+
const HashMapUtil = module.exports;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Set a Map in a functional manner (adding a value and returning the map)
|
|
32
|
+
* @param {Map} map - the map to be updated
|
|
33
|
+
* @param {any} key -
|
|
34
|
+
* @param {any} value -
|
|
35
|
+
* @returns {Map} - the updated map value
|
|
36
|
+
* @example
|
|
37
|
+
* const objectToMap = { key1: 1, key2: 2, key3: 3 };
|
|
38
|
+
* const keys = [...Object.keys(objectToMap)];
|
|
39
|
+
* // ['key1', 'key2', 'key3'];
|
|
40
|
+
*
|
|
41
|
+
* const result = keys.reduce(
|
|
42
|
+
* (result, key) => utils.hashMap.add(result, key, objectToMap[key]),
|
|
43
|
+
* new Map()
|
|
44
|
+
* );
|
|
45
|
+
* // Map([[ 'key1',1 ], ['key2', 2], ['key3', 3]]);
|
|
46
|
+
*/
|
|
47
|
+
module.exports.add = function add(map, key, value) {
|
|
48
|
+
map.set(key, value);
|
|
49
|
+
return map;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Clones a Map
|
|
54
|
+
* @param {Map} target - Map to clone
|
|
55
|
+
* @returns {Map} - clone of the target map
|
|
56
|
+
* @example
|
|
57
|
+
* const sourceMap = new Map();
|
|
58
|
+
* sourceMap.set('first', 1);
|
|
59
|
+
* const mapClone = utils.hashMap.clone(sourceMap);
|
|
60
|
+
* mapClone.has('first'); // true
|
|
61
|
+
*/
|
|
62
|
+
module.exports.clone = function clone(target) {
|
|
63
|
+
if (!(target instanceof Map)) {
|
|
64
|
+
throw Error('hashMap.clone(targetMap): targetMap must be a Map');
|
|
65
|
+
}
|
|
66
|
+
return new Map(target.entries());
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Creates a new map that includes all entries of targetMap, and all entries of additionalMap.
|
|
71
|
+
*
|
|
72
|
+
* If allowOverwrite is true, then values found in additionalMap will take priority in case of conflicts.
|
|
73
|
+
*
|
|
74
|
+
* ```
|
|
75
|
+
* const targetMap = new Map([['first', 'John'], ['amount': 100]]);
|
|
76
|
+
* const additionalMap = new Map([['last': 'Doe'], ['amount': 200]]);
|
|
77
|
+
*
|
|
78
|
+
* utils.hashMap.union(targetMap, additionalMap, true);
|
|
79
|
+
* // Map([['first', 'John'], ['last', 'Doe'], ['amount', 200]]);
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* If allowOverwrite is false, then values found in targetMap will take priority in case of conflicts.
|
|
83
|
+
*
|
|
84
|
+
* ```
|
|
85
|
+
* const targetMap = new Map([['first', 'John'], ['amount': 100]]);
|
|
86
|
+
* const additionalMap = new Map([['last': 'Doe'], ['amount': 200]]);
|
|
87
|
+
*
|
|
88
|
+
* utils.hashMap.union(targetMap, additionalMap);
|
|
89
|
+
* utils.hashMap.union(targetMap, additionalMap, false);
|
|
90
|
+
* // Map([['first', 'John'], ['last', 'Doe'], ['amount', 100]]);
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @param {Map} targetMap
|
|
94
|
+
* @param {Map} additionalMap -
|
|
95
|
+
* @param {Boolean} [allowOverwrite=false] - whether targetMap is prioritized (false) or additional prioritized (true)
|
|
96
|
+
* @returns {Map}
|
|
97
|
+
*/
|
|
98
|
+
module.exports.union = function union(targetMap, additionalMap, allowOverwrite) {
|
|
99
|
+
if (!(targetMap instanceof Map)) {
|
|
100
|
+
return HashMapUtil.clone(additionalMap);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const result = new Map(targetMap.entries());
|
|
104
|
+
|
|
105
|
+
if (!(additionalMap instanceof Map)) {
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const key of additionalMap.keys()) {
|
|
110
|
+
if (!result.has(key) || allowOverwrite) {
|
|
111
|
+
result.set(key, additionalMap.get(key));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Serializes a hashMap (plain javascript Map) to a string
|
|
119
|
+
*
|
|
120
|
+
* ```
|
|
121
|
+
* const target = new Map([['first', 1], ['second', 2]]);
|
|
122
|
+
* HashMapUtil.stringify(target);
|
|
123
|
+
* // '{"dataType":"Map","value":[["first",1],["second",2]]}'
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* Note, that passing indent will make the results much more legible.
|
|
127
|
+
*
|
|
128
|
+
* ```
|
|
129
|
+
* {
|
|
130
|
+
* "dataType": "Map",
|
|
131
|
+
* "value": [
|
|
132
|
+
* [
|
|
133
|
+
* "first",
|
|
134
|
+
* 1
|
|
135
|
+
* ],
|
|
136
|
+
* [
|
|
137
|
+
* "second",
|
|
138
|
+
* 2
|
|
139
|
+
* ]
|
|
140
|
+
* ]
|
|
141
|
+
* }
|
|
142
|
+
* ```
|
|
143
|
+
* @param {Map} target - the Map to be serialized
|
|
144
|
+
* @param {Number} indentation - the indentation passed to JSON.serialize
|
|
145
|
+
* @returns {String} - JSON.stringify string for the map
|
|
146
|
+
*/
|
|
147
|
+
module.exports.stringify = function stringify(map, indentation) {
|
|
148
|
+
return JSON.stringify(map, FormatUtils.mapReplacer, indentation);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Converts a map to an object
|
|
153
|
+
*
|
|
154
|
+
* For example, say we have a Map:
|
|
155
|
+
*
|
|
156
|
+
* ```
|
|
157
|
+
* const targetMap = new Map([['first', 1], ['second', 2], ['third', 3]]);
|
|
158
|
+
* ```
|
|
159
|
+
*
|
|
160
|
+
* We can convert it to an Object as follows:
|
|
161
|
+
*
|
|
162
|
+
* ```
|
|
163
|
+
* const targetMap = utils.hashMap.toObject(targetObject)
|
|
164
|
+
* // { first: 1, second: 2, third: 3 };
|
|
165
|
+
* ```
|
|
166
|
+
*
|
|
167
|
+
* @param {Map} target - map to be converted
|
|
168
|
+
* @returns {Object} - object with the properties as the target map's keys.
|
|
169
|
+
* @see {@link hashMap.fromObject} - to reverse the process
|
|
170
|
+
*/
|
|
171
|
+
module.exports.toObject = function toObject(target) {
|
|
172
|
+
const results = {};
|
|
173
|
+
|
|
174
|
+
if (!target) { // eslint-disable-line no-empty
|
|
175
|
+
} else if (!(target instanceof Map)) {
|
|
176
|
+
throw Error('hashMap.toObject(map): must be passed a Map');
|
|
177
|
+
} else {
|
|
178
|
+
[...target.keys()]
|
|
179
|
+
.forEach((key) => {
|
|
180
|
+
results[key] = target.get(key);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return results;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Creates a Map from the properties of an Object
|
|
189
|
+
*
|
|
190
|
+
* For example, say we have an object:
|
|
191
|
+
*
|
|
192
|
+
* ```
|
|
193
|
+
* const targetObject = { first: 1, second: 2, third: 3 };
|
|
194
|
+
* ```
|
|
195
|
+
*
|
|
196
|
+
* We can convert it to a Map as follows:
|
|
197
|
+
*
|
|
198
|
+
* ```
|
|
199
|
+
* const targetMap = utils.hashMap.fromObject(targetObject)
|
|
200
|
+
* // new Map([['first', 1], ['second', 2], ['third', 3]]);
|
|
201
|
+
* ```
|
|
202
|
+
*
|
|
203
|
+
* @param {Object} target - target object with properties that should be considered keys
|
|
204
|
+
* @returns {Map<String,any>} - converted properties as keys in a new map
|
|
205
|
+
* @see {@link hashMap.toObject} - to reverse the process
|
|
206
|
+
*/
|
|
207
|
+
module.exports.fromObject = function fromObject(target) {
|
|
208
|
+
if (!(typeof target === 'object')) {
|
|
209
|
+
throw Error('hashMap.fromObject(object): must be passed an object');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (target.dataType === 'Map' && Array.isArray(target.value)) {
|
|
213
|
+
return new Map(target.value);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return [...Object.keys(target)]
|
|
217
|
+
.reduce((result, key) => HashMapUtil.add(result, key, target[key]), new Map());
|
|
218
|
+
};
|
package/src/index.js
CHANGED
|
@@ -2,7 +2,9 @@ const aggregate = require('./aggregate');
|
|
|
2
2
|
const array = require('./array');
|
|
3
3
|
const base64 = require('./base64');
|
|
4
4
|
const datasets = require('./datasets');
|
|
5
|
+
const describe = require('./describe');
|
|
5
6
|
const group = require('./group');
|
|
7
|
+
const hashMap = require('./hashMap');
|
|
6
8
|
const ijsUtils = require('./ijs');
|
|
7
9
|
const file = require('./file');
|
|
8
10
|
const vega = require('./vega');
|
|
@@ -29,39 +31,43 @@ const table = function table(...rest) {
|
|
|
29
31
|
* @private
|
|
30
32
|
*/
|
|
31
33
|
module.exports = {
|
|
32
|
-
/** @see module:aggregate */
|
|
34
|
+
/** @see {@link module:aggregate} */
|
|
33
35
|
aggregate,
|
|
34
36
|
agg: aggregate,
|
|
35
|
-
/** @see module:array */
|
|
37
|
+
/** @see {@link module:array} */
|
|
36
38
|
array,
|
|
37
|
-
/** @see module:base64 */
|
|
39
|
+
/** @see {@link module:base64} */
|
|
38
40
|
base64,
|
|
39
|
-
/** @see module:datasets */
|
|
41
|
+
/** @see {@link module:datasets} */
|
|
40
42
|
datasets,
|
|
41
43
|
dataset: datasets,
|
|
42
|
-
/** @see module:
|
|
44
|
+
/** @see {@link module:describe} */
|
|
45
|
+
describe,
|
|
46
|
+
/** @see {@link module:file} */
|
|
43
47
|
file,
|
|
44
|
-
/** @see module:group */
|
|
48
|
+
/** @see {@link module:group} */
|
|
45
49
|
group,
|
|
46
|
-
/** @see module:
|
|
50
|
+
/** @see {@link module:hashMap} */
|
|
51
|
+
hashMap,
|
|
52
|
+
/** @see {@link module:format} */
|
|
47
53
|
format,
|
|
48
|
-
/** @see IJSUtils */
|
|
54
|
+
/** @see {@link IJSUtils} */
|
|
49
55
|
ijs: ijsUtils,
|
|
50
|
-
/** @see module:latex */
|
|
56
|
+
/** @see {@link module:latex} */
|
|
51
57
|
latex,
|
|
52
|
-
/** @see module:leaflet */
|
|
58
|
+
/** @see {@link module:leaflet} */
|
|
53
59
|
leaflet,
|
|
54
|
-
/** @see module:object */
|
|
60
|
+
/** @see {@link module:object} */
|
|
55
61
|
object,
|
|
56
62
|
/** @see {@link module:plantuml} */
|
|
57
63
|
plantuml,
|
|
58
|
-
/** @see module:random */
|
|
64
|
+
/** @see {@link module:random} */
|
|
59
65
|
random,
|
|
60
|
-
/** @see module:set */
|
|
66
|
+
/** @see {@link module:set} */
|
|
61
67
|
set,
|
|
62
|
-
/** @see module:svg */
|
|
68
|
+
/** @see {@link module:svg} */
|
|
63
69
|
svg,
|
|
64
|
-
/** @see module:vega */
|
|
70
|
+
/** @see {@link module:vega} */
|
|
65
71
|
vega,
|
|
66
72
|
|
|
67
73
|
/** @see SourceMap */
|