jupyter-ijavascript-utils 1.57.0 → 1.58.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
@@ -74,6 +74,10 @@ Give it a try here:
74
74
  [![Binder:what can I do with this](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/paulroth3d/jupyter-ijavascript-utils/main?labpath=example.ipynb)
75
75
 
76
76
  ## What's New
77
+ * 1.58 -
78
+ * #91 - add {@link module:object.splitIntoDatums|splitIntoDatums(collection, fieldsToSplitBy)} - to make working with datum libraries - like vega-lite - easier
79
+ * #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.
80
+ * #93 / #94 - bug fixes
77
81
  * 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
82
  * #87 - add in {@link module:file.fileExists|file.fileExists} to make things easier for new people getting started
79
83
  * #89 - allow {@link module:array.resize|array.resize} to work with defaults, if zipping arrays of different sizes
package/Dockerfile CHANGED
@@ -1,3 +1,3 @@
1
1
  # syntax=docker/dockerfile:1
2
2
 
3
- FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.57.0
3
+ FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.58.0
package/README.md CHANGED
@@ -54,6 +54,10 @@ This is not intended to be the only way to accomplish many of these tasks, and a
54
54
  ![Screenshot of example notebook](docResources/img/mainExampleNotebook.png)
55
55
 
56
56
  # What's New
57
+ * 1.58 -
58
+ * #91 - add object.splitIntoDatums - to make working with datum libraries - like vega-lite - easier
59
+ * #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.
60
+ * #93 / #94 - bug fixes
57
61
  * 1.57 - #86 - include examples on binning based on time series
58
62
  * #87 - add in fileExists to make things easier for new people getting started
59
63
  * #89 - allow resizing arrays with defaults, if zipping arrays of different sizes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-ijavascript-utils",
3
- "version": "1.57.0",
3
+ "version": "1.58.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/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.formatter - formatter to use when writing the JSON
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
- let cleanedOptions = { ...optionsDefaults, ...fsOptions };
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 formatter = cleanedOptions.formatter || null;
241
+ const replacer = cleanedOptions.replacer || null;
245
242
  const spacing = cleanedOptions.spacing || 2;
246
- const jsonContents = JSON.stringify(contents, formatter, spacing);
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, formatter: FileUtil.cacheDeserializer };
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 = { ...fsOptions, formatter: null }; // FileUtil.cacheSerializer not needed
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,7 +26,7 @@ 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.noOutputNeeded|ijs.noOutputNeeded} - clears the output to declutter results (like importing libraries, or functions)
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
32
  * * using a cache for long running executions
@@ -592,7 +592,7 @@ module.exports.htmlScript = function htmlScripts(
592
592
  * (This is useful to put after importing libraries,
593
593
  * or defining a list of functions)
594
594
  */
595
- module.exports.noOutputNeeded = function clearOutput(outputText = '') {
595
+ module.exports.clearOutput = function clearOutput(outputText = '') {
596
596
  //-- you must be in iJavaScript container to rendeer
597
597
  const context = IJSUtils.detectContext();
598
598
 
@@ -602,7 +602,7 @@ module.exports.noOutputNeeded = function clearOutput(outputText = '') {
602
602
 
603
603
  context.$$.text(outputText);
604
604
  };
605
- module.exports.clearOutput = module.exports.noOutputNeeded;
605
+ module.exports.noOutputNeeded = module.exports.clearOutput;
606
606
 
607
607
  /**
608
608
  * Required to be called first - in order to write page-breaks in the html results.
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
+ };