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 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
@@ -1,3 +1,3 @@
1
1
  # syntax=docker/dockerfile:1
2
2
 
3
- FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.32.0
3
+ FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.35.0
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-ijavascript-utils",
3
- "version": "1.32.0",
3
+ "version": "1.35.0",
4
4
  "description": "Utilities for working with iJavaScript - a Jupyter Kernel",
5
5
  "homepage": "https://jupyter-ijavascript-utils.onrender.com/",
6
6
  "license": "MIT",
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
+ * ![Screenshot of the chart above](img/randomMap_normalDistribution.png)
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.objAssign|objAssign()} -
20
- * * {@link module:object.objAssignEntities|objAssignEntities()} -
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
+ * ![Screenshot of the chart above](img/randomMap_normalDistribution.png)
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)) {