From bf208c528e338258b0c1ac6706d6055031db2169 Mon Sep 17 00:00:00 2001 From: Jesse Greenberg Date: Thu, 8 Feb 2018 02:23:22 -0700 Subject: [PATCH] instrument ResistanceInAWireScreenView and FormulaNode with a11y, see #126 --- .../ResistanceInAWireA11yStrings.js | 36 ++++ .../ResistanceInAWireConstants.js | 44 +++++ .../view/AccessibleSummaryNode.js | 1 - js/resistance-in-a-wire/view/FormulaNode.js | 155 ++++++++++++++++-- .../view/ResistanceInAWireScreenView.js | 30 +++- 5 files changed, 247 insertions(+), 19 deletions(-) diff --git a/js/resistance-in-a-wire/ResistanceInAWireA11yStrings.js b/js/resistance-in-a-wire/ResistanceInAWireA11yStrings.js index fcc60f6..a73634d 100644 --- a/js/resistance-in-a-wire/ResistanceInAWireA11yStrings.js +++ b/js/resistance-in-a-wire/ResistanceInAWireA11yStrings.js @@ -35,6 +35,42 @@ define( function( require ) { summaryAreaPatternString: { value: 'area, A is {{value}} centimeters squared', }, + resistanceEquationString: { + value: 'Resistance Equation' + }, + resistanceEquationDescriptionString: { + value: 'Resistance, R, is equal to resistivity, rho, times length, L, over area, A.' + }, + rhoLAndAComparablePatternString: { + value: 'Size of letter R is {{rToAll}} the size of the letter rho, letter L, and letter A.' + }, + lAndAComparablePatternString: { + value: 'Size of letter R is {{rToRho}} the size of letter rho, and {{rToLAndA}} than letter L and letter A.' + }, + noneComparablePatternString: { + value: 'Size of letter R is {{rToRho}} the size of letter rho, {{rToL}} letter L, and {{rToA}} letter A.' + }, + muchMuchSmallerThanString: { + value: 'much much smaller than', + }, + muchSmallerThanString: { + value: 'much smaller than', + }, + slightlySmallerThanString: { + value: 'slightly smaller than', + }, + comparableToString: { + value: 'comparable to', + }, + slightlyLargerThanString: { + value: 'slightly larger than', + }, + muchLargerThanString: { + value: 'much larger than', + }, + muchMuchLargerThanString: { + value: 'much much larger than', + }, resistivityUnitsPatternString: { value: '{{value}} ohm centimeters', }, diff --git a/js/resistance-in-a-wire/ResistanceInAWireConstants.js b/js/resistance-in-a-wire/ResistanceInAWireConstants.js index e3a559b..dad45b0 100644 --- a/js/resistance-in-a-wire/ResistanceInAWireConstants.js +++ b/js/resistance-in-a-wire/ResistanceInAWireConstants.js @@ -11,8 +11,19 @@ define( function( require ) { // modules var PhetFont = require( 'SCENERY_PHET/PhetFont' ); var RangeWithValue = require( 'DOT/RangeWithValue' ); + var Range = require( 'DOT/Range' ); + var ResistanceInAWireA11yStrings = require( 'RESISTANCE_IN_A_WIRE/resistance-in-a-wire/ResistanceInAWireA11yStrings' ); var resistanceInAWire = require( 'RESISTANCE_IN_A_WIRE/resistanceInAWire' ); + // a11y strings + var muchMuchSmallerThanString = ResistanceInAWireA11yStrings.muchMuchSmallerThanString.value; + var muchSmallerThanString = ResistanceInAWireA11yStrings.muchSmallerThanString.value; + var slightlySmallerThanString = ResistanceInAWireA11yStrings.slightlySmallerThanString.value; + var comparableToString = ResistanceInAWireA11yStrings.comparableToString.value; + var slightlyLargerThanString = ResistanceInAWireA11yStrings.slightlyLargerThanString.value; + var muchLargerThanString = ResistanceInAWireA11yStrings.muchLargerThanString.value; + var muchMuchLargerThanString = ResistanceInAWireA11yStrings.muchMuchLargerThanString.value; + var ResistanceInAWireConstants = { // colors @@ -54,6 +65,39 @@ define( function( require ) { resistance < 0.001 ? 4 : // when less than 0.001, show 4 decimals, see #125 resistance < 1 ? 3 : // when less than 1, show 3 decimal places, see #125 2; // Numbers less than 10 show 2 decimal points, like 8.35 + }, + + + // a11y - used to map relative scale magnitudes of the letters to relative size description + RELATIVE_SIZE_MAP: { + muchMuchSmaller: { + description: muchMuchSmallerThanString, + range: new Range( 0, 0.1) + }, + muchSmaller: { + description: muchSmallerThanString, + range: new Range( 0.1, 0.4 ) + }, + slightlySmaller: { + description: slightlySmallerThanString, + range: new Range( 0.4, 0.7 ) + }, + comparable: { + description: comparableToString, + range: new Range( 0.7, 1.3 ) + }, + slightlyLarger: { + description: slightlyLargerThanString, + range: new Range( 1.3, 2 ) + }, + muchLarger: { + description: muchLargerThanString, + range: new Range( 2, 20 ) + }, + muchMuchLarger: { + description: muchMuchLargerThanString, + range: new Range( 20, Number.MAX_VALUE ) + } } }; diff --git a/js/resistance-in-a-wire/view/AccessibleSummaryNode.js b/js/resistance-in-a-wire/view/AccessibleSummaryNode.js index 9f22de6..b9fb506 100644 --- a/js/resistance-in-a-wire/view/AccessibleSummaryNode.js +++ b/js/resistance-in-a-wire/view/AccessibleSummaryNode.js @@ -96,7 +96,6 @@ define( function( require ) { // the precision might change during interaction, get precision if property is a function var precision = typeof item.precision === 'number' ? item.precision : item.precision( value ); - console.log( item.patternString ); item.node.accessibleLabelAsHTML = StringUtils.fillIn( item.patternString, { value: Util.toFixed( value, precision ) } ); diff --git a/js/resistance-in-a-wire/view/FormulaNode.js b/js/resistance-in-a-wire/view/FormulaNode.js index 345488c..1fc808f 100644 --- a/js/resistance-in-a-wire/view/FormulaNode.js +++ b/js/resistance-in-a-wire/view/FormulaNode.js @@ -22,7 +22,9 @@ define( function( require ) { var resistanceInAWire = require( 'RESISTANCE_IN_A_WIRE/resistanceInAWire' ); var ResistanceInAWireConstants = require( 'RESISTANCE_IN_A_WIRE/resistance-in-a-wire/ResistanceInAWireConstants' ); var Shape = require( 'KITE/Shape' ); + var ResistanceInAWireA11yStrings = require( 'RESISTANCE_IN_A_WIRE/resistance-in-a-wire/ResistanceInAWireA11yStrings' ); var Text = require( 'SCENERY/nodes/Text' ); + var StringUtils = require( 'PHETCOMMON/util/StringUtils' ); var Vector2 = require( 'DOT/Vector2' ); // strings @@ -31,6 +33,20 @@ define( function( require ) { var resistanceSymbolString = require( 'string!RESISTANCE_IN_A_WIRE/resistanceSymbol' ); var resistivitySymbolString = require( 'string!RESISTANCE_IN_A_WIRE/resistivitySymbol' ); + // a11y strings + var resistanceEquationString = ResistanceInAWireA11yStrings.resistanceEquationString.value; + var resistanceEquationDescriptionString = ResistanceInAWireA11yStrings.resistanceEquationDescriptionString.value; + var rhoLAndAComparablePatternString = ResistanceInAWireA11yStrings.rhoLAndAComparablePatternString.value; + var lAndAComparablePatternString = ResistanceInAWireA11yStrings.lAndAComparablePatternString.value; + var noneComparablePatternString = ResistanceInAWireA11yStrings.noneComparablePatternString.value; + + // constants - rather than keep a reference to each letter node, a map from key to scale magnitude is used + // to track letter scales + var RESISTANCE_KEY = 'resistance'; + var RESISTIVITY_KEY = 'resistivity'; + var AREA_KEY = 'area'; + var LENGTH_KEY = 'length'; + /** * @param {ResistanceInAWireModel} model * @param {Tandem} tandem @@ -39,7 +55,16 @@ define( function( require ) { */ function FormulaNode( model, tandem, options ) { - Node.call( this, { tandem: tandem } ); + Node.call( this, { + tandem: tandem, + + // a11y + tagName: 'div', + labelTagName: 'h3', + accessibleLabel: resistanceEquationString, + prependLabels: true, + accessibleDescriptionAsHTML: resistanceEquationDescriptionString + } ); // equals sign, hard coded var equalsSignText = new Text( '=', { // we never internationalize the '=' sign @@ -49,6 +74,13 @@ define( function( require ) { tandem: tandem.createTandem( 'equalsSign' ) } ); + // maps identifier to scale magnitude + this.a11yScaleMap = {}; + this.a11yScaleMap[ RESISTANCE_KEY ] = 0; + this.a11yScaleMap[ RESISTIVITY_KEY ] = 0; + this.a11yScaleMap[ AREA_KEY ] = 0; + this.a11yScaleMap[ LENGTH_KEY ] = 0; + // An array of attributes related to text var symbolTexts = [ { label: resistanceSymbolString, @@ -56,28 +88,34 @@ define( function( require ) { property: model.resistanceProperty, color: ResistanceInAWireConstants.RED_COLOR, cappedSize: true, // To make sure that the 'R' doesn't get too big, see https://github.com/phetsims/resistance-in-a-wire/issues/28 - tandem: tandem.createTandem( 'resistanceSymbol' ) + tandem: tandem.createTandem( 'resistanceSymbol' ), + scaleKey: RESISTANCE_KEY }, { label: resistivitySymbolString, center: new Vector2( equalsSignText.centerX + 120, -90 ), property: model.resistivityProperty, color: ResistanceInAWireConstants.BLUE_COLOR, - tandem: tandem.createTandem( 'resistivitySymbol' ) + tandem: tandem.createTandem( 'resistivitySymbol' ), + scaleKey: RESISTIVITY_KEY }, { label: lengthSymbolString, center: new Vector2( equalsSignText.centerX + 220, -90 ), property: model.lengthProperty, color: ResistanceInAWireConstants.BLUE_COLOR, - tandem: tandem.createTandem( 'lengthSymbol' ) + tandem: tandem.createTandem( 'lengthSymbol' ), + scaleKey: LENGTH_KEY }, { label: areaSymbolString, center: new Vector2( equalsSignText.centerX + 170, 90 ), property: model.areaProperty, color: ResistanceInAWireConstants.BLUE_COLOR, - tandem: tandem.createTandem( 'areaSymbol' ) + tandem: tandem.createTandem( 'areaSymbol' ), + scaleKey: AREA_KEY } ]; - var lettersNode = new Node(); + // parent for all letters in the equation - given a 'p' tag for a11y because this node will hold the relative + // size description, see getRelativeSizeDescription() + var lettersNode = new Node( { tagName: 'p' } ); // if we are on a safari platform render with canvas to prevent these issues, but only on safari because // canvas doesn't perform as well on other browsers @@ -86,6 +124,7 @@ define( function( require ) { if ( platform.safari ) { lettersNode.renderer = 'canvas'; } // dynamically sized text + var self = this; symbolTexts.forEach( function( entry ) { var text = new Text( entry.label, { @@ -106,11 +145,20 @@ define( function( require ) { // Set the scale based on the default value of the property; normalize the scale for all letters. var scale = 7 / entry.property.value; // empirically determined '7' - // The size of the formula letter will scale with the value the letter represents. This does not need an unlink - // because it exists for the life of the sim. + // The size of the formula letter will scale with the value the letter represents. The accessible description for + // the equation will also update. This does not need an unlink because it exists for the life of the sim. entry.property.link( function( value ) { - letterNode.setScaleMagnitude( scale * value + 1 ); + var scaleMagnitude = scale * value + 1; + letterNode.setScaleMagnitude( scaleMagnitude ); letterNode.center = entry.center; + + // for lookup when describing relative letter sizes + self.a11yScaleMap[ entry.scaleKey ] = scaleMagnitude; + } ); + + // linked lazily so that relative scales are defined + entry.property.lazyLink( function() { + lettersNode.setAccessibleDescription( self.getRelativeSizeDescription() ); } ); } ); @@ -127,9 +175,96 @@ define( function( require ) { } ) ); this.mutate( options ); + + // a11y - set the initial description + lettersNode.setAccessibleDescription( self.getRelativeSizeDescription() ); } resistanceInAWire.register( 'FormulaNode', FormulaNode ); - return inherit( Node, FormulaNode ); + inherit( Node, FormulaNode, { + + /** + * Get a description of the relative size of various letters. Size of each letter is described relative to + * resistance R. When all or L and A letters are the same size, a simplified sentence is used to reduce verbosity, + * so this function might return something like: + * + * "Size of letter R is comparable to the size of letter rho, letter L, and letter A" or + * "Size of letter R is much larger than the size of letter rho, and slightly larger than letter L and letter A." or + * "Size of letter R is much smaller than letter rho, comparable to letter L, and much much larger than letter A." + * + * @return {string} + * @a11y + */ + getRelativeSizeDescription: function() { + var resistanceScale = this.a11yScaleMap[ RESISTANCE_KEY ]; + var resistivityScale = this.a11yScaleMap[ RESISTIVITY_KEY ]; + var areaScale = this.a11yScaleMap[ AREA_KEY ]; + var lengthScale = this.a11yScaleMap[ LENGTH_KEY ]; + + var rToRho = resistanceScale / resistivityScale; + var rToA = resistanceScale / areaScale; + var rToL = resistanceScale / lengthScale; + var lToA = lengthScale / areaScale; + var lToRho = lengthScale / resistivityScale; + + var rToRhoDescription = getRelativeSizeDescription( rToRho ); + var roTLDescription = getRelativeSizeDescription( rToL ); + var rToADescription = getRelativeSizeDescription( rToA ); + + var description; + var comparableRange = ResistanceInAWireConstants.RELATIVE_SIZE_MAP.comparable.range; + if ( comparableRange.contains( lToA ) && comparableRange.contains( lToRho ) ) { + + // all right hand side letters are comparable in size + description = StringUtils.fillIn( rhoLAndAComparablePatternString, { + rToAll: rToRhoDescription // any size description will work + } ); + } + else if ( comparableRange.contains( lToA ) ) { + + // L and A are comparable, so they are the same size relative to R + description = StringUtils.fillIn( lAndAComparablePatternString, { + rToRho: rToRhoDescription, + rToLAndA: roTLDescription // either length or area relative descriptions will work + } ); + } + else { + + // all relative sizes could be unique + description = StringUtils.fillIn( noneComparablePatternString, { + rToRho: rToRhoDescription, + rToL: roTLDescription, + rToA: rToADescription + } ); + } + + return description; + } + } ); + + /** + * Get a relative size description from a relative scale, used to describe letters relative to each other. Will return + * something like + * + * "comparable to" or + * "much much larger than" + * + * @param {number} relativeScale + * @return {string} + */ + var getRelativeSizeDescription = function( relativeScale ) { + + // get described ranges of each relative scale + var keys = Object.keys( ResistanceInAWireConstants.RELATIVE_SIZE_MAP ); + for ( var i = 0; i < keys.length; i++ ) { + var relativeEntry = ResistanceInAWireConstants.RELATIVE_SIZE_MAP[ keys[ i ] ]; + + if ( relativeEntry.range.contains( relativeScale ) ) { + return relativeEntry.description; + } + } + }; + + return FormulaNode; } ); \ No newline at end of file diff --git a/js/resistance-in-a-wire/view/ResistanceInAWireScreenView.js b/js/resistance-in-a-wire/view/ResistanceInAWireScreenView.js index 9923f23..9281d5b 100644 --- a/js/resistance-in-a-wire/view/ResistanceInAWireScreenView.js +++ b/js/resistance-in-a-wire/view/ResistanceInAWireScreenView.js @@ -11,6 +11,7 @@ define( function( require ) { // modules var AccessibleSummaryNode = require( 'RESISTANCE_IN_A_WIRE/resistance-in-a-wire/view/AccessibleSummaryNode' ); + var AccessibleSectionNode = require( 'SCENERY_PHET/accessibility/AccessibleSectionNode' ); var ArrowNode = require( 'SCENERY_PHET/ArrowNode' ); var ControlPanel = require( 'RESISTANCE_IN_A_WIRE/resistance-in-a-wire/view/ControlPanel' ); var FocusHighlightPath = require( 'SCENERY/accessibility/FocusHighlightPath' ); @@ -22,6 +23,11 @@ define( function( require ) { var ScreenView = require( 'JOIST/ScreenView' ); var Shape = require( 'KITE/Shape' ); var WireNode = require( 'RESISTANCE_IN_A_WIRE/resistance-in-a-wire/view/WireNode' ); + var JoistA11yStrings = require( 'JOIST/JoistA11yStrings' ); + + // a11y strings + var playAreaString = JoistA11yStrings.playAreaString; + var controlPanelString = JoistA11yStrings.controlPanelString; /** * @param {ResistanceInAWireModel} model @@ -35,8 +41,16 @@ define( function( require ) { } ); // a11y - Create and add the summary for this simulation, the first thing screen reader users encounter - var summaryNode = new AccessibleSummaryNode( model ); - this.addChild( summaryNode ); + var a11ySummaryNode = new AccessibleSummaryNode( model ); + this.addChild( a11ySummaryNode ); + + // a11y - the play area for this sim, containing elements that are significant to the pedagogy of the sim + var a11yPlayAreaNode = new AccessibleSectionNode( playAreaString ); + this.addChild( a11yPlayAreaNode ); + + // a11y - the control panel for this sim, containing supplemental controls + var a11yControlPanelNode = new AccessibleSectionNode( controlPanelString ); + this.addChild( a11yControlPanelNode ); // Create the control panel with sliders that change the values of the equation's variables. Hard coded var controlPanel = new ControlPanel( model, tandem.createTandem( 'controlPanel' ), { @@ -49,14 +63,14 @@ define( function( require ) { centerX: controlPanel.left / 2, centerY: 190 } ); - this.addChild( formulaNode ); + a11yPlayAreaNode.addChild( formulaNode ); // Create the wire display to represent the formula var wireNode = new WireNode( model, tandem.createTandem( 'wireNode' ), { centerX: formulaNode.centerX, centerY: formulaNode.centerY + 270 } ); - this.addChild( wireNode ); + a11yPlayAreaNode.addChild( wireNode ); var tailX = wireNode.centerX - ResistanceInAWireConstants.TAIL_LENGTH / 2; var tipX = wireNode.centerX + ResistanceInAWireConstants.TAIL_LENGTH / 2; @@ -72,7 +86,7 @@ define( function( require ) { lineWidth: 1, tandem: tandem.createTandem( 'arrowNode' ) } ); - this.addChild( arrowNode ); + a11yPlayAreaNode.addChild( arrowNode ); var resetAllButton = new ResetAllButton( { listener: function() { model.reset(); }, @@ -81,7 +95,7 @@ define( function( require ) { bottom: this.layoutBounds.bottom - 20, tandem: tandem.createTandem( 'resetAllButton' ) } ); - this.addChild( resetAllButton ); + a11yControlPanelNode.addChild( resetAllButton ); // the outer stroke of the ResetAllButton focus highlight is black so that it is visible when the equation // resistance letter grows too large @@ -90,10 +104,10 @@ define( function( require ) { resetAllButton.focusHighlight = new FocusHighlightPath( highlightShape , { outerStroke: 'black' } ); // add the control panel last so it is always on top. - this.addChild( controlPanel ); + a11yPlayAreaNode.addChild( controlPanel ); // a11y - the reset all button should come last, control panel first - this.accessibleOrder = [ summaryNode, controlPanel ]; + this.accessibleOrder = [ a11ySummaryNode, a11yPlayAreaNode ]; } resistanceInAWire.register( 'ResistanceInAWireScreenView', ResistanceInAWireScreenView );