chartjs-plugin-chart2music 0.6.1 → 0.7.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 +93 -77
- package/dist/plugin.amd.js +174 -43
- package/dist/plugin.cjs +333 -230
- package/dist/plugin.d.ts +8 -1
- package/dist/plugin.js +174 -43
- package/dist/plugin.mjs +174 -43
- package/package.json +52 -23
package/dist/plugin.d.ts
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import type { Plugin } from "chart.js";
|
|
2
|
+
import type { C2MChartConfig } from "chart2music";
|
|
3
|
+
|
|
4
|
+
export type C2MPluginOptions = Pick<C2MChartConfig, "audioEngine" | "axes" | "cc" | "lang" | "options"> & {
|
|
5
|
+
errorCallback?: (err: string) => void;
|
|
6
|
+
}
|
|
7
|
+
declare const plugin: Plugin;
|
|
8
|
+
export default plugin;
|
package/dist/plugin.js
CHANGED
|
@@ -85,7 +85,8 @@ var chartjs2music = (function (c2mChart) {
|
|
|
85
85
|
boxplot: "box",
|
|
86
86
|
radar: "bar",
|
|
87
87
|
wordCloud: "bar",
|
|
88
|
-
scatter: "scatter"
|
|
88
|
+
scatter: "scatter",
|
|
89
|
+
matrix: "matrix"
|
|
89
90
|
};
|
|
90
91
|
const processChartType = (chart) => {
|
|
91
92
|
const topLevelType = chart.config.type;
|
|
@@ -135,13 +136,13 @@ var chartjs2music = (function (c2mChart) {
|
|
|
135
136
|
}
|
|
136
137
|
return axis;
|
|
137
138
|
};
|
|
138
|
-
const generateAxes = (chart) => {
|
|
139
|
+
const generateAxes = (chart, options) => {
|
|
139
140
|
const axes = {
|
|
140
141
|
x: {
|
|
141
142
|
...generateAxisInfo(chart.options?.scales?.x, chart),
|
|
142
143
|
},
|
|
143
144
|
y: {
|
|
144
|
-
format: (value) => value.toLocaleString(),
|
|
145
|
+
format: options?.axes?.y?.format || ((value) => value.toLocaleString()),
|
|
145
146
|
...generateAxisInfo(chart.options?.scales?.y, chart),
|
|
146
147
|
}
|
|
147
148
|
};
|
|
@@ -160,6 +161,55 @@ var chartjs2music = (function (c2mChart) {
|
|
|
160
161
|
}
|
|
161
162
|
return data;
|
|
162
163
|
};
|
|
164
|
+
const labelIndex = (labels, value) => {
|
|
165
|
+
let index = labels.indexOf(value);
|
|
166
|
+
if (index === -1) {
|
|
167
|
+
labels.push(value);
|
|
168
|
+
index = labels.length - 1;
|
|
169
|
+
}
|
|
170
|
+
return index;
|
|
171
|
+
};
|
|
172
|
+
const processMatrixDataPoints = (data, datasetIndex, xLabels, yLabels) => {
|
|
173
|
+
return data.map((point, index) => {
|
|
174
|
+
const x = typeof point.x === "string" ? labelIndex(xLabels, point.x) : point.x;
|
|
175
|
+
const y = typeof point.y === "string" ? labelIndex(yLabels, point.y) : point.y;
|
|
176
|
+
return {
|
|
177
|
+
...point,
|
|
178
|
+
x,
|
|
179
|
+
y,
|
|
180
|
+
custom: {
|
|
181
|
+
...point.custom,
|
|
182
|
+
group: datasetIndex,
|
|
183
|
+
datasetIndex,
|
|
184
|
+
index
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
const processMatrixData = (data) => {
|
|
190
|
+
const xLabels = [];
|
|
191
|
+
const yLabels = [];
|
|
192
|
+
// Chart2Music represents matrix rows as groups and columns as points within
|
|
193
|
+
// each group. This preserves two-dimensional keyboard navigation.
|
|
194
|
+
const result = {};
|
|
195
|
+
data.datasets.forEach((obj, index) => {
|
|
196
|
+
const points = processMatrixDataPoints(obj.data, index, xLabels, yLabels);
|
|
197
|
+
points.forEach((point) => {
|
|
198
|
+
const rowLabel = typeof point.y === "number" && yLabels[point.y] !== undefined
|
|
199
|
+
? yLabels[point.y]
|
|
200
|
+
: String(point.y);
|
|
201
|
+
const groupName = data.datasets.length === 1
|
|
202
|
+
? rowLabel
|
|
203
|
+
: `${obj.label ?? `Group ${index + 1}`}: ${rowLabel}`;
|
|
204
|
+
if (!result[groupName]) {
|
|
205
|
+
result[groupName] = [];
|
|
206
|
+
}
|
|
207
|
+
result[groupName].push(point);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
Object.values(result).forEach((row) => row.sort((a, b) => a.x - b.x));
|
|
211
|
+
return { groups: Object.keys(result), data: result, xLabels, yLabels };
|
|
212
|
+
};
|
|
163
213
|
const scrubX = (data) => {
|
|
164
214
|
const blackboard = JSON.parse(JSON.stringify(data));
|
|
165
215
|
let labels = [];
|
|
@@ -174,11 +224,18 @@ var chartjs2music = (function (c2mChart) {
|
|
|
174
224
|
});
|
|
175
225
|
return { labels, data: blackboard };
|
|
176
226
|
}
|
|
227
|
+
else {
|
|
228
|
+
// Grouped
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
177
231
|
};
|
|
178
232
|
const processData = (data, c2m_types) => {
|
|
179
233
|
if (c2m_types === "box") {
|
|
180
234
|
return processBoxData(data);
|
|
181
235
|
}
|
|
236
|
+
if (c2m_types === "matrix") {
|
|
237
|
+
return processMatrixData(data);
|
|
238
|
+
}
|
|
182
239
|
let groups = [];
|
|
183
240
|
if (data.datasets.length === 1) {
|
|
184
241
|
return {
|
|
@@ -210,26 +267,36 @@ var chartjs2music = (function (c2mChart) {
|
|
|
210
267
|
canvas.insertAdjacentElement("afterend", cc);
|
|
211
268
|
return cc;
|
|
212
269
|
};
|
|
270
|
+
const createDataSnapshot = (chart) => {
|
|
271
|
+
return JSON.stringify({
|
|
272
|
+
datasets: chart.data.datasets.map(ds => ds.data),
|
|
273
|
+
labels: chart.data.labels
|
|
274
|
+
});
|
|
275
|
+
};
|
|
213
276
|
const displayPoint = (chart) => {
|
|
214
277
|
if (!chartStates.has(chart)) {
|
|
215
278
|
return;
|
|
216
279
|
}
|
|
217
|
-
const { c2m: ref
|
|
280
|
+
const { c2m: ref } = chartStates.get(chart);
|
|
218
281
|
const { point, index } = ref.getCurrent();
|
|
282
|
+
// Use Chart2Music's internal visible group tracking
|
|
283
|
+
// @ts-ignore - accessing internal Chart2Music property
|
|
284
|
+
const visibleGroupIndices = ref._visible_group_indices?.slice(1) || [];
|
|
219
285
|
try {
|
|
220
286
|
const highlightElements = [];
|
|
221
287
|
if ("custom" in point) {
|
|
288
|
+
const customPoint = point;
|
|
222
289
|
highlightElements.push({
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
// @ts-ignore
|
|
226
|
-
index: point.custom.index
|
|
290
|
+
datasetIndex: customPoint.custom.datasetIndex ?? customPoint.custom.group,
|
|
291
|
+
index: customPoint.custom.index
|
|
227
292
|
});
|
|
228
293
|
}
|
|
229
294
|
else {
|
|
230
|
-
|
|
295
|
+
// For stacked charts, Chart2Music includes an "All" group at index 0,
|
|
296
|
+
// so we subtract 1 to get the actual dataset index
|
|
297
|
+
visibleGroupIndices.forEach((groupIndex) => {
|
|
231
298
|
highlightElements.push({
|
|
232
|
-
datasetIndex,
|
|
299
|
+
datasetIndex: groupIndex - 1,
|
|
233
300
|
index
|
|
234
301
|
});
|
|
235
302
|
});
|
|
@@ -245,11 +312,10 @@ var chartjs2music = (function (c2mChart) {
|
|
|
245
312
|
const generateChart = (chart, options) => {
|
|
246
313
|
const { valid, c2m_types, invalidType } = processChartType(chart);
|
|
247
314
|
if (!valid) {
|
|
248
|
-
// @ts-ignore
|
|
249
315
|
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(", ")}`);
|
|
250
316
|
return;
|
|
251
317
|
}
|
|
252
|
-
let axes = generateAxes(chart);
|
|
318
|
+
let axes = generateAxes(chart, options);
|
|
253
319
|
if (chart.config.type === "wordCloud") {
|
|
254
320
|
delete axes.x.minimum;
|
|
255
321
|
delete axes.x.maximum;
|
|
@@ -264,8 +330,20 @@ var chartjs2music = (function (c2mChart) {
|
|
|
264
330
|
}
|
|
265
331
|
// Generate CC element
|
|
266
332
|
const cc = determineCCElement(chart.canvas, options.cc);
|
|
267
|
-
const
|
|
333
|
+
const processedData = processData(chart.data, c2m_types);
|
|
334
|
+
const { data } = processedData;
|
|
268
335
|
// lastDataObj = JSON.stringify(data);
|
|
336
|
+
if (c2m_types === "matrix") {
|
|
337
|
+
if (processedData.xLabels?.length > 0) {
|
|
338
|
+
axes.x.valueLabels = processedData.xLabels.slice(0);
|
|
339
|
+
}
|
|
340
|
+
if (processedData.yLabels?.length > 0) {
|
|
341
|
+
// Category labels provide the Matrix Plot's Y-axis formatter.
|
|
342
|
+
// The general numeric formatter would otherwise announce row indexes.
|
|
343
|
+
delete axes.y.format;
|
|
344
|
+
axes.y.valueLabels = processedData.yLabels.slice(0);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
269
347
|
let scrub = scrubX(data);
|
|
270
348
|
if (scrub?.labels && scrub?.labels?.length > 0) { // Something was scrubbed
|
|
271
349
|
if (!chart.data.labels || chart.data.labels.length === 0) {
|
|
@@ -287,6 +365,13 @@ var chartjs2music = (function (c2mChart) {
|
|
|
287
365
|
...(options.axes?.y)
|
|
288
366
|
},
|
|
289
367
|
};
|
|
368
|
+
// Start with plugin's internal onFocusCallback
|
|
369
|
+
const pluginOnFocusCallback = () => {
|
|
370
|
+
displayPoint(chart);
|
|
371
|
+
};
|
|
372
|
+
// Merge user's options, wrapping onFocusCallback if provided
|
|
373
|
+
const userOptions = options.options || {};
|
|
374
|
+
const userOnFocusCallback = userOptions.onFocusCallback;
|
|
290
375
|
const c2mOptions = {
|
|
291
376
|
cc,
|
|
292
377
|
element: chart.canvas,
|
|
@@ -295,10 +380,13 @@ var chartjs2music = (function (c2mChart) {
|
|
|
295
380
|
title: determineChartTitle(chart.options),
|
|
296
381
|
axes,
|
|
297
382
|
options: {
|
|
298
|
-
|
|
299
|
-
onFocusCallback:
|
|
300
|
-
|
|
301
|
-
|
|
383
|
+
...userOptions,
|
|
384
|
+
onFocusCallback: userOnFocusCallback
|
|
385
|
+
? (point) => {
|
|
386
|
+
pluginOnFocusCallback();
|
|
387
|
+
userOnFocusCallback(point);
|
|
388
|
+
}
|
|
389
|
+
: pluginOnFocusCallback
|
|
302
390
|
}
|
|
303
391
|
};
|
|
304
392
|
if (Array.isArray(c2mOptions.data)) {
|
|
@@ -307,8 +395,9 @@ var chartjs2music = (function (c2mChart) {
|
|
|
307
395
|
return {
|
|
308
396
|
...point,
|
|
309
397
|
custom: {
|
|
310
|
-
|
|
311
|
-
|
|
398
|
+
...point.custom,
|
|
399
|
+
group: point.custom?.group ?? 0,
|
|
400
|
+
index: point.custom?.index ?? index
|
|
312
401
|
}
|
|
313
402
|
};
|
|
314
403
|
});
|
|
@@ -327,10 +416,11 @@ var chartjs2music = (function (c2mChart) {
|
|
|
327
416
|
}
|
|
328
417
|
}
|
|
329
418
|
else {
|
|
330
|
-
const
|
|
419
|
+
const dataObj = c2mOptions.data;
|
|
420
|
+
const groups = Object.keys(dataObj);
|
|
331
421
|
groups.forEach((groupName, groupNumber) => {
|
|
332
|
-
if (!isNaN(
|
|
333
|
-
|
|
422
|
+
if (!isNaN(dataObj[groupName][0])) {
|
|
423
|
+
dataObj[groupName] = dataObj[groupName].map((num, index) => {
|
|
334
424
|
return {
|
|
335
425
|
x: index,
|
|
336
426
|
y: num,
|
|
@@ -342,26 +432,25 @@ var chartjs2music = (function (c2mChart) {
|
|
|
342
432
|
});
|
|
343
433
|
}
|
|
344
434
|
else {
|
|
345
|
-
|
|
435
|
+
dataObj[groupName] = dataObj[groupName].map((point, index) => {
|
|
346
436
|
return {
|
|
347
437
|
...point,
|
|
348
438
|
custom: {
|
|
349
|
-
|
|
350
|
-
|
|
439
|
+
...point.custom,
|
|
440
|
+
group: point.custom?.group ?? groupNumber,
|
|
441
|
+
index: point.custom?.index ?? index
|
|
351
442
|
}
|
|
352
443
|
};
|
|
353
444
|
});
|
|
354
445
|
}
|
|
355
446
|
});
|
|
356
447
|
}
|
|
357
|
-
// @ts-ignore
|
|
358
448
|
if (chart.config.options?.scales?.x?.stacked) {
|
|
359
|
-
|
|
360
|
-
|
|
449
|
+
if (c2mOptions.options) {
|
|
450
|
+
c2mOptions.options.stack = true;
|
|
451
|
+
}
|
|
361
452
|
}
|
|
362
|
-
// @ts-ignore
|
|
363
453
|
if (options.audioEngine) {
|
|
364
|
-
// @ts-ignore
|
|
365
454
|
c2mOptions.audioEngine = options.audioEngine;
|
|
366
455
|
}
|
|
367
456
|
if (c2mOptions.data.length === 0) {
|
|
@@ -373,7 +462,6 @@ var chartjs2music = (function (c2mChart) {
|
|
|
373
462
|
const { err, data: c2m } = c2mChart(c2mOptions);
|
|
374
463
|
/* istanbul-ignore-next */
|
|
375
464
|
if (err) {
|
|
376
|
-
// @ts-ignore
|
|
377
465
|
options.errorCallback?.(err);
|
|
378
466
|
return;
|
|
379
467
|
}
|
|
@@ -382,12 +470,32 @@ var chartjs2music = (function (c2mChart) {
|
|
|
382
470
|
}
|
|
383
471
|
chartStates.set(chart, {
|
|
384
472
|
c2m,
|
|
385
|
-
|
|
473
|
+
lastDataSnapshot: createDataSnapshot(chart)
|
|
386
474
|
});
|
|
475
|
+
if (c2m_types === "matrix") {
|
|
476
|
+
const matrixKeydown = (event) => {
|
|
477
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
event.preventDefault();
|
|
481
|
+
event.stopImmediatePropagation();
|
|
482
|
+
const actions = c2m._availableActions;
|
|
483
|
+
if (event.key === "ArrowUp") {
|
|
484
|
+
actions.next_category();
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
actions.previous_category();
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
chart.canvas.addEventListener("keydown", matrixKeydown, true);
|
|
491
|
+
const state = chartStates.get(chart);
|
|
492
|
+
state.matrixKeydown = matrixKeydown;
|
|
493
|
+
state.matrixKeydownTarget = chart.canvas;
|
|
494
|
+
}
|
|
387
495
|
};
|
|
388
496
|
const plugin = {
|
|
389
497
|
id: "chartjs2music",
|
|
390
|
-
afterInit: (chart,
|
|
498
|
+
afterInit: (chart, _args, options) => {
|
|
391
499
|
if (!chartStates.has(chart)) {
|
|
392
500
|
generateChart(chart, options);
|
|
393
501
|
// Remove tooltip when the chart blurs
|
|
@@ -414,19 +522,17 @@ var chartjs2music = (function (c2mChart) {
|
|
|
414
522
|
if (!chartStates.has(chart)) {
|
|
415
523
|
generateChart(chart, options);
|
|
416
524
|
}
|
|
417
|
-
const { c2m: ref
|
|
525
|
+
const { c2m: ref } = chartStates.get(chart);
|
|
418
526
|
if (!ref) {
|
|
419
527
|
return;
|
|
420
528
|
}
|
|
421
|
-
|
|
422
|
-
const groups =
|
|
423
|
-
|
|
424
|
-
if (ref._options.stack) {
|
|
529
|
+
const refInternal = ref;
|
|
530
|
+
const groups = refInternal._groups.slice(0);
|
|
531
|
+
if (refInternal._options.stack) {
|
|
425
532
|
groups.shift();
|
|
426
533
|
}
|
|
427
534
|
if (args.mode === "hide") {
|
|
428
535
|
const err = ref.setCategoryVisibility(groups[args.index], false);
|
|
429
|
-
visible_groups.splice(args.index, 1);
|
|
430
536
|
if (err) {
|
|
431
537
|
console.error(err);
|
|
432
538
|
}
|
|
@@ -434,24 +540,49 @@ var chartjs2music = (function (c2mChart) {
|
|
|
434
540
|
}
|
|
435
541
|
if (args.mode === "show") {
|
|
436
542
|
const err = ref.setCategoryVisibility(groups[args.index], true);
|
|
437
|
-
visible_groups.push(args.index);
|
|
438
543
|
if (err) {
|
|
439
544
|
console.error(err);
|
|
440
545
|
}
|
|
441
546
|
return;
|
|
442
547
|
}
|
|
443
548
|
},
|
|
549
|
+
afterDatasetsUpdate: (chart, _args, options) => {
|
|
550
|
+
const state = chartStates.get(chart);
|
|
551
|
+
if (!state?.c2m)
|
|
552
|
+
return;
|
|
553
|
+
// Check if data has changed
|
|
554
|
+
const currentSnapshot = createDataSnapshot(chart);
|
|
555
|
+
if (currentSnapshot === state.lastDataSnapshot) {
|
|
556
|
+
return; // No data change, skip update
|
|
557
|
+
}
|
|
558
|
+
// Get chart type
|
|
559
|
+
const { valid, c2m_types } = processChartType(chart);
|
|
560
|
+
if (!valid)
|
|
561
|
+
return;
|
|
562
|
+
// Process data and generate axes
|
|
563
|
+
const { data } = processData(chart.data, c2m_types);
|
|
564
|
+
const axes = generateAxes(chart, options);
|
|
565
|
+
// Update Chart2Music with new data
|
|
566
|
+
state.c2m.setData(data, axes);
|
|
567
|
+
// Update snapshot
|
|
568
|
+
state.lastDataSnapshot = currentSnapshot;
|
|
569
|
+
},
|
|
444
570
|
afterDestroy: (chart) => {
|
|
445
|
-
const
|
|
446
|
-
if (!
|
|
571
|
+
const state = chartStates.get(chart);
|
|
572
|
+
if (!state?.c2m) {
|
|
447
573
|
return;
|
|
448
574
|
}
|
|
575
|
+
const { c2m: ref } = state;
|
|
576
|
+
if (state?.matrixKeydown && state.matrixKeydownTarget) {
|
|
577
|
+
state.matrixKeydownTarget.removeEventListener("keydown", state.matrixKeydown, true);
|
|
578
|
+
}
|
|
449
579
|
ref.cleanUp();
|
|
450
580
|
},
|
|
451
581
|
defaults: {
|
|
452
582
|
cc: null,
|
|
453
583
|
audioEngine: null,
|
|
454
|
-
errorCallback: null
|
|
584
|
+
errorCallback: null,
|
|
585
|
+
options: {}
|
|
455
586
|
}
|
|
456
587
|
};
|
|
457
588
|
|