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 +12 -0
- package/Dockerfile +1 -1
- package/README.md +6 -5
- package/package.json +1 -1
- package/src/TableGenerator.js +141 -4
- package/src/chain.js +69 -0
- package/src/format.js +67 -0
- package/src/group.js +9 -7
- package/src/vega.js +163 -0
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
|

|
|
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
|
+
[](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
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
114
|
+
Give it a try here:
|
|
115
|
+
[](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
package/src/TableGenerator.js
CHANGED
|
@@ -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
|
* 
|
|
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(
|
|
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
|
|
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: ` ` or `‑`), 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,
|
|
239
|
-
* { city: 'New York', min: 3.58, max: 4.13, avg: 3.883,
|
|
240
|
-
* { city: 'Chicago', min: 2.56, max: 3.98, avg: 3.387,
|
|
241
|
-
* { city: 'Seattle', min: 0.87, max: 5.31, avg: 2.953,
|
|
242
|
-
* { city: 'New York', min: 3.58, max: 4.13, avg: 3.883,
|
|
243
|
-
* { city: 'Chicago', min: 2.56, max: 3.98, avg: 3.387,
|
|
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,
|
|
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
|
+
* 
|
|
60
|
+
*
|
|
61
|
+
* and with simple changes, convert it to a bar graph
|
|
62
|
+
*
|
|
63
|
+
* 
|
|
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
|
* 
|
|
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
|
+
* 
|
|
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
|
+
* 
|
|
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
|
+
* 
|
|
328
|
+
*
|
|
166
329
|
* ---
|
|
167
330
|
*
|
|
168
331
|
* For more:
|