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/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;
@@ -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, visible_groups } = chartStates.get(chart);
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
- // @ts-ignore
224
- datasetIndex: point.custom.group,
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
- 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) => {
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 { data, groups } = processData(chart.data, c2m_types);
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
- // @ts-ignore
299
- onFocusCallback: () => {
300
- displayPoint(chart);
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
- group: 0,
311
- index
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 groups = Object.keys(c2mOptions.data);
419
+ const dataObj = c2mOptions.data;
420
+ const groups = Object.keys(dataObj);
331
421
  groups.forEach((groupName, groupNumber) => {
332
- if (!isNaN(c2mOptions.data[groupName][0])) {
333
- c2mOptions.data[groupName] = c2mOptions.data[groupName].map((num, index) => {
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
- c2mOptions.data[groupName] = c2mOptions.data[groupName].map((point, index) => {
435
+ dataObj[groupName] = dataObj[groupName].map((point, index) => {
346
436
  return {
347
437
  ...point,
348
438
  custom: {
349
- group: groupNumber,
350
- index
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
- // @ts-ignore
360
- c2mOptions.options.stack = true;
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
- visible_groups: groups?.map((g, i) => i) ?? []
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, args, options) => {
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, visible_groups } = chartStates.get(chart);
525
+ const { c2m: ref } = chartStates.get(chart);
418
526
  if (!ref) {
419
527
  return;
420
528
  }
421
- // @ts-ignore
422
- const groups = ref._groups.slice(0);
423
- // @ts-ignore
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 { c2m: ref } = chartStates.get(chart);
446
- if (!ref) {
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