jupyter-ijavascript-utils 1.22.4 → 1.24.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
@@ -63,8 +63,20 @@ This is not intended to be the only way to accomplish many of these tasks, and a
63
63
 
64
64
  ![Screenshot of example notebook](img/mainExampleNotebook.png)
65
65
 
66
+ ## Running on Binder
67
+
68
+ [mybinder.org](https://mybinder.org/) is a great place to run a Jupyter Notebook online.
69
+
70
+ It means you can run Jupyter Notebooks with additional kernels without having to install anything,
71
+ and can try right in your browser.
72
+
73
+ Give it a try here:
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
+
66
76
  ## What's New
67
77
 
78
+ * 1.24 - format.stripHtmlTags, TableGenerator.offset, chain.chainFlatMap, chain.chainFilter
79
+ * 1.23 - add format.parseNumber and TableGenerator.styleColumn, align group.separateByFields to vega-lite fold transform
68
80
  * 1.22 - make chain iJavaScript aware, but still able to work outside of Jupyter
69
81
  * 1.21 - include {@link module:chain|chain} - simple monoid
70
82
  * 1.20 - fix vega dependency
package/Dockerfile CHANGED
@@ -1,3 +1,3 @@
1
1
  # syntax=docker/dockerfile:1
2
2
 
3
- FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.22.4
3
+ FROM darkbluestudios/jupyter-ijavascript-utils:binder_1.24.0
package/README.md CHANGED
@@ -55,6 +55,8 @@ This is not intended to be the only way to accomplish many of these tasks, and a
55
55
 
56
56
  # What's New
57
57
 
58
+ * 1.24 - format.stripHtmlTags, TableGenerator.offset, chain.chainFlatMap, chain.chainFilter
59
+ * 1.23 - add format.parseNumber and TableGenerator.styleColumn, align group.separateByFields to vega-lite fold transform
58
60
  * 1.22 - make chain iJavaScript aware, but still able to work outside of Jupyter
59
61
  * 1.21 - include chain - simple monoid
60
62
  * 1.20 - fix vega dependency
@@ -104,14 +106,13 @@ Steps Overview:
104
106
 
105
107
  ## Running on Binder
106
108
 
107
- !TODO
108
-
109
109
  [mybinder.org](https://mybinder.org/) is a great place to run a Jupyter Notebook online.
110
110
 
111
- We are still attempting to sort out a suitable link that will work for everyone
112
- (without overloading their service)
111
+ It means you can run Jupyter Notebooks with additional kernels without having to install anything,
112
+ and can try right in your browser.
113
113
 
114
- (Please see [Issue #4](https://github.com/paulroth3d/jupyter-ijavascript-utils/issues/4))
114
+ Give it a try here:
115
+ [![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)
115
116
 
116
117
  # For Example
117
118
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jupyter-ijavascript-utils",
3
- "version": "1.22.4",
3
+ "version": "1.24.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",
@@ -105,6 +105,7 @@ const { createSort } = require('./array');
105
105
  * * sort and limit the output
106
106
  * * {@link TableGenerator#filter|filter(fn)} - determine which rows to include or not
107
107
  * * {@link TableGenerator#limit|limit(number)} - limit only specific # of rows
108
+ * * {@link TableGenerator#offset|offset(number)} - starts results only after an offset number of rows
108
109
  * * {@link TableGenerator#sortFn|sortFn(fn)} - Standard Array sort function
109
110
  * * {@link TableGenerator#sort|sort(field, field, ...)} - sorts by fields, or descending with '-'
110
111
  * * transpose the output
@@ -112,6 +113,7 @@ const { createSort } = require('./array');
112
113
  * * style the table
113
114
  * * {@link TableGenerator#styleTable|styleTable(string)} - css style for the table
114
115
  * * {@link TableGenerator#styleHeader|styleHeader(string)} - css styles for the header row
116
+ * * {@link TableGenerator#styleColumn|styleColumn(object)} - Function to style cells based on the column
115
117
  * * {@link TableGenerator#styleRow|styleRow(fn)} - Function to style rows
116
118
  * * {@link TableGenerator#styleCell|styleCell(fn)} - Function to style cells
117
119
  * * {@link TableGenerator#border|border(string)} - Apply a border to the table data cells
@@ -196,6 +198,17 @@ class TableGenerator {
196
198
  */
197
199
  #limit = 0;
198
200
 
201
+ /**
202
+ * The number of rows to skip before showing results.
203
+ *
204
+ * 10 : means start showing results only after the first 10 records
205
+ *
206
+ * -10 : means only show the last 10
207
+ *
208
+ * @type {Number}
209
+ */
210
+ #offset = 0;
211
+
199
212
  /**
200
213
  * PrintValue options to use when rendering the table values
201
214
  * @type {PrintOptions}
@@ -229,6 +242,12 @@ class TableGenerator {
229
242
  */
230
243
  #styleRow = null;
231
244
 
245
+ /**
246
+ * Style to apply at the column level
247
+ * @type {Function}
248
+ */
249
+ #styleColumn = null;
250
+
232
251
  /**
233
252
  * Style to apply at the cell
234
253
  * @type {Function}
@@ -282,11 +301,13 @@ class TableGenerator {
282
301
  this.#formatterFn = null;
283
302
  this.#labels = {};
284
303
  this.#limit = 0;
304
+ this.#offset = 0;
285
305
  this.#printOptions = null;
286
306
  this.#sortFn = null;
287
307
  this.#styleTable = '';
288
308
  this.#styleHeader = '';
289
309
  this.#styleRow = null;
310
+ this.#styleColumn = null;
290
311
  this.#styleCell = null;
291
312
  this.#isTransposed = false;
292
313
  }
@@ -386,6 +407,7 @@ class TableGenerator {
386
407
  * As this adds additional CSS, the styling applied:
387
408
  * * {@link TableGenerator#styleTable|to the whole table}
388
409
  * * or {@link TableGenerator#styleRow|to the rows}
410
+ * * or {@link TableGenerator#styleColumn|to the column}
389
411
  * * or {@link TableGenerator#styleCell|to the data cells} will be affected
390
412
  *
391
413
  * For example:
@@ -668,6 +690,21 @@ class TableGenerator {
668
690
  return this;
669
691
  }
670
692
 
693
+ /**
694
+ * The number of rows to skip before showing any records.
695
+ *
696
+ * 10 : means start showing results only after the first 10 records
697
+ *
698
+ * -10 : means only show the last 10
699
+ *
700
+ * @param {Number} offsetRecords - the number of rows to skip
701
+ * @returns {TableGenerator} - chainable interface
702
+ */
703
+ offset(offsetRecords) {
704
+ this.#offset = offsetRecords;
705
+ return this;
706
+ }
707
+
671
708
  /**
672
709
  * Sets the alternative labels to be used for specific fields.
673
710
  *
@@ -882,11 +919,99 @@ class TableGenerator {
882
919
  return this;
883
920
  }
884
921
 
922
+ /**
923
+ * Function that can apply a style to a given column
924
+ *
925
+ * (rowIndex, ({ columnHeader, columnIndex, record, row, rowIndex, value })) => string
926
+ *
927
+ * note: see {@link TableGenerator#styleCell} for another way to do style a cell.
928
+ *
929
+ * ```
930
+ * dataSet = [
931
+ * {reg: 'z', source: 'A', temp: 10},
932
+ * {reg: 'z', source: 'B', temp: 98},
933
+ * {reg: 'z', source: 'A', temp: 100}
934
+ * ];
935
+ *
936
+ * utils.table(dataSet)
937
+ * .styleColumn({
938
+ * //-- we want to make the background color of the color red, if the temp > 50
939
+ * temp: (temp) => temp > 50 ? 'background-color:pink' : '',
940
+ *
941
+ * //-- we want to make the source bold if the source is B
942
+ * source: (source) => source === 'B' ? 'font-weight:bold' : ''
943
+ * })
944
+ * .render();
945
+ * ```
946
+ * <table cellspacing="0px" >
947
+ * <tr ><th>reg</th><th>source</th><th>temp</th></tr>
948
+ * <tr ><td >z</td><td >A</td><td >10</td></tr>
949
+ * <tr ><td >z</td><td style="font-weight:bold;">B</td><td style="background-color:pink;">98</td></tr>
950
+ * <tr ><td >z</td><td >A</td><td style="background-color:pink;">100</td></tr>
951
+ * </table>
952
+ *
953
+ * Or you could style the cell based on information in other columns.
954
+ *
955
+ * ```
956
+ * dataSet = [
957
+ * {reg: 'z', source: 'A', tempFormat: 'c', temp: 42},
958
+ * {reg: 'z', source: 'B', tempFormat: 'f', temp: 98},
959
+ * {reg: 'z', source: 'A', tempFormat: 'f', temp: 100}
960
+ * ];
961
+ *
962
+ * utils.table(dataSet)
963
+ * .styleColumn({
964
+ * //-- we want to make the background color of the color red, if the temp > 50
965
+ * temp: (temp, { record }) => convertToKelvin(temp, record.tempFormat) > 283
966
+ * ? 'background-color:pink'
967
+ * : ''
968
+ * })
969
+ * .render();
970
+ * ```
971
+ *
972
+ * <table cellspacing="0px" >
973
+ * <tr ><th>reg</th><th>source</th><th>tempFormat</th><th>temp</th></tr>
974
+ * <tr ><td >z</td><td >A</td><td >c</td><td style="background-color:pink;">10</td></tr>
975
+ * <tr ><td >z</td><td >B</td><td >f</td><td style="background-color:pink;">98</td></tr>
976
+ * <tr ><td >z</td><td >A</td><td >f</td><td style="background-color:pink;">100</td></tr>
977
+ * </table>
978
+ *
979
+ * @param {object} styleObj - object with properties matching the column header label
980
+ * @param {function(value, contextObj)} styleObj.property - Function to evaluate for each row returning the inline css styles to apply.
981
+ *
982
+ * When it runs it will get passed the value and context,
983
+ * and should return the css inline styles to apply
984
+ *
985
+ * @param {any} styleObj.property.value - the value for a given row for that column
986
+ * @param {any} styleObj.property.context.value - destructured value of the cell
987
+ * @param {Number} styleObj.property.context.columnIndex - destructured 0 index column of the cell
988
+ * @param {Number} styleObj.property.context.rowIndex - destructured 0 index row of the cell
989
+ * @param {Array} styleObj.property.context.row - destructured full row provided
990
+ * @param {Array} styleObj.property.context.record - destructured original record
991
+ * @returns {TableGenerator} - chainable instance
992
+ */
993
+ styleColumn(styleObj) {
994
+ if (!styleObj) {
995
+ this.#styleColumn = null;
996
+ return this;
997
+ }
998
+
999
+ if (typeof styleObj !== 'object') {
1000
+ throw Error('styleColumn(styleObj): expects an object with properties matching the column LABELs');
1001
+ }
1002
+
1003
+ this.#styleColumn = styleObj;
1004
+
1005
+ return this;
1006
+ }
1007
+
885
1008
  /**
886
1009
  * Function that can apply a style to a given cell
887
1010
  *
888
1011
  * (value, columnIndex, rowIndex, row, record) => string
889
1012
  *
1013
+ * Note: see {@link TableGenerator#styleColumn} for another way to do style a cell.
1014
+ *
890
1015
  * ```
891
1016
  * dataSet = [
892
1017
  * { title:'row 0', a: 0.608, b: 0.351, c: 0.823, d: 0.206, e: 0.539 },
@@ -914,13 +1039,14 @@ class TableGenerator {
914
1039
  * ```
915
1040
  * ![Screenshot of styling the cell](img/Table_StyleCell.png)
916
1041
  *
917
- * @param {function(*):any} formatterFn - Translation function to apply to all cells.
918
1042
  *
919
1043
  * When it runs, you will receive a single parameter representing the current cell and row.
920
1044
  *
921
1045
  * Return what the new value should be.
1046
+ * @param {function(*):any} formatterFn - Translation function to apply to all cells.
922
1047
  * @param {any} formatterFn.value - destructured value of the cell
923
1048
  * @param {Number} formatterFn.columnIndex - destructured 0 index column of the cell
1049
+ * @param {Number} formatterFn.columnHeader - destructured header of the column
924
1050
  * @param {Number} formatterFn.rowIndex - destructured 0 index row of the cell
925
1051
  * @param {Array} formatterFn.row - destructured full row provided
926
1052
  * @param {Array} formatterFn.record - destructured original record
@@ -1032,8 +1158,12 @@ class TableGenerator {
1032
1158
 
1033
1159
  if (this.#limit < 0) {
1034
1160
  data = data.reverse().slice(0, -this.#limit);
1161
+ } else if (this.#offset < 0) {
1162
+ data = data.slice(this.#offset);
1035
1163
  } else if (this.#limit > 0) {
1036
- data = data.slice(0, this.#limit);
1164
+ data = data.slice(this.#offset, this.#offset + this.#limit);
1165
+ } else if (this.#offset > 0) {
1166
+ data = data.slice(this.#offset);
1037
1167
  }
1038
1168
 
1039
1169
  if (this.#isTransposed) {
@@ -1061,6 +1191,7 @@ class TableGenerator {
1061
1191
  const styleTable = this.#styleTable;
1062
1192
  const styleHeader = this.#styleHeader;
1063
1193
  const styleRowFn = this.#styleRow;
1194
+ const styleColumnObj = this.#styleColumn;
1064
1195
  const styleCellFn = this.#styleCell;
1065
1196
  const printOptions = this.#printOptions;
1066
1197
  const borderCSS = this.#borderCSS;
@@ -1099,14 +1230,20 @@ class TableGenerator {
1099
1230
 
1100
1231
  return `<tr ${printInlineCSS(rowStyle)}>\n\t`
1101
1232
  + dataRow.map((value, columnIndex) => {
1233
+ const columnHeader = results.headers[columnIndex];
1102
1234
  //-- style for the cell
1103
- const cellStyle = !styleCellFn ? '' : styleCellFn({ value, columnIndex, rowIndex, row: dataRow, record });
1235
+ const cellData = { value, columnIndex, columnHeader, rowIndex, row: dataRow, record };
1236
+ const cellStyle = !styleCellFn ? '' : styleCellFn(cellData);
1237
+ const columnStyle = !styleColumnObj || !styleColumnObj[columnHeader] || !typeof styleColumnObj[columnHeader] === 'function'
1238
+ ? ''
1239
+ : styleColumnObj[columnHeader](value, cellData);
1104
1240
 
1105
1241
  return `<td ${
1106
1242
  printInlineCSS(
1107
1243
  borderCSS,
1108
1244
  //-- could be inline, but not as clear
1109
- cellStyle
1245
+ cellStyle,
1246
+ columnStyle
1110
1247
  )
1111
1248
  }>${
1112
1249
  cleanFn(value, printOptions)
package/src/chain.js CHANGED
@@ -13,6 +13,9 @@
13
13
  * * {@link ChainContainer#close|.close()} - gets the value of the current chain
14
14
  * * {@link ChainContainer#chain|.chain(function)} - where it is passed the value, and returns a new Chain with that value.
15
15
  * * {@link ChainContainer#chainMap|.chainMap(function)} - where it treats value as an array, and maps function on every item in the array
16
+ * * {@link ChainContainer#chainFlatMap|.chainFlatMap(function)} - where it treats value as an array, and maps function on every item in the array,
17
+ * flattening the results
18
+ * * {@link ChainContainer#chainFilter|.chainFilter(function)} - where it treats the value as an array, and filters values based on the result
16
19
  * * {@link ChainContainer#chainReduce|.chainReduce(function, initialValue)} - where it treats value as an array, and reduces the value array
17
20
  * * {@link ChainContainer#errorHandler|.errorHandler(fn)} - custom function called if an error is ever thrown
18
21
  * * {@link ChainContainer#debug|.debug()} - console.logs the current value, and continues the chain with that value
@@ -158,6 +161,69 @@ class ChainContainer {
158
161
  return this.chain((value) => value.map(fn));
159
162
  }
160
163
 
164
+ /**
165
+ * Assuming that value is an array, performs a
166
+ * [javaScript flatMap](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap)
167
+ * against the results.
168
+ *
169
+ * This can be very helpful in expanding the list of items in an array, or removing items from an array.
170
+ *
171
+ * ```
172
+ * // expanding size of the array
173
+ * initializeArray = (size) => Array.from(Array(size)).map((val, index) => index);
174
+ * initializeArray(3); // [0, 1, 2]
175
+ *
176
+ * utils.chain([1, 2, 3, 4])
177
+ * .chainFlatMap(initializeArray)
178
+ * .close();
179
+ *
180
+ * // [1, 1, 2, 1, 2, 3, 1, 2, 3, 4];
181
+ * ```
182
+ *
183
+ * or similar to {@link ChainContainer#chainFilter|chainFilter}
184
+ *
185
+ * ```
186
+ * // reducing the size of the array
187
+ * filterOdd = (value) => value % 2 === 0 ? [value] : [];
188
+ * filterOdd(2); // [2]
189
+ * filterOdd(1); // []
190
+ *
191
+ * chain([1, 2, 3, 4, 5])
192
+ * .chainFlatMap(filterOdd)
193
+ * .close();
194
+ *
195
+ * // [2, 4];
196
+ * ```
197
+ *
198
+ * @param {function(any):any} fn - function that can either return a value or array of values.
199
+ * @returns {ChainContainer}
200
+ * @see {@link ChainContainer#chainFilter} - for other options in filtering
201
+ */
202
+ chainFlatMap(fn) {
203
+ if (!Array.isArray(this.value)) throw Error(`chainFlatMap expects an array, but was passed:${this.value}`);
204
+
205
+ return this.chain((value) => value.flatMap(fn));
206
+ }
207
+
208
+ /**
209
+ * Assuming that value is an array, this maps fn to filter the results in the array.
210
+ *
211
+ * ```
212
+ * chain([1,2,3,4])
213
+ * .chainFilter((value) => value < 3)
214
+ * .close();
215
+ * // [1, 2]
216
+ * ```
217
+ *
218
+ * @param {function(any):Boolean} fn - Function accepting a value and returning whether it should be included (true) or not (false)
219
+ * @returns {ChainContainer}
220
+ */
221
+ chainFilter(fn) {
222
+ if (!Array.isArray(this.value)) throw Error(`chainFilter expects an array, but was passed:${this.value}`);
223
+
224
+ return this.chain((value) => value.filter(fn));
225
+ }
226
+
161
227
  /**
162
228
  * Assuming that the value is an array, performs a reduce using fn and initialValue
163
229
  *
@@ -361,6 +427,9 @@ class ChainContainer {
361
427
  * * {@link ChainContainer#close|.close()} - gets the value of the current chain
362
428
  * * {@link ChainContainer#chain|.chain(function)} - where it is passed the value, and returns a new Chain with that value.
363
429
  * * {@link ChainContainer#chainMap|.chainMap(function)} - where it treats value as an array, and maps function on every item in the array
430
+ * * {@link ChainContainer#chainFlatMap|.chainFlatMap(function)} - where it treats value as an array, and maps function on every item in the array,
431
+ * flattening the results
432
+ * * {@link ChainContainer#chainFilter|.chainFilter(function)} - where it treats the value as an array, and filters values based on the result
364
433
  * * {@link ChainContainer#chainReduce|.chainReduce(function, initialValue)} - where it treats value as an array, and reduces the value array
365
434
  * * {@link ChainContainer#errorHandler|.errorHandler(fn)} - custom function called if an error is ever thrown
366
435
  * * {@link ChainContainer#debug|.debug()} - console.logs the current value, and continues the chain with that value
package/src/format.js CHANGED
@@ -15,6 +15,7 @@
15
15
  * * {@link module:format.capitalize|format.capitalize} - Capitalizes only the first character in the string (ex: 'John paul');
16
16
  * * {@link module:format.capitalizeAll|format.capitalizeAll} - Capitalizes all the words in a string (ex: 'John Paul')
17
17
  * * {@link module:format.ellipsify|format.ellipsify} - Truncates a string if the length is 'too long'
18
+ * * {@link module:format.stripHtmlTags|format.stripHtmlTags} - removes html / xml tags from strings.
18
19
  * * {@link module:format.limitLines|format.limitLines(string, toLine, fromLine, lineSeparator)} - selects only a subset of lines in a string
19
20
  * * {@link module:format.consoleLines|format.consoleLines(...)} - same as limit lines, only console.logs the string out.
20
21
  * * Formatting Time
@@ -29,6 +30,9 @@
29
30
  * * {@link module:format.safeConvertFloat|format.safeConvertFloat} - converts a value to a Number (123.4), or uses a default for any error or NaN
30
31
  * * {@link module:format.safeConvertInteger|format.safeConvertInteger} - converts a value to a Number (123), or uses a default for any error or NaN
31
32
  * * {@link module:format.safeConvertBoolean|format.safeConvertBoolean} - converts a value to a boolean
33
+ * * Parsing values
34
+ * * {@link module:format.parseBoolean|format.parseBoolean(val)} - converts a value to a boolean value
35
+ * * {@link module:format.parseNumber|format.parseNumber(val, locale)} - converts a value to a number
32
36
  * * Identifying values
33
37
  * * {@link module:format.isEmptyValue|format.isEmptyValue} - determine if a value is not 'empty'
34
38
  *
@@ -877,6 +881,51 @@ module.exports.isBoolean = function isBoolean(val) {
877
881
  || val === 'true' || val === 'false';
878
882
  };
879
883
 
884
+ FormatUtils.parseLocaleCache = new Map();
885
+
886
+ /**
887
+ * Parses a given number, based on a {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat|Intl.NumberFormat}
888
+ *
889
+ * If the locale is not passed (ex: 'fr-FR'), then 'en-US' is assumed
890
+ *
891
+ * @param {any} val - value to parse
892
+ * @returns {Number} - parsed number
893
+ * @example
894
+ *
895
+ * utils.format.parseNumber(10); // 10
896
+ * utils.format.parseNumber('10'); // 10
897
+ * utils.format.parseNumber('1,000'); // 1000
898
+ * utils.format.parseNumber('1,000.5'); // 1000.5
899
+ * utils.format.parseNumber('1,000', 'en-US'); // 1000
900
+ * utils.format.parseNumber('1,000.5', 'en-US'); // 1000.5
901
+ * utils.format.parseNumber('1 000', 'fr-FR'); // 1000
902
+ * utils.format.parseNumber('1 000,5', 'fr-FR'); // 1000.5
903
+ */
904
+ module.exports.parseNumber = function parseNumber(val, locale = 'en-US') {
905
+ const valType = typeof val;
906
+ if (valType === 'number') {
907
+ return val;
908
+ } else if (valType === 'string') {
909
+ let separator;
910
+ if (FormatUtils.parseLocaleCache.has(locale)) {
911
+ separator = FormatUtils.parseLocaleCache.get(locale);
912
+ } else {
913
+ const example = Intl.NumberFormat(locale).format('1.1');
914
+ separator = example.charAt(1);
915
+ FormatUtils.parseLocaleCache.set(locale, separator);
916
+ }
917
+
918
+ const cleanPattern = new RegExp(`[^-+0-9${separator}]`, 'g');
919
+ const cleaned = val.replace(cleanPattern, '');
920
+ const normalized = cleaned.replace(separator, '.');
921
+
922
+ return parseFloat(normalized);
923
+ } else if (!val) {
924
+ return val;
925
+ }
926
+ return parseFloat(val);
927
+ };
928
+
880
929
  /**
881
930
  * Narrows to only fromLine - toLine (inclusive) within a string.
882
931
  *
@@ -929,3 +978,21 @@ module.exports.limitLines = function limitLines(str, toLine, fromLine, lineSepar
929
978
  module.exports.consoleLines = function consoleLines(str, toLine, fromLine, lineSeparator) {
930
979
  console.log(FormatUtils.limitLines(str, toLine, fromLine, lineSeparator));
931
980
  };
981
+
982
+ /**
983
+ * Strips any html or xml tags from a string.
984
+ *
985
+ * Note, if you want to remove html entities (ex: `&nbsp;` or `&#8209;`), please consider [other libraries](https://www.npmjs.com/search?q=html%20entities)
986
+ *
987
+ * @param {String} str - string to strip html / xml entities from
988
+ * @returns {String}
989
+ * @example
990
+ *
991
+ * utils.format.stripHtmlTags('Hello <br />Nice to see <b>you</b>'); // 'Hello Nice to see you'
992
+ * utils.format.stripHtmlTags('example string'); // 'example string' -- untouched
993
+ */
994
+ module.exports.stripHtmlTags = function stripHtmlTags(str) {
995
+ if (!str) return str;
996
+
997
+ return str.replace(/<[^>]+>/g, '');
998
+ };
package/src/group.js CHANGED
@@ -215,6 +215,8 @@ module.exports.rollup = function rollup(collection, reducer, prop, ...fields) {
215
215
  *
216
216
  * The object generated by the function is then merged.
217
217
  *
218
+ * See [vega-lite fold transform](https://vega.github.io/vega-lite/docs/fold.html)
219
+ *
218
220
  * @example
219
221
  * aggregateWeather = utils.group.by(weather, 'city')
220
222
  * .reduce((group) => ({
@@ -235,12 +237,12 @@ module.exports.rollup = function rollup(collection, reducer, prop, ...fields) {
235
237
  *
236
238
  * //-- gives
237
239
  * [
238
- * { city: 'Seattle', min: 0.87, max: 5.31, avg: 2.953, _field: 'min', _value: 0.87 },
239
- * { city: 'New York', min: 3.58, max: 4.13, avg: 3.883, _field: 'min', _value: 3.58 },
240
- * { city: 'Chicago', min: 2.56, max: 3.98, avg: 3.387, _field: 'min', _value: 2.56 },
241
- * { city: 'Seattle', min: 0.87, max: 5.31, avg: 2.953, _field: 'max', _value: 5.31 },
242
- * { city: 'New York', min: 3.58, max: 4.13, avg: 3.883, _field: 'max', _value: 4.13 },
243
- * { city: 'Chicago', min: 2.56, max: 3.98, avg: 3.387, _field: 'max', _value: 3.98},
240
+ * { city: 'Seattle', min: 0.87, max: 5.31, avg: 2.953, key: 'min', value: 0.87 },
241
+ * { city: 'New York', min: 3.58, max: 4.13, avg: 3.883, key: 'min', value: 3.58 },
242
+ * { city: 'Chicago', min: 2.56, max: 3.98, avg: 3.387, key: 'min', value: 2.56 },
243
+ * { city: 'Seattle', min: 0.87, max: 5.31, avg: 2.953, key: 'max', value: 5.31 },
244
+ * { city: 'New York', min: 3.58, max: 4.13, avg: 3.883, key: 'max', value: 4.13 },
245
+ * { city: 'Chicago', min: 2.56, max: 3.98, avg: 3.387, key: 'max', value: 3.98},
244
246
  * ...
245
247
  * ]
246
248
  *
@@ -256,7 +258,7 @@ module.exports.separateByFields = function separateByFields(collection, ...field
256
258
  if (!fields || !Array.isArray(fields) || fields.length < 1) {
257
259
  throw (Error('separateByFields: fields are expected'));
258
260
  }
259
- return fields.flatMap((field) => collection.map((obj) => ({ ...obj, _field: field, _value: obj[field] })));
261
+ return fields.flatMap((field) => collection.map((obj) => ({ ...obj, key: field, value: obj[field] })));
260
262
  };
261
263
 
262
264
  /**
package/src/vega.js CHANGED
@@ -8,6 +8,9 @@ const IJSUtils = require('./ijs');
8
8
  /**
9
9
  * Helper for working with [Vega-Lite](https://vega.github.io/vega-lite/) (and [Vega](https://vega.github.io/vega/)) within iJavaScript notebooks.
10
10
  *
11
+ * * [Vega-Lite Example Gallery](https://vega.github.io/vega-lite/examples/)
12
+ * * [Vega Example Gallery](https://vega.github.io/vega/examples/)
13
+ *
11
14
  * ([Vega-Lite-Api](https://vega.github.io/vega-lite-api/):
12
15
  * creates -> [Vega-Lite JSON specifications](https://vega.github.io/vega-lite/tutorials/getting_started.html):
13
16
  * creates -> [Vega charting specifications](https://vega.github.io/):
@@ -25,6 +28,39 @@ const IJSUtils = require('./ijs');
25
28
  * * Rendering specifications through the Jupyter Lab mime-type
26
29
  * * {@link module:vega.vegaMimeType|vega.vegaMimeType(Object | String)} - render the chart using the Vega mime-type (as png)
27
30
  * * {@link module:vega.vegaLiteMimeType|vega.vegaLiteMimeType(Object | String)} - render the chart using the Vega-Lite mime-type (as png)
31
+ *
32
+ * For example, this is a very simple demonstration for writing a vega-lite chart (the simplest way to get started)
33
+ *
34
+ * ```
35
+ * simpleData = [{fruit:'Apples',yield:20,year:'2020'},{fruit:'Apples',yield:22,year:'2021'},
36
+ * {fruit:'Bananas',yield:15,year:'2020'},{fruit:'Bananas',yield:12,year:'2021'},
37
+ * {fruit:'Pears',yield:18,year:'2020'},{fruit:'Pears',yield:19,year:'2021'}];
38
+ *
39
+ * utils.vega.svg(
40
+ * // accept the reference to the vega-lite instance passed
41
+ * (vl) => vl
42
+ * // render as points
43
+ * .markPoint()
44
+ * // use simpleData as the data source
45
+ * .data(simpleData)
46
+ * // title
47
+ * .title('Fruit by Yield')
48
+ * .width(100)
49
+ * .encode(
50
+ * // define the x axis as the Qualitative / Numerical 'yield' property
51
+ * vl.y().fieldQ('yield'),
52
+ * // define the y axis as the Nominative / TextBased 'fruit' property
53
+ * vl.x().fieldN('fruit'),
54
+ * // define the color series based on the Qualitative / Numerical 'year' property
55
+ * vl.color().fieldN('year')
56
+ * )
57
+ * );
58
+ * ```
59
+ * ![Screenshot](img/vegaSimpleChart.jpg)
60
+ *
61
+ * and with simple changes, convert it to a bar graph
62
+ *
63
+ * ![Screenshot](img/fruitYieldByYearBar.png)
28
64
  * -----
29
65
  *
30
66
  * * Check out the {@tutorial vegaLite1} tutorials
@@ -155,6 +191,7 @@ const IJSUtils = require('./ijs');
155
191
  * "x": {"field": "Horsepower", "type": "quantitative"},
156
192
  * "y": {"field": "Miles_per_Gallon", "type": "quantitative"},
157
193
  * "color": {"field": "Origin", "type": "nominal"},
194
+ * //-- simply by adding the tooltip encoding here
158
195
  * "tooltip": {"field": "Name", "type": "nominal"},
159
196
  * "href": {"field": "url", "type": "nominal"}
160
197
  * }
@@ -163,6 +200,132 @@ const IJSUtils = require('./ijs');
163
200
  *
164
201
  * ![Screenshot for tooltips](img/vegaScript_tooltips.png)
165
202
  *
203
+ * or through the vega lite
204
+ *
205
+ * ---
206
+ *
207
+ * # FAQ
208
+ *
209
+ * The following are a series of common questions / issues, put here for visibility.
210
+ *
211
+ * ## Passing Objects to vega-lite-api
212
+ *
213
+ * Note that there are some things that are not supported by the vega-lite-api <br />
214
+ * ([such as grouping](https://github.com/vega/vega-lite/issues/4703))
215
+ *
216
+ * This is only a problem with the vega-lite-api, as it writes the spec used for vega-lite
217
+ *
218
+ * In these cases, you can send an object within many of the methods,
219
+ * as it no longer needs to translate how that object should look.
220
+ *
221
+ * (like sending `mark( type:'bar')` instead of `.markBar()` - to allow for tooltips <br />
222
+ * or passing an object to encode to support grouping)
223
+ *
224
+ * ```
225
+ * simpleData = [{fruit:'Apples',yield:20,year:'2020'},{fruit:'Apples',yield:22,year:'2021'},
226
+ * {fruit:'Bananas',yield:15,year:'2020'},{fruit:'Bananas',yield:12,year:'2021'},
227
+ * {fruit:'Pears',yield:18,year:'2020'},{fruit:'Pears',yield:19,year:'2021'}];
228
+ *
229
+ * utils.vega.svg((vl) => vl
230
+ * // render as points
231
+ * .mark({ type: 'bar', tooltip: true})
232
+ * .data(simpleData)
233
+ * .title('Fruit by Yield')
234
+ * .width(100)
235
+ * .encode({
236
+ * "x": {"field": "fruit"},
237
+ * "y": {"field": "yield", "type": "quantitative"},
238
+ * "xOffset": {"field": "year"},
239
+ * "color": {"field": "year"}
240
+ * })
241
+ * );
242
+ * ```
243
+ *
244
+ * ![Screenshot](img/vega_groupedBarCharts.jpg)
245
+ *
246
+ * ---
247
+ *
248
+ * ## Chart Series
249
+ *
250
+ * Instead of grouping values, you can also create a series of charts instead.
251
+ *
252
+ * ```
253
+ * utils.vega.svg((vl) => vl
254
+ * // render as points
255
+ * .mark({ type: 'arc', innerRadius: 30, tooltip: true})
256
+ * .data(simpleData)
257
+ * .title('Fruit by Yield')
258
+ * .width(100)
259
+ * .encode(
260
+ * // define the arc on the graph with the Qualitative / Numerical 'yield' property
261
+ * vl.theta().fieldQ('yield'),
262
+ * // define the y axis as the based on the Qualitative / Numerical 'year' property
263
+ * vl.color().fieldN('year'),
264
+ * // define the color series Nominative / TextBased 'fruit' property
265
+ * vl.column().fieldN('fruit')
266
+ * )
267
+ * );
268
+ * ```
269
+ *
270
+ * ![Screenshot](img/vega_chartColumns.jpg)
271
+ *
272
+ * Other examples can be found [under the vega-lite examples](https://vega.github.io/vega-lite/examples/#repeat--concatenation)
273
+ * and rendered with {@link vega.svgFromSpec|svgFromSpec} or {@link vega.ßembedFromSpec|embedFromSpec}
274
+ *
275
+ * ---
276
+ *
277
+ * ## Object Formatting / Conversion
278
+ *
279
+ * Note that vega-lite examples use data at the mark level:
280
+ *
281
+ * ```
282
+ * [{fruit:'Apples',yield:20,year:'2020'},{fruit:'Apples',yield:22,year:'2021'},
283
+ * {fruit:'Bananas',yield:15,year:'2020'},{fruit:'Bananas',yield:12,year:'2021'},
284
+ * {fruit:'Pears',yield:18,year:'2020'},{fruit:'Pears',yield:19,year:'2021'}];
285
+ * ```
286
+ *
287
+ * not at the series level:
288
+ *
289
+ * ```
290
+ * [{ year:'2020', apples:20, bananas:15, pears:18 },
291
+ * { year:'2021', apples:22, bananas:12, pears:19 }];
292
+ * ```
293
+ *
294
+ * If your data is at the series level, you can:
295
+ *
296
+ * * Transform the data with the {@link module:group.separateByFields|group.separateByFields}
297
+ * * utils.group.separateByFields(fruitSeriesData, 'apples', 'bananas', 'pears');
298
+ * * this makes a new field called 'key' that will have values of either apples, bananas or pears
299
+ * * or using the [vega-lite fold transform](https://vega.github.io/vega-lite/docs/fold.html)
300
+ * * by adding in the `.transform()` into the spec - like below
301
+ *
302
+ * ```
303
+ * fruitSeriesData = [{ year:'2020', apples:20, bananas:15, pears:18 },
304
+ * { year:'2021', apples:22, bananas:12, pears:19 }];
305
+ *
306
+ * utils.vega.svg((vl) => vl
307
+ * .mark({ type: 'arc', innerRadius: 30, tooltip: true})
308
+ * .data(fruitSeriesData)
309
+ *
310
+ * //-- apples, bananas and pears will now be separate records
311
+ * //-- with the new `key` field as either 'apple', 'banana', or 'pears'
312
+ * //-- and the new `value` field storing the value of those fields.
313
+ * .transform([{ fold: ['apples', 'bananas', 'pears']}])
314
+ *
315
+ * .title('Fruit by Yield')
316
+ * .width(100)
317
+ * .encode(
318
+ * // define the arc on the graph with the Qualitative / Numerical 'yield' property
319
+ * vl.theta().fieldQ('value'),
320
+ * // define the y axis as the Nominative / TextBased 'fruit' property
321
+ * vl.color().fieldN('year'),
322
+ * // define the color series based on the Qualitative / Numerical 'year' property
323
+ * vl.column().fieldN('key')
324
+ * )
325
+ * )
326
+ * ```
327
+ * ![Screenshot](img/vega_chartColumns.jpg)
328
+ *
166
329
  * ---
167
330
  *
168
331
  * For more: