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/dist/plugin.d.ts CHANGED
@@ -1 +1,8 @@
1
- declare module "chartjs-plugin-chart2music";
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;
@@ -110,7 +111,7 @@ var chartjs2music = (function (c2mChart) {
110
111
  };
111
112
  const generateAxisInfo = (chartAxisInfo, chart) => {
112
113
  const axis = {};
113
- if (chartAxisInfo?.min) {
114
+ if (chartAxisInfo?.min !== undefined) {
114
115
  if (typeof chartAxisInfo.min === "string") {
115
116
  axis.minimum = chart.data.labels.indexOf(chartAxisInfo.min);
116
117
  }
@@ -118,7 +119,7 @@ var chartjs2music = (function (c2mChart) {
118
119
  axis.minimum = chartAxisInfo.min;
119
120
  }
120
121
  }
121
- if (chartAxisInfo?.max) {
122
+ if (chartAxisInfo?.max !== undefined) {
122
123
  if (typeof chartAxisInfo.max === "string") {
123
124
  axis.maximum = chart.data.labels.indexOf(chartAxisInfo.max);
124
125
  }
@@ -135,17 +136,20 @@ 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
- valueLabels: chart.data.labels.slice(0)
143
143
  },
144
144
  y: {
145
+ format: options?.axes?.y?.format || ((value) => value.toLocaleString()),
145
146
  ...generateAxisInfo(chart.options?.scales?.y, chart),
146
- format: (value) => value.toLocaleString()
147
147
  }
148
148
  };
149
+ const xAxisValueLabels = chart.data.labels.slice(0);
150
+ if (xAxisValueLabels.length > 0) {
151
+ axes.x.valueLabels = xAxisValueLabels;
152
+ }
149
153
  return axes;
150
154
  };
151
155
  const whichDataStructure = (data) => {
@@ -157,6 +161,55 @@ var chartjs2music = (function (c2mChart) {
157
161
  }
158
162
  return data;
159
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
+ };
160
213
  const scrubX = (data) => {
161
214
  const blackboard = JSON.parse(JSON.stringify(data));
162
215
  let labels = [];
@@ -171,11 +224,18 @@ var chartjs2music = (function (c2mChart) {
171
224
  });
172
225
  return { labels, data: blackboard };
173
226
  }
227
+ else {
228
+ // Grouped
229
+ return undefined;
230
+ }
174
231
  };
175
232
  const processData = (data, c2m_types) => {
176
233
  if (c2m_types === "box") {
177
234
  return processBoxData(data);
178
235
  }
236
+ if (c2m_types === "matrix") {
237
+ return processMatrixData(data);
238
+ }
179
239
  let groups = [];
180
240
  if (data.datasets.length === 1) {
181
241
  return {
@@ -207,26 +267,36 @@ var chartjs2music = (function (c2mChart) {
207
267
  canvas.insertAdjacentElement("afterend", cc);
208
268
  return cc;
209
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
+ };
210
276
  const displayPoint = (chart) => {
211
277
  if (!chartStates.has(chart)) {
212
278
  return;
213
279
  }
214
- const { c2m: ref, visible_groups } = chartStates.get(chart);
280
+ const { c2m: ref } = chartStates.get(chart);
215
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) || [];
216
285
  try {
217
286
  const highlightElements = [];
218
287
  if ("custom" in point) {
288
+ const customPoint = point;
219
289
  highlightElements.push({
220
- // @ts-ignore
221
- datasetIndex: point.custom.group,
222
- // @ts-ignore
223
- index: point.custom.index
290
+ datasetIndex: customPoint.custom.datasetIndex ?? customPoint.custom.group,
291
+ index: customPoint.custom.index
224
292
  });
225
293
  }
226
294
  else {
227
- visible_groups.forEach((datasetIndex) => {
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) => {
228
298
  highlightElements.push({
229
- datasetIndex,
299
+ datasetIndex: groupIndex - 1,
230
300
  index
231
301
  });
232
302
  });
@@ -242,11 +312,10 @@ var chartjs2music = (function (c2mChart) {
242
312
  const generateChart = (chart, options) => {
243
313
  const { valid, c2m_types, invalidType } = processChartType(chart);
244
314
  if (!valid) {
245
- // @ts-ignore
246
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(", ")}`);
247
316
  return;
248
317
  }
249
- let axes = generateAxes(chart);
318
+ let axes = generateAxes(chart, options);
250
319
  if (chart.config.type === "wordCloud") {
251
320
  delete axes.x.minimum;
252
321
  delete axes.x.maximum;
@@ -261,8 +330,20 @@ var chartjs2music = (function (c2mChart) {
261
330
  }
262
331
  // Generate CC element
263
332
  const cc = determineCCElement(chart.canvas, options.cc);
264
- const { data, groups } = processData(chart.data, c2m_types);
333
+ const processedData = processData(chart.data, c2m_types);
334
+ const { data } = processedData;
265
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
+ }
266
347
  let scrub = scrubX(data);
267
348
  if (scrub?.labels && scrub?.labels?.length > 0) { // Something was scrubbed
268
349
  if (!chart.data.labels || chart.data.labels.length === 0) {
@@ -284,6 +365,13 @@ var chartjs2music = (function (c2mChart) {
284
365
  ...(options.axes?.y)
285
366
  },
286
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;
287
375
  const c2mOptions = {
288
376
  cc,
289
377
  element: chart.canvas,
@@ -292,10 +380,13 @@ var chartjs2music = (function (c2mChart) {
292
380
  title: determineChartTitle(chart.options),
293
381
  axes,
294
382
  options: {
295
- // @ts-ignore
296
- onFocusCallback: () => {
297
- displayPoint(chart);
298
- }
383
+ ...userOptions,
384
+ onFocusCallback: userOnFocusCallback
385
+ ? (point) => {
386
+ pluginOnFocusCallback();
387
+ userOnFocusCallback(point);
388
+ }
389
+ : pluginOnFocusCallback
299
390
  }
300
391
  };
301
392
  if (Array.isArray(c2mOptions.data)) {
@@ -304,8 +395,9 @@ var chartjs2music = (function (c2mChart) {
304
395
  return {
305
396
  ...point,
306
397
  custom: {
307
- group: 0,
308
- index
398
+ ...point.custom,
399
+ group: point.custom?.group ?? 0,
400
+ index: point.custom?.index ?? index
309
401
  }
310
402
  };
311
403
  });
@@ -324,10 +416,11 @@ var chartjs2music = (function (c2mChart) {
324
416
  }
325
417
  }
326
418
  else {
327
- const groups = Object.keys(c2mOptions.data);
419
+ const dataObj = c2mOptions.data;
420
+ const groups = Object.keys(dataObj);
328
421
  groups.forEach((groupName, groupNumber) => {
329
- if (!isNaN(c2mOptions.data[groupName][0])) {
330
- c2mOptions.data[groupName] = c2mOptions.data[groupName].map((num, index) => {
422
+ if (!isNaN(dataObj[groupName][0])) {
423
+ dataObj[groupName] = dataObj[groupName].map((num, index) => {
331
424
  return {
332
425
  x: index,
333
426
  y: num,
@@ -339,26 +432,25 @@ var chartjs2music = (function (c2mChart) {
339
432
  });
340
433
  }
341
434
  else {
342
- c2mOptions.data[groupName] = c2mOptions.data[groupName].map((point, index) => {
435
+ dataObj[groupName] = dataObj[groupName].map((point, index) => {
343
436
  return {
344
437
  ...point,
345
438
  custom: {
346
- group: groupNumber,
347
- index
439
+ ...point.custom,
440
+ group: point.custom?.group ?? groupNumber,
441
+ index: point.custom?.index ?? index
348
442
  }
349
443
  };
350
444
  });
351
445
  }
352
446
  });
353
447
  }
354
- // @ts-ignore
355
448
  if (chart.config.options?.scales?.x?.stacked) {
356
- // @ts-ignore
357
- c2mOptions.options.stack = true;
449
+ if (c2mOptions.options) {
450
+ c2mOptions.options.stack = true;
451
+ }
358
452
  }
359
- // @ts-ignore
360
453
  if (options.audioEngine) {
361
- // @ts-ignore
362
454
  c2mOptions.audioEngine = options.audioEngine;
363
455
  }
364
456
  if (c2mOptions.data.length === 0) {
@@ -370,7 +462,6 @@ var chartjs2music = (function (c2mChart) {
370
462
  const { err, data: c2m } = c2mChart(c2mOptions);
371
463
  /* istanbul-ignore-next */
372
464
  if (err) {
373
- // @ts-ignore
374
465
  options.errorCallback?.(err);
375
466
  return;
376
467
  }
@@ -379,12 +470,32 @@ var chartjs2music = (function (c2mChart) {
379
470
  }
380
471
  chartStates.set(chart, {
381
472
  c2m,
382
- visible_groups: groups?.map((g, i) => i) ?? []
473
+ lastDataSnapshot: createDataSnapshot(chart)
383
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
+ }
384
495
  };
385
496
  const plugin = {
386
497
  id: "chartjs2music",
387
- afterInit: (chart, args, options) => {
498
+ afterInit: (chart, _args, options) => {
388
499
  if (!chartStates.has(chart)) {
389
500
  generateChart(chart, options);
390
501
  // Remove tooltip when the chart blurs
@@ -411,19 +522,17 @@ var chartjs2music = (function (c2mChart) {
411
522
  if (!chartStates.has(chart)) {
412
523
  generateChart(chart, options);
413
524
  }
414
- const { c2m: ref, visible_groups } = chartStates.get(chart);
525
+ const { c2m: ref } = chartStates.get(chart);
415
526
  if (!ref) {
416
527
  return;
417
528
  }
418
- // @ts-ignore
419
- const groups = ref._groups.slice(0);
420
- // @ts-ignore
421
- if (ref._options.stack) {
529
+ const refInternal = ref;
530
+ const groups = refInternal._groups.slice(0);
531
+ if (refInternal._options.stack) {
422
532
  groups.shift();
423
533
  }
424
534
  if (args.mode === "hide") {
425
535
  const err = ref.setCategoryVisibility(groups[args.index], false);
426
- visible_groups.splice(args.index, 1);
427
536
  if (err) {
428
537
  console.error(err);
429
538
  }
@@ -431,24 +540,49 @@ var chartjs2music = (function (c2mChart) {
431
540
  }
432
541
  if (args.mode === "show") {
433
542
  const err = ref.setCategoryVisibility(groups[args.index], true);
434
- visible_groups.push(args.index);
435
543
  if (err) {
436
544
  console.error(err);
437
545
  }
438
546
  return;
439
547
  }
440
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
+ },
441
570
  afterDestroy: (chart) => {
442
- const { c2m: ref } = chartStates.get(chart);
443
- if (!ref) {
571
+ const state = chartStates.get(chart);
572
+ if (!state?.c2m) {
444
573
  return;
445
574
  }
575
+ const { c2m: ref } = state;
576
+ if (state?.matrixKeydown && state.matrixKeydownTarget) {
577
+ state.matrixKeydownTarget.removeEventListener("keydown", state.matrixKeydown, true);
578
+ }
446
579
  ref.cleanUp();
447
580
  },
448
581
  defaults: {
449
582
  cc: null,
450
583
  audioEngine: null,
451
- errorCallback: null
584
+ errorCallback: null,
585
+ options: {}
452
586
  }
453
587
  };
454
588