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 CHANGED
@@ -1,117 +1,133 @@
1
1
  # chartjs-plugin-chart2music
2
- [![npm version](https://badge.fury.io/js/chartjs-plugin-chart2music.svg)](https://badge.fury.io/js/chartjs-plugin-chart2music)
3
2
 
4
- **This is a beta release of this plugin. Not all chart.js features are supported yet.**
3
+ [![npm version](https://badge.fury.io/js/chartjs-plugin-chart2music.svg)](https://www.npmjs.com/package/chartjs-plugin-chart2music)
5
4
 
6
- Turns your chart.js charts into music so the blind can hear data. This plugin will automatically add Chart2Music, an interactive sonification library, to your chart.js charts. The contents of the chart element will be modified to best support screen reader users, and the interactions will be visually synchronized to provide support for keyboard-only users.
5
+ `chartjs-plugin-chart2music` connects [Chart.js](https://www.chartjs.org/) charts to [Chart2Music](https://chart2music.com/), providing keyboard navigation, screen-reader output, and interactive data sonification. Focus a chart to explore its data with the keyboard; the active data point is also highlighted on the visual chart.
7
6
 
8
- [Check out our CodePen collection of examples using the plugin.](https://codepen.io/collection/VYEvEQ)
7
+ This plugin is in beta. The supported chart types and integration points are listed below.
9
8
 
10
- ## Getting started
9
+ ## Install and use
11
10
 
12
- Add the chartjs2music plugin to your existing chart.js code like this:
11
+ ```bash
12
+ npm install chart.js chartjs-plugin-chart2music
13
+ ```
14
+
15
+ Register the plugin globally:
13
16
 
14
17
  ```js
15
- import {Chart} from "chart.js/auto";
16
- import chartjs2music from "chartjs-plugin-chart2music";
18
+ import Chart from "chart.js/auto";
19
+ import chart2music from "chartjs-plugin-chart2music";
17
20
 
18
- Chart.register(chartjs2music);
21
+ Chart.register(chart2music);
22
+
23
+ new Chart(canvasElement, {
24
+ type: "bar",
25
+ data: {
26
+ labels: ["Q1", "Q2", "Q3", "Q4"],
27
+ datasets: [{label: "Revenue", data: [1, 4, 2, 8]}]
28
+ }
29
+ });
19
30
  ```
20
31
 
21
- That will register the plugin globally. Alternatively, if you only want to enable for a given chart, you can do this:
32
+ Or add it to one chart only:
22
33
 
23
34
  ```js
24
- import {Chart} from "chart.js/auto";
25
- import chartjs2music from "chartjs-plugin-chart2music";
26
-
27
35
  new Chart(canvasElement, {
28
36
  type: "bar",
29
37
  data: {
30
- datasets: [{
31
- data: [1,4,2,8]
32
- }]
38
+ datasets: [{data: [1, 4, 2, 8]}]
33
39
  },
34
- plugins: [chartjs2music]
35
- })
36
-
40
+ plugins: [chart2music]
41
+ });
37
42
  ```
38
43
 
39
- ## Available options
44
+ When no `cc` element is supplied, the plugin creates a caption element immediately after the canvas. Supply your own element when you need to control where the Chart2Music output appears.
40
45
 
41
- The following plugin options are available:
42
- * `errorCallback` - A callback that will return errors if any arise while the plugin works.
43
- * `cc` - the equivalent of the [chart2music option `cc`](https://chart2music.com/docs/API/Config#axes)
44
- * `audioEngine` - the equivalent of the [chart2music option `audioEngine`](https://chart2music.com/docs/API/Config#cc)
45
- * `axes` - the equivalent of the [chart2music option `axes`](https://chart2music.com/docs/API/Config#axes)
46
- * `lang` - the language your user speaks. The available languages that Chart2Music supports are: "en", "de", "es", "fr", "it". The default is "en". If you would like to add translations for another language, [Chart2Music is open to PRs](https://github.com/julianna-langston/chart2music).
46
+ ## Plugin options
47
47
 
48
- Here's an example for providing options:
49
- ```js
50
- import {Chart} from "chart.js/auto";
51
- import chartjs2music from "chartjs-plugin-chart2music";
48
+ Configure the plugin through `options.plugins.chartjs2music`:
52
49
 
50
+ ```js
53
51
  new Chart(canvasElement, {
54
52
  type: "bar",
55
53
  data: {
56
- datasets: [{
57
- data: [1,4,2,8]
58
- }]
54
+ labels: ["Q1", "Q2", "Q3", "Q4"],
55
+ datasets: [{label: "Revenue", data: [1, 4, 2, 8]}]
59
56
  },
60
57
  options: {
61
58
  plugins: {
62
59
  chartjs2music: {
63
- // All errors should be logged as errors
64
- errorCallback: console.error,
65
- // Here's a div I made to be the CC
66
- cc: myDiv,
67
- // The Y values should all be money
60
+ cc: outputElement,
61
+ lang: "en",
68
62
  axes: {
63
+ x: {label: "Quarter"},
69
64
  y: {
70
- format: (value) => "$" + value
65
+ label: "Revenue",
66
+ format: (value) => `$${value.toLocaleString()}`
71
67
  }
72
- }
68
+ },
69
+ options: {
70
+ enableSound: true,
71
+ onFocusCallback: (point) => console.log(point),
72
+ onSelectCallback: ({point}) => console.log(point)
73
+ },
74
+ errorCallback: console.error
73
75
  }
74
76
  }
75
- },
76
- plugins: [chartjs2music]
77
+ }
77
78
  });
78
79
  ```
79
80
 
80
- If you are using TypeScript, replace the first 4 lines with this:
81
- ```ts
82
- import {Chart, type ChartTypeRegistry} from "chart.js/auto";
83
- import chartjs2music from "chartjs-plugin-chart2music";
81
+ Available plugin options:
84
82
 
85
- new Chart(canvasElement, {
86
- type: "bar" as keyof ChartTypeRegistry,
83
+ - `cc`: Element that receives Chart2Music's accessible chart caption. Optional.
84
+ - `lang`: Chart2Music language. Supported values include `en`, `de`, `es`, `fr`, and `it`; default is `en`.
85
+ - `axes`: Chart2Music axis configuration, including labels and value formatters.
86
+ - `audioEngine`: A custom Chart2Music audio engine.
87
+ - `options`: Chart2Music interaction options such as `enableSound`, `onFocusCallback`, and `onSelectCallback`.
88
+ - `errorCallback`: Receives errors while the plugin initializes or updates a chart.
89
+
90
+ ## Supported charts
91
+
92
+ The plugin supports Chart.js bar, line, pie, doughnut, polar area, and scatter charts, including mixed bar/line configurations. It also supports:
93
+
94
+ - Box plots from [`@sgratzl/chartjs-chart-boxplot`](https://www.npmjs.com/package/@sgratzl/chartjs-chart-boxplot).
95
+ - Matrix plots from [`chartjs-chart-matrix`](https://www.npmjs.com/package/chartjs-chart-matrix).
96
+ - Word clouds from [`chartjs-chart-wordcloud`](https://www.npmjs.com/package/chartjs-chart-wordcloud).
97
+ - Chart titles, axis titles, minimum and maximum values, linear and logarithmic axes, custom axis formatting, dataset visibility, and chart data updates.
98
+
99
+ Visual-only Chart.js settings such as color, padding, and line thickness do not affect the sonification. Advanced Chart.js parsing configurations and nonstandard axis identifiers are not currently supported.
100
+
101
+ ## Future support
102
+
103
+ Planned support includes:
104
+
105
+ - Chart subtitles.
106
+ - Radar plots.
107
+ - Error bars via [`chartjs-chart-error-bars`](https://www.npmjs.com/package/chartjs-chart-error-bars).
108
+ - Parallel coordinate plots via [`chartjs-chart-pcp`](https://www.npmjs.com/package/chartjs-chart-pcp).
109
+ - Zoom interactions via [`chartjs-plugin-zoom`](https://www.npmjs.com/package/chartjs-plugin-zoom).
110
+
111
+ ## Examples and Storybook
112
+
113
+ The repository includes runnable chart configurations under [`stories/charts`](./stories/charts), covering the supported chart types and plugin integrations. A Storybook catalogue exposes these examples as Bar, Line, Multi-chart, Distribution, Circular, Scatter, Matrix, Word cloud, and Options stories.
114
+
115
+ ```bash
116
+ npm run storybook
117
+ ```
118
+
119
+ Build the static Storybook site with:
120
+
121
+ ```bash
122
+ npm run build-storybook
87
123
  ```
88
124
 
89
- ## Supported features
90
-
91
- This plugin is currently in beta, so not all of the chart.js features are currently supported.
92
-
93
- A quick list of chart.js features we currently support includes:
94
- * Chart types: bar, doughnut, line, pie, polar, radar, scatter, and combinations therein.
95
- * Boxplots using the `@sgratzl/chartjs-chart-boxplot` plugin (only support boxplots when there are no other chart types present)
96
- * Wordclouds using the `chartjs-chart-wordcloud` plugin
97
- * Axes options: `title`, `min`, `max`, `type="linear"`, `type="logarithmic"`.
98
- * Chart title
99
- * Most data structures (not including `parsing` or non-standard axes identifiers)
100
- * Dataset visibility (when you show/hide a category from the legend)
101
-
102
- Note that visual-specific chart features are ignored. This includes things like color, padding, line thickness, etc.
103
-
104
- Things we plan to support in the future:
105
- * Interactions and updates
106
- * Other chart.js plugins
107
- * Complex `parsing` options for data
108
- * Date/Time support for axes
109
- * Subtitle
110
- * Radar charts
111
- * Custom formatting for axis tick values
112
-
113
- Plugins we plan to support in the future:
114
- * [chartjs-char-error-bars](https://www.npmjs.com/package/chartjs-chart-error-bars)
115
- * [chartjs-chart-matrix](https://www.npmjs.com/package/chartjs-chart-matrix)
116
- * [chartjs-chart-pcp](https://www.npmjs.com/package/chartjs-chart-pcp)
117
- * [chartjs-plugin-zoom](https://www.npmjs.com/package/chartjs-plugin-zoom)
125
+ More examples are available in the [CodePen collection](https://codepen.io/collection/VYEvEQ).
126
+
127
+ ## Development
128
+
129
+ ```bash
130
+ npm test
131
+ npm run build
132
+ npm run depcheck
133
+ ```
@@ -84,7 +84,8 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
84
84
  boxplot: "box",
85
85
  radar: "bar",
86
86
  wordCloud: "bar",
87
- scatter: "scatter"
87
+ scatter: "scatter",
88
+ matrix: "matrix"
88
89
  };
89
90
  const processChartType = (chart) => {
90
91
  const topLevelType = chart.config.type;
@@ -134,13 +135,13 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
134
135
  }
135
136
  return axis;
136
137
  };
137
- const generateAxes = (chart) => {
138
+ const generateAxes = (chart, options) => {
138
139
  const axes = {
139
140
  x: {
140
141
  ...generateAxisInfo(chart.options?.scales?.x, chart),
141
142
  },
142
143
  y: {
143
- format: (value) => value.toLocaleString(),
144
+ format: options?.axes?.y?.format || ((value) => value.toLocaleString()),
144
145
  ...generateAxisInfo(chart.options?.scales?.y, chart),
145
146
  }
146
147
  };
@@ -159,6 +160,55 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
159
160
  }
160
161
  return data;
161
162
  };
163
+ const labelIndex = (labels, value) => {
164
+ let index = labels.indexOf(value);
165
+ if (index === -1) {
166
+ labels.push(value);
167
+ index = labels.length - 1;
168
+ }
169
+ return index;
170
+ };
171
+ const processMatrixDataPoints = (data, datasetIndex, xLabels, yLabels) => {
172
+ return data.map((point, index) => {
173
+ const x = typeof point.x === "string" ? labelIndex(xLabels, point.x) : point.x;
174
+ const y = typeof point.y === "string" ? labelIndex(yLabels, point.y) : point.y;
175
+ return {
176
+ ...point,
177
+ x,
178
+ y,
179
+ custom: {
180
+ ...point.custom,
181
+ group: datasetIndex,
182
+ datasetIndex,
183
+ index
184
+ }
185
+ };
186
+ });
187
+ };
188
+ const processMatrixData = (data) => {
189
+ const xLabels = [];
190
+ const yLabels = [];
191
+ // Chart2Music represents matrix rows as groups and columns as points within
192
+ // each group. This preserves two-dimensional keyboard navigation.
193
+ const result = {};
194
+ data.datasets.forEach((obj, index) => {
195
+ const points = processMatrixDataPoints(obj.data, index, xLabels, yLabels);
196
+ points.forEach((point) => {
197
+ const rowLabel = typeof point.y === "number" && yLabels[point.y] !== undefined
198
+ ? yLabels[point.y]
199
+ : String(point.y);
200
+ const groupName = data.datasets.length === 1
201
+ ? rowLabel
202
+ : `${obj.label ?? `Group ${index + 1}`}: ${rowLabel}`;
203
+ if (!result[groupName]) {
204
+ result[groupName] = [];
205
+ }
206
+ result[groupName].push(point);
207
+ });
208
+ });
209
+ Object.values(result).forEach((row) => row.sort((a, b) => a.x - b.x));
210
+ return { groups: Object.keys(result), data: result, xLabels, yLabels };
211
+ };
162
212
  const scrubX = (data) => {
163
213
  const blackboard = JSON.parse(JSON.stringify(data));
164
214
  let labels = [];
@@ -173,11 +223,18 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
173
223
  });
174
224
  return { labels, data: blackboard };
175
225
  }
226
+ else {
227
+ // Grouped
228
+ return undefined;
229
+ }
176
230
  };
177
231
  const processData = (data, c2m_types) => {
178
232
  if (c2m_types === "box") {
179
233
  return processBoxData(data);
180
234
  }
235
+ if (c2m_types === "matrix") {
236
+ return processMatrixData(data);
237
+ }
181
238
  let groups = [];
182
239
  if (data.datasets.length === 1) {
183
240
  return {
@@ -209,26 +266,36 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
209
266
  canvas.insertAdjacentElement("afterend", cc);
210
267
  return cc;
211
268
  };
269
+ const createDataSnapshot = (chart) => {
270
+ return JSON.stringify({
271
+ datasets: chart.data.datasets.map(ds => ds.data),
272
+ labels: chart.data.labels
273
+ });
274
+ };
212
275
  const displayPoint = (chart) => {
213
276
  if (!chartStates.has(chart)) {
214
277
  return;
215
278
  }
216
- const { c2m: ref, visible_groups } = chartStates.get(chart);
279
+ const { c2m: ref } = chartStates.get(chart);
217
280
  const { point, index } = ref.getCurrent();
281
+ // Use Chart2Music's internal visible group tracking
282
+ // @ts-ignore - accessing internal Chart2Music property
283
+ const visibleGroupIndices = ref._visible_group_indices?.slice(1) || [];
218
284
  try {
219
285
  const highlightElements = [];
220
286
  if ("custom" in point) {
287
+ const customPoint = point;
221
288
  highlightElements.push({
222
- // @ts-ignore
223
- datasetIndex: point.custom.group,
224
- // @ts-ignore
225
- index: point.custom.index
289
+ datasetIndex: customPoint.custom.datasetIndex ?? customPoint.custom.group,
290
+ index: customPoint.custom.index
226
291
  });
227
292
  }
228
293
  else {
229
- visible_groups.forEach((datasetIndex) => {
294
+ // For stacked charts, Chart2Music includes an "All" group at index 0,
295
+ // so we subtract 1 to get the actual dataset index
296
+ visibleGroupIndices.forEach((groupIndex) => {
230
297
  highlightElements.push({
231
- datasetIndex,
298
+ datasetIndex: groupIndex - 1,
232
299
  index
233
300
  });
234
301
  });
@@ -244,11 +311,10 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
244
311
  const generateChart = (chart, options) => {
245
312
  const { valid, c2m_types, invalidType } = processChartType(chart);
246
313
  if (!valid) {
247
- // @ts-ignore
248
314
  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(", ")}`);
249
315
  return;
250
316
  }
251
- let axes = generateAxes(chart);
317
+ let axes = generateAxes(chart, options);
252
318
  if (chart.config.type === "wordCloud") {
253
319
  delete axes.x.minimum;
254
320
  delete axes.x.maximum;
@@ -263,8 +329,20 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
263
329
  }
264
330
  // Generate CC element
265
331
  const cc = determineCCElement(chart.canvas, options.cc);
266
- const { data, groups } = processData(chart.data, c2m_types);
332
+ const processedData = processData(chart.data, c2m_types);
333
+ const { data } = processedData;
267
334
  // lastDataObj = JSON.stringify(data);
335
+ if (c2m_types === "matrix") {
336
+ if (processedData.xLabels?.length > 0) {
337
+ axes.x.valueLabels = processedData.xLabels.slice(0);
338
+ }
339
+ if (processedData.yLabels?.length > 0) {
340
+ // Category labels provide the Matrix Plot's Y-axis formatter.
341
+ // The general numeric formatter would otherwise announce row indexes.
342
+ delete axes.y.format;
343
+ axes.y.valueLabels = processedData.yLabels.slice(0);
344
+ }
345
+ }
268
346
  let scrub = scrubX(data);
269
347
  if (scrub?.labels && scrub?.labels?.length > 0) { // Something was scrubbed
270
348
  if (!chart.data.labels || chart.data.labels.length === 0) {
@@ -286,6 +364,13 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
286
364
  ...(options.axes?.y)
287
365
  },
288
366
  };
367
+ // Start with plugin's internal onFocusCallback
368
+ const pluginOnFocusCallback = () => {
369
+ displayPoint(chart);
370
+ };
371
+ // Merge user's options, wrapping onFocusCallback if provided
372
+ const userOptions = options.options || {};
373
+ const userOnFocusCallback = userOptions.onFocusCallback;
289
374
  const c2mOptions = {
290
375
  cc,
291
376
  element: chart.canvas,
@@ -294,10 +379,13 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
294
379
  title: determineChartTitle(chart.options),
295
380
  axes,
296
381
  options: {
297
- // @ts-ignore
298
- onFocusCallback: () => {
299
- displayPoint(chart);
300
- }
382
+ ...userOptions,
383
+ onFocusCallback: userOnFocusCallback
384
+ ? (point) => {
385
+ pluginOnFocusCallback();
386
+ userOnFocusCallback(point);
387
+ }
388
+ : pluginOnFocusCallback
301
389
  }
302
390
  };
303
391
  if (Array.isArray(c2mOptions.data)) {
@@ -306,8 +394,9 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
306
394
  return {
307
395
  ...point,
308
396
  custom: {
309
- group: 0,
310
- index
397
+ ...point.custom,
398
+ group: point.custom?.group ?? 0,
399
+ index: point.custom?.index ?? index
311
400
  }
312
401
  };
313
402
  });
@@ -326,10 +415,11 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
326
415
  }
327
416
  }
328
417
  else {
329
- const groups = Object.keys(c2mOptions.data);
418
+ const dataObj = c2mOptions.data;
419
+ const groups = Object.keys(dataObj);
330
420
  groups.forEach((groupName, groupNumber) => {
331
- if (!isNaN(c2mOptions.data[groupName][0])) {
332
- c2mOptions.data[groupName] = c2mOptions.data[groupName].map((num, index) => {
421
+ if (!isNaN(dataObj[groupName][0])) {
422
+ dataObj[groupName] = dataObj[groupName].map((num, index) => {
333
423
  return {
334
424
  x: index,
335
425
  y: num,
@@ -341,26 +431,25 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
341
431
  });
342
432
  }
343
433
  else {
344
- c2mOptions.data[groupName] = c2mOptions.data[groupName].map((point, index) => {
434
+ dataObj[groupName] = dataObj[groupName].map((point, index) => {
345
435
  return {
346
436
  ...point,
347
437
  custom: {
348
- group: groupNumber,
349
- index
438
+ ...point.custom,
439
+ group: point.custom?.group ?? groupNumber,
440
+ index: point.custom?.index ?? index
350
441
  }
351
442
  };
352
443
  });
353
444
  }
354
445
  });
355
446
  }
356
- // @ts-ignore
357
447
  if (chart.config.options?.scales?.x?.stacked) {
358
- // @ts-ignore
359
- c2mOptions.options.stack = true;
448
+ if (c2mOptions.options) {
449
+ c2mOptions.options.stack = true;
450
+ }
360
451
  }
361
- // @ts-ignore
362
452
  if (options.audioEngine) {
363
- // @ts-ignore
364
453
  c2mOptions.audioEngine = options.audioEngine;
365
454
  }
366
455
  if (c2mOptions.data.length === 0) {
@@ -372,7 +461,6 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
372
461
  const { err, data: c2m } = c2mChart(c2mOptions);
373
462
  /* istanbul-ignore-next */
374
463
  if (err) {
375
- // @ts-ignore
376
464
  options.errorCallback?.(err);
377
465
  return;
378
466
  }
@@ -381,12 +469,32 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
381
469
  }
382
470
  chartStates.set(chart, {
383
471
  c2m,
384
- visible_groups: groups?.map((g, i) => i) ?? []
472
+ lastDataSnapshot: createDataSnapshot(chart)
385
473
  });
474
+ if (c2m_types === "matrix") {
475
+ const matrixKeydown = (event) => {
476
+ if (event.key !== "ArrowUp" && event.key !== "ArrowDown") {
477
+ return;
478
+ }
479
+ event.preventDefault();
480
+ event.stopImmediatePropagation();
481
+ const actions = c2m._availableActions;
482
+ if (event.key === "ArrowUp") {
483
+ actions.next_category();
484
+ }
485
+ else {
486
+ actions.previous_category();
487
+ }
488
+ };
489
+ chart.canvas.addEventListener("keydown", matrixKeydown, true);
490
+ const state = chartStates.get(chart);
491
+ state.matrixKeydown = matrixKeydown;
492
+ state.matrixKeydownTarget = chart.canvas;
493
+ }
386
494
  };
387
495
  const plugin = {
388
496
  id: "chartjs2music",
389
- afterInit: (chart, args, options) => {
497
+ afterInit: (chart, _args, options) => {
390
498
  if (!chartStates.has(chart)) {
391
499
  generateChart(chart, options);
392
500
  // Remove tooltip when the chart blurs
@@ -413,19 +521,17 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
413
521
  if (!chartStates.has(chart)) {
414
522
  generateChart(chart, options);
415
523
  }
416
- const { c2m: ref, visible_groups } = chartStates.get(chart);
524
+ const { c2m: ref } = chartStates.get(chart);
417
525
  if (!ref) {
418
526
  return;
419
527
  }
420
- // @ts-ignore
421
- const groups = ref._groups.slice(0);
422
- // @ts-ignore
423
- if (ref._options.stack) {
528
+ const refInternal = ref;
529
+ const groups = refInternal._groups.slice(0);
530
+ if (refInternal._options.stack) {
424
531
  groups.shift();
425
532
  }
426
533
  if (args.mode === "hide") {
427
534
  const err = ref.setCategoryVisibility(groups[args.index], false);
428
- visible_groups.splice(args.index, 1);
429
535
  if (err) {
430
536
  console.error(err);
431
537
  }
@@ -433,24 +539,49 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
433
539
  }
434
540
  if (args.mode === "show") {
435
541
  const err = ref.setCategoryVisibility(groups[args.index], true);
436
- visible_groups.push(args.index);
437
542
  if (err) {
438
543
  console.error(err);
439
544
  }
440
545
  return;
441
546
  }
442
547
  },
548
+ afterDatasetsUpdate: (chart, _args, options) => {
549
+ const state = chartStates.get(chart);
550
+ if (!state?.c2m)
551
+ return;
552
+ // Check if data has changed
553
+ const currentSnapshot = createDataSnapshot(chart);
554
+ if (currentSnapshot === state.lastDataSnapshot) {
555
+ return; // No data change, skip update
556
+ }
557
+ // Get chart type
558
+ const { valid, c2m_types } = processChartType(chart);
559
+ if (!valid)
560
+ return;
561
+ // Process data and generate axes
562
+ const { data } = processData(chart.data, c2m_types);
563
+ const axes = generateAxes(chart, options);
564
+ // Update Chart2Music with new data
565
+ state.c2m.setData(data, axes);
566
+ // Update snapshot
567
+ state.lastDataSnapshot = currentSnapshot;
568
+ },
443
569
  afterDestroy: (chart) => {
444
- const { c2m: ref } = chartStates.get(chart);
445
- if (!ref) {
570
+ const state = chartStates.get(chart);
571
+ if (!state?.c2m) {
446
572
  return;
447
573
  }
574
+ const { c2m: ref } = state;
575
+ if (state?.matrixKeydown && state.matrixKeydownTarget) {
576
+ state.matrixKeydownTarget.removeEventListener("keydown", state.matrixKeydown, true);
577
+ }
448
578
  ref.cleanUp();
449
579
  },
450
580
  defaults: {
451
581
  cc: null,
452
582
  audioEngine: null,
453
- errorCallback: null
583
+ errorCallback: null,
584
+ options: {}
454
585
  }
455
586
  };
456
587