diff --git a/dist/maidr.js b/dist/maidr.js index 58b8184a..7c3d3b3e 100644 --- a/dist/maidr.js +++ b/dist/maidr.js @@ -1,6 +1,7 @@ class Constants { // element ids chart_container_id = 'chart-container'; + main_container_id = 'maidr-container'; //chart_container_class = 'chart-container'; // remove later braille_container_id = 'braille-div'; braille_input_id = 'braille-input'; @@ -15,6 +16,7 @@ class Constants { reviewSaveBrailleMode; chartId = ''; events = []; + postLoadEvents = []; // default constructor for all charts constructor() {} @@ -40,20 +42,24 @@ class Constants { NULL_FREQUENCY = 100; // autoplay speed - MAX_SPEED = 2000; - MIN_SPEED = 50; - INTERVAL = 50; + MAX_SPEED = 500; + MIN_SPEED = 50; // 50; + DEFAULT_SPEED = 250; + INTERVAL = 20; + AUTOPLAY_DURATION = 5000; // 5s // user settings vol = 0.5; MAX_VOL = 30; - autoPlayRate = 250; // ms per tone + // autoPlayRate = this.DEFAULT_SPEED; // ms per tone + autoPlayRate = this.DEFAULT_SPEED; // ms per tone colorSelected = '#03C809'; brailleDisplayLength = 32; // num characters in user's braille display. 40 is common length for desktop / mobile applications // advanced user settings showRect = 1; // true / false hasRect = 1; // true / false + hasSmooth = 1; // true / false (for smooth line points) duration = 0.3; outlierDuration = 0.06; autoPlayOutlierRate = 50; // ms per tone @@ -61,6 +67,7 @@ class Constants { colorUnselected = '#595959'; // we don't use this yet, but remember: don't rely on color! also do a shape or pattern fill isTracking = 1; // 0 / 1, is tracking on or off visualBraille = false; // do we want to represent braille based on what's visually there or actually there. Like if we have 2 outliers with the same position, do we show 1 (visualBraille true) or 2 (false) + globalMinMax = true; // user controls (not exposed to menu, with shortcuts usually) showDisplay = 1; // true / false @@ -74,7 +81,10 @@ class Constants { alt = this.isMac ? 'option' : 'Alt'; home = this.isMac ? 'fn + Left arrow' : 'Home'; end = this.isMac ? 'fn + Right arrow' : 'End'; + + // internal controls keypressInterval = 2000; // ms or 2s + tabMovement = null; // debug stuff debugLevel = 3; // 0 = no console output, 1 = some console, 2 = more console, etc @@ -106,6 +116,19 @@ class Constants { constants.autoPlayRate += this.INTERVAL; } } + + SpeedReset() { + constants.autoPlayRate = constants.DEFAULT_SPEED; + } + + ColorInvert(color) { + // invert an rgb color + let rgb = color.replace(/[^\d,]/g, '').split(','); + let r = 255 - rgb[0]; + let g = 255 - rgb[1]; + let b = 255 - rgb[2]; + return 'rgb(' + r + ',' + g + ',' + b + ')'; + } } class Resources { @@ -125,6 +148,9 @@ class Resources { 25: '25%', 50: '50%', 75: '75%', + q1: '25%', + q2: '50%', + q3: '75%', son_on: 'Sonification on', son_off: 'Sonification off', son_des: 'Sonification descrete', @@ -233,7 +259,7 @@ class Menu {

-

+

@@ -247,7 +273,7 @@ class Menu { - + `; CreateMenu() { @@ -256,28 +282,62 @@ class Menu { .querySelector('body') .insertAdjacentHTML('beforeend', this.menuHtml); - // menu events + // menu close events let allClose = document.querySelectorAll('#close_menu, #menu .close'); for (let i = 0; i < allClose.length; i++) { - allClose[i].addEventListener('click', function (e) { - this.Toggle(false); - }); + constants.events.push([ + allClose[i], + 'click', + function (e) { + menu.Toggle(false); + }, + ]); } - document - .getElementById('save_and_close_menu') - .addEventListener('click', function (e) { - this.SaveData(); - this.Toggle(false); - }); - document.getElementById('menu').addEventListener('keydown', function (e) { - if (e.which == 27) { - // esc - this.Toggle(false); - } - }); + constants.events.push([ + document.getElementById('save_and_close_menu'), + 'click', + function (e) { + menu.SaveData(); + menu.Toggle(false); + }, + ]); + constants.events.push([ + document.getElementById('menu'), + 'keydown', + function (e) { + if (e.key == 'Esc') { + // esc + menu.Toggle(false); + } + }, + ]); + + // open events + // note: this triggers a maidr destroy + constants.events.push([ + document, + 'keyup', + function (e) { + if (e.key == 'h') { + menu.Toggle(true); + } + }, + ]); } - Toggle(onoff) { + Destroy() { + // menu element destruction + let menu = document.getElementById('menu'); + if (menu) { + menu.remove(); + } + let backdrop = document.getElementById('menu_modal_backdrop'); + if (backdrop) { + backdrop.remove(); + } + } + + Toggle(onoff = false) { if (typeof onoff == 'undefined') { if (document.getElementById('menu').classList.contains('hidden')) { onoff = true; @@ -289,13 +349,14 @@ class Menu { // open this.whereWasMyFocus = document.activeElement; this.PopulateData(); + constants.tabMovement = 0; document.getElementById('menu').classList.remove('hidden'); - document.getElementById('modal_backdrop').classList.remove('hidden'); + document.getElementById('menu_modal_backdrop').classList.remove('hidden'); document.querySelector('#menu .close').focus(); } else { // close document.getElementById('menu').classList.add('hidden'); - document.getElementById('modal_backdrop').classList.add('hidden'); + document.getElementById('menu_modal_backdrop').classList.add('hidden'); this.whereWasMyFocus.focus(); this.whereWasMyFocus = null; } @@ -356,6 +417,252 @@ class Menu { } } +class Description { + // This class creates an html modal containing summary info of the active chart + // Trigger popup with 'D' key + // Info is basically anything available, but stuff like: + // - chart type + // - chart labels, like title, subtitle, caption etc + // - chart data (an accessible html table) + + constructor() { + //this.CreateComponent(); // disabled as we're in development and have switched priorities + } + + CreateComponent() { + // modal containing description summary stuff + let html = ` + + + + `; + + document.querySelector('body').insertAdjacentHTML('beforeend', html); + + // close events + let allClose = document.querySelectorAll( + '#close_desc, #description .close' + ); + for (let i = 0; i < allClose.length; i++) { + constants.events.push([ + allClose[i], + 'click', + function (e) { + description.Toggle(false); + }, + ]); + } + constants.events.push([ + document.getElementById('description'), + 'keydown', + function (e) { + if (e.key == 'Esc') { + // esc + description.Toggle(false); + } + }, + ]); + + // open events + constants.events.push([ + document, + 'keyup', + function (e) { + if (e.key == 'd') { + description.Toggle(true); + } + }, + ]); + } + + Destroy() { + // description element destruction + let description = document.getElementById('menu'); + if (description) { + description.remove(); + } + let backdrop = document.getElementById('desc_modal_backdrop'); + if (backdrop) { + backdrop.remove(); + } + } + + Toggle(onoff = false) { + if (typeof onoff == 'undefined') { + if (document.getElementById('description').classList.contains('hidden')) { + onoff = true; + } else { + onoff = false; + } + } + if (onoff) { + // open + this.whereWasMyFocus = document.activeElement; + constants.tabMovement = 0; + this.PopulateData(); + document.getElementById('description').classList.remove('hidden'); + document.getElementById('desc_modal_backdrop').classList.remove('hidden'); + document.querySelector('#description .close').focus(); + } else { + // close + document.getElementById('description').classList.add('hidden'); + document.getElementById('desc_modal_backdrop').classList.add('hidden'); + this.whereWasMyFocus.focus(); + this.whereWasMyFocus = null; + } + } + + PopulateData() { + let descHtml = ''; + + // chart labels and descriptions + let descType = ''; + if (constants.chartType == 'bar') { + descType = 'Bar chart'; + } else if (constants.chartType == 'heat') { + descType = 'Heatmap'; + } else if (constants.chartType == 'box') { + descType = 'Box plot'; + } else if (constants.chartType == 'scatter') { + descType = 'Scatter plot'; + } else if (constants.chartType == 'line') { + descType = 'Line chart'; + } else if (constants.chartType == 'hist') { + descType = 'Histogram'; + } + + if (descType) { + descHtml += `

Type: ${descType}

`; + } + if (plot.title != null) { + descHtml += `

Title: ${plot.title}

`; + } + if (plot.subtitle != null) { + descHtml += `

Subtitle: ${plot.subtitle}

`; + } + if (plot.caption != null) { + descHtml += `

Caption: ${plot.caption}

`; + } + + // table of data, prep + let descTableHtml = ''; + let descLabelX = null; + let descLabelY = null; + let descTickX = null; + let descTickY = null; + let descData = null; + let descNumCols = 0; + let descNumColsWithLabels = 0; + let descNumRows = 0; + let descNumRowsWithLabels = 0; + if (constants.chartType == 'bar') { + if (plot.plotLegend.x != null) { + descLabelX = plot.plotLegend.x; + descNumColsWithLabels += 1; + } + if (plot.plotLegend.y != null) { + descLabelY = plot.plotLegend.y; + descNumRowsWithLabels += 1; + } + if (plot.columnLabels != null) { + descTickX = plot.columnLabels; + descNumRowsWithLabels += 1; + } + if (plot.plotData != null) { + descData = []; + descData[0] = plot.plotData; + descNumCols = plot.plotData.length; + descNumRows = 1; + descNumColsWithLabels += descNumCols; + descNumRowsWithLabels += descNumRows; + } + } + + // table of data, create + if (descData != null) { + descTableHtml += ''; + + // header rows + if (descLabelX != null || descTickX != null) { + descTableHtml += ''; + if (descLabelX != null) { + descTableHtml += ''; + if (descLabelY != null) { + descTableHtml += ''; + } + if (descTickY != null) { + descTableHtml += ''; + } + descTableHtml += ``; + descTableHtml += ''; + } + if (descTickX != null) { + descTableHtml += ''; + if (descLabelY != null) { + descTableHtml += ''; + } + if (descTickY != null) { + descTableHtml += ''; + } + for (let i = 0; i < descNumCols; i++) { + descTableHtml += ``; + } + descTableHtml += ''; + } + descTableHtml += ''; + } + + // body rows + if (descNumRows > 0) { + descTableHtml += ''; + for (let i = 0; i < descNumRows; i++) { + descTableHtml += ''; + if (descLabelY != null && i == 0) { + descTableHtml += ``; + } + if (descTickY != null) { + descTableHtml += ``; + } + for (let j = 0; j < descNumCols; j++) { + descTableHtml += ``; + } + descTableHtml += ''; + } + descTableHtml += ''; + } + + descTableHtml += '
${descLabelX}
${descTickX[i]}
${descLabelY}${descTickY[i]}${descData[i][j]}
'; + } + + // bar: don't need colspan or rowspan stuff, put legendX and Y as headers + + document.getElementById('desc_title').innerHTML = descType + ' description'; + document.getElementById('desc_content').innerHTML = descHtml; + document.getElementById('desc_table').innerHTML = descTableHtml; + } +} + class Position { constructor(x, y, z = -1) { this.x = x; @@ -431,7 +738,6 @@ class Tracker { eventToLog.timestamp = Object.assign(e.timeStamp); eventToLog.time = Date().toString(); eventToLog.key = Object.assign(e.key); - eventToLog.which = Object.assign(e.which); eventToLog.altKey = Object.assign(e.altKey); eventToLog.ctrlKey = Object.assign(e.ctrlKey); eventToLog.shiftKey = Object.assign(e.shiftKey); @@ -612,7 +918,7 @@ class Tracker { } } } - } else if (constants.chartType == 'scatter') { + } else if (constants.chartType == 'point') { if (!this.isUndefinedOrNull(plot.x_group_label)) { x_label = plot.x_group_label; } @@ -654,30 +960,7 @@ class Tracker { } class Review { - constructor() { - // review mode form field - if (!document.getElementById(constants.review_id)) { - if (document.getElementById(constants.info_id)) { - document - .getElementById(constants.info_id) - .insertAdjacentHTML( - 'beforebegin', - '' - ); - } - } - - if (constants) { - constants.review_container = document.querySelector( - '#' + constants.review_id_container - ); - constants.review = document.querySelector('#' + constants.review_id); - } - } + constructor() {} ToggleReviewMode(onoff = true) { // true means on or show @@ -701,6 +984,41 @@ class Review { } } +class LogError { + constructor() {} + + LogAbsentElement(a) { + console.log(a, 'not found. Visual highlighting is turned off.'); + } + + LogCriticalElement(a) { + consolelog(a, 'is critical. MAIDR unable to run'); + } + + LogDifferentLengths(a, b) { + console.log( + a, + 'and', + b, + 'do not have the same length. Visual highlighting is turned off.' + ); + } + + LogTooManyElements(a, b) { + console.log( + 'Too many', + a, + 'elements. Only the first', + b, + 'will be highlighted.' + ); + } + + LogNotArray(a) { + console.log(a, 'is not an array. Visual highlighting is turned off.'); + } +} + // Audio class // Sets up audio stuff (compressor, gain), // sets up an oscillator that has good falloff (no clipping sounds) and can be instanced to be played anytime and can handle overlaps, @@ -737,6 +1055,9 @@ class Audio { let rawFreq = 0; let frequency = 0; let panning = 0; + + let waveType = 'sine'; + // freq goes between min / max as rawFreq goes between min(0) / max if (constants.chartType == 'bar') { rawFreq = plot.plotData[position.x]; @@ -758,23 +1079,17 @@ class Audio { } else if (constants.chartType == 'box') { let plotPos = constants.plotOrientation == 'vert' ? position.x : position.y; - let sectionPos = - constants.plotOrientation == 'vert' ? position.y : position.x; - if ( - position.z > -1 && - Object.hasOwn(plot.plotData[plotPos][sectionPos], 'values') - ) { + let sectionKey = plot.GetSectionKey( + constants.plotOrientation == 'vert' ? position.y : position.x + ); + if (Array.isArray(plot.plotData[plotPos][sectionKey])) { // outliers are stored in values with a seperate itterator - rawFreq = plot.plotData[plotPos][sectionPos].values[position.z]; + rawFreq = plot.plotData[plotPos][sectionKey][position.z]; } else { // normal points - if (constants.plotOrientation == 'vert') { - rawFreq = plot.plotData[plotPos][sectionPos].y; - } else { - rawFreq = plot.plotData[plotPos][sectionPos].x; - } + rawFreq = plot.plotData[plotPos][sectionKey]; } - if (plot.plotData[plotPos][sectionPos].type != 'blank') { + if (plot.plotData[plotPos][sectionKey] != null) { if (constants.plotOrientation == 'vert') { frequency = this.SlideBetween( rawFreq, @@ -828,10 +1143,22 @@ class Audio { 1 ); } else if ( - constants.chartType == 'scatter' || - constants.chartType == 'line' + constants.chartType == 'point' || + constants.chartType == 'smooth' ) { - if (constants.chartType == 'scatter') { + // are we using global min / max, or just this layer? + constants.globalMinMax = true; + let chartMin = constants.minY; + let chartMax = constants.maxY; + if (constants.chartType == 'smooth') { + chartMin = plot.curveMinY; + chartMax = plot.curveMaxY; + } + if (constants.globalMinMax) { + chartMin = Math.min(constants.minY, plot.curveMinY); + chartMax = Math.max(constants.maxY, plot.curveMaxY); + } + if (constants.chartType == 'point') { // point layer // more than one point with same x-value rawFreq = plot.y[position.x][position.z]; @@ -850,38 +1177,89 @@ class Audio { rawPanning = position.x; frequency = this.SlideBetween( rawFreq, - constants.minY, - constants.maxY, + chartMin, + chartMax, constants.MIN_FREQUENCY, constants.MAX_FREQUENCY ); - panning = this.SlideBetween( - rawPanning, - constants.minX, - constants.maxX, - -1, - 1 - ); - } else if (constants.chartType == 'line') { - // best fit line layer + panning = this.SlideBetween(rawPanning, chartMin, chartMax, -1, 1); + } else if (constants.chartType == 'smooth') { + // best fit smooth layer rawFreq = plot.curvePoints[positionL1.x]; rawPanning = positionL1.x; frequency = this.SlideBetween( rawFreq, - plot.curveMinY, - plot.curveMaxY, + chartMin, + chartMax, constants.MIN_FREQUENCY, constants.MAX_FREQUENCY ); - panning = this.SlideBetween( - rawPanning, - constants.minX, - constants.maxX, - -1, - 1 - ); + panning = this.SlideBetween(rawPanning, chartMin, chartMax, -1, 1); + } + } else if (constants.chartType == 'hist') { + rawFreq = plot.plotData[position.x].y; + rawPanning = plot.plotData[position.x].x; + frequency = this.SlideBetween( + rawFreq, + constants.minY, + constants.maxY, + constants.MIN_FREQUENCY, + constants.MAX_FREQUENCY + ); + panning = this.SlideBetween( + rawPanning, + constants.minX, + constants.maxX, + -1, + 1 + ); + } else if (constants.chartType == 'line') { + rawFreq = plot.pointValuesY[position.x]; + rawPanning = position.x; + frequency = this.SlideBetween( + rawFreq, + constants.minY, + constants.maxY, + constants.MIN_FREQUENCY, + constants.MAX_FREQUENCY + ); + panning = this.SlideBetween( + rawPanning, + constants.minX, + constants.maxX, + -1, + 1 + ); + } else if ( + constants.chartType == 'stacked_bar' || + constants.chartType == 'stacked_normalized_bar' || + constants.chartType == 'dodged_bar' + ) { + rawFreq = plot.plotData[position.x][position.y]; + if (rawFreq == 0) { + this.PlayNull(); + return; + } else if (Array.isArray(rawFreq)) { + rawFreq = rawFreq[position.z]; } + rawPanning = position.x; + frequency = this.SlideBetween( + rawFreq, + constants.minY, + constants.maxY, + constants.MIN_FREQUENCY, + constants.MAX_FREQUENCY + ); + panning = this.SlideBetween( + rawPanning, + constants.minX, + constants.maxX, + -1, + 1 + ); + let waveTypeArr = ['triangle', 'square', 'sawtooth', 'sine']; + waveType = waveTypeArr[position.y]; } if (constants.debugLevel > 5) { @@ -920,14 +1298,16 @@ class Audio { // outlier = short tone // whisker = normal tone // range = chord - let plotPos = - constants.plotOrientation == 'vert' ? position.x : position.y; - let sectionPos = - constants.plotOrientation == 'vert' ? position.y : position.x; - let sectionType = plot.plotData[plotPos][sectionPos].type; - if (sectionType == 'outlier') { + let sectionKey = plot.GetSectionKey( + constants.plotOrientation == 'vert' ? position.y : position.x + ); + if (sectionKey == 'lower_outlier' || sectionKey == 'upper_outlier') { currentDuration = constants.outlierDuration; - } else if (sectionType == 'whisker') { + } else if ( + sectionKey == 'q1' || + sectionKey == 'q2' || + sectionKey == 'q3' + ) { //currentDuration = constants.duration * 2; } else { //currentDuration = constants.duration * 2; @@ -935,14 +1315,12 @@ class Audio { } // create tones - this.playOscillator(frequency, currentDuration, panning, volume, 'sine'); + this.playOscillator(frequency, currentDuration, panning, volume, waveType); if (constants.chartType == 'box') { - let plotPos = - constants.plotOrientation == 'vert' ? position.x : position.y; - let sectionPos = - constants.plotOrientation == 'vert' ? position.y : position.x; - let sectionType = plot.plotData[plotPos][sectionPos].type; - if (sectionType == 'range') { + let sectionKey = plot.GetSectionKey( + constants.plotOrientation == 'vert' ? position.y : position.x + ); + if (sectionKey == 'q1' || sectionKey == 'q2' || sectionKey == 'q3') { // also play an octive below at lower vol let freq2 = frequency / 2; this.playOscillator( @@ -1089,7 +1467,6 @@ class Audio { } PlayNull() { - console.log('playing null'); let frequency = constants.NULL_FREQUENCY; let duration = constants.duration; let panning = 0; @@ -1127,7 +1504,7 @@ class Audio { panning = this.SlideBetween(position.x, 0, plot.plotData[position.y].length-1, -1, 1); } else if ( constants.chartType == 'heat' ) { panning = this.SlideBetween(position.x, 0, plot.num_cols-1, -1, 1); - } else if ( constants.chartType == 'scatter' ) { + } else if ( constants.chartType == 'point' ) { panning = this.SlideBetween(position.x, 0, plot.x.length-1, -1, 1); } } catch { @@ -1204,7 +1581,7 @@ class Display { } toggleBrailleMode(onoff) { - if (constants.chartType == 'scatter') { + if (constants.chartType == 'point') { this.announceText('Braille is not supported in point layer.'); return; } @@ -1248,7 +1625,7 @@ class Display { constants.brailleInput.focus(); constants.brailleInput.setSelectionRange(position.x, position.x); - this.SetBraille(plot); + this.SetBraille(); if (constants.chartType == 'heat') { let pos = position.y * (plot.num_cols + 1) + position.x; @@ -1281,11 +1658,11 @@ class Display { } toggleSonificationMode() { - if (constants.chartType == 'scatter') { + if (singleMaidr.type == 'point' || singleMaidr.type.includes('point')) { if (constants.sonifMode == 'off') { - constants.sonifMode = 'sep'; + constants.sonifMode = 'on'; this.announceText(resources.GetString('son_sep')); - } else if (constants.sonifMode == 'sep') { + } else if (constants.sonifMode == 'on') { constants.sonifMode = 'same'; this.announceText(resources.GetString('son_same')); } else if (constants.sonifMode == 'same') { @@ -1313,15 +1690,29 @@ class Display { //constants.chartType = chartTypes[chartTypes.length - 1]; } else { constants.chartType = chartTypes[currentIndex - 1]; + this.announceText('Switched to ' + constants.chartType); // todo: connect this to a resource file so it can be localized } } else { if (currentIndex == chartTypes.length - 1) { //constants.chartType = chartTypes[0]; } else { constants.chartType = chartTypes[currentIndex + 1]; + this.announceText('Switched to ' + constants.chartType); // todo: connect this to a resource file so it can be localized } } - this.announceText('Switched to ' + constants.chartType); // todo: connect this to a resource file so it can be localized + } + + // update position relative to where we were on the previous layer + // newX = oldX * newLen / oldLen + if (constants.chartType == 'point') { + position.x = Math.round( + ((plot.x.length - 1) * positionL1.x) / (plot.curvePoints.length - 1) + ); + } else if (constants.chartType == 'smooth') { + // reverse math of the above + positionL1.x = Math.round( + ((plot.curvePoints.length - 1) * position.x) / (plot.x.length - 1) + ); } } @@ -1330,13 +1721,30 @@ class Display { } UpdateBraillePos() { - if (constants.chartType == 'bar') { + if ( + constants.chartType == 'bar' || + constants.chartType == 'hist' || + constants.chartType == 'line' + ) { constants.brailleInput.setSelectionRange(position.x, position.x); + } else if ( + constants.chartType == 'stacked_bar' || + constants.chartType == 'stacked_normalized_bar' || + constants.chartType == 'dodged_bar' + ) { + // if we're not on the top y position + let pos = null; + if (position.y < plot.plotData[0].length - 1) { + pos = position.x; + } else { + pos = position.x * (plot.fill.length + 1) + position.y; + } + constants.brailleInput.setSelectionRange(pos, pos); } else if (constants.chartType == 'heat') { let pos = position.y * (plot.num_cols + 1) + position.x; constants.brailleInput.setSelectionRange(pos, pos); } else if (constants.chartType == 'box') { - // on box we extend characters a lot and have blanks, so we go to our label + // on box we extend characters a lot and have blanks, so we go to our type let sectionPos = constants.plotOrientation == 'vert' ? position.y : position.x; let targetLabel = this.boxplotGridPlaceholders[sectionPos]; @@ -1346,7 +1754,7 @@ class Display { for (let i = 0; i < constants.brailleData.length; i++) { if (constants.brailleData[i].type != 'blank') { if ( - resources.GetString(constants.brailleData[i].label) == targetLabel + resources.GetString(constants.brailleData[i].type) == targetLabel ) { haveTargetLabel = true; break; @@ -1364,12 +1772,15 @@ class Display { } constants.brailleInput.setSelectionRange(adjustedPos, adjustedPos); - } else if (constants.chartType == 'scatter') { + } else if ( + singleMaidr.type == 'point' || + singleMaidr.type.includes('point') + ) { constants.brailleInput.setSelectionRange(positionL1.x, positionL1.x); } } - displayValues(plot) { + displayValues() { // we build an html text string to output to both visual users and aria live based on what chart we're on, our position, and the mode // note: we do this all as one string rather than changing individual element IDs so that aria-live receives a single update @@ -1378,13 +1789,12 @@ class Display { let reviewText = ''; if (constants.chartType == 'bar') { // {legend x} is {colname x}, {legend y} is {value y} - if (plot.plotLegend.length > 0 && plot.columnLabels[positionx]) { + if (plot.plotLegend.x.length > 0 && plot.columnLabels[position.x]) { verboseText = - plot.plotLegend.x + ' is ' + plot.columnLabels[position.x]; + plot.plotLegend.x + ' is ' + plot.columnLabels[position.x] + ', '; } if (plot.plotData[position.x]) { - verboseText += - ', ' + plot.plotLegend.y + ' is ' + plot.plotData[position.x]; + verboseText += plot.plotLegend.y + ' is ' + plot.plotData[position.x]; } if (constants.textMode == 'off') { // do nothing :D @@ -1411,11 +1821,11 @@ class Display { ' ' + plot.y_labels[position.y] + ', ' + - plot.box_label + + plot.fill + ' is '; - if (constants.hasRect) { - verboseText += plot.plotData[2][position.y][position.x]; - } + // if (constants.hasRect) { + verboseText += plot.plotData[2][position.y][position.x]; + // } } else { verboseText += plot.y_group_label + @@ -1426,11 +1836,11 @@ class Display { ' ' + plot.x_labels[position.x] + ', ' + - plot.box_label + + plot.fill + ' is '; - if (constants.hasRect) { - verboseText += plot.plotData[2][position.y][position.x]; - } + // if (constants.hasRect) { + verboseText += plot.plotData[2][position.y][position.x]; + // } } // terse and verbose alternate between columns and rows if (constants.textMode == 'off') { @@ -1464,33 +1874,23 @@ class Display { let isOutlier = false; let plotPos = constants.plotOrientation == 'vert' ? position.x : position.y; - let sectionPos = - constants.plotOrientation == 'vert' ? position.y : position.x; + let sectionKey = plot.GetSectionKey( + constants.plotOrientation == 'vert' ? position.y : position.x + ); let textTerse = ''; let textVerbose = ''; - if ( - plot.plotData[plotPos][sectionPos].label == 'lower_outlier' || - plot.plotData[plotPos][sectionPos].label == 'upper_outlier' - ) { + if (sectionKey == 'lower_outlier' || sectionKey == 'upper_outlier') { isOutlier = true; } - if (plot.plotData[plotPos][sectionPos].type == 'outlier') { - val = plot.plotData[plotPos][sectionPos].values.join(', '); - if (plot.plotData[plotPos][sectionPos].values.length > 0) { - numPoints = plot.plotData[plotPos][sectionPos].values.length; - } else { - numPoints = 0; - } - } else if (plot.plotData[plotPos][sectionPos].type == 'blank') { + if (plot.plotData[plotPos][sectionKey] == null) { val = ''; if (isOutlier) numPoints = 0; + } else if (isOutlier) { + val = plot.plotData[plotPos][sectionKey].join(', '); + numPoints = plot.plotData[plotPos][sectionKey].length; } else { - if (constants.plotOrientation == 'vert') { - val = plot.plotData[plotPos][sectionPos].y; - } else { - val = plot.plotData[plotPos][sectionPos].x; - } + val = plot.plotData[plotPos][sectionKey]; } // set output @@ -1501,7 +1901,7 @@ class Display { } else if (!constants.navigation) { if (plot.y_group_label) textVerbose += plot.y_group_label; } - // and axis label + // and axes label if (constants.navigation) { if (plot.x_labels[plotPos]) { textVerbose += ' is '; @@ -1525,9 +1925,7 @@ class Display { textVerbose += numPoints + ' '; } // label - textVerbose += resources.GetString( - plot.plotData[plotPos][sectionPos].label - ); + textVerbose += resources.GetString(sectionKey); if (numPoints == 1) textVerbose += ' is '; else { textVerbose += 's '; @@ -1538,9 +1936,7 @@ class Display { (constants.navigation && constants.plotOrientation == 'horz') || (!constants.navigation && constants.plotOrientation == 'vert') ) { - textTerse += resources.GetString( - plot.plotData[plotPos][sectionPos].label - ); + textTerse += resources.GetString(sectionKey); // grammar if (numPoints != 1) { @@ -1549,7 +1945,7 @@ class Display { textTerse += ' '; } // val - if (plot.plotData[plotPos][sectionPos].type == 'blank' && !isOutlier) { + if (plot.plotData[plotPos][sectionKey] != null && !isOutlier) { textTerse += 'empty'; textVerbose += 'empty'; } else { @@ -1562,8 +1958,11 @@ class Display { output = '

' + textVerbose + '

\n'; else if (constants.textMode == 'terse') output = '

' + textTerse + '

\n'; - } else if (constants.chartType == 'scatter') { - if (constants.chartType == 'scatter') { + } else if ( + singleMaidr.type == 'point' || + singleMaidr.type.includes('point') + ) { + if (constants.chartType == 'point') { // point layer verboseText += plot.x_group_label + @@ -1589,8 +1988,8 @@ class Display { } else if (constants.textMode == 'verbose') { // set from verboseText } - } else if (constants.chartType == 'line') { - // best fit line layer + } else if (constants.chartType == 'smooth') { + // best fit smooth layer verboseText += plot.x_group_label + ' ' + @@ -1614,6 +2013,84 @@ class Display { } if (constants.textMode == 'verbose') output = '

' + verboseText + '

\n'; + } else if (constants.chartType == 'hist') { + if (constants.textMode == 'terse') { + // terse: {x}, {y} + output = + '

' + + plot.plotData[position.x].x + + ', ' + + plot.plotData[position.x].y + + '

\n'; + } else if (constants.textMode == 'verbose') { + // verbose: {xlabel} is xmin through xmax, {ylabel} is y + output = '

'; + if (plot.legendX) { + output = plot.legendX + ' is '; + } + output += plot.plotData[position.x].xmin; + output += ' through ' + plot.plotData[position.x].xmax + ', '; + if (plot.legendY) { + output += plot.legendY + ' is '; + } + output += plot.plotData[position.x].y; + } + } else if (constants.chartType == 'line') { + // line layer + verboseText += + plot.x_group_label + + ' is ' + + plot.pointValuesX[position.x] + + ', ' + + plot.y_group_label + + ' is ' + + plot.pointValuesY[position.x]; + + if (constants.textMode == 'off') { + // do nothing + } else if (constants.textMode == 'terse') { + output += + '

' + + plot.pointValuesX[position.x] + + ', ' + + plot.pointValuesY[position.x] + + '

\n'; + } else if (constants.textMode == 'verbose') { + // set from verboseText + output += '

' + verboseText + '

\n'; + } + } else if ( + constants.chartType == 'stacked_bar' || + constants.chartType == 'stacked_normalized_bar' || + constants.chartType == 'dodged_bar' + ) { + // {legend x} is {colname x}, {legend y} is {colname y}, value is {plotData[x][y]} + verboseText += plot.plotLegend.x + ' is ' + plot.level[position.x] + ', '; + verboseText += plot.plotLegend.y + ' is ' + plot.fill[position.y] + ', '; + verboseText += 'value is ' + plot.plotData[position.x][position.y]; + + if (constants.textMode == 'off') { + // do nothing + } else if (constants.textMode == 'terse') { + // navigation == 1 ? {colname x} : {colname y} is {plotData[x][y]} + if (constants.navigation == 1) { + output += + '

' + + plot.level[position.x] + + ' is ' + + plot.plotData[position.x][position.y] + + '

\n'; + } else { + output += + '

' + + plot.fill[position.y] + + ' is ' + + plot.plotData[position.x][position.y] + + '

\n'; + } + } else { + output += '

' + verboseText + '

\n'; + } } if (constants.infoDiv) constants.infoDiv.innerHTML = output; @@ -1626,72 +2103,29 @@ class Display { } } - displayXLabel(plot) { - let xlabel = ''; - if (constants.chartType == 'bar') { - xlabel = plot.plotLegend.x; - } else if ( - constants.chartType == 'heat' || - constants.chartType == 'box' || - constants.chartType == 'scatter' - ) { - xlabel = plot.x_group_label; - } - if (constants.textMode == 'terse') { - constants.infoDiv.innerHTML = '

' + xlabel + '

'; - } else if (constants.textMode == 'verbose') { - constants.infoDiv.innerHTML = '

x label is ' + xlabel + '

'; - } - } - - displayYLabel(plot) { - let ylabel = ''; - if (constants.chartType == 'bar') { - ylabel = plot.plotLegend.y; - } else if ( - constants.chartType == 'heat' || - constants.chartType == 'box' || - constants.chartType == 'scatter' - ) { - ylabel = plot.y_group_label; - } - if (constants.textMode == 'terse') { - constants.infoDiv.innerHTML = '

' + ylabel + '

'; - } else if (constants.textMode == 'verbose') { - constants.infoDiv.innerHTML = '

y label is ' + ylabel + '

'; - } - } - - displayTitle(plot) { - if (constants.textMode == 'terse') { - if (plot.title != '') { - constants.infoDiv.innerHTML = '

' + plot.title + '

'; - } else { - constants.infoDiv.innerHTML = '

Plot does not have a title.

'; - } - } else if (constants.textMode == 'verbose') { - if (plot.title != '') { - constants.infoDiv.innerHTML = '

Title is ' + plot.title + '

'; + displayInfo(textType, textValue) { + if (textType) { + if (textValue) { + if (constants.textMode == 'terse') { + constants.infoDiv.innerHTML = '

' + textValue + '

'; + } else if (constants.textMode == 'verbose') { + let capsTextType = + textType.charAt(0).toUpperCase() + textType.slice(1); + constants.infoDiv.innerHTML = + '

' + capsTextType + ' is ' + textValue + '

'; + } } else { - constants.infoDiv.innerHTML = '

Plot does not have a title.

'; - } - } - } + let aOrAn = ['a', 'e', 'i', 'o', 'u'].includes(textType.charAt(0)) + ? 'an' + : 'a'; - displayFill(plot) { - if (constants.textMode == 'terse') { - if (constants.chartType == 'heat') { - constants.infoDiv.innerHTML = '

' + plot.box_label + '

'; - } - } else if (constants.textMode == 'verbose') { - if (constants.chartType == 'heat') { constants.infoDiv.innerHTML = - '

Fill label is ' + plot.box_label + '

'; + '

Plot does not have ' + aOrAn + ' ' + textType + '

'; } } } - SetBraille(plot) { + SetBraille() { let brailleArray = []; if (constants.chartType == 'heat') { @@ -1713,6 +2147,68 @@ class Display { } brailleArray.push('⠳'); } + } else if ( + constants.chartType == 'stacked_bar' || + constants.chartType == 'stacked_normalized_bar' || + constants.chartType == 'dodged_bar' + ) { + // if we're not on the top y position, display just this level, using local min max + if (position.y < plot.plotData[0].length - 1) { + let localMin = null; + let localMax = null; + for (let i = 0; i < plot.plotData.length; i++) { + if (i == 0) { + localMin = plot.plotData[i][position.y]; + localMax = plot.plotData[i][position.y]; + } else { + if (plot.plotData[i][position.y] < localMin) { + localMin = plot.plotData[i][position.y]; + } + if (plot.plotData[i][position.y] > localMax) { + localMax = plot.plotData[i][position.y]; + } + } + } + let range = (localMax - localMin) / 4; + let low = localMin + range; + let medium = low + range; + let medium_high = medium + range; + for (let i = 0; i < plot.plotData.length; i++) { + if (plot.plotData[i][position.y] == 0) { + brailleArray.push('⠀'); + } else if (plot.plotData[i][position.y] <= low) { + brailleArray.push('⣀'); + } else if (plot.plotData[i][position.y] <= medium) { + brailleArray.push('⠤'); + } else if (plot.plotData[i][position.y] <= medium_high) { + brailleArray.push('⠒'); + } else { + brailleArray.push('⠉'); + } + } + } else { + // all mode, do braille similar to heatmap, with all data and seperator + for (let i = 0; i < plot.plotData.length; i++) { + let range = (constants.maxY - constants.minY) / 4; + let low = constants.minY + range; + let medium = low + range; + let medium_high = medium + range; + for (let j = 0; j < plot.plotData[i].length; j++) { + if (plot.plotData[i][j] == 0) { + brailleArray.push('⠀'); + } else if (plot.plotData[i][j] <= low) { + brailleArray.push('⣀'); + } else if (plot.plotData[i][j] <= medium) { + brailleArray.push('⠤'); + } else if (plot.plotData[i][j] <= medium_high) { + brailleArray.push('⠒'); + } else { + brailleArray.push('⠉'); + } + } + brailleArray.push('⠳'); + } + } } else if (constants.chartType == 'bar') { let range = (constants.maxY - constants.minY) / 4; let low = constants.minY + range; @@ -1729,7 +2225,7 @@ class Display { brailleArray.push('⠉'); } } - } else if (constants.chartType == 'line') { + } else if (constants.chartType == 'smooth') { let range = (plot.curveMaxY - plot.curveMinY) / 4; let low = plot.curveMinY + range; let medium = low + range; @@ -1746,8 +2242,23 @@ class Display { brailleArray.push('⠉'); } } + } else if (constants.chartType == 'hist') { + let range = (constants.maxY - constants.minY) / 4; + let low = constants.minY + range; + let medium = low + range; + let medium_high = medium + range; + for (let i = 0; i < plot.plotData.length; i++) { + if (plot.plotData[i].y <= low) { + brailleArray.push('⣀'); + } else if (plot.plotData[i].y <= medium) { + brailleArray.push('⠤'); + } else if (plot.plotData[i].y <= medium_high) { + brailleArray.push('⠒'); + } else { + brailleArray.push('⠉'); + } + } } else if (constants.chartType == 'box' && position.y > -1) { - // only run if we're on a plot // Idea here is to use different braille characters to physically represent the box // if sections are longer or shorter we'll add more characters // example: outlier, small space, long min, med 25/50/75, short max: ⠂ ⠒⠒⠒⠒⠒⠒⠿⠸⠿⠒ @@ -1756,165 +2267,87 @@ class Display { // and then create the appropriate number of characters // Full explanation on readme // - // This is messy and long (250 lines). If anyone wants to improve. Be my guest - - // First some prep work, we make an array of lengths and types that represent our plot - let brailleData = []; - let isBeforeMid = true; - let plotPos = - constants.plotOrientation == 'vert' ? position.x : position.y; - let valCoord = constants.plotOrientation == 'vert' ? 'y' : 'x'; - for (let i = 0; i < plot.plotData[plotPos].length; i++) { - let point = plot.plotData[plotPos][i]; - // pre clean up, we may want to remove outliers that share the same coordinates. Reasoning: We want this to visually represent the data, and I can't see 2 points on top of each other - if (point.values && constants.visualBraille) { - point.values = [...new Set(point.values)]; - } + // This is messy and long (250 lines). If anyone wants to improve, be my guest - let nextPoint = null; - let prevPoint = null; - if (i < plot.plotData[plotPos].length - 1) { - nextPoint = plot.plotData[plotPos][i + 1]; - } - if (i > 0) { - prevPoint = plot.plotData[plotPos][i - 1]; - } + // Some init stuff + let plotPos; + let globalMin; + let globalMax; + let numSections = plot.sections.length; + if (constants.plotOrientation == 'vert') { + plotPos = position.x; + globalMin = constants.minY; + globalMax = constants.maxY; + } else { + plotPos = position.y; + globalMin = constants.minX; + globalMax = constants.maxX; + } + // We convert main plot data to array of values and types, including min and max, and seperating outliers and removing nulls + let valData = []; + valData.push({ type: 'global_min', value: globalMin }); + for (let i = 0; i < numSections; i++) { + let sectionKey = plot.sections[i]; + let point = plot.plotData[plotPos][sectionKey]; let charData = {}; - if (i == 0) { - // first point, add space to next actual point - let firstCoord = 0; - for (let j = 0; j < plot.plotData[plotPos].length; j++) { - // find next actual point - if (valCoord in plot.plotData[plotPos][j]) { - firstCoord = plot.plotData[plotPos][j][valCoord]; - break; + if (point != null) { + if (sectionKey == 'lower_outlier' || sectionKey == 'upper_outlier') { + for (let j = 0; j < point.length; j++) { + charData = { + type: sectionKey, + value: point[j], + }; + valData.push(charData); } - } - charData = {}; - let minVal = - constants.plotOrientation == 'vert' - ? constants.minY - : constants.minX; - if (firstCoord - minVal > 0) { - charData.length = firstCoord; } else { - charData.length = 0; + charData = { + type: sectionKey, + value: point, + }; + valData.push(charData); } - if (charData.length < 0) charData.length = 0; // dunno why, but this happens sometimes - charData.type = 'blank'; - charData.label = 'blank'; - brailleData.push(charData); } + } + valData.push({ type: 'global_max', value: globalMax }); - if (point.type == 'blank') { - // this is a placeholder point, do nothing - } else if (point.type == 'outlier') { - // there might be lots of these or none - - // Spacing is messy: - // isBeforeMid: no pre space, yes after space - // ! isBeforeMid: yes pre space, no after space - // either way add spaces in between outlier points - - // pre point space - if (isBeforeMid) { - // no pre space - } else { - // yes after space - charData = {}; - charData.length = point.values[0] - prevPoint[valCoord]; - charData.type = 'blank'; - charData.label = 'blank'; - brailleData.push(charData); - } - - // now add points with spaces in between - for (var k = 0; k < point.values.length; k++) { - if (k == 0) { - charData = {}; - charData.length = 0; - charData.type = 'outlier'; - charData.label = point.label; - brailleData.push(charData); - } else { - charData = {}; - charData.length = point.values[k] - point.values[k - 1]; - charData.type = 'blank'; - charData.label = 'blank'; - brailleData.push(charData); - - charData = {}; - charData.length = 0; - charData.type = 'outlier'; - charData.label = point.label; - brailleData.push(charData); - } - } + // Then we convert to lengths and types + // We assign lengths based on the difference between each point, and assign blanks if this comes before or after an outlier + let lenData = []; + let isBeforeMid = true; + for (let i = 0; i < valData.length; i++) { + let diff; + // we compare inwardly, and midpoint is len 0 + if (isBeforeMid) { + diff = Math.abs(valData[i + 1].value - valData[i].value); + } else { + diff = Math.abs(valData[i].value - valData[i - 1].value); + } - // after point space - if (isBeforeMid) { - // yes pre space - charData = {}; - charData.length = - nextPoint[valCoord] - point.values[point.values.length - 1]; - charData.type = 'blank'; - charData.label = 'blank'; - brailleData.push(charData); - } else { - // no after space - } + if ( + valData[i].type == 'global_min' || + valData[i].type == 'global_max' + ) { + lenData.push({ type: 'blank', length: diff }); + } else if (valData[i].type == 'lower_outlier') { + // add diff as space, as well as a 0 len outlier point + // add blank last, as the earlier point is covered by global_min + lenData.push({ type: valData[i].type, length: 0 }); + lenData.push({ type: 'blank', length: diff }); + } else if (valData[i].type == 'upper_outlier') { + // add diff as space, as well as a 0 len outlier point, but reverse order from lower_outlier obvs + lenData.push({ type: 'blank', length: diff }); + lenData.push({ type: valData[i].type, length: 0 }); + } else if (valData[i].type == 'q2') { + // change calc method after midpoint, as we want spacing to go outward from center (and so center has no length) + isBeforeMid = false; + lenData.push({ type: valData[i].type, length: 0 }); } else { - if (point.label == '50') { - // exception: another 0 width point here - charData = {}; - charData.length = 0; - charData.type = point.type; - charData.label = point.label; - brailleData.push(charData); - - isBeforeMid = false; // mark this as we pass - } else { - // normal points: we calc dist between this point and point closest to middle - charData = {}; - if (isBeforeMid) { - charData.length = nextPoint[valCoord] - point[valCoord]; - } else { - charData.length = point[valCoord] - prevPoint[valCoord]; - } - charData.type = point.type; - charData.label = point.label; - brailleData.push(charData); - } - } - if (i == plot.plotData[plotPos].length - 1) { - // last point gotta add ending space manually - charData = {}; - let lastCoord = 0; - for (let j = 0; j < plot.plotData[plotPos].length; j++) { - // find last actual point - - if (point.type == 'outlier') { - lastCoord = valCoord == 'y' ? point.yMax : point.xMax; - } else if (valCoord in plot.plotData[plotPos][j]) { - lastCoord = plot.plotData[plotPos][j][valCoord]; - } - } - charData.length = - valCoord == 'y' - ? constants.maxY - lastCoord - : constants.maxX - lastCoord; - charData.type = 'blank'; - charData.label = 'blank'; - brailleData.push(charData); + // normal points + lenData.push({ type: valData[i].type, length: diff }); } } - // cleanup - for (let i = 0; i < brailleData.length; i++) { - // A bit of rounding to account for floating point errors - brailleData[i].length = Math.round(brailleData[i].length); // we currently just use rounding to whole number (pixel), but if other rounding is needed add it here - } // We create a set of braille characters based on the lengths @@ -1929,69 +2362,71 @@ class Display { // exception: for 25/75 and min/max, if they aren't exactly equal, assign different num characters // exception: center is always 456 123 - // Step 1, prepopulate each section with a single character, and log for character offset + // Step 1, sorta init. + // We prepopulate each non null section with a single character, and log for character offset let locMin = -1; + let locQ1 = -1; + let locQ3 = -1; let locMax = -1; - let loc25 = -1; - let loc75 = -1; - let numDefaultChars = 0; - for (let i = 0; i < brailleData.length; i++) { + let numAllocatedChars = 0; // counter for number of characters we've already assigned + for (let i = 0; i < lenData.length; i++) { if ( - brailleData[i].type != 'blank' && - (brailleData[i].length > 0 || brailleData[i].type == 'outlier') + lenData[i].type != 'blank' && + (lenData[i].length > 0 || + lenData[i].type == 'lower_outlier' || + lenData[i].type == 'upper_outlier') ) { - brailleData[i].numChars = 1; - numDefaultChars++; + lenData[i].numChars = 1; + numAllocatedChars++; } else { - brailleData[i].numChars = 0; + lenData[i].numChars = 0; } // store 25/75 min/max locations so we can check them later more easily - if (brailleData[i].label == 'min' && brailleData[i].length > 0) - locMin = i; - if (brailleData[i].label == 'max' && brailleData[i].length > 0) - locMax = i; - if (brailleData[i].label == '25') loc25 = i; - if (brailleData[i].label == '75') loc75 = i; + if (lenData[i].type == 'min' && lenData[i].length > 0) locMin = i; + if (lenData[i].type == 'max' && lenData[i].length > 0) locMax = i; + if (lenData[i].type == 'q1') locQ1 = i; + if (lenData[i].type == 'q3') locQ3 = i; // 50 gets 2 characters by default - if (brailleData[i].label == '50') { - brailleData[i].numChars = 2; - numDefaultChars++; + if (lenData[i].type == 'q2') { + lenData[i].numChars = 2; + numAllocatedChars++; // we just ++ here as we already ++'d above } } - // add extras to 25/75 min/max if needed - let currentPairs = ['25', '75']; + + // make sure rules are set for pairs (q1 / q3, min / max) + // if they're equal length, we don't need to do anything as they already each have 1 character + // if they're not equal length, we need to add 1 character to the longer one if (locMin > -1 && locMax > -1) { - currentPairs.push('min'); // we add these seperately because we don't always have both min and max - currentPairs.push('max'); - if (brailleData[locMin].length != brailleData[locMax].length) { - if (brailleData[locMin].length > brailleData[locMax].length) { - // make sure if they're different, they appear different - brailleData[locMin].numChars++; - numDefaultChars++; + // we do it this way as we don't always have both min and max + + if (lenData[locMin].length != lenData[locMax].length) { + if (lenData[locMin].length > lenData[locMax].length) { + lenData[locMin].numChars++; + numAllocatedChars++; } else { - brailleData[locMax].numChars++; - numDefaultChars++; + lenData[locMax].numChars++; + numAllocatedChars++; } } } - if (brailleData[loc25].length != brailleData[loc75].length) { - if (brailleData[loc25].length > brailleData[loc75].length) { - brailleData[loc25].numChars++; - numDefaultChars++; + // same for q1/q3 + if (lenData[locQ1].length != lenData[locQ3].length) { + if (lenData[locQ1].length > lenData[locQ3].length) { + lenData[locQ1].numChars++; + numAllocatedChars++; } else { - brailleData[loc75].numChars++; - numDefaultChars++; + lenData[locQ3].numChars++; + numAllocatedChars++; } } // Step 2: normalize and allocate remaining characters and add to our main braille array - let charsAvailable = constants.brailleDisplayLength - numDefaultChars; - let allocateCharacters = this.AllocateCharacters( - brailleData, - charsAvailable - ); + let charsAvailable = constants.brailleDisplayLength - numAllocatedChars; + let allocateCharacters = this.AllocateCharacters(lenData, charsAvailable); + // apply allocation + let brailleData = lenData; for (let i = 0; i < allocateCharacters.length; i++) { if (allocateCharacters[i]) { brailleData[i].numChars += allocateCharacters[i]; @@ -2001,6 +2436,8 @@ class Display { constants.brailleData = brailleData; if (constants.debugLevel > 5) { console.log('plotData[i]', plot.plotData[plotPos]); + console.log('valData', valData); + console.log('lenData', lenData); console.log('brailleData', brailleData); } @@ -2008,25 +2445,108 @@ class Display { for (let i = 0; i < brailleData.length; i++) { for (let j = 0; j < brailleData[i].numChars; j++) { let brailleChar = '⠀'; // blank - if (brailleData[i].label == 'min' || brailleData[i].label == 'max') { + if (brailleData[i].type == 'min' || brailleData[i].type == 'max') { brailleChar = '⠒'; } else if ( - brailleData[i].label == '25' || - brailleData[i].label == '75' + brailleData[i].type == 'q1' || + brailleData[i].type == 'q3' ) { brailleChar = '⠿'; - } else if (brailleData[i].label == '50') { + } else if (brailleData[i].type == 'q2') { if (j == 0) { brailleChar = '⠸'; } else { brailleChar = '⠇'; } - } else if (brailleData[i].type == 'outlier') { + } else if ( + brailleData[i].type == 'lower_outlier' || + brailleData[i].type == 'upper_outlier' + ) { brailleChar = '⠂'; } brailleArray.push(brailleChar); } } + } else if (constants.chartType == 'line') { + // TODO + // ⠑ + let range = (constants.maxY - constants.minY) / 4; + let low = constants.minY + range; + let medium = low + range; + let medium_high = medium + range; + let high = medium_high + range; + + for (let i = 0; i < plot.pointValuesY.length; i++) { + if ( + plot.pointValuesY[i] <= low && + i - 1 >= 0 && + plot.pointValuesY[i - 1] > low + ) { + // move from higher ranges to low + if (plot.pointValuesY[i - 1] <= medium) { + // move away from medium range + brailleArray.push('⢄'); + } else if (plot.pointValuesY[i - 1] <= medium_high) { + // move away from medium high range + brailleArray.push('⢆'); + } else if (plot.pointValuesY[i - 1] > medium_high) { + // move away from high range + brailleArray.push('⢇'); + } + } else if (plot.pointValuesY[i] <= low) { + // in the low range + brailleArray.push('⣀'); + } else if (i - 1 >= 0 && plot.pointValuesY[i - 1] <= low) { + // move from low to higher ranges + if (plot.pointValuesY[i] <= medium) { + // move to medium range + brailleArray.push('⡠'); + } else if (plot.pointValuesY[i] <= medium_high) { + // move to medium high range + brailleArray.push('⡰'); + } else if (plot.pointValuesY[i] > medium_high) { + // move to high range + brailleArray.push('⡸'); + } + } else if ( + plot.pointValuesY[i] <= medium && + i - 1 >= 0 && + plot.pointValuesY[i - 1] > medium + ) { + if (plot.pointValuesY[i - 1] <= medium_high) { + // move away from medium high range to medium + brailleArray.push('⠢'); + } else if (plot.pointValuesY[i - 1] > medium_high) { + // move away from high range + brailleArray.push('⠣'); + } + } else if (plot.pointValuesY[i] <= medium) { + brailleArray.push('⠤'); + } else if (i - 1 >= 0 && plot.pointValuesY[i - 1] <= medium) { + // move from medium to higher ranges + if (plot.pointValuesY[i] <= medium_high) { + // move to medium high range + brailleArray.push('⠔'); + } else if (plot.pointValuesY[i] > medium_high) { + // move to high range + brailleArray.push('⠜'); + } + } else if ( + plot.pointValuesY[i] <= medium_high && + i - 1 >= 0 && + plot.pointValuesY[i - 1] > medium_high + ) { + // move away from high range to medium high + brailleArray.push('⠑'); + } else if (plot.pointValuesY[i] <= medium_high) { + brailleArray.push('⠒'); + } else if (i - 1 >= 0 && plot.pointValuesY[i - 1] <= medium_high) { + // move from medium high to high range + brailleArray.push('⠊'); + } else if (plot.pointValuesY[i] <= high) { + brailleArray.push('⠉'); + } + } } constants.brailleInput.value = brailleArray.join(''); @@ -2047,8 +2567,8 @@ class Display { * This function allocates a total number of characters among an array of lengths, * proportionally to each length. * - * @param {Array} arr - The array of lengths. Each length should be a positive number. - * @param {number} totalCharacters - The total number of characters to be allocated. + * @param {Array} arr - The array of objects containing lengths, type, and current numChars. Each length should be a positive number. + * @param {number} charsToAllocate - The total number of characters to be allocated. * * The function first calculates the sum of all lengths in the array. Then, it * iterates over the array and calculates an initial allocation for each length, @@ -2074,37 +2594,37 @@ class Display { * * @returns {Array} The array of allocations. */ - AllocateCharacters(arr, totalCharacters) { + AllocateCharacters(arr, charsToAllocate) { // init let allocation = []; let sumLen = 0; for (let i = 0; i < arr.length; i++) { sumLen += arr[i].length; } - let notAllowed = ['lower_outlier', 'upper_outlier', '50']; + let notAllowed = ['lower_outlier', 'upper_outlier', '50']; // these types only have the 1 char they were assigned above // main allocation for (let i = 0; i < arr.length; i++) { - if (!notAllowed.includes(arr[i].label)) { - allocation[i] = Math.round((arr[i].length / sumLen) * totalCharacters); + if (!notAllowed.includes(arr[i].type)) { + allocation[i] = Math.round((arr[i].length / sumLen) * charsToAllocate); } } - // did it work? check for differences + // main allocation is not perfect, so we need to adjust let allocatedSum = allocation.reduce((a, b) => a + b, 0); - let difference = totalCharacters - allocatedSum; + let difference = charsToAllocate - allocatedSum; // If there's a rounding error, add/subtract characters proportionally let maxIterations = arr.length; // inf loop handler :D while (difference !== 0 && maxIterations > 0) { // (same method as above) for (let i = 0; i < arr.length; i++) { - if (!notAllowed.includes(arr[i].label)) { + if (!notAllowed.includes(arr[i].type)) { allocation[i] += Math.round((arr[i].length / sumLen) * difference); } } allocatedSum = allocation.reduce((a, b) => a + b, 0); - difference = totalCharacters - allocatedSum; + difference = charsToAllocate - allocatedSum; maxIterations--; } @@ -2145,62 +2665,108 @@ class Display { class BarChart { constructor() { - // bars. The actual bar elements in the SVG. Used to highlight visually + // initialize variables xlevel, data, and elements + let xlevel = null; + if ('axes' in singleMaidr) { + if (singleMaidr.axes.x) { + if (singleMaidr.axes.x.level) { + xlevel = singleMaidr.axes.x.level; + } + } + // todo: handle y for vertical bar charts + } + let data = null; + if ('data' in singleMaidr) { + data = singleMaidr.data; + } + let elements = null; if ('elements' in singleMaidr) { - this.bars = singleMaidr.elements; - constants.hasRect = 1; - } else { - this.bars = constants.chart.querySelectorAll('g[id^="geom_rect"] > rect'); - constants.hasRect = 0; + elements = singleMaidr.elements; + } + + if (xlevel && data && elements) { + if (elements.length != data.length) { + // I didn't throw an error but give a warning + constants.hasRect = 0; + logError.logDifferentLengths('elements', 'data'); + } else if (xlevel.length != elements.length) { + constants.hasRect = 0; + logError.logDifferentLengths('x level', 'elements'); + } else if (data.length != xlevel.length) { + constants.hasRect = 0; + logError.logDifferentLengths('x level', 'data'); + } else { + this.bars = elements; + constants.hasRect = 1; + } + } else if (data && elements) { + if (data.length != elements.length) { + constants.hasRect = 0; + logError.logDifferentLengths('data', 'elements'); + } else { + this.bars = elements; + constants.hasRect = 1; + } + } else if (xlevel && data) { + if (xlevel.length != data.length) { + constants.hasRect = 0; + logError.logDifferentLengths('x level', 'data'); + } + logError.LogAbsentElement('elements'); + } else if (data) { + logError.LogAbsentElement('x level'); + logError.LogAbsentElement('elements'); } + // bars. The actual bar elements in the SVG. Used to highlight visually + // if ('elements' in singleMaidr) { + // this.bars = singleMaidr.elements; + // constants.hasRect = 1; + // } else { + // // this.bars = constants.chart.querySelectorAll('g[id^="geom_rect"] > rect'); // if we use plot.plotData.length instead of plot.bars.length, we don't have to include this + // constants.hasRect = 0; + // } + // column labels, both legend and tick this.columnLabels = []; let legendX = ''; let legendY = ''; - if ('axis' in singleMaidr) { + if ('labels' in singleMaidr) { + if ('x' in singleMaidr.labels) { + legendX = singleMaidr.labels.x; + } + if ('y' in singleMaidr.labels) { + legendY = singleMaidr.labels.y; + } + } + if ('axes' in singleMaidr) { // legend labels - if (singleMaidr.axis.x) { - if (singleMaidr.axis.x.label) { - legendX = singleMaidr.axis.x.label; + if (singleMaidr.axes.x) { + if (singleMaidr.axes.x.label) { + if (legendX == '') { + legendX = singleMaidr.axes.x.label; + } } } - if (singleMaidr.axis.y) { - if (singleMaidr.axis.y.label) { - legendY = singleMaidr.axis.y.label; + if (singleMaidr.axes.y) { + if (singleMaidr.axes.y.label) { + if (legendY == '') { + legendY = singleMaidr.axes.y.label; + } } } // tick labels - if (singleMaidr.axis.x) { - if (singleMaidr.axis.x.format) { - this.columnLabels = singleMaidr.axis.x.format; + if (singleMaidr.axes.x) { + if (singleMaidr.axes.x.level) { + this.columnLabels = singleMaidr.axes.x.level; } } - if (singleMaidr.axis.y) { - if (singleMaidr.axis.y.format) { - this.columnLabels = singleMaidr.axis.y.format; + if (singleMaidr.axes.y) { + if (singleMaidr.axes.y.level) { + this.columnLabels = singleMaidr.axes.y.level; } } - } else { - // legend labels - if (constants.chart.querySelector('g[id^="xlab"] tspan')) { - legendX = constants.chart.querySelector( - 'g[id^="xlab"] tspan' - ).innerHTML; - } - if (constants.chart.querySelector('g[id^="ylab"] tspan')) { - legendY = constants.chart.querySelector( - 'g[id^="ylab"] tspan' - ).innerHTML; - } - - // tick labels - this.columnLabels = this.ParseInnerHTML( - constants.chart.querySelectorAll( - 'g:not([id^="xlab"]):not([id^="ylab"]) > g > g > g > text[text-anchor="middle"]' - ) - ); } this.plotLegend = { @@ -2208,17 +2774,30 @@ class BarChart { y: legendY, }; - // title, either pulled from data or from the SVG + // title this.title = ''; - if ('title' in singleMaidr) { - this.title = singleMaidr.title; - } else if ( - constants.chart.querySelector('g[id^="plot.title..titleGrob"] tspan') - ) { - this.title = constants.chart.querySelector( - 'g[id^="plot.title..titleGrob"] tspan' - ).innerHTML; - this.title = this.title.replace('\n', '').replace(/ +(?= )/g, ''); // there are multiple spaces and newlines, sometimes + if ('labels' in singleMaidr) { + if ('title' in singleMaidr.labels) { + this.title = singleMaidr.labels.title; + } + } + if (this.title == '') { + if ('title' in singleMaidr) { + this.title = singleMaidr.title; + } + } + + // subtitle + if ('labels' in singleMaidr) { + if ('subtitle' in singleMaidr.labels) { + this.subtitle = singleMaidr.labels.subtitle; + } + } + // caption + if ('labels' in singleMaidr) { + if ('caption' in singleMaidr.labels) { + this.caption = singleMaidr.labels.caption; + } } if (Array.isArray(singleMaidr)) { @@ -2250,6 +2829,18 @@ class BarChart { } } constants.maxX = this.columnLabels.length; + constants.autoPlayRate = Math.min( + Math.ceil(constants.AUTOPLAY_DURATION / (constants.maxX + 1)), + constants.MAX_SPEED + ); + constants.DEFAULT_SPEED = constants.autoPlayRate; + if (constants.autoPlayRate < constants.MIN_SPEED) { + constants.MIN_SPEED = constants.autoPlayRate; + } + } + + PlayTones() { + audio.playTone(); } GetLegendFromManualData() { @@ -2307,17 +2898,20 @@ class BarChart { } Select() { - this.DeselectAll(); + this.UnSelectPrevious(); if (this.bars) { - this.bars[position.x].style.fill = constants.colorSelected; + this.activeElement = this.bars[position.x]; + if (this.activeElement) { + this.activeElementColor = this.activeElement.style.fill; + this.activeElement.style.fill = constants.colorSelected; + } } } - DeselectAll() { - if (this.bars) { - for (let i = 0; i < this.bars.length; i++) { - this.bars[i].style.fill = constants.colorUnselected; - } + UnSelectPrevious() { + if (this.activeElement) { + this.activeElement.style.fill = this.activeElementColor; + this.activeElement = null; } } } @@ -2326,104 +2920,101 @@ class BarChart { // BoxPlot class. // This initializes and contains the JSON data model for this chart // +// todo: class BoxPlot { constructor() { - constants.plotId = 0; - constants.plotOrientation = 'horz'; // default - if (typeof maidr !== 'undefined') { - constants.plotOrientation = maidr.orientation; + this.sections = [ + 'lower_outlier', + 'min', + 'q1', + 'q2', + 'q3', + 'max', + 'upper_outlier', + ]; + + if ('axes' in singleMaidr) { + if ('x' in singleMaidr.axes) { + if ('level' in singleMaidr.axes.x) { + constants.plotOrientation = 'vert'; + } + } } - if ( - constants.chart.querySelector( - 'g[id^="panel"] > g[id^="geom_boxplot.gTree"]' - ) - ) { - constants.plotId = constants.chart - .querySelector('g[id^="panel"] > g[id^="geom_boxplot.gTree"]') - .getAttribute('id'); - } - - if (constants.manualData) { - // title - let boxplotTitle = ''; - if (typeof maidr !== 'undefined' && typeof maidr.title !== 'undefined') { - boxplotTitle = maidr.title; - } else if (constants.chart.querySelector('tspan[dy="9.45"]')) { - boxplotTitle = - constants.chart.querySelector('tspan[dy="9.45"]').innerHTML; - boxplotTitle = boxplotTitle.replace('\n', '').replace(/ +(?= )/g, ''); // there are multiple spaces and newlines, sometimes - } - this.title = - typeof boxplotTitle !== 'undefined' && typeof boxplotTitle != null - ? boxplotTitle - : ''; - - // axis labels - if (typeof maidr !== 'undefined') { - this.x_group_label = maidr.x_group_label; - } else { - this.x_group_label = constants.chart.querySelector( - 'text:not([transform^="rotate"]) > tspan[dy="7.88"]' - ).innerHTML; + // title + this.title = ''; + if ('labels' in singleMaidr) { + if ('title' in singleMaidr.labels) { + this.title = singleMaidr.labels.title; } - if (typeof maidr !== 'undefined') { - this.y_group_label = maidr.y_group_label; - } else { - this.y_group_label = constants.chart.querySelector( - 'text[transform^="rotate"] > tspan[dy="7.88"]' - ).innerHTML; + } + if (this.title == '') { + if ('title' in singleMaidr) { + this.title = singleMaidr.title; + } + } + // subtitle + this.subtitle = ''; + if ('labels' in singleMaidr) { + if ('subtitle' in singleMaidr.labels) { + this.subtitle = singleMaidr.labels.subtitle; + } + } + // caption + this.caption = ''; + if ('labels' in singleMaidr) { + if ('caption' in singleMaidr.labels) { + this.caption = singleMaidr.labels.caption; } + } - // x y tick labels - let labels = []; - if (typeof maidr !== 'undefined') { - this.x_labels = maidr.x_labels; - this.y_labels = maidr.y_labels; - } else { - let elDy = '3.15'; - if (constants.plotOrientation == 'vert') { - elDy = '6.3'; + // axes labels + if ('labels' in singleMaidr) { + if (!this.x_group_label) { + if ('x' in singleMaidr.labels) { + this.x_group_label = singleMaidr.labels.x; } - let els = constants.chart.querySelectorAll('tspan[dy="' + elDy + '"]'); - for (let i = 0; i < els.length; i++) { - labels.push(els[i].innerHTML.trim()); + } + if (!this.y_group_label) { + if ('y' in singleMaidr.labels) { + this.y_group_label = singleMaidr.labels.y; } - if (constants.plotOrientation == 'vert') { - this.x_labels = labels; - this.y_labels = []; + } + } + if ('axes' in singleMaidr) { + if ('x' in singleMaidr.axes) { + if ('label' in singleMaidr.axes.x) { + if (!this.x_group_label) { + this.x_group_label = singleMaidr.axes.x.label; + } + } + if ('level' in singleMaidr.axes.x) { + this.x_labels = singleMaidr.axes.x.level; } else { this.x_labels = []; - this.y_labels = labels; } } - - // main data - if (typeof maidr !== 'undefined') { - this.plotData = maidr.data; - } else { - this.plotData = maidr; - } - } else { - this.x_group_label = constants.chart.getElementById( - 'GRID.text.199.1.1.tspan.1' - ).innerHTML; - this.y_group_label = constants.chart.getElementById( - 'GRID.text.202.1.1.tspan.1' - ).innerHTML; - if (constants.plotOrientation == 'vert') { - this.x_labels = this.GetLabels(); - this.y_labels = []; - } else { - this.x_labels = []; - this.y_labels = this.GetLabels(); + if ('y' in singleMaidr.axes) { + if ('label' in singleMaidr.axes.y) { + if (!this.y_group_label) { + this.y_group_label = singleMaidr.axes.y.label; + } + } + if ('level' in singleMaidr.axes.y) { + this.y_labels = singleMaidr.axes.y.level; + } else { + this.y_labels = []; + } } - this.plotData = this.GetData(); // main json data } - if (constants.plotId) { - this.plotBounds = this.GetPlotBounds(constants.plotId); // bound data + // main data + this.plotData = singleMaidr.data; + + // bounds data + if ('elements' in singleMaidr) { + this.plotBounds = this.GetPlotBounds(); constants.hasRect = true; } else { constants.hasRect = false; @@ -2432,312 +3023,82 @@ class BoxPlot { this.CleanData(); } - GetLabels() { - let labels = []; - let query = 'tspan[dy="5"]'; - let els = constants.chart.querySelectorAll(query); - for (let i = 0; i < els.length; i++) { - labels.push(els[i].innerHTML.trim()); - } - return labels; - } - CleanData() { - // we manually input data, so now we need to clean it up and set other vars - - if (constants.plotOrientation == 'vert') { - constants.minY = 0; - constants.maxY = 0; - for (let i = 0; i < this.plotData.length; i++) { - // each plot - for (let j = 0; j < this.plotData[i].length; j++) { - // each section in plot - let point = this.plotData[i][j]; - if (point.hasOwnProperty('y')) { - if (point.y < constants.minY) { - constants.yMin = point.y; - } - if (point.hasOwnProperty('yMax')) { - if (point.yMax > constants.maxY) { - constants.maxY = point.yMax; - } - } else { - if (point.y > constants.maxY) { - constants.maxY = point.y; - } - } - } - if (point.hasOwnProperty('x')) { - if (point.x < constants.minX) { - constants.minX = point.x; - } - if (point.x > constants.maxX) { - constants.maxX = point.x; - } - } - } - } - } else { - constants.minX = 0; - constants.maxX = 0; - for (let i = 0; i < this.plotData.length; i++) { - // each plot - for (let j = 0; j < this.plotData[i].length; j++) { - // each section in plot - let point = this.plotData[i][j]; - if (point.hasOwnProperty('x')) { - if (point.x < constants.minX) { - constants.xMin = point.x; - } - if (point.hasOwnProperty('xMax')) { - if (point.xMax > constants.maxX) { - constants.maxX = point.xMax; - } - } else { - if (point.x > constants.maxX) { - constants.maxX = point.x; - } - } - } - if (point.hasOwnProperty('y')) { - if (point.y < constants.minY) { - constants.minY = point.y; - } - if (point.y > constants.maxY) { - constants.maxY = point.y; - } - } - } - } - } - } - - GetData() { - // data in chart is formed as nested elements. Loop through and get all point data - // goal is to get bounding x values and type (outlier, whisker, range, placeholder) - - let plotData = []; + // clean up data and extra vars like min / max stuff - let plots = document.querySelector(singleMaidr.element).children; - for (let i = 0; i < plots.length; i++) { - // each plot - - let sections = plots[i].children; - let points = []; - for (let j = 0; j < sections.length; j++) { - // each segment (outlier, whisker, etc) - // get segments for this section, there are 2 each - // sometimes they're 0, so ignore those TODO - let segments = sections[j].children; - for (let k = 0; k < segments.length; k++) { - let segment = segments[k]; - - let segmentType = this.GetBoxplotSegmentType( - sections[j].getAttribute('id') - ); - let segmentPoints = this.GetBoxplotSegmentPoints( - segment, - segmentType - ); + let min, max; + for (let i = 0; i < this.plotData.length; i++) { + if (this.plotData[i].lower_outlier) { + let outlierMin = Math.min(...this.plotData[i].lower_outlier); + let outlierMax = Math.max(...this.plotData[i].lower_outlier); - for (let l = 0; l < segmentPoints.length; l += 2) { - if ( - segmentType == 'whisker' && - l == 0 && - constants.plotOrientation == 'vert' - ) { - } else { - let thisPoint = { - x: Number(segmentPoints[l]), - y: Number(segmentPoints[l + 1]), - type: segmentType, - }; - if (thisPoint.y > constants.maxY) constants.maxY = thisPoint.y; - points.push(thisPoint); - } - } - } + if (min == undefined || outlierMin < min) min = outlierMin; + if (max == undefined || outlierMax > max) max = outlierMax; } - - // post processing - // Sort this plot - points.sort(function (a, b) { - if (constants.plotOrientation == 'vert') { - return a.y - b.y; - } else { - return a.x - b.x; - } - }); - - if (constants.plotOrientation == 'horz') { - // and remove whisker from range dups - let noDupPoints = []; - for (let d = 0; d < points.length; d++) { - if (d > 0) { - if (points[d - 1].x == points[d].x) { - if (points[d - 1].type == 'whisker') { - noDupPoints.splice(-1, 1); - noDupPoints.push(points[d]); - } else { - } - } else { - noDupPoints.push(points[d]); - } - } else { - noDupPoints.push(points[d]); - } - } - points = noDupPoints; + if (this.plotData[i].min) { + if (min == undefined || this.plotData[i].min < min) + min = this.plotData[i].min; + if (max == undefined || this.plotData[i].max > max) + max = this.plotData[i].max; } - - plotData.push(points); - } - - // put plots in order - plotData.sort(function (a, b) { - if (constants.plotOrientation == 'vert') { - return a[0].x - b[0].x; - } else { - return a[0].y - b[0].y; + if (this.plotData[i].q1) { + if (min == undefined || this.plotData[i].q1 < min) + min = this.plotData[i].q1; + if (max == undefined || this.plotData[i].q1 > max) + max = this.plotData[i].q1; } - }); - - // combine outliers into a single object for easier display - // info to grab: arr of values=y's or x's, y or x = ymin or xmin, yn xn = ymax xmax. The rest can stay as is - for (let i = 0; i < plotData.length; i++) { - let section = plotData[i]; - // loop through points and find outliers - let outlierGroup = []; - for (let j = 0; j < section.length + 1; j++) { - let runProcessOutliers = false; // run if we're past outliers (catching the first set), or if we're at the end (catching the last set) - if (j == section.length) { - runProcessOutliers = true; - } else if (section[j].type != 'outlier') { - runProcessOutliers = true; - } - if (!runProcessOutliers) { - // add this to the group and continue - outlierGroup.push(section[j]); - } else if (outlierGroup.length > 0) { - // process!! This is the main bit of work done - let vals = []; - for (let k = 0; k < outlierGroup.length; k++) { - // save array of values - if (constants.plotOrientation == 'vert') { - vals.push(outlierGroup[k].y); - } else { - vals.push(outlierGroup[k].x); - } - - // We're only keeping 1 outlier value, so mark all others to delete after we're done processing - if (k > 0) { - plotData[i][j + k - outlierGroup.length].type = 'delete'; - } - } - - // save data - if (constants.plotOrientation == 'vert') { - plotData[i][j - outlierGroup.length].y = outlierGroup[0].y; - plotData[i][j - outlierGroup.length].yMax = - outlierGroup[outlierGroup.length - 1].y; - } else { - plotData[i][j - outlierGroup.length].x = outlierGroup[0].x; - plotData[i][j - outlierGroup.length].xMax = - outlierGroup[outlierGroup.length - 1].x; - } - plotData[i][j - outlierGroup.length].values = vals; - - // reset for next set - outlierGroup = []; - } + if (this.plotData[i].q2) { + if (min == undefined || this.plotData[i].q2 < min) + min = this.plotData[i].q2; + if (max == undefined || this.plotData[i].q2 > max) + max = this.plotData[i].q2; } - } - // clean up from the above outlier processing - let cleanData = []; - for (let i = 0; i < plotData.length; i++) { - cleanData[i] = []; - for (let j = 0; j < plotData[i].length; j++) { - if (plotData[i][j].type != 'delete') { - cleanData[i][j] = plotData[i][j]; - } + if (this.plotData[i].q3) { + if (min == undefined || this.plotData[i].q3 < min) + min = this.plotData[i].q3; + if (max == undefined || this.plotData[i].q3 > max) + max = this.plotData[i].q3; } - cleanData[i] = cleanData[i].filter(function () { - return true; - }); - } - plotData = cleanData; - - // add labeling for display - for (let i = 0; i < plotData.length; i++) { - // each box section - let rangeCounter = 0; - for (let j = 0; j < plotData[i].length; j++) { - let point = plotData[i][j]; - // each point, decide based on position with respect to range - if (point.type == 'outlier') { - if (rangeCounter > 0) { - plotData[i][j].label = resources.GetString('upper_outlier'); - } else { - plotData[i][j].label = resources.GetString('lower_outlier'); - } - } else if (point.type == 'whisker') { - if (rangeCounter > 0) { - plotData[i][j].label = resources.GetString('max'); - } else { - plotData[i][j].label = resources.GetString('min'); - } - } else if (point.type == 'range') { - if (rangeCounter == 0) { - plotData[i][j].label = resources.GetString('25'); - } else if (rangeCounter == 1) { - plotData[i][j].label = resources.GetString('50'); - } else if (rangeCounter == 2) { - plotData[i][j].label = resources.GetString('75'); - } - rangeCounter++; - } + if (this.plotData[i].max) { + if (min == undefined || this.plotData[i].max < min) + min = this.plotData[i].max; + if (max == undefined || this.plotData[i].max > max) + max = this.plotData[i].max; } - } - - // often a plot doesn't have various sections. - // we expect outlier - min - 25 - 50 - 75 - max - outlier - // add blank placeholders where they don't exist for better vertical navigation - let allWeNeed = this.GetAllSegmentTypes(); - for (let i = 0; i < plotData.length; i++) { - if (plotData[i].length == 7) { - // skip, this one has it all. The rare boi - } else { - let whatWeGot = []; // we'll get a set of labels that we have so we can find what's missing - for (let j = 0; j < plotData[i].length; j++) { - whatWeGot.push(plotData[i][j].label); - } + if (this.plotData[i].upper_outlier) { + let outlierMin = Math.min(...this.plotData[i].upper_outlier); + let outlierMax = Math.max(...this.plotData[i].upper_outlier); - // add missing stuff where it should go. We use .label as the user facing var (todo, might be a mistake, maybe use .type?) - for (let j = 0; j < allWeNeed.length; j++) { - if (!whatWeGot.includes(allWeNeed[j])) { - // add a blank where it belongs - let blank = { type: 'blank', label: allWeNeed[j] }; - plotData[i].splice(j, 0, blank); - whatWeGot.splice(j, 0, allWeNeed[j]); - } - } + if (min == undefined || outlierMin < min) min = outlierMin; + if (max == undefined || outlierMax > max) max = outlierMax; } } - // update 50% value as a midpoint of 25 and 75 - for (let i = 0; i < plotData.length; i++) { - plotData[i][3].y = Math.round((plotData[i][2].y + plotData[i][4].y) / 2); + if (constants.plotOrientation == 'vert') { + constants.minY = min; + constants.maxY = max; + constants.minX = 0; + constants.maxX = this.plotData.length - 1; + } else { + constants.minX = min; + constants.maxX = max; + constants.minY = 0; + constants.maxY = this.plotData.length - 1; } - - if (constants.debugLevel > 1) { - console.log('plotData:', plotData); + constants.autoPlayRate = Math.min( + Math.ceil(constants.AUTOPLAY_DURATION / this.plotData.length), + constants.MAX_SPEED + ); + constants.DEFAULT_SPEED = constants.autoPlayRate; + if (constants.autoPlayRate < constants.MIN_SPEED) { + constants.MIN_SPEED = constants.autoPlayRate; } - - return plotData; } - GetPlotBounds(plotId) { - // we fetch the elements in our parent, and similar to GetData we run through and get bounding boxes (or blanks) for everything, and store in an identical structure + GetPlotBounds() { + // we fetch the elements in our parent, + // and similar to old GetData we run through and get bounding boxes (or blanks) for everything, + // and store in an identical structure let plotBounds = []; let allWeNeed = this.GetAllSegmentTypes(); @@ -2745,7 +3106,7 @@ class BoxPlot { // get initial set of elements, a parent element for all outliers, whiskers, and range let initialElemSet = []; - let plots = document.getElementById(constants.plotId).children; + let plots = singleMaidr.elements.children; for (let i = 0; i < plots.length; i++) { // each plot let plotSet = {}; @@ -3036,6 +3397,7 @@ class BoxPlot { return segmentType; } + GetBoxplotSegmentPoints(segment, segmentType) { // Helper function for main GetData: // Fetch x and y point data from chart @@ -3071,6 +3433,19 @@ class BoxPlot { return points; } + GetAllSegmentTypes() { + let allWeNeed = [ + resources.GetString('lower_outlier'), + resources.GetString('min'), + resources.GetString('25'), + resources.GetString('50'), + resources.GetString('75'), + resources.GetString('max'), + resources.GetString('upper_outlier'), + ]; + + return allWeNeed; + } convertBoundingClientRectToObj(rect) { return { @@ -3085,21 +3460,27 @@ class BoxPlot { }; } - PlayTones(audio) { + PlayTones() { + // init let plotPos = null; - let sectionPos = null; + let sectionKey = null; if (constants.outlierInterval) clearInterval(constants.outlierInterval); if (constants.plotOrientation == 'vert') { plotPos = position.x; - sectionPos = position.y; + sectionKey = this.GetSectionKey(position.y); } else { plotPos = position.y; - sectionPos = position.x; + sectionKey = this.GetSectionKey(position.x); } - if (plot.plotData[plotPos][sectionPos].type == 'blank') { + + // chose tone to play + if (plot.plotData[plotPos][sectionKey] == null) { audio.PlayNull(); - } else if (plot.plotData[plotPos][sectionPos].type != 'outlier') { + } else if (sectionKey != 'lower_outlier' && sectionKey != 'upper_outlier') { + // normal tone audio.playTone(); + } else if (plot.plotData[plotPos][sectionKey].length == 0) { + audio.PlayNull(); } else { // outlier(s): we play a run of tones position.z = 0; @@ -3111,19 +3492,20 @@ class BoxPlot { position.z += 1; // and kill if we're done - if (!Object.hasOwn(plot.plotData[plotPos][sectionPos], 'values')) { + if (plot.plotData[plotPos][sectionKey] == null) { clearInterval(constants.outlierInterval); position.z = -1; - } else if ( - position.z + 1 > - plot.plotData[plotPos][sectionPos].values.length - ) { + } else if (position.z + 1 > plot.plotData[plotPos][sectionKey].length) { clearInterval(constants.outlierInterval); position.z = -1; } }, constants.autoPlayOutlierRate); } } + + GetSectionKey(sectionPos) { + return this.sections[sectionPos]; + } } // BoxplotRect class @@ -3150,10 +3532,12 @@ class BoxplotRect { let plotPos = position.x; let sectionPos = position.y; + let sectionKey = plot.GetSectionKey(position.y); if (constants.plotOrientation == 'vert') { } else { plotPos = position.y; sectionPos = position.x; + sectionKey = plot.GetSectionKey(position.x); } if ( @@ -3175,7 +3559,7 @@ class BoxplotRect { if (constants.debugLevel > 5) { console.log( 'Point', - plot.plotData[plotPos][sectionPos].label, + sectionKey, 'bottom:', bounds.bottom, 'top:', @@ -3217,20 +3601,106 @@ class BoxplotRect { class HeatMap { constructor() { - if ('elements' in maidr) { - this.plots = maidr.elements; - constants.hasRect = 1; - } else { - this.plots = constants.chart.querySelectorAll( - 'g[id^="geom_rect"] > rect' - ); - constants.hasRect = 0; + // initialize variables xlevel, data, and elements + let xlevel = null; + let ylevel = null; + if ('axes' in singleMaidr) { + if (singleMaidr.axes.x) { + if (singleMaidr.axes.x.level) { + xlevel = singleMaidr.axes.x.level; + } + } + if (singleMaidr.axes.y) { + if (singleMaidr.axes.y.level) { + ylevel = singleMaidr.axes.y.level; + } + } } + let data = null; + let dataLength = 0; + if ('data' in singleMaidr) { + data = singleMaidr.data; + for (let i = 0; i < data.length; i++) { + dataLength += data[i].length; + } + } + let elements = null; + if ('elements' in singleMaidr) { + elements = singleMaidr.elements; + } + + // if (xlevel && ylevel && data && elements) { + // if (elements.length != dataLength) { + // // I didn't throw an error but give a warning + // constants.hasRect = 0; + // logError.LogDifferentLengths('data', 'elements'); + // } else if (ylevel.length != data.length) { + // constants.hasRect = 0; + // logError.logDifferentLengths('y level', 'rows'); + // } else if (data[0].length != xlevel.length) { + // constants.hasRect = 0; + // logError.logDifferentLengths('x level', 'columns'); + // } else { + // this.plots = elements; + // constants.hasRect = 1; + // } + // } else if (ylevel && data && elements) { + // if (dataLength != elements.length) { + // constants.hasRect = 0; + // logError.logDifferentLengths('data', 'elements'); + // } else if (ylevel.length != data.length) { + // constants.hasRect = 0; + // logError.logDifferentLengths('y level', 'rows'); + // } else { + // this.plots = elements; + // constants.hasRect = 1; + // } + // } else if (xlevel && data && elements) { + // if (dataLength != elements.length) { + // constants.hasRect = 0; + // logError.logDifferentLengths('data', 'elements'); + // } else if (xlevel.length != data[0].length) { + // constants.hasRect = 0; + // logError.logDifferentLengths('x level', 'columns'); + // } else { + // this.plots = elements; + // constants.hasRect = 1; + // } + // } + // else if (xlevel && ylevel && data) { + // constants.hasRect = 0; + // if (ylevel.length != data.length) { + // logError.logDifferentLengths('y level', 'rows'); + // } else if (data[0].length != xlevel.length) { + // logError.logDifferentLengths('x level', 'columns'); + // } + // logError.LogAbsentElement('elements'); + // } + // else if (data && elements) { + // if (dataLength != elements.length) { + // constants.hasRect = 0; + // logError.logDifferentLengths('data', 'elements'); + // } else { + // this.plots = elements; + // constants.hasRect = 1; + // } + // } else if (data) { + // constants.hasRect = 0; + // if (!xlevel) logError.LogAbsentElement('x level'); + // if (!ylevel) logError.LogAbsentElement('y level'); + // if (!elements) logError.LogAbsentElement('elements'); + // } + + this.plots = maidr.elements; + constants.hasRect = 1; this.group_labels = this.getGroupLabels(); - this.x_labels = this.getXLabels(); - this.y_labels = this.getYLabels(); + // this.x_labels = this.getXLabels(); + // this.y_labels = this.getYLabels(); + this.x_labels = xlevel; + this.y_labels = ylevel; this.title = this.getTitle(); + this.fill = this.getFill(); this.plotData = this.getHeatMapData(); this.updateConstants(); @@ -3243,7 +3713,6 @@ class HeatMap { this.x_group_label = this.group_labels[0].trim(); this.y_group_label = this.group_labels[1].trim(); - this.box_label = this.group_labels[2].trim(); } getHeatMapData() { @@ -3269,13 +3738,12 @@ class HeatMap { return a - b; }); - let svgScales = this.GetSVGScales(); - console.log(svgScales); + let svgScaler = this.GetSVGScaler(); // inverse scale if svg is scaled - if (svgScales[0] == -1) { + if (svgScaler[0] == -1) { x_coord_check = x_coord_check.reverse(); } - if (svgScales[1] == -1) { + if (svgScaler[1] == -1) { y_coord_check = y_coord_check.reverse(); } @@ -3288,9 +3756,9 @@ class HeatMap { let num_rows = 0; let num_cols = 0; let num_squares = 0; - if ('data' in maidr) { - num_rows = maidr.data.length; - num_cols = maidr.data[0].length; + if ('data' in singleMaidr) { + num_rows = singleMaidr.data.length; + num_cols = singleMaidr.data[0].length; } else { num_rows = unique_y_coord.length; num_cols = unique_x_coord.length; @@ -3298,8 +3766,8 @@ class HeatMap { num_squares = num_rows * num_cols; let norms = []; - if ('data' in maidr) { - norms = [...maidr.data]; + if ('data' in singleMaidr) { + norms = [...singleMaidr.data]; } else { norms = Array(num_rows) .fill() @@ -3336,9 +3804,21 @@ class HeatMap { constants.maxY = this.plotData[2][i][j]; } } + constants.autoPlayRate = Math.min( + Math.ceil(constants.AUTOPLAY_DURATION / (constants.maxX + 1)), + constants.MAX_SPEED + ); + constants.DEFAULT_SPEED = constants.autoPlayRate; + if (constants.autoPlayRate < constants.MIN_SPEED) { + constants.MIN_SPEED = constants.autoPlayRate; + } + } + + PlayTones() { + audio.playTone(); } - GetSVGScales() { + GetSVGScaler() { let scaleX = 1; let scaleY = 1; // start with some square (first), look all the way up the parents to the svg, and record any scales along the way @@ -3398,88 +3878,90 @@ class HeatMap { getGroupLabels() { let labels_nodelist; - let title = ''; let legendX = ''; let legendY = ''; - if ('title' in maidr) { - title = maidr.title; - } else { - title = constants.chart.querySelector( - 'g[id^="guide.title"] text > tspan' - ).innerHTML; + if ('labels' in singleMaidr) { + if ('x' in singleMaidr.labels) { + legendX = singleMaidr.labels.x; + } + if ('y' in singleMaidr.labels) { + legendY = singleMaidr.labels.y; + } } - - if ('axis' in maidr) { - if ('x' in maidr.axis) { - if ('label' in maidr.axis.x) { - legendX = maidr.axis.x.label; + if ('axes' in singleMaidr) { + if ('x' in singleMaidr.axes) { + if ('label' in singleMaidr.axes.x) { + if (legendX == '') { + legendX = singleMaidr.axes.x.label; + } } } - if ('y' in maidr.axis) { - if ('label' in maidr.axis.y) { - legendY = maidr.axis.y.label; + if ('y' in singleMaidr.axes) { + if ('label' in singleMaidr.axes.y) { + if (legendY == '') { + legendY = singleMaidr.axes.y.label; + } } } - } else { - legendX = constants.chart.querySelector( - 'g[id^="xlab"] text > tspan' - ).innerHTML; - legendY = constants.chart.querySelector( - 'g[id^="ylab"] text > tspan' - ).innerHTML; } - labels_nodelist = [legendX, legendY, title]; + labels_nodelist = [legendX, legendY]; return labels_nodelist; } getXLabels() { - if ('axis' in maidr) { - if ('x' in maidr.axis) { - if ('format' in maidr.axis.x) { - return maidr.axis.x.format; + if ('axes' in singleMaidr) { + if ('x' in singleMaidr.axes) { + if ('level' in singleMaidr.axes.x) { + return singleMaidr.axes.x.level; } } - } else { - let x_labels_nodelist; - x_labels_nodelist = constants.chart.querySelectorAll('tspan[dy="10"]'); - let labels = []; - for (let i = 0; i < x_labels_nodelist.length; i++) { - labels.push(x_labels_nodelist[i].innerHTML.trim()); - } - - return labels; } } getYLabels() { - if ('axis' in maidr) { - if ('y' in maidr.axis) { - if ('format' in maidr.axis.y) { - return maidr.axis.y.format; + if ('axes' in singleMaidr) { + if ('y' in singleMaidr.axes) { + if ('level' in singleMaidr.axes.y) { + return singleMaidr.axes.y.level; } } - } else { - let y_labels_nodelist; - let labels = []; - y_labels_nodelist = constants.chart.querySelectorAll( - 'tspan[id^="GRID.text.19.1"]' - ); - for (let i = 0; i < y_labels_nodelist.length; i++) { - labels.push(y_labels_nodelist[i].innerHTML.trim()); + } + } + + getTitle() { + if ('title' in singleMaidr) { + return singleMaidr.title; + } else if ('labels' in singleMaidr) { + if ('title' in singleMaidr.labels) { + return singleMaidr.labels.title; } + } + } - return labels.reverse(); + getSubtitle() { + if ('labels' in singleMaidr) { + if ('subtitle' in singleMaidr.labels) { + return singleMaidr.labels.subtitle; + } } } - getTitle() { - if ('title' in maidr) { - return maidr.title; - } else { - return ''; + getCaption() { + if ('labels' in singleMaidr) { + if ('caption' in singleMaidr.labels) { + return singleMaidr.labels.caption; + } + } + } + + getFill() { + if ('labels' in singleMaidr) { + if ('fill' in singleMaidr.labels) { + return singleMaidr.labels.fill; + } } } } @@ -3536,66 +4018,162 @@ document.addEventListener('DOMContentLoaded', function (e) { class ScatterPlot { constructor() { this.prefix = this.GetPrefix(); + // this.SetVisualHighlight(); this.SetScatterLayer(); this.SetLineLayer(); this.SetAxes(); - this.svgScales = this.GetSVGScales(); + this.svgScaler = this.GetSVGScaler(); } + // SetVisualHighlight() { + // let point_index = this.GetElementIndex('point'); + // let smooth_index = this.GetElementIndex('smooth'); + // if (point_index && smooth_index && elements < 2) { + // logError.LogAbsentElement('point or/and smooth line elements'); + // } + // if (point_index != -1) { + // this.CheckData(point_index); + // } + + // if (smooth_index != -1) { + // this.CheckData(smooth_index); + // } + // } + + // CheckData(i) { + // let elements = 'elements' in singleMaidr ? singleMaidr.elements : null; + + // // elements does not exist at all + // if (elements == null) { + // logError.LogAbsentElement('elements'); + // if (i == 0) constants.hasRect = 0; + // if (i == 1) constants.hasSmooth = 0; + // return; + // } + + // // elements exists but is empty + // if (elements.length == 0) { + // logError.LogAbsentElement('elements'); + // if (i == 0) constants.hasRect = 0; + // if (i == 1) constants.hasSmooth = 0; + // return; + // } + + // // elements exists but is not an array + // if (!Array.isArray(elements)) { + // logError.LogNotArray('elements'); + // if (i == 0) constants.hasRect = 0; + // if (i == 1) constants.hasSmooth = 0; + // return; + // } + + // // elements.length is more than 2 + // if (elements.length > 2) { + // logError.LogTooManyElements('elements', 2); + // } + + // if ('data' in singleMaidr) { + // if (i == 0) { + // // check point elements + // if ( + // singleMaidr.data[i] == null || + // singleMaidr.data[i].length != singleMaidr.elements[i].length + // ) { + // constants.hasRect = 0; + // logError.LogDifferentLengths('point data', 'point elements'); + // } + // } else if (i == 1) { + // // check smooth line elements + // if ( + // singleMaidr.data[i] == null || + // (!Array.isArray(singleMaidr.data[i]) && + // singleMaidr.data[i].length != this.chartLineX.length) + // ) { + // constants.hasSmooth = 0; + // logError.LogDifferentLengths( + // 'smooth line data', + // 'smooth line elements' + // ); + // } + // } + // } + // } + SetAxes() { this.x_group_label = ''; this.y_group_label = ''; this.title = ''; - if (typeof maidr !== 'undefined') { - if ('axis' in maidr) { - if ('x' in maidr.axis) { - this.x_group_label = maidr.axis.x.label; + if ('labels' in singleMaidr) { + if ('x' in singleMaidr.labels) { + this.x_group_label = singleMaidr.labels.x; + } + if ('y' in singleMaidr.labels) { + this.y_group_label = singleMaidr.labels.y; + } + if ('title' in singleMaidr.labels) { + this.title = singleMaidr.labels.title; + } + } + if ('axes' in singleMaidr) { + if ('x' in singleMaidr.axes) { + if (this.x_group_label == '') { + this.x_group_label = singleMaidr.axes.x.label; } - if ('y' in maidr.axis) { - this.y_group_label = maidr.axis.y.label; + } + if ('y' in singleMaidr.axes) { + if (this.y_group_label == '') { + this.y_group_label = singleMaidr.axes.y.label; } } - if ('title' in maidr) { - this.title = maidr.title; + } + if ('title' in singleMaidr) { + if (this.title == '') { + this.title = singleMaidr.title; } } } SetScatterLayer() { - // initially set as line layer (layer 2), if possible - let elIndex = this.GetElementIndex('scatter'); + // initially set as smooth layer (layer 2), if possible + let elIndex = this.GetElementIndex('point'); if (elIndex != -1) { - this.plotPoints = maidr.elements[elIndex]; - } else if (maidr.type == 'scatter') { - this.plotPoints = maidr.elements; + this.plotPoints = singleMaidr.elements[elIndex]; + } else if (singleMaidr.type == 'point') { + this.plotPoints = singleMaidr.elements; } if (typeof this.plotPoints !== 'undefined') { - this.chartPointsX = this.GetSvgPointCoords()[0]; // x coordinates of points - this.chartPointsY = this.GetSvgPointCoords()[1]; // y coordinates of points + let svgPointCoords = this.GetSvgPointCoords(); + let pointValues = this.GetPointValues(); - this.x = this.GetPointValues()[0]; // actual values of x - this.y = this.GetPointValues()[1]; // actual values of y + this.chartPointsX = svgPointCoords[0]; // x coordinates of points + this.chartPointsY = svgPointCoords[1]; // y coordinates of points + + this.x = pointValues[0]; // actual values of x + this.y = pointValues[1]; // actual values of y // for sound weight use - this.points_count = this.GetPointValues()[2]; // number of each points - this.max_count = this.GetPointValues()[3]; + this.points_count = pointValues[2]; // number of each points + this.max_count = pointValues[3]; } } SetLineLayer() { - // layer = 2, line layer (from maidr types) - let elIndex = this.GetElementIndex('line'); + // layer = 2, smooth layer (from singleMaidr types) + let elIndex = this.GetElementIndex('smooth'); if (elIndex != -1) { - this.plotLine = maidr.elements[elIndex]; - } else if (maidr.type == 'line') { - this.plotLine = maidr.elements; + this.plotLine = singleMaidr.elements[elIndex]; + } else if (singleMaidr.type == 'smooth') { + this.plotLine = singleMaidr.elements; } if (typeof this.plotLine !== 'undefined') { - this.chartLineX = this.GetSvgLineCoords()[0]; // x coordinates of curve - this.chartLineY = this.GetSvgLineCoords()[1]; // y coordinates of curve + let svgLineCoords = this.GetSvgLineCoords(); + let smoothCurvePoints = this.GetSmoothCurvePoints(); + + this.chartLineX = svgLineCoords[0]; // x coordinates of curve + this.chartLineY = svgLineCoords[1]; // y coordinates of curve - this.curveX = this.GetSmoothCurvePoints()[0]; // actual values of x - this.curvePoints = this.GetSmoothCurvePoints()[1]; // actual values of y + this.curveX = smoothCurvePoints[0]; // actual values of x + this.curvePoints = smoothCurvePoints[1]; // actual values of y this.curveMinY = Math.min(...this.curvePoints); this.curveMaxY = Math.max(...this.curvePoints); @@ -3638,15 +4216,26 @@ class ScatterPlot { return [X, Y]; } - GetElementIndex(elementName = 'scatter') { + GetElementIndex(elementName = 'point') { let elIndex = -1; - if ('type' in maidr) { - elIndex = maidr.type.indexOf(elementName); + if ('type' in singleMaidr) { + elIndex = singleMaidr.type.indexOf(elementName); } return elIndex; } - GetSVGScales() { + GetDataXYFormat(dataIndex) { + // detect if data is in form [{x: 1, y: 2}, {x: 2, y: 3}] (object) or {x: [1, 2], y: [2, 3]]} (array) + let xyFormat = 'array'; + if (singleMaidr.data[dataIndex]) { + if (Array.isArray(singleMaidr.data[dataIndex])) { + xyFormat = 'object'; + } + } + return xyFormat; + } + + GetSVGScaler() { let scaleX = 1; let scaleY = 1; // start with some square (first), look all the way up the parents to the svg, and record any scales along the way @@ -3654,6 +4243,7 @@ class ScatterPlot { // but first, are we even in an svg that can be scaled? let isSvg = false; let element = this.plotPoints[0]; // a random start, may as well be the first + console.log(element); while (element) { if (element.tagName.toLowerCase() == 'body') { break; @@ -3692,15 +4282,15 @@ class ScatterPlot { } GetPrefix() { - let elIndex = this.GetElementIndex('scatter'); + let elIndex = this.GetElementIndex('point'); let element; if (elIndex != -1) { - element = maidr.elements[elIndex][0]; - } else if (maidr.type == 'scatter') { - element = maidr.elements[0]; + element = singleMaidr.elements[elIndex][0]; + } else if (singleMaidr.type == 'point') { + element = singleMaidr.elements[0]; } let prefix = ''; - if (element.tagName.toLowerCase() == 'circle') { + if ('element' in singleMaidr && element.tagName.toLowerCase() == 'circle') { prefix = 'c'; } return prefix; @@ -3712,20 +4302,40 @@ class ScatterPlot { let xValues = []; let yValues = []; - let elIndex = this.GetElementIndex('scatter'); + // prepare to fetch data from the correct index in the correct format + let elIndex = this.GetElementIndex('point'); + let xyFormat = this.GetDataXYFormat(elIndex); let data; if (elIndex > -1) { - data = maidr.data[elIndex]; - } else if (maidr.type == 'scatter') { - data = maidr.data; + // data comes directly as an array, in a 'point' layer, so fetch directly as an array from that index + data = singleMaidr.data[elIndex]; + } else if (singleMaidr.type == 'point') { + // data comes directly as an array, no 'point' layer, so fetch directly as an array + data = singleMaidr.data; } if (typeof data !== 'undefined') { - for (let i = 0; i < maidr.data[elIndex].length; i++) { - let x = maidr.data[elIndex][i]['x']; - let y = maidr.data[elIndex][i]['y']; - xValues.push(x); - yValues.push(y); + // assuming we got something, loop through the data and extract the x and y values + + if (xyFormat == 'array') { + if ('x' in singleMaidr.data[elIndex]) { + xValues = singleMaidr.data[elIndex]['x']; + } + if ('y' in singleMaidr.data[elIndex]) { + yValues = singleMaidr.data[elIndex]['y']; + } + } else if (xyFormat == 'object') { + for (let i = 0; i < singleMaidr.data[elIndex].length; i++) { + let x = singleMaidr.data[elIndex][i]['x']; + let y = singleMaidr.data[elIndex][i]['y']; + xValues.push(x); + yValues.push(y); + } + } + + for (let i = 0; i < xValues.length; i++) { + let x = xValues[i]; + let y = yValues[i]; if (!points.has(x)) { points.set(x, new Map([[y, 1]])); } else { @@ -3744,6 +4354,15 @@ class ScatterPlot { constants.minY = Math.min(...yValues); constants.maxY = Math.max(...yValues); + constants.autoPlayRate = Math.min( + Math.ceil(constants.AUTOPLAY_DURATION / (constants.maxX + 1)), + constants.MAX_SPEED + ); + constants.DEFAULT_SPEED = constants.autoPlayRate; + if (constants.autoPlayRate < constants.MIN_SPEED) { + constants.MIN_SPEED = constants.autoPlayRate; + } + points = new Map( [...points].sort(function (a, b) { return a[0] - b[0]; @@ -3778,12 +4397,12 @@ class ScatterPlot { } } - PlayTones(audio) { + PlayTones() { // kill the previous separate-points play before starting the next play if (constants.sepPlayId) { constants.KillSepPlay(); } - if (constants.chartType == 'scatter') { + if (constants.chartType == 'point') { // point layer // we play a run of tones position.z = 0; @@ -3801,10 +4420,10 @@ class ScatterPlot { position.z = -1; } }, - constants.sonifMode == 'sep' ? constants.autoPlayPointsRate : 0 + constants.sonifMode == 'on' ? constants.autoPlayPointsRate : 0 ); // play all tones at the same time - } else if (constants.chartType == 'line') { - // best fit line layer + } else if (constants.chartType == 'smooth') { + // best fit smooth layer audio.playTone(); } } @@ -3830,18 +4449,30 @@ class ScatterPlot { let x_points = []; let y_points = []; - let elIndex = this.GetElementIndex('line'); + let elIndex = this.GetElementIndex('smooth'); + let xyFormat = this.GetDataXYFormat(elIndex); let data; if (elIndex > -1) { - data = maidr.data[elIndex]; - } else if (maidr.type == 'line') { - data = maidr.data; + // data comes directly as an array, in a 'smooth' layer, so fetch directly as an array from that index + data = singleMaidr.data[elIndex]; + } else if (singleMaidr.type == 'smooth') { + // data comes directly as an array, no 'smooth' layer, so fetch directly as an array + data = singleMaidr.data; } if (typeof data !== 'undefined') { - for (let i = 0; i < maidr.data[elIndex].length; i++) { - x_points.push(maidr.data[elIndex][i]['x']); - y_points.push(maidr.data[elIndex][i]['y']); + if (xyFormat == 'object') { + for (let i = 0; i < singleMaidr.data[elIndex].length; i++) { + x_points.push(singleMaidr.data[elIndex][i]['x']); + y_points.push(singleMaidr.data[elIndex][i]['y']); + } + } else if (xyFormat == 'array') { + if ('x' in singleMaidr.data[elIndex]) { + x_points = singleMaidr.data[elIndex]['x']; + } + if ('y' in singleMaidr.data[elIndex]) { + y_points = singleMaidr.data[elIndex]['y']; + } } return [x_points, y_points]; @@ -3904,7 +4535,7 @@ class Layer0Point { var point = document.createElementNS(svgns, 'circle'); point.setAttribute('class', 'highlight_point'); point.setAttribute('cx', this.x); - if (plot.svgScales[1] == -1) { + if (plot.svgScaler[1] == -1) { point.setAttribute( 'cy', constants.chart.getBoundingClientRect().height - this.y[i] @@ -3919,7 +4550,7 @@ class Layer0Point { point.setAttribute('stroke', constants.colorSelected); point.setAttribute('stroke-width', this.strokeWidth); point.setAttribute('fill', constants.colorSelected); - if (plot.svgScales[1] == -1) { + if (plot.svgScaler[1] == -1) { constants.chart.appendChild(point); } else { plot.plotPoints[this.circleIndex[i]].parentNode.appendChild(point); @@ -3944,7 +4575,7 @@ class Layer0Point { } class Layer1Point { - // line segments + // smooth segments constructor() { this.x = plot.chartLineX[0]; @@ -3964,20 +4595,24 @@ class Layer1Point { const svgns = 'http://www.w3.org/2000/svg'; var point = document.createElementNS(svgns, 'circle'); point.setAttribute('id', 'highlight_point'); - point.setAttribute(plot.prefix + 'x', this.x); - if (plot.svgScales[1] == -1) { + point.setAttribute('cx', this.x); + if (plot.svgScaler[1] == -1) { point.setAttribute( - plot.prefix + 'y', + 'cy', constants.chart.getBoundingClientRect().height - this.y ); } else { - point.setAttribute(plot.prefix + 'y', this.y); + point.setAttribute('cy', this.y); } point.setAttribute('r', 3.95); point.setAttribute('stroke', constants.colorSelected); point.setAttribute('stroke-width', this.strokeWidth); point.setAttribute('fill', constants.colorSelected); - constants.chart.appendChild(point); + if (plot.svgScaler[1] == -1) { + constants.chart.appendChild(point); + } else { + plot.plotLine.parentNode.appendChild(point); + } } async ClearPoints() { @@ -3996,42 +4631,400 @@ class Layer1Point { } } -class Control { +class Histogram { constructor() { - this.SetControls(); - } + // initialize main data: data, elements - SetControls() { - // variable initialization - if ([].concat(singleMaidr.type).includes('bar')) { - window.position = new Position(-1, -1); - window.plot = new BarChart(); + // data (required) + if ('data' in singleMaidr) { + this.plotData = singleMaidr.data; + } else { + console.log('Error: no data found'); + return; + } + // elements (optional) + this.bars = null; + if ('elements' in singleMaidr) { + this.bars = singleMaidr.elements; + } - let audio = new Audio(); + // labels (optional) + this.legendX = null; + this.legendY = null; + if ('labels' in singleMaidr) { + if ('x' in singleMaidr.labels) { + this.legendX = singleMaidr.labels.x; + } + if ('y' in singleMaidr.labels) { + this.legendY = singleMaidr.labels.y; + } + } + if ('axes' in singleMaidr) { + if ('x' in singleMaidr.axes) { + if ('label' in singleMaidr.axes.x) { + if (!this.legendX) { + this.legendX = singleMaidr.axes.x.label; + } + } + } + if ('y' in singleMaidr.axes) { + if ('label' in singleMaidr.axes.y) { + if (!this.legendY) { + this.legendY = singleMaidr.axes.y.label; + } + } + } + } - // global variables - let lastPlayed = ''; - let lastx = 0; - let lastKeyTime = 0; - let pressedL = false; + // tick labels: todo, not sure if they'll exist or not - // control eventlisteners - constants.events.push([ - constants.chart, - 'keydown', - function (e) { - let updateInfoThisRound = false; // we only update info and play tones on certain keys - let isAtEnd = false; + // title (optional) + this.title = ''; + if ('labels' in singleMaidr) { + if ('title' in singleMaidr.labels) { + this.title = singleMaidr.labels.title; + } + } + if (this.title == '') { + if ('title' in singleMaidr) { + this.title = singleMaidr.title; + } + } - if (e.which === 39) { - // right arrow 39 - if (constants.isMac ? e.metaKey : e.ctrlKey) { - if (e.shiftKey) { - // lastx = position.x; - position.x -= 1; - Autoplay('right', position.x, plot.bars.length); - } else { - position.x = plot.bars.length - 1; // go all the way + // title (optional) + if ('labels' in singleMaidr) { + if ('subtitle' in singleMaidr.labels) { + this.subtitle = singleMaidr.labels.subtitle; + } + } + // title (optional) + if ('labels' in singleMaidr) { + if ('caption' in singleMaidr.labels) { + this.caption = singleMaidr.labels.caption; + } + } + + this.SetMaxMin(); + + this.autoplay = null; + } + + PlayTones() { + audio.playTone(); + } + + SetMaxMin() { + for (let i = 0; i < this.plotData.length; i++) { + if (i == 0) { + constants.maxY = this.plotData[i].y; + constants.minY = this.plotData[i].y; + constants.maxX = this.plotData[i].xmax; + constants.minX = this.plotData[i].xmin; + } else { + if (this.plotData[i].y > constants.maxY) { + constants.maxY = this.plotData[i].y; + } + if (this.plotData[i].y < constants.minY) { + constants.minY = this.plotData[i].y; + } + if (this.plotData[i].xmax > constants.maxX) { + constants.maxX = this.plotData[i].xmax; + } + if (this.plotData[i].xmin < constants.minX) { + constants.minX = this.plotData[i].xmin; + } + } + } + constants.autoPlayRate = Math.min( + Math.ceil(constants.AUTOPLAY_DURATION / (constants.maxX + 1)), + constants.MAX_SPEED + ); + constants.DEFAULT_SPEED = constants.autoPlayRate; + if (constants.autoPlayRate < constants.MIN_SPEED) { + constants.MIN_SPEED = constants.autoPlayRate; + } + } + + Select() { + this.UnSelectPrevious(); + if (this.bars) { + this.activeElement = this.bars[position.x]; + if (this.activeElement) { + this.activeElementColor = this.activeElement.style.fill; + this.activeElement.style.fill = constants.colorSelected; + } + } + } + + UnSelectPrevious() { + if (this.activeElement) { + this.activeElement.style.fill = this.activeElementColor; + this.activeElement = null; + } + } +} + +class Control { + constructor() { + this.SetControls(); + } + + SetControls() { + // global controls + + // variable initialization + let controlElements = [ + constants.chart, + constants.brailleInput, + constants.review_container, + ]; + let pressedL = false; + let pressedTimeout = null; + + // main BTS controls + for (let i = 0; i < controlElements.length; i++) { + constants.events.push([ + controlElements[i], + 'keydown', + function (e) { + // init + let lastPlayed = ''; + + // if we're awaiting an L + X prefix, we don't want to do anything else + if (pressedL) { + return; + } + + // B: braille mode + if (e.key == 'b') { + constants.tabMovement = 0; + e.preventDefault(); + display.toggleBrailleMode(); + } + + // T: aria live text output mode + if (e.key == 't') { + display.toggleTextMode(); + } + + // S: sonification mode + if (e.key == 's') { + display.toggleSonificationMode(); + } + + // R: review mode + if (e.key == 'r' && !e.ctrlKey && !e.shiftKey) { + // r, but let Ctrl and Shift R go through cause I use that to refresh + constants.tabMovement = 0; + e.preventDefault(); + if (constants.review_container.classList.contains('hidden')) { + review.ToggleReviewMode(true); + } else { + review.ToggleReviewMode(false); + } + } + + if (e.key == ' ') { + // space 32, replay info but no other changes + if (constants.showDisplay) { + display.displayValues(); + } + if (constants.sonifMode != 'off') { + plot.PlayTones(); + } + } + + // switch layer controls + if (Array.isArray(singleMaidr.type)) { + // page down /(fn+down arrow): change chart type (layer) + if (e.key == 'PageDown' && constants.brailleMode == 'off') { + display.changeChartLayer('down'); + } + + // page up / (fn+up arrow): change chart type (layer) + if (e.key == 'PageUp' && constants.brailleMode == 'off') { + display.changeChartLayer('up'); + } + } + }, + ]); + } + + // We want to tab or shift tab past the chart, + // but we delay adding this eventlistener for a moment so the chart loads first + for (let i = 0; i < controlElements.length; i++) { + constants.events.push([ + controlElements[i], + 'keydown', + function (e) { + if (e.key == 'Tab') { + // save key to be used on blur event later + if (e.shiftKey) { + constants.tabDirection = -1; + } else { + constants.tabDirection = 1; + } + } + }, + ]); + } + + // prefix events + constants.events.push([ + document, + 'keydown', + function (e) { + // init + let lastPlayed = ''; + + // enable / disable prefix mode + if (e.key == 'l') { + pressedL = true; + if (pressedTimeout != null) { + clearTimeout(pressedTimeout); + pressedTimeout = null; + } + pressedTimeout = setTimeout(function () { + pressedL = false; + }, constants.keypressInterval); + } + + // ctrl/cmd: stop autoplay + if (constants.isMac ? e.metaKey : e.ctrlKey) { + // (ctrl/cmd)+(home/fn+left arrow): first element + if (e.key == 'Home') { + // chart types + if (constants.chartType == 'bar' || constants.chartType == 'hist') { + position.x = 0; + } else if (constants.chartType == 'box') { + position.x = 0; + position.y = plot.sections.length - 1; + } else if (constants.chartType == 'heat') { + position.x = 0; + position.y = 0; + } else if (constants.chartType == 'point') { + position.x = 0; + } else if (constants.chartType == 'smooth') { + positionL1.x = 0; + } + + UpdateAllBraille(); + } + + // (ctrl/cmd)+(end/fn+right arrow): last element + else if (e.key == 'End') { + // chart types + if (constants.chartType == 'bar' || constants.chartType == 'hist') { + position.x = plot.bars.length - 1; + } else if (constants.chartType == 'box') { + position.x = plot.sections.length - 1; + position.y = 0; + } else if (constants.chartType == 'heat') { + position.x = plot.num_cols - 1; + position.y = plot.num_rows - 1; + } else if (constants.chartType == 'point') { + position.x = plot.y.length - 1; + } else if (constants.chartType == 'smooth') { + positionL1.x = plot.curvePoints.length - 1; + } + + UpdateAllBraille(); + } + } + + // Prefix mode stuff: L is enabled, look for these keys + if (pressedL) { + if (e.key == 'x') { + // X: x label + let xlabel = ''; + if (constants.chartType == 'bar') { + xlabel = plot.plotLegend.x; + } else if ( + constants.chartType == 'heat' || + constants.chartType == 'box' || + singleMaidr.type == 'point' || + singleMaidr.type == 'line' || + singleMaidr.type.includes('point') + ) { + xlabel = plot.x_group_label; + } + display.displayInfo('x label', xlabel); + pressedL = false; + } else if (e.key == 'y') { + // Y: y label + let ylabel = ''; + if (constants.chartType == 'bar') { + ylabel = plot.plotLegend.y; + } else if ( + constants.chartType == 'heat' || + constants.chartType == 'box' || + singleMaidr.type == 'point' || + singleMaidr.type == 'line' || + singleMaidr.type.includes('point') + ) { + ylabel = plot.y_group_label; + } + display.displayInfo('y label', ylabel); + pressedL = false; + } else if (e.key == 't') { + // T: title + display.displayInfo('title', plot.title); + pressedL = false; + } else if (e.key == 's') { + // subtitle + display.displayInfo('subtitle', plot.subtitle); + pressedL = false; + } else if (e.key == 'c') { + // caption + display.displayInfo('caption', plot.caption); + pressedL = false; + } else if (e.key != 'l') { + pressedL = false; + } + } + + // // period: speed up + // if (e.key == '.') { + // constants.SpeedUp(); + // display.announceText('Speed up'); + // } + + // // comma: speed down + // if (e.key == ',') { + // constants.SpeedDown(); + // display.announceText('Speed down'); + // } + // // /: reset speed + // if (e.key == '/') { + // constants.SpeedReset(); + // display.announceText('Speed reset'); + // } + }, + ]); + + if ([].concat(singleMaidr.type).includes('bar')) { + window.position = new Position(-1, -1); + window.plot = new BarChart(); + + // global variables + constants.lastx = 0; + let lastPlayed = ''; + + // control eventlisteners + constants.events.push([ + constants.chart, + 'keydown', + function (e) { + let updateInfoThisRound = false; // we only update info and play tones on certain keys + let isAtEnd = false; + + if (e.key == 'ArrowRight') { + if (constants.isMac ? e.metaKey : e.ctrlKey) { + if (e.shiftKey) { + position.x -= 1; + Autoplay('right', position.x, plot.plotData.length); + } else { + position.x = plot.plotData.length - 1; // go all the way updateInfoThisRound = true; isAtEnd = lockPosition(); } @@ -4040,19 +5033,19 @@ class Control { e.shiftKey && position.x != plot.bars.length - 1 ) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-right', plot.bars.length, position.x); } else { position.x += 1; updateInfoThisRound = true; isAtEnd = lockPosition(); } - } - if (e.which === 37) { + } else if (e.key == 'ArrowLeft') { + // var prevLink = document.getElementById('prev'); // what is prev in the html? + // if (prevLink) { // left arrow 37 if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { - // lastx = position.x; position.x += 1; Autoplay('left', position.x, -1); } else { @@ -4061,13 +5054,14 @@ class Control { isAtEnd = lockPosition(); } } else if (e.altKey && e.shiftKey && position.x != 0) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-left', -1, position.x); } else { position.x += -1; updateInfoThisRound = true; isAtEnd = lockPosition(); } + // } } // update display / text / audio @@ -4084,23 +5078,17 @@ class Control { constants.brailleInput, 'keydown', function (e) { - // We block all input, except if it's B or Tab so we move focus - let updateInfoThisRound = false; // we only update info and play tones on certain keys let isAtEnd = false; - if (e.which == 9) { - // tab - // do nothing, let the user Tab away - } else if (e.which == 39) { + if (e.key == 'ArrowRight') { // right arrow e.preventDefault(); if (e.target.selectionStart > e.target.value.length - 2) { } else if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { - // lastx = position.x; position.x -= 1; - Autoplay('right', position.x, plot.bars.length); + Autoplay('right', position.x, plot.plotData.length); } else { position.x = plot.bars.length - 1; // go all the way updateInfoThisRound = true; @@ -4111,19 +5099,18 @@ class Control { e.shiftKey && position.x != plot.bars.length - 1 ) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-right', plot.bars.length, position.x); } else { position.x += 1; updateInfoThisRound = true; isAtEnd = lockPosition(); } - } else if (e.which == 37) { + } else if (e.key == 'ArrowLeft') { // left arrow e.preventDefault(); if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { - // lastx = position.x; position.x += 1; Autoplay('left', position.x, -1); } else { @@ -4132,22 +5119,19 @@ class Control { isAtEnd = lockPosition(); } } else if (e.altKey && e.shiftKey && position.x != 0) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-left', -1, position.x); } else { position.x += -1; updateInfoThisRound = true; isAtEnd = lockPosition(); } + } else if (e.key == 'Tab') { + // do nothing, we handle this in global events } else { e.preventDefault(); } - // auto turn off braille mode if we leave the braille box - constants.brailleInput.addEventListener('focusout', function (e) { - display.toggleBrailleMode('off'); - }); - // update display / text / audio if (updateInfoThisRound && !isAtEnd) { UpdateAllBraille(); @@ -4158,188 +5142,103 @@ class Control { }, ]); - // main BTS controls let controlElements = [constants.chart, constants.brailleInput]; + let lastx = 0; for (let i = 0; i < controlElements.length; i++) { constants.events.push([ controlElements[i], - 'keyup', + 'keydown', function (e) { - // B: braille mode - if (e.which == 66) { - display.toggleBrailleMode(); - e.preventDefault(); - } - // keys = (keys || []); - // keys[e.keyCode] = true; - // if (keys[84] && !keys[76]) { - // display.toggleTextMode(); - // } - - // T: aria live text output mode - if (e.which == 84) { - let timediff = window.performance.now() - lastKeyTime; - if (!pressedL || timediff > constants.keypressInterval) { - display.toggleTextMode(); - } + // period: speed up + if (e.key == '.') { + constants.SpeedUp(); + PlayDuringSpeedChange(); + display.announceText('Speed up'); } - // S: sonification mode - if (e.which == 83) { - display.toggleSonificationMode(); + // comma: speed down + if (e.key == ',') { + constants.SpeedDown(); + PlayDuringSpeedChange(); + display.announceText('Speed down'); } - if (e.which === 32) { - // space 32, replay info but no other changes - UpdateAll(); + // /: reset speed + if (e.key == '/') { + constants.SpeedReset(); + PlayDuringSpeedChange(); + display.announceText('Speed reset'); } }, ]); } - - constants.events.push([ - document, - 'keydown', - function (e) { - // ctrl/cmd: stop autoplay - if (constants.isMac ? e.metaKey : e.ctrlKey) { - // (ctrl/cmd)+(home/fn+left arrow): first element - if (e.which == 36) { - position.x = 0; - UpdateAllBraille(); - } - - // (ctrl/cmd)+(end/fn+right arrow): last element - else if (e.which == 35) { - position.x = plot.bars.length - 1; - UpdateAllBraille(); - } - } - - // must come before prefix L - if (pressedL) { - if (e.which == 88) { - // X: x label - let timediff = window.performance.now() - lastKeyTime; - if (pressedL && timediff <= constants.keypressInterval) { - display.displayXLabel(plot); - } - pressedL = false; - } else if (e.which == 89) { - // Y: y label - let timediff = window.performance.now() - lastKeyTime; - if (pressedL && timediff <= constants.keypressInterval) { - display.displayYLabel(plot); - } - pressedL = false; - } else if (e.which == 84) { - // T: title - let timediff = window.performance.now() - lastKeyTime; - if (pressedL && timediff <= constants.keypressInterval) { - display.displayTitle(plot); - } - pressedL = false; - } else if (e.which == 76) { - lastKeyTime = window.performance.now(); - pressedL = true; - } else { - pressedL = false; - } - } - - // L: prefix for label; must come after the suffix - if (e.which == 76) { - lastKeyTime = window.performance.now(); - pressedL = true; - } - - // period: speed up - if (e.which == 190) { - constants.SpeedUp(); - if (constants.autoplayId != null) { - constants.KillAutoplay(); - if (lastPlayed == 'reverse-left') { - Autoplay('right', position.x, lastx); - } else if (lastPlayed == 'reverse-right') { - Autoplay('left', position.x, lastx); - } else { - Autoplay(lastPlayed, position.x, lastx); - } - } - } - - // comma: speed down - if (e.which == 188) { - constants.SpeedDown(); - if (constants.autoplayId != null) { - constants.KillAutoplay(); - if (lastPlayed == 'reverse-left') { - Autoplay('right', position.x, lastx); - } else if (lastPlayed == 'reverse-right') { - Autoplay('left', position.x, lastx); - } else { - Autoplay(lastPlayed, position.x, lastx); - } - } + function PlayDuringSpeedChange() { + if (constants.autoplayId != null) { + constants.KillAutoplay(); + if (lastPlayed == 'reverse-left') { + Autoplay('right', position.x, lastx); + } else if (lastPlayed == 'reverse-right') { + Autoplay('left', position.x, lastx); + } else { + Autoplay(lastPlayed, position.x, lastx); } - }, - ]); - + } + } function lockPosition() { // lock to min / max postions - let isLockNeeded = false; - if (!constants.hasRect) { - return isLockNeeded; - } + let didLockHappen = false; + // if (!constants.hasRect) { + // return didLockHappen; + // } if (position.x < 0) { position.x = 0; - isLockNeeded = true; + didLockHappen = true; } - if (position.x > plot.bars.length - 1) { - position.x = plot.bars.length - 1; - isLockNeeded = true; + if (position.x > plot.plotData.length - 1) { + position.x = plot.plotData.length - 1; + didLockHappen = true; } - return isLockNeeded; + return didLockHappen; } function UpdateAll() { if (constants.showDisplay) { - display.displayValues(plot); + display.displayValues(); } if (constants.showRect && constants.hasRect) { plot.Select(); } if (constants.sonifMode != 'off') { - audio.playTone(); + plot.PlayTones(); } } function UpdateAllAutoplay() { if (constants.showDisplayInAutoplay) { - display.displayValues(plot); + display.displayValues(); } if (constants.showRect && constants.hasRect) { plot.Select(); } if (constants.sonifMode != 'off') { - audio.playTone(); + plot.PlayTones(); } if (constants.brailleMode != 'off') { - display.UpdateBraillePos(plot); + display.UpdateBraillePos(); } } function UpdateAllBraille() { if (constants.showDisplayInBraille) { - display.displayValues(plot); + display.displayValues(); } if (constants.showRect && constants.hasRect) { plot.Select(); } if (constants.sonifMode != 'off') { - audio.playTone(); + plot.PlayTones(); } - display.UpdateBraillePos(plot); + display.UpdateBraillePos(); } function Autoplay(dir, start, end) { lastPlayed = dir; @@ -4359,7 +5258,7 @@ class Control { constants.autoplayId = setInterval(function () { position.x += step; - if (position.x < 0 || plot.bars.length - 1 < position.x) { + if (position.x < 0 || plot.plotData.length - 1 < position.x) { constants.KillAutoplay(); lockPosition(); } else if (position.x == end) { @@ -4375,17 +5274,17 @@ class Control { constants.plotId = 'geom_boxplot.gTree.78.1'; window.plot = new BoxPlot(); if (constants.plotOrientation == 'vert') { - window.position = new Position(0, plot.plotData[0].length - 1); + window.position = new Position(0, 6); // always 6 } else { window.position = new Position(-1, plot.plotData.length); } - let rect = new BoxplotRect(); - let audio = new Audio(); + let rect; + constants.hasRect = false; + if ('elements' in singleMaidr) { + rect = new BoxplotRect(); + constants.hasRect = true; + } let lastPlayed = ''; - let lastY = 0; - let lastx = 0; - let lastKeyTime = 0; - let pressedL = false; // control eventlisteners constants.events.push([ @@ -4396,24 +5295,20 @@ class Control { let isAtEnd = false; // right arrow - if (e.which === 39) { + if (e.key == 'ArrowRight') { if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { if (constants.plotOrientation == 'vert') { Autoplay('right', position.x, plot.plotData.length - 1); } else { - Autoplay( - 'right', - position.x, - plot.plotData[position.y].length - ); + Autoplay('right', position.x, plot.sections.length - 1); } } else { isAtEnd = lockPosition(); if (constants.plotOrientation == 'vert') { position.x = plot.plotData.length - 1; } else { - position.x = plot.plotData[position.y].length - 1; + position.x = plot.sections.length - 1; } updateInfoThisRound = true; isAtEnd = lockPosition(); @@ -4422,15 +5317,12 @@ class Control { if ( e.altKey && e.shiftKey && - plot.plotData.length - 1 != position.x + plot.sections.length - 1 != position.x ) { lastY = position.y; Autoplay('reverse-right', plot.plotData.length - 1, position.x); } else { - if ( - position.x == -1 && - position.y == plot.plotData[position.x].length - ) { + if (position.x == -1 && position.y == plot.sections.length) { position.y -= 1; } position.x += 1; @@ -4441,14 +5333,10 @@ class Control { if ( e.altKey && e.shiftKey && - plot.plotData[position.y].length - 1 != position.x + plot.sections.length - 1 != position.x ) { - lastx = position.x; - Autoplay( - 'reverse-right', - plot.plotData[position.y].length - 1, - position.x - ); + constants.lastx = position.x; + Autoplay('reverse-right', plot.sections.length - 1, position.x); } else { if (position.x == -1 && position.y == plot.plotData.length) { position.y -= 1; @@ -4461,7 +5349,7 @@ class Control { constants.navigation = 1; } // left arrow - if (e.which === 37) { + if (e.key == 'ArrowLeft') { if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { Autoplay('left', position.x, -1); @@ -4474,7 +5362,7 @@ class Control { if (constants.plotOrientation == 'vert') { lastY = position.y; } else { - lastx = position.x; + constants.lastx = position.x; } Autoplay('reverse-left', 0, position.x); } else { @@ -4485,18 +5373,18 @@ class Control { constants.navigation = 1; } // up arrow - if (e.which === 38) { + if (e.key == 'ArrowUp') { let oldY = position.y; if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { if (constants.plotOrientation == 'vert') { - Autoplay('up', position.y, plot.plotData[position.x].length); + Autoplay('up', position.y, plot.sections.length); } else { Autoplay('up', position.y, plot.plotData.length); } } else { if (constants.plotOrientation == 'vert') { - position.y = plot.plotData[position.x].length - 1; + position.y = plot.sections.length - 1; } else { position.y = plot.plotData.length - 1; } @@ -4507,14 +5395,10 @@ class Control { if ( e.altKey && e.shiftKey && - position.y != plot.plotData[position.x].length - 1 + position.y != plot.sections.length - 1 ) { lastY = position.y; - Autoplay( - 'reverse-up', - plot.plotData[position.x].length - 1, - position.y - ); + Autoplay('reverse-up', plot.sections.length - 1, position.y); } else { position.y += 1; updateInfoThisRound = true; @@ -4524,9 +5408,9 @@ class Control { if ( e.altKey && e.shiftKey && - position.y != plot.plotData.length - 1 + position.y != plot.sections.length - 1 ) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-up', plot.plotData.length - 1, position.y); } else { position.y += 1; @@ -4537,7 +5421,7 @@ class Control { constants.navigation = 0; } // down arrow - if (e.which === 40) { + if (e.key == 'ArrowDown') { let oldY = position.y; if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { @@ -4551,15 +5435,12 @@ class Control { if (constants.plotOrientation == 'vert') { lastY = position.y; } else { - lastx = position.x; + constants.lastx = position.x; } Autoplay('reverse-down', 0, position.y); } else { if (constants.plotOrientation == 'vert') { - if ( - position.x == -1 && - position.y == plot.plotData[position.x].length - ) { + if (position.x == -1 && position.y == plot.sections.length) { position.x += 1; } } else { @@ -4589,16 +5470,11 @@ class Control { constants.brailleInput, 'keydown', function (e) { - // We block all input, except if it's B or Tab so we move focus - let updateInfoThisRound = false; // we only update info and play tones on certain keys let setBrailleThisRound = false; let isAtEnd = false; - if (e.which == 9) { - // tab - // do nothing, let the user Tab away - } else if (e.which == 39) { + if (e.key == 'ArrowRight') { // right arrow e.preventDefault(); if (constants.isMac ? e.metaKey : e.ctrlKey) { @@ -4606,17 +5482,13 @@ class Control { if (constants.plotOrientation == 'vert') { Autoplay('right', position.x, plot.plotData.length - 1); } else { - Autoplay( - 'right', - position.x, - plot.plotData[position.y].length - ); + Autoplay('right', position.x, plot.sections.length); } } else { if (constants.plotOrientation == 'vert') { position.x = plot.plotData.length - 1; } else { - position.x = plot.plotData[position.y].length - 1; + position.x = plot.sections.length - 1; } updateInfoThisRound = true; isAtEnd = lockPosition(); @@ -4644,14 +5516,10 @@ class Control { if ( e.altKey && e.shiftKey && - plot.plotData[position.y].length - 1 != position.x + plot.sections.length - 1 != position.x ) { - lastx = position.x; - Autoplay( - 'reverse-right', - plot.plotData[position.y].length - 1, - position.x - ); + constants.lastx = position.x; + Autoplay('reverse-right', plot.sections.length - 1, position.x); } else { if (position.x == -1 && position.y == plot.plotData.length) { position.y -= 1; @@ -4663,7 +5531,7 @@ class Control { } setBrailleThisRound = true; constants.navigation = 1; - } else if (e.which == 37) { + } else if (e.key == 'ArrowLeft') { // left arrow e.preventDefault(); if (constants.isMac ? e.metaKey : e.ctrlKey) { @@ -4678,7 +5546,7 @@ class Control { if (constants.plotOrientation == 'vert') { lastY = position.y; } else { - lastx = position.x; + constants.lastx = position.x; } Autoplay('reverse-left', 0, position.x); } else { @@ -4688,19 +5556,19 @@ class Control { } setBrailleThisRound = true; constants.navigation = 1; - } else if (e.which === 38) { + } else if (e.key == 'ArrowUp') { // up arrow let oldY = position.y; if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { if (constants.plotOrientation == 'vert') { if (position.x < 0) position.x = 0; - Autoplay('up', position.y, plot.plotData[position.x].length); + Autoplay('up', position.y, plot.sections.length); } else { Autoplay('up', position.y, plot.plotData.length); } } else if (constants.plotOrientation == 'vert') { - position.y = plot.plotData[position.x].length - 1; + position.y = plot.sections.length - 1; updateInfoThisRound = true; } else { position.y = plot.plotData.length - 1; @@ -4710,14 +5578,10 @@ class Control { if ( e.altKey && e.shiftKey && - position.y != plot.plotData[position.x].length - 1 + position.y != plot.sections.length - 1 ) { lasY = position.y; - Autoplay( - 'reverse-up', - plot.plotData[position.x].length - 1, - position.y - ); + Autoplay('reverse-up', plot.sections.length - 1, position.y); } else { position.y += 1; updateInfoThisRound = true; @@ -4729,7 +5593,7 @@ class Control { e.shiftKey && position.y != plot.plotData.length - 1 ) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-up', plot.plotData.length - 1, position.y); } else { position.y += 1; @@ -4742,7 +5606,7 @@ class Control { setBrailleThisRound = true; } constants.navigation = 0; - } else if (e.which === 40) { + } else if (e.key == 'ArrowDown') { // down arrow let oldY = position.y; if (constants.isMac ? e.metaKey : e.ctrlKey) { @@ -4757,15 +5621,12 @@ class Control { if (constants.plotOrientation == 'vert') { lastY = position.y; } else { - lastx = position.x; + constants.lastx = position.x; } Autoplay('reverse-down', 0, position.y); } else { if (constants.plotOrientation == 'vert') { - if ( - position.x == -1 && - position.y == plot.plotData[position.x].length - ) { + if (position.x == -1 && position.y == plot.sections.length) { position.x += 1; } } else { @@ -4783,6 +5644,8 @@ class Control { setBrailleThisRound = true; } constants.navigation = 0; + } else if (e.key == 'Tab') { + // do nothing, we handle this in global events } else { e.preventDefault(); // todo: allow some controls through like page refresh @@ -4796,267 +5659,145 @@ class Control { if (isAtEnd) { audio.playEnd(); } - - // auto turn off braille mode if we leave the braille box - constants.brailleInput.addEventListener('focusout', function (e) { - display.toggleBrailleMode('off'); - }); }, ]); - // main BTS controls let controlElements = [constants.chart, constants.brailleInput]; + let lastx = 0; for (let i = 0; i < controlElements.length; i++) { constants.events.push([ controlElements[i], 'keydown', function (e) { - // B: braille mode - if (e.which == 66) { - display.toggleBrailleMode(); - e.preventDefault(); + // period: speed up + if (e.key == '.') { + constants.SpeedUp(); + PlayDuringSpeedChange(); + display.announceText('Speed up'); } - // T: aria live text output mode - if (e.which == 84) { - let timediff = window.performance.now() - lastKeyTime; - if (!pressedL || timediff > constants.keypressInterval) { - display.toggleTextMode(); - } - } - - // keys = (keys || []); - // keys[e.keyCode] = true; - // if (keys[84] && !keys[76]) { - // display.toggleTextMode(); - // } - // S: sonification mode - if (e.which == 83) { - display.toggleSonificationMode(); + // comma: speed down + if (e.key == ',') { + constants.SpeedDown(); + PlayDuringSpeedChange(); + display.announceText('Speed down'); } - if (e.which === 32) { - // space 32, replay info but no other changes - UpdateAll(); + // /: reset speed + if (e.key == '/') { + constants.SpeedReset(); + PlayDuringSpeedChange(); + display.announceText('Speed reset'); } }, ]); } - - constants.events.push([ - document, - 'keydown', - function (e) { - if (constants.isMac ? e.metaKey : e.ctrlKey) { - // (ctrl/cmd)+(home/fn+left arrow): top left element - if (e.which == 36) { - position.x = 0; - position.y = plot.plotData.length - 1; - UpdateAllBraille(); + function PlayDuringSpeedChange() { + if (constants.autoplayId != null) { + constants.KillAutoplay(); + if (lastPlayed == 'reverse-left') { + if (constants.plotOrientation == 'vert') { + Autoplay('right', position.y, lastY); + } else { + Autoplay('right', position.x, lastx); } - - // (ctrl/cmd)+(end/fn+right arrow): right bottom element - else if (e.which == 35) { - position.x = plot.plotData[0].length - 1; - position.y = 0; - UpdateAllBraille(); + } else if (lastPlayed == 'reverse-right') { + if (constants.plotOrientation == 'vert') { + Autoplay('left', position.y, lastY); + } else { + Autoplay('left', position.x, lastx); } - } - - // must come before the prefix L - if (pressedL) { - if (e.which == 88) { - // X: x label - let timediff = window.performance.now() - lastKeyTime; - if (pressedL && timediff <= constants.keypressInterval) { - display.displayXLabel(plot); - } - pressedL = false; - } else if (e.which == 89) { - // Y: y label - let timediff = window.performance.now() - lastKeyTime; - if (pressedL && timediff <= constants.keypressInterval) { - display.displayYLabel(plot); - } - pressedL = false; - } else if (e.which == 84) { - // T: title - let timediff = window.performance.now() - lastKeyTime; - if (pressedL && timediff <= constants.keypressInterval) { - display.displayTitle(plot); - } - pressedL = false; - } else if (e.which == 76) { - lastKeyTime = window.performance.now(); - pressedL = true; + } else if (lastPlayed == 'reverse-up') { + if (constants.plotOrientation == 'vert') { + Autoplay('down', position.y, lastY); } else { - pressedL = false; + Autoplay('down', position.x, lastx); } - } - - // L: prefix for label; must come after suffix - if (e.which == 76) { - lastKeyTime = window.performance.now(); - pressedL = true; - } - - // period: speed up - if (e.which == 190) { - constants.SpeedUp(); - if (constants.autoplayId != null) { - constants.KillAutoplay(); - if (lastPlayed == 'reverse-left') { - if (constants.plotOrientation == 'vert') { - Autoplay('right', position.y, lastY); - } else { - Autoplay('right', position.x, lastx); - } - } else if (lastPlayed == 'reverse-right') { - if (constants.plotOrientation == 'vert') { - Autoplay('left', position.y, lastY); - } else { - Autoplay('left', position.x, lastx); - } - } else if (lastPlayed == 'reverse-up') { - if (constants.plotOrientation == 'vert') { - Autoplay('down', position.y, lastY); - } else { - Autoplay('down', position.x, lastx); - } - } else if (lastPlayed == 'reverse-down') { - if (constants.plotOrientation == 'vert') { - Autoplay('up', position.y, lastY); - } else { - Autoplay('up', position.x, lastx); - } - } else { - if (constants.plotOrientation == 'vert') { - Autoplay(lastPlayed, position.y, lastY); - } else { - Autoplay(lastPlayed, position.x, lastx); - } - } + } else if (lastPlayed == 'reverse-down') { + if (constants.plotOrientation == 'vert') { + Autoplay('up', position.y, lastY); + } else { + Autoplay('up', position.x, lastx); } - } - - // comma: speed down - if (e.which == 188) { - constants.SpeedDown(); - if (constants.autoplayId != null) { - constants.KillAutoplay(); - if (lastPlayed == 'reverse-left') { - if (constants.plotOrientation == 'vert') { - Autoplay('right', position.y, lastY); - } else { - Autoplay('right', position.x, lastx); - } - } else if (lastPlayed == 'reverse-right') { - if (constants.plotOrientation == 'vert') { - Autoplay('left', position.y, lastY); - } else { - Autoplay('left', position.x, lastx); - } - } else if (lastPlayed == 'reverse-up') { - if (constants.plotOrientation == 'vert') { - Autoplay('down', position.y, lastY); - } else { - Autoplay('down', position.x, lastx); - } - } else if (lastPlayed == 'reverse-down') { - if (constants.plotOrientation == 'vert') { - Autoplay('up', position.y, lastY); - } else { - Autoplay('up', position.x, lastx); - } - } else { - if (constants.plotOrientation == 'vert') { - Autoplay(lastPlayed, position.y, lastY); - } else { - Autoplay(lastPlayed, position.x, lastx); - } - } + } else { + if (constants.plotOrientation == 'vert') { + Autoplay(lastPlayed, position.y, lastY); + } else { + Autoplay(lastPlayed, position.x, lastx); } } - }, - ]); + } + } function UpdateAll() { if (constants.showDisplay) { - display.displayValues(plot); + display.displayValues(); } if (constants.showRect && constants.hasRect) { rect.UpdateRect(); } if (constants.sonifMode != 'off') { - plot.PlayTones(audio); + plot.PlayTones(); } } function UpdateAllAutoplay() { if (constants.showDisplayInAutoplay) { - display.displayValues(plot); + display.displayValues(); } if (constants.showRect && constants.hasRect) { rect.UpdateRect(); } if (constants.sonifMode != 'off') { - plot.PlayTones(audio); + plot.PlayTones(); } if (constants.brailleMode != 'off') { - display.UpdateBraillePos(plot); + display.UpdateBraillePos(); } } function UpdateAllBraille() { if (constants.showDisplayInBraille) { - display.displayValues(plot); + display.displayValues(); } if (constants.showRect && constants.hasRect) { rect.UpdateRect(); } if (constants.sonifMode != 'off') { - plot.PlayTones(audio); + plot.PlayTones(); } - display.UpdateBraillePos(plot); + display.UpdateBraillePos(); } function lockPosition() { // lock to min / max postions - let isLockNeeded = false; + let didLockHappen = false; + if (position.y < 0) { + position.y = 0; + didLockHappen = true; + } + if (position.x < 0) { + position.x = 0; + didLockHappen = true; + } if (constants.plotOrientation == 'vert') { - if (position.y < 0) { - position.y = 0; - isLockNeeded = true; - } - if (position.x < 0) { - position.x = 0; - isLockNeeded = true; - } if (position.x > plot.plotData.length - 1) { position.x = plot.plotData.length - 1; - isLockNeeded = true; + didLockHappen = true; } - if (position.y > plot.plotData[position.x].length - 1) { - position.y = plot.plotData[position.x].length - 1; - isLockNeeded = true; + if (position.y > plot.sections.length - 1) { + position.y = plot.sections.length - 1; + didLockHappen = true; } } else { - if (position.x < 0) { - position.x = 0; - isLockNeeded = true; - } - if (position.y < 0) { - position.y = 0; - isLockNeeded = true; - } if (position.y > plot.plotData.length - 1) { position.y = plot.plotData.length - 1; - isLockNeeded = true; + didLockHappen = true; } - if (position.x > plot.plotData[position.y].length - 1) { - position.x = plot.plotData[position.y].length - 1; - isLockNeeded = true; + if (position.x > plot.sections.length - 1) { + position.x = plot.sections.length - 1; + didLockHappen = true; } } - return isLockNeeded; + return didLockHappen; } function Autoplay(dir, start, end) { @@ -5083,7 +5824,7 @@ class Control { } if (constants.debugLevel > 0) { - console.log('starting autoplay', dir); + console.log('starting autoplay', dir, start, end); } UpdateAllAutoplay(); // play current tone before we move @@ -5094,13 +5835,13 @@ class Control { (position.x < 1 && dir == 'left') || (constants.plotOrientation == 'vert' && dir == 'up' && - position.y > plot.plotData[position.x].length - 2) || + position.y > plot.sections.length - 2) || (constants.plotOrientation == 'horz' && dir == 'up' && position.y > plot.plotData.length - 2) || (constants.plotOrientation == 'horz' && dir == 'right' && - position.x > plot.plotData[position.y].length - 2) || + position.x > plot.sections.length - 2) || (constants.plotOrientation == 'vert' && dir == 'right' && position.x > plot.plotData.length - 2) || @@ -5150,11 +5891,8 @@ class Control { window.position = new Position(-1, -1); window.plot = new HeatMap(); let rect = new HeatMapRect(); - let audio = new Audio(); let lastPlayed = ''; - let lastx = 0; - let lastKeyTime = 0; - let pressedL = false; + constants.lastx = 0; // control eventlisteners constants.events.push([ @@ -5165,10 +5903,9 @@ class Control { let isAtEnd = false; // right arrow 39 - if (e.which === 39) { + if (e.key == 'ArrowRight') { if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { - // lastx = position.x; position.x -= 1; Autoplay('right', position.x, plot.num_cols); } else { @@ -5180,7 +5917,7 @@ class Control { e.shiftKey && position.x != plot.num_cols - 1 ) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-right', plot.num_cols, position.x); } else { if (position.x == -1 && position.y == -1) { @@ -5194,10 +5931,9 @@ class Control { } // left arrow 37 - if (e.which === 37) { + if (e.key == 'ArrowLeft') { if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { - // lastx = position.x; position.x += 1; Autoplay('left', position.x, -1); } else { @@ -5205,7 +5941,7 @@ class Control { updateInfoThisRound = true; } } else if (e.altKey && e.shiftKey && position.x != 0) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-left', -1, position.x); } else { position.x -= 1; @@ -5216,10 +5952,9 @@ class Control { } // up arrow 38 - if (e.which === 38) { + if (e.key == 'ArrowUp') { if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { - // lastx = position.y; position.y += 1; Autoplay('up', position.y, -1); } else { @@ -5227,7 +5962,7 @@ class Control { updateInfoThisRound = true; } } else if (e.altKey && e.shiftKey && position.y != 0) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-up', -1, position.y); } else { position.y -= 1; @@ -5238,10 +5973,9 @@ class Control { } // down arrow 40 - if (e.which === 40) { + if (e.key == 'ArrowDown') { if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { - // lastx = position.y; position.y -= 1; Autoplay('down', position.y, plot.num_rows); } else { @@ -5253,7 +5987,7 @@ class Control { e.shiftKey && position.y != plot.num_rows - 1 ) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-down', plot.num_rows, position.y); } else { if (position.x == -1 && position.y == -1) { @@ -5276,6 +6010,53 @@ class Control { }, ]); + let controlElements = [constants.chart, constants.brailleInput]; + let lastx = 0; + for (let i = 0; i < controlElements.length; i++) { + constants.events.push([ + controlElements[i], + 'keydown', + function (e) { + // period: speed up + if (e.key == '.') { + constants.SpeedUp(); + PlayDuringSpeedChange(); + display.announceText('Speed up'); + } + + // comma: speed down + if (e.key == ',') { + constants.SpeedDown(); + PlayDuringSpeedChange(); + display.announceText('Speed down'); + } + + // /: reset speed + if (e.key == '/') { + constants.SpeedReset(); + PlayDuringSpeedChange(); + display.announceText('Speed reset'); + } + }, + ]); + } + function PlayDuringSpeedChange() { + if (constants.autoplayId != null) { + constants.KillAutoplay(); + if (lastPlayed == 'reverse-left') { + Autoplay('right', position.x, lastx); + } else if (lastPlayed == 'reverse-right') { + Autoplay('left', position.x, lastx); + } else if (lastPlayed == 'reverse-up') { + Autoplay('down', position.x, lastx); + } else if (lastPlayed == 'reverse-down') { + Autoplay('up', position.x, lastx); + } else { + Autoplay(lastPlayed, position.x, lastx); + } + } + } + constants.events.push([ constants.brailleInput, 'keydown', @@ -5283,9 +6064,7 @@ class Control { let updateInfoThisRound = false; let isAtEnd = false; - if (e.which == 9) { - // let user tab - } else if (e.which == 39) { + if (e.key == 'ArrowRight') { // right arrow if ( e.target.selectionStart > e.target.value.length - 3 || @@ -5314,7 +6093,7 @@ class Control { e.shiftKey && position.x != plot.num_cols - 1 ) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-right', plot.num_cols, position.x); } else { if (position.x == -1 && position.y == -1) { @@ -5332,7 +6111,7 @@ class Control { constants.navigation = 1; } - } else if (e.which == 37) { + } else if (e.key == 'ArrowLeft') { // left if ( e.target.selectionStart == 0 || @@ -5345,7 +6124,6 @@ class Control { } else { if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { - // lastx = position.x; position.x += 1; Autoplay('left', position.x, -1); } else { @@ -5353,7 +6131,7 @@ class Control { updateInfoThisRound = true; } } else if (e.altKey && e.shiftKey && position.x != 0) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-left', -1, position.x); } else { position.x += -1; @@ -5367,7 +6145,7 @@ class Control { constants.navigation = 1; } - } else if (e.which == 40) { + } else if (e.key == 'ArrowDown') { // down if (position.y + 1 == plot.num_rows) { e.preventDefault(); @@ -5389,7 +6167,7 @@ class Control { e.shiftKey && position.y != plot.num_rows - 1 ) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-down', plot.num_rows, position.y); } else { if (position.x == -1 && position.y == -1) { @@ -5406,14 +6184,13 @@ class Control { constants.navigation = 0; } - } else if (e.which == 38) { + } else if (e.key == 'ArrowUp') { // up if (e.target.selectionStart - plot.num_cols - 1 < 0) { e.preventDefault(); } else { if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { - // lastx = position.y; position.y += 1; Autoplay('up', position.y, -1); } else { @@ -5421,7 +6198,7 @@ class Control { updateInfoThisRound = true; } } else if (e.altKey && e.shiftKey && position.y != 0) { - lastx = position.x; + constants.lastx = position.x; Autoplay('reverse-up', -1, position.y); } else { position.y += -1; @@ -5435,15 +6212,12 @@ class Control { constants.navigation = 0; } + } else if (e.key == 'Tab') { + // do nothing, we handle this in global events } else { e.preventDefault(); } - // auto turn off braille mode if we leave the braille box - constants.brailleInput.addEventListener('focusout', function (e) { - display.toggleBrailleMode('off'); - }); - if (updateInfoThisRound && !isAtEnd) { UpdateAllBraille(); } @@ -5453,168 +6227,6 @@ class Control { }, ]); - // main BTS controls - let controlElements = [constants.chart, constants.brailleInput]; - for (let i = 0; i < controlElements.length; i++) { - constants.events.push([ - controlElements[i], - 'keydown', - function (e) { - // B: braille mode - if (e.which == 66) { - display.toggleBrailleMode(); - e.preventDefault(); - } - // keys = (keys || []); - // keys[e.keyCode] = true; - // if (keys[84] && !keys[76]) { - // display.toggleTextMode(); - // } - - // T: aria live text output mode - if (e.which == 84) { - let timediff = window.performance.now() - lastKeyTime; - if (!pressedL || timediff > constants.keypressInterval) { - display.toggleTextMode(); - } - } - - // S: sonification mode - if (e.which == 83) { - display.toggleSonificationMode(); - } - - // space: replay info but no other changes - if (e.which === 32) { - UpdateAll(); - } - }, - ]); - } - - constants.events.push([ - document, - 'keydown', - function (e) { - if (constants.isMac ? e.metaKey : e.ctrlKey) { - // (ctrl/cmd)+(home/fn+left arrow): first element - if (e.which == 36) { - position.x = 0; - position.y = 0; - UpdateAllBraille(); - } - - // (ctrl/cmd)+(end/fn+right arrow): last element - else if (e.which == 35) { - position.x = plot.num_cols - 1; - position.y = plot.num_rows - 1; - UpdateAllBraille(); - } - } - - // keys = (keys || []); - // keys[e.keyCode] = true; - // // lx: x label, ly: y label, lt: title, lf: fill - // if (keys[76] && keys[88]) { // lx - // display.displayXLabel(plot); - // } - - // if (keys[76] && keys[89]) { // ly - // display.displayYLabel(plot); - // } - - // if (keys[76] && keys[84]) { // lt - // display.displayTitle(plot); - // } - - // if (keys[76] && keys[70]) { // lf - // display.displayFill(plot); - // } - - // must come before the prefix L - if (pressedL) { - if (e.which == 88) { - // X: x label - let timediff = window.performance.now() - lastKeyTime; - if (pressedL && timediff <= constants.keypressInterval) { - display.displayXLabel(plot); - } - pressedL = false; - } else if (e.which == 89) { - // Y: y label - let timediff = window.performance.now() - lastKeyTime; - if (pressedL && timediff <= constants.keypressInterval) { - display.displayYLabel(plot); - } - pressedL = false; - } else if (e.which == 84) { - // T: title - let timediff = window.performance.now() - lastKeyTime; - if (pressedL && timediff <= constants.keypressInterval) { - display.displayTitle(plot); - } - pressedL = false; - } else if (e.which == 70) { - // F: fill label - let timediff = window.performance.now() - lastKeyTime; - if (pressedL && timediff <= constants.keypressInterval) { - display.displayFill(plot); - } - pressedL = false; - } else if (e.which == 76) { - lastKeyTime = window.performance.now(); - pressedL = true; - } else { - pressedL = false; - } - } - - // L: prefix for label; must come after suffix - if (e.which == 76) { - lastKeyTime = window.performance.now(); - pressedL = true; - } - - // period: speed up - if (e.which == 190) { - constants.SpeedUp(); - if (constants.autoplayId != null) { - constants.KillAutoplay(); - if (lastPlayed == 'reverse-left') { - Autoplay('right', position.x, lastx); - } else if (lastPlayed == 'reverse-right') { - Autoplay('left', position.x, lastx); - } else if (lastPlayed == 'reverse-up') { - Autoplay('down', position.x, lastx); - } else if (lastPlayed == 'reverse-down') { - Autoplay('up', position.x, lastx); - } else { - Autoplay(lastPlayed, position.x, lastx); - } - } - } - - // comma: speed down - if (e.which == 188) { - constants.SpeedDown(); - if (constants.autoplayId != null) { - constants.KillAutoplay(); - if (lastPlayed == 'reverse-left') { - Autoplay('right', position.x, lastx); - } else if (lastPlayed == 'reverse-right') { - Autoplay('left', position.x, lastx); - } else if (lastPlayed == 'reverse-up') { - Autoplay('down', position.x, lastx); - } else if (lastPlayed == 'reverse-down') { - Autoplay('up', position.x, lastx); - } else { - Autoplay(lastPlayed, position.x, lastx); - } - } - } - }, - ]); - function sleep(time) { return new Promise((resolve) => setTimeout(resolve, time)); } @@ -5622,64 +6234,64 @@ class Control { // helper functions function lockPosition() { // lock to min / max postions - let isLockNeeded = false; + let didLockHappen = false; if (position.x < 0) { position.x = 0; - isLockNeeded = true; + didLockHappen = true; } if (position.x > plot.num_cols - 1) { position.x = plot.num_cols - 1; - isLockNeeded = true; + didLockHappen = true; } if (position.y < 0) { position.y = 0; - isLockNeeded = true; + didLockHappen = true; } if (position.y > plot.num_rows - 1) { position.y = plot.num_rows - 1; - isLockNeeded = true; + didLockHappen = true; } - return isLockNeeded; + return didLockHappen; } function UpdateAll() { if (constants.showDisplay) { - display.displayValues(plot); + display.displayValues(); } if (constants.showRect && constants.hasRect) { rect.UpdateRectDisplay(); } if (constants.sonifMode != 'off') { - audio.playTone(); + plot.PlayTones(); } } function UpdateAllAutoplay() { if (constants.showDisplayInAutoplay) { - display.displayValues(plot); + display.displayValues(); } if (constants.showRect && constants.hasRect) { rect.UpdateRectDisplay(); } if (constants.sonifMode != 'off') { - audio.playTone(); + plot.PlayTones(); } if (constants.brailleMode != 'off') { - display.UpdateBraillePos(plot); + display.UpdateBraillePos(); } } function UpdateAllBraille() { if (constants.showDisplayInBraille) { - display.displayValues(plot); + display.displayValues(); } if (constants.showRect && constants.hasRect) { rect.UpdateRectDisplay(); } if (constants.sonifMode != 'off') { - audio.playTone(); + plot.PlayTones(); } - display.UpdateBraillePos(plot); + display.UpdateBraillePos(); } function Autoplay(dir, start, end) { @@ -5738,42 +6350,38 @@ class Control { }, constants.autoPlayRate); } } else if ( - [].concat(singleMaidr.type).includes('scatter') || - singleMaidr.type == 'scatter' + [].concat(singleMaidr.type).includes('point') || + singleMaidr.type == 'point' ) { // variable initialization constants.plotId = 'geom_point.points.12.1'; window.position = new Position(-1, -1); window.plot = new ScatterPlot(); - let audio = new Audio(); let layer0Point = new Layer0Point(); let layer1Point = new Layer1Point(); let lastPlayed = ''; // for autoplay use - let lastx = 0; // for scatter point layer autoplay use - let lastx1 = 0; // for line layer autoplay use - let lastKeyTime = 0; - let pressedL = false; + constants.lastx = 0; // for scatter point layer autoplay use + let lastx1 = 0; // for smooth layer autoplay use window.positionL1 = new Position(lastx1, lastx1); // control eventlisteners constants.events.push([ - constants.chart, + [constants.chart, constants.brailleInput], 'keydown', function (e) { let updateInfoThisRound = false; let isAtEnd = false; // left and right arrows are enabled only at point layer - if (constants.chartType == 'scatter') { + if (constants.chartType == 'point') { // right arrow 39 - if (e.which === 39) { + if (e.key == 'ArrowRight') { if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { - // lastx = position.x; position.x -= 1; - Autoplay('outward_right', position.x, plot.x.length); + Autoplay('right', position.x, plot.x.length); } else { position.x = plot.x.length - 1; updateInfoThisRound = true; @@ -5784,8 +6392,8 @@ class Control { e.shiftKey && position.x != plot.x.length - 1 ) { - lastx = position.x; - Autoplay('inward_right', plot.x.length, position.x); + constants.lastx = position.x; + Autoplay('reverse-right', plot.x.length, position.x); } else { position.x += 1; updateInfoThisRound = true; @@ -5794,48 +6402,49 @@ class Control { } // left arrow 37 - if (e.which === 37) { + if (e.key == 'ArrowLeft') { if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { - // lastx = position.x; position.x += 1; - Autoplay('outward_left', position.x, -1); + Autoplay('left', position.x, -1); } else { position.x = 0; updateInfoThisRound = true; isAtEnd = lockPosition(); } } else if (e.altKey && e.shiftKey && position.x != 0) { - lastx = position.x; - Autoplay('inward_left', -1, position.x); + constants.lastx = position.x; + Autoplay('reverse-left', -1, position.x); } else { position.x -= 1; updateInfoThisRound = true; isAtEnd = lockPosition(); } } - } else if (constants.chartType == 'line') { - positionL1.x = lastx1; + } else if (constants.chartType == 'smooth') { + if (!positionL1.x) { + positionL1.x = lastx1; + } - if (e.which == 39 && e.shiftKey) { + if (e.key == 'ArrowRight' && e.shiftKey) { if ( (constants.isMac ? e.metaKey : e.ctrlKey) && constants.sonifMode != 'off' ) { - PlayLine('outward_right'); + PlayLine('right'); } else if (e.altKey && constants.sonifMode != 'off') { - PlayLine('inward_right'); + PlayLine('reverse-right'); } } - if (e.which == 37 && e.shiftKey) { + if (e.key == 'ArrowLeft' && e.shiftKey) { if ( (constants.isMac ? e.metaKey : e.ctrlKey) && constants.sonifMode != 'off' ) { - PlayLine('outward_left'); + PlayLine('left'); } else if (e.altKey && constants.sonifMode != 'off') { - PlayLine('inward_left'); + PlayLine('reverse-left'); } } } @@ -5843,7 +6452,7 @@ class Control { // update text, display, and audio if ( updateInfoThisRound && - constants.chartType == 'scatter' && + constants.chartType == 'point' && !isAtEnd ) { UpdateAll(); @@ -5854,6 +6463,62 @@ class Control { }, ]); + let controlElements = [constants.chart, constants.brailleInput]; + let lastx = 0; + for (let i = 0; i < controlElements.length; i++) { + constants.events.push([ + controlElements[i], + 'keydown', + function (e) { + // period: speed up + if (e.key == '.') { + constants.SpeedUp(); + PlayDuringSpeedChange(); + display.announceText('Speed up'); + } + + // comma: speed down + if (e.key == ',') { + constants.SpeedDown(); + PlayDuringSpeedChange(); + display.announceText('Speed down'); + } + + // /: reset speed + if (e.key == '/') { + constants.SpeedReset(); + PlayDuringSpeedChange(); + display.announceText('Speed reset'); + } + }, + ]); + } + function PlayDuringSpeedChange() { + if (constants.autoplayId != null) { + constants.KillAutoplay(); + audio.KillSmooth(); + if (lastPlayed == 'reverse-left') { + if (constants.chartType == 'point') { + Autoplay('right', position.x, lastx); + } else if (constants.chartType == 'smooth') { + Autoplay('right', positionL1.x, lastx1); + } + } else if (lastPlayed == 'reverse-right') { + if (constants.chartType == 'point') { + Autoplay('left', position.x, lastx); + } else if (constants.chartType == 'smooth') { + Autoplay('left', positionL1.x, lastx1); + } + } else { + if (constants.chartType == 'point') { + Autoplay(lastPlayed, position.x, lastx); + } else if (constants.chartType == 'smooth') { + Autoplay(lastPlayed, positionL1.x, lastx1); + } + } + } + } + constants.events.push([ constants.brailleInput, 'keydown', @@ -5862,13 +6527,10 @@ class Control { let isAtEnd = false; // @TODO - // only line layer can access to braille display - if (e.which == 9) { - // constants.brailleInput.setSelectionRange(positionL1.x, positionL1.x); - } else if (constants.chartType == 'line') { + // only smooth layer can access to braille display + if (constants.chartType == 'smooth') { lockPosition(); - if (e.which == 9) { - } else if (e.which == 39) { + if (e.key == 'ArrowRight') { // right arrow e.preventDefault(); constants.brailleInput.setSelectionRange( @@ -5880,11 +6542,7 @@ class Control { } else if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { positionL1.x -= 1; - Autoplay( - 'outward_right', - positionL1.x, - plot.curvePoints.length - ); + Autoplay('right', positionL1.x, plot.curvePoints.length); } else { positionL1.x = plot.curvePoints.length - 1; updateInfoThisRound = true; @@ -5896,27 +6554,30 @@ class Control { positionL1.x != plot.curvePoints.length - 1 ) { lastx1 = positionL1.x; - Autoplay('inward_right', plot.curvePoints.length, positionL1.x); + Autoplay( + 'reverse-right', + plot.curvePoints.length, + positionL1.x + ); } else { positionL1.x += 1; updateInfoThisRound = true; isAtEnd = lockPosition(); } - } else if (e.which == 37) { + } else if (e.key == 'ArrowLeft') { // left e.preventDefault(); if (constants.isMac ? e.metaKey : e.ctrlKey) { if (e.shiftKey) { - // lastx = position.x; positionL1.x += 1; - Autoplay('outward_left', positionL1.x, -1); + Autoplay('left', positionL1.x, -1); } else { positionL1.x = 0; // go all the way updateInfoThisRound = true; isAtEnd = lockPosition(); } } else if (e.altKey && e.shiftKey && positionL1.x != 0) { - Autoplay('inward_left', -1, positionL1.x); + Autoplay('reverse-left', -1, positionL1.x); } else { positionL1.x -= 1; updateInfoThisRound = true; @@ -5925,15 +6586,12 @@ class Control { } else { e.preventDefault(); } + } else if (e.key == 'Tab') { + // do nothing, we handle this in global events } else { e.preventDefault(); } - // auto turn off braille mode if we leave the braille box - constants.brailleInput.addEventListener('focusout', function (e) { - display.toggleBrailleMode('off'); - }); - lastx1 = positionL1.x; if (updateInfoThisRound && !isAtEnd) { @@ -5945,331 +6603,1235 @@ class Control { }, ]); - // main BTS controls - let controlElements = [constants.chart, constants.brailleInput]; - for (let i = 0; i < controlElements.length; i++) { - constants.events.push([ - controlElements[i], - 'keydown', - function (e) { - // B: braille mode - if (e.which == 66) { - display.toggleBrailleMode(); - e.preventDefault(); - } - // T: aria live text output mode - if (e.which == 84) { - let timediff = window.performance.now() - lastKeyTime; - if (!pressedL || timediff > constants.keypressInterval) { - display.toggleTextMode(); - } - } - - // keys = (keys || []); - // keys[e.keyCode] = true; - // if (keys[84] && !keys[76]) { - // display.toggleTextMode(); - // } - - // S: sonification mode - if (e.which == 83) { - display.toggleSonificationMode(); - } - - // page down /(fn+down arrow): change chart type (layer) - if (e.which == 34 && constants.brailleMode == 'off') { - lastx1 = positionL1.x; - display.changeChartLayer('down'); - } - - // page up / (fn+up arrow): change chart type (layer) - if (e.which == 33 && constants.brailleMode == 'off') { - display.changeChartLayer('up'); - } + // helper functions + function lockPosition() { + // lock to min / max positions + let didLockHappen = false; + if (constants.chartType == 'point') { + if (position.x < 0) { + position.x = 0; + didLockHappen = true; + } + if (position.x > plot.x.length - 1) { + position.x = plot.x.length - 1; + didLockHappen = true; + } + } else if (constants.chartType == 'smooth') { + if (positionL1.x < 0) { + positionL1.x = 0; + didLockHappen = true; + } + if (positionL1.x > plot.curvePoints.length - 1) { + positionL1.x = plot.curvePoints.length - 1; + didLockHappen = true; + } + } - // space: replay info but no other changes - if (e.which === 32) { - UpdateAll(); - } - }, - ]); + return didLockHappen; } - constants.events.push([ - document, - 'keydown', - function (e) { - if (constants.isMac ? e.metaKey : e.ctrlKey) { - // (ctrl/cmd)+(home/fn+left arrow): first element - if (e.which == 36) { - if (constants.chartType == 'scatter') { - position.x = 0; - UpdateAll(); - // move cursor for braille - constants.brailleInput.setSelectionRange(0, 0); - } else if (constants.chartType == 'line') { - positionL1.x = 0; - UpdateAllBraille(); - } - } + function UpdateAll() { + if (constants.showDisplay) { + display.displayValues(); + } + if (constants.showRect) { + layer0Point.UpdatePointDisplay(); + } + if (constants.sonifMode != 'off') { + plot.PlayTones(); + } + } - // (ctrl/cmd)+(end/fn+right arrow): last element - else if (e.which == 35) { - if (constants.chartType == 'scatter') { - position.x = plot.y.length - 1; - UpdateAll(); - // move cursor for braille - constants.brailleInput.setSelectionRange( - plot.curvePoints.length - 1, - plot.curvePoints.length - 1 - ); - } else if (constants.chartType == 'line') { - positionL1.x = plot.curvePoints.length - 1; - UpdateAllBraille(); - } + function UpdateAllAutoplay() { + if (constants.showDisplayInAutoplay) { + display.displayValues(); + } + if (constants.showRect) { + if (constants.chartType == 'point') { + layer0Point.UpdatePointDisplay(); + } else { + layer1Point.UpdatePointDisplay(); + } + } + if (constants.sonifMode != 'off') { + plot.PlayTones(); + } + if (constants.brailleMode != 'off') { + display.UpdateBraillePos(); + } + } + function UpdateAllBraille() { + if (constants.showDisplayInBraille) { + display.displayValues(); + } + if (constants.showRect) { + layer1Point.UpdatePointDisplay(); + } + if (constants.sonifMode != 'off') { + plot.PlayTones(); + } + display.UpdateBraillePos(); + } + + function Autoplay(dir, start, end) { + lastPlayed = dir; + let step = 1; // default right and reverse left + if (dir == 'left' || dir == 'reverse-right') { + step = -1; + } + + // clear old autoplay if exists + if (constants.autoplayId) { + constants.KillAutoplay(); + } + if (constants.isSmoothAutoplay) { + audio.KillSmooth(); + } + + if (dir == 'reverse-left' || dir == 'reverse-right') { + position.x = start; + position.L1x = start; + } + + if (constants.chartType == 'point') { + constants.autoplayId = setInterval(function () { + position.x += step; + // autoplay for two layers: point layer & smooth layer in braille + // plot.numPoints is not available anymore + if (position.x < 0 || position.x > plot.y.length - 1) { + constants.KillAutoplay(); + lockPosition(); + } else if (position.x == end) { + constants.KillAutoplay(); + UpdateAllAutoplay(); + } else { + UpdateAllAutoplay(); + } + }, constants.autoPlayRate); + } else if (constants.chartType == 'smooth') { + constants.autoplayId = setInterval(function () { + positionL1.x += step; + // autoplay for two layers: point layer & smooth layer in braille + // plot.numPoints is not available anymore + if ( + positionL1.x < 0 || + positionL1.x > plot.curvePoints.length - 1 + ) { + constants.KillAutoplay(); + lockPosition(); + } else if (positionL1.x == end) { + constants.KillAutoplay(); + UpdateAllAutoplay(); + } else { + UpdateAllAutoplay(); + } + }, constants.autoPlayRate); + } + } + + function PlayLine(dir) { + lastPlayed = dir; + + let freqArr = []; + let panningArr = []; + let panPoint = audio.SlideBetween( + positionL1.x, + 0, + plot.curvePoints.length - 1, + -1, + 1 + ); + let x = positionL1.x < 0 ? 0 : positionL1.x; + let duration = 0; + if (dir == 'right') { + for (let i = x; i < plot.curvePoints.length; i++) { + freqArr.push( + audio.SlideBetween( + plot.curvePoints[i], + plot.curveMinY, + plot.curveMaxY, + constants.MIN_FREQUENCY, + constants.MAX_FREQUENCY + ) + ); + } + panningArr = [panPoint, 1]; + duration = + (Math.abs(plot.curvePoints.length - x) / plot.curvePoints.length) * + 3; + } else if (dir == 'left') { + for (let i = x; i >= 0; i--) { + freqArr.push( + audio.SlideBetween( + plot.curvePoints[i], + plot.curveMinY, + plot.curveMaxY, + constants.MIN_FREQUENCY, + constants.MAX_FREQUENCY + ) + ); + } + panningArr = [panPoint, -1]; + duration = (Math.abs(x) / plot.curvePoints.length) * 3; + } else if (dir == 'reverse-right') { + for (let i = plot.curvePoints.length - 1; i >= x; i--) { + freqArr.push( + audio.SlideBetween( + plot.curvePoints[i], + plot.curveMinY, + plot.curveMaxY, + constants.MIN_FREQUENCY, + constants.MAX_FREQUENCY + ) + ); + } + panningArr = [1, panPoint]; + duration = + (Math.abs(plot.curvePoints.length - x) / plot.curvePoints.length) * + 3; + } else if (dir == 'reverse-left') { + for (let i = 0; i <= x; i++) { + freqArr.push( + audio.SlideBetween( + plot.curvePoints[i], + plot.curveMinY, + plot.curveMaxY, + constants.MIN_FREQUENCY, + constants.MAX_FREQUENCY + ) + ); + } + panningArr = [-1, panPoint]; + duration = (Math.abs(x) / plot.curvePoints.length) * 3; + } + + if (constants.isSmoothAutoplay) { + audio.KillSmooth(); + } + + // audio.playSmooth(freqArr, 2, panningArr, constants.vol, 'sine'); + audio.playSmooth(freqArr, duration, panningArr, constants.vol, 'sine'); + } + } else if ([].concat(singleMaidr.type).includes('hist')) { + window.position = new Position(-1, -1); + window.plot = new Histogram(); + + // global variables + let lastPlayed = ''; + constants.lastx = 0; + + // control eventlisteners + constants.events.push([ + [constants.chart, constants.brailleInput], + 'keydown', + function (e) { + let updateInfoThisRound = false; // we only update info and play tones on certain keys + let isAtEnd = false; + + // Right + if ( + e.key == 'ArrowRight' && + !(constants.isMac ? e.metaKey : e.ctrlKey) && + !e.shiftKey + ) { + // just right arrow, move right + e.preventDefault(); + position.x += 1; + updateInfoThisRound = true; + isAtEnd = lockPosition(); + } else if ( + e.key == 'ArrowRight' && + (constants.isMac ? e.metaKey : e.ctrlKey) && + e.shiftKey + ) { + // ctrl shift right arrow, autoplay right + e.preventDefault(); + position.x -= 1; + Autoplay('right', position.x, plot.plotData.length); + } else if ( + e.key == 'ArrowRight' && + !(constants.isMac ? e.metaKey : e.ctrlKey) && + e.altKey && + e.shiftKey + ) { + // alt shift right, autoplay from right + e.preventDefault(); + constants.lastx = position.x; + Autoplay('reverse-right', plot.bars.length, position.x); + } else if ( + e.key == 'ArrowRight' && + (constants.isMac ? e.metaKey : e.ctrlKey) && + !e.shiftKey + ) { + // ctrl right arrow, go to end + e.preventDefault(); + position.x = plot.plotData.length - 1; + updateInfoThisRound = true; + isAtEnd = lockPosition(); + } + + // Left + if ( + e.key == 'ArrowLeft' && + !(constants.isMac ? e.metaKey : e.ctrlKey) && + !e.shiftKey + ) { + // just left arrow, move left + e.preventDefault(); + position.x += -1; + updateInfoThisRound = true; + isAtEnd = lockPosition(); + } else if ( + e.key == 'ArrowLeft' && + (constants.isMac ? e.metaKey : e.ctrlKey) && + e.shiftKey + ) { + // ctrl shift left arrow, autoplay left + e.preventDefault(); + position.x += 1; + Autoplay('left', position.x, -1); + } else if ( + e.key == 'ArrowLeft' && + !(constants.isMac ? e.metaKey : e.ctrlKey) && + e.altKey && + e.shiftKey + ) { + // alt shift left, autoplay from left + e.preventDefault(); + constants.lastx = position.x; + Autoplay('reverse-left', -1, position.x); + } else if ( + e.key == 'ArrowLeft' && + (constants.isMac ? e.metaKey : e.ctrlKey) && + !e.shiftKey + ) { + // ctrl left arrow, go to beginning + e.preventDefault(); + position.x = 0; + updateInfoThisRound = true; + isAtEnd = lockPosition(); + } + + // update display / text / audio + if (updateInfoThisRound && !isAtEnd) { + if (constants.brailleMode == 'off') { + UpdateAll(); + } else { + UpdateAllBraille(); + } + } + if (isAtEnd) { + audio.playEnd(); + } + }, + ]); + + let controlElements = [constants.chart, constants.brailleInput]; + let lastx = 0; + for (let i = 0; i < controlElements.length; i++) { + constants.events.push([ + controlElements[i], + 'keydown', + function (e) { + // period: speed up + if (e.key == '.') { + e.preventDefault(); + constants.SpeedUp(); + PlayDuringSpeedChange(); + display.announceText('Speed up'); + } + + // comma: speed down + if (e.key == ',') { + e.preventDefault(); + constants.SpeedDown(); + PlayDuringSpeedChange(); + display.announceText('Speed down'); + } + + // /: reset speed + if (e.key == '/') { + e.preventDefault(); + constants.SpeedReset(); + PlayDuringSpeedChange(); + display.announceText('Speed reset'); + } + }, + ]); + } + function PlayDuringSpeedChange() { + if (constants.autoplayId != null) { + constants.KillAutoplay(); + if (lastPlayed == 'reverse-left') { + Autoplay('right', position.x, lastx); + } else if (lastPlayed == 'reverse-right') { + Autoplay('left', position.x, lastx); + } else { + Autoplay(lastPlayed, position.x, lastx); + } + } + } + + // lock to min / max postions + function lockPosition() { + let didLockHappen = false; + + if (position.x < 0) { + position.x = 0; + didLockHappen = true; + } + if (position.x > plot.plotData.length - 1) { + position.x = plot.plotData.length - 1; + didLockHappen = true; + } + + return didLockHappen; + } + function UpdateAll() { + if (constants.showDisplay) { + display.displayValues(); + } + if (constants.showRect && constants.hasRect) { + plot.Select(); + } + if (constants.sonifMode != 'off') { + plot.PlayTones(); + } + } + function UpdateAllAutoplay() { + if (constants.showDisplayInAutoplay) { + display.displayValues(); + } + if (constants.showRect && constants.hasRect) { + plot.Select(); + } + if (constants.sonifMode != 'off') { + plot.PlayTones(); + } + + if (constants.brailleMode != 'off') { + display.UpdateBraillePos(); + } + } + function UpdateAllBraille() { + if (constants.showDisplayInBraille) { + display.displayValues(); + } + if (constants.showRect && constants.hasRect) { + plot.Select(); + } + if (constants.sonifMode != 'off') { + plot.PlayTones(); + } + display.UpdateBraillePos(); + } + function Autoplay(dir, start, end) { + lastPlayed = dir; + let step = 1; // default right and reverse-left + if (dir == 'left' || dir == 'reverse-right') { + step = -1; + } + + // clear old autoplay if exists + if (constants.autoplayId != null) { + constants.KillAutoplay(); + } + + if (dir == 'reverse-right' || dir == 'reverse-left') { + position.x = start; + } + + constants.autoplayId = setInterval(function () { + position.x += step; + if (position.x < 0 || plot.plotData.length - 1 < position.x) { + constants.KillAutoplay(); + lockPosition(); + } else if (position.x == end) { + constants.KillAutoplay(); + UpdateAllAutoplay(); + } else { + UpdateAllAutoplay(); + } + }, constants.autoPlayRate); + } + } else if ( + [].concat(singleMaidr.type).includes('stacked_bar') || + [].concat(singleMaidr.type).includes('stacked_normalized_bar') || + [].concat(singleMaidr.type).includes('dodged_bar') + ) { + window.position = new Position(-1, -1); + window.plot = new Segmented(); + + // global variables + let lastPlayed = ''; + constants.lastx = 0; + + // control eventlisteners + constants.events.push([ + [constants.chart, constants.brailleInput], + 'keydown', + function (e) { + let updateInfoThisRound = false; // we only update info and play tones on certain keys + let isAtEnd = false; + constants.navigation = 0; // 0 for up/down, 1 for left/right + + if (constants.brailleMode == 'on') { + if (e.key == 'Tab') { + // allow + } else { + e.preventDefault(); + } + } + + // Right + if ( + e.key == 'ArrowRight' && + !(constants.isMac ? e.metaKey : e.ctrlKey) && + !e.shiftKey + ) { + // just right arrow, move right + position.x += 1; + updateInfoThisRound = true; + constants.navigation = 1; + isAtEnd = lockPosition(); + } else if ( + e.key == 'ArrowRight' && + (constants.isMac ? e.metaKey : e.ctrlKey) && + e.shiftKey + ) { + // ctrl shift right arrow, autoplay right + position.x -= 1; + Autoplay('right', position.x, plot.plotData.length); + } else if ( + e.key == 'ArrowRight' && + !(constants.isMac ? e.metaKey : e.ctrlKey) && + e.altKey && + e.shiftKey + ) { + // alt shift right, autoplay from right + constants.lastx = position.x; + Autoplay('reverse-right', plot.plotData.length, position.x); + } else if ( + e.key == 'ArrowRight' && + (constants.isMac ? e.metaKey : e.ctrlKey) && + !e.shiftKey + ) { + // ctrl right arrow, go to end + position.x = plot.plotData.length - 1; + updateInfoThisRound = true; + isAtEnd = lockPosition(); + } + + // Left + if ( + e.key == 'ArrowLeft' && + !(constants.isMac ? e.metaKey : e.ctrlKey) && + !e.shiftKey + ) { + // just left arrow, move left + position.x += -1; + updateInfoThisRound = true; + constants.navigation = 1; + isAtEnd = lockPosition(); + } else if ( + e.key == 'ArrowLeft' && + (constants.isMac ? e.metaKey : e.ctrlKey) && + e.shiftKey + ) { + // ctrl shift left arrow, autoplay left + position.x += 1; + Autoplay('left', position.x, -1); + } else if ( + e.key == 'ArrowLeft' && + !(constants.isMac ? e.metaKey : e.ctrlKey) && + e.altKey && + e.shiftKey + ) { + // alt shift left, autoplay from left + constants.lastx = position.x; + Autoplay('reverse-left', -1, position.x); + } else if ( + e.key == 'ArrowLeft' && + (constants.isMac ? e.metaKey : e.ctrlKey) && + !e.shiftKey + ) { + // ctrl left arrow, go to beginning + position.x = 0; + updateInfoThisRound = true; + isAtEnd = lockPosition(); + } + + // Up + if ( + e.key == 'ArrowUp' && + !(constants.isMac ? e.metaKey : e.ctrlKey) && + !e.shiftKey + ) { + // just up arrow, move up + position.y += 1; + updateInfoThisRound = true; + constants.navigation = 0; + isAtEnd = lockPosition(); + } else if ( + e.key == 'ArrowUp' && + (constants.isMac ? e.metaKey : e.ctrlKey) && + e.shiftKey + ) { + // ctrl shift up arrow, autoplay up + Autoplay('up', position.y, plot.plotData[0].length); + } else if ( + e.key == 'ArrowUp' && + !(constants.isMac ? e.metaKey : e.ctrlKey) && + e.altKey && + e.shiftKey + ) { + // alt shift up, autoplay from up + constants.lastx = position.x; + Autoplay('reverse-up', -1, plot.plotData[0].length); + } else if ( + e.key == 'ArrowUp' && + (constants.isMac ? e.metaKey : e.ctrlKey) && + !e.shiftKey + ) { + // ctrl up arrow, go to top + position.y = plot.plotData[0].length - 1; + updateInfoThisRound = true; + } + + // Down + if ( + e.key == 'ArrowDown' && + !(constants.isMac ? e.metaKey : e.ctrlKey) && + !e.shiftKey + ) { + // just down arrow, move down + position.y += -1; + updateInfoThisRound = true; + constants.navigation = 0; + isAtEnd = lockPosition(); + } else if ( + e.key == 'ArrowDown' && + (constants.isMac ? e.metaKey : e.ctrlKey) && + e.shiftKey + ) { + // ctrl shift down arrow, autoplay down + Autoplay('down', position.y, -1); + } else if ( + e.key == 'ArrowDown' && + !(constants.isMac ? e.metaKey : e.ctrlKey) && + e.altKey && + e.shiftKey + ) { + // alt shift down, autoplay from down + constants.lastx = position.x; + Autoplay('reverse-down', -1, position.y); + } else if ( + e.key == 'ArrowDown' && + (constants.isMac ? e.metaKey : e.ctrlKey) && + !e.shiftKey + ) { + // ctrl down arrow, go to bottom + position.y = 0; + updateInfoThisRound = true; + } + + // update display / text / audio + if (updateInfoThisRound && !isAtEnd) { + if (constants.brailleMode == 'off') { + UpdateAll(); + } else { + UpdateAllBraille(); + } + } + if (isAtEnd) { + audio.playEnd(); + } + }, + ]); + + let controlElements = [constants.chart, constants.brailleInput]; + let lastx = 0; + for (let i = 0; i < controlElements.length; i++) { + constants.events.push([ + controlElements[i], + 'keydown', + function (e) { + // period: speed up + if (e.key == '.') { + constants.SpeedUp(); + PlayDuringSpeedChange(); + display.announceText('Speed up'); + } + + // comma: speed down + if (e.key == ',') { + constants.SpeedDown(); + PlayDuringSpeedChange(); + display.announceText('Speed down'); } - // if you're only hitting control - if (!e.shiftKey) { - audio.KillSmooth(); + // /: reset speed + if (e.key == '/') { + constants.SpeedReset(); + PlayDuringSpeedChange(); + display.announceText('Speed reset'); + } + }, + ]); + } + function PlayDuringSpeedChange() { + if (constants.autoplayId != null) { + constants.KillAutoplay(); + if (lastPlayed == 'reverse-left') { + Autoplay('right', position.x, lastx); + } else if (lastPlayed == 'reverse-right') { + Autoplay('left', position.x, lastx); + } else if (lastPlayed == 'reverse-up') { + Autoplay('down', position.x, lastx); + } else if (lastPlayed == 'reverse-down') { + Autoplay('up', position.x, lastx); + } else { + Autoplay(lastPlayed, position.x, lastx); + } + } + } + + // lock to min / max postions + function lockPosition() { + let didLockHappen = false; + + if (position.x < 0) { + position.x = 0; + didLockHappen = true; + } + if (position.x > plot.level.length - 1) { + position.x = plot.plotData.length - 1; + didLockHappen = true; + } + if (position.y < 0) { + position.y = 0; + didLockHappen = true; + } + if (position.y > plot.fill.length - 1) { + position.y = plot.fill.length - 1; + didLockHappen = true; + } + + return didLockHappen; + } + function UpdateAll() { + if (constants.showDisplay) { + display.displayValues(); + } + if (constants.showRect && constants.hasRect) { + plot.Select(); + } + if (constants.sonifMode != 'off') { + plot.PlayTones(); + } + } + function UpdateAllAutoplay() { + if (constants.showDisplayInAutoplay) { + display.displayValues(); + } + if (constants.showRect && constants.hasRect) { + plot.Select(); + } + if (constants.sonifMode != 'off') { + plot.PlayTones(); + } + + if (constants.brailleMode != 'off') { + display.UpdateBraillePos(); + } + } + function UpdateAllBraille() { + if (constants.showDisplayInBraille) { + display.SetBraille(); + display.displayValues(); + } + if (constants.showRect && constants.hasRect) { + plot.Select(); + } + if (constants.sonifMode != 'off') { + plot.PlayTones(); + } + + display.UpdateBraillePos(); + } + function Autoplay(dir, start, end) { + lastPlayed = dir; + let step = 1; // default right, up, reverse-left, and reverse-down + if ( + dir == 'left' || + dir == 'down' || + dir == 'reverse-right' || + dir == 'reverse-up' + ) { + step = -1; + } + + // clear old autoplay if exists + if (constants.autoplayId != null) { + constants.KillAutoplay(); + } + + if (dir == 'reverse-left' || dir == 'reverse-right') { + position.x = start; + } else if (dir == 'reverse-up' || dir == 'reverse-down') { + position.y = start; + } + + constants.autoplayId = setInterval(function () { + if ( + dir == 'left' || + dir == 'right' || + dir == 'reverse-left' || + dir == 'reverse-right' + ) { + position.x += step; + if (position.x < 0 || plot.plotData.length - 1 < position.x) { + constants.KillAutoplay(); + lockPosition(); + } else if (position.x == end) { + constants.KillAutoplay(); + UpdateAllAutoplay(); + } else { + UpdateAllAutoplay(); + } + } else { + // up or down + position.y += step; + if (position.y < 0 || plot.plotData[0].length - 1 < position.y) { + constants.KillAutoplay(); + lockPosition(); + } else if (position.y == end) { + constants.KillAutoplay(); + UpdateAllAutoplay(); + } else { + UpdateAllAutoplay(); } } + }, constants.autoPlayRate); + } + } else if (singleMaidr.type == 'line') { + window.position = new Position(-1, -1); + window.plot = new LinePlot(); + let point = new Point(); + + // global variables + let lastPlayed = ''; + constants.lastx = 0; + + // control eventlisteners + constants.events.push([ + constants.chart, + 'keydown', + function (e) { + let updateInfoThisRound = false; // we only update info and play tones on certain keys + let isAtEnd = false; - // keys = (keys || []); - // keys[e.keyCode] = true; - // // lx: x label, ly: y label, lt: title, lf: fill - // if (keys[76] && keys[88]) { // lx - // display.displayXLabel(plot); - // } + if (e.key == 'ArrowRight') { + if (constants.isMac ? e.metaKey : e.ctrlKey) { + if (e.shiftKey) { + position.x -= 1; + Autoplay('right', position.x, plot.pointValuesY.length); + } else { + position.x = plot.pointValuesY.length - 1; // go all the way + updateInfoThisRound = true; + isAtEnd = lockPosition(); + } + } else if ( + e.altKey && + e.shiftKey && + position.x != plot.pointValuesY.length - 1 + ) { + constants.lastx = position.x; + Autoplay('reverse-right', plot.pointValuesY.length, position.x); + } else { + position.x += 1; + updateInfoThisRound = true; + isAtEnd = lockPosition(); + } + } else if (e.key == 'ArrowLeft') { + // var prevLink = document.getElementById('prev'); // what is prev in the html? + // if (prevLink) { + // left arrow 37 + if (constants.isMac ? e.metaKey : e.ctrlKey) { + if (e.shiftKey) { + position.x += 1; + Autoplay('left', position.x, -1); + } else { + position.x = 0; // go all the way + updateInfoThisRound = true; + isAtEnd = lockPosition(); + } + } else if (e.altKey && e.shiftKey && position.x != 0) { + constants.lastx = position.x; + Autoplay('reverse-left', -1, position.x); + } else { + position.x += -1; + updateInfoThisRound = true; + isAtEnd = lockPosition(); + } + // } + } - // if (keys[76] && keys[89]) { // ly - // display.displayYLabel(plot); - // } + // update display / text / audio + if (updateInfoThisRound && !isAtEnd) { + UpdateAll(); + } + if (isAtEnd) { + audio.playEnd(); + } + }, + ]); - // if (keys[76] && keys[84]) { // lt - // display.displayTitle(plot); - // } + constants.events.push([ + constants.brailleInput, + 'keydown', + function (e) { + let updateInfoThisRound = false; // we only update info and play tones on certain keys + let isAtEnd = false; - if (pressedL) { - if (e.which == 88) { - // X: x label - let timediff = window.performance.now() - lastKeyTime; - if (pressedL && timediff <= constants.keypressInterval) { - display.displayXLabel(plot); - } - pressedL = false; - } else if (e.which == 89) { - // Y: y label - let timediff = window.performance.now() - lastKeyTime; - if (pressedL && timediff <= constants.keypressInterval) { - display.displayYLabel(plot); + if (e.key == 'ArrowRight') { + // right arrow + e.preventDefault(); + if (e.target.selectionStart > e.target.value.length - 2) { + } else if (constants.isMac ? e.metaKey : e.ctrlKey) { + if (e.shiftKey) { + position.x -= 1; + Autoplay('right', position.x, plot.pointValuesY.length); + } else { + position.x = plot.pointValuesY.length - 1; // go all the way + updateInfoThisRound = true; + isAtEnd = lockPosition(); } - pressedL = false; - } else if (e.which == 84) { - // T: title - let timediff = window.performance.now() - lastKeyTime; - if (pressedL && timediff <= constants.keypressInterval) { - display.displayTitle(plot); + } else if ( + e.altKey && + e.shiftKey && + position.x != plot.pointValues.length - 1 + ) { + constants.lastx = position.x; + Autoplay('reverse-right', plot.pointValuesY.length, position.x); + } else { + position.x += 1; + updateInfoThisRound = true; + isAtEnd = lockPosition(); + } + } else if (e.key == 'ArrowLeft') { + // left arrow + e.preventDefault(); + if (constants.isMac ? e.metaKey : e.ctrlKey) { + if (e.shiftKey) { + position.x += 1; + Autoplay('left', position.x, -1); + } else { + position.x = 0; // go all the way + updateInfoThisRound = true; + isAtEnd = lockPosition(); } - pressedL = false; - } else if (e.which == 76) { - lastKeyTime = window.performance.now(); - pressedL = true; + } else if (e.altKey && e.shiftKey && position.x != 0) { + constants.lastx = position.x; + Autoplay('reverse-left', -1, position.x); } else { - pressedL = false; + position.x += -1; + updateInfoThisRound = true; + isAtEnd = lockPosition(); } + } else if (e.key == 'Tab') { + // do nothing, we handle this in global events + } else { + e.preventDefault(); } - // L: prefix for label - if (e.which == 76) { - lastKeyTime = window.performance.now(); - pressedL = true; + // update display / text / audio + if (updateInfoThisRound && !isAtEnd) { + UpdateAllBraille(); + } + if (isAtEnd) { + audio.playEnd(); } + }, + ]); - // period: speed up - if (e.which == 190) { - constants.SpeedUp(); - if (constants.autoplayId != null) { - constants.KillAutoplay(); - audio.KillSmooth(); - if (lastPlayed == 'inward_left') { - if (constants.chartType == 'scatter') { - Autoplay('outward_right', position.x, lastx); - } else if (constants.chartType == 'line') { - Autoplay('outward_right', positionL1.x, lastx1); - } - } else if (lastPlayed == 'inward_right') { - if (constants.chartType == 'scatter') { - Autoplay('outward_left', position.x, lastx); - } else if (constants.chartType == 'line') { - Autoplay('outward_left', positionL1.x, lastx1); - } - } else { - if (constants.chartType == 'scatter') { - Autoplay(lastPlayed, position.x, lastx); - } else if (constants.chartType == 'line') { - Autoplay(lastPlayed, positionL1.x, lastx1); - } - } + // control eventlisteners + // constants.events.push([ + // [constants.chart, constants.brailleInput], + // 'keydown', + // function (e) { + // let updateInfoThisRound = false; // we only update info and play tones on certain keys + // let isAtEnd = false; + + // if (e.key == 'ArrowRight') { + // // right arrow + // e.preventDefault(); + // if (e.target.selectionStart > e.target.value.length - 2) { + // } else if (constants.isMac ? e.metaKey : e.ctrlKey) { + // if (e.shiftKey) { + // position.x -= 1; + // Autoplay('right', position.x, plot.pointValuesY.length); + // } else { + // position.x = plot.pointValuesY.length - 1; // go all the way + // updateInfoThisRound = true; + // isAtEnd = lockPosition(); + // } + // } else if ( + // e.altKey && + // e.shiftKey && + // position.x != plot.pointValuesY.length - 1 + // ) { + // constants.lastx = position.x; + // Autoplay('reverse-right', plot.pointValuesY.length, position.x); + // } else { + // position.x += 1; + // updateInfoThisRound = true; + // isAtEnd = lockPosition(); + // } + // } else if (e.key == 'ArrowLeft') { + // // left arrow + // e.preventDefault(); + // if (constants.isMac ? e.metaKey : e.ctrlKey) { + // if (e.shiftKey) { + // position.x += 1; + // Autoplay('left', position.x, -1); + // } else { + // position.x = 0; // go all the way + // updateInfoThisRound = true; + // isAtEnd = lockPosition(); + // } + // } else if (e.altKey && e.shiftKey && position.x != 0) { + // constants.lastx = position.x; + // Autoplay('reverse-left', -1, position.x); + // } else { + // position.x += -1; + // updateInfoThisRound = true; + // isAtEnd = lockPosition(); + // } + // } else if (e.key == 'Tab') { + // // do nothing, we handle this in global events + // } else { + // e.preventDefault(); + // // Right + // if ( + // e.key == 'ArrowRight' && + // !(constants.isMac ? e.metaKey : e.ctrlKey) && + // !e.shiftKey + // ) { + // // just right arrow, move right + // position.x += 1; + // updateInfoThisRound = true; + // isAtEnd = lockPosition(); + // } else if ( + // e.key == 'ArrowRight' && + // (constants.isMac ? e.metaKey : e.ctrlKey) && + // e.shiftKey + // ) { + // // ctrl shift right arrow, autoplay right + // position.x += -1; + // Autoplay('outward_right', position.x, plot.pointValuesY.length); + // } else if ( + // e.key == 'ArrowRight' && + // !(constants.isMac ? e.metaKey : e.ctrlKey) && + // e.altKey && + // e.shiftKey && + // position.x != plot.pointValuesY.length - 1 + // ) { + // // alt shift right, autoplay from right + // constants.lastx = position.x; + // Autoplay('inward_right', plot.pointValues.length, position.x); + // } else if ( + // e.key == 'ArrowRight' && + // (constants.isMac ? e.metaKey : e.ctrlKey) && + // !e.shiftKey + // ) { + // // ctrl right arrow, go to end + // position.x = plot.pointValuesY.length - 1; // go all the way + // updateInfoThisRound = true; + // isAtEnd = lockPosition(); + // } + + // // Left + // if ( + // e.key == 'ArrowLeft' && + // !(constants.isMac ? e.metaKey : e.ctrlKey) && + // !e.shiftKey + // ) { + // // just left arrow, move left + // position.x += -1; + // updateInfoThisRound = true; + // isAtEnd = lockPosition(); + // } else if ( + // e.key == 'ArrowLeft' && + // (constants.isMac ? e.metaKey : e.ctrlKey) && + // e.shiftKey + // ) { + // // ctrl shift left arrow, autoplay left + // position.x += 1; + // Autoplay('outward_left', position.x, -1); + // } else if ( + // e.key == 'ArrowLeft' && + // !(constants.isMac ? e.metaKey : e.ctrlKey) && + // e.altKey && + // e.shiftKey + // ) { + // // alt shift left, autoplay from left + // constants.lastx = position.x; + // Autoplay('inward_left', -1, position.x); + // } else if ( + // e.key == 'ArrowLeft' && + // (constants.isMac ? e.metaKey : e.ctrlKey) && + // !e.shiftKey + // ) { + // // ctrl left arrow, go to beginning + // position.x = 0; // go all the way + // updateInfoThisRound = true; + // isAtEnd = lockPosition(); + // } + + // // update display / text / audio + // if (updateInfoThisRound && !isAtEnd) { + // if (constants.brailleMode == 'off') { + // UpdateAll(); + // } else { + // UpdateAllBraille(); + // } + // } + // if (isAtEnd) { + // audio.playEnd(); + // } + // } + // }, + // ]); + + let controlElements = [constants.chart, constants.brailleInput]; + let lastx = 0; + for (let i = 0; i < controlElements.length; i++) { + constants.events.push([ + controlElements[i], + 'keydown', + function (e) { + // period: speed up + if (e.key == '.') { + constants.SpeedUp(); + PlayDuringSpeedChange(); + display.announceText('Speed up'); } - } - // comma: speed down - if (e.which == 188) { - constants.SpeedDown(); - if (constants.autoplayId != null) { - constants.KillAutoplay(); - audio.KillSmooth(); - if (lastPlayed == 'inward_left') { - if (constants.chartType == 'scatter') { - Autoplay('outward_right', position.x, lastx); - } else if (constants.chartType == 'line') { - Autoplay('outward_right', positionL1.x, lastx1); - } - } else if (lastPlayed == 'inward_right') { - if (constants.chartType == 'scatter') { - Autoplay('outward_left', position.x, lastx); - } else if (constants.chartType == 'line') { - Autoplay('outward_left', positionL1.x, lastx1); - } - } else { - if (constants.chartType == 'scatter') { - Autoplay(lastPlayed, position.x, lastx); - } else if (constants.chartType == 'line') { - Autoplay(lastPlayed, positionL1.x, lastx1); - } - } + // comma: speed down + if (e.key == ',') { + constants.SpeedDown(); + PlayDuringSpeedChange(); + display.announceText('Speed down'); + } + + // /: reset speed + if (e.key == '/') { + constants.SpeedReset(); + PlayDuringSpeedChange(); + display.announceText('Speed reset'); } + }, + ]); + } + function PlayDuringSpeedChange() { + if (constants.autoplayId != null) { + constants.KillAutoplay(); + if (lastPlayed == 'reverse-left') { + Autoplay('right', position.x, lastx); + } else if (lastPlayed == 'reverse-right') { + Autoplay('left', position.x, lastx); + } else { + Autoplay(lastPlayed, position.x, lastx); } - }, - ]); + } + } - // helper functions function lockPosition() { - // lock to min / max positions - let isLockNeeded = false; - if (constants.chartType == 'scatter') { - if (position.x < 0) { - position.x = 0; - isLockNeeded = true; - } - if (position.x > plot.x.length - 1) { - position.x = plot.x.length - 1; - isLockNeeded = true; - } - } else if (constants.chartType == 'line') { - if (positionL1.x < 0) { - positionL1.x = 0; - isLockNeeded = true; - } - if (positionL1.x > plot.curvePoints.length - 1) { - positionL1.x = plot.curvePoints.length - 1; - isLockNeeded = true; - } + // lock to min / max postions + let didLockHappen = false; + // if (!constants.hasRect) { + // return didLockHappen; + // } + + if (position.x < 0) { + position.x = 0; + didLockHappen = true; + } + if (position.x > plot.pointValuesY.length - 1) { + position.x = plot.pointValuesY.length - 1; + didLockHappen = true; } - return isLockNeeded; + return didLockHappen; } - function UpdateAll() { if (constants.showDisplay) { - display.displayValues(plot); + display.displayValues(); } if (constants.showRect) { - layer0Point.UpdatePointDisplay(); + point.UpdatePointDisplay(); } if (constants.sonifMode != 'off') { - plot.PlayTones(audio); + plot.PlayTones(); } } - function UpdateAllAutoplay() { if (constants.showDisplayInAutoplay) { - display.displayValues(plot); + display.displayValues(); } if (constants.showRect) { - if (constants.chartType == 'scatter') { - layer0Point.UpdatePointDisplay(); - } else { - layer1Point.UpdatePointDisplay(); - } + point.UpdatePointDisplay(); } if (constants.sonifMode != 'off') { - plot.PlayTones(audio); + plot.PlayTones(); } + if (constants.brailleMode != 'off') { - display.UpdateBraillePos(plot); + display.UpdateBraillePos(); } } function UpdateAllBraille() { if (constants.showDisplayInBraille) { - display.displayValues(plot); + display.displayValues(); } if (constants.showRect) { - layer1Point.UpdatePointDisplay(); + point.UpdatePointDisplay(); } if (constants.sonifMode != 'off') { - plot.PlayTones(audio); + plot.PlayTones(); } - display.UpdateBraillePos(plot); + display.UpdateBraillePos(); } - function Autoplay(dir, start, end) { lastPlayed = dir; - let step = 1; // default right and reverse left - if (dir == 'outward_left' || dir == 'inward_right') { + let step = 1; // default right and reverse-left + if (dir == 'left' || dir == 'reverse-right') { step = -1; } // clear old autoplay if exists - if (constants.autoplayId) { + if (constants.autoplayId != null) { constants.KillAutoplay(); } - if (constants.isSmoothAutoplay) { - audio.KillSmooth(); - } - if (dir == 'inward_left' || dir == 'inward_right') { + if (dir == 'reverse-right' || dir == 'reverse-left') { position.x = start; - position.L1x = start; } - if (constants.chartType == 'scatter') { - constants.autoplayId = setInterval(function () { - position.x += step; - // autoplay for two layers: point layer & line layer in braille - // plot.numPoints is not available anymore - if (position.x < 0 || position.x > plot.y.length - 1) { - constants.KillAutoplay(); - lockPosition(); - } else if (position.x == end) { - constants.KillAutoplay(); - UpdateAllAutoplay(); - } else { - UpdateAllAutoplay(); - } - }, constants.autoPlayRate); - } else if (constants.chartType == 'line') { - constants.autoplayId = setInterval(function () { - positionL1.x += step; - // autoplay for two layers: point layer & line layer in braille - // plot.numPoints is not available anymore - if ( - positionL1.x < 0 || - positionL1.x > plot.curvePoints.length - 1 - ) { - constants.KillAutoplay(); - lockPosition(); - } else if (positionL1.x == end) { - constants.KillAutoplay(); - UpdateAllAutoplay(); - } else { - UpdateAllAutoplay(); - } - }, constants.autoPlayRate); - } + constants.autoplayId = setInterval(function () { + position.x += step; + if (position.x < 0 || plot.pointValuesY.length - 1 < position.x) { + constants.KillAutoplay(); + lockPosition(); + } else if (position.x == end) { + constants.KillAutoplay(); + UpdateAllAutoplay(); + } else { + UpdateAllAutoplay(); + } + }, constants.autoPlayRate); } - function PlayLine(dir) { lastPlayed = dir; @@ -6284,7 +7846,7 @@ class Control { ); let x = positionL1.x < 0 ? 0 : positionL1.x; let duration = 0; - if (dir == 'outward_right') { + if (dir == 'right') { for (let i = x; i < plot.curvePoints.length; i++) { freqArr.push( audio.SlideBetween( @@ -6300,7 +7862,7 @@ class Control { duration = (Math.abs(plot.curvePoints.length - x) / plot.curvePoints.length) * 3; - } else if (dir == 'outward_left') { + } else if (dir == 'left') { for (let i = x; i >= 0; i--) { freqArr.push( audio.SlideBetween( @@ -6314,7 +7876,7 @@ class Control { } panningArr = [panPoint, -1]; duration = (Math.abs(x) / plot.curvePoints.length) * 3; - } else if (dir == 'inward_right') { + } else if (dir == 'reverse-right') { for (let i = plot.curvePoints.length - 1; i >= x; i--) { freqArr.push( audio.SlideBetween( @@ -6330,7 +7892,7 @@ class Control { duration = (Math.abs(plot.curvePoints.length - x) / plot.curvePoints.length) * 3; - } else if (dir == 'inward_left') { + } else if (dir == 'reverse-left') { for (let i = 0; i <= x; i++) { freqArr.push( audio.SlideBetween( @@ -6355,6 +7917,34 @@ class Control { } } } + + GetNextPrevFocusable(nextprev = 'next') { + // store all focusable elements for future tabbing away from chart + let focusableSelectors = + 'a[href], button:not([disabled]), textarea:not([disabled]), input[type="text"]:not([disabled]), input[type="radio"]:not([disabled]), input[type="checkbox"]:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'; + constants.focusables = Array.from( + document.querySelectorAll(focusableSelectors) + ); + + // get index of chart in focusables + let chartIndex = constants.focusables.indexOf(constants.chart); + + // remove all the stuff we add manually from focusables + let maidrFocusables = + constants.main_container.querySelectorAll(focusableSelectors); + for (let i = 0; i < maidrFocusables.length; i++) { + let index = constants.focusables.indexOf(maidrFocusables[i]); + if (index > -1) { + constants.focusables.splice(index, 1); + } + // and adjust chartIndex + if (chartIndex > index) { + chartIndex--; + } + } + + // now we get next / prev based on chartIndex. If DNE, return null + } } // events and init functions @@ -6365,8 +7955,8 @@ document.addEventListener('DOMContentLoaded', function (e) { // create global vars window.constants = new Constants(); window.resources = new Resources(); - window.menu = new Menu(); window.tracker = new Tracker(); + window.logError = new LogError(); // set focus events for all charts matching maidr ids let maidrObjects = []; @@ -6405,10 +7995,8 @@ document.addEventListener('DOMContentLoaded', function (e) { // reset tracking with Ctrl + F5 / command + F5, and Ctrl + Shift + R / command + Shift + R // future todo: this should probably be a button with a confirmation. This is dangerous if ( - (e.which == 116 && (constants.isMac ? e.metaKey : e.ctrlKey)) || - (e.which == 82 && - e.shiftKey && - (constants.isMac ? e.metaKey : e.ctrlKey)) + (e.key == 'F5' && (constants.isMac ? e.metaKey : e.ctrlKey)) || + (e.key == 'R' && (constants.isMac ? e.metaKey : e.ctrlKey)) ) { e.preventDefault(); tracker.Delete(); @@ -6417,7 +8005,7 @@ document.addEventListener('DOMContentLoaded', function (e) { // Tracker if (constants.isTracking) { - if (e.which == 121) { + if (e.key == 'F10') { //tracker.DownloadTrackerData(); } else { if (plot) { @@ -6428,29 +8016,12 @@ document.addEventListener('DOMContentLoaded', function (e) { // Stuff to only run if we're on a chart (so check if the info div exists?) if (document.getElementById('info')) { - // Kill autoplay - if (constants.isMac ? e.which == 91 || e.which == 93 : e.which == 17) { - // ctrl (either one) - constants.KillAutoplay(); - } - - // Review mode - if (e.which == 82 && !e.ctrlKey && !e.shiftKey && !e.altKey) { - // R, but let Ctrl etc R go through cause I use that to refresh - e.preventDefault(); - if (constants.review_container.classList.contains('hidden')) { - review.ToggleReviewMode(true); - } else { - review.ToggleReviewMode(false); - } - } } }); } }); function InitMaidr(thisMaidr) { - console.log('Initializing Maidr'); // just in case if (typeof constants != 'undefined') { // init vars and html @@ -6462,38 +8033,26 @@ function InitMaidr(thisMaidr) { constants.chartType = singleMaidr.type; } CreateChartComponents(singleMaidr); - window.control = new Control(); + window.control = new Control(); // this inits the plot window.review = new Review(); window.display = new Display(); - - // help menu events - constants.events.push([ - constants.chart_container, - 'keydown', - function (e) { - // Menu open - if (e.which == 72) { - // M(77) for menu, or H(72) for help? I don't like it - menu.Toggle(true); - } - }, - ]); + window.audio = new Audio(); // blur destruction events - constants.events.push([ - document.getElementById(singleMaidr.id), - 'blur', - ShouldWeDestroyMaidr, - ]); - - // add all events - for (let i = 0; i < constants.events.length; i++) { - constants.events[i][0].addEventListener( - constants.events[i][1], - constants.events[i][2] - ); + let controlElements = [ + constants.chart, + constants.brailleInput, + constants.review, + ]; + for (let i = 0; i < controlElements.length; i++) { + constants.events.push([controlElements[i], 'blur', ShouldWeDestroyMaidr]); } + // kill autoplay event + constants.events.push([document, 'keydown', KillAutoplayEvent]); + + this.SetEvents(); + // once everything is set up, announce the chart name (or title as a backup) to the user if ('name' in singleMaidr) { display.announceText(singleMaidr.name); @@ -6523,55 +8082,146 @@ function ShouldWeInitMaidr(thisMaidr) { } function ShouldWeDestroyMaidr(e) { - // conditions: we're not about to focus on any chart that is maidr enabled - // note: the case where we move from one maidr enabled chart to another is handled by ShouldWeInitMaidr + // conditions: we've tabbed away from the chart or any component - // timeout to delay blur event + // timeout to delay blur event. I forget why this is necessary, but it is setTimeout(() => { - let focusedElement = document.activeElement; - if (focusedElement.id) { - if (maidrIds.includes(focusedElement.id)) { - return; - } else if (focusedElement.id == constants.braille_input_id) { - return; - } else { - DestroyMaidr(); - } + if (constants.tabMovement == 0) { + // do nothing, this is an allowed move + // but also reset so we can leave later + constants.tabMovement = null; } else { - // we're focused somewhere on the page that doesn't have an id, which means not maidr, so destroy + if (constants.tabMovement == 1 || constants.tabMovement == -1) { + // move to before / after, and then destroy + FocusBeforeOrAfter(); + } DestroyMaidr(); } }, 0); } -function DestroyMaidr() { - console.log('Destroying Maidr'); +function FocusBeforeOrAfter() { + // Tab / forward + if (constants.tabMovement == 1) { + let focusTemp = document.createElement('div'); + focusTemp.setAttribute('tabindex', '0'); + constants.main_container.after(focusTemp); + focusTemp.focus(); + focusTemp.remove(); + } + // Shift + Tab / backward + else if (constants.tabMovement == -1) { + // create an element to focus on, add it before currentFocus, focus it, then remove it + let focusTemp = document.createElement('div'); + focusTemp.setAttribute('tabindex', '0'); + constants.main_container.before(focusTemp); + focusTemp.focus(); + focusTemp.remove(); + } +} +function DestroyMaidr() { // chart cleanup - if (constants.chartType == 'bar') { + if (constants.chartType == 'bar' || constants.chartType == 'hist') { plot.DeselectAll(); } // remove events for (let i = 0; i < constants.events.length; i++) { - constants.events[i][0].removeEventListener( - constants.events[i][1], - constants.events[i][2] - ); + if (Array.isArray(constants.events[i][0])) { + for (let j = 0; j < constants.events[i][0].length; j++) { + constants.events[i][0][j].removeEventListener( + constants.events[i][1], + constants.events[i][2] + ); + } + } else { + constants.events[i][0].removeEventListener( + constants.events[i][1], + constants.events[i][2] + ); + } + } + for (let i = 0; i < constants.postLoadEvents.length; i++) { + if (Array.isArray(constants.postLoadEvents[i][0])) { + for (let j = 0; j < constants.postLoadEvents[i][0].length; j++) { + constants.postLoadEvents[i][0][j].removeEventListener( + constants.postLoadEvents[i][1], + constants.postLoadEvents[i][2] + ); + } + } else { + constants.postLoadEvents[i][0].removeEventListener( + constants.postLoadEvents[i][1], + constants.postLoadEvents[i][2] + ); + } } constants.events = []; + constants.postLoadEvents = []; // remove global vars constants.chartId = null; constants.chartType = null; + constants.tabMovement = null; DestroyChartComponents(); window.review = null; window.display = null; window.control = null; window.plot = null; + window.audio = null; window.singleMaidr = null; } +function KillAutoplayEvent(e) { + // Kill autoplay + if ( + constants.isMac + ? e.key == 'Meta' || e.key == 'ContextMenu' + : e.key == 'Control' + ) { + // ctrl (either one) + constants.KillAutoplay(); + } +} + +function SetEvents() { + // add all events + for (let i = 0; i < constants.events.length; i++) { + if (Array.isArray(constants.events[i][0])) { + for (let j = 0; j < constants.events[i][0].length; j++) { + constants.events[i][0][j].addEventListener( + constants.events[i][1], + constants.events[i][2] + ); + } + } else { + constants.events[i][0].addEventListener( + constants.events[i][1], + constants.events[i][2] + ); + } + } + // add all post load events + // we delay adding post load events just a tick so the chart loads + setTimeout(function () { + for (let i = 0; i < constants.postLoadEvents.length; i++) { + if (Array.isArray(constants.postLoadEvents[i][0])) { + for (let j = 0; j < constants.postLoadEvents[i][0].length; j++) { + constants.postLoadEvents[i][0][j].addEventListener( + constants.postLoadEvents[i][1], + constants.postLoadEvents[i][2] + ); + } + } else { + constants.postLoadEvents[i][0].addEventListener( + constants.postLoadEvents[i][1], + constants.postLoadEvents[i][2] + ); + } + } + }, 100); +} function CreateChartComponents() { // init html stuff. aria live regions, braille input, etc @@ -6579,16 +8229,21 @@ function CreateChartComponents() { // core chart let chart = document.getElementById(singleMaidr.id); - // chart container, we create a parent of chart + // we create a structure with a main container, and a chart container + let main_container = document.createElement('div'); + main_container.id = constants.main_container_id; let chart_container = document.createElement('div'); chart_container.id = constants.chart_container_id; - // replace chart with chart container, and append chart to chart container + // update parents from just chart, to main container > chart container > chart + chart.parentNode.replaceChild(main_container, chart); + main_container.appendChild(chart); chart.parentNode.replaceChild(chart_container, chart); chart_container.appendChild(chart); chart.focus(); // focus used to be on chart and just got lost as we rearranged, so redo focus constants.chart = chart; constants.chart_container = chart_container; + constants.main_container = main_container; // braille input, pre sibling of chart container constants.chart_container.insertAdjacentHTML( @@ -6599,25 +8254,22 @@ function CreateChartComponents() { constants.braille_input_id + '" class="braille-input" type="text" size="' + constants.brailleDisplayLength + - '" />\n\n' + '" ' + + 'aria-brailleroledescription="" ' + // this kills the 2 char 'edit' that screen readers add + '/>\n\n' ); - // set destruction possibility on braille - constants.events.push([ - document.getElementById(constants.braille_input_id), - 'blur', - ShouldWeDestroyMaidr, - ]); - // info aria live, next sibling of chart container constants.chart_container.insertAdjacentHTML( 'afterend', - '
\n

\n

\n

\n
\n' + '
\n
\n

\n

\n
\n' ); // announcements, next sibling of info document - .getElementById('info') + .getElementById(constants.info_id) .insertAdjacentHTML( 'afterend', '
\n' @@ -6631,6 +8283,18 @@ function CreateChartComponents() { '' ); + // review mode form field + document + .getElementById(constants.info_id) + .insertAdjacentHTML( + 'beforebegin', + '' + ); + // some tweaks constants.chart_container.setAttribute('role', 'application'); @@ -6645,6 +8309,16 @@ function CreateChartComponents() { ); constants.nonMenuFocus = constants.chart; constants.endChime = document.getElementById(constants.end_chime_id); + constants.review_container = document.querySelector( + '#' + constants.review_id_container + ); + constants.review = document.querySelector('#' + constants.review_id); + + // help menu + window.menu = new Menu(); + + // Description modal + window.description = new Description(); // developement on hold } function DestroyChartComponents() { @@ -6672,6 +8346,16 @@ function DestroyChartComponents() { if (constants.endChime != null) { constants.endChime.remove(); } + if (constants.review_container != null) { + constants.review_container.remove(); + } + + if (typeof menu != 'undefined') { + menu.Destroy(); + } + if (typeof description != 'undefined') { + description.Destroy(); + } constants.chart = null; constants.chart_container = null; @@ -6680,4 +8364,7 @@ function DestroyChartComponents() { constants.infoDiv = null; constants.announceContainer = null; constants.endChime = null; + constants.review_container = null; + menu = null; + description = null; } diff --git a/dist/maidr.min.js b/dist/maidr.min.js index 486318fb..43daf67a 100644 --- a/dist/maidr.min.js +++ b/dist/maidr.min.js @@ -1 +1 @@ -class Constants{chart_container_id="chart-container";braille_container_id="braille-div";braille_input_id="braille-input";info_id="info";announcement_container_id="announcements";end_chime_id="end_chime";container_id="container";project_id="maidr";review_id_container="review_container";review_id="review";reviewSaveSpot;reviewSaveBrailleMode;chartId="";events=[];constructor(){}textMode="verbose";brailleMode="off";sonifMode="on";reviewMode="off";minX=0;maxX=0;minY=0;maxY=0;plotId="";chartType="";navigation=1;MAX_FREQUENCY=1e3;MIN_FREQUENCY=200;NULL_FREQUENCY=100;MAX_SPEED=2e3;MIN_SPEED=50;INTERVAL=50;vol=.5;MAX_VOL=30;autoPlayRate=250;colorSelected="#03C809";brailleDisplayLength=32;showRect=1;hasRect=1;duration=.3;outlierDuration=.06;autoPlayOutlierRate=50;autoPlayPointsRate=30;colorUnselected="#595959";isTracking=1;visualBraille=!1;showDisplay=1;showDisplayInBraille=1;showDisplayInAutoplay=0;outlierInterval=null;isMac=navigator.userAgent.toLowerCase().includes("mac");control=this.isMac?"Cmd":"Ctrl";alt=this.isMac?"option":"Alt";home=this.isMac?"fn + Left arrow":"Home";end=this.isMac?"fn + Right arrow":"End";keypressInterval=2e3;debugLevel=3;canPlayEndChime=!1;manualData=!0;KillAutoplay(){this.autoplayId&&(clearInterval(this.autoplayId),this.autoplayId=null)}KillSepPlay(){this.sepPlayId&&(clearInterval(this.sepPlayId),this.sepPlayId=null)}SpeedUp(){constants.autoPlayRate-this.INTERVAL>this.MIN_SPEED&&(constants.autoPlayRate-=this.INTERVAL)}SpeedDown(){constants.autoPlayRate+this.INTERVAL<=this.MAX_SPEED&&(constants.autoPlayRate+=this.INTERVAL)}}class Resources{constructor(){}language="en";knowledgeLevel="basic";strings={en:{basic:{upper_outlier:"Upper Outlier",lower_outlier:"Lower Outlier",min:"Minimum",max:"Maximum",25:"25%",50:"50%",75:"75%",son_on:"Sonification on",son_off:"Sonification off",son_des:"Sonification descrete",son_comp:"Sonification compare",son_ch:"Sonification chord",son_sep:"Sonification separate",son_same:"Sonification combined",empty:"Empty"}}};GetString(t){return this.strings[this.language][this.knowledgeLevel][t]}}class Menu{whereWasMyFocus=null;constructor(){this.CreateMenu(),this.LoadDataFromLocalStorage()}menuHtml=`\n \n \n `;CreateMenu(){document.querySelector("body").insertAdjacentHTML("beforeend",this.menuHtml);let t=document.querySelectorAll("#close_menu, #menu .close");for(let e=0;e0&&console.log("tracking data cleared"),this.DataSetup()}LogEvent(t){let e={};if(e.timestamp=Object.assign(t.timeStamp),e.time=Date().toString(),e.key=Object.assign(t.key),e.which=Object.assign(t.which),e.altKey=Object.assign(t.altKey),e.ctrlKey=Object.assign(t.ctrlKey),e.shiftKey=Object.assign(t.shiftKey),t.path&&(e.focus=Object.assign(t.path[0].tagName)),this.isUndefinedOrNull(constants.position)||(e.position=Object.assign(constants.position)),this.isUndefinedOrNull(constants.minX)||(e.min_x=Object.assign(constants.minX)),this.isUndefinedOrNull(constants.maxX)||(e.max_x=Object.assign(constants.maxX)),this.isUndefinedOrNull(constants.minY)||(e.min_y=Object.assign(constants.minY)),this.isUndefinedOrNull(constants.MAX_FREQUENCY)||(e.max_frequency=Object.assign(constants.MAX_FREQUENCY)),this.isUndefinedOrNull(constants.MIN_FREQUENCY)||(e.min_frequency=Object.assign(constants.MIN_FREQUENCY)),this.isUndefinedOrNull(constants.NULL_FREQUENCY)||(e.null_frequency=Object.assign(constants.NULL_FREQUENCY)),this.isUndefinedOrNull(constants.MAX_SPEED)||(e.max_speed=Object.assign(constants.MAX_SPEED)),this.isUndefinedOrNull(constants.MIN_SPEED)||(e.min_speed=Object.assign(constants.MIN_SPEED)),this.isUndefinedOrNull(constants.INTERVAL)||(e.interval=Object.assign(constants.INTERVAL)),this.isUndefinedOrNull(constants.vol)||(e.volume=Object.assign(constants.vol)),this.isUndefinedOrNull(constants.autoPlayRate)||(e.autoplay_rate=Object.assign(constants.autoPlayRate)),this.isUndefinedOrNull(constants.colorSelected)||(e.color=Object.assign(constants.colorSelected)),this.isUndefinedOrNull(constants.brailleDisplayLength)||(e.braille_display_length=Object.assign(constants.brailleDisplayLength)),this.isUndefinedOrNull(constants.duration)||(e.tone_duration=Object.assign(constants.duration)),this.isUndefinedOrNull(constants.autoPlayOutlierRate)||(e.autoplay_outlier_rate=Object.assign(constants.autoPlayOutlierRate)),this.isUndefinedOrNull(constants.autoPlayPointsRate)||(e.autoplay_points_rate=Object.assign(constants.autoPlayPointsRate)),this.isUndefinedOrNull(constants.textMode)||(e.text_mode=Object.assign(constants.textMode)),this.isUndefinedOrNull(constants.sonifMode)||(e.sonification_mode=Object.assign(constants.sonifMode)),this.isUndefinedOrNull(constants.brailleMode)||(e.braille_mode=Object.assign(constants.brailleMode)),this.isUndefinedOrNull(constants.chartType)||(e.chart_type=Object.assign(constants.chartType)),!this.isUndefinedOrNull(constants.infoDiv.innerHTML)){let t=Object.assign(constants.infoDiv.innerHTML);t=t.replaceAll(/<[^>]*>?/gm,""),e.text_display=t}this.isUndefinedOrNull(location.href)||(e.location=Object.assign(location.href));let n="",o="",s="",i="",a="",l="";if("bar"==constants.chartType)this.isUndefinedOrNull(plot.columnLabels[position.x])||(n=plot.columnLabels[position.x]),this.isUndefinedOrNull(plot.plotLegend.x)||(s=plot.plotLegend.x),this.isUndefinedOrNull(plot.plotLegend.y)||(i=plot.plotLegend.y),this.isUndefinedOrNull(plot.plotData[position.x])||(a=plot.plotData[position.x]);else if("heat"==constants.chartType)this.isUndefinedOrNull(plot.x_labels[position.x])||(n=plot.x_labels[position.x].trim()),this.isUndefinedOrNull(plot.y_labels[position.y])||(o=plot.y_labels[position.y].trim()),this.isUndefinedOrNull(plot.x_group_label)||(s=plot.x_group_label),this.isUndefinedOrNull(plot.y_group_label)||(i=plot.y_group_label),this.isUndefinedOrNull(plot.values)||this.isUndefinedOrNull(plot.values[position.x][position.y])||(a=plot.values[position.x][position.y]),this.isUndefinedOrNull(plot.group_labels[2])||(l=plot.group_labels[2]);else if("box"==constants.chartType){let t="vert"==constants.plotOrientation?position.x:position.y,e="vert"==constants.plotOrientation?position.y:position.x;this.isUndefinedOrNull(plot.x_group_label)||(s=plot.x_group_label),this.isUndefinedOrNull(plot.y_group_label)||(i=plot.y_group_label),"vert"==constants.plotOrientation?t>-1&&e>-1&&(this.isUndefinedOrNull(plot.plotData[t][e].label)||(o=plot.plotData[t][e].label),this.isUndefinedOrNull(plot.x_labels[position.x])||(n=plot.x_labels[position.x]),this.isUndefinedOrNull(plot.plotData[t][e].values)?this.isUndefinedOrNull(plot.plotData[t][e].y)||(a=plot.plotData[t][e].y):a=plot.plotData[t][e].values):t>-1&&e>-1&&(this.isUndefinedOrNull(plot.plotData[t][e].label)||(n=plot.plotData[t][e].label),this.isUndefinedOrNull(plot.y_labels[position.y])||(o=plot.y_labels[position.y]),this.isUndefinedOrNull(plot.plotData[t][e].values)?this.isUndefinedOrNull(plot.plotData[t][e].x)||(a=plot.plotData[t][e].x):a=plot.plotData[t][e].values)}else"scatter"==constants.chartType&&(this.isUndefinedOrNull(plot.x_group_label)||(s=plot.x_group_label),this.isUndefinedOrNull(plot.y_group_label)||(i=plot.y_group_label),this.isUndefinedOrNull(plot.x[position.x])||(n=plot.x[position.x]),this.isUndefinedOrNull(plot.y[position.x])||(o=plot.y[position.x]),a=[n,o]);e.x_tickmark=Object.assign(n),e.y_tickmark=Object.assign(o),e.x_label=Object.assign(s),e.y_label=Object.assign(i),e.value=Object.assign(a),e.fill_value=Object.assign(l);let r=this.GetTrackerData();r.events.push(e),this.SaveTrackerData(r)}isUndefinedOrNull(t){try{return null==t}catch{return!0}}}class Review{constructor(){document.getElementById(constants.review_id)||document.getElementById(constants.info_id)&&document.getElementById(constants.info_id).insertAdjacentHTML("beforebegin",''),constants&&(constants.review_container=document.querySelector("#"+constants.review_id_container),constants.review=document.querySelector("#"+constants.review_id))}ToggleReviewMode(t=!0){t?(constants.reviewSaveSpot=document.activeElement,constants.review_container.classList.remove("hidden"),constants.reviewSaveBrailleMode=constants.brailleMode,constants.review.focus(),display.announceText("Review on")):(constants.review_container.classList.add("hidden"),"on"==constants.reviewSaveBrailleMode?display.toggleBrailleMode("on"):constants.reviewSaveSpot.focus(),display.announceText("Review off"))}}class Audio{constructor(){this.AudioContext=window.AudioContext||window.webkitAudioContext,this.audioContext=new AudioContext,this.compressor=this.compressorSetup(this.audioContext)}compressorSetup(){let t=this.audioContext.createDynamicsCompressor();t.threshold.value=-50,t.knee.value=40,t.ratio.value=12,t.attack.value=0,t.release.value=.25;let e=this.audioContext.createGain();return e.gain.value=constants.vol,t.connect(e),e.connect(this.audioContext.destination),t}playTone(){let t=constants.duration,e=constants.vol,n=0,o=0,s=0,i=0;if("bar"==constants.chartType)o=plot.plotData[position.x],n=position.x,s=this.SlideBetween(o,constants.minY,constants.maxY,constants.MIN_FREQUENCY,constants.MAX_FREQUENCY),i=this.SlideBetween(n,constants.minX,constants.maxX,-1,1);else if("box"==constants.chartType){let t="vert"==constants.plotOrientation?position.x:position.y,e="vert"==constants.plotOrientation?position.y:position.x;o=position.z>-1&&Object.hasOwn(plot.plotData[t][e],"values")?plot.plotData[t][e].values[position.z]:"vert"==constants.plotOrientation?plot.plotData[t][e].y:plot.plotData[t][e].x,"blank"!=plot.plotData[t][e].type?"vert"==constants.plotOrientation?(s=this.SlideBetween(o,constants.minY,constants.maxY,constants.MIN_FREQUENCY,constants.MAX_FREQUENCY),i=this.SlideBetween(o,constants.minY,constants.maxY,-1,1)):(s=this.SlideBetween(o,constants.minX,constants.maxX,constants.MIN_FREQUENCY,constants.MAX_FREQUENCY),i=this.SlideBetween(o,constants.minX,constants.maxX,-1,1)):(s=constants.MIN_FREQUENCY,i=0)}else"heat"==constants.chartType?(o=plot.values[position.y][position.x],n=position.x,s=this.SlideBetween(o,constants.minY,constants.maxY,constants.MIN_FREQUENCY,constants.MAX_FREQUENCY),i=this.SlideBetween(n,constants.minX,constants.maxX,-1,1)):"scatter"!=constants.chartType&&"line"!=constants.chartType||("scatter"==constants.chartType?(o=plot.y[position.x][position.z],e=1==plot.max_count?constants.vol:this.SlideBetween(plot.points_count[position.x][position.z],1,plot.max_count,constants.vol,constants.MAX_VOL),n=position.x,s=this.SlideBetween(o,constants.minY,constants.maxY,constants.MIN_FREQUENCY,constants.MAX_FREQUENCY),i=this.SlideBetween(n,constants.minX,constants.maxX,-1,1)):"line"==constants.chartType&&(o=plot.curvePoints[positionL1.x],n=positionL1.x,s=this.SlideBetween(o,plot.curveMinY,plot.curveMaxY,constants.MIN_FREQUENCY,constants.MAX_FREQUENCY),i=this.SlideBetween(n,constants.minX,constants.maxX,-1,1)));if(constants.debugLevel>5&&(console.log("will play tone at freq",s),"box"==constants.chartType?console.log("based on",constants.minY,"<",o,"<",constants.maxY," | freq min",constants.MIN_FREQUENCY,"max",constants.MAX_FREQUENCY):console.log("based on",constants.minX,"<",o,"<",constants.maxX," | freq min",constants.MIN_FREQUENCY,"max",constants.MAX_FREQUENCY)),"box"==constants.chartType){let e="vert"==constants.plotOrientation?position.x:position.y,n="vert"==constants.plotOrientation?position.y:position.x,o=plot.plotData[e][n].type;"outlier"==o&&(t=constants.outlierDuration)}if(this.playOscillator(s,t,i,e,"sine"),"box"==constants.chartType){let e="vert"==constants.plotOrientation?position.x:position.y,n="vert"==constants.plotOrientation?position.y:position.x;if("range"==plot.plotData[e][n].type){let e=s/2;this.playOscillator(e,t,i,constants.vol/4,"triangle")}}else"heat"==constants.chartType&&0==o&&this.PlayNull()}playOscillator(t,e,n,o=1,s="sine"){const i=this.audioContext.currentTime,a=this.audioContext.createOscillator();a.type=s,a.frequency.value=parseFloat(t),a.start();const l=this.audioContext.createGain();l.gain.setValueCurveAtTime([.5*o,1*o,.5*o,.5*o,.5*o,.1*o,1e-4*o],i,e);const r=new PannerNode(this.audioContext,{panningModel:"HRTF",distanceModel:"linear",positionX:position.x,positionY:position.y,positionZ:1,plotOrientationX:0,plotOrientationY:0,plotOrientationZ:-1,refDistance:1,maxDistance:1e4,rolloffFactor:10,coneInnerAngle:40,coneOuterAngle:50,coneOuterGain:.4}),c=this.audioContext.createStereoPanner();c.pan.value=n,a.connect(l),l.connect(c),c.connect(r),r.connect(this.compressor),setTimeout((()=>{r.disconnect(),l.disconnect(),a.stop(),a.disconnect()}),1e3*e*2)}playSmooth(t=[600,500,400,300],e=2,n=[-1,0,1],o=1,s="sine"){let i=new Array(3*t.length).fill(.5*o);i.push(1e-4*o);const a=this.audioContext.currentTime,l=this.audioContext.createOscillator();l.type=s,l.frequency.setValueCurveAtTime(t,a,e),l.start(),constants.isSmoothAutoplay=!0,this.smoothGain=this.audioContext.createGain(),this.smoothGain.gain.setValueCurveAtTime(i,a,e);const r=new PannerNode(this.audioContext,{panningModel:"HRTF",distanceModel:"linear",positionX:position.x,positionY:position.y,positionZ:1,plotOrientationX:0,plotOrientationY:0,plotOrientationZ:-1,refDistance:1,maxDistance:1e4,rolloffFactor:10,coneInnerAngle:40,coneOuterAngle:50,coneOuterGain:.4}),c=this.audioContext.createStereoPanner();c.pan.setValueCurveAtTime(n,a,e),l.connect(this.smoothGain),this.smoothGain.connect(c),c.connect(r),r.connect(this.compressor),constants.smoothId=setTimeout((()=>{r.disconnect(),this.smoothGain.disconnect(),l.stop(),l.disconnect(),constants.isSmoothAutoplay=!1}),1e3*e*2)}PlayNull(){console.log("playing null");let t=constants.NULL_FREQUENCY,e=constants.duration,n=constants.vol,o="triangle";this.playOscillator(t,e,0,n,o),setTimeout((function(s){s.playOscillator(23*t/24,e,0,n,o)}),Math.round(e/5*1e3),this)}playEnd(){if(constants.canPlayEndChime){let t=constants.endChime.cloneNode(!0);t.play(),t=null}}KillSmooth(){constants.smoothId&&(this.smoothGain.gain.cancelScheduledValues(0),this.smoothGain.gain.exponentialRampToValueAtTime(1e-4,this.audioContext.currentTime+.03),clearTimeout(constants.smoothId),constants.isSmoothAutoplay=!1)}SlideBetween(t,e,n,o,s){let i=(t-e)/(n-e)*(s-o)+o;return 0==e&&0==n&&(i=0),i}}class Display{constructor(){this.infoDiv=constants.infoDiv,this.x={},this.x.id="x",this.x.textBase="x-value: ",this.y={},this.y.id="y",this.y.textBase="y-value: ",this.boxplotGridPlaceholders=[resources.GetString("lower_outlier"),resources.GetString("min"),resources.GetString("25"),resources.GetString("50"),resources.GetString("75"),resources.GetString("max"),resources.GetString("upper_outlier")]}toggleTextMode(){"off"==constants.textMode?constants.textMode="terse":"terse"==constants.textMode?constants.textMode="verbose":"verbose"==constants.textMode&&(constants.textMode="off"),this.announceText(' '+constants.textMode)}toggleBrailleMode(t){if("scatter"!=constants.chartType){if(void 0===t&&(void 0===constants.brailleMode?(constants.brailleMode="off",t="on"==constants.brailleMode):(t="on"==constants.brailleMode?"off":"on",constants.brailleMode=t)),"on"==t){if("box"==constants.chartType&&("vert"!=constants.plotOrientation&&-1==position.x&&position.y==plot.plotData.length?(position.x+=1,position.y-=1):"vert"==constants.plotOrientation&&0==position.x&&(position.y,plot.plotData[0].length)),constants.brailleMode="on",document.getElementById(constants.braille_container_id).classList.remove("hidden"),constants.brailleInput.focus(),constants.brailleInput.setSelectionRange(position.x,position.x),this.SetBraille(plot),"heat"==constants.chartType){let t=position.y*(plot.num_cols+1)+position.x;constants.brailleInput.setSelectionRange(t,t)}-1==position.x&&-1==position.y&&constants.brailleInput.setSelectionRange(0,0)}else constants.brailleMode="off",document.getElementById(constants.braille_container_id).classList.add("hidden"),constants.review_container?constants.review_container.classList.contains("hidden")?constants.chart.focus():constants.review.focus():constants.chart.focus();this.announceText("Braille "+constants.brailleMode)}else this.announceText("Braille is not supported in point layer.")}toggleSonificationMode(){"scatter"==constants.chartType?"off"==constants.sonifMode?(constants.sonifMode="sep",this.announceText(resources.GetString("son_sep"))):"sep"==constants.sonifMode?(constants.sonifMode="same",this.announceText(resources.GetString("son_same"))):"same"==constants.sonifMode&&(constants.sonifMode="off",this.announceText(resources.GetString("son_off"))):"off"==constants.sonifMode?(constants.sonifMode="on",this.announceText(resources.GetString("son_on"))):(constants.sonifMode="off",this.announceText(resources.GetString("son_off")))}changeChartLayer(t="down"){let e=maidr.type;if(Array.isArray(e)){let n=e.indexOf(constants.chartType);"down"==t?0==n||(constants.chartType=e[n-1]):n==e.length-1||(constants.chartType=e[n+1]),this.announceText("Switched to "+constants.chartType)}}announceText(t){constants.announceContainer.innerHTML=t}UpdateBraillePos(){if("bar"==constants.chartType)constants.brailleInput.setSelectionRange(position.x,position.x);else if("heat"==constants.chartType){let t=position.y*(plot.num_cols+1)+position.x;constants.brailleInput.setSelectionRange(t,t)}else if("box"==constants.chartType){let t="vert"==constants.plotOrientation?position.y:position.x,e=this.boxplotGridPlaceholders[t],n=!1,o=0;if(!constants.brailleData)throw"Braille data not set up, cannot move cursor in braille, sorry.";for(let t=0;t0&&t.columnLabels[positionx]&&(n=t.plotLegend.x+" is "+t.columnLabels[position.x]),t.plotData[position.x]&&(n+=", "+t.plotLegend.y+" is "+t.plotData[position.x]),"off"==constants.textMode||("terse"==constants.textMode?e+="

"+t.columnLabels[position.x]+" "+t.plotData[position.x]+"

\n":"verbose"==constants.textMode&&(e+="

"+n+"

\n"));else if("heat"==constants.chartType)1==constants.navigation?(n+=t.x_group_label+" "+t.x_labels[position.x]+", "+t.y_group_label+" "+t.y_labels[position.y]+", "+t.box_label+" is ",constants.hasRect&&(n+=t.plotData[2][position.y][position.x])):(n+=t.y_group_label+" "+t.y_labels[position.y]+", "+t.x_group_label+" "+t.x_labels[position.x]+", "+t.box_label+" is ",constants.hasRect&&(n+=t.plotData[2][position.y][position.x])),"off"==constants.textMode||("terse"==constants.textMode?1==constants.navigation?e+="

"+t.x_labels[position.x]+", "+t.plotData[2][position.y][position.x]+"

\n":e+="

"+t.y_labels[position.y]+", "+t.plotData[2][position.y][position.x]+"

\n":"verbose"==constants.textMode&&(e+="

"+n+"

\n"));else if("box"==constants.chartType){let o=0,s=1,i=!1,a="vert"==constants.plotOrientation?position.x:position.y,l="vert"==constants.plotOrientation?position.y:position.x,r="",c="";"lower_outlier"!=t.plotData[a][l].label&&"upper_outlier"!=t.plotData[a][l].label||(i=!0),"outlier"==t.plotData[a][l].type?(o=t.plotData[a][l].values.join(", "),s=t.plotData[a][l].values.length>0?t.plotData[a][l].values.length:0):"blank"==t.plotData[a][l].type?(o="",i&&(s=0)):o="vert"==constants.plotOrientation?t.plotData[a][l].y:t.plotData[a][l].x,constants.navigation?t.x_group_label&&(c+=t.x_group_label):constants.navigation||t.y_group_label&&(c+=t.y_group_label),constants.navigation?t.x_labels[a]?(c+=" is ",r+=t.x_labels[a]+", ",c+=t.x_labels[a]+", "):c+=", ":constants.navigation||(t.y_labels[a]?(c+=" is ",r+=t.y_labels[a]+", ",c+=t.y_labels[a]+", "):c+=", "),i&&(r+=s+" ",c+=s+" "),c+=resources.GetString(t.plotData[a][l].label),1==s?c+=" is ":(c+="s ",s>1&&(c+=" are ")),(i||constants.navigation&&"horz"==constants.plotOrientation||!constants.navigation&&"vert"==constants.plotOrientation)&&(r+=resources.GetString(t.plotData[a][l].label),1!=s&&(r+="s"),r+=" "),"blank"!=t.plotData[a][l].type||i?(r+=o,c+=o):(r+="empty",c+="empty"),n=c,"verbose"==constants.textMode?e="

"+c+"

\n":"terse"==constants.textMode&&(e="

"+r+"

\n")}else"scatter"==constants.chartType&&("scatter"==constants.chartType?(n+=t.x_group_label+" "+t.x[position.x]+", "+t.y_group_label+" ["+t.y[position.x].join(", ")+"]","off"==constants.textMode||("terse"==constants.textMode?e+="

"+t.x[position.x]+", ["+t.y[position.x].join(", ")+"]

\n":constants.textMode)):"line"==constants.chartType&&(n+=t.x_group_label+" "+t.curveX[positionL1.x]+", "+t.y_group_label+" "+t.curvePoints[positionL1.x],"off"==constants.textMode||("terse"==constants.textMode?e+="

"+t.curvePoints[positionL1.x]+"

\n":constants.textMode)),"verbose"==constants.textMode&&(e="

"+n+"

\n"));constants.infoDiv&&(constants.infoDiv.innerHTML=e),constants.review&&(e.length>0?constants.review.value=e.replace(/<[^>]*>?/gm,""):constants.review.value=n)}displayXLabel(t){let e="";"bar"==constants.chartType?e=t.plotLegend.x:"heat"!=constants.chartType&&"box"!=constants.chartType&&"scatter"!=constants.chartType||(e=t.x_group_label),"terse"==constants.textMode?constants.infoDiv.innerHTML="

"+e+"

":"verbose"==constants.textMode&&(constants.infoDiv.innerHTML="

x label is "+e+"

")}displayYLabel(t){let e="";"bar"==constants.chartType?e=t.plotLegend.y:"heat"!=constants.chartType&&"box"!=constants.chartType&&"scatter"!=constants.chartType||(e=t.y_group_label),"terse"==constants.textMode?constants.infoDiv.innerHTML="

"+e+"

":"verbose"==constants.textMode&&(constants.infoDiv.innerHTML="

y label is "+e+"

")}displayTitle(t){"terse"==constants.textMode?""!=t.title?constants.infoDiv.innerHTML="

"+t.title+"

":constants.infoDiv.innerHTML="

Plot does not have a title.

":"verbose"==constants.textMode&&(""!=t.title?constants.infoDiv.innerHTML="

Title is "+t.title+"

":constants.infoDiv.innerHTML="

Plot does not have a title.

")}displayFill(t){"terse"==constants.textMode?"heat"==constants.chartType&&(constants.infoDiv.innerHTML="

"+t.box_label+"

"):"verbose"==constants.textMode&&"heat"==constants.chartType&&(constants.infoDiv.innerHTML="

Fill label is "+t.box_label+"

")}SetBraille(t){let e=[];if("heat"==constants.chartType){let n=(constants.maxY-constants.minY)/3,o=constants.minY+n,s=o+n;for(let n=0;n-1){let o=[],s=!0,i="vert"==constants.plotOrientation?position.x:position.y,a="vert"==constants.plotOrientation?"y":"x";for(let e=0;e0&&(c=t.plotData[i][e-1]);let p={};if(0==e){let e=0;for(let n=0;n0?e:0,p.length<0&&(p.length=0),p.type="blank",p.label="blank",o.push(p)}if("blank"==l.type);else if("outlier"==l.type){s||(p={},p.length=l.values[0]-c[a],p.type="blank",p.label="blank",o.push(p));for(var n=0;n0||"outlier"==o[t].type)?(o[t].numChars=1,h++):o[t].numChars=0,"min"==o[t].label&&o[t].length>0&&(l=t),"max"==o[t].label&&o[t].length>0&&(r=t),"25"==o[t].label&&(c=t),"75"==o[t].label&&(p=t),"50"==o[t].label&&(o[t].numChars=2,h++);let d=["25","75"];l>-1&&r>-1&&(d.push("min"),d.push("max"),o[l].length!=o[r].length&&(o[l].length>o[r].length?(o[l].numChars++,h++):(o[r].numChars++,h++))),o[c].length!=o[p].length&&(o[c].length>o[p].length?(o[c].numChars++,h++):(o[p].numChars++,h++));let u=constants.brailleDisplayLength-h,y=this.AllocateCharacters(o,u);for(let t=0;t5&&(console.log("plotData[i]",t.plotData[i]),console.log("brailleData",o));for(let t=0;t5&&console.log("braille:",constants.brailleInput.value),this.UpdateBraillePos()}CharLenImpact(t){return t.length/t.numChars}AllocateCharacters(t,e){let n=[],o=0;for(let e=0;et+e),0),a=e-i,l=t.length;for(;0!==a&&l>0;){for(let e=0;et+e),0),a=e-i,l--}if(0!==a){let e=[];for(let n=0;nn[t]-n[e]));let o=-1;a>0&&(o=1);let s=0,i=3*e.length;for(;a>0&&i>0;)n[e[s]]+=o,a+=-o,s+=1,s>=e.length&&(s=0),i+=-1}return n}}class BarChart{constructor(){"elements"in singleMaidr?(this.bars=singleMaidr.elements,constants.hasRect=1):(this.bars=constants.chart.querySelectorAll('g[id^="geom_rect"] > rect'),constants.hasRect=0),this.columnLabels=[];let t="",e="";"axis"in singleMaidr?(singleMaidr.axis.x&&singleMaidr.axis.x.label&&(t=singleMaidr.axis.x.label),singleMaidr.axis.y&&singleMaidr.axis.y.label&&(e=singleMaidr.axis.y.label),singleMaidr.axis.x&&singleMaidr.axis.x.format&&(this.columnLabels=singleMaidr.axis.x.format),singleMaidr.axis.y&&singleMaidr.axis.y.format&&(this.columnLabels=singleMaidr.axis.y.format)):(constants.chart.querySelector('g[id^="xlab"] tspan')&&(t=constants.chart.querySelector('g[id^="xlab"] tspan').innerHTML),constants.chart.querySelector('g[id^="ylab"] tspan')&&(e=constants.chart.querySelector('g[id^="ylab"] tspan').innerHTML),this.columnLabels=this.ParseInnerHTML(constants.chart.querySelectorAll('g:not([id^="xlab"]):not([id^="ylab"]) > g > g > g > text[text-anchor="middle"]'))),this.plotLegend={x:t,y:e},this.title="","title"in singleMaidr?this.title=singleMaidr.title:constants.chart.querySelector('g[id^="plot.title..titleGrob"] tspan')&&(this.title=constants.chart.querySelector('g[id^="plot.title..titleGrob"] tspan').innerHTML,this.title=this.title.replace("\n","").replace(/ +(?= )/g,"")),Array.isArray(singleMaidr)?this.plotData=singleMaidr:"data"in singleMaidr&&(this.plotData=singleMaidr.data),this.SetMaxMin(),this.autoplay=null}SetMaxMin(){for(let t=0;tconstants.maxY&&(constants.maxY=this.plotData[t]),this.plotData[t] g[id^="geom_boxplot.gTree"]')&&(constants.plotId=constants.chart.querySelector('g[id^="panel"] > g[id^="geom_boxplot.gTree"]').getAttribute("id")),constants.manualData){let t="";"undefined"!=typeof maidr&&void 0!==maidr.title?t=maidr.title:constants.chart.querySelector('tspan[dy="9.45"]')&&(t=constants.chart.querySelector('tspan[dy="9.45"]').innerHTML,t=t.replace("\n","").replace(/ +(?= )/g,"")),this.title=void 0!==t&&null!=typeof t?t:"","undefined"!=typeof maidr?this.x_group_label=maidr.x_group_label:this.x_group_label=constants.chart.querySelector('text:not([transform^="rotate"]) > tspan[dy="7.88"]').innerHTML,"undefined"!=typeof maidr?this.y_group_label=maidr.y_group_label:this.y_group_label=constants.chart.querySelector('text[transform^="rotate"] > tspan[dy="7.88"]').innerHTML;let e=[];if("undefined"!=typeof maidr)this.x_labels=maidr.x_labels,this.y_labels=maidr.y_labels;else{let t="3.15";"vert"==constants.plotOrientation&&(t="6.3");let n=constants.chart.querySelectorAll('tspan[dy="'+t+'"]');for(let t=0;tconstants.maxY&&(constants.maxY=n.yMax):n.y>constants.maxY&&(constants.maxY=n.y)),n.hasOwnProperty("x")&&(n.xconstants.maxX&&(constants.maxX=n.x))}}else{constants.minX=0,constants.maxX=0;for(let t=0;tconstants.maxX&&(constants.maxX=n.xMax):n.x>constants.maxX&&(constants.maxX=n.x)),n.hasOwnProperty("y")&&(n.yconstants.maxY&&(constants.maxY=n.y))}}}GetData(){let t=[],e=document.querySelector(singleMaidr.element).children;for(let n=0;nconstants.maxY&&(constants.maxY=e.y),s.push(e)}}}if(s.sort((function(t,e){return"vert"==constants.plotOrientation?t.y-e.y:t.x-e.x})),"horz"==constants.plotOrientation){let t=[];for(let e=0;e0&&s[e-1].x==s[e].x?"whisker"==s[e-1].type&&(t.splice(-1,1),t.push(s[e])):t.push(s[e]);s=t}t.push(s)}t.sort((function(t,e){return"vert"==constants.plotOrientation?t[0].x-e[0].x:t[0].y-e[0].y}));for(let e=0;e0){let n=[];for(let i=0;i0&&(t[e][s+i-o.length].type="delete");"vert"==constants.plotOrientation?(t[e][s-o.length].y=o[0].y,t[e][s-o.length].yMax=o[o.length-1].y):(t[e][s-o.length].x=o[0].x,t[e][s-o.length].xMax=o[o.length-1].x),t[e][s-o.length].values=n,o=[]}}else o.push(n[s])}}let n=[];for(let e=0;e0?resources.GetString("upper_outlier"):resources.GetString("lower_outlier"):"whisker"==s.type?t[e][o].label=n>0?resources.GetString("max"):resources.GetString("min"):"range"==s.type&&(0==n?t[e][o].label=resources.GetString("25"):1==n?t[e][o].label=resources.GetString("50"):2==n&&(t[e][o].label=resources.GetString("75")),n++)}}let o=this.GetAllSegmentTypes();for(let e=0;e1&&console.log("plotData:",t),t}GetPlotBounds(t){let e=[],n=this.GetAllSegmentTypes(),o=/(?:\d+(?:\.\d*)?|\.\d+)/g,s=[],i=document.getElementById(constants.plotId).children;for(let t=0;ta.bottom&&(o=!0),e.topa.right&&(l=!0)),o?(i[1]=this.convertBoundingClientRectToObj(e),i[1].label=n[1],i[1].type="whisker","vert"==constants.plotOrientation?(i[1].top=i[2].bottom,i[1].y=i[1].top,i[1].height=i[1].bottom-i[1].top):i[1].width=i[2].left-i[1].left):(i[1]={},i[1].label=n[1],i[1].type="blank"),l?(i[5]=this.convertBoundingClientRectToObj(e),i[5].label=n[5],i[5].type="whisker","vert"==constants.plotOrientation?(i[5].bottom=i[4].top,i[5].height=i[5].bottom-i[5].top):(i[5].left=i[4].right,i[5].x=i[4].right,i[5].width=i[5].right-i[5].left)):(i[5]={},i[5].label=n[5],i[5].type="blank")}if(Object.hasOwn(s[t],"outlier")){let e=s[t].outlier.children,o=null,l=null;for(let t=0;to.bottom&&(o.bottom=n.bottom)):o=this.convertBoundingClientRectToObj(n):l?(n.yl.bottom&&(l.bottom=n.bottom)):l=this.convertBoundingClientRectToObj(n):n.x>a.x?o?(n.xo.right&&(o.right=n.right)):o=this.convertBoundingClientRectToObj(n):l?(n.xl.right&&(l.right=n.right)):l=this.convertBoundingClientRectToObj(n)}l?(l.height=l.bottom-l.top,l.width=l.right-l.left,i[0]=this.convertBoundingClientRectToObj(l),i[0].label=n[0],i[0].type="outlier"):(i[0]={},i[0].label=n[0],i[0].type="blank"),o?(o.height=o.bottom-o.top,o.width=o.right-o.left,i[6]=this.convertBoundingClientRectToObj(o),i[6].label=n[6],i[6].type="outlier"):(i[6]={},i[6].label=n[6],i[6].type="blank")}else i[0]={},i[0].label=n[0],i[0].type="blank",i[6]={},i[6].label=n[6],i[6].type="blank";e.push(i)}return constants.debugLevel>5&&console.log("plotBounds",e),e}GetAllSegmentTypes(){return[resources.GetString("lower_outlier"),resources.GetString("min"),resources.GetString("25"),resources.GetString("50"),resources.GetString("75"),resources.GetString("max"),resources.GetString("upper_outlier")]}GetBoxplotSegmentType(t){let e="outlier";return t.includes("geom_crossbar")?e="range":t.includes("GRID")?e="whisker":t.includes("points")&&(e="outlier"),e}GetBoxplotSegmentPoints(t,e){let n=/(?:\d+(?:\.\d*)?|\.\d+)/g,o=[];if("range"==e){let e=t.children[0].getAttribute("points").match(n);o.push(e[0],e[1]),e[0]!=e[2]&&o.push(e[2],e[3])}else if("outlier"==e)o.push(t.getAttribute("x"),t.getAttribute("y"));else{let e=t.getAttribute("points").match(n);"vert"==constants.plotOrientation?e[1]!=e[3]&&o.push(e[0],e[1],e[2],e[3]):e[0]!=e[2]&&o.push(e[0],e[1],e[2],e[3])}return o}convertBoundingClientRectToObj(t){return{top:t.top,right:t.right,bottom:t.bottom,left:t.left,width:t.width,height:t.height,x:t.x,y:t.y}}PlayTones(t){let e=null,n=null;constants.outlierInterval&&clearInterval(constants.outlierInterval),"vert"==constants.plotOrientation?(e=position.x,n=position.y):(e=position.y,n=position.x),"blank"==plot.plotData[e][n].type?t.PlayNull():"outlier"!=plot.plotData[e][n].type?t.playTone():(position.z=0,constants.outlierInterval=setInterval((function(){t.playTone(),position.z+=1,Object.hasOwn(plot.plotData[e][n],"values")?position.z+1>plot.plotData[e][n].values.length&&(clearInterval(constants.outlierInterval),position.z=-1):(clearInterval(constants.outlierInterval),position.z=-1)}),constants.autoPlayOutlierRate))}}class BoxplotRect{rectPadding=15;rectStrokeWidth=4;constructor(){this.x1=0,this.width=0,this.y1=0,this.height=0,this.chartOffsetLeft=constants.chart.getBoundingClientRect().left,this.chartOffsetTop=constants.chart.getBoundingClientRect().top}UpdateRect(){document.getElementById("highlight_rect")&&document.getElementById("highlight_rect").remove();let t=position.x,e=position.y;if("vert"==constants.plotOrientation||(t=position.y,e=position.x),"vert"==constants.plotOrientation&&position.y>-1||"horz"==constants.plotOrientation&&position.x>-1){let n=plot.plotBounds[t][e];"blank"!=n.type&&(this.x1=n.left-this.rectPadding-this.chartOffsetLeft,this.width=n.width+2*this.rectPadding,this.y1=n.top-this.rectPadding-this.chartOffsetTop,this.height=n.height+2*this.rectPadding,constants.debugLevel>5&&(console.log("Point",plot.plotData[t][e].label,"bottom:",n.bottom,"top:",n.top),console.log("x1:",this.x1,"y1:",this.y1,"width:",this.width,"height:",this.height)),this.CreateRectDisplay())}}CreateRectDisplay(){let t=document.createElementNS("http://www.w3.org/2000/svg","rect");t.setAttribute("id","highlight_rect"),t.setAttribute("x",this.x1),t.setAttribute("y",this.y1),t.setAttribute("width",this.width),t.setAttribute("height",this.height),t.setAttribute("stroke",constants.colorSelected),t.setAttribute("stroke-width",this.rectStrokeWidth),t.setAttribute("fill","none"),constants.chart.appendChild(t)}}class HeatMap{constructor(){"elements"in maidr?(this.plots=maidr.elements,constants.hasRect=1):(this.plots=constants.chart.querySelectorAll('g[id^="geom_rect"] > rect'),constants.hasRect=0),this.group_labels=this.getGroupLabels(),this.x_labels=this.getXLabels(),this.y_labels=this.getYLabels(),this.title=this.getTitle(),this.plotData=this.getHeatMapData(),this.updateConstants(),this.x_coord=this.plotData[0],this.y_coord=this.plotData[1],this.values=this.plotData[2],this.num_rows=this.plotData[3],this.num_cols=this.plotData[4],this.x_group_label=this.group_labels[0].trim(),this.y_group_label=this.group_labels[1].trim(),this.box_label=this.group_labels[2].trim()}getHeatMapData(){let t=[],e=[],n=[],o=[];if(constants.hasRect){for(let n=0;nArray(i).fill(0)));let a=3*Math.pow(255,2),h=0;for(var r=0;rh&&(h=s)}}return[n,o,l,s,i]}updateConstants(){constants.minX=0,constants.maxX=this.plotData[4],constants.minY=this.plotData[2][0][0],constants.maxY=this.plotData[2][0][0];for(let t=0;tconstants.maxY&&(constants.maxY=this.plotData[2][t][e])}GetSVGScales(){let t=1,e=1,n=!1,o=this.plots[0];for(;o&&"body"!=o.tagName.toLowerCase();)o.tagName&&"svg"===o.tagName.toLowerCase()&&(n=!0),o=o.parentNode;if(n){let n=this.plots[0];for(;n&&"body"!=n.tagName.toLowerCase();){if(n.getAttribute("transform")){let o=n.getAttribute("transform").match(/scale\((-?\d+(\.\d+)?),\s*(-?\d+(\.\d+)?)\)/);o&&(isNaN(o[1])||(t*=parseFloat(o[1])),isNaN(o[3])||(e*=parseFloat(o[3])))}n=n.parentNode}}return[t,e]}getRGBNorm(t){return this.plots[t].getAttribute("fill").slice(4,-1).split(",").map((function(t){return Math.pow(t,2)})).reduce((function(t,e){return t+e}))}getGroupLabels(){let t,e="",n="",o="";return e="title"in maidr?maidr.title:constants.chart.querySelector('g[id^="guide.title"] text > tspan').innerHTML,"axis"in maidr?("x"in maidr.axis&&"label"in maidr.axis.x&&(n=maidr.axis.x.label),"y"in maidr.axis&&"label"in maidr.axis.y&&(o=maidr.axis.y.label)):(n=constants.chart.querySelector('g[id^="xlab"] text > tspan').innerHTML,o=constants.chart.querySelector('g[id^="ylab"] text > tspan').innerHTML),t=[n,o,e],t}getXLabels(){if(!("axis"in maidr)){let t;t=constants.chart.querySelectorAll('tspan[dy="10"]');let e=[];for(let n=0;n-1?t=maidr.data[s]:"scatter"==maidr.type&&(t=maidr.data),void 0!==t){for(let t=0;tMath.max(...t))));return[t,i,a,l]}}PlayTones(t){constants.sepPlayId&&constants.KillSepPlay(),"scatter"==constants.chartType?(position.z=0,constants.sepPlayId=setInterval((function(){t.playTone(),position.z+=1,position.z+1>plot.y[position.x].length&&(constants.KillSepPlay(),position.z=-1)}),"sep"==constants.sonifMode?constants.autoPlayPointsRate:0)):"line"==constants.chartType&&t.playTone()}GetSvgLineCoords(){let t=this.plotLine.getAttribute("points").split(" "),e=[],n=[];for(let o=0;o-1?t=maidr.data[o]:"line"==maidr.type&&(t=maidr.data),void 0!==t){for(let t=0;te.target.value.length-2||((constants.isMac?e.metaKey:e.ctrlKey)?e.shiftKey?(position.x-=1,p("right",position.x,plot.bars.length)):(position.x=plot.bars.length-1,o=!0,s=a()):e.altKey&&e.shiftKey&&position.x!=plot.bars.length-1?(n=position.x,p("reverse-right",plot.bars.length,position.x)):(position.x+=1,o=!0,s=a()))):37==e.which?(e.preventDefault(),(constants.isMac?e.metaKey:e.ctrlKey)?e.shiftKey?(position.x+=1,p("left",position.x,-1)):(position.x=0,o=!0,s=a()):e.altKey&&e.shiftKey&&0!=position.x?(n=position.x,p("reverse-left",-1,position.x)):(position.x+=-1,o=!0,s=a())):e.preventDefault()),constants.brailleInput.addEventListener("focusout",(function(t){display.toggleBrailleMode("off")})),o&&!s&&c(),s&&t.playEnd()}]);let i=[constants.chart,constants.brailleInput];for(let h=0;hconstants.keypressInterval)&&display.toggleTextMode()}83==t.which&&display.toggleSonificationMode(),32===t.which&&l()}]);function a(){let t=!1;return constants.hasRect?(position.x<0&&(position.x=0,t=!0),position.x>plot.bars.length-1&&(position.x=plot.bars.length-1,t=!0),t):t}function l(){constants.showDisplay&&display.displayValues(plot),constants.showRect&&constants.hasRect&&plot.Select(),"off"!=constants.sonifMode&&t.playTone()}function r(){constants.showDisplayInAutoplay&&display.displayValues(plot),constants.showRect&&constants.hasRect&&plot.Select(),"off"!=constants.sonifMode&&t.playTone(),"off"!=constants.brailleMode&&display.UpdateBraillePos(plot)}function c(){constants.showDisplayInBraille&&display.displayValues(plot),constants.showRect&&constants.hasRect&&plot.Select(),"off"!=constants.sonifMode&&t.playTone(),display.UpdateBraillePos(plot)}function p(t,n,o){e=t;let s=1;"left"!=t&&"reverse-right"!=t||(s=-1),null!=constants.autoplayId&&constants.KillAutoplay(),"reverse-right"!=t&&"reverse-left"!=t||(position.x=n),constants.autoplayId=setInterval((function(){position.x+=s,position.x<0||plot.bars.length-10?("vert"==constants.plotOrientation?g=position.y:f=position.x,D("reverse-left",0,position.x)):(position.x+=-1,e=!0,n=M()),constants.navigation=1),38===t.which){position.y;(constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?"vert"==constants.plotOrientation?D("up",position.y,plot.plotData[position.x].length):D("up",position.y,plot.plotData.length):("vert"==constants.plotOrientation?position.y=plot.plotData[position.x].length-1:position.y=plot.plotData.length-1,e=!0,n=M()):"vert"==constants.plotOrientation?t.altKey&&t.shiftKey&&position.y!=plot.plotData[position.x].length-1?(g=position.y,D("reverse-up",plot.plotData[position.x].length-1,position.y)):(position.y+=1,e=!0,n=M()):t.altKey&&t.shiftKey&&position.y!=plot.plotData.length-1?(f=position.x,D("reverse-up",plot.plotData.length-1,position.y)):(position.y+=1,e=!0,n=M()),constants.navigation=0}if(40===t.which){position.y;(constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?D("down",position.y,-1):(position.y=0,e=!0,n=M()):t.altKey&&t.shiftKey&&0!=position.y?("vert"==constants.plotOrientation?g=position.y:f=position.x,D("reverse-down",0,position.y)):("vert"==constants.plotOrientation?-1==position.x&&position.y==plot.plotData[position.x].length&&(position.x+=1):-1==position.x&&position.y==plot.plotData.length&&(position.x+=1),position.y+=-1,e=!0,n=M()),constants.navigation=0}e&&!n&&b(),n&&u.playEnd()}]),constants.events.push([constants.brailleInput,"keydown",function(t){let e=!1,n=!1,o=!1;if(9==t.which);else if(39==t.which)t.preventDefault(),(constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?"vert"==constants.plotOrientation?D("right",position.x,plot.plotData.length-1):D("right",position.x,plot.plotData[position.y].length):("vert"==constants.plotOrientation?position.x=plot.plotData.length-1:position.x=plot.plotData[position.y].length-1,e=!0,o=M()):"vert"==constants.plotOrientation?t.altKey&&t.shiftKey&&plot.plotData.length-1!=position.x?(g=position.y,D("reverse-right",plot.plotData.length-1,position.x)):(-1==position.x&&position.y==plot.plotData[position.x].length&&(position.y-=1),position.x+=1,e=!0,o=M()):t.altKey&&t.shiftKey&&plot.plotData[position.y].length-1!=position.x?(f=position.x,D("reverse-right",plot.plotData[position.y].length-1,position.x)):(-1==position.x&&position.y==plot.plotData.length&&(position.y-=1),position.x+=1,e=!0,o=M()),n=!0,constants.navigation=1;else if(37==t.which)t.preventDefault(),(constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?D("left",position.x,-1):(position.x=0,e=!0,o=M()):t.altKey&&t.shiftKey&&position.x>0?("vert"==constants.plotOrientation?g=position.y:f=position.x,D("reverse-left",0,position.x)):(position.x+=-1,e=!0,o=M()),n=!0,constants.navigation=1;else if(38===t.which){position.y;(constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?"vert"==constants.plotOrientation?(position.x<0&&(position.x=0),D("up",position.y,plot.plotData[position.x].length)):D("up",position.y,plot.plotData.length):"vert"==constants.plotOrientation?(position.y=plot.plotData[position.x].length-1,e=!0):(position.y=plot.plotData.length-1,e=!0):"vert"==constants.plotOrientation?t.altKey&&t.shiftKey&&position.y!=plot.plotData[position.x].length-1?(lasY=position.y,D("reverse-up",plot.plotData[position.x].length-1,position.y)):(position.y+=1,e=!0,o=M()):t.altKey&&t.shiftKey&&position.y!=plot.plotData.length-1?(f=position.x,D("reverse-up",plot.plotData.length-1,position.y)):(position.y+=1,e=!0,o=M()),"vert"==constants.plotOrientation||(n=!0),constants.navigation=0}else if(40===t.which){position.y;(constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?D("down",position.y,-1):(position.y=0,e=!0,o=M()):t.altKey&&t.shiftKey&&0!=position.y?("vert"==constants.plotOrientation?g=position.y:f=position.x,D("reverse-down",0,position.y)):("vert"==constants.plotOrientation?-1==position.x&&position.y==plot.plotData[position.x].length&&(position.x+=1):-1==position.x&&position.y==plot.plotData.length&&(position.x+=1),position.y+=-1,e=!0,o=M()),constants.navigation=0,"vert"==constants.plotOrientation||(n=!0),constants.navigation=0}else t.preventDefault();e&&!o&&(n&&display.SetBraille(plot),setTimeout(_,50)),o&&u.playEnd(),constants.brailleInput.addEventListener("focusout",(function(t){display.toggleBrailleMode("off")}))}]);let v=[constants.chart,constants.brailleInput];for(let S=0;Sconstants.keypressInterval)&&display.toggleTextMode()}83==t.which&&display.toggleSonificationMode(),32===t.which&&b()}]);function b(){constants.showDisplay&&display.displayValues(plot),constants.showRect&&constants.hasRect&&d.UpdateRect(),"off"!=constants.sonifMode&&plot.PlayTones(u)}function w(){constants.showDisplayInAutoplay&&display.displayValues(plot),constants.showRect&&constants.hasRect&&d.UpdateRect(),"off"!=constants.sonifMode&&plot.PlayTones(u),"off"!=constants.brailleMode&&display.UpdateBraillePos(plot)}function _(){constants.showDisplayInBraille&&display.displayValues(plot),constants.showRect&&constants.hasRect&&d.UpdateRect(),"off"!=constants.sonifMode&&plot.PlayTones(u),display.UpdateBraillePos(plot)}function M(){let t=!1;return"vert"==constants.plotOrientation?(position.y<0&&(position.y=0,t=!0),position.x<0&&(position.x=0,t=!0),position.x>plot.plotData.length-1&&(position.x=plot.plotData.length-1,t=!0),position.y>plot.plotData[position.x].length-1&&(position.y=plot.plotData[position.x].length-1,t=!0)):(position.x<0&&(position.x=0,t=!0),position.y<0&&(position.y=0,t=!0),position.y>plot.plotData.length-1&&(position.y=plot.plotData.length-1,t=!0),position.x>plot.plotData[position.y].length-1&&(position.x=plot.plotData[position.y].length-1,t=!0)),t}function D(t,e,n){y=t;let o=1;"left"!=t&&"down"!=t&&"reverse-right"!=t&&"reverse-up"!=t||(o=-1),null!=constants.autoplayId&&constants.KillAutoplay(),"reverse-left"==t||"reverse-right"==t?position.x=e:"reverse-up"!=t&&"reverse-down"!=t||(position.y=e),constants.debugLevel>0&&console.log("starting autoplay",t),w(),constants.autoplayId=setInterval((function(){let e=!1;"left"==t||"right"==t||"up"==t||"down"==t?(position.x<1&&"left"==t||"vert"==constants.plotOrientation&&"up"==t&&position.y>plot.plotData[position.x].length-2||"horz"==constants.plotOrientation&&"up"==t&&position.y>plot.plotData.length-2||"horz"==constants.plotOrientation&&"right"==t&&position.x>plot.plotData[position.y].length-2||"vert"==constants.plotOrientation&&"right"==t&&position.x>plot.plotData.length-2||"horz"==constants.plotOrientation&&"down"==t&&position.y<1||"vert"==constants.plotOrientation&&"down"==t&&position.y<1)&&(e=!0):("reverse-left"==t&&position.x>=n||"reverse-right"==t&&position.x<=n||"reverse-up"==t&&position.y<=n||"reverse-down"==t&&position.y>=n)&&(e=!0),e?constants.KillAutoplay():("left"==t||"right"==t||"reverse-left"==t||"reverse-right"==t?position.x+=o:position.y+=o,w()),constants.debugLevel>5&&console.log("autoplay pos",position)}),constants.autoPlayRate)}constants.events.push([document,"keydown",function(t){if((constants.isMac?t.metaKey:t.ctrlKey)&&(36==t.which?(position.x=0,position.y=plot.plotData.length-1,_()):35==t.which&&(position.x=plot.plotData[0].length-1,position.y=0,_())),m)if(88==t.which){let t=window.performance.now()-x;m&&t<=constants.keypressInterval&&display.displayXLabel(plot),m=!1}else if(89==t.which){let t=window.performance.now()-x;m&&t<=constants.keypressInterval&&display.displayYLabel(plot),m=!1}else if(84==t.which){let t=window.performance.now()-x;m&&t<=constants.keypressInterval&&display.displayTitle(plot),m=!1}else 76==t.which?(x=window.performance.now(),m=!0):m=!1;76==t.which&&(x=window.performance.now(),m=!0),190==t.which&&(constants.SpeedUp(),null!=constants.autoplayId&&(constants.KillAutoplay(),"reverse-left"==y?"vert"==constants.plotOrientation?D("right",position.y,g):D("right",position.x,f):"reverse-right"==y?"vert"==constants.plotOrientation?D("left",position.y,g):D("left",position.x,f):"reverse-up"==y?"vert"==constants.plotOrientation?D("down",position.y,g):D("down",position.x,f):"reverse-down"==y?"vert"==constants.plotOrientation?D("up",position.y,g):D("up",position.x,f):"vert"==constants.plotOrientation?D(y,position.y,g):D(y,position.x,f))),188==t.which&&(constants.SpeedDown(),null!=constants.autoplayId&&(constants.KillAutoplay(),"reverse-left"==y?"vert"==constants.plotOrientation?D("right",position.y,g):D("right",position.x,f):"reverse-right"==y?"vert"==constants.plotOrientation?D("left",position.y,g):D("left",position.x,f):"reverse-up"==y?"vert"==constants.plotOrientation?D("down",position.y,g):D("down",position.x,f):"reverse-down"==y?"vert"==constants.plotOrientation?D("up",position.y,g):D("up",position.x,f):"vert"==constants.plotOrientation?D(y,position.y,g):D(y,position.x,f)))}])}else if([].concat(singleMaidr.type).includes("heat")){constants.plotId="geom_rect.rect.2.1",window.position=new Position(-1,-1),window.plot=new HeatMap;let L=new HeatMapRect,I=new Audio,O="",T=0,E=0,P=!1;constants.events.push([constants.chart,"keydown",function(t){let e=!1,n=!1;39===t.which&&((constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?(position.x-=1,B("right",position.x,plot.num_cols)):(position.x=plot.num_cols-1,e=!0):t.altKey&&t.shiftKey&&position.x!=plot.num_cols-1?(T=position.x,B("reverse-right",plot.num_cols,position.x)):(-1==position.x&&-1==position.y&&(position.y+=1),position.x+=1,e=!0,n=C()),constants.navigation=1),37===t.which&&((constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?(position.x+=1,B("left",position.x,-1)):(position.x=0,e=!0):t.altKey&&t.shiftKey&&0!=position.x?(T=position.x,B("reverse-left",-1,position.x)):(position.x-=1,e=!0,n=C()),constants.navigation=1),38===t.which&&((constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?(position.y+=1,B("up",position.y,-1)):(position.y=0,e=!0):t.altKey&&t.shiftKey&&0!=position.y?(T=position.x,B("reverse-up",-1,position.y)):(position.y-=1,e=!0,n=C()),constants.navigation=0),40===t.which&&((constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?(position.y-=1,B("down",position.y,plot.num_rows)):(position.y=plot.num_rows-1,e=!0):t.altKey&&t.shiftKey&&position.y!=plot.num_rows-1?(T=position.x,B("reverse-down",plot.num_rows,position.y)):(-1==position.x&&-1==position.y&&(position.x+=1),position.y+=1,e=!0,n=C()),constants.navigation=0),e&&!n&&K(),n&&I.playEnd()}]),constants.events.push([constants.brailleInput,"keydown",function(t){let e=!1,n=!1;if(9==t.which);else if(39==t.which)if(t.target.selectionStart>t.target.value.length-3||"⠳"==t.target.value.substring(t.target.selectionStart+1,t.target.selectionStart+2))t.preventDefault();else{(constants.isMac?t.metaKey:t.ctrlKey)?(-1==position.x&&-1==position.y&&(position.x+=1,position.y+=1),t.shiftKey?(position.x-=1,B("right",position.x,plot.num_cols)):(position.x=plot.num_cols-1,e=!0)):t.altKey&&t.shiftKey&&position.x!=plot.num_cols-1?(T=position.x,B("reverse-right",plot.num_cols,position.x)):(-1==position.x&&-1==position.y&&(position.y+=1),position.x+=1,e=!0,n=C());let o=position.y*(plot.num_cols+1)+position.x;t.target.setSelectionRange(o,o),t.preventDefault(),constants.navigation=1}else if(37==t.which)if(0==t.target.selectionStart||"⠳"==t.target.value.substring(t.target.selectionStart-1,t.target.selectionStart))t.preventDefault();else{(constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?(position.x+=1,B("left",position.x,-1)):(position.x=0,e=!0):t.altKey&&t.shiftKey&&0!=position.x?(T=position.x,B("reverse-left",-1,position.x)):(position.x+=-1,e=!0,n=C());let o=position.y*(plot.num_cols+1)+position.x;t.target.setSelectionRange(o,o),t.preventDefault(),constants.navigation=1}else if(40==t.which)if(position.y+1==plot.num_rows)t.preventDefault();else{(constants.isMac?t.metaKey:t.ctrlKey)?(-1==position.x&&-1==position.y&&(position.x+=1,position.y+=1),t.shiftKey?(position.y-=1,B("down",position.y,plot.num_rows)):(position.y=plot.num_rows-1,e=!0)):t.altKey&&t.shiftKey&&position.y!=plot.num_rows-1?(T=position.x,B("reverse-down",plot.num_rows,position.y)):(-1==position.x&&-1==position.y&&(position.x+=1),position.y+=1,e=!0,n=C());let o=position.y*(plot.num_cols+1)+position.x;t.target.setSelectionRange(o,o),t.preventDefault(),constants.navigation=0}else if(38==t.which)if(t.target.selectionStart-plot.num_cols-1<0)t.preventDefault();else{(constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?(position.y+=1,B("up",position.y,-1)):(position.y=0,e=!0):t.altKey&&t.shiftKey&&0!=position.y?(T=position.x,B("reverse-up",-1,position.y)):(position.y+=-1,e=!0,n=C());let o=position.y*(plot.num_cols+1)+position.x;t.target.setSelectionRange(o,o),t.preventDefault(),constants.navigation=0}else t.preventDefault();constants.brailleInput.addEventListener("focusout",(function(t){display.toggleBrailleMode("off")})),e&&!n&&N(),n&&I.playEnd()}]);let A=[constants.chart,constants.brailleInput];for(let k=0;kconstants.keypressInterval)&&display.toggleTextMode()}83==t.which&&display.toggleSonificationMode(),32===t.which&&K()}]);function C(){let t=!1;return position.x<0&&(position.x=0,t=!0),position.x>plot.num_cols-1&&(position.x=plot.num_cols-1,t=!0),position.y<0&&(position.y=0,t=!0),position.y>plot.num_rows-1&&(position.y=plot.num_rows-1,t=!0),t}function K(){constants.showDisplay&&display.displayValues(plot),constants.showRect&&constants.hasRect&&L.UpdateRectDisplay(),"off"!=constants.sonifMode&&I.playTone()}function R(){constants.showDisplayInAutoplay&&display.displayValues(plot),constants.showRect&&constants.hasRect&&L.UpdateRectDisplay(),"off"!=constants.sonifMode&&I.playTone(),"off"!=constants.brailleMode&&display.UpdateBraillePos(plot)}function N(){constants.showDisplayInBraille&&display.displayValues(plot),constants.showRect&&constants.hasRect&&L.UpdateRectDisplay(),"off"!=constants.sonifMode&&I.playTone(),display.UpdateBraillePos(plot)}function B(t,e,n){O=t;let o=1;"left"!=t&&"up"!=t&&"reverse-right"!=t&&"reverse-down"!=t||(o=-1),null!=constants.autoplayId&&constants.KillAutoplay(),"reverse-left"==t||"reverse-right"==t?position.x=e:"reverse-up"!=t&&"reverse-down"!=t||(position.y=e),constants.autoplayId=setInterval((function(){"left"==t||"right"==t||"reverse-left"==t||"reverse-right"==t?(position.x+=o,position.x<0||plot.num_cols-1t.target.value.length-2?t.preventDefault():(constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?(positionL1.x-=1,J("outward_right",positionL1.x,plot.curvePoints.length)):(positionL1.x=plot.curvePoints.length-1,e=!0,n=V()):t.altKey&&t.shiftKey&&positionL1.x!=plot.curvePoints.length-1?(j=positionL1.x,J("inward_right",plot.curvePoints.length,positionL1.x)):(positionL1.x+=1,e=!0,n=V())):37==t.which?(t.preventDefault(),(constants.isMac?t.metaKey:t.ctrlKey)?t.shiftKey?(positionL1.x+=1,J("outward_left",positionL1.x,-1)):(positionL1.x=0,e=!0,n=V()):t.altKey&&t.shiftKey&&0!=positionL1.x?J("inward_left",-1,positionL1.x):(positionL1.x-=1,e=!0,n=V())):t.preventDefault())):t.preventDefault()),constants.brailleInput.addEventListener("focusout",(function(t){display.toggleBrailleMode("off")})),j=positionL1.x,e&&!n&&$(),n&&U.playEnd()}]);let Q=[constants.chart,constants.brailleInput];for(let tt=0;ttconstants.keypressInterval)&&display.toggleTextMode()}83==t.which&&display.toggleSonificationMode(),34==t.which&&"off"==constants.brailleMode&&(j=positionL1.x,display.changeChartLayer("down")),33==t.which&&"off"==constants.brailleMode&&display.changeChartLayer("up"),32===t.which&&z()}]);function V(){let t=!1;return"scatter"==constants.chartType?(position.x<0&&(position.x=0,t=!0),position.x>plot.x.length-1&&(position.x=plot.x.length-1,t=!0)):"line"==constants.chartType&&(positionL1.x<0&&(positionL1.x=0,t=!0),positionL1.x>plot.curvePoints.length-1&&(positionL1.x=plot.curvePoints.length-1,t=!0)),t}function z(){constants.showDisplay&&display.displayValues(plot),constants.showRect&&Y.UpdatePointDisplay(),"off"!=constants.sonifMode&&plot.PlayTones(U)}function W(){constants.showDisplayInAutoplay&&display.displayValues(plot),constants.showRect&&("scatter"==constants.chartType?Y.UpdatePointDisplay():G.UpdatePointDisplay()),"off"!=constants.sonifMode&&plot.PlayTones(U),"off"!=constants.brailleMode&&display.UpdateBraillePos(plot)}function $(){constants.showDisplayInBraille&&display.displayValues(plot),constants.showRect&&G.UpdatePointDisplay(),"off"!=constants.sonifMode&&plot.PlayTones(U),display.UpdateBraillePos(plot)}function J(t,e,n){X=t;let o=1;"outward_left"!=t&&"inward_right"!=t||(o=-1),constants.autoplayId&&constants.KillAutoplay(),constants.isSmoothAutoplay&&U.KillSmooth(),"inward_left"!=t&&"inward_right"!=t||(position.x=e,position.L1x=e),"scatter"==constants.chartType?constants.autoplayId=setInterval((function(){position.x+=o,position.x<0||position.x>plot.y.length-1?(constants.KillAutoplay(),V()):position.x==n?(constants.KillAutoplay(),W()):W()}),constants.autoPlayRate):"line"==constants.chartType&&(constants.autoplayId=setInterval((function(){positionL1.x+=o,positionL1.x<0||positionL1.x>plot.curvePoints.length-1?(constants.KillAutoplay(),V()):positionL1.x==n?(constants.KillAutoplay(),W()):W()}),constants.autoPlayRate))}function Z(t){X=t;let e=[],n=[],o=U.SlideBetween(positionL1.x,0,plot.curvePoints.length-1,-1,1),s=positionL1.x<0?0:positionL1.x,i=0;if("outward_right"==t){for(let t=s;t=0;t--)e.push(U.SlideBetween(plot.curvePoints[t],plot.curveMinY,plot.curveMaxY,constants.MIN_FREQUENCY,constants.MAX_FREQUENCY));n=[o,-1],i=Math.abs(s)/plot.curvePoints.length*3}else if("inward_right"==t){for(let t=plot.curvePoints.length-1;t>=s;t--)e.push(U.SlideBetween(plot.curvePoints[t],plot.curveMinY,plot.curveMaxY,constants.MIN_FREQUENCY,constants.MAX_FREQUENCY));n=[1,o],i=Math.abs(plot.curvePoints.length-s)/plot.curvePoints.length*3}else if("inward_left"==t){for(let t=0;t<=s;t++)e.push(U.SlideBetween(plot.curvePoints[t],plot.curveMinY,plot.curveMaxY,constants.MIN_FREQUENCY,constants.MAX_FREQUENCY));n=[-1,o],i=Math.abs(s)/plot.curvePoints.length*3}constants.isSmoothAutoplay&&U.KillSmooth(),U.playSmooth(e,i,n,constants.vol,"sine")}constants.events.push([document,"keydown",function(t){if((constants.isMac?t.metaKey:t.ctrlKey)&&(36==t.which?"scatter"==constants.chartType?(position.x=0,z(),constants.brailleInput.setSelectionRange(0,0)):"line"==constants.chartType&&(positionL1.x=0,$()):35==t.which&&("scatter"==constants.chartType?(position.x=plot.y.length-1,z(),constants.brailleInput.setSelectionRange(plot.curvePoints.length-1,plot.curvePoints.length-1)):"line"==constants.chartType&&(positionL1.x=plot.curvePoints.length-1,$())),t.shiftKey||U.KillSmooth()),H)if(88==t.which){let t=window.performance.now()-q;H&&t<=constants.keypressInterval&&display.displayXLabel(plot),H=!1}else if(89==t.which){let t=window.performance.now()-q;H&&t<=constants.keypressInterval&&display.displayYLabel(plot),H=!1}else if(84==t.which){let t=window.performance.now()-q;H&&t<=constants.keypressInterval&&display.displayTitle(plot),H=!1}else 76==t.which?(q=window.performance.now(),H=!0):H=!1;76==t.which&&(q=window.performance.now(),H=!0),190==t.which&&(constants.SpeedUp(),null!=constants.autoplayId&&(constants.KillAutoplay(),U.KillSmooth(),"inward_left"==X?"scatter"==constants.chartType?J("outward_right",position.x,F):"line"==constants.chartType&&J("outward_right",positionL1.x,j):"inward_right"==X?"scatter"==constants.chartType?J("outward_left",position.x,F):"line"==constants.chartType&&J("outward_left",positionL1.x,j):"scatter"==constants.chartType?J(X,position.x,F):"line"==constants.chartType&&J(X,positionL1.x,j))),188==t.which&&(constants.SpeedDown(),null!=constants.autoplayId&&(constants.KillAutoplay(),U.KillSmooth(),"inward_left"==X?"scatter"==constants.chartType?J("outward_right",position.x,F):"line"==constants.chartType&&J("outward_right",positionL1.x,j):"inward_right"==X?"scatter"==constants.chartType?J("outward_left",position.x,F):"line"==constants.chartType&&J("outward_left",positionL1.x,j):"scatter"==constants.chartType?J(X,position.x,F):"line"==constants.chartType&&J(X,positionL1.x,j)))}])}}}function InitMaidr(t){if(console.log("Initializing Maidr"),"undefined"!=typeof constants){window.singleMaidr=t,constants.chartId=singleMaidr.id,Array.isArray(singleMaidr.type)?constants.chartType=singleMaidr.type[0]:constants.chartType=singleMaidr.type,CreateChartComponents(singleMaidr),window.control=new Control,window.review=new Review,window.display=new Display,constants.events.push([constants.chart_container,"keydown",function(t){72==t.which&&menu.Toggle(!0)}]),constants.events.push([document.getElementById(singleMaidr.id),"blur",ShouldWeDestroyMaidr]);for(let t=0;t{let t=document.activeElement;if(t.id){if(maidrIds.includes(t.id))return;if(t.id==constants.braille_input_id)return;DestroyMaidr()}else DestroyMaidr()}),0)}function DestroyMaidr(){console.log("Destroying Maidr"),"bar"==constants.chartType&&plot.DeselectAll();for(let t=0;t

\n

\n

\n
\n'),document.getElementById("info").insertAdjacentHTML("afterend",'
\n'),document.getElementById(constants.info_id).insertAdjacentHTML("afterend",''),constants.chart_container.setAttribute("role","application"),constants.brailleContainer=document.getElementById(constants.braille_container_id),constants.brailleInput=document.getElementById(constants.braille_input_id),constants.infoDiv=document.getElementById(constants.info_id),constants.announceContainer=document.getElementById(constants.announcement_container_id),constants.nonMenuFocus=constants.chart,constants.endChime=document.getElementById(constants.end_chime_id)}function DestroyChartComponents(){null!=constants.chart_container&&(null!=constants.chart&&null!=constants.chart_container.parentNode&&constants.chart_container.parentNode.replaceChild(constants.chart,constants.chart_container),constants.chart_container.remove()),null!=constants.brailleContainer&&constants.brailleContainer.remove(),null!=constants.infoDiv&&constants.infoDiv.remove(),null!=constants.announceContainer&&constants.announceContainer.remove(),null!=constants.endChime&&constants.endChime.remove(),constants.chart=null,constants.chart_container=null,constants.brailleContainer=null,constants.brailleInput=null,constants.infoDiv=null,constants.announceContainer=null,constants.endChime=null}document.addEventListener("DOMContentLoaded",(function(t){window.constants=new Constants,window.resources=new Resources,window.menu=new Menu,window.tracker=new Tracker;let e=[];Array.isArray(maidr)?e=maidr:e.push(maidr),DestroyMaidr(),window.maidrIds=[];for(let t=0;tthis.MIN_SPEED&&(constants.autoPlayRate-=this.INTERVAL)}SpeedDown(){constants.autoPlayRate+this.INTERVAL<=this.MAX_SPEED&&(constants.autoPlayRate+=this.INTERVAL)}SpeedReset(){constants.autoPlayRate=constants.DEFAULT_SPEED}ColorInvert(t){let e=t.replace(/[^\d,]/g,"").split(",");return"rgb("+(255-e[0])+","+(255-e[1])+","+(255-e[2])+")"}}class Resources{constructor(){}language="en";knowledgeLevel="basic";strings={en:{basic:{upper_outlier:"Upper Outlier",lower_outlier:"Lower Outlier",min:"Minimum",max:"Maximum",25:"25%",50:"50%",75:"75%",q1:"25%",q2:"50%",q3:"75%",son_on:"Sonification on",son_off:"Sonification off",son_des:"Sonification descrete",son_comp:"Sonification compare",son_ch:"Sonification chord",son_sep:"Sonification separate",son_same:"Sonification combined",empty:"Empty"}}};GetString(t){return this.strings[this.language][this.knowledgeLevel][t]}}class Menu{whereWasMyFocus=null;constructor(){this.CreateMenu(),this.LoadDataFromLocalStorage()}menuHtml=`\n \n \n `;CreateMenu(){document.querySelector("body").insertAdjacentHTML("beforeend",this.menuHtml);let t=document.querySelectorAll("#close_menu, #menu .close");for(let e=0;e