diff --git a/galleries/bar_plot.html b/galleries/bar_plot.html index 346020850..574889c12 100644 --- a/galleries/bar_plot.html +++ b/galleries/bar_plot.html @@ -1154,6 +1154,7 @@ + diff --git a/galleries/boxplot_horizontal.html b/galleries/boxplot_horizontal.html index bda8b690a..32edafdc7 100644 --- a/galleries/boxplot_horizontal.html +++ b/galleries/boxplot_horizontal.html @@ -1684,6 +1684,7 @@ + diff --git a/galleries/boxplot_vertical.html b/galleries/boxplot_vertical.html index 68bad5ec6..3dd24a540 100644 --- a/galleries/boxplot_vertical.html +++ b/galleries/boxplot_vertical.html @@ -1802,6 +1802,7 @@ + diff --git a/galleries/dodged_bar.html b/galleries/dodged_bar.html index 8793f6d95..5aebe8a60 100644 --- a/galleries/dodged_bar.html +++ b/galleries/dodged_bar.html @@ -824,6 +824,7 @@ + diff --git a/galleries/heatmap.html b/galleries/heatmap.html index 5ca2edac1..b8662107f 100644 --- a/galleries/heatmap.html +++ b/galleries/heatmap.html @@ -1656,6 +1656,7 @@ + diff --git a/galleries/histogram.html b/galleries/histogram.html index cef231804..217aecf8b 100644 --- a/galleries/histogram.html +++ b/galleries/histogram.html @@ -1548,6 +1548,7 @@ + diff --git a/galleries/lineplot.html b/galleries/lineplot.html index 43aa81e29..f5cb6e4b7 100644 --- a/galleries/lineplot.html +++ b/galleries/lineplot.html @@ -1003,6 +1003,7 @@ + diff --git a/galleries/scatterplot.html b/galleries/scatterplot.html index 760750e82..e972e03f7 100644 --- a/galleries/scatterplot.html +++ b/galleries/scatterplot.html @@ -5885,6 +5885,7 @@ + diff --git a/galleries/stacked.html b/galleries/stacked.html index 13926658a..62de18ced 100644 --- a/galleries/stacked.html +++ b/galleries/stacked.html @@ -756,6 +756,7 @@ + diff --git a/galleries/stacked_normalized.html b/galleries/stacked_normalized.html index 193f82f6c..5c8ee6088 100644 --- a/galleries/stacked_normalized.html +++ b/galleries/stacked_normalized.html @@ -789,6 +789,7 @@ + diff --git a/gulpfile.js b/gulpfile.js index e7f481ee5..8807bf611 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -25,6 +25,7 @@ gulp.task('scripts', function () { './src/js/lineplot.js', './src/js/segmented.js', './src/js/controls.js', + './src/js/goto.js', './src/js/init.js', ]) // order matters here .pipe(concat('maidr.js')) diff --git a/src/css/styles.css b/src/css/styles.css index 8a54ed317..067c412a6 100644 --- a/src/css/styles.css +++ b/src/css/styles.css @@ -62,6 +62,36 @@ /* pointer-events: none; */ } +.btn { + display: inline-block; + font-weight: 400; + color: #212529; + text-align: center; + vertical-align: middle; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + border-radius: 0.25rem; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +.btn-link { + font-weight: 400; + color: #007bff; + background-color: transparent; +} + +.btn-link.active { + color: #0056b3; + border: 2px solid #0056b3; +} + /* Menu stuff */ .modal { position: fixed; diff --git a/src/js/controls.js b/src/js/controls.js index fedadd47e..a922eeafb 100644 --- a/src/js/controls.js +++ b/src/js/controls.js @@ -200,95 +200,97 @@ class Control { SetPrefixControls() { // prefix events, l + x, where x is a key for the title, axis, etc // we listen for a moment when l is hit for a key to follow - constants.events.push([ - document, - 'keydown', - function (e) { - // init - let pressedTimeout = null; - - // enable / disable prefix mode - if (e.key == 'l') { - control.pressedL = true; - if (pressedTimeout != null) { - clearTimeout(pressedTimeout); - pressedTimeout = null; - } - pressedTimeout = setTimeout(function () { - control.pressedL = false; - }, constants.keypressInterval); - } - - // Prefix mode stuff: L is enabled, look for these keys - if (control.pressedL) { - if (e.key == 'x') { - // X: x label - let xlabel = ''; - if (singleMaidr.type == 'bar' || singleMaidr.type == 'line') { - xlabel = plot.plotLegend.x; - } else if (singleMaidr.type == 'hist') { - xlabel = plot.legendX; - } else if ( - singleMaidr.type == 'heat' || - singleMaidr.type == 'box' || - singleMaidr.type == 'point' || - singleMaidr.type.includes('point') - ) { - xlabel = plot.x_group_label; - } else if ( - singleMaidr.type == 'stacked_bar' || - singleMaidr.type == 'stacked_normalized_bar' || - singleMaidr.type == 'dodged_bar' - ) { - xlabel = plot.plotLegend.x; - } - display.displayInfo('x label', xlabel); - control.pressedL = false; - } else if (e.key == 'y') { - // Y: y label - let ylabel = ''; - if (singleMaidr.type == 'bar' || singleMaidr.type == 'line') { - ylabel = plot.plotLegend.y; - } else if (singleMaidr.type == 'hist') { - ylabel = plot.legendY; - } else if ( - singleMaidr.type == 'heat' || - singleMaidr.type == 'box' || - singleMaidr.type == 'point' || - singleMaidr.type == 'line' || - singleMaidr.type.includes('point') - ) { - ylabel = plot.y_group_label; - } else if ( - singleMaidr.type == 'stacked_bar' || - singleMaidr.type == 'stacked_normalized_bar' || - singleMaidr.type == 'dodged_bar' - ) { - ylabel = plot.plotLegend.y; - } - display.displayInfo('y label', ylabel); - control.pressedL = false; - } else if (e.key == 't') { - // T: title - display.displayInfo('title', plot.title); - control.pressedL = false; - } else if (e.key == 's') { - // subtitle - display.displayInfo('subtitle', plot.subtitle); - control.pressedL = false; - } else if (e.key == 'c') { - // caption - display.displayInfo('caption', plot.caption); - control.pressedL = false; - } else if (e.key == 'f') { - display.displayInfo('fill', plot.fill); - control.pressedL = false; - } else if (e.key != 'l') { - control.pressedL = false; + for (let i = 0; i < this.allControlElements.length; i++) { + constants.events.push([ + this.allControlElements[i], + 'keydown', + function (e) { + // init + let pressedTimeout = null; + + // enable / disable prefix mode + if (e.key == 'l') { + control.pressedL = true; + if (pressedTimeout != null) { + clearTimeout(pressedTimeout); + pressedTimeout = null; + } + pressedTimeout = setTimeout(function () { + control.pressedL = false; + }, constants.keypressInterval); + } + + // Prefix mode stuff: L is enabled, look for these keys + if (control.pressedL) { + if (e.key == 'x') { + // X: x label + let xlabel = ''; + if (singleMaidr.type == 'bar' || singleMaidr.type == 'line') { + xlabel = plot.plotLegend.x; + } else if (singleMaidr.type == 'hist') { + xlabel = plot.legendX; + } else if ( + singleMaidr.type == 'heat' || + singleMaidr.type == 'box' || + singleMaidr.type == 'point' || + singleMaidr.type.includes('point') + ) { + xlabel = plot.x_group_label; + } else if ( + singleMaidr.type == 'stacked_bar' || + singleMaidr.type == 'stacked_normalized_bar' || + singleMaidr.type == 'dodged_bar' + ) { + xlabel = plot.plotLegend.x; + } + display.displayInfo('x label', xlabel); + control.pressedL = false; + } else if (e.key == 'y') { + // Y: y label + let ylabel = ''; + if (singleMaidr.type == 'bar' || singleMaidr.type == 'line') { + ylabel = plot.plotLegend.y; + } else if (singleMaidr.type == 'hist') { + ylabel = plot.legendY; + } else if ( + singleMaidr.type == 'heat' || + singleMaidr.type == 'box' || + singleMaidr.type == 'point' || + singleMaidr.type == 'line' || + singleMaidr.type.includes('point') + ) { + ylabel = plot.y_group_label; + } else if ( + singleMaidr.type == 'stacked_bar' || + singleMaidr.type == 'stacked_normalized_bar' || + singleMaidr.type == 'dodged_bar' + ) { + ylabel = plot.plotLegend.y; + } + display.displayInfo('y label', ylabel); + control.pressedL = false; + } else if (e.key == 't') { + // T: title + display.displayInfo('title', plot.title); + control.pressedL = false; + } else if (e.key == 's') { + // subtitle + display.displayInfo('subtitle', plot.subtitle); + control.pressedL = false; + } else if (e.key == 'c') { + // caption + display.displayInfo('caption', plot.caption); + control.pressedL = false; + } else if (e.key == 'f') { + display.displayInfo('fill', plot.fill); + control.pressedL = false; + } else if (e.key != 'l') { + control.pressedL = false; + } } - } - }, - ]); + }, + ]); + } } /** diff --git a/src/js/goto.js b/src/js/goto.js new file mode 100644 index 000000000..122e5d386 --- /dev/null +++ b/src/js/goto.js @@ -0,0 +1,179 @@ +class Goto { + //locations = ['Max Value', 'Min Value', 'Mean', 'Median']; + locations = ['Max Value', 'Min Value']; + + constructor() { + this.popupOpen = false; + this.popupIndex = 0; + this.attachBootstrapModal(); + this.attachEventListeners(); + } + + attachBootstrapModal() { + // Create modal container + const modalHtml = ` + + + `; + + document.body.insertAdjacentHTML('beforeend', modalHtml); + + constants.gotoModal = document.getElementById('goto-modal'); + } + + openPopup() { + this.popupOpen = true; + this.popupIndex = 0; + constants.tabMovement = 0; + + this.whereWasMyFocus = document.activeElement; + document.getElementById('goto-modal').classList.remove('hidden'); + document.getElementById('goto_modal_backdrop').classList.remove('hidden'); + document.querySelector('#goto-modal .close').focus(); + this.updateSelection(this.popupIndex); + } + + closePopup() { + this.popupOpen = false; + // close + document.getElementById('goto-modal').classList.add('hidden'); + document.getElementById('goto_modal_backdrop').classList.add('hidden'); + this.whereWasMyFocus.focus(); + this.whereWasMyFocus = null; + } + + updateSelection(index) { + const items = document.querySelectorAll('#goto-list button'); + Array.from(items).forEach((item, idx) => { + item.classList.toggle('active', idx === index); + }); + } + + attachEventListeners() { + // Open the modal when the user presses 'g' + constants.events.push([ + [constants.chart, constants.brailleInput], + 'keydown', + function (event) { + if (event.key === 'g' && !goto.popupOpen) { + goto.openPopup(); + } + }, + ]); + // arrow keys to navigate the list, enter to select, escape to close + constants.events.push([ + constants.gotoModal, + 'keydown', + function (event) { + if (goto.popupOpen) { + if (event.key === 'ArrowDown') { + event.preventDefault(); + goto.popupIndex = (goto.popupIndex + 1) % goto.locations.length; + goto.updateSelection(goto.popupIndex); + } else if (event.key === 'ArrowUp') { + event.preventDefault(); + goto.popupIndex = + (goto.popupIndex - 1 + goto.locations.length) % + goto.locations.length; + goto.updateSelection(goto.popupIndex); + } else if (event.key === 'Enter') { + event.preventDefault(); + const selectedLocation = goto.locations[goto.popupIndex]; + goto.closePopup(); + goto.goToLocation(selectedLocation); + } else if (event.key === 'Escape') { + goto.closePopup(); + } + } + }, + ]); + } + + goToLocation(loc) { + console.log(`Navigating to: ${loc}`); + // Replace this with your actual navigation logic + + if (loc == 'Max Value') { + if (constants.chartType == 'bar' || constants.chartType == 'hist') { + // get the max value of this array and return the index + let max = Math.max(...plot.plotData); + let index = plot.plotData.indexOf(max); + position.x = index; + control.UpdateAll(); + } else if (constants.chartType == 'line') { + let max = Math.max(...plot.pointValuesY); + let index = plot.pointValuesY.indexOf(max); + position.x = index; + control.UpdateAll(); + } else if (constants.chartType == 'point') { + let max = Math.max(...plot.curvePoints); + let index = plot.curvePoints.indexOf(max); + position.x = index; + control.UpdateAll(); + } else if (constants.chartType == 'heat') { + // here we have a 2d array to search, and we need both y (parent) and x (child) indexes + let max = Math.max(...plot.data.flat()); + let index = plot.data.flat().indexOf(max); + let y = Math.floor(index / plot.data[0].length); + let x = index % plot.data[0].length; + position.x = x; + position.y = y; + control.UpdateAll(); + } + } else if (loc == 'Min Value') { + if (constants.chartType == 'bar' || constants.chartType == 'hist') { + // get the min value of this array and return the index + let min = Math.min(...plot.plotData); + let index = plot.plotData.indexOf(min); + position.x = index; + control.UpdateAll(); + } else if (constants.chartType == 'line') { + let min = Math.min(...plot.pointValuesY); + let index = plot.pointValuesY.indexOf(min); + position.x = index; + control.UpdateAll(); + } else if (constants.chartType == 'point') { + let min = Math.min(...plot.curvePoints); + let index = plot.curvePoints.indexOf(min); + position.x = index; + control.UpdateAll(); + } else if (constants.chartType == 'heat') { + // here we have a 2d array to search, and we need both y (parent) and x (child) indexes + let min = Math.min(...plot.data.flat()); + let index = plot.data.flat().indexOf(min); + let y = Math.floor(index / plot.data[0].length); + let x = index % plot.data[0].length; + position.x = x; + position.y = y; + control.UpdateAll(); + } + } else if (loc == 'Mean') { + } else if (loc == 'Median') { + } + } +} diff --git a/src/js/init.js b/src/js/init.js index 97567bdb5..325f35191 100644 --- a/src/js/init.js +++ b/src/js/init.js @@ -65,6 +65,7 @@ function InitMaidr(thisMaidr) { window.control = new Control(); // this inits the actual chart object and Position window.review = new Review(); window.display = new Display(); + window.goto = new Goto(); window.audio = new Audio(); // blur destruction events