jupyter-ijavascript-utils 1.32.0 → 1.35.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/Dockerfile +1 -1
- package/README.md +3 -0
- package/package.json +1 -1
- package/src/aggregate.js +1 -0
- package/src/array.js +5 -0
- package/src/format.js +107 -0
- package/src/object.js +284 -2
- package/src/random.js +62 -3
package/DOCS.md
CHANGED
|
@@ -75,6 +75,9 @@ Give it a try here:
|
|
|
75
75
|
|
|
76
76
|
## What's New
|
|
77
77
|
|
|
78
|
+
* 1.35 - {@link module:object.extractObjectProperties|extractObjectProperties} / {@link module:object.extractObjectProperty|extractObjectProperty} - to do horizontal transposes on objects
|
|
79
|
+
* 1.34 - {@link module:format.mapArrayDomain|format.mapArrayDomain} and add notes in the header of {@link module:random|random} on using non-uniform distributions.
|
|
80
|
+
* 1.33 - Object.augmentInherit and Object.union
|
|
78
81
|
* 1.32 - Array.indexify to identify sections within a 1d array into a hierarchy.
|
|
79
82
|
* 1.31 - harden Array.transpose for arrays with nulls, and Table.generateTSV
|
|
80
83
|
* 1.30 - add Format.wordWrap and Format.lineCount
|
package/Dockerfile
CHANGED
package/README.md
CHANGED
|
@@ -55,6 +55,9 @@ This is not intended to be the only way to accomplish many of these tasks, and a
|
|
|
55
55
|
|
|
56
56
|
# What's New
|
|
57
57
|
|
|
58
|
+
* 1.35 - object.extractObjectProperties / object.extractObjectProperty - to do horizontal transposes on objects
|
|
59
|
+
* 1.34 - format.mapArrayDomain and add notes in to random on using non-uniform distributions.
|
|
60
|
+
* 1.33 - Object.augmentInherit and Object.union
|
|
58
61
|
* 1.32 - Array.indexify to identify sections within a 1d array into a hierarchy.
|
|
59
62
|
* 1.31 - harden Array.transpose for arrays with nulls, and Table.generateTSV
|
|
60
63
|
* 1.30 - add Format.wordWrap and Format.lineCount
|
package/package.json
CHANGED
package/src/aggregate.js
CHANGED
|
@@ -502,6 +502,7 @@ module.exports.unique = function unique(collection, accessor, uniquifierFn) {
|
|
|
502
502
|
|
|
503
503
|
return Array.from(new Set(
|
|
504
504
|
collection.map(cleanedFunc)
|
|
505
|
+
.reduce((result, val) => (val instanceof Set || Array.isArray(val)) ? [...result, ...val] : [...result, val], [])
|
|
505
506
|
));
|
|
506
507
|
};
|
|
507
508
|
|
package/src/array.js
CHANGED
|
@@ -639,6 +639,11 @@ module.exports.arangeMulti = module.exports.arrangeMulti;
|
|
|
639
639
|
* // }
|
|
640
640
|
* // ];
|
|
641
641
|
* ```
|
|
642
|
+
*
|
|
643
|
+
* @param {Array} source - list of values to index
|
|
644
|
+
* @param {...Function} sectionIndicatorFunctions - each function indicates a new section
|
|
645
|
+
* @returns {Object[]} - collection of objects, each with a new section (indicating the layers)
|
|
646
|
+
* and subIndex: unique value in the section (always 0 for header)
|
|
642
647
|
*/
|
|
643
648
|
module.exports.indexify = function indexify(source, ...sectionIndicatorFunctions) {
|
|
644
649
|
const functionSignature = 'indexify(source, ...sectionIndicatorFunctions)';
|
package/src/format.js
CHANGED
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* * {@link module:format.millisecondDuration|format.millisecondDuration}
|
|
25
25
|
* * Mapping Values
|
|
26
26
|
* * {@link module:format.mapDomain|format.mapDomain} - projects a value from a domain of expected values to a range of output values, ex: 10% of 2 Pi
|
|
27
|
+
* * {@link module:format.mapArrayDomain|format.mapArrayDomain} - projects a value from between a range a value, and picks the corresponding value from an array
|
|
27
28
|
* * Identifying Time Periods
|
|
28
29
|
* * {@link module:format.timePeriod|format.timePeriod} - Converts a time to a time period, very helpful for animations
|
|
29
30
|
* * {@link module:format.timePeriodPercent|format.timePeriodPercent} - Determines the percent complete of the current time period
|
|
@@ -383,6 +384,112 @@ module.exports.mapDomain = function mapDomain(val, [domainMin, domainMax], [rang
|
|
|
383
384
|
return (((val - domainMin) * (rangeMax - rangeMin)) / (domainMax - domainMin)) + rangeMin;
|
|
384
385
|
};
|
|
385
386
|
|
|
387
|
+
/**
|
|
388
|
+
* projects a value from a domain of expected values to an array - very useful for random distributions.
|
|
389
|
+
*
|
|
390
|
+
* like mapping normal / gaussian distributions to an array of values with
|
|
391
|
+
* [d3-random](https://observablehq.com/@d3/d3-random)
|
|
392
|
+
* as format.mapArrayDomain projects a value from between a range a value,
|
|
393
|
+
* and picks the corresponding value from an array.
|
|
394
|
+
*
|
|
395
|
+
* For example:
|
|
396
|
+
*
|
|
397
|
+
* ```
|
|
398
|
+
* require('esm-hook');
|
|
399
|
+
* d3 = require('d3');
|
|
400
|
+
*
|
|
401
|
+
* //-- create a number generator using Normal / Gaussian distribution
|
|
402
|
+
* randomGenerator = d3.randomNormal(
|
|
403
|
+
* 0.5, // mu - or centerline
|
|
404
|
+
* 0.1 // sigma - or spread of values
|
|
405
|
+
* );
|
|
406
|
+
*
|
|
407
|
+
* randomValue = randomGenerator();
|
|
408
|
+
* // randomValue - 0.4
|
|
409
|
+
*
|
|
410
|
+
* randomDataset = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
|
|
411
|
+
*
|
|
412
|
+
* //-- create an array of 3 items, each with the results from randomGenerator
|
|
413
|
+
* results = utils.array.size(3, () => randomGenerator());
|
|
414
|
+
* // [ 0.6235937672428706, 0.4991359903898883, 0.4279365561645624 ]
|
|
415
|
+
*
|
|
416
|
+
* //-- map those values to the randomDataset
|
|
417
|
+
* results.map(val => ({ pick: utils.format.mapArrayDomain(val, randomDataset) }));
|
|
418
|
+
* // [ { pick: 'g' }, { pick: 'e' }, { pick: 'e' } ]
|
|
419
|
+
*
|
|
420
|
+
* //-- group them by the pick field
|
|
421
|
+
* //-- then add a new property called count - using the # of records with the same value
|
|
422
|
+
* groupedResults = utils.group.by(resultPicks, 'pick')
|
|
423
|
+
* .reduce((list) => ({ count: list.length }));
|
|
424
|
+
* // [ { pick: 'g', count: 1 }, { pick: 'e', count: 2 } ]
|
|
425
|
+
*
|
|
426
|
+
* //-- make a bar chart (only with 10k results)
|
|
427
|
+
* utils.vega.embed((vl) => {
|
|
428
|
+
* return vl
|
|
429
|
+
* .markBar()
|
|
430
|
+
* .title('Distribution')
|
|
431
|
+
* .data(groupedResults)
|
|
432
|
+
* .encode(
|
|
433
|
+
* vl.x().fieldN('value'),
|
|
434
|
+
* vl.y().fieldQ('count').scale({type: 'log'})
|
|
435
|
+
* );
|
|
436
|
+
* });
|
|
437
|
+
* ```
|
|
438
|
+
* 
|
|
439
|
+
*
|
|
440
|
+
* @param {Number} val - value to be mapped
|
|
441
|
+
* @param {Array} targetArray - array of values to pick from
|
|
442
|
+
* @param {Array} domain - [min, max] - domain of possible input values
|
|
443
|
+
* @param {Array} [domain.domainMin = 0] - minimum input value (anything at or below maps to rangeMin)
|
|
444
|
+
* @param {Array} [domain.domainMax = 1] - maximum input value (anything at or above maps to rangeMax)
|
|
445
|
+
* @returns Number
|
|
446
|
+
* @see {@link module:format.clampDomain|clampDomain(value, [min, max])}
|
|
447
|
+
* @example
|
|
448
|
+
*
|
|
449
|
+
* //-- array of 10 values
|
|
450
|
+
* randomArray = ['a', 'b', 'c', 'd', 'e'];
|
|
451
|
+
*
|
|
452
|
+
* format.mapArrayDomain(-1, randomArray, [0, 5]);
|
|
453
|
+
* // 'a' - since it is below the minimum value
|
|
454
|
+
* format.mapArrayDomain(6, randomArray, [0, 5]);
|
|
455
|
+
* // 'e' - since it is the minimum value
|
|
456
|
+
*
|
|
457
|
+
* format.mapArrayDomain(0.9, randomArray, [0, 5]);
|
|
458
|
+
* // 'a'
|
|
459
|
+
* format.mapArrayDomain(1, randomArray, [0, 5]);
|
|
460
|
+
* // 'b'
|
|
461
|
+
* format.mapArrayDomain(2.5, randomArray, [0, 5]);
|
|
462
|
+
* // 'c'
|
|
463
|
+
*
|
|
464
|
+
* //-- or leaving the domain of possible values value can be out:
|
|
465
|
+
* format.mapArrayDomain(0.5, randomArray); // assumed [0, 1]
|
|
466
|
+
* // 'c'
|
|
467
|
+
*/
|
|
468
|
+
module.exports.mapArrayDomain = function mapArrayDomain(val, targetArray, domain = null) {
|
|
469
|
+
if (!targetArray || !Array.isArray(targetArray)) {
|
|
470
|
+
throw Error('mapArrayDomain: targetArray is not an array');
|
|
471
|
+
} else if (targetArray.length < 1) {
|
|
472
|
+
throw Error('mapArrayDomain: targetArray is not a populated array');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const cleanArray = domain || [];
|
|
476
|
+
const [domainMin = 0, domainMax = 1] = cleanArray;
|
|
477
|
+
|
|
478
|
+
if (val <= domainMin) {
|
|
479
|
+
return targetArray[0];
|
|
480
|
+
} else if (val >= domainMax) {
|
|
481
|
+
return targetArray[targetArray.length - 1];
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const targetIndex = Math.floor(FormatUtils.mapDomain(
|
|
485
|
+
val,
|
|
486
|
+
[domainMin, domainMax],
|
|
487
|
+
[0, targetArray.length]
|
|
488
|
+
));
|
|
489
|
+
// console.log(targetIndex);
|
|
490
|
+
return targetArray[targetIndex];
|
|
491
|
+
};
|
|
492
|
+
|
|
386
493
|
/**
|
|
387
494
|
* Given that a period of time is millisecondPeriod number of milliseconds long,
|
|
388
495
|
* determines which period of time we are currently in (timeEpoch / millisecondPeriod)
|
package/src/object.js
CHANGED
|
@@ -16,18 +16,23 @@ const FormatUtils = require('./format');
|
|
|
16
16
|
* * {@link module:object.setPropertyDefaults|setPropertyDefaults()} - sets values for objects that don't currently have the property
|
|
17
17
|
* * {@link module:object.propertyValueSample|propertyValueSample(collection)} - finds non-empty values for all properties found in the collection
|
|
18
18
|
* * Manipulating objects
|
|
19
|
-
* * {@link module:object.
|
|
20
|
-
* * {@link module:object.
|
|
19
|
+
* * {@link module:object.assign|objAssign(object, property, value)} - Applies properties to an object in functional programming style.
|
|
20
|
+
* * {@link module:object.assignEntities|objAssignEntities(object, [property, value])} - Applies properties to an object using Array values - [key,value]
|
|
21
|
+
* * {@link module:object.augment|augment(object, augmentFn)} - Applies properties to an object similar to Map
|
|
22
|
+
* * {@link module:object.augmentInherit|augmentInherit(object, augmentFn)} - Applies properties to a collection of objects, 'remembering' the last value - useful for 1d to *D lists.
|
|
21
23
|
* * {@link module:object.selectObjectProperties|selectObjectProperties()} - keep only specific properties
|
|
22
24
|
* * {@link module:object.filterObjectProperties|filterObjectProperties()} - remove specific properties
|
|
23
25
|
* * {@link module:object.mapProperties|mapProperties(collection, fn, ...properties)} - map multiple properties at once (like parseInt, or toString)
|
|
24
26
|
* * {@link module:object.formatProperties|formatProperties(collection, propertyTranslation)} - map specific properties (ex: toString, toNumber, etc)
|
|
27
|
+
* * {@link module:object.union|union(objectList1, objectList2)} - Unites the properties of two collections of objects.
|
|
25
28
|
* * Fetch child properties from related objects
|
|
26
29
|
* * {@link module:object.fetchObjectProperty|fetchObjectProperty(object, string)} - use dot notation to bring a child property onto a parent
|
|
27
30
|
* * {@link module:object.fetchObjectProperties|fetchObjectProperties(object, string[])} - use dot notation to bring multiple child properties onto a parent
|
|
28
31
|
* * {@link module:object.join|join(array, index, map, fn)} - join a collection against a map by a given index
|
|
29
32
|
* * {@link module:object.joinProperties|join(array, index, map, ...fields)} - join a collection, and copy properties over from the mapped object.
|
|
30
33
|
* * {@link module:object.propertyFromList|propertyFromList(array, propertyName)} - fetches a specific property from all objects in a list
|
|
34
|
+
* * {@link module:object.extractObjectProperty|extractObjectProperty(list, propertyNameOrFn)} - extracts a property or fn across all objects in list.
|
|
35
|
+
* * {@link module:object.extractObjectProperties|extractObjectProperties(list, propertyNameOrFnMap)} - extracts multiple propertie or fn across all objects in list.
|
|
31
36
|
* * Rename properties
|
|
32
37
|
* * {@link module:object.cleanProperties|cleanProperties()} - correct inaccessible property names in a list of objects - in place
|
|
33
38
|
* * * {@link module:object.cleanProperties2|cleanProperties2()} - correct inaccessible property names in a list of objects - on a cloned list
|
|
@@ -533,6 +538,100 @@ module.exports.fetchObjectProperties = function fetchObjectProperties(list, prop
|
|
|
533
538
|
});
|
|
534
539
|
};
|
|
535
540
|
|
|
541
|
+
/**
|
|
542
|
+
* Similar to a transpose, this finds all the values of a particular property
|
|
543
|
+
* within a list of objects.
|
|
544
|
+
*
|
|
545
|
+
* ```
|
|
546
|
+
* weather = [
|
|
547
|
+
* { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
|
|
548
|
+
* null,
|
|
549
|
+
* { id: 3, city: 'New York', month: 'Apr', precip: 3.94 },
|
|
550
|
+
* { id: 6, city: 'Chicago', month: 'Apr', precip: 3.62 }
|
|
551
|
+
* ];
|
|
552
|
+
*
|
|
553
|
+
* utils.object.extractObjectProperty(weather, 'city');
|
|
554
|
+
* // [ 'Seattle', 'New York', 'Chicago'];
|
|
555
|
+
* ```
|
|
556
|
+
*
|
|
557
|
+
* @param {Object|Object[]} objectList - list of objects to extract the property from
|
|
558
|
+
* @param {Function | String} propertyOrFn - Name of the property or accessor function
|
|
559
|
+
* @returns {Array} - single array the values stored in propertyOrFn across all objects in objectList.
|
|
560
|
+
* @see {@link module:aggregate.unique|unique()} to see all the unique values stored
|
|
561
|
+
* @see {@link module:object.extractObjectProperties|extractObjectProperties(list, propertyNameOrFnMap)} to see the values extracted into a single object / horizontal transpose.
|
|
562
|
+
*/
|
|
563
|
+
module.exports.extractObjectProperty = function extractObjectProperty(list, propertyOrFn) {
|
|
564
|
+
let cleanList = !list ? [] : Array.isArray(list) ? list : [list];
|
|
565
|
+
cleanList = cleanList.filter((r) => r);
|
|
566
|
+
const fn = ObjectUtils.evaluateFunctionOrProperty(propertyOrFn);
|
|
567
|
+
return cleanList.map(fn);
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Similar to a transpose, this finds all the values of a particular property
|
|
572
|
+
* within a list of objects.
|
|
573
|
+
*
|
|
574
|
+
* ```
|
|
575
|
+
* weather = [
|
|
576
|
+
* { id: 1, city: 'Seattle', month: 'Aug', precip: 0.87 },
|
|
577
|
+
* null,
|
|
578
|
+
* { id: 3, city: 'New York', month: 'Apr', precip: 3.94 },
|
|
579
|
+
* { id: 6, city: 'Chicago', month: 'Apr', precip: 3.62 }
|
|
580
|
+
* ];
|
|
581
|
+
*
|
|
582
|
+
* utils.object.extractObjectProperties(weather, ['city', 'month']);
|
|
583
|
+
* // { city: [ 'Seattle', 'New York', 'Chicago'], month: ['Aug', 'Apr', 'Apr'] }
|
|
584
|
+
*
|
|
585
|
+
* // note you can also pass maps with property name strings, or functions.
|
|
586
|
+
* extractionMap = new Map();
|
|
587
|
+
* extractionMap.set('city', null); // default the property by the key name
|
|
588
|
+
* extractionMap.set('city2', 'city'); // specify the property to use
|
|
589
|
+
* extractionMap.set('city3', (r) => r.city); // specify a function
|
|
590
|
+
*
|
|
591
|
+
* utils.object.extractObjectProperties(weather, extractionMap);
|
|
592
|
+
* // {
|
|
593
|
+
* // city: ['Seattle', 'New York', 'Chicago'],
|
|
594
|
+
* // city2: ['Seattle', 'New York', 'Chicago'],
|
|
595
|
+
* // city3: ['Seattle', 'New York', 'Chicago']
|
|
596
|
+
* // };
|
|
597
|
+
* ```
|
|
598
|
+
*
|
|
599
|
+
* @param {Object|Object[]} objectList - list of objects to extract the property from
|
|
600
|
+
* @param {Map<Function | String>} propertyOrFnMap - Name of the property or accessor function
|
|
601
|
+
* @returns {Object} - Object with the keys in the map as properties - extracting the values across all in list.
|
|
602
|
+
* @see {@link module:object.extractObjectProperty|extractObjectProperty(list, propertyNameOrFn)} to see all the values stored for a single property.
|
|
603
|
+
*/
|
|
604
|
+
module.exports.extractObjectProperties = function extractObjectProperties(list, propertyOrFnMap) {
|
|
605
|
+
let propertyEntries = [];
|
|
606
|
+
const signature = 'object.extractObjectProperties(list:Object[], propertyOrFnMap:Map<String, stringOrFn>)';
|
|
607
|
+
|
|
608
|
+
if (!propertyOrFnMap) {
|
|
609
|
+
return [];
|
|
610
|
+
} else if (Array.isArray(propertyOrFnMap)) {
|
|
611
|
+
for (let i = 0; i < propertyOrFnMap.length; i += 1) {
|
|
612
|
+
const propertyOrFnKey = propertyOrFnMap[i];
|
|
613
|
+
|
|
614
|
+
//-- only string properties are accepted as an array
|
|
615
|
+
if (typeof propertyOrFnKey === 'string') {
|
|
616
|
+
propertyEntries.push([propertyOrFnKey, propertyOrFnKey]);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
} else if (propertyOrFnMap instanceof Map) {
|
|
620
|
+
propertyEntries = [...propertyOrFnMap.entries()];
|
|
621
|
+
} else {
|
|
622
|
+
throw Error(`${signature}: propertyOrFnMap must be a map of propertyName keys, with a function or property name as the value`);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const results = {};
|
|
626
|
+
propertyEntries.forEach(([propertyName, propertyOrFn]) => {
|
|
627
|
+
results[propertyName] = ObjectUtils.extractObjectProperty(list, propertyOrFn || propertyName);
|
|
628
|
+
// const fn = ObjectUtils.evaluateFunctionOrProperty(propertyOrFn);
|
|
629
|
+
// results[propertyName] = cleanList.map(fn);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
return results;
|
|
633
|
+
};
|
|
634
|
+
|
|
536
635
|
/**
|
|
537
636
|
* Accesses a property using a string
|
|
538
637
|
* @param {Object} obj - object to access the properties on
|
|
@@ -1129,3 +1228,186 @@ module.exports.propertyValueSample = function propertyValueSample(objCollection)
|
|
|
1129
1228
|
|
|
1130
1229
|
return result;
|
|
1131
1230
|
};
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* Appends values to a collection of objects,
|
|
1234
|
+
* where if the value `undefined` is provided,
|
|
1235
|
+
* then it "remembers" or "inherits" the value previously used.
|
|
1236
|
+
*
|
|
1237
|
+
* This is VERY useful for converting a 1 dimensional list, into a hierarchical tree structure.
|
|
1238
|
+
*
|
|
1239
|
+
* For example, say we got this from a previous successful scrape:
|
|
1240
|
+
*
|
|
1241
|
+
* ```
|
|
1242
|
+
* source = [
|
|
1243
|
+
* { text: '# Overview' },
|
|
1244
|
+
* { text: 'This entire list is a hierarchy of data.' },
|
|
1245
|
+
* { text: '# Section A' },
|
|
1246
|
+
* { text: 'This describes section A' },
|
|
1247
|
+
* { text: '## SubSection 1' },
|
|
1248
|
+
* { text: 'With a subsection belonging to Section A' },
|
|
1249
|
+
* { text: '# Section B' },
|
|
1250
|
+
* { text: 'With an entirely unrelated section B, that is sibling to Section A' }
|
|
1251
|
+
* ];
|
|
1252
|
+
* ```
|
|
1253
|
+
*
|
|
1254
|
+
* We would like to know which heading1 and heading2 the texts belong to:
|
|
1255
|
+
*
|
|
1256
|
+
* ```
|
|
1257
|
+
*
|
|
1258
|
+
* const isHeader1 = (str) => str.startsWith('# ');
|
|
1259
|
+
* const isHeader2 = (str) => str.startsWith('## ');
|
|
1260
|
+
*
|
|
1261
|
+
* //-- note, return undefined for any property you don't want to have inherited.
|
|
1262
|
+
* inheritFn = (entry) => ({
|
|
1263
|
+
* section: isHeader1(entry.text) ? entry.text.replace(/#+\s+/, '') : undefined,
|
|
1264
|
+
* subSection: isHeader2(entry.text) ? entry.text.replace(/#+\s+/, '') : undefined
|
|
1265
|
+
* });
|
|
1266
|
+
*
|
|
1267
|
+
* results = utils.object.augmentInherit(source, inheritFn);
|
|
1268
|
+
* ```
|
|
1269
|
+
*
|
|
1270
|
+
* text |section |subSection
|
|
1271
|
+
* -- |-- |--
|
|
1272
|
+
* Overview |Overview |undefined
|
|
1273
|
+
* This entire list is a hierarchy of data. |Overview |undefined
|
|
1274
|
+
* Section A |Section A|undefined
|
|
1275
|
+
* This describes section A |Section A|undefined
|
|
1276
|
+
* SubSection 1 |Section A|SubSection 1
|
|
1277
|
+
* With a subsection belonging to Section A |Section A|SubSection 1
|
|
1278
|
+
* Section B |Section B|undefined
|
|
1279
|
+
* With an entirely unrelated section B, that is sibling to Section A|Section B|undefined
|
|
1280
|
+
* SubSection 1 |Section B|SubSection 1
|
|
1281
|
+
* And another subsection 1, but this time related to Section B. |Section B|SubSection 1
|
|
1282
|
+
*
|
|
1283
|
+
* So we pass the collection of results as the source, and an augment function,
|
|
1284
|
+
* that returns the heading 1 value - that is then kept until the next heading 1.
|
|
1285
|
+
* (Similar for subSection using heading 2)
|
|
1286
|
+
*
|
|
1287
|
+
* @param {Object[]} source - the collection of objects to check and augment.
|
|
1288
|
+
* @param {Function} augmentFn - function accepting each entry, and returning the properties to "inherit" <br /> or a property with a value of undefined - if it should not be preserved.
|
|
1289
|
+
* @returns {Object[]} - new version of the source objects with the properties applied.
|
|
1290
|
+
*
|
|
1291
|
+
* @see {@link module:object.augment|augment()} - Applies properties to an object similar to Map
|
|
1292
|
+
*/
|
|
1293
|
+
module.exports.augmentInherit = function augmentInherit(source, augmentFn) {
|
|
1294
|
+
const signature = 'augmentInherit(source, augmentFn)';
|
|
1295
|
+
if (!Array.isArray(source)) {
|
|
1296
|
+
throw new Error(`${signature}: source must be an array`);
|
|
1297
|
+
} else if (typeof augmentFn !== 'function') {
|
|
1298
|
+
throw new Error(`${signature}: augmentFn must be a function of signature: (entry, lastValue) => obj`);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
let keys;
|
|
1302
|
+
|
|
1303
|
+
let lastValue = {};
|
|
1304
|
+
return source.map((entry, index) => {
|
|
1305
|
+
const fnResult = augmentFn(entry, lastValue);
|
|
1306
|
+
|
|
1307
|
+
//-- ignore all values that are undefined
|
|
1308
|
+
const newValue = { ...lastValue };
|
|
1309
|
+
let isFlipped = false;
|
|
1310
|
+
keys = Object.keys(fnResult || {});
|
|
1311
|
+
keys.forEach((key) => {
|
|
1312
|
+
if (isFlipped) {
|
|
1313
|
+
newValue[key] = undefined;
|
|
1314
|
+
} else if (fnResult[key] !== undefined) {
|
|
1315
|
+
newValue[key] = fnResult[key];
|
|
1316
|
+
isFlipped = true;
|
|
1317
|
+
}
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
// console.log(`index:${index}: entry:${JSON.stringify(entry)}, newValue:${JSON.stringify(newValue)}, lastValue:${JSON.stringify(lastValue)}`)
|
|
1321
|
+
const result = ({ ...entry, ...newValue });
|
|
1322
|
+
lastValue = newValue;
|
|
1323
|
+
return result;
|
|
1324
|
+
});
|
|
1325
|
+
};
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Unites the properties of two collections of objects.
|
|
1329
|
+
*
|
|
1330
|
+
* For example:
|
|
1331
|
+
*
|
|
1332
|
+
* ```
|
|
1333
|
+
* source1 = [
|
|
1334
|
+
* { first: 'john' },
|
|
1335
|
+
* { first: 'jane' }
|
|
1336
|
+
* ];
|
|
1337
|
+
* source2 = [
|
|
1338
|
+
* { last: 'doe' },
|
|
1339
|
+
* { last: 'dough' }
|
|
1340
|
+
* ];
|
|
1341
|
+
* utils.object.union(source1, source2);
|
|
1342
|
+
* // [{ first: 'john', last: 'doe' },
|
|
1343
|
+
* // { first: 'jane', last: 'dough' }];
|
|
1344
|
+
* ```
|
|
1345
|
+
*
|
|
1346
|
+
* Note that you can also pass a single object, to have it union to multiple.
|
|
1347
|
+
*
|
|
1348
|
+
* ```
|
|
1349
|
+
* source1 = [
|
|
1350
|
+
* { first: 'john' },
|
|
1351
|
+
* { first: 'jane' }
|
|
1352
|
+
* ];
|
|
1353
|
+
* //-- same object to be applied to all
|
|
1354
|
+
* source2 = { last: 'doe' };
|
|
1355
|
+
*
|
|
1356
|
+
* utils.object.union(source1, source2);
|
|
1357
|
+
* // [{ first: 'john', last: 'doe' },
|
|
1358
|
+
* // { first: 'jane', last: 'doe' }];
|
|
1359
|
+
* ```
|
|
1360
|
+
*
|
|
1361
|
+
* @param {Object[]|Object} source1 - object or array of objects to union
|
|
1362
|
+
* @param {Object[]|Object} source2 - object or array of objects to union
|
|
1363
|
+
* @returns {Object[]} - collection of objects merging the values between the two sources
|
|
1364
|
+
*
|
|
1365
|
+
* @see {@link module:object.join|join} - to instead join based on a value instead of index
|
|
1366
|
+
* @see {@link module:object.filterObjectProperties|filterObjectProperties} - to remove properties from collection of objects.
|
|
1367
|
+
*/
|
|
1368
|
+
module.exports.union = function union(source1, source2) {
|
|
1369
|
+
const signature = 'union(source1:object[], source2:object[])';
|
|
1370
|
+
|
|
1371
|
+
let s1Iterator;
|
|
1372
|
+
let s1Entry;
|
|
1373
|
+
let s1Length;
|
|
1374
|
+
let s2Iterator;
|
|
1375
|
+
let s2Entry;
|
|
1376
|
+
let s2Length;
|
|
1377
|
+
|
|
1378
|
+
if (Array.isArray(source1)) {
|
|
1379
|
+
s1Iterator = source1.entries();
|
|
1380
|
+
s1Length = source1.length;
|
|
1381
|
+
} else if (typeof source1 === 'object') {
|
|
1382
|
+
s1Iterator = ({ next: () => ({ done: false, value: [0, source1] }) });
|
|
1383
|
+
s1Length = 1;
|
|
1384
|
+
} else {
|
|
1385
|
+
throw new Error(`${signature}: source1 must be a collection of objects, or a single object`);
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
if (Array.isArray(source2)) {
|
|
1389
|
+
s2Iterator = source2.entries();
|
|
1390
|
+
s2Length = source2.length;
|
|
1391
|
+
} else if (typeof source2 === 'object') {
|
|
1392
|
+
s2Iterator = ({ next: () => ({ done: false, value: [0, source2] }) });
|
|
1393
|
+
s2Length = 1;
|
|
1394
|
+
} else {
|
|
1395
|
+
throw new Error(`${signature}: source2 must be a collection of objects, or a single object`);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
const len = Math.max(s1Length, s2Length);
|
|
1399
|
+
const results = new Array(len);
|
|
1400
|
+
|
|
1401
|
+
for (let i = 0; i < len; i += 1) {
|
|
1402
|
+
s1Entry = s1Iterator.next();
|
|
1403
|
+
s1Entry = s1Entry.done ? {} : s1Entry.value[1];
|
|
1404
|
+
|
|
1405
|
+
s2Entry = s2Iterator.next();
|
|
1406
|
+
s2Entry = s2Entry.done ? {} : s2Entry.value[1];
|
|
1407
|
+
|
|
1408
|
+
//console.log(`s1Entry: ${JSON.stringify(s1Entry)}, s2Entry: ${JSON.stringify(s2Entry)}`);
|
|
1409
|
+
results[i] = { ...s1Entry, ...s2Entry };
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
return results;
|
|
1413
|
+
};
|
package/src/random.js
CHANGED
|
@@ -17,6 +17,8 @@ const SimplexModule = require('./random_simplex');
|
|
|
17
17
|
* * Simplex Noise
|
|
18
18
|
* * {@link module:random.simplexGenerator|simplexGenerator(seed)} - Number generator between -1 and 1 given an x/y/z coordinate
|
|
19
19
|
*
|
|
20
|
+
* If leveraging
|
|
21
|
+
*
|
|
20
22
|
* While generating a simple number between two values is common, it is very important - and useful in generating fake data.
|
|
21
23
|
*
|
|
22
24
|
* ```
|
|
@@ -39,6 +41,60 @@ const SimplexModule = require('./random_simplex');
|
|
|
39
41
|
*
|
|
40
42
|
* The possibilities are endless.
|
|
41
43
|
*
|
|
44
|
+
* <hr />
|
|
45
|
+
*
|
|
46
|
+
* <b>This library is meant to provide simple use cases.</b>
|
|
47
|
+
* Please use Standard libraries
|
|
48
|
+
* - like [d3-random](https://observablehq.com/@d3/d3-random) for additional alternatives
|
|
49
|
+
*
|
|
50
|
+
* see {@link module:format.mapArrayDomain|format.mapArrayDomain} - as it projects a value
|
|
51
|
+
* from between a range a value, and picks the corresponding value from an array.
|
|
52
|
+
*
|
|
53
|
+
* For example:
|
|
54
|
+
*
|
|
55
|
+
* ```
|
|
56
|
+
* require('esm-hook');
|
|
57
|
+
* d3 = require('d3');
|
|
58
|
+
*
|
|
59
|
+
* //-- create a number generator using Normal / Gaussian distribution
|
|
60
|
+
* randomGenerator = d3.randomNormal(
|
|
61
|
+
* 0.5, // mu - or centerline
|
|
62
|
+
* 0.1 // sigma - or spread of values
|
|
63
|
+
* );
|
|
64
|
+
*
|
|
65
|
+
* randomValue = randomGenerator();
|
|
66
|
+
* // randomValue - 0.4
|
|
67
|
+
*
|
|
68
|
+
* randomDataset = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'];
|
|
69
|
+
*
|
|
70
|
+
* //-- create an array of 3 items, each with the results from randomGenerator
|
|
71
|
+
* results = utils.array.size(3, () => randomGenerator());
|
|
72
|
+
* // [ 0.6235937672428706, 0.4991359903898883, 0.4279365561645624 ]
|
|
73
|
+
*
|
|
74
|
+
* //-- map those values to the randomDataset
|
|
75
|
+
* results.map(val => ({ pick: utils.format.mapArrayDomain(val, randomDataset) }));
|
|
76
|
+
* // [ { pick: 'g' }, { pick: 'e' }, { pick: 'e' } ]
|
|
77
|
+
*
|
|
78
|
+
* //-- group them by the pick field
|
|
79
|
+
* //-- then add a new property called count - using the # of records with the same value
|
|
80
|
+
* groupedResults = utils.group.by(resultPicks, 'pick')
|
|
81
|
+
* .reduce((list) => ({ count: list.length }));
|
|
82
|
+
* // [ { pick: 'g', count: 1 }, { pick: 'e', count: 2 } ]
|
|
83
|
+
*
|
|
84
|
+
* //-- make a bar chart (only with 10k results)
|
|
85
|
+
* utils.vega.embed((vl) => {
|
|
86
|
+
* return vl
|
|
87
|
+
* .markBar()
|
|
88
|
+
* .title('Distribution')
|
|
89
|
+
* .data(groupedResults)
|
|
90
|
+
* .encode(
|
|
91
|
+
* vl.x().fieldN('value'),
|
|
92
|
+
* vl.y().fieldQ('count').scale({type: 'log'})
|
|
93
|
+
* );
|
|
94
|
+
* });
|
|
95
|
+
* ```
|
|
96
|
+
* 
|
|
97
|
+
*
|
|
42
98
|
* @module random
|
|
43
99
|
*/
|
|
44
100
|
const RandomUtil = module.exports;
|
|
@@ -65,13 +121,14 @@ module.exports.getSeed = function getSeed() {
|
|
|
65
121
|
};
|
|
66
122
|
|
|
67
123
|
/**
|
|
68
|
-
* Generate a random integer
|
|
124
|
+
* Generate a random integer (with uniform distribution)
|
|
69
125
|
* @param {Number} [min = 0] - Minimum integer (inclusive) that could be generated
|
|
70
126
|
* @param {Number} [max = 100] - Maximum integer (inclusive) number that could be generated
|
|
71
127
|
* @returns {Number} - number between (and including) min and max
|
|
72
128
|
* @example
|
|
73
129
|
* utils.random.randomInteger(0, 100) // 40
|
|
74
130
|
* utils.random.randomInteger(0, 100) // 96
|
|
131
|
+
* @see [d3-random](https://observablehq.com/@d3/d3-random) for additional alternatives
|
|
75
132
|
*/
|
|
76
133
|
module.exports.randomInteger = function randomInteger(min = 0, max = 100) {
|
|
77
134
|
//-- can only occur if the user exports to htmlScript
|
|
@@ -84,11 +141,12 @@ module.exports.randomInteger = function randomInteger(min = 0, max = 100) {
|
|
|
84
141
|
};
|
|
85
142
|
|
|
86
143
|
/**
|
|
87
|
-
* Generates a random floating point number
|
|
144
|
+
* Generates a random floating point number (with uniform distribution)
|
|
88
145
|
* @param {Number} [min = 0] - Minimum float (inclusive) that could be generated
|
|
89
146
|
* @param {Number} [max = 100] - Maximum float (inclusive) number that the value will be less than
|
|
90
147
|
* @returns {Number} - number between (and including) min and max
|
|
91
148
|
* utils.random.randomInteger(0, 1) // 0.224223
|
|
149
|
+
* @see [d3-random](https://observablehq.com/@d3/d3-random) for additional alternatives
|
|
92
150
|
*/
|
|
93
151
|
module.exports.random = function random(min = 0, max = 1) {
|
|
94
152
|
//-- can only occur if the user exports to htmlScript
|
|
@@ -102,11 +160,12 @@ module.exports.random = function random(min = 0, max = 1) {
|
|
|
102
160
|
};
|
|
103
161
|
|
|
104
162
|
/**
|
|
105
|
-
* Picks a random value from an array of values
|
|
163
|
+
* Picks a random value from an array of values (with uniform distribution)
|
|
106
164
|
* @param {Array} targetArray - Array of values to pick from
|
|
107
165
|
* @returns {any} - one of the values picked at random from the target array
|
|
108
166
|
* @example
|
|
109
167
|
* utils.random.pickRandom(['apple', 'orange', 'pear']); // 'pear'
|
|
168
|
+
* @see [d3-random](https://observablehq.com/@d3/d3-random) for additional alternatives
|
|
110
169
|
*/
|
|
111
170
|
module.exports.pickRandom = function pickRandom(targetArray) {
|
|
112
171
|
if (!targetArray || !Array.isArray(targetArray)) {
|