jupyter-ijavascript-utils 1.33.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 +2 -0
- package/Dockerfile +1 -1
- package/README.md +2 -0
- package/package.json +1 -1
- package/src/aggregate.js +1 -0
- package/src/format.js +107 -0
- package/src/object.js +96 -0
- package/src/random.js +62 -3
package/DOCS.md
CHANGED
|
@@ -75,6 +75,8 @@ 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.
|
|
78
80
|
* 1.33 - Object.augmentInherit and Object.union
|
|
79
81
|
* 1.32 - Array.indexify to identify sections within a 1d array into a hierarchy.
|
|
80
82
|
* 1.31 - harden Array.transpose for arrays with nulls, and Table.generateTSV
|
package/Dockerfile
CHANGED
package/README.md
CHANGED
|
@@ -55,6 +55,8 @@ 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.
|
|
58
60
|
* 1.33 - Object.augmentInherit and Object.union
|
|
59
61
|
* 1.32 - Array.indexify to identify sections within a 1d array into a hierarchy.
|
|
60
62
|
* 1.31 - harden Array.transpose for arrays with nulls, and Table.generateTSV
|
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/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
|
@@ -31,6 +31,8 @@ const FormatUtils = require('./format');
|
|
|
31
31
|
* * {@link module:object.join|join(array, index, map, fn)} - join a collection against a map by a given index
|
|
32
32
|
* * {@link module:object.joinProperties|join(array, index, map, ...fields)} - join a collection, and copy properties over from the mapped object.
|
|
33
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.
|
|
34
36
|
* * Rename properties
|
|
35
37
|
* * {@link module:object.cleanProperties|cleanProperties()} - correct inaccessible property names in a list of objects - in place
|
|
36
38
|
* * * {@link module:object.cleanProperties2|cleanProperties2()} - correct inaccessible property names in a list of objects - on a cloned list
|
|
@@ -536,6 +538,100 @@ module.exports.fetchObjectProperties = function fetchObjectProperties(list, prop
|
|
|
536
538
|
});
|
|
537
539
|
};
|
|
538
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
|
+
|
|
539
635
|
/**
|
|
540
636
|
* Accesses a property using a string
|
|
541
637
|
* @param {Object} obj - object to access the properties on
|
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)) {
|