jupyter-ijavascript-utils 1.57.0 → 1.59.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 +7 -0
- package/Dockerfile +1 -1
- package/README.md +7 -0
- package/package.json +1 -1
- package/src/date.js +1 -1
- package/src/file.js +11 -13
- package/src/ijs.js +127 -13
- package/src/object.js +167 -5
package/DOCS.md
CHANGED
|
@@ -74,6 +74,13 @@ Give it a try here:
|
|
|
74
74
|
[](https://mybinder.org/v2/gh/paulroth3d/jupyter-ijavascript-utils/main?labpath=example.ipynb)
|
|
75
75
|
|
|
76
76
|
## What's New
|
|
77
|
+
* 1.59 -
|
|
78
|
+
* #95 - give control with page breaks. So we can render text before the page break (like for headers) - or even get the html used and render it how we want. (ex: {@link module:ijs.generatePageBreakStylesHTML|ijs.printPageBreak})
|
|
79
|
+
* #96 - Support {@link module:ijs.internalComment|ijs.internalComment} in notebooks (meaning it can render in markdown, but can be disabled when preparing to print)
|
|
80
|
+
* 1.58 -
|
|
81
|
+
* #91 - add {@link module:object.splitIntoDatums|splitIntoDatums(collection, fieldsToSplitBy)} - to make working with datum libraries - like vega-lite - easier
|
|
82
|
+
* #92 - make using the cache easier for {@link module:file.useCache|file.useCache()} - as you can say to use the cache, and it will still work if the cache file does not exist yet.
|
|
83
|
+
* #93 / #94 - bug fixes
|
|
77
84
|
* 1.57 - #86 - include examples on binning based on time series {@link https://github.com/paulroth3d/jupyter-ijavascript-utils/issues/86|see more here}
|
|
78
85
|
* #87 - add in {@link module:file.fileExists|file.fileExists} to make things easier for new people getting started
|
|
79
86
|
* #89 - allow {@link module:array.resize|array.resize} to work with defaults, if zipping arrays of different sizes
|
package/Dockerfile
CHANGED
package/README.md
CHANGED
|
@@ -54,6 +54,13 @@ This is not intended to be the only way to accomplish many of these tasks, and a
|
|
|
54
54
|

|
|
55
55
|
|
|
56
56
|
# What's New
|
|
57
|
+
* 1.59 -
|
|
58
|
+
* #95 - give control with page breaks. So we can render text before the page break (like for headers) - or even get the html used and render it how we want.
|
|
59
|
+
* #96 - Support internal comments in notebooks (meaning it can render in markdown, but can be disabled when preparing to print)
|
|
60
|
+
* 1.58 -
|
|
61
|
+
* #91 - add object.splitIntoDatums - to make working with datum libraries - like vega-lite - easier
|
|
62
|
+
* #92 - make using the cache easier - as you can say to use the cache, and it will still work if the cache file does not exist yet.
|
|
63
|
+
* #93 / #94 - bug fixes
|
|
57
64
|
* 1.57 - #86 - include examples on binning based on time series
|
|
58
65
|
* #87 - add in fileExists to make things easier for new people getting started
|
|
59
66
|
* #89 - allow resizing arrays with defaults, if zipping arrays of different sizes
|
package/package.json
CHANGED
package/src/date.js
CHANGED
|
@@ -258,7 +258,7 @@ module.exports.getTimezoneEntry = function getTimezoneEntry(timezoneStr) {
|
|
|
258
258
|
}, {});
|
|
259
259
|
// const impactedDate = new Date(d.toLocaleString('en-US', { timezone: timezoneStr }));
|
|
260
260
|
const dateStr = `${dm.year}-${DateUtils.padTime(dm.month)}-${DateUtils.padTime(dm.day)}T${
|
|
261
|
-
DateUtils.padTime(dm.hour)}:${DateUtils.padTime(dm.minute)}:${DateUtils.padTime(dm.second)}.${
|
|
261
|
+
DateUtils.padTime(dm.hour % 24)}:${DateUtils.padTime(dm.minute)}:${DateUtils.padTime(dm.second)}.${
|
|
262
262
|
DateUtils.padTime(dm.fractionalSecond, 3)}`;
|
|
263
263
|
|
|
264
264
|
return dateStr;
|
package/src/file.js
CHANGED
|
@@ -99,7 +99,7 @@ const FileUtil = module.exports;
|
|
|
99
99
|
*
|
|
100
100
|
* @param {string} filePath - path of the file to load
|
|
101
101
|
* @param {Object} fsOptions - options to pass for fsRead (ex: { encoding: 'utf-8' })
|
|
102
|
-
* @param {Function} fsOptions.
|
|
102
|
+
* @param {Function} fsOptions.reviver - reviver to use when writing the JSON
|
|
103
103
|
* @param {String} fsOptions.encoding - the encoding to write the JSON out with
|
|
104
104
|
* @example
|
|
105
105
|
* const weather = [
|
|
@@ -122,11 +122,7 @@ const FileUtil = module.exports;
|
|
|
122
122
|
module.exports.readJSON = function readJSON(filePath, fsOptions = {}) {
|
|
123
123
|
const resolvedPath = path.resolve(filePath);
|
|
124
124
|
const optionsDefaults = { encoding: 'utf-8' };
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
//-- unfortunately we cannot pass the formatter in addition, it must replace
|
|
128
|
-
//-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
|
|
129
|
-
if (cleanedOptions.formatter) cleanedOptions = cleanedOptions.formatter;
|
|
125
|
+
const cleanedOptions = { ...optionsDefaults, ...fsOptions };
|
|
130
126
|
|
|
131
127
|
/** @type {string} */
|
|
132
128
|
let result;
|
|
@@ -231,6 +227,7 @@ module.exports.readFile = function readFile(filePath, fsOptions = {}) {
|
|
|
231
227
|
* @param {Boolean} fsOptions.append - if true, will append the text to the file
|
|
232
228
|
* @param {Boolean} fsOptions.prefix - string to add before writing the json, like an opening bracket '[' or comma ','
|
|
233
229
|
* @param {Boolean} fsOptions.prefix - string to add before writing the json, like a closing bracket ']'
|
|
230
|
+
* @param {Function} fsOptions.replacer - function to use when writing JSON passed to stringify
|
|
234
231
|
* @param {String} fsOptions.encoding - encoding to use when writing the file.
|
|
235
232
|
* @see {@link module:file.readJSON|readJSON(filePath, fsOptions)} - for reading
|
|
236
233
|
*/
|
|
@@ -241,9 +238,9 @@ module.exports.writeJSON = function writeJSON(filePath, contents, fsOptions = {}
|
|
|
241
238
|
const isAppend = cleanedOptions.append === true;
|
|
242
239
|
const prefix = cleanedOptions.prefix || '';
|
|
243
240
|
const suffix = cleanedOptions.suffix || '';
|
|
244
|
-
const
|
|
241
|
+
const replacer = cleanedOptions.replacer || null;
|
|
245
242
|
const spacing = cleanedOptions.spacing || 2;
|
|
246
|
-
const jsonContents = JSON.stringify(contents,
|
|
243
|
+
const jsonContents = JSON.stringify(contents, replacer, spacing);
|
|
247
244
|
|
|
248
245
|
// const resolvedPath = path.resolve(filePath);
|
|
249
246
|
try {
|
|
@@ -498,7 +495,7 @@ module.exports.cacheSerializer = (key, value) => {
|
|
|
498
495
|
*/
|
|
499
496
|
|
|
500
497
|
module.exports.cacheDeserializer = (key, value) => {
|
|
501
|
-
if (key && (key === 'date' || key.endsWith('_date'))) {
|
|
498
|
+
if (key && (key === 'date' || key.endsWith('_date') || key.endsWith('Date'))) {
|
|
502
499
|
return new Date(value);
|
|
503
500
|
}
|
|
504
501
|
return value;
|
|
@@ -561,17 +558,18 @@ module.exports.cacheDeserializer = (key, value) => {
|
|
|
561
558
|
module.exports.useCache = function useCache(shouldWrite, cachePath, cacheFile, expensiveFn, fsOptions = null) {
|
|
562
559
|
const ensureEndsWithSlash = (str) => str.endsWith('/') ? str : `${str}/`;
|
|
563
560
|
const cacheFilePath = `${ensureEndsWithSlash(cachePath)}${cacheFile}`;
|
|
561
|
+
|
|
562
|
+
const cacheExists = FileUtil.fileExists(cacheFilePath);
|
|
564
563
|
|
|
565
|
-
if (!shouldWrite) {
|
|
566
|
-
const cleanOptions = { ...fsOptions,
|
|
564
|
+
if (cacheExists && !shouldWrite) {
|
|
565
|
+
const cleanOptions = { ...fsOptions, reviver: FileUtil.cacheDeserializer };
|
|
567
566
|
const results = FileUtil.readJSON(cacheFilePath, cleanOptions);
|
|
568
567
|
return results;
|
|
569
568
|
}
|
|
570
569
|
|
|
571
570
|
const results = expensiveFn();
|
|
572
571
|
|
|
573
|
-
const cleanOptions =
|
|
574
|
-
|
|
572
|
+
const cleanOptions = fsOptions; // FileUtil.cacheSerializer not needed
|
|
575
573
|
FileUtil.writeJSON(cacheFilePath, results, cleanOptions);
|
|
576
574
|
|
|
577
575
|
return results;
|
package/src/ijs.js
CHANGED
|
@@ -26,9 +26,14 @@ require('./_types/global');
|
|
|
26
26
|
* * {@link module:ijs.markdown|ijs.markdown} - Render output as markdown
|
|
27
27
|
* * {@link module:ijs.htmlScript|ijs.htmlScript} - Leverage external libraries like D3, Leaflet, etc.
|
|
28
28
|
* * Printing
|
|
29
|
-
* * {@link module:ijs.
|
|
29
|
+
* * {@link module:ijs.clearOutput|ijs.clearOutput} - clears the output to declutter results (like importing libraries, or functions)
|
|
30
30
|
* * {@link module:ijs.initializePageBreaks|ijs.initializePageBreaks} - call at least once to allow pageBreaks when rendering PDFs
|
|
31
31
|
* * {@link module:ijs.printPageBreak|ijs.printPageBreak} - call to print a page break when rendering PDFs
|
|
32
|
+
* * {@link module:ijs.generatePageBreakStylesHTML|ijs.generatePageBreakStylesHTML} - generates the html used in to allow for pagebreaks
|
|
33
|
+
* (so you can render them as you'd like)
|
|
34
|
+
* * {@link module:ijs.generatePageBreakHTML|ijs.generatePageBreakHTML} - generates the html that uses the styles to render pagebreaks
|
|
35
|
+
* (so you can render them as you'd like)
|
|
36
|
+
* * {@link module:ijs.internalComment|ijs.internalComment} - render markdown, but be able to turn it off, prior to printing
|
|
32
37
|
* * using a cache for long running executions
|
|
33
38
|
* * {@link module:ijs.useCache|ijs.useCache()} - perform an expensive calculation and write to a cache, or read from the cache transparently
|
|
34
39
|
*
|
|
@@ -235,6 +240,8 @@ module.exports.detectIJS = function detectIJS() {
|
|
|
235
240
|
* 
|
|
236
241
|
*
|
|
237
242
|
* @param {String} markdownText - The markdown to be rendered
|
|
243
|
+
* @param {*} markdownText - the markdown text to render
|
|
244
|
+
* @param {Jupyter$$} [display] - the display to render the output to.
|
|
238
245
|
* @example
|
|
239
246
|
*
|
|
240
247
|
* utils.ijs.markdown(`# Overview
|
|
@@ -246,6 +253,33 @@ module.exports.markdown = function markdown(markdownText, display) {
|
|
|
246
253
|
displayToUse.mime({ 'text/markdown': markdownText });
|
|
247
254
|
};
|
|
248
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Capture internal comments that can render as markdown (including images)
|
|
258
|
+
* but be turned off - so they are not rendered in the final output
|
|
259
|
+
*
|
|
260
|
+
* ```
|
|
261
|
+
* utils.ijs.internalComment(true, ``);
|
|
262
|
+
* ```
|
|
263
|
+
*
|
|
264
|
+
* and then when preparing to ship, you don't include it
|
|
265
|
+
*
|
|
266
|
+
* ```
|
|
267
|
+
* printable=true
|
|
268
|
+
* utils.ijs.internalComment(!printable, ``);
|
|
269
|
+
* ```
|
|
270
|
+
*
|
|
271
|
+
* @param {Boolean} shouldRender - whether the comment should be rendered to the output
|
|
272
|
+
* @param {String} markdownText - the markdown text to render
|
|
273
|
+
* @param {Jupyter$$} [display] - the display to render the output to.
|
|
274
|
+
*/
|
|
275
|
+
module.exports.internalComment = function internalComment(shouldRender, markdownText, display) {
|
|
276
|
+
if (!shouldRender) {
|
|
277
|
+
IJSUtils.clearOutput();
|
|
278
|
+
} else {
|
|
279
|
+
IJSUtils.markdown(markdownText, display);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
249
283
|
/**
|
|
250
284
|
* List the globals currently defined.
|
|
251
285
|
*
|
|
@@ -592,7 +626,7 @@ module.exports.htmlScript = function htmlScripts(
|
|
|
592
626
|
* (This is useful to put after importing libraries,
|
|
593
627
|
* or defining a list of functions)
|
|
594
628
|
*/
|
|
595
|
-
module.exports.
|
|
629
|
+
module.exports.clearOutput = function clearOutput(outputText = '') {
|
|
596
630
|
//-- you must be in iJavaScript container to rendeer
|
|
597
631
|
const context = IJSUtils.detectContext();
|
|
598
632
|
|
|
@@ -602,7 +636,37 @@ module.exports.noOutputNeeded = function clearOutput(outputText = '') {
|
|
|
602
636
|
|
|
603
637
|
context.$$.text(outputText);
|
|
604
638
|
};
|
|
605
|
-
module.exports.
|
|
639
|
+
module.exports.noOutputNeeded = module.exports.clearOutput;
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Returns the HTML used for generating pageBreaks within the library.
|
|
643
|
+
*
|
|
644
|
+
* This gives you control over rendering the text together
|
|
645
|
+
*
|
|
646
|
+
* ```
|
|
647
|
+
* utils.ijs.generatePageBreakStylesHTML();
|
|
648
|
+
* // <style>
|
|
649
|
+
*
|
|
650
|
+
* //-- an identifier that can be used to find if this script exists on the page
|
|
651
|
+
* \/\* ID:___InitializePageBreaks___ \*\/
|
|
652
|
+
*
|
|
653
|
+
* // @media print {
|
|
654
|
+
* // .pagebreak { page-break-before: always; }
|
|
655
|
+
* // }
|
|
656
|
+
*
|
|
657
|
+
* // </style>
|
|
658
|
+
* ```
|
|
659
|
+
*
|
|
660
|
+
* @returns {String} - the html styles tag used to allow for page breaks to be generated
|
|
661
|
+
*/
|
|
662
|
+
module.exports.generatePageBreakStylesHTML = function generatePageBreakStylesHTML() {
|
|
663
|
+
return `<style>
|
|
664
|
+
/* ID:___InitializePageBreaks___ */
|
|
665
|
+
@media print {
|
|
666
|
+
.pagebreak { page-break-before: always; } /* page-break-after works, as well */
|
|
667
|
+
}
|
|
668
|
+
</style>`;
|
|
669
|
+
};
|
|
606
670
|
|
|
607
671
|
/**
|
|
608
672
|
* Required to be called first - in order to write page-breaks in the html results.
|
|
@@ -634,8 +698,26 @@ module.exports.clearOutput = module.exports.noOutputNeeded;
|
|
|
634
698
|
*
|
|
635
699
|
* * end of document
|
|
636
700
|
* ```
|
|
701
|
+
*
|
|
702
|
+
* Note, sometimes you want to include a text prior to the page break,
|
|
703
|
+
* like mentioning that a page is left intentionally blank - or to identify
|
|
704
|
+
* this is the cell that has the style - so it shouldn't be removed prior to printing.
|
|
705
|
+
*
|
|
706
|
+
* ```
|
|
707
|
+
* utils.ijs.initializePageBreaks('<h1>This page left intentionally blank</h1>');
|
|
708
|
+
* ```
|
|
709
|
+
*
|
|
710
|
+
* Or, perhaps you always want a page break after defining the styles
|
|
711
|
+
*
|
|
712
|
+
* ```
|
|
713
|
+
* utils.ijs.initializePageBreaks(null, utils.ijs.generatePageBreakHTML());
|
|
714
|
+
* ```
|
|
715
|
+
*
|
|
716
|
+
* @param {String} [htmlToInjectBefore] - optional html text to include prior to the pageBreak
|
|
717
|
+
* (This can be helpful like - page left intentionally blank)
|
|
718
|
+
* @param {String} [htmlToInjectAfter] - optional html text to include prior to the pageBreak
|
|
637
719
|
*/
|
|
638
|
-
module.exports.initializePageBreaks = function initializePageBreaks() {
|
|
720
|
+
module.exports.initializePageBreaks = function initializePageBreaks(htmlToInjectBefore, htmlToInjectAfter) {
|
|
639
721
|
//-- you must be in iJavaScript container to rendeer
|
|
640
722
|
const context = IJSUtils.detectContext();
|
|
641
723
|
|
|
@@ -643,20 +725,50 @@ module.exports.initializePageBreaks = function initializePageBreaks() {
|
|
|
643
725
|
return;
|
|
644
726
|
}
|
|
645
727
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
728
|
+
const cleanInjectionPrior = htmlToInjectBefore || '';
|
|
729
|
+
const htmlToRender = IJSUtils.generatePageBreakStylesHTML();
|
|
730
|
+
const cleanInjectionAfter = htmlToInjectAfter || '';
|
|
731
|
+
|
|
732
|
+
context.$$.html(`
|
|
733
|
+
${cleanInjectionPrior}
|
|
734
|
+
${htmlToRender}
|
|
735
|
+
${cleanInjectionAfter}`);
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Generates the html used for creating a page break used by the library.
|
|
740
|
+
*
|
|
741
|
+
* This gives you options about when and how to render it.
|
|
742
|
+
*
|
|
743
|
+
* ```
|
|
744
|
+
* utils.ijs.generatePageBreakHTML();
|
|
745
|
+
* //-- uses the style defined in initializePageBreaks
|
|
746
|
+
* // <div class="pagebreak"></div>
|
|
747
|
+
* ```
|
|
748
|
+
*
|
|
749
|
+
* For example, you can use this so you automatically have a page break after generating the styles
|
|
750
|
+
*
|
|
751
|
+
* ```
|
|
752
|
+
* utils.ijs.initializePageBreaks(null, utils.ijs.generatePageBreakHTML());
|
|
753
|
+
* ```
|
|
754
|
+
*
|
|
755
|
+
* @returns {String}
|
|
756
|
+
* @see {@link module:ijs.printPageBreak|ijs.printPageBreak}
|
|
757
|
+
* @see {@link module:ijs.initializePageBreaks|ijs.initializePageBreaks}
|
|
758
|
+
*/
|
|
759
|
+
module.exports.generatePageBreakHTML = function generatePageBreakHTML() {
|
|
760
|
+
return '<div class="pagebreak"></div>';
|
|
653
761
|
};
|
|
654
762
|
|
|
655
763
|
/**
|
|
656
764
|
* After the {@see module:ijs.initializePageBreaks|utils.ijs.initializePageBreaks} is called,
|
|
657
765
|
* this will create another page break.
|
|
766
|
+
*
|
|
767
|
+
* @param {String} [htmlToInjectBefore] - optional html text to include prior to the pageBreak
|
|
768
|
+
* (This can be helpful like - page left intentionally blank)
|
|
769
|
+
* @param {String} [htmlToInjectAfter] - optional html text to include prior to the pageBreak
|
|
658
770
|
*/
|
|
659
|
-
module.exports.printPageBreak = function printPageBreak() {
|
|
771
|
+
module.exports.printPageBreak = function printPageBreak(htmlToInjectBefore, htmlToInjectAfter) {
|
|
660
772
|
//-- you must be in iJavaScript container to rendeer
|
|
661
773
|
const context = IJSUtils.detectContext();
|
|
662
774
|
|
|
@@ -664,7 +776,9 @@ module.exports.printPageBreak = function printPageBreak() {
|
|
|
664
776
|
return;
|
|
665
777
|
}
|
|
666
778
|
|
|
667
|
-
|
|
779
|
+
const htmlToRender = IJSUtils.generatePageBreakHTML();
|
|
780
|
+
|
|
781
|
+
context.$$.html(`${htmlToInjectBefore || ''}${htmlToRender}${htmlToInjectAfter || ''}`);
|
|
668
782
|
};
|
|
669
783
|
|
|
670
784
|
/**
|
package/src/object.js
CHANGED
|
@@ -51,13 +51,14 @@ const FormatUtils = require('./format');
|
|
|
51
51
|
* * {@link module:object.flatten|flatten()} - creates dot notation properties (similar to arrow notation) of all child objects.
|
|
52
52
|
* * {@link module:object.expand|expand()} - expands dot notation properties onto sub children (inverse of flatten)
|
|
53
53
|
* * Create Map of objects by key
|
|
54
|
-
* * {@link module:object.mapByProperty|mapByProperty()}
|
|
55
|
-
* * {@link module:group.by|group(collection, accessor)}
|
|
54
|
+
* * {@link module:object.mapByProperty|mapByProperty()}
|
|
55
|
+
* * {@link module:group.by|group.by(collection, accessor)}
|
|
56
56
|
* * Convert collections of objects
|
|
57
57
|
* * {@link module:object.objectCollectionFromArray|objectCollectionFromArray} - convert rows/columns 2d array to objects
|
|
58
|
-
* * {@link module:object.objectCollectionToArray} - convert objects to a rows/columns 2d array
|
|
59
|
-
* * {@link module:object.objectCollectionFromDataFrameObject} - convert tensor object with each field as 1d array of values
|
|
60
|
-
* * {@link module:object.objectCollectionToDataFrameObject} - convert objects from a tensor object
|
|
58
|
+
* * {@link module:object.objectCollectionToArray|objectCollectionToArray} - convert objects to a rows/columns 2d array
|
|
59
|
+
* * {@link module:object.objectCollectionFromDataFrameObject|objectCollectionFromDataFrameObject} - convert tensor object with each field as 1d array of values
|
|
60
|
+
* * {@link module:object.objectCollectionToDataFrameObject|objectCollectionToDataFrameObject} - convert objects from a tensor object
|
|
61
|
+
* * {@link module:object.splitIntoDatums|splitIntoDatums(object, fieldsToSplitBy)} - separate objects into series by fields
|
|
61
62
|
*
|
|
62
63
|
* @module object
|
|
63
64
|
* @exports object
|
|
@@ -331,6 +332,78 @@ module.exports.keys = function keys(objOrArray = {}, maxRows = -1) {
|
|
|
331
332
|
return Array.from(result);
|
|
332
333
|
};
|
|
333
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Identifies which keys provided are also in objOrArray.
|
|
337
|
+
*
|
|
338
|
+
* ```
|
|
339
|
+
* dataSet = [
|
|
340
|
+
* { first: 'john', last: 'McCartney' },
|
|
341
|
+
* { first: 'ringo', last: 'Starr' }
|
|
342
|
+
* ];
|
|
343
|
+
*
|
|
344
|
+
* utils.object.keysWithinList(dataSet, 'first', 'last', 'favouriteColor');
|
|
345
|
+
* // ['first', 'last'] // no favouriteColor defined
|
|
346
|
+
* ```
|
|
347
|
+
*
|
|
348
|
+
* Note you can also pass the list of keys as an array in the first argument
|
|
349
|
+
*
|
|
350
|
+
* ```
|
|
351
|
+
* fieldsToCheck = ['first', 'last', 'favouriteColor'];
|
|
352
|
+
* utils.object.keysWithinList(dataSet, fieldsToCheck);
|
|
353
|
+
* // ['first', 'last']
|
|
354
|
+
* ```
|
|
355
|
+
*
|
|
356
|
+
* @param {Object|Object[]} objOrArray - object or list of objects to identify the keys they have
|
|
357
|
+
* @param {...String} listOfKeys - a list of keys to check if they are defined within objOrArray
|
|
358
|
+
* @returns {String[]} - list of keys that are both in the objOrArray or within listOfKeys
|
|
359
|
+
*/
|
|
360
|
+
module.exports.keysWithinList = function keysWithinList(objOrArray, ...listOfKeys) {
|
|
361
|
+
const cleanListOfKeys = listOfKeys.length > 0 && Array.isArray(listOfKeys[0])
|
|
362
|
+
? listOfKeys[0]
|
|
363
|
+
: listOfKeys;
|
|
364
|
+
|
|
365
|
+
const keySet = new Set(ObjectUtils.keys(objOrArray));
|
|
366
|
+
const keyIntersection = cleanListOfKeys.filter((keyToTest) => keySet.has(keyToTest));
|
|
367
|
+
return keyIntersection;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Identifies which other keys are defined that are not in the list provided.
|
|
372
|
+
*
|
|
373
|
+
* This is quite helpful for dynamic APIs.
|
|
374
|
+
*
|
|
375
|
+
* ```
|
|
376
|
+
* dataSet = [
|
|
377
|
+
* { first: 'john', last: 'McCartney', favouriteColor: 'blue' },
|
|
378
|
+
* { first: 'ringo', last: 'Starr', favouriteColor: 'red' }
|
|
379
|
+
* ];
|
|
380
|
+
*
|
|
381
|
+
* utils.object.keysNotInList(dataSet, 'first', 'last', 'favouriteColor');
|
|
382
|
+
* // ['favouriteColor']
|
|
383
|
+
* ```
|
|
384
|
+
*
|
|
385
|
+
* Note you can also pass the list of keys as an array in the first argument
|
|
386
|
+
*
|
|
387
|
+
* ```
|
|
388
|
+
* fieldsToCheck = ['first', 'last', 'favouriteColor'];
|
|
389
|
+
* utils.object.keysNotInList(dataSet, fieldsToCheck);
|
|
390
|
+
* // ['favouriteColor']
|
|
391
|
+
* ```
|
|
392
|
+
*
|
|
393
|
+
* @param {Object|Object[]} objOrArray - object or list of objects to identify the keys they have
|
|
394
|
+
* @param {...String} listOfKeys - a list of keys to check if they are defined within objOrArray
|
|
395
|
+
* @returns {String[]} - list of keys that are both in the objOrArray or within listOfKeys
|
|
396
|
+
*/
|
|
397
|
+
module.exports.keysNotInList = function keysNotInList(objOrArray, ...listOfKeys) {
|
|
398
|
+
const setOfKeysToCheck = listOfKeys.length > 0 && Array.isArray(listOfKeys[0])
|
|
399
|
+
? new Set(listOfKeys[0])
|
|
400
|
+
: new Set(listOfKeys);
|
|
401
|
+
|
|
402
|
+
const keys = ObjectUtils.keys(objOrArray);
|
|
403
|
+
const keyIntersection = keys.filter((keyToTest) => !setOfKeysToCheck.has(keyToTest));
|
|
404
|
+
return keyIntersection;
|
|
405
|
+
};
|
|
406
|
+
|
|
334
407
|
/**
|
|
335
408
|
* Cleans all the properties of the array of objects in place (does not make Copies)
|
|
336
409
|
*
|
|
@@ -2170,3 +2243,92 @@ module.exports.objectCollectionToDataFrameObject = function objectCollectionToDa
|
|
|
2170
2243
|
});
|
|
2171
2244
|
return dataFrameObject;
|
|
2172
2245
|
};
|
|
2246
|
+
|
|
2247
|
+
/**
|
|
2248
|
+
* Some charting software (such as vega-lite) does not allow a single object to be used
|
|
2249
|
+
* for multiple line series.
|
|
2250
|
+
*
|
|
2251
|
+
* This is intended to help with that.
|
|
2252
|
+
*
|
|
2253
|
+
* ```
|
|
2254
|
+
* [
|
|
2255
|
+
* { category: 'A', source: 'chicago', x: 0.1, y: 0.6, z: 0.9 },
|
|
2256
|
+
* { category: 'B', source: 'springfield', x: 0.7, y: 0.2, z: 1.1 },
|
|
2257
|
+
* { category: 'C', source: 'winnetka', x: 0.6, y: 0.1, z: 0.2 }
|
|
2258
|
+
* ]
|
|
2259
|
+
* ```
|
|
2260
|
+
*
|
|
2261
|
+
* must have a separate object for each x, y and z field for the A category.
|
|
2262
|
+
*
|
|
2263
|
+
* ```
|
|
2264
|
+
* utils.object.splitIntoDatums(category, ['x', 'y', 'z']);
|
|
2265
|
+
* [
|
|
2266
|
+
* { category: 'A', source: 'chicago', series: 'x', value: 0.1 },
|
|
2267
|
+
* { category: 'A', source: 'chicago', series: 'y', value: 0.6 },
|
|
2268
|
+
* { category: 'A', source: 'chicago', series: 'z', value: 0.9 },
|
|
2269
|
+
* { category: 'B', source: 'springfield', series: 'x', value: 0.7 },
|
|
2270
|
+
* { category: 'B', source: 'springfield', series: 'y', value: 0.2 },
|
|
2271
|
+
* { category: 'B', source: 'springfield', series: 'z', value: 1.1 },
|
|
2272
|
+
* { category: 'C', source: 'winnetka', series: 'x', value: 0.6 },
|
|
2273
|
+
* { category: 'C', source: 'winnetka', series: 'y', value: 0.1 },
|
|
2274
|
+
* { category: 'C', source: 'winnetka', series: 'z', value: 0.2 }
|
|
2275
|
+
* ]
|
|
2276
|
+
* ```
|
|
2277
|
+
*
|
|
2278
|
+
* note that the fields NOT within the list of fields specified, are preserved
|
|
2279
|
+
* in the denormalized objects...
|
|
2280
|
+
*
|
|
2281
|
+
* while the fields listed are put into separate objects.
|
|
2282
|
+
*
|
|
2283
|
+
* You can specify which fields that are generated in those new objects
|
|
2284
|
+
*
|
|
2285
|
+
* ```
|
|
2286
|
+
* utils.object.splitIntoDatums(category, ['x', 'y', 'z'], 'group', 'val');
|
|
2287
|
+
* [
|
|
2288
|
+
* { category: 'A', source: 'chicago', group: 'x', val: 0.1 },
|
|
2289
|
+
* { category: 'A', source: 'chicago', group: 'y', val: 0.6 },
|
|
2290
|
+
* { category: 'A', source: 'chicago', group: 'z', val: 0.9 },
|
|
2291
|
+
* { category: 'B', source: 'springfield', group: 'x', val: 0.7 },
|
|
2292
|
+
* { category: 'B', source: 'springfield', group: 'y', val: 0.2 },
|
|
2293
|
+
* { category: 'B', source: 'springfield', group: 'z', val: 1.1 },
|
|
2294
|
+
* { category: 'C', source: 'winnetka', group: 'x', val: 0.6 },
|
|
2295
|
+
* { category: 'C', source: 'winnetka', group: 'y', val: 0.1 },
|
|
2296
|
+
* { category: 'C', source: 'winnetka', group: 'z', val: 0.2 }
|
|
2297
|
+
* ]
|
|
2298
|
+
* ```
|
|
2299
|
+
*
|
|
2300
|
+
* @param {Object[]} objectCollection - collection
|
|
2301
|
+
* @param {String[]} keysForEachSeries - fields that will each be a series spread across datum records
|
|
2302
|
+
* @param {String} [seriesFieldName='series'] - the name of the field to indicate which series this datum is in
|
|
2303
|
+
* @param {String} [valueFieldName='value'] - the name of the value for that datum
|
|
2304
|
+
* @returns {Object} - list of objects x keysForEachSeries.length, where field within keysForEachSeries
|
|
2305
|
+
* are then individually assigned to a value, and series - to indicate which field is used.
|
|
2306
|
+
*/
|
|
2307
|
+
module.exports.splitIntoDatums = function splitIntoDatums(
|
|
2308
|
+
collection,
|
|
2309
|
+
keysToSplit,
|
|
2310
|
+
seriesFieldName = 'series',
|
|
2311
|
+
valueFieldName = 'value'
|
|
2312
|
+
) {
|
|
2313
|
+
const keysToKeep = ObjectUtils.keysNotInList(collection, keysToSplit);
|
|
2314
|
+
const keysToSeparate = ObjectUtils.keysWithinList(collection, keysToSplit);
|
|
2315
|
+
const rowLength = keysToSeparate.length;
|
|
2316
|
+
|
|
2317
|
+
const results = new Array(collection.length * rowLength).fill(null);
|
|
2318
|
+
collection.forEach((obj, objIndex) => {
|
|
2319
|
+
const basis = {};
|
|
2320
|
+
keysToKeep.forEach((key) => {
|
|
2321
|
+
basis[key] = obj[key];
|
|
2322
|
+
});
|
|
2323
|
+
|
|
2324
|
+
keysToSeparate.forEach((key, keyIndex) => {
|
|
2325
|
+
const newRecord = { ...basis };
|
|
2326
|
+
newRecord[seriesFieldName] = key;
|
|
2327
|
+
newRecord[valueFieldName] = obj[key];
|
|
2328
|
+
results[objIndex * rowLength + keyIndex] = newRecord;
|
|
2329
|
+
});
|
|
2330
|
+
});
|
|
2331
|
+
|
|
2332
|
+
// return ({keysForSplitting, keysForKeeping});
|
|
2333
|
+
return results;
|
|
2334
|
+
};
|