chartjs-plugin-chart2music 0.6.0 → 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,108 +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
- ## Supported features
81
-
82
- This plugin is currently in beta, so not all of the chart.js features are currently supported.
83
-
84
- A quick list of chart.js features we currently support includes:
85
- * Chart types: bar, doughnut, line, pie, polar, radar, scatter, and combinations therein.
86
- * Boxplots using the `@sgratzl/chartjs-chart-boxplot` plugin (only support boxplots when there are no other chart types present)
87
- * Wordclouds using the `chartjs-chart-wordcloud` plugin
88
- * Axes options: `title`, `min`, `max`, `type="linear"`, `type="logarithmic"`.
89
- * Chart title
90
- * Most data structures (not including `parsing` or non-standard axes identifiers)
91
-
92
- Note that visual-specific chart features are ignored. This includes things like color, padding, line thickness, etc.
93
-
94
- Things we plan to support in the future:
95
- * Interactions and updates
96
- * Other chart.js plugins
97
- * Complex `parsing` options for data
98
- * Date/Time support for axes
99
- * Subtitle
100
- * Dataset visibility (when you show/hide a category from the legend)
101
- * Radar charts
102
- * Custom formatting for axis tick values
103
-
104
- Plugins we plan to support in the future:
105
- * [chartjs-char-error-bars](https://www.npmjs.com/package/chartjs-chart-error-bars)
106
- * [chartjs-chart-matrix](https://www.npmjs.com/package/chartjs-chart-matrix)
107
- * [chartjs-chart-pcp](https://www.npmjs.com/package/chartjs-chart-pcp)
108
- * [chartjs-plugin-zoom](https://www.npmjs.com/package/chartjs-plugin-zoom)
81
+ Available plugin options:
82
+
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
123
+ ```
124
+
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;
@@ -109,7 +110,7 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
109
110
  };
110
111
  const generateAxisInfo = (chartAxisInfo, chart) => {
111
112
  const axis = {};
112
- if (chartAxisInfo?.min) {
113
+ if (chartAxisInfo?.min !== undefined) {
113
114
  if (typeof chartAxisInfo.min === "string") {
114
115
  axis.minimum = chart.data.labels.indexOf(chartAxisInfo.min);
115
116
  }
@@ -117,7 +118,7 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
117
118
  axis.minimum = chartAxisInfo.min;
118
119
  }
119
120
  }
120
- if (chartAxisInfo?.max) {
121
+ if (chartAxisInfo?.max !== undefined) {
121
122
  if (typeof chartAxisInfo.max === "string") {
122
123
  axis.maximum = chart.data.labels.indexOf(chartAxisInfo.max);
123
124
  }
@@ -134,17 +135,20 @@ 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
- valueLabels: chart.data.labels.slice(0)
142
142
  },
143
143
  y: {
144
+ format: options?.axes?.y?.format || ((value) => value.toLocaleString()),
144
145
  ...generateAxisInfo(chart.options?.scales?.y, chart),
145
- format: (value) => value.toLocaleString()
146
146
  }
147
147
  };
148
+ const xAxisValueLabels = chart.data.labels.slice(0);
149
+ if (xAxisValueLabels.length > 0) {
150
+ axes.x.valueLabels = xAxisValueLabels;
151
+ }
148
152
  return axes;
149
153
  };
150
154
  const whichDataStructure = (data) => {
@@ -156,6 +160,55 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
156
160
  }
157
161
  return data;
158
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
+ };
159
212
  const scrubX = (data) => {
160
213
  const blackboard = JSON.parse(JSON.stringify(data));
161
214
  let labels = [];
@@ -170,11 +223,18 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
170
223
  });
171
224
  return { labels, data: blackboard };
172
225
  }
226
+ else {
227
+ // Grouped
228
+ return undefined;
229
+ }
173
230
  };
174
231
  const processData = (data, c2m_types) => {
175
232
  if (c2m_types === "box") {
176
233
  return processBoxData(data);
177
234
  }
235
+ if (c2m_types === "matrix") {
236
+ return processMatrixData(data);
237
+ }
178
238
  let groups = [];
179
239
  if (data.datasets.length === 1) {
180
240
  return {
@@ -206,26 +266,36 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
206
266
  canvas.insertAdjacentElement("afterend", cc);
207
267
  return cc;
208
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
+ };
209
275
  const displayPoint = (chart) => {
210
276
  if (!chartStates.has(chart)) {
211
277
  return;
212
278
  }
213
- const { c2m: ref, visible_groups } = chartStates.get(chart);
279
+ const { c2m: ref } = chartStates.get(chart);
214
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) || [];
215
284
  try {
216
285
  const highlightElements = [];
217
286
  if ("custom" in point) {
287
+ const customPoint = point;
218
288
  highlightElements.push({
219
- // @ts-ignore
220
- datasetIndex: point.custom.group,
221
- // @ts-ignore
222
- index: point.custom.index
289
+ datasetIndex: customPoint.custom.datasetIndex ?? customPoint.custom.group,
290
+ index: customPoint.custom.index
223
291
  });
224
292
  }
225
293
  else {
226
- 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) => {
227
297
  highlightElements.push({
228
- datasetIndex,
298
+ datasetIndex: groupIndex - 1,
229
299
  index
230
300
  });
231
301
  });
@@ -241,11 +311,10 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
241
311
  const generateChart = (chart, options) => {
242
312
  const { valid, c2m_types, invalidType } = processChartType(chart);
243
313
  if (!valid) {
244
- // @ts-ignore
245
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(", ")}`);
246
315
  return;
247
316
  }
248
- let axes = generateAxes(chart);
317
+ let axes = generateAxes(chart, options);
249
318
  if (chart.config.type === "wordCloud") {
250
319
  delete axes.x.minimum;
251
320
  delete axes.x.maximum;
@@ -260,8 +329,20 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
260
329
  }
261
330
  // Generate CC element
262
331
  const cc = determineCCElement(chart.canvas, options.cc);
263
- const { data, groups } = processData(chart.data, c2m_types);
332
+ const processedData = processData(chart.data, c2m_types);
333
+ const { data } = processedData;
264
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
+ }
265
346
  let scrub = scrubX(data);
266
347
  if (scrub?.labels && scrub?.labels?.length > 0) { // Something was scrubbed
267
348
  if (!chart.data.labels || chart.data.labels.length === 0) {
@@ -283,6 +364,13 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
283
364
  ...(options.axes?.y)
284
365
  },
285
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;
286
374
  const c2mOptions = {
287
375
  cc,
288
376
  element: chart.canvas,
@@ -291,10 +379,13 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
291
379
  title: determineChartTitle(chart.options),
292
380
  axes,
293
381
  options: {
294
- // @ts-ignore
295
- onFocusCallback: () => {
296
- displayPoint(chart);
297
- }
382
+ ...userOptions,
383
+ onFocusCallback: userOnFocusCallback
384
+ ? (point) => {
385
+ pluginOnFocusCallback();
386
+ userOnFocusCallback(point);
387
+ }
388
+ : pluginOnFocusCallback
298
389
  }
299
390
  };
300
391
  if (Array.isArray(c2mOptions.data)) {
@@ -303,8 +394,9 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
303
394
  return {
304
395
  ...point,
305
396
  custom: {
306
- group: 0,
307
- index
397
+ ...point.custom,
398
+ group: point.custom?.group ?? 0,
399
+ index: point.custom?.index ?? index
308
400
  }
309
401
  };
310
402
  });
@@ -323,10 +415,11 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
323
415
  }
324
416
  }
325
417
  else {
326
- const groups = Object.keys(c2mOptions.data);
418
+ const dataObj = c2mOptions.data;
419
+ const groups = Object.keys(dataObj);
327
420
  groups.forEach((groupName, groupNumber) => {
328
- if (!isNaN(c2mOptions.data[groupName][0])) {
329
- c2mOptions.data[groupName] = c2mOptions.data[groupName].map((num, index) => {
421
+ if (!isNaN(dataObj[groupName][0])) {
422
+ dataObj[groupName] = dataObj[groupName].map((num, index) => {
330
423
  return {
331
424
  x: index,
332
425
  y: num,
@@ -338,26 +431,25 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
338
431
  });
339
432
  }
340
433
  else {
341
- c2mOptions.data[groupName] = c2mOptions.data[groupName].map((point, index) => {
434
+ dataObj[groupName] = dataObj[groupName].map((point, index) => {
342
435
  return {
343
436
  ...point,
344
437
  custom: {
345
- group: groupNumber,
346
- index
438
+ ...point.custom,
439
+ group: point.custom?.group ?? groupNumber,
440
+ index: point.custom?.index ?? index
347
441
  }
348
442
  };
349
443
  });
350
444
  }
351
445
  });
352
446
  }
353
- // @ts-ignore
354
447
  if (chart.config.options?.scales?.x?.stacked) {
355
- // @ts-ignore
356
- c2mOptions.options.stack = true;
448
+ if (c2mOptions.options) {
449
+ c2mOptions.options.stack = true;
450
+ }
357
451
  }
358
- // @ts-ignore
359
452
  if (options.audioEngine) {
360
- // @ts-ignore
361
453
  c2mOptions.audioEngine = options.audioEngine;
362
454
  }
363
455
  if (c2mOptions.data.length === 0) {
@@ -369,7 +461,6 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
369
461
  const { err, data: c2m } = c2mChart(c2mOptions);
370
462
  /* istanbul-ignore-next */
371
463
  if (err) {
372
- // @ts-ignore
373
464
  options.errorCallback?.(err);
374
465
  return;
375
466
  }
@@ -378,12 +469,32 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
378
469
  }
379
470
  chartStates.set(chart, {
380
471
  c2m,
381
- visible_groups: groups?.map((g, i) => i) ?? []
472
+ lastDataSnapshot: createDataSnapshot(chart)
382
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
+ }
383
494
  };
384
495
  const plugin = {
385
496
  id: "chartjs2music",
386
- afterInit: (chart, args, options) => {
497
+ afterInit: (chart, _args, options) => {
387
498
  if (!chartStates.has(chart)) {
388
499
  generateChart(chart, options);
389
500
  // Remove tooltip when the chart blurs
@@ -410,19 +521,17 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
410
521
  if (!chartStates.has(chart)) {
411
522
  generateChart(chart, options);
412
523
  }
413
- const { c2m: ref, visible_groups } = chartStates.get(chart);
524
+ const { c2m: ref } = chartStates.get(chart);
414
525
  if (!ref) {
415
526
  return;
416
527
  }
417
- // @ts-ignore
418
- const groups = ref._groups.slice(0);
419
- // @ts-ignore
420
- if (ref._options.stack) {
528
+ const refInternal = ref;
529
+ const groups = refInternal._groups.slice(0);
530
+ if (refInternal._options.stack) {
421
531
  groups.shift();
422
532
  }
423
533
  if (args.mode === "hide") {
424
534
  const err = ref.setCategoryVisibility(groups[args.index], false);
425
- visible_groups.splice(args.index, 1);
426
535
  if (err) {
427
536
  console.error(err);
428
537
  }
@@ -430,24 +539,49 @@ define(['chart2music'], (function (c2mChart) { 'use strict';
430
539
  }
431
540
  if (args.mode === "show") {
432
541
  const err = ref.setCategoryVisibility(groups[args.index], true);
433
- visible_groups.push(args.index);
434
542
  if (err) {
435
543
  console.error(err);
436
544
  }
437
545
  return;
438
546
  }
439
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
+ },
440
569
  afterDestroy: (chart) => {
441
- const { c2m: ref } = chartStates.get(chart);
442
- if (!ref) {
570
+ const state = chartStates.get(chart);
571
+ if (!state?.c2m) {
443
572
  return;
444
573
  }
574
+ const { c2m: ref } = state;
575
+ if (state?.matrixKeydown && state.matrixKeydownTarget) {
576
+ state.matrixKeydownTarget.removeEventListener("keydown", state.matrixKeydown, true);
577
+ }
445
578
  ref.cleanUp();
446
579
  },
447
580
  defaults: {
448
581
  cc: null,
449
582
  audioEngine: null,
450
- errorCallback: null
583
+ errorCallback: null,
584
+ options: {}
451
585
  }
452
586
  };
453
587