Skip to main content

Developer integration

Using c2mChart to sonify a static chart is already powerful, but you can also sonify dynamic charts.

Visual syncing

You can also use the c2mChart option onFocusCallback to keep your visual chart synchronized with the user as they navigate. How this works will depedn on how you built your chart visuals in the first place.

onFocusCallback calls a function and provides an object with 3 properties:

  • slice - the name of the category
  • index - the index of the data point
  • point - the data point, including custom metadata if you provided it with the data points

Here's an example using Chart.js with a single line:

// Create your chart.js visual
const myChart = new Chart(canvas, config);

// Add Chart2Music
c2mChart({
title: "My chart",
type: "line",
element: canvas,
data: [1, 2, 3, 4, 5],
options: {
onFocusCallback: ({ index }) => {
myChart.setActiveElements([
{
datasetIndex: 0,
index,
},
]);
myChart.update();
},
},
});

Here's an example using Chart.js with multiple lines:

// Create your chart.js visual
const myChart = new Chart(canvas, config);

// Add Chart2Music
const categories = ["a", "b"];
c2mChart({
title: "My chart",
type: "line",
element: canvas,
data: {
a: [1, 2, 3, 4, 5],
b: [5, 4, 3, 2, 1],
},
options: {
onFocusCallback: ({ slice, index }) => {
myChart.setActiveElements([
{
datasetIndex: categories.indexOf(slice),
index,
},
]);
myChart.update();
},
},
});
tip

If you already have aesthetics for hovering over an element with a mouse, you should try to duplicate that treatment with your onFocusCallback. This way, you ensure that your mouse users and your keyboard users have access to the same information. Some common hover treatment includes:

  • Changing the color or size of a highlighted element
  • Having a tooltip appear near the hovered/focused data point
  • Showing a crosshair near the data point
caution

The contents of the onFocusCallback should be quick. If a user plays the full contents of a chart, the onFocusCallback could be invoked up to 40 times per second. A heavy onFocusCallback could negatively impact the performance of the play speed.

Other examples:

Dialogs

When you call c2mChart, you are adding the possibility for 3 dialogs to be added to the page:

  1. The Help dialog, which lists all of the keyboard interactions available to the user.
  2. The Options dialog, which gives users more fine-grained control over how Chart2Music renders the chart.
  3. Optionally, the Info dialog, which will display notes and annotations. If there are no notes nor annotations, the Info dialog will not be available.

These dialogs are built using un-styled vanilla HTML. If your site already applies global styles to any DIALOG, H1, BUTTON, or TABLE element, the built-in dialogs will inherit those styles.

However, you may want to style the dialogs to align with the rest of the page. To help with that, all dialogs come with the class chart2music-dialog. Furthermore, each individual dialog comes with its own class: chart2music-help-dialog, chart2music-info-dialog, and chart2music-option-dialog.

Here is the CSS used on this documentation site for the dialogs:

.chart2music-dialog {
border-radius: 8px;

h1 {
text-align: center;
}
button {
border: none;
background-color: transparent;
cursor: pointer;
}
table {
th:first-child {
text-align: right;
}
td:last-child {
display: none;
}
}
}

.chart2music-option-dialog {
fieldset {
border-radius: 8px;
}

label {
input[type="range"] {
display: block;
}
}
input[type="submit"]{
margin-top: 8px;
float: right;
border-radius: 8px;
}
}

Note that the above example uses CSS nesting, which became supported by most desktop browsers in late 2023, according to CanIUse.

Continuous Mode

Let's take a closer look at the chart above. Its X values are years: 1999, 2000, through 2021. Each value is consistently spaced 1 year apart. These equidistant values are ideal for Chart2Music's standard form of interaction, but not all charts follow that layout.

Let's look at another, more complicated chart.

In this example, the "S&P 500" line's first few X-values are 2:30, 2:35, 2:39, 2:44, and 2:50. Meanwhile, its neighbor Ibovespa's first few X-values are 1:05, 1:09, 1:14, 1:20, and 1:25. These values are roughly 5 minutes apart, but not exactly. Also, their ranges are different - Ibovespa starts and ends sooner than S&P, as well as having a different number of data points.

When you have irregular data like this, continuous mode may provide a better user experience than the standard UX.

The standard play mode plays every data point in a line, one after another. Playing everything will take as long, or as short, as the number of data points provides. Additionally, this mode impacts how users change groups. If a user is on the 3rd data point in group A, and they want to move to group B, they will move focus to the 3rd data point in group B. (If there are fewer than 3 data points in group B, they will be moved to B's last data point.)

In continuous mode, every data points are played proportionally to where they appear on the X axis. If there are large gaps between X-values, this will result in a long pause; if there are a lot of data points around an X-value, all of them will be played rapidly. The total play time is restricted to, at most, 20 seconds. Additionally, this mode impacts how users change groups. If a user is on x = 2:35pm on the S&P line, when they switch to another line, they'll switch to whichever point is closest to x = 2:35pm.

Here's a cheatsheet for the differences between the modes:

Default ModeContinuous Mode
When data points are playedOne at a timeProportionally
How long it takes to playAs long as it takesWithin a set time
Switching groups takes you to the closestindexX-value

If you believe that continuous mode is appropriate for your chart, you can set it in the axes options, like this:

axes: {
x: {
continuous: true
}
}

The continuous option is only available for the X axis.

Whatever your choice, users can always choose to turn on continuous mode on or off.

Live charts

All of our examples so far have been ignoring the fact that the c2mChart function actually returns a value. Let's dig into those values and what you can do with them.

c2mChart returns a golang-style object with 2 properties:

  • err - either null if there was no error, or a string with the errors that came about while generating your chart.
  • data - only provided if err is not null, data provides an instance of your chart. You can use this object to call the available chart methods, like appendData and setData.

Add data to a chart in real time.

Example:

const { data: chart } = c2mChart({
title: "Random numbers",
type: "line",
element: myElement,
data: [0], // There must always be data. You can't start with an empty chart.
options: {
live: true,
},
});
// Add a random number every 5 seconds
setInterval(() => {
chart.appendData(Math.random());
}, 5000);

This way, you can append one data point at a time. This is ideal for streaming a live chart, but sometimes you need to do more...

Re-setting data

If you want to reflash the entire contents of the chart, you can use the method setData.

<canvas id="myElement"></canvas> <button id="randomize">Randomize data</button>
const generateData = () => {
return [Math.random(), Math.random(), Math.random(), Math.random()];
};

const { data: chart } = c2mChart({
title: "Random sets",
type: "line",
element: myElement,
data: generateData(),
});

document.getElementById("randomize").addEventListener("click", () => {
chart.setData(generateData());
});

If the data change also changes the metadata of the axes (such as changing the minimum or maximum), you can also update that. For example:

chart.setData([1, 2, 3, 4, 5], {
x: {
minimum: 0,
},
});
caution

When you reset data, the user's focus moves back to the beginning of the chart by default. This behavior could confuse users. It's best practices to only reset data as the result of a user's action. For example, you can reset the chart based on the user clicking a button or making a selection from a form field.

If you don't want the user's focus to be reset, you can control where the focus goes. For example, let's say you have implemented zooming in/out of a chart. When you zoom, you change the range of data, but you want the user to continue to focus on the same data point. If you know that the user was on point #15, but once you zoom in, that will be point #5, you can include that in your data reset:

chart.setData(zoomedInData, {}, 5);

You can also specify the name of the group that should receive focus. This code resets the data to zoomedInData, with no changes to the axes metadata, and focuses on point[5] in group "b":

chart.setData(zoomedInData, {}, 5, "b");
info

By default, the point that receives focus is the first point in the first group. If you attempt to assign focus to a point or group that doesn't exist, then focus will be assigned to the first point in the first group.

note

When you reset data, the user will be informed which chart was updated, if the chart has a title. If there are multiple update-able charts on a page, be sure to include a title for each of them. This will help a user to distinguish which chart changed.

Also, since users will be informed every time a chart is updated, if you update your charts constantly, you will potentially annoy your users. If you need to regularly update your chart, consider using appendData, which only updates users if they've enabled monitoring mode.

Custom hotkeys

If you want to integrate custom interactions, you can use the c2mChart option customHotkeys. For example, let's imagine a bar chart where you can drill into data in individual bars. Let's say you've decided to use Alt+Down and Alt+Up to drill in/out of the bars. Here is how that code would look:

c2mChart({
title: "Drillable data",
type: "bar",
element: myElement,
data,
options: {
customHotkeys: [
{
key: {
altKey: true,
key: "ArrowDown",
},
title: "Drill in",
callback: drillIn,
},
{
key: {
altKey: true,
key: "ArrowUp",
},
title: "Drill out",
callback: drillOut,
},
],
},
});

If you want to overwrite a hotkey that Chart2Music has already defined, you can. We'd rather you didn't, because that could confuse users, but you can.

Here's an example for overwriting the hotkey "[" (which jumps to the minimum value):

customHotkeys: [
{
key: {
key: "[",
},
title: "Pan left",
callback: panLeft,
force: true,
},
];

Without the force:true, the hotkey would simply not get added.

Unsupported groups of data

Sometimes, you'll have a complicated chart that contains some features that are supported by Chart2Music, and some features that are not. We want to make accessible what we can, but we don't want to hide the features that we can't make accessible.

If you want to integrate Chart2Music into a chart, but it has chart types that C2M doesn't support, you can use the type unsupported.

Here's an example. Let's say you have a chart with a scatter plot and a violin plot. While you can provide the data for the scatter plot, you will need to identify the violin plot as unsupported.

{
type: ["scatter", "unsupported"],
data: {
Students: [
/* ... */
],
Distribution: null
}
}
caution

The data group that corresponds to the "unsupported" chart type MUST be null. Otherwise, you'll receive an error.

The end user will first arrive on the "Students" scatter plot. If they press Page Down, they'll navigate to the next group, which they'll be told is an "unsupported" chart type with the label "Distribution".

If you do find yourself in a situation where Chart2Music doesn't support a chart type that you need, please let us know.

Overriding verbiage

Chart2Music provides a large amount of information to users. You may want to override some or all aspects of that information, and replace it with your own verbiage. While Chart2Music supports that with the translationCallback option, you should proceed with caution.

There are typically 2 reasons why someone would to override verbiage:

  1. Foreign translations. While C2M currently supports English, French, German, Italian, and Spanish, you may have another language you'd like to support. You can use the translationCallback to override English text with your own languages' text. However, before doing this, please consider submitting your translations to Chart2Music. Even if you are only using a subset of the features and don't have time to translate everything in Chart2Music, we would still appreciate even partial translations.
  2. Accessibility researchers or specialists. If you have specific interests in accessibility, you may have strong preferences in what verbiage should be communicated to screen reader users. You can override the verbiage with your own preferences, but please keep in mind that Chart2Music has already user tested its verbiage with screen reader users. If you wish to override it, make sure that you conduct users testing before going into production.

If you are not an expert in accessibility, and you don't have a foreign translation issue, we strongly recommend against overriding any verbiage.

If you still want to proceed, here is an example of how to use translationCallback.

Let's say that have a chart with the title "Trees". When a user first arrives at the chart, their screen reader will read the summary as Sonified chart titled "Trees". Let's say you want that summary to say "Trees", Sonified chart. You can do that using the following code:

c2mChart({
...
options: {
translationCallback: ({language, id, evaluators}) => {
// If the language is something other than english, we're not going to translate it
if(language !== "en"){
return false;
}

// Go through all of the verbiage ID's we care about, and return the text we want to override with
switch(id){
case "summ-chart-title": {
return `"${evaluators["title"]}", Sonified chart`;
}
}

// Since we're not dealing with any of the specific ID's we want to deal with, just use the default translations. The way to communicate that is to return `false`.
return false;
}
}
});

The translationCallback intercepts verbiage translations before they happen. If you return false, the normal translation happens. If you return a string, the normal translation is skipped, and instead the returned string is provided to the user.

Let's look closer at the callback parameters a bit more. The translationCallback includes 3 items:

  • language - the human language that the translation should be in. By default, this will be "en".
  • id - the id from the translation dictionary, representing which verbiage string should be used. To see which ID's exist, and how they are usually translated, see the existing language file.
  • evaluators - Sometimes, verbiage strings have placeholders that need to be filled with variables. For example, the summ-chart-title ID refers to the string Sonified chart titled "{title}". For that ID, the evalutaors would look like {title: "Tree"}. Evaluators provide the variable contents to fill these placeholders.

It's important to note that the translationCallback happens before normal translations happen. If you return false, the normal translation happens. If you return a string, the normal translation is intercepted, and the returned string is provided to the end user.