chartjs-plugin-chart2music 0.0.2 → 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 +291 -0
- package/dist/plugin.mjs +288 -0
- 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
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
var chartjs2music = (function (c2mChart) {
|
|
2
|
+
'use strict';
|
|
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
|
+
|
|
78
|
+
// let lastDataObj = "";
|
|
79
|
+
const chartjs_c2m_converter = {
|
|
80
|
+
bar: "bar",
|
|
81
|
+
line: "line",
|
|
82
|
+
pie: "pie",
|
|
83
|
+
polarArea: "bar",
|
|
84
|
+
doughnut: "pie",
|
|
85
|
+
boxplot: "box",
|
|
86
|
+
radar: "bar",
|
|
87
|
+
wordCloud: "bar"
|
|
88
|
+
};
|
|
89
|
+
const processChartType = (chart) => {
|
|
90
|
+
const topLevelType = chart.config.type;
|
|
91
|
+
const panelTypes = chart.data.datasets.map(({ type }) => type ?? topLevelType);
|
|
92
|
+
const invalid = panelTypes.find((t) => !(t in chartjs_c2m_converter));
|
|
93
|
+
if (invalid) {
|
|
94
|
+
return {
|
|
95
|
+
valid: false,
|
|
96
|
+
invalidType: invalid
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if ([...new Set(panelTypes)].length === 1) {
|
|
100
|
+
return {
|
|
101
|
+
valid: true,
|
|
102
|
+
c2m_types: chartjs_c2m_converter[panelTypes[0]]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
valid: true,
|
|
107
|
+
c2m_types: panelTypes.map((t) => chartjs_c2m_converter[t])
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
const generateAxisInfo = (chartAxisInfo, chart) => {
|
|
111
|
+
const axis = {};
|
|
112
|
+
if (chartAxisInfo?.min) {
|
|
113
|
+
if (typeof chartAxisInfo.min === "string") {
|
|
114
|
+
axis.minimum = chart.data.labels.indexOf(chartAxisInfo.min);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
axis.minimum = chartAxisInfo.min;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (chartAxisInfo?.max) {
|
|
121
|
+
if (typeof chartAxisInfo.max === "string") {
|
|
122
|
+
axis.maximum = chart.data.labels.indexOf(chartAxisInfo.max);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
axis.maximum = chartAxisInfo.max;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const label = chartAxisInfo?.title?.text;
|
|
129
|
+
if (label) {
|
|
130
|
+
axis.label = label;
|
|
131
|
+
}
|
|
132
|
+
if (chartAxisInfo?.type === "logarithmic") {
|
|
133
|
+
axis.type = "log10";
|
|
134
|
+
}
|
|
135
|
+
return axis;
|
|
136
|
+
};
|
|
137
|
+
const generateAxes = (chart) => {
|
|
138
|
+
const axes = {
|
|
139
|
+
x: {
|
|
140
|
+
...generateAxisInfo(chart.options?.scales?.x, chart),
|
|
141
|
+
valueLabels: chart.data.labels.slice(0)
|
|
142
|
+
},
|
|
143
|
+
y: {
|
|
144
|
+
...generateAxisInfo(chart.options?.scales?.y, chart),
|
|
145
|
+
format: (value) => value.toLocaleString()
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
return axes;
|
|
149
|
+
};
|
|
150
|
+
const whichDataStructure = (data) => {
|
|
151
|
+
if (Array.isArray(data[0])) {
|
|
152
|
+
return data.map((arr, x) => {
|
|
153
|
+
let [low, high] = arr.sort();
|
|
154
|
+
return { x, low, high };
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return data;
|
|
158
|
+
};
|
|
159
|
+
const scrubX = (data) => {
|
|
160
|
+
const blackboard = JSON.parse(JSON.stringify(data));
|
|
161
|
+
let labels = [];
|
|
162
|
+
if (Array.isArray(data)) {
|
|
163
|
+
// console.log("not grouped");
|
|
164
|
+
// Not grouped
|
|
165
|
+
blackboard.forEach((item, x) => {
|
|
166
|
+
if (typeof item === "object" && "x" in item) {
|
|
167
|
+
labels.push(item.x);
|
|
168
|
+
item.x = x;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
return { labels, data: blackboard };
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const processData = (data, c2m_types) => {
|
|
175
|
+
if (c2m_types === "box") {
|
|
176
|
+
return processBoxData(data);
|
|
177
|
+
}
|
|
178
|
+
let groups = [];
|
|
179
|
+
if (data.datasets.length === 1) {
|
|
180
|
+
return {
|
|
181
|
+
data: whichDataStructure(data.datasets[0].data)
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const result = {};
|
|
185
|
+
data.datasets.forEach((obj, index) => {
|
|
186
|
+
const groupName = obj.label ?? `Group ${index + 1}`;
|
|
187
|
+
groups.push(groupName);
|
|
188
|
+
result[groupName] = whichDataStructure(obj.data);
|
|
189
|
+
});
|
|
190
|
+
return { groups, data: result };
|
|
191
|
+
};
|
|
192
|
+
const determineChartTitle = (options) => {
|
|
193
|
+
if (options.plugins?.title?.text) {
|
|
194
|
+
if (Array.isArray(options.plugins.title.text)) {
|
|
195
|
+
return options.plugins.title.text.join(", ");
|
|
196
|
+
}
|
|
197
|
+
return options.plugins.title.text;
|
|
198
|
+
}
|
|
199
|
+
return "";
|
|
200
|
+
};
|
|
201
|
+
const determineCCElement = (canvas, provided) => {
|
|
202
|
+
if (provided) {
|
|
203
|
+
return provided;
|
|
204
|
+
}
|
|
205
|
+
const cc = document.createElement("div");
|
|
206
|
+
canvas.insertAdjacentElement("afterend", cc);
|
|
207
|
+
return cc;
|
|
208
|
+
};
|
|
209
|
+
const plugin = {
|
|
210
|
+
id: "chartjs2music",
|
|
211
|
+
afterInit: (chart, args, options) => {
|
|
212
|
+
const { valid, c2m_types, invalidType } = processChartType(chart);
|
|
213
|
+
if (!valid) {
|
|
214
|
+
options.errorCallback?.(`Unable to connect chart2music to chart. The chart is of type "${invalidType}", which is not one of the supported chart types for this plugin. This plugin supports: ${Object.keys(chartjs_c2m_converter).join(", ")}`);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
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
|
+
}
|
|
230
|
+
// Generate CC element
|
|
231
|
+
const cc = determineCCElement(chart.canvas, options.cc);
|
|
232
|
+
const { data, groups } = processData(chart.data, c2m_types);
|
|
233
|
+
// lastDataObj = JSON.stringify(data);
|
|
234
|
+
let scrub = scrubX(data);
|
|
235
|
+
if (scrub?.labels && scrub?.labels?.length > 0) { // Something was scrubbed
|
|
236
|
+
if (!chart.data.labels || chart.data.labels.length === 0) {
|
|
237
|
+
axes.x.valueLabels = scrub.labels.slice(0);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const c2mOptions = {
|
|
241
|
+
cc,
|
|
242
|
+
element: chart.canvas,
|
|
243
|
+
type: c2m_types,
|
|
244
|
+
data: scrub?.data ?? data,
|
|
245
|
+
title: determineChartTitle(chart.options),
|
|
246
|
+
axes,
|
|
247
|
+
options: {
|
|
248
|
+
// @ts-ignore
|
|
249
|
+
onFocusCallback: ({ slice, index }) => {
|
|
250
|
+
try {
|
|
251
|
+
const highlightElements = [{
|
|
252
|
+
datasetIndex: groups?.indexOf(slice) ?? 0,
|
|
253
|
+
index
|
|
254
|
+
}];
|
|
255
|
+
chart?.setActiveElements(highlightElements);
|
|
256
|
+
chart?.tooltip?.setActiveElements(highlightElements, {});
|
|
257
|
+
chart?.update();
|
|
258
|
+
}
|
|
259
|
+
catch (e) {
|
|
260
|
+
// console.warn(e);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
if (options.audioEngine) {
|
|
266
|
+
// @ts-ignore
|
|
267
|
+
c2mOptions.audioEngine = options.audioEngine;
|
|
268
|
+
}
|
|
269
|
+
const { err, data: c2m } = c2mChart(c2mOptions);
|
|
270
|
+
// /* istanbul-ignore-next */
|
|
271
|
+
if (err) {
|
|
272
|
+
options.errorCallback?.(err);
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
// afterUpdate: (chart: Chart) => {
|
|
276
|
+
// const {data} = processData(chart.data);
|
|
277
|
+
// if(JSON.stringify(data) !== lastDataObj){
|
|
278
|
+
// c2mChartRef?.setData(data);
|
|
279
|
+
// lastDataObj = JSON.stringify(data);
|
|
280
|
+
// }
|
|
281
|
+
// },
|
|
282
|
+
defaults: {
|
|
283
|
+
cc: null,
|
|
284
|
+
audioEngine: null,
|
|
285
|
+
errorCallback: null
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
return plugin;
|
|
290
|
+
|
|
291
|
+
})(c2mChart);
|
package/dist/plugin.mjs
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import c2mChart from 'chart2music';
|
|
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
|
+
|
|
77
|
+
// let lastDataObj = "";
|
|
78
|
+
const chartjs_c2m_converter = {
|
|
79
|
+
bar: "bar",
|
|
80
|
+
line: "line",
|
|
81
|
+
pie: "pie",
|
|
82
|
+
polarArea: "bar",
|
|
83
|
+
doughnut: "pie",
|
|
84
|
+
boxplot: "box",
|
|
85
|
+
radar: "bar",
|
|
86
|
+
wordCloud: "bar"
|
|
87
|
+
};
|
|
88
|
+
const processChartType = (chart) => {
|
|
89
|
+
const topLevelType = chart.config.type;
|
|
90
|
+
const panelTypes = chart.data.datasets.map(({ type }) => type ?? topLevelType);
|
|
91
|
+
const invalid = panelTypes.find((t) => !(t in chartjs_c2m_converter));
|
|
92
|
+
if (invalid) {
|
|
93
|
+
return {
|
|
94
|
+
valid: false,
|
|
95
|
+
invalidType: invalid
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if ([...new Set(panelTypes)].length === 1) {
|
|
99
|
+
return {
|
|
100
|
+
valid: true,
|
|
101
|
+
c2m_types: chartjs_c2m_converter[panelTypes[0]]
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
valid: true,
|
|
106
|
+
c2m_types: panelTypes.map((t) => chartjs_c2m_converter[t])
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
const generateAxisInfo = (chartAxisInfo, chart) => {
|
|
110
|
+
const axis = {};
|
|
111
|
+
if (chartAxisInfo?.min) {
|
|
112
|
+
if (typeof chartAxisInfo.min === "string") {
|
|
113
|
+
axis.minimum = chart.data.labels.indexOf(chartAxisInfo.min);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
axis.minimum = chartAxisInfo.min;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (chartAxisInfo?.max) {
|
|
120
|
+
if (typeof chartAxisInfo.max === "string") {
|
|
121
|
+
axis.maximum = chart.data.labels.indexOf(chartAxisInfo.max);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
axis.maximum = chartAxisInfo.max;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const label = chartAxisInfo?.title?.text;
|
|
128
|
+
if (label) {
|
|
129
|
+
axis.label = label;
|
|
130
|
+
}
|
|
131
|
+
if (chartAxisInfo?.type === "logarithmic") {
|
|
132
|
+
axis.type = "log10";
|
|
133
|
+
}
|
|
134
|
+
return axis;
|
|
135
|
+
};
|
|
136
|
+
const generateAxes = (chart) => {
|
|
137
|
+
const axes = {
|
|
138
|
+
x: {
|
|
139
|
+
...generateAxisInfo(chart.options?.scales?.x, chart),
|
|
140
|
+
valueLabels: chart.data.labels.slice(0)
|
|
141
|
+
},
|
|
142
|
+
y: {
|
|
143
|
+
...generateAxisInfo(chart.options?.scales?.y, chart),
|
|
144
|
+
format: (value) => value.toLocaleString()
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
return axes;
|
|
148
|
+
};
|
|
149
|
+
const whichDataStructure = (data) => {
|
|
150
|
+
if (Array.isArray(data[0])) {
|
|
151
|
+
return data.map((arr, x) => {
|
|
152
|
+
let [low, high] = arr.sort();
|
|
153
|
+
return { x, low, high };
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return data;
|
|
157
|
+
};
|
|
158
|
+
const scrubX = (data) => {
|
|
159
|
+
const blackboard = JSON.parse(JSON.stringify(data));
|
|
160
|
+
let labels = [];
|
|
161
|
+
if (Array.isArray(data)) {
|
|
162
|
+
// console.log("not grouped");
|
|
163
|
+
// Not grouped
|
|
164
|
+
blackboard.forEach((item, x) => {
|
|
165
|
+
if (typeof item === "object" && "x" in item) {
|
|
166
|
+
labels.push(item.x);
|
|
167
|
+
item.x = x;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
return { labels, data: blackboard };
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const processData = (data, c2m_types) => {
|
|
174
|
+
if (c2m_types === "box") {
|
|
175
|
+
return processBoxData(data);
|
|
176
|
+
}
|
|
177
|
+
let groups = [];
|
|
178
|
+
if (data.datasets.length === 1) {
|
|
179
|
+
return {
|
|
180
|
+
data: whichDataStructure(data.datasets[0].data)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const result = {};
|
|
184
|
+
data.datasets.forEach((obj, index) => {
|
|
185
|
+
const groupName = obj.label ?? `Group ${index + 1}`;
|
|
186
|
+
groups.push(groupName);
|
|
187
|
+
result[groupName] = whichDataStructure(obj.data);
|
|
188
|
+
});
|
|
189
|
+
return { groups, data: result };
|
|
190
|
+
};
|
|
191
|
+
const determineChartTitle = (options) => {
|
|
192
|
+
if (options.plugins?.title?.text) {
|
|
193
|
+
if (Array.isArray(options.plugins.title.text)) {
|
|
194
|
+
return options.plugins.title.text.join(", ");
|
|
195
|
+
}
|
|
196
|
+
return options.plugins.title.text;
|
|
197
|
+
}
|
|
198
|
+
return "";
|
|
199
|
+
};
|
|
200
|
+
const determineCCElement = (canvas, provided) => {
|
|
201
|
+
if (provided) {
|
|
202
|
+
return provided;
|
|
203
|
+
}
|
|
204
|
+
const cc = document.createElement("div");
|
|
205
|
+
canvas.insertAdjacentElement("afterend", cc);
|
|
206
|
+
return cc;
|
|
207
|
+
};
|
|
208
|
+
const plugin = {
|
|
209
|
+
id: "chartjs2music",
|
|
210
|
+
afterInit: (chart, args, options) => {
|
|
211
|
+
const { valid, c2m_types, invalidType } = processChartType(chart);
|
|
212
|
+
if (!valid) {
|
|
213
|
+
options.errorCallback?.(`Unable to connect chart2music to chart. The chart is of type "${invalidType}", which is not one of the supported chart types for this plugin. This plugin supports: ${Object.keys(chartjs_c2m_converter).join(", ")}`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
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
|
+
}
|
|
229
|
+
// Generate CC element
|
|
230
|
+
const cc = determineCCElement(chart.canvas, options.cc);
|
|
231
|
+
const { data, groups } = processData(chart.data, c2m_types);
|
|
232
|
+
// lastDataObj = JSON.stringify(data);
|
|
233
|
+
let scrub = scrubX(data);
|
|
234
|
+
if (scrub?.labels && scrub?.labels?.length > 0) { // Something was scrubbed
|
|
235
|
+
if (!chart.data.labels || chart.data.labels.length === 0) {
|
|
236
|
+
axes.x.valueLabels = scrub.labels.slice(0);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const c2mOptions = {
|
|
240
|
+
cc,
|
|
241
|
+
element: chart.canvas,
|
|
242
|
+
type: c2m_types,
|
|
243
|
+
data: scrub?.data ?? data,
|
|
244
|
+
title: determineChartTitle(chart.options),
|
|
245
|
+
axes,
|
|
246
|
+
options: {
|
|
247
|
+
// @ts-ignore
|
|
248
|
+
onFocusCallback: ({ slice, index }) => {
|
|
249
|
+
try {
|
|
250
|
+
const highlightElements = [{
|
|
251
|
+
datasetIndex: groups?.indexOf(slice) ?? 0,
|
|
252
|
+
index
|
|
253
|
+
}];
|
|
254
|
+
chart?.setActiveElements(highlightElements);
|
|
255
|
+
chart?.tooltip?.setActiveElements(highlightElements, {});
|
|
256
|
+
chart?.update();
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
// console.warn(e);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
if (options.audioEngine) {
|
|
265
|
+
// @ts-ignore
|
|
266
|
+
c2mOptions.audioEngine = options.audioEngine;
|
|
267
|
+
}
|
|
268
|
+
const { err, data: c2m } = c2mChart(c2mOptions);
|
|
269
|
+
// /* istanbul-ignore-next */
|
|
270
|
+
if (err) {
|
|
271
|
+
options.errorCallback?.(err);
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
// afterUpdate: (chart: Chart) => {
|
|
275
|
+
// const {data} = processData(chart.data);
|
|
276
|
+
// if(JSON.stringify(data) !== lastDataObj){
|
|
277
|
+
// c2mChartRef?.setData(data);
|
|
278
|
+
// lastDataObj = JSON.stringify(data);
|
|
279
|
+
// }
|
|
280
|
+
// },
|
|
281
|
+
defaults: {
|
|
282
|
+
cc: null,
|
|
283
|
+
audioEngine: null,
|
|
284
|
+
errorCallback: null
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export { plugin as default };
|
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
|
+
}
|