- 
          
- 
                Notifications
    You must be signed in to change notification settings 
- Fork 1.9k
add Chart2Music keyboard and sound accessibility features #6680
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 69 commits
6fb9c65
              87b4c5c
              64ae505
              d37d24f
              d0467bb
              3a73776
              b4df959
              97b608f
              6205d62
              7e40cde
              6d9185b
              935cf54
              44deae2
              c38ab67
              c6a27be
              cac174c
              159ca1b
              7467a47
              08b820b
              41b0fb9
              94e25c3
              61ea8ce
              c9a0b91
              cc92639
              7baa0d3
              3127dee
              2429e55
              00572e8
              30f4e11
              247b301
              d08adfb
              dbce5da
              e1f9563
              3f4ab54
              92d97b7
              6b2dcc9
              b5bb517
              25ca44a
              7328324
              00b2750
              b04bb17
              d4bd71b
              af2ff80
              80b4cf3
              ee99f35
              3841640
              d84d108
              0c43407
              4c75035
              3c49ed6
              f7be666
              4a260af
              f67c513
              41baab9
              26754b7
              ed18907
              1e4bcef
              2c1abf4
              614fb8b
              5f5e599
              fe9736e
              59ad5c6
              d18c584
              043d408
              129eb3b
              acc8517
              c5ad240
              0113d90
              771029e
              bfbbbf1
              cadfd7f
              56e582d
              8e55f98
              86d75d8
              15671b0
              057e067
              44f7ad3
              8e3b62b
              aed1e02
              1777fba
              3a0384f
              dba0b53
              56f2ebc
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # accessibility | ||
|  | ||
| While anticipating more accessibility libraries in the future, as of today this is a trivial wrapper over the `sonification` directory. | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # `plotly.js` wrapper for `chart2music`, sonified charts | ||
|  | ||
| This wrapper attaches a context object to `gd._context._c2m` with the following properties: | ||
|  | ||
| * `.options`: the options object as required by c2m | ||
| * `.info`: the info object as required by c2m | ||
| * `.ccOptions`: information about where to place the closed caption div | ||
| * `.c2mHandler`: the object returned by calling the c2m library's initializer | ||
|  | ||
| The first three have most of their values set by the defaults in *src/plot_api/plot_config.js*. | ||
|  | ||
|  | ||
| ### index.js | ||
|  | ||
| **index.js** exposes the following api: | ||
|  | ||
| * `initC2M(gd, defaultConfig)` full resets the `c2mChart` object. | ||
| * `defaultConfig` is equal to `gd._context.sonification`, as defined in *src/plot_api/plot_config.js*. | ||
|  | ||
|  | ||
| * `initClosedCaptionDiv(gd, config)` finds or creates the closed caption div, depending on `config`. | ||
| * `config` is equal to `defaultConfig.closedCaptions`. | ||
|  | ||
| ### all_codecs.js | ||
| **all_codecs.js** agregates all the individual **_codec.js* files, all are expect to export exactly two functions: `test` and `process`. | ||
|  | ||
| I chose to aggregate all codecs in a separate file because it will ultimately be a 1-1 conversion if `plotly.js` adopts ES modules. | ||
|  | ||
| ## Writing a Codec | ||
|  | ||
| This section is TODO | ||
|  | ||
| chart2music supports N types of graphs. We should figure out how how to convert as many plotly types to chart2music types. I will write descriptions for each type in the folder, if not the actual code, along with what `test` must return and what `process` must return. | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| 'use strict'; | ||
|  | ||
| var scatterXY = require('./xy_scatter_codec'); | ||
|  | ||
| exports.codecs = [ scatterXY ]; | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| 'use strict'; | ||
|  | ||
| var c2mPlotly = require('.'); | ||
|  | ||
| function enable_sonification(gd) { | ||
| // Collecting defaults | ||
| var defaultConfig = gd._context.sonification; | ||
| c2mPlotly.initC2M(gd, defaultConfig); | ||
| } | ||
| exports.enable_sonification = enable_sonification; | 
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| 'use strict'; | ||
|  | ||
| var c2m = require('chart2music'); | ||
| var Fx = require('../../components/fx'); | ||
| var Lib = require('../../lib'); | ||
|  | ||
| var codecs = require('./all_codecs').codecs; | ||
|  | ||
| function initC2M(gd, defaultConfig) { | ||
| // TODO: should this be Lib.getGraphDiv()? | ||
| // TODO what is there besides a fullReset? | ||
|          | ||
| // TODO Do we need the capacity to add data (live listen?) | ||
| // TODO Do we need the capacity to reset all data | ||
| var c2mContext = gd._context._c2m = {}; | ||
| c2mContext.options = Lib.extendDeepAll({}, defaultConfig.options); | ||
| c2mContext.info = Lib.extendDeepAll({}, defaultConfig.info); | ||
| c2mContext.ccOptions = Lib.extendDeepAll({}, defaultConfig.closedCaptions); | ||
|  | ||
| var labels = []; // TODO this probably needs to be stored in context- | ||
| // when data is updated this specific instance needs to be updated | ||
| // or the below function eneds to be reset to use the new instance of labels. | ||
| c2mContext.options.onFocusCallback = function(dataInfo) { | ||
| Fx.hover(gd, [{ | ||
| curveNumber: labels.indexOf(dataInfo.slice), | ||
| pointNumber: dataInfo.index | ||
| }]); | ||
| }; | ||
| // I generally don't like using closures like this. | ||
| // In this case it works out, we effectively treat 'labels' | ||
| // like a global, but it's a local. | ||
| // I'd rather the callback be given its c2m handler which | ||
| // could store extra data. Or bind c2mContext as `this`. | ||
|  | ||
| var titleText = gd._fullLayout.title?.text ?? 'Chart'; | ||
|         
                  marthacryan marked this conversation as resolved.
              Outdated
          
            Show resolved
            Hide resolved | ||
| var ccElement = initClosedCaptionDiv(gd, c2mContext.ccOptions); | ||
|  | ||
| // TODO: | ||
| // I believe that title OR title.text could contain the information needed | ||
|          | ||
| // So we should stop if title is a type string | ||
| // Furthermore, I think that the traces would have to point to their axis, | ||
| // since it might not be x1, could be x2, etc | ||
| // So this really needs to be part of process() | ||
| var xAxisText = gd._fullLayout.xaxis?.title?.text ?? 'X Axis'; | ||
| var yAxisText = gd._fullLayout.yaxis?.title?.text ?? 'Y Axis'; | ||
|  | ||
| var c2mData = {}; | ||
| var types = []; | ||
| var fullData = gd._fullData; | ||
| // TODO: We're looping through all traces, and that's fine, but it might be helpful to discern how things are organized | ||
| for(var i = 0; i < fullData.length; i++) { | ||
| var trace = fullData[i]; | ||
| // TODO: what happens if this doesn't run, weird c2m errors | ||
| for(var codecI = 0; codecI < codecs.length; codecI++) { | ||
| var test = codecs[codecI].test(trace); | ||
| if (!test) continue; | ||
| var label = test.name ? test.name : i.toString() + ' '; | ||
|  | ||
| var labelCount = 0; | ||
| var originalLabel = label; | ||
| while (label in c2mData) { | ||
| labelCount++; | ||
| label = originalLabel + labelCount.toString(); | ||
| } | ||
|  | ||
| labels.push(label); | ||
| types.push(test.type); | ||
| c2mData[label] = codecs[codecI].process(trace); | ||
| } | ||
| } // TODO add unsupported codec | ||
|  | ||
| c2mContext.c2mHandler = c2m.c2mChart({ | ||
| title: titleText, | ||
| type: types, | ||
| axes: { | ||
| x: { // needs to be generated | ||
| label: xAxisText | ||
| }, | ||
| y: { | ||
| label: yAxisText | ||
| }, | ||
| }, | ||
| element: gd, | ||
| cc: ccElement, | ||
| data: c2mData, | ||
| options: c2mContext.options, | ||
| info: c2mContext.info | ||
| }); | ||
| // TODO: We need to handle the possible error that c2mChart returns | ||
| } | ||
| exports.initC2M = initC2M; | ||
|  | ||
| function initClosedCaptionDiv(gd, config) { | ||
| if(config.generate) { | ||
| var closedCaptions = document.createElement('div'); | ||
| closedCaptions.id = config.elId; | ||
| closedCaptions.className = config.elClassname; | ||
| gd.parentNode.insertBefore(closedCaptions, gd.nextSibling); // this really might not work | ||
| return closedCaptions; | ||
| } else { | ||
| return document.getElementById(config.elId); | ||
| } | ||
| } | ||
|  | ||
| exports.initClosedCaptionDiv = initClosedCaptionDiv; | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| 'use strict'; | ||
| // This codec serves for one x axis and one y axis | ||
|  | ||
| function test(trace) { | ||
| if(!trace) return null; | ||
| if(!trace.type || trace.type !== 'scatter') return null; | ||
| // TODO: I think we can have an x OR a y | ||
| if((trace.y === undefined || trace.y.length === 0) && (trace.x === undefined || trace.x.length === 0)) return null; | ||
| return {type: 'scatter', name: trace.name}; | ||
| } | ||
| exports.test = test; | ||
|  | ||
| function process(trace) { | ||
| var traceData = []; | ||
| var x = trace.x && trace.x.length !== 0 ? trace.x : []; | ||
| var y = trace.y && trace.y.length !== 0 ? trace.y : []; | ||
|  | ||
| for(var p = 0; p < Math.max(x.length, y.length); p++) { | ||
| traceData.push({ // TODO I think we're copying the data here, bad. | ||
| x: x[p] ? x[p] : p, | ||
| y: y[p] ? y[p] : p, | ||
| label: trace.text[p] ? trace.text[p] : p | ||
| }); | ||
| } | ||
| return traceData; | ||
| } | ||
| exports.process = process; | 
Uh oh!
There was an error while loading. Please reload this page.