jupyter-ijavascript-utils 1.9.2 → 1.11.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
@@ -28,6 +28,8 @@ See the [#Installation section for requirements and installation](#install)
28
28
 
29
29
  ## What's New
30
30
 
31
+ * 1.11 - provide {@link module:aggregate.topValues|topValues} (like top 5, bottom 3)
32
+ * 1.10 - provide {@link module:aggregate.percentile|percentile} (like 50th percentile) aggregates
31
33
  * 1.9 - allow {@link TableGenerator#transpose|transposing results} on TableGenerator.
32
34
  * 1.8 - add in What can I Do tutorial, and {@link module:object.join|object.join methods}
33
35
  * 1.7 - revamp of `animation` method for ijs.htmlScript
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  </a>
8
8
  <img src="https://img.shields.io/badge/Coverage-98-green" />
9
9
  <a href="https://github.com/paulroth3d/jupyter-ijavascript-utils" alt="npm">
10
- <img src="https://img.shields.io/badge/npm-%5E1.9.2-red" />
10
+ <img src="https://img.shields.io/badge/npm-%5E1.11.0-red" />
11
11
  </a>
12
12
  </p>
13
13
 
@@ -21,6 +21,8 @@ See documentation at: [https://jupyter-ijavascript-utils.onrender.com/](https://
21
21
 
22
22
  # What's New
23
23
 
24
+ * 1.11 - provide topValues (like top 5, bottom 3)
25
+ * 1.10 - provide percentile (like 50th percentile) aggregates
24
26
  * 1.9 - allow transposing results on TableGenerator.
25
27
  * 1.8 - add in What can I Do tutorial, and object.join methods
26
28
  * 1.7 - revamp of `animation` method to htmlScript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-ijavascript-utils",
3
- "version": "1.9.2",
3
+ "version": "1.11.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",
@@ -27,7 +27,6 @@
27
27
  "jupyter"
28
28
  ],
29
29
  "author": "Paul Roth",
30
- "license": "MIT",
31
30
  "devDependencies": {
32
31
  "babel-eslint": "^10.1.0",
33
32
  "docdash": "^1.2.0",
@@ -44,6 +43,7 @@
44
43
  "fs-extra": "^10.0.1",
45
44
  "generate-schema": "^2.6.0",
46
45
  "node-fetch": "^2.6.5",
46
+ "percentile": "^1.6.0",
47
47
  "pino": "^6.12.0",
48
48
  "pino-pretty": "^5.1.2",
49
49
  "plantuml-encoder": "^1.4.0",
@@ -13,7 +13,7 @@
13
13
 
14
14
  const { printValue } = require('./format');
15
15
 
16
- const generateRange = (length, defaultValue) => new Array(length).fill(defaultValue);
16
+ // const generateRange = (length, defaultValue) => new Array(length).fill(defaultValue);
17
17
 
18
18
  const IJSUtils = require('./ijs');
19
19
 
@@ -82,6 +82,7 @@ const { createSort } = require('./array');
82
82
  * * {@link TableGenerator#styleHeader|styleHeader(string)} - css styles for the header row
83
83
  * * {@link TableGenerator#styleRow|styleRow(fn)} - Function to style rows
84
84
  * * {@link TableGenerator#styleCell|styleCell(fn)} - Function to style cells
85
+ * * {@link TableGenerator#border|border(string)} - Apply a border to the table data cells
85
86
  * * generate output
86
87
  * * {@link TableGenerator#generateHTML|generateHTML()} - returns html table with the results
87
88
  * * {@link TableGenerator#generateMarkdown|generateMarkdown()} - returns markdown with the results
@@ -105,6 +106,12 @@ class TableGenerator {
105
106
  */
106
107
  #augmentFn = null;
107
108
 
109
+ /**
110
+ * Border CSS to also apply to the cells
111
+ * @type {String}
112
+ */
113
+ #borderCSS = ''; // 'solid 1px #AAA';
114
+
108
115
  /**
109
116
  * Optional array of exclusive columns to show based on the properties of each row\n
110
117
  * ex: ['Miles_per_Gallon', 'Name', 'Cylinders', etc]
@@ -235,6 +242,7 @@ class TableGenerator {
235
242
  reset() {
236
243
  this.#data = [];
237
244
  this.#augmentFn = null;
245
+ this.#borderCSS = '';
238
246
  this.#columns = null;
239
247
  this.#columnsToExclude = [];
240
248
  this.#fetch = null;
@@ -338,6 +346,62 @@ class TableGenerator {
338
346
  return this;
339
347
  }
340
348
 
349
+ /**
350
+ * Convenience function to set an a border on the Data Cells.
351
+ *
352
+ * This only applies when {@link TableGenerator#render|rendering HTML}
353
+ * or {@link TableGenerator#generateHTML|generating HTML}
354
+ *
355
+ * As this adds additional CSS, the styling applied:
356
+ * * {@link TableGenerator#styleTable|to the whole table}
357
+ * * or {@link TableGenerator#styleRow|to the rows}
358
+ * * or {@link TableGenerator#styleCell|to the data cells} will be affected
359
+ *
360
+ * For example:
361
+ *
362
+ * ```
363
+ * sourceData = [{id: 1, temp_F:98}, {id: 2, temp_F:99}, {id: 3, temp_F:100}];
364
+ *
365
+ * new utils.TableGenerator(sourceData)
366
+ * .border('1px solid #aaa')
367
+ * .render();
368
+ * ```
369
+ *
370
+ * <table cellspacing="0px" >
371
+ * <tr >
372
+ * <th>id</th>
373
+ * <th>temp_F</th>
374
+ * </tr>
375
+ * <tr >
376
+ * <td style=" border: 1px solid #aaa">1</td>
377
+ * <td style=" border: 1px solid #aaa">98</td>
378
+ * </tr>
379
+ * <tr >
380
+ * <td style=" border: 1px solid #aaa">2</td>
381
+ * <td style=" border: 1px solid #aaa">99</td>
382
+ * </tr>
383
+ * <tr >
384
+ * <td style=" border: 1px solid #aaa">3</td>
385
+ * <td style=" border: 1px solid #aaa">100</td>
386
+ * </tr>
387
+ * </table>
388
+ *
389
+ * @param {String | Boolean} borderCSS - CSS String to additionally apply HTML TD elements
390
+ */
391
+ border(borderCSS) {
392
+ let cleanCSS = '';
393
+
394
+ if (borderCSS === true) {
395
+ cleanCSS = 'border: 1px solid #AAA';
396
+ } else if (borderCSS) {
397
+ cleanCSS = `border: ${borderCSS}`;
398
+ }
399
+
400
+ this.#borderCSS = cleanCSS;
401
+
402
+ return this;
403
+ }
404
+
341
405
  /**
342
406
  * Applies an optional set of columns / properties to render
343
407
  *
@@ -452,20 +516,64 @@ class TableGenerator {
452
516
  *
453
517
  * (This is an alternate to {@link formatterFn} or simple `.map()` call on the source data)
454
518
  *
519
+ * **NOTE: Only matching properties on the formatter object are changed - all others are left alone.**
520
+ *
455
521
  * For example:
456
522
  *
457
523
  * ```
458
- * data = [{temp: 98, type: 'F'}, {temp: 99, type: 'F'}, {temp: 100, type: 'F'}];
524
+ * data = [
525
+ * {station: 'A', temp: 98, type: 'F', descr: '0123'},
526
+ * {station: 'A', temp: 99, type: 'F', descr: '0123456'},
527
+ * {station: 'A', temp: 100, type: 'F', descr: '0123456789'}
528
+ * ];
459
529
  *
460
530
  * //-- simple example where the temp property is converted, and type property overwritten
461
531
  * new TableGenerator(data)
462
532
  * .formatter({
533
+ * //-- property 'station' not mentioned, so no change
534
+ *
535
+ * //-- convert temperature to celsius
463
536
  * temp: (value) => (value - 32) * 0.5556,
464
- * type: (value) => 'C'
465
- * })...
537
+ * //-- overwrite type from 'F' to 'C'
538
+ * type: 'C',
539
+ * //-- ellipsify to shorten the description string, if longer than 8 characters
540
+ * descr: (str) => utils.format.ellipsify(str, 8)
541
+ * }).renderMarkdown()
466
542
  * ```
467
543
  *
468
- * Only properties on the object are checked - all others are left alone.
544
+ * station|temp |type|descr
545
+ * -- |-- |-- |--
546
+ * A |36.67 |F |0123
547
+ * A |37.225|F |0123456
548
+ * A |37.781|F |01234567…
549
+ *
550
+ * Note, due to frequent requests, simple datatype conversions can be requested.
551
+ *
552
+ * Only ('String', 'Number', and 'Boolean') are supported
553
+ *
554
+ * ```
555
+ * data = [
556
+ * { propA: ' 8009', propB: 8009, isBoolean: 0},
557
+ * { propA: ' 92032', propB: 92032, isBoolean: 1},
558
+ * { propA: ' 234234', propB: 234234, isBoolean: 1},
559
+ * ];
560
+ *
561
+ * new utils.TableGenerator(data)
562
+ * .formatter({
563
+ * //-- convert Prop A to Number - so render with Locale Number Formatting
564
+ * propA: 'number',
565
+ * //-- conver PropB to String - so render without Locale Number Formatting
566
+ * propB: 'string',
567
+ * //-- render 'True' or 'False'
568
+ * isBoolean: 'boolean'
569
+ * }).renderMarkdown();
570
+ * ```
571
+ *
572
+ * propA|propB|isBoolean
573
+ * -- |-- |--
574
+ * 8,009 |8009 |false
575
+ * 92,032 |92032 |true
576
+ * 234,234 |234234 |true
469
577
  *
470
578
  * @param {Object} obj - object with properties storing arrow functions
471
579
  * @param {Function} obj.PropertyToTranslate - (value) => result
@@ -480,10 +588,25 @@ class TableGenerator {
480
588
 
481
589
  const fnMap = new Map();
482
590
  Object.getOwnPropertyNames(obj).forEach((key) => {
483
- if ((typeof obj[key]) !== 'function') {
484
- throw (Error(`Formatter properties must be functions. [${key}]`));
591
+ if ((typeof obj[key]) === 'string') {
592
+ let fn;
593
+ const str = obj[key].toLowerCase();
594
+ if (str === 'string') {
595
+ fn = (val) => String(val);
596
+ } else if (str === 'number') {
597
+ fn = (val) => Number(val);
598
+ } else if (str === 'boolean') {
599
+ fn = (val) => val ? 'true' : 'false';
600
+ } else {
601
+ throw Error(`TableGenerator.format: property ${key} formatter of ${str} is unsupported. Only (String, Number, Boolean) are supported`);
602
+ }
603
+ fnMap.set(key, fn);
604
+ } else {
605
+ if ((typeof obj[key]) !== 'function') {
606
+ throw (Error(`Formatter properties must be functions. [${key}]`));
607
+ }
608
+ fnMap.set(key, obj[key]);
485
609
  }
486
- fnMap.set(key, obj[key]);
487
610
  });
488
611
 
489
612
  this.#formatterFn = ({ value, property }) => fnMap.has(property)
@@ -928,6 +1051,7 @@ class TableGenerator {
928
1051
  const styleRowFn = this.#styleRow;
929
1052
  const styleCellFn = this.#styleCell;
930
1053
  const printOptions = this.#printOptions;
1054
+ const borderCSS = this.#borderCSS;
931
1055
 
932
1056
  const cleanFn = printValue;
933
1057
 
@@ -948,16 +1072,17 @@ class TableGenerator {
948
1072
  + dataRow.map((value, columnIndex) => {
949
1073
  //-- note - the data is from the original dataset, not the results
950
1074
  const cellStyle = !styleCellFn
951
- ? null
1075
+ ? ''
952
1076
  : styleCellFn({ value, columnIndex, rowIndex, row: dataRow, record });
953
1077
  return '<td '
954
- + (!cellStyle ? '' : `style="${cellStyle}"`)
1078
+ + (borderCSS || cellStyle ? `style="${cellStyle} ${borderCSS}"` : '')
955
1079
  + `>${cleanFn(value, printOptions)}</td>`;
956
1080
  }).join('\n\t')
957
1081
  + '\n</tr>';
958
1082
  }).join('\n');
959
1083
 
960
1084
  const tableResults = '<table '
1085
+ + 'cellspacing="0px" '
961
1086
  + (!styleTable ? '' : `style="${styleTable}"`)
962
1087
  + '>'
963
1088
  + printHeader(results.headers, '')
package/src/aggregate.js CHANGED
@@ -1,5 +1,8 @@
1
1
  /* eslint-disable implicit-arrow-linebreak */
2
2
 
3
+ const Percentile = require('percentile');
4
+
5
+ const ArrayUtils = require('./array');
3
6
  const ObjectUtils = require('./object');
4
7
  const FormatUtils = require('./format');
5
8
 
@@ -13,6 +16,8 @@ const FormatUtils = require('./format');
13
16
  *
14
17
  * Types of methods:
15
18
  *
19
+ * * Select a single property
20
+ * * {@link module:aggregate.property|property()} - maps to a single property (often used with other libraries)
16
21
  * * Ranges of values
17
22
  * * {@link module:aggregate.extent|extent()} - returns the min and max of range
18
23
  * * {@link module:aggregate.min|min()} - returns the minimum value of the range
@@ -35,6 +40,19 @@ const FormatUtils = require('./format');
35
40
  * * {@link module:aggregate.sum|sum()} - sum of a collection
36
41
  * * Functional
37
42
  * * {@link module:aggregate.deferCollection|deferCollection(function, bindArg, bindArg, ...)} - bind a function with arguments
43
+ * * Percentile
44
+ * * {@link module:aggregate.percentile|percentile()} - determines the Nth percentile of a field or value
45
+ * * {@link module:aggregate.percentile_01|percentile_01()} - 1th percentile
46
+ * * {@link module:aggregate.percentile_05|percentile_05()} - 5th percentile
47
+ * * {@link module:aggregate.percentile_10|percentile_10()} - 10th percentile
48
+ * * {@link module:aggregate.percentile_25|percentile_25()} - 25th percentile
49
+ * * {@link module:aggregate.percentile_50|percentile_50()} - 50th percentile
50
+ * * {@link module:aggregate.percentile_75|percentile_75()} - 75th percentile
51
+ * * {@link module:aggregate.percentile_90|percentile_90()} - 90th percentile
52
+ * * {@link module:aggregate.percentile_95|percentile_95()} - 95th percentile
53
+ * * {@link module:aggregate.percentile_99|percentile_99()} - 99th percentile
54
+ * * Top Values
55
+ * * {@link module:aggregate.topValues|topValues} - top (or bottom) values from a list of objects or literals
38
56
  *
39
57
  * Please note, there is nothing special for these functions, such as working with {@link SourceMap#reduce|SourceMap.reduce()}
40
58
  *
@@ -225,6 +243,34 @@ module.exports = {};
225
243
  // eslint-disable-next-line no-unused-vars
226
244
  const AggregateUtils = module.exports;
227
245
 
246
+ /**
247
+ * Maps an array of values to a single property.
248
+ *
249
+ * For example:
250
+ *
251
+ * ```
252
+ * const data = [{ record: 'jobA', val: 1 }, { record: 'jobA', val: 2 },
253
+ * { record: 'jobA', val: 3 }, { record: 'jobA', val: 4 },
254
+ * { record: 'jobA', val: 5 }, { record: 'jobA', val: 6 },
255
+ * { record: 'jobA', val: 7 }, { record: 'jobA', val: 8 },
256
+ * { record: 'jobA', val: 9 }, { record: 'jobA', val: 10 }
257
+ * ];
258
+ *
259
+ * utils.object.propertyFromList(data, 'val')
260
+ * //-- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
261
+ *
262
+ * utils.object.propertyFromList(data, (r) => r.val);
263
+ * //-- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
264
+ * ```
265
+ *
266
+ * @param {Object[]} objectArray - Array of Objects to be mapped to a single property / value
267
+ * @param {Function | String} propertyOrFn - Name of the property or Function to return a value
268
+ * @returns {Array} - Array of values
269
+ */
270
+ module.exports.property = function propertyFromList(objectArray, propertyOrFn) {
271
+ return ObjectUtils.propertyFromList(objectArray, propertyOrFn);
272
+ };
273
+
228
274
  /**
229
275
  * Converts an aggregate function to two functions -
230
276
  * one that takes all arguments except the collection
@@ -662,3 +708,240 @@ module.exports.isUnique = function isUnique(collection, accessor) {
662
708
  });
663
709
  return duplicateValue === undefined;
664
710
  };
711
+
712
+ /**
713
+ * Returns a given percentile from a list of objects.
714
+ *
715
+ * **Note: this simply aggregates the values and passes to the [Percentile NPM Package](https://www.npmjs.com/package/percentile)**
716
+ *
717
+ * @param {Object[]} collection - collection of objects
718
+ * @param {Function | String} accessor - function to access the value, string property or null
719
+ * @param {Number} pct - Percentile (either .5 or 50)
720
+ * @returns {Number} - the pct percentile of a property within the collection
721
+ * @example
722
+ * const data = [{ record: 'jobA', val: 1 }, { record: 'jobA', val: 2 },
723
+ * { record: 'jobA', val: 3 }, { record: 'jobA', val: 4 },
724
+ * { record: 'jobA', val: 5 }, { record: 'jobA', val: 6 },
725
+ * { record: 'jobA', val: 7 }, { record: 'jobA', val: 8 },
726
+ * { record: 'jobA', val: 9 }, { record: 'jobA', val: 10 }
727
+ * ];
728
+ *
729
+ * utils.aggregate.percentile(data, 'val', 50) //-- returns 5
730
+ * utils.aggregate.percentile(data, (r) => r.val, 70) //-- returns 7
731
+ */
732
+ module.exports.percentile = function percentile(collection, accessor, pct) {
733
+ const values = ObjectUtils.propertyFromList(collection, accessor);
734
+ const cleanPercentile = pct > 0 && pct < 1
735
+ ? pct * 100
736
+ : pct;
737
+ return Percentile(cleanPercentile, values);
738
+ };
739
+
740
+ /**
741
+ * Returns a hard coded percentage
742
+ *
743
+ * {@link module:aggregate.percentage|See Percentage for more detail}
744
+ *
745
+ * @param {Object[]} collection - collection of objects
746
+ * @param {Function | String} accessor - function to access the value, string property or null
747
+ * @returns {Number} - the percentile of a property within the collection
748
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
749
+ */
750
+ module.exports.percentile_01 = function percentile(collection, accessor) {
751
+ return AggregateUtils.percentile(collection, accessor, 1);
752
+ };
753
+
754
+ /**
755
+ * Returns a hard coded percentage
756
+ *
757
+ * {@link module:aggregate.percentage|See Percentage for more detail}
758
+ *
759
+ * @param {Object[]} collection - collection of objects
760
+ * @param {Function | String} accessor - function to access the value, string property or null
761
+ * @returns {Number} - the percentile of a property within the collection
762
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
763
+ */
764
+ module.exports.percentile_05 = function percentile(collection, accessor) {
765
+ return AggregateUtils.percentile(collection, accessor, 5);
766
+ };
767
+
768
+ /**
769
+ * Returns a hard coded percentage
770
+ *
771
+ * {@link module:aggregate.percentage|See Percentage for more detail}
772
+ *
773
+ * @param {Object[]} collection - collection of objects
774
+ * @param {Function | String} accessor - function to access the value, string property or null
775
+ * @returns {Number} - the percentile of a property within the collection
776
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
777
+ */
778
+ module.exports.percentile_10 = function percentile(collection, accessor) {
779
+ return AggregateUtils.percentile(collection, accessor, 10);
780
+ };
781
+
782
+ /**
783
+ * Returns a hard coded percentage
784
+ *
785
+ * {@link module:aggregate.percentage|See Percentage for more detail}
786
+ *
787
+ * @param {Object[]} collection - collection of objects
788
+ * @param {Function | String} accessor - function to access the value, string property or null
789
+ * @returns {Number} - the percentile of a property within the collection
790
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
791
+ */
792
+ module.exports.percentile_25 = function percentile(collection, accessor) {
793
+ return AggregateUtils.percentile(collection, accessor, 25);
794
+ };
795
+
796
+ /**
797
+ * Returns a hard coded percentage
798
+ *
799
+ * {@link module:aggregate.percentage|See Percentage for more detail}
800
+ *
801
+ * @param {Object[]} collection - collection of objects
802
+ * @param {Function | String} accessor - function to access the value, string property or null
803
+ * @returns {Number} - the percentile of a property within the collection
804
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
805
+ */
806
+ module.exports.percentile_50 = function percentile(collection, accessor) {
807
+ return AggregateUtils.percentile(collection, accessor, 50);
808
+ };
809
+
810
+ /**
811
+ * Returns a hard coded percentage
812
+ *
813
+ * {@link module:aggregate.percentage|See Percentage for more detail}
814
+ *
815
+ * @param {Object[]} collection - collection of objects
816
+ * @param {Function | String} accessor - function to access the value, string property or null
817
+ * @returns {Number} - the percentile of a property within the collection
818
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
819
+ */
820
+ module.exports.percentile_75 = function percentile(collection, accessor) {
821
+ return AggregateUtils.percentile(collection, accessor, 75);
822
+ };
823
+
824
+ /**
825
+ * Returns a hard coded percentage
826
+ *
827
+ * {@link module:aggregate.percentage|See Percentage for more detail}
828
+ *
829
+ * @param {Object[]} collection - collection of objects
830
+ * @param {Function | String} accessor - function to access the value, string property or null
831
+ * @returns {Number} - the percentile of a property within the collection
832
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
833
+ */
834
+ module.exports.percentile_90 = function percentile(collection, accessor) {
835
+ return AggregateUtils.percentile(collection, accessor, 90);
836
+ };
837
+
838
+ /**
839
+ * Returns a hard coded percentage
840
+ *
841
+ * {@link module:aggregate.percentage|See Percentage for more detail}
842
+ *
843
+ * @param {Object[]} collection - collection of objects
844
+ * @param {Function | String} accessor - function to access the value, string property or null
845
+ * @returns {Number} - the percentile of a property within the collection
846
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
847
+ */
848
+ module.exports.percentile_95 = function percentile(collection, accessor) {
849
+ return AggregateUtils.percentile(collection, accessor, 95);
850
+ };
851
+
852
+ /**
853
+ * Returns a hard coded percentage
854
+ *
855
+ * {@link module:aggregate.percentage|See Percentage for more detail}
856
+ *
857
+ * @param {Object[]} collection - collection of objects
858
+ * @param {Function | String} accessor - function to access the value, string property or null
859
+ * @returns {Number} - the percentile of a property within the collection
860
+ * @see {@link module:aggregate.percentile|percentile} - as this simply hard codes the percentage
861
+ */
862
+ module.exports.percentile_99 = function percentile(collection, accessor) {
863
+ return AggregateUtils.percentile(collection, accessor, 99);
864
+ };
865
+
866
+ /**
867
+ * Returns the Top N values from within a collection.
868
+ *
869
+ * For example, if we have a list of weather records,
870
+ * we can get the month with the greatest rain.
871
+ *
872
+ * **Note: this can also return the Bottom N values, if sorting in ascending order.
873
+ * ({@link module:array.createSort|see array.createSort() for more.})**
874
+ *
875
+ * ```
876
+ * collection = [
877
+ * { id: 0, month: '2021-Sep', precip: 2.68 },
878
+ * { id: 1, month: '2021-Aug', precip: 0.87 },
879
+ * { id: 2, month: '2021-Oct', precip: 5.31 },
880
+ * { id: 3, month: '2021-Nov', precip: 3.94 },
881
+ * { id: 4, month: '2021-Dec', precip: 4.13 },
882
+ * { id: 5, month: '2022-Jan', precip: 3.58 },
883
+ * { id: 6, month: '2022-Feb', precip: 3.62 },
884
+ * { id: 7, month: '2022-Mar', precip: 3.98 },
885
+ * { id: 8, month: '2022-Apr', precip: 2.56 }
886
+ * ];
887
+ *
888
+ * //-- We can get the top 3 months with the highest rainfall
889
+ * utils.aggregate.topValues(collection, 3, 'month', '-precip');
890
+ * // '2021-Oct', '2021-Dec', '2022-Mar'
891
+ *
892
+ * //-- Or the 3 most recent precipitation values:
893
+ * utils.aggregate.topValues(collection, 3, 'precip', '-id');
894
+ * // 2.56, 3.98, 3.62
895
+ *
896
+ * //-- Lowest Rainfall is simply sorting in ascending order
897
+ * utils.aggregate.topValues(collection, 5, 'month', 'precip');
898
+ * // 0.87, 2.56, 2.68, 3.58, 3.62
899
+ *
900
+ * //-- you can also combine values to make the values clearer, by passing a function
901
+ * const monthPrecip = function (record) => `${record.month} (${record.precip})`;
902
+ * utils.aggregate.topValues(collection, 3, monthPrecip, '-precip');
903
+ * // '2021-Oct (5.31)', '2021-Dec (4.13)', '2022-Mar (3.98)'
904
+ * ```
905
+ *
906
+ * Literal values are also supported
907
+ *
908
+ * ```
909
+ * collection = [ 2.68, 0.87, 5.31, 3.94, 4.13, 3.58, 3.62, 3.98, 2.56 ];
910
+ *
911
+ * //-- top 5 values
912
+ * utils.aggregate.topValues(collection, 5);
913
+ * utils.aggregate.topValues(collection, 5, null, '-')
914
+ * // [5.31, 4.13, 3.98, 3.94, 3.62]
915
+ *
916
+ * //-- bottom 5 values
917
+ * utils.aggregate.topValues(collection, 5, null, '');
918
+ * //
919
+ * ```
920
+ *
921
+ * @param {Array} collection - Collection of values we want to get the top values from
922
+ * @param {Number} [numValues=5] - the number of values to return
923
+ * @param {string | Function} [fieldOrFn = null] - field of the object to use as the value, <br />
924
+ * Or the Function to generate the value, <br />
925
+ * Or null if the value is an array of Comparables (like Number)
926
+ * @param {...String} sortFields - field in the object to sort by,<br />
927
+ * Prefixed by '-' if it should be sorted in Descending order
928
+ * (ex: '-Year', 'Manufacturer')
929
+ * @returns {Array} - array of values
930
+ */
931
+ module.exports.topValues = function topValues(collection, numValues = 5, fieldOrFn, ...sortFields) {
932
+ let cleanCollection = collection || [];
933
+
934
+ //-- if no sort fields are provided, sort in descending order
935
+ const cleanSortFields = sortFields.length === 0
936
+ ? ['-']
937
+ : sortFields;
938
+
939
+ cleanCollection = cleanCollection.sort(
940
+ ArrayUtils.createSort(...cleanSortFields)
941
+ );
942
+
943
+ return AggregateUtils.property(
944
+ cleanCollection.slice(0, numValues),
945
+ fieldOrFn
946
+ );
947
+ };
package/src/format.js CHANGED
@@ -12,6 +12,7 @@
12
12
  * * Formatting Strings
13
13
  * * {@link module:format.capitalize|format.capitalize} - Capitalizes only the first character in the string (ex: 'John paul');
14
14
  * * {@link module:format.capitalizeAll|format.capitalizeAll} - Capitalizes all the words in a string (ex: 'John Paul')
15
+ * * {@link module:format.ellipsify|format.ellipsify} - Truncates a string if the length is 'too long'
15
16
  * * Formatting Time
16
17
  * * {@link module:format.millisecondDuration|format.millisecondDuration}
17
18
  * * Mapping Values
package/src/object.js CHANGED
@@ -22,6 +22,7 @@ const schemaGenerator = require('generate-schema');
22
22
  * * {@link module:object.fetchObjectProperties|fetchObjectProperties(object, string[])} - use dot notation to bring multiple child properties onto a parent
23
23
  * * {@link module:object.join|join(array, index, map, fn)} - join a collection against a map by a given index
24
24
  * * {@link module:object.joinProperties|join(array, index, map, ...fields)} - join a collection, and copy properties over from the mapped object.
25
+ * * {@link module:object.propertyFromList|propertyFromList(array, propertyName)} - fetches a specific property from all objects in a list
25
26
  * * Rename properties
26
27
  * * {@link module:object.cleanProperties|cleanProperties()} - correct inaccessible property names in a list of objects
27
28
  * * {@link module:object.cleanPropertyNames|cleanPropertyNames()} - create a translation of inaccessible names to accessible ones
@@ -638,6 +639,40 @@ module.exports.joinProperties = function join(objectArray, indexField, targetMap
638
639
  return ObjectUtils.join(objectArray, indexField, targetMap, joinFn);
639
640
  };
640
641
 
642
+ /**
643
+ * Maps an array of values to a single property.
644
+ *
645
+ * For example:
646
+ *
647
+ * ```
648
+ * const data = [{ record: 'jobA', val: 1 }, { record: 'jobA', val: 2 },
649
+ * { record: 'jobA', val: 3 }, { record: 'jobA', val: 4 },
650
+ * { record: 'jobA', val: 5 }, { record: 'jobA', val: 6 },
651
+ * { record: 'jobA', val: 7 }, { record: 'jobA', val: 8 },
652
+ * { record: 'jobA', val: 9 }, { record: 'jobA', val: 10 }
653
+ * ];
654
+ *
655
+ * utils.object.propertyFromList(data, 'val')
656
+ * //-- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
657
+ *
658
+ * utils.object.propertyFromList(data, (r) => r.val);
659
+ * //-- [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
660
+ * ```
661
+ *
662
+ * @param {Object[]} objectArray - Array of Objects to be mapped to a single property / value
663
+ * @param {Function | String} propertyOrFn - Name of the property or Function to return a value
664
+ * @returns {Array} - Array of values
665
+ */
666
+ module.exports.propertyFromList = function propertyFromList(objectArray, propertyOrFn) {
667
+ const cleanArray = Array.isArray(objectArray)
668
+ ? objectArray
669
+ : [];
670
+
671
+ const fn = ObjectUtils.evaluateFunctionOrProperty(propertyOrFn);
672
+
673
+ return cleanArray.map(fn);
674
+ };
675
+
641
676
  /**
642
677
  * Finds objects that do not have ALL the properties specified.
643
678
  *