chartjs-plugin-chart2music 0.0.3 → 0.0.4
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 +10 -2
- package/dist/plugin.js +95 -3
- package/dist/plugin.mjs +95 -3
- package/package.json +59 -55
package/README.md
CHANGED
|
@@ -39,7 +39,8 @@ new Chart(canvasElement, {
|
|
|
39
39
|
This plugin is currently in beta, so not all of the chart.js features are currently supported.
|
|
40
40
|
|
|
41
41
|
A quick list of chart.js features we currently support includes:
|
|
42
|
-
* Chart types: bar, line, pie, doughnut, polar, and combinations therein.
|
|
42
|
+
* Chart types: bar, line, pie, doughnut, polar, radar, and combinations therein.
|
|
43
|
+
* Boxplots using the `@sgratzl/chartjs-chart-boxplot` plugin (only support boxplots when there are no other chart types present)
|
|
43
44
|
* Axes options: `title`, `min`, `max`, `type="linear"`, `type="logarithmic"`.
|
|
44
45
|
* Chart title
|
|
45
46
|
* Most data structures (not including `parsing` or non-standard axes identifiers)
|
|
@@ -55,4 +56,11 @@ Things we plan to support in the future:
|
|
|
55
56
|
* Locale
|
|
56
57
|
* Dataset visibility (when you show/hide a category from the legend)
|
|
57
58
|
* Radar charts
|
|
58
|
-
* Custom formatting for axis tick values
|
|
59
|
+
* Custom formatting for axis tick values
|
|
60
|
+
|
|
61
|
+
Plugins we plan to support in the future:
|
|
62
|
+
* [chartjs-char-error-bars](https://www.npmjs.com/package/chartjs-chart-error-bars)
|
|
63
|
+
* [chartjs-chart-matrix](https://www.npmjs.com/package/chartjs-chart-matrix)
|
|
64
|
+
* [chartjs-chart-pcp](https://www.npmjs.com/package/chartjs-chart-pcp)
|
|
65
|
+
* [chartjs-chart-wordcloud](https://www.npmjs.com/package/chartjs-chart-wordcloud)
|
|
66
|
+
* [chartjs-plugin-zoom](https://www.npmjs.com/package/chartjs-plugin-zoom)
|
package/dist/plugin.js
CHANGED
|
@@ -1,13 +1,90 @@
|
|
|
1
1
|
var chartjs2music = (function (c2mChart) {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
const calcMedian = (nums) => (nums.length % 2) ? nums[Math.floor(nums.length / 2)] : (nums[nums.length / 2] + nums[nums.length / 2 - 1]) / 2;
|
|
5
|
+
const fiveNumberSummary = (nums) => {
|
|
6
|
+
const sortedNumbers = nums.sort();
|
|
7
|
+
let min, max, q1, q3;
|
|
8
|
+
const datamin = Math.min(...sortedNumbers);
|
|
9
|
+
const datamax = Math.max(...sortedNumbers);
|
|
10
|
+
const median = calcMedian(sortedNumbers);
|
|
11
|
+
const length = sortedNumbers.length;
|
|
12
|
+
if (length % 2) {
|
|
13
|
+
q1 = calcMedian(sortedNumbers.slice(0, Math.floor(length / 2)));
|
|
14
|
+
q3 = calcMedian(sortedNumbers.slice(Math.ceil(length % 2)));
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
q1 = calcMedian(sortedNumbers.slice(0, length / 2 - 1));
|
|
18
|
+
q3 = calcMedian(sortedNumbers.slice(length / 2));
|
|
19
|
+
}
|
|
20
|
+
const iqr = q3 - q1;
|
|
21
|
+
if (datamax < q3 + iqr) {
|
|
22
|
+
max = datamax;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
max = sortedNumbers.reverse().find((num) => num < datamax) ?? datamax;
|
|
26
|
+
}
|
|
27
|
+
if (datamin < q1 - iqr) {
|
|
28
|
+
min = datamin;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
min = sortedNumbers.reverse().find((num) => num < datamin) ?? datamin;
|
|
32
|
+
}
|
|
33
|
+
const outlier = sortedNumbers.filter((num) => num < min || num > max);
|
|
34
|
+
return {
|
|
35
|
+
low: min,
|
|
36
|
+
high: max,
|
|
37
|
+
median,
|
|
38
|
+
q1,
|
|
39
|
+
q3,
|
|
40
|
+
...(outlier.length > 0 ? { outlier } : {})
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
const whichBoxData = (data) => {
|
|
44
|
+
return data.map((row, index) => {
|
|
45
|
+
if (typeof row === "object" && "min" in row) {
|
|
46
|
+
return {
|
|
47
|
+
...row,
|
|
48
|
+
low: row.min,
|
|
49
|
+
high: row.max,
|
|
50
|
+
x: index,
|
|
51
|
+
...("outliers" in row ? { outlier: row.outliers } : {})
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(row)) {
|
|
55
|
+
return {
|
|
56
|
+
...fiveNumberSummary(row),
|
|
57
|
+
x: index
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
const processBoxData = (data) => {
|
|
63
|
+
if (data.datasets.length === 1) {
|
|
64
|
+
return {
|
|
65
|
+
data: whichBoxData(data.datasets[0].data)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const groups = [];
|
|
69
|
+
const result = {};
|
|
70
|
+
data.datasets.forEach((obj, index) => {
|
|
71
|
+
const groupName = obj.label ?? `Group ${index + 1}`;
|
|
72
|
+
groups.push(groupName);
|
|
73
|
+
result[groupName] = whichBoxData(obj.data);
|
|
74
|
+
});
|
|
75
|
+
return { groups, data: result };
|
|
76
|
+
};
|
|
77
|
+
|
|
4
78
|
// let lastDataObj = "";
|
|
5
79
|
const chartjs_c2m_converter = {
|
|
6
80
|
bar: "bar",
|
|
7
81
|
line: "line",
|
|
8
82
|
pie: "pie",
|
|
9
83
|
polarArea: "bar",
|
|
10
|
-
doughnut: "pie"
|
|
84
|
+
doughnut: "pie",
|
|
85
|
+
boxplot: "box",
|
|
86
|
+
radar: "bar",
|
|
87
|
+
wordCloud: "bar"
|
|
11
88
|
};
|
|
12
89
|
const processChartType = (chart) => {
|
|
13
90
|
const topLevelType = chart.config.type;
|
|
@@ -94,7 +171,10 @@ var chartjs2music = (function (c2mChart) {
|
|
|
94
171
|
return { labels, data: blackboard };
|
|
95
172
|
}
|
|
96
173
|
};
|
|
97
|
-
const processData = (data) => {
|
|
174
|
+
const processData = (data, c2m_types) => {
|
|
175
|
+
if (c2m_types === "box") {
|
|
176
|
+
return processBoxData(data);
|
|
177
|
+
}
|
|
98
178
|
let groups = [];
|
|
99
179
|
if (data.datasets.length === 1) {
|
|
100
180
|
return {
|
|
@@ -135,9 +215,21 @@ var chartjs2music = (function (c2mChart) {
|
|
|
135
215
|
return;
|
|
136
216
|
}
|
|
137
217
|
const axes = generateAxes(chart);
|
|
218
|
+
if (chart.config.type === "wordCloud") {
|
|
219
|
+
delete axes.x.minimum;
|
|
220
|
+
delete axes.x.maximum;
|
|
221
|
+
delete axes.y.minimum;
|
|
222
|
+
delete axes.y.maximum;
|
|
223
|
+
if (!axes.x.label) {
|
|
224
|
+
axes.x.label = "Word";
|
|
225
|
+
}
|
|
226
|
+
if (!axes.y.label) {
|
|
227
|
+
axes.y.label = "Emphasis";
|
|
228
|
+
}
|
|
229
|
+
}
|
|
138
230
|
// Generate CC element
|
|
139
231
|
const cc = determineCCElement(chart.canvas, options.cc);
|
|
140
|
-
const { data, groups } = processData(chart.data);
|
|
232
|
+
const { data, groups } = processData(chart.data, c2m_types);
|
|
141
233
|
// lastDataObj = JSON.stringify(data);
|
|
142
234
|
let scrub = scrubX(data);
|
|
143
235
|
if (scrub?.labels && scrub?.labels?.length > 0) { // Something was scrubbed
|
package/dist/plugin.mjs
CHANGED
|
@@ -1,12 +1,89 @@
|
|
|
1
1
|
import c2mChart from 'chart2music';
|
|
2
2
|
|
|
3
|
+
const calcMedian = (nums) => (nums.length % 2) ? nums[Math.floor(nums.length / 2)] : (nums[nums.length / 2] + nums[nums.length / 2 - 1]) / 2;
|
|
4
|
+
const fiveNumberSummary = (nums) => {
|
|
5
|
+
const sortedNumbers = nums.sort();
|
|
6
|
+
let min, max, q1, q3;
|
|
7
|
+
const datamin = Math.min(...sortedNumbers);
|
|
8
|
+
const datamax = Math.max(...sortedNumbers);
|
|
9
|
+
const median = calcMedian(sortedNumbers);
|
|
10
|
+
const length = sortedNumbers.length;
|
|
11
|
+
if (length % 2) {
|
|
12
|
+
q1 = calcMedian(sortedNumbers.slice(0, Math.floor(length / 2)));
|
|
13
|
+
q3 = calcMedian(sortedNumbers.slice(Math.ceil(length % 2)));
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
q1 = calcMedian(sortedNumbers.slice(0, length / 2 - 1));
|
|
17
|
+
q3 = calcMedian(sortedNumbers.slice(length / 2));
|
|
18
|
+
}
|
|
19
|
+
const iqr = q3 - q1;
|
|
20
|
+
if (datamax < q3 + iqr) {
|
|
21
|
+
max = datamax;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
max = sortedNumbers.reverse().find((num) => num < datamax) ?? datamax;
|
|
25
|
+
}
|
|
26
|
+
if (datamin < q1 - iqr) {
|
|
27
|
+
min = datamin;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
min = sortedNumbers.reverse().find((num) => num < datamin) ?? datamin;
|
|
31
|
+
}
|
|
32
|
+
const outlier = sortedNumbers.filter((num) => num < min || num > max);
|
|
33
|
+
return {
|
|
34
|
+
low: min,
|
|
35
|
+
high: max,
|
|
36
|
+
median,
|
|
37
|
+
q1,
|
|
38
|
+
q3,
|
|
39
|
+
...(outlier.length > 0 ? { outlier } : {})
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
const whichBoxData = (data) => {
|
|
43
|
+
return data.map((row, index) => {
|
|
44
|
+
if (typeof row === "object" && "min" in row) {
|
|
45
|
+
return {
|
|
46
|
+
...row,
|
|
47
|
+
low: row.min,
|
|
48
|
+
high: row.max,
|
|
49
|
+
x: index,
|
|
50
|
+
...("outliers" in row ? { outlier: row.outliers } : {})
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (Array.isArray(row)) {
|
|
54
|
+
return {
|
|
55
|
+
...fiveNumberSummary(row),
|
|
56
|
+
x: index
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
const processBoxData = (data) => {
|
|
62
|
+
if (data.datasets.length === 1) {
|
|
63
|
+
return {
|
|
64
|
+
data: whichBoxData(data.datasets[0].data)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const groups = [];
|
|
68
|
+
const result = {};
|
|
69
|
+
data.datasets.forEach((obj, index) => {
|
|
70
|
+
const groupName = obj.label ?? `Group ${index + 1}`;
|
|
71
|
+
groups.push(groupName);
|
|
72
|
+
result[groupName] = whichBoxData(obj.data);
|
|
73
|
+
});
|
|
74
|
+
return { groups, data: result };
|
|
75
|
+
};
|
|
76
|
+
|
|
3
77
|
// let lastDataObj = "";
|
|
4
78
|
const chartjs_c2m_converter = {
|
|
5
79
|
bar: "bar",
|
|
6
80
|
line: "line",
|
|
7
81
|
pie: "pie",
|
|
8
82
|
polarArea: "bar",
|
|
9
|
-
doughnut: "pie"
|
|
83
|
+
doughnut: "pie",
|
|
84
|
+
boxplot: "box",
|
|
85
|
+
radar: "bar",
|
|
86
|
+
wordCloud: "bar"
|
|
10
87
|
};
|
|
11
88
|
const processChartType = (chart) => {
|
|
12
89
|
const topLevelType = chart.config.type;
|
|
@@ -93,7 +170,10 @@ const scrubX = (data) => {
|
|
|
93
170
|
return { labels, data: blackboard };
|
|
94
171
|
}
|
|
95
172
|
};
|
|
96
|
-
const processData = (data) => {
|
|
173
|
+
const processData = (data, c2m_types) => {
|
|
174
|
+
if (c2m_types === "box") {
|
|
175
|
+
return processBoxData(data);
|
|
176
|
+
}
|
|
97
177
|
let groups = [];
|
|
98
178
|
if (data.datasets.length === 1) {
|
|
99
179
|
return {
|
|
@@ -134,9 +214,21 @@ const plugin = {
|
|
|
134
214
|
return;
|
|
135
215
|
}
|
|
136
216
|
const axes = generateAxes(chart);
|
|
217
|
+
if (chart.config.type === "wordCloud") {
|
|
218
|
+
delete axes.x.minimum;
|
|
219
|
+
delete axes.x.maximum;
|
|
220
|
+
delete axes.y.minimum;
|
|
221
|
+
delete axes.y.maximum;
|
|
222
|
+
if (!axes.x.label) {
|
|
223
|
+
axes.x.label = "Word";
|
|
224
|
+
}
|
|
225
|
+
if (!axes.y.label) {
|
|
226
|
+
axes.y.label = "Emphasis";
|
|
227
|
+
}
|
|
228
|
+
}
|
|
137
229
|
// Generate CC element
|
|
138
230
|
const cc = determineCCElement(chart.canvas, options.cc);
|
|
139
|
-
const { data, groups } = processData(chart.data);
|
|
231
|
+
const { data, groups } = processData(chart.data, c2m_types);
|
|
140
232
|
// lastDataObj = JSON.stringify(data);
|
|
141
233
|
let scrub = scrubX(data);
|
|
142
234
|
if (scrub?.labels && scrub?.labels?.length > 0) { // Something was scrubbed
|
package/package.json
CHANGED
|
@@ -1,55 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "chartjs-plugin-chart2music",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "Chart.js plugin for Chart2Music. Turns chart.js charts into music so the blind can hear data.",
|
|
6
|
-
"main": "dist/plugin.js",
|
|
7
|
-
"module": "dist/plugin.mjs",
|
|
8
|
-
"exports": {
|
|
9
|
-
|
|
10
|
-
},
|
|
11
|
-
"files": [
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
}
|
|
55
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "chartjs-plugin-chart2music",
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Chart.js plugin for Chart2Music. Turns chart.js charts into music so the blind can hear data.",
|
|
6
|
+
"main": "dist/plugin.js",
|
|
7
|
+
"module": "dist/plugin.mjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
"import": "./dist/plugin.mjs"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/*"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"a11y",
|
|
16
|
+
"accessibility",
|
|
17
|
+
"chart.js",
|
|
18
|
+
"chart",
|
|
19
|
+
"dataviz",
|
|
20
|
+
"screen reader",
|
|
21
|
+
"sonification"
|
|
22
|
+
],
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/julianna-langston/chartjs2music"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/julianna-langston/chartjs2music/issues"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"scripts": {
|
|
32
|
+
"start": "vite",
|
|
33
|
+
"build": "rollup -c rollup.config.js --silent",
|
|
34
|
+
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest",
|
|
35
|
+
"depcheck": "depcheck",
|
|
36
|
+
"clean": "rimraf dist coverage"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@rollup/plugin-typescript": "^11.0.0",
|
|
40
|
+
"@sgratzl/chartjs-chart-boxplot": "^4.1.1",
|
|
41
|
+
"@types/jest": "^29.4.0",
|
|
42
|
+
"canvas": "^2.11.0",
|
|
43
|
+
"chartjs-chart-wordcloud": "^4.1.1",
|
|
44
|
+
"cross-env": "^7.0.3",
|
|
45
|
+
"depcheck": "^1.4.3",
|
|
46
|
+
"jest": "^29.4.2",
|
|
47
|
+
"jest-environment-jsdom": "^29.4.2",
|
|
48
|
+
"rimraf": "^4.1.2",
|
|
49
|
+
"rollup": "^3.15.0",
|
|
50
|
+
"ts-jest": "^29.0.5",
|
|
51
|
+
"tslib": "^2.5.0",
|
|
52
|
+
"typescript": "^4.9.3",
|
|
53
|
+
"vite": "^4.1.0"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"chart.js": "^4.2.1",
|
|
57
|
+
"chart2music": "^1.8.2"
|
|
58
|
+
}
|
|
59
|
+
}
|