@d3plus/data 3.0.0-alpha.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/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # @d3plus/data
2
+
3
+ JavaScript data loading, manipulation, and analysis functions.
4
+
5
+ ## Installing
6
+
7
+ If using npm, `npm install @d3plus/data`. Otherwise, you can download the [latest release from GitHub](https://github.com/d3plus/d3plus/releases/latest) or load from a [CDN](https://cdn.jsdelivr.net/npm/@d3plus/data@3.0.0-alpha.0/+esm).
8
+
9
+ ```js
10
+ import modules from "@d3plus/data";
11
+ ```
12
+
13
+ In vanilla JavaScript, a `d3plus` global is exported from the pre-bundled version:
14
+
15
+ ```html
16
+ <script src="https://cdn.jsdelivr.net/npm/@d3plus/data@3.0.0-alpha.0"></script>
17
+ <script>
18
+ console.log(d3plus);
19
+ </script>
20
+ ```
21
+
22
+ ## Examples
23
+
24
+ Live examples can be found on [d3plus.org](https://d3plus.org/), which includes a collection of example visualizations using @d3plus/react.
25
+
26
+ ## API Reference
27
+
28
+ #####
29
+ * [isData](#isData) - Adds the provided value to the internal queue to be loaded, if necessary. This is used internally in new d3plus visualizations that fold in additional data sources, like the nodes and links of Network or the topojson of Geomap.
30
+ * [dataConcat](#dataConcat) - Reduce and concat all the elements included in arrayOfArrays if they are arrays. If it is a JSON object try to concat the array under given key data. If the key doesn't exists in object item, a warning message is lauched to the console. You need to implement DataFormat callback to concat the arrays manually.
31
+ * [dataFold](#dataFold) - Given a JSON object where the data values and headers have been split into separate key lookups, this function will combine the data values with the headers and returns one large array of objects.
32
+ * [isData](#isData) - Returns true/false whether the argument provided to the function should be loaded using an internal XHR request. Valid data can either be a string URL or an Object with "url" and "headers" keys.
33
+ * [dataLoad](#dataLoad) - Loads data from a filepath or URL, converts it to a valid JSON object, and returns it to a callback function.
34
+ * [merge](#merge) - Combines an Array of Objects together and returns a new Object.
35
+ * [unique](#unique) - ES5 implementation to reduce an Array of values to unique instances.
36
+
37
+ ---
38
+
39
+ <a name="isData"></a>
40
+ #### d3plus.**isData**(data, [data], data) [<>](https://github.com/d3plus/d3plus/blob/main/packages/data/src/addToQueue.js#L4)
41
+
42
+ Adds the provided value to the internal queue to be loaded, if necessary. This is used internally in new d3plus visualizations that fold in additional data sources, like the nodes and links of Network or the topojson of Geomap.
43
+
44
+
45
+ This is a global function
46
+
47
+ ---
48
+
49
+ <a name="dataConcat"></a>
50
+ #### d3plus.**dataConcat**(arrayOfArray, [data]) [<>](https://github.com/d3plus/d3plus/blob/main/packages/data/src/concat.js#L1)
51
+
52
+ Reduce and concat all the elements included in arrayOfArrays if they are arrays. If it is a JSON object try to concat the array under given key data. If the key doesn't exists in object item, a warning message is lauched to the console. You need to implement DataFormat callback to concat the arrays manually.
53
+
54
+
55
+ This is a global function
56
+
57
+ ---
58
+
59
+ <a name="dataFold"></a>
60
+ #### d3plus.**dataFold**(json, [data], [headers]) [<>](https://github.com/d3plus/d3plus/blob/main/packages/data/src/fold.js#L1)
61
+
62
+ Given a JSON object where the data values and headers have been split into separate key lookups, this function will combine the data values with the headers and returns one large array of objects.
63
+
64
+
65
+ This is a global function
66
+
67
+ ---
68
+
69
+ <a name="isData"></a>
70
+ #### d3plus.**isData**(dataItem) [<>](https://github.com/d3plus/d3plus/blob/main/packages/data/src/isData.js#L1)
71
+
72
+ Returns true/false whether the argument provided to the function should be loaded using an internal XHR request. Valid data can either be a string URL or an Object with "url" and "headers" keys.
73
+
74
+
75
+ This is a global function
76
+
77
+ ---
78
+
79
+ <a name="dataLoad"></a>
80
+ #### d3plus.**dataLoad**(path, [formatter], [key], [callback]) [<>](https://github.com/d3plus/d3plus/blob/main/packages/data/src/load.js#L8)
81
+
82
+ Loads data from a filepath or URL, converts it to a valid JSON object, and returns it to a callback function.
83
+
84
+
85
+ This is a global function
86
+
87
+ ---
88
+
89
+ <a name="merge"></a>
90
+ #### d3plus.**merge**(objects, aggs) [<>](https://github.com/d3plus/d3plus/blob/main/packages/data/src/merge.js#L4)
91
+
92
+ Combines an Array of Objects together and returns a new Object.
93
+
94
+
95
+ This is a global function
96
+ this
97
+
98
+ ```js
99
+ merge([
100
+ {id: "foo", group: "A", value: 10, links: [1, 2]},
101
+ {id: "bar", group: "A", value: 20, links: [1, 3]}
102
+ ]);
103
+
104
+ ```
105
+ returns this
106
+
107
+ ```js
108
+ {id: ["bar", "foo"], group: "A", value: 30, links: [1, 2, 3]}
109
+ ```
110
+
111
+ ---
112
+
113
+ <a name="unique"></a>
114
+ #### d3plus.**unique**(arr, [accessor]) [<>](https://github.com/d3plus/d3plus/blob/main/packages/data/src/unique.js#L1)
115
+
116
+ ES5 implementation to reduce an Array of values to unique instances.
117
+
118
+
119
+ This is a global function
120
+ this
121
+
122
+ ```js
123
+ unique(["apple", "banana", "apple"]);
124
+
125
+ ```
126
+ returns this
127
+
128
+ ```js
129
+ ["apple", "banana"]
130
+ ```
131
+
132
+ ---
133
+
134
+
135
+ ###### <sub>Documentation generated on Thu, 13 Mar 2025 19:58:29 GMT</sub>
package/es/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { default as addToQueue } from "./src/addToQueue.js";
2
+ export { default as concat } from "./src/concat.js";
3
+ export { default as fold } from "./src/fold.js";
4
+ export { default as isData } from "./src/isData.js";
5
+ export { default as merge } from "./src/merge.js";
6
+ export { default as load } from "./src/load.js";
7
+ export { default as nest } from "./src/nest.js";
8
+ export { default as unique } from "./src/unique.js";
@@ -0,0 +1,36 @@
1
+ function _instanceof(left, right) {
2
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
3
+ return !!right[Symbol.hasInstance](left);
4
+ } else {
5
+ return left instanceof right;
6
+ }
7
+ }
8
+ import isData from "./isData.js";
9
+ import load from "./load.js";
10
+ /**
11
+ @function isData
12
+ @desc Adds the provided value to the internal queue to be loaded, if necessary. This is used internally in new d3plus visualizations that fold in additional data sources, like the nodes and links of Network or the topojson of Geomap.
13
+ @param {Array|String|Object} data The data to be loaded
14
+ @param {Function} [data] An optional data formatter/callback
15
+ @param {String} data The internal Viz method to be modified
16
+ */ export default function(_, f, key) {
17
+ if (!_instanceof(_, Array)) _ = [
18
+ _
19
+ ];
20
+ var needToLoad = _.find(isData);
21
+ if (needToLoad) {
22
+ var prev = this._queue.find(function(q) {
23
+ return q[3] === key;
24
+ });
25
+ var d = [
26
+ load.bind(this),
27
+ _,
28
+ f,
29
+ key
30
+ ];
31
+ if (prev) this._queue[this._queue.indexOf(prev)] = d;
32
+ else this._queue.push(d);
33
+ } else {
34
+ this["_".concat(key)] = _;
35
+ }
36
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ @function dataConcat
3
+ @desc Reduce and concat all the elements included in arrayOfArrays if they are arrays. If it is a JSON object try to concat the array under given key data. If the key doesn't exists in object item, a warning message is lauched to the console. You need to implement DataFormat callback to concat the arrays manually.
4
+ @param {Array} arrayOfArray Array of elements
5
+ @param {String} [data = "data"] The key used for the flat data array if exists inside of the JSON object.
6
+ */ export default function(arrayOfArrays) {
7
+ var data = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "data";
8
+ return arrayOfArrays.reduce(function(acc, item) {
9
+ var dataArray = [];
10
+ if (Array.isArray(item)) {
11
+ dataArray = item;
12
+ } else {
13
+ if (item[data]) {
14
+ dataArray = item[data];
15
+ }
16
+ // else {
17
+ // console.warn(`d3plus-viz: Please implement a "dataFormat" callback to concat the arrays manually (consider using the d3plus.dataConcat method in your callback). Currently unable to concatenate (using key: "${data}") the following response:`, item);
18
+ // }
19
+ }
20
+ return acc.concat(dataArray);
21
+ }, []);
22
+ };
package/es/src/fold.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ @function dataFold
3
+ @desc Given a JSON object where the data values and headers have been split into separate key lookups, this function will combine the data values with the headers and returns one large array of objects.
4
+ @param {Object} json A JSON data Object with `data` and `headers` keys.
5
+ @param {String} [data = "data"] The key used for the flat data array inside of the JSON object.
6
+ @param {String} [headers = "headers"] The key used for the flat headers array inside of the JSON object.
7
+ */ export default function(json) {
8
+ var data = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "data", headers = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : "headers";
9
+ return json[data].map(function(data) {
10
+ return json[headers].reduce(function(obj, header, i) {
11
+ return obj[header] = data[i], obj;
12
+ }, {});
13
+ });
14
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ @function isData
3
+ @desc Returns true/false whether the argument provided to the function should be loaded using an internal XHR request. Valid data can either be a string URL or an Object with "url" and "headers" keys.
4
+ @param {*} dataItem The value to be tested
5
+ */ function _type_of(obj) {
6
+ "@swc/helpers - typeof";
7
+ return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
8
+ }
9
+ export default function(dataItem) {
10
+ return typeof dataItem === "string" || (typeof dataItem === "undefined" ? "undefined" : _type_of(dataItem)) === "object" && dataItem.url && dataItem.headers;
11
+ };
package/es/src/load.js ADDED
@@ -0,0 +1,138 @@
1
+ function _instanceof(left, right) {
2
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
3
+ return !!right[Symbol.hasInstance](left);
4
+ } else {
5
+ return left instanceof right;
6
+ }
7
+ }
8
+ function _type_of(obj) {
9
+ "@swc/helpers - typeof";
10
+ return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
11
+ }
12
+ import { csv, json, text, tsv } from "d3-request";
13
+ import { isObject } from "@d3plus/dom";
14
+ import fold from "./fold.js";
15
+ import concat from "./concat.js";
16
+ import isData from "./isData.js";
17
+ /**
18
+ @function dataLoad
19
+ @desc Loads data from a filepath or URL, converts it to a valid JSON object, and returns it to a callback function.
20
+ @param {Array|String} path The path to the file or url to be loaded. Also support array of paths strings. If an Array of objects is passed, the xhr request logic is skipped.
21
+ @param {Function} [formatter] An optional formatter function that is run on the loaded data.
22
+ @param {String} [key] The key in the `this` context to save the resulting data to.
23
+ @param {Function} [callback] A function that is called when the final data is loaded. It is passed 2 variables, any error present and the data loaded.
24
+ */ export default function(path, formatter, key, callback) {
25
+ var _this = this;
26
+ var parser;
27
+ var getParser = function(path) {
28
+ var ext = path.slice(path.length - 4);
29
+ switch(ext){
30
+ case ".csv":
31
+ return csv;
32
+ case ".tsv":
33
+ return tsv;
34
+ case ".txt":
35
+ return text;
36
+ default:
37
+ return json;
38
+ }
39
+ };
40
+ var validateData = function(err, parser, data) {
41
+ if (parser !== json && !err && data && _instanceof(data, Array)) {
42
+ data.forEach(function(d) {
43
+ for(var k in d){
44
+ if (!isNaN(d[k])) d[k] = parseFloat(d[k]);
45
+ else if (d[k].toLowerCase() === "false") d[k] = false;
46
+ else if (d[k].toLowerCase() === "true") d[k] = true;
47
+ else if (d[k].toLowerCase() === "null") d[k] = null;
48
+ else if (d[k].toLowerCase() === "undefined") d[k] = undefined;
49
+ }
50
+ });
51
+ }
52
+ return data;
53
+ };
54
+ var loadedLength = function(loadedArray) {
55
+ return loadedArray.reduce(function(prev, current) {
56
+ return current ? prev + 1 : prev;
57
+ }, 0);
58
+ };
59
+ var getPathIndex = function(url, array) {
60
+ return array.indexOf(url);
61
+ };
62
+ // If path param is a not an Array then convert path to a 1 element Array to re-use logic
63
+ if (!_instanceof(path, Array)) path = [
64
+ path
65
+ ];
66
+ var needToLoad = path.find(isData);
67
+ var loaded = new Array(path.length);
68
+ var toLoad = [];
69
+ // If there is a string I'm assuming is a Array to merge, urls or data
70
+ if (needToLoad) {
71
+ path.forEach(function(dataItem, ix) {
72
+ if (isData(dataItem)) toLoad.push(dataItem);
73
+ else loaded[ix] = dataItem;
74
+ });
75
+ } else {
76
+ loaded[0] = path;
77
+ }
78
+ // Load all urls an combine them with data arrays
79
+ var alreadyLoaded = loadedLength(loaded);
80
+ toLoad.forEach(function(dataItem) {
81
+ var headers = {}, url = dataItem;
82
+ if ((typeof dataItem === "undefined" ? "undefined" : _type_of(dataItem)) === "object") {
83
+ url = dataItem.url;
84
+ headers = dataItem.headers;
85
+ }
86
+ parser = getParser(url);
87
+ var request = parser(url);
88
+ for(var _$key in headers){
89
+ if (({}).hasOwnProperty.call(headers, _$key)) {
90
+ request.header(_$key, headers[_$key]);
91
+ }
92
+ }
93
+ request.get(function(err, data) {
94
+ data = err ? [] : data;
95
+ if (data && !_instanceof(data, Array) && data.data && data.headers) data = fold(data);
96
+ data = validateData(err, parser, data);
97
+ loaded[getPathIndex(url, path)] = data;
98
+ if (loadedLength(loaded) - alreadyLoaded === toLoad.length) {
99
+ // Format data
100
+ data = loadedLength(loaded) === 1 ? loaded[0] : loaded;
101
+ if (_this._cache) _this._lrucache.set("".concat(key, "_").concat(url), data);
102
+ if (formatter) {
103
+ var formatterResponse = formatter(loadedLength(loaded) === 1 ? loaded[0] : loaded);
104
+ if (key === "data" && isObject(formatterResponse)) {
105
+ data = formatterResponse.data || [];
106
+ delete formatterResponse.data;
107
+ _this.config(formatterResponse);
108
+ } else data = formatterResponse || [];
109
+ } else if (key === "data") {
110
+ data = concat(loaded, "data");
111
+ }
112
+ if (key && "_".concat(key) in _this) _this["_".concat(key)] = data;
113
+ if (callback) callback(err, data);
114
+ }
115
+ });
116
+ });
117
+ // If there is no data to Load response is immediately
118
+ if (toLoad.length === 0) {
119
+ loaded = loaded.map(function(data) {
120
+ if (data && !_instanceof(data, Array) && data.data && data.headers) data = fold(data);
121
+ return data;
122
+ });
123
+ // Format data
124
+ var data = loadedLength(loaded) === 1 ? loaded[0] : loaded;
125
+ if (formatter) {
126
+ var formatterResponse = formatter(loadedLength(loaded) === 1 ? loaded[0] : loaded);
127
+ if (key === "data" && isObject(formatterResponse)) {
128
+ data = formatterResponse.data || [];
129
+ delete formatterResponse.data;
130
+ this.config(formatterResponse);
131
+ } else data = formatterResponse || [];
132
+ } else if (key === "data") {
133
+ data = concat(loaded, "data");
134
+ }
135
+ if (key && "_".concat(key) in this) this["_".concat(key)] = data;
136
+ if (callback) callback(null, data);
137
+ }
138
+ }
@@ -0,0 +1,71 @@
1
+ function _instanceof(left, right) {
2
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
3
+ return !!right[Symbol.hasInstance](left);
4
+ } else {
5
+ return left instanceof right;
6
+ }
7
+ }
8
+ import { merge, sum } from "d3-array";
9
+ import unique from "./unique.js";
10
+ /**
11
+ @function merge
12
+ @desc Combines an Array of Objects together and returns a new Object.
13
+ @param {Array} objects The Array of objects to be merged together.
14
+ @param {Object} aggs An object containing specific aggregation methods (functions) for each key type. By default, numbers are summed and strings are returned as an array of unique values.
15
+ @example <caption>this</caption>
16
+ merge([
17
+ {id: "foo", group: "A", value: 10, links: [1, 2]},
18
+ {id: "bar", group: "A", value: 20, links: [1, 3]}
19
+ ]);
20
+ @example <caption>returns this</caption>
21
+ {id: ["bar", "foo"], group: "A", value: 30, links: [1, 2, 3]}
22
+ */ function objectMerge(objects) {
23
+ var aggs = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
24
+ var availableKeys = unique(merge(objects.map(function(o) {
25
+ return Object.keys(o);
26
+ }))), newObject = {};
27
+ availableKeys.forEach(function(k) {
28
+ var value;
29
+ if (aggs[k]) value = aggs[k](objects, function(o) {
30
+ return o[k];
31
+ });
32
+ else {
33
+ var values = objects.map(function(o) {
34
+ return o[k];
35
+ });
36
+ var types = values.map(function(v) {
37
+ return v || v === false ? v.constructor : v;
38
+ }).filter(function(v) {
39
+ return v !== void 0;
40
+ });
41
+ if (!types.length) value = undefined;
42
+ else if (types.indexOf(Array) >= 0) {
43
+ value = merge(values.map(function(v) {
44
+ return _instanceof(v, Array) ? v : [
45
+ v
46
+ ];
47
+ }));
48
+ value = unique(value);
49
+ if (value.length === 1) value = value[0];
50
+ } else if (types.indexOf(String) >= 0) {
51
+ value = unique(values);
52
+ if (value.length === 1) value = value[0];
53
+ } else if (types.indexOf(Number) >= 0) value = sum(values);
54
+ else if (types.indexOf(Object) >= 0) {
55
+ value = unique(values.filter(function(v) {
56
+ return v;
57
+ }));
58
+ if (value.length === 1) value = value[0];
59
+ else value = objectMerge(value);
60
+ } else {
61
+ value = unique(values.filter(function(v) {
62
+ return v !== void 0;
63
+ }));
64
+ if (value.length === 1) value = value[0];
65
+ }
66
+ }
67
+ newObject[k] = value;
68
+ });
69
+ return newObject;
70
+ }
71
+ export default objectMerge;
package/es/src/nest.js ADDED
@@ -0,0 +1,36 @@
1
+ function _instanceof(left, right) {
2
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
3
+ return !!right[Symbol.hasInstance](left);
4
+ } else {
5
+ return left instanceof right;
6
+ }
7
+ }
8
+ import { nest } from "d3-collection";
9
+ /**
10
+ @function nest
11
+ @summary Extends the base behavior of d3.nest to allow for multiple depth levels.
12
+ @param {Array} *data* The data array to be nested.
13
+ @param {Array} *keys* An array of key accessors that signify each nest level.
14
+ @private
15
+ */ export default function(data, keys) {
16
+ if (!_instanceof(keys, Array)) keys = [
17
+ keys
18
+ ];
19
+ var dataNest = nest();
20
+ for(var i = 0; i < keys.length; i++)dataNest.key(keys[i]);
21
+ var nestedData = dataNest.entries(data);
22
+ return bubble(nestedData);
23
+ }
24
+ /**
25
+ Bubbles up values that do not nest to the furthest key.
26
+ @param {Array} *values* The "values" of a nest object.
27
+ @private
28
+ */ function bubble(values) {
29
+ return values.map(function(d) {
30
+ if (d.key && d.values) {
31
+ if (d.values[0].key === "undefined") return d.values[0].values[0];
32
+ else d.values = bubble(d.values);
33
+ }
34
+ return d;
35
+ });
36
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ @function unique
3
+ @desc ES5 implementation to reduce an Array of values to unique instances.
4
+ @param {Array} arr The Array of objects to be filtered.
5
+ @param {Function} [accessor] An optional accessor function used to extract data points from an Array of Objects.
6
+ @example <caption>this</caption>
7
+ unique(["apple", "banana", "apple"]);
8
+ @example <caption>returns this</caption>
9
+ ["apple", "banana"]
10
+ */ function _instanceof(left, right) {
11
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
12
+ return !!right[Symbol.hasInstance](left);
13
+ } else {
14
+ return left instanceof right;
15
+ }
16
+ }
17
+ export default function(arr) {
18
+ var accessor = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : function(d) {
19
+ return d;
20
+ };
21
+ var values = arr.map(accessor).map(function(d) {
22
+ return _instanceof(d, Date) ? +d : d;
23
+ });
24
+ return arr.filter(function(obj, i) {
25
+ var d = accessor(obj);
26
+ return values.indexOf(_instanceof(d, Date) ? +d : d) === i;
27
+ });
28
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@d3plus/data",
3
+ "version": "3.0.0-alpha.0",
4
+ "description": "JavaScript data loading, manipulation, and analysis functions.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": "./es/index.js",
8
+ "browser": "./umd/d3plus-data.full.js",
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "sideEffects": false,
13
+ "files": [
14
+ "umd",
15
+ "es"
16
+ ],
17
+ "homepage": "https://d3plus.org",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/d3plus/d3plus.git",
21
+ "directory": "packages/data"
22
+ },
23
+ "keywords": [
24
+ "data",
25
+ "d3",
26
+ "d3plus",
27
+ "data",
28
+ "visualization"
29
+ ],
30
+ "scripts": {
31
+ "build:esm": "node ../../scripts/build-esm.js",
32
+ "build:umd": "node ../../scripts/build-umd.js",
33
+ "dev": "node ../../scripts/dev.js",
34
+ "test": "eslint index.js src/**/*.js && eslint --global=it test && mocha 'test/**/*-test.js'"
35
+ },
36
+ "dependencies": {
37
+ "@d3plus/dom": "3.0.0-alpha.0",
38
+ "d3-array": "^3.2.4",
39
+ "d3-collection": "^1.0.7",
40
+ "d3-request": "^1.0.6"
41
+ }
42
+ }