-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Moving general fraction display code to common code, see phetsims/fra…
- Loading branch information
1 parent
3dd8e0e
commit a32ad0b
Showing
2 changed files
with
311 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
// Copyright 2019, University of Colorado Boulder | ||
|
||
/** | ||
* Capable of displaying a mixed-fraction display with three spots that can be filled with numbers (numerator, | ||
* denominator, and a whole number on the left). | ||
* | ||
* @author Jonathan Olson <[email protected]> | ||
*/ | ||
define( require => { | ||
'use strict'; | ||
|
||
// modules | ||
const AlignBox = require( 'SCENERY/nodes/AlignBox' ); | ||
const Bounds2 = require( 'DOT/Bounds2' ); | ||
const sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' ); | ||
const HBox = require( 'SCENERY/nodes/HBox' ); | ||
const Line = require( 'SCENERY/nodes/Line' ); | ||
const PhetFont = require( 'SCENERY_PHET/PhetFont' ); | ||
const Text = require( 'SCENERY/nodes/Text' ); | ||
const VBox = require( 'SCENERY/nodes/VBox' ); | ||
|
||
class MixedFractionNode extends HBox { | ||
/** | ||
* @param {Object} [options] | ||
*/ | ||
constructor( options ) { | ||
super( { | ||
spacing: 5 | ||
} ); | ||
|
||
options = _.extend( { | ||
// {number|null} - Main values for the fraction (can also be changed with setters). The spot will be empty if | ||
// null is the given value. | ||
whole: null, | ||
numerator: null, | ||
denominator: null, | ||
|
||
// {number|null} - If provided, it will ensure that spacing is provided from 0 up to the specified number for | ||
// that slot (e.g. if given maxNumerator:10, it will check the layout size for 0,1,2,...,10 and ensure that | ||
// changing the numerator between those values will not change the layout). | ||
maxWhole: null, | ||
maxNumerator: null, | ||
maxDenominator: null, | ||
|
||
// {ColorDef} | ||
wholeFill: 'black', | ||
numeratorFill: 'black', | ||
denominatorFill: 'black', | ||
separatorFill: 'black', | ||
|
||
// {number} - How far past the numbers' bounds that the vinculum should extend. | ||
vinculumExtension: 0, | ||
|
||
// {string} - The lineCap of the vinculum | ||
vinculumLineCap: 'butt' | ||
}, options ); | ||
|
||
// @private {Text} | ||
this.wholeText = new Text( '1', { | ||
font: new PhetFont( 50 ), | ||
fill: options.wholeFill | ||
} ); | ||
this.numeratorText = new Text( '1', { | ||
font: new PhetFont( 30 ), | ||
fill: options.numeratorFill | ||
} ); | ||
this.denominatorText = new Text( '1', { | ||
font: new PhetFont( 30 ), | ||
fill: options.denominatorFill | ||
} ); | ||
|
||
const maxTextBounds = ( textNode, maxNumber ) => { | ||
return _.reduce( _.range( 0, maxNumber + 1 ), ( bounds, number ) => { | ||
textNode.text = number; | ||
return bounds.union( textNode.bounds ); | ||
}, Bounds2.NOTHING ); | ||
}; | ||
|
||
// @private {Node} | ||
this.wholeContainer = options.maxWhole ? new AlignBox( this.wholeText, { | ||
alignBounds: maxTextBounds( this.wholeText, options.maxWhole ) | ||
} ) : this.wholeText; | ||
this.numeratorContainer = options.maxNumerator ? new AlignBox( this.numeratorText, { | ||
alignBounds: maxTextBounds( this.numeratorText, options.maxNumerator ) | ||
} ) : this.numeratorText; | ||
this.denominatorContainer = options.maxDenominator ? new AlignBox( this.denominatorText, { | ||
alignBounds: maxTextBounds( this.denominatorText, options.maxDenominator ) | ||
} ) : this.denominatorText; | ||
|
||
// @private {Line} | ||
this.vinculumNode = new Line( 0, 0, 10, 0, { | ||
stroke: options.separatorFill, | ||
lineWidth: 2, | ||
lineCap: options.vinculumLineCap | ||
} ); | ||
|
||
// @private {VBox} | ||
this.vbox = new VBox( { | ||
children: [ this.numeratorContainer, this.vinculumNode, this.denominatorContainer ], | ||
spacing: 1 | ||
} ); | ||
|
||
// @private {number|null} | ||
this._whole = options.whole; | ||
this._numerator = options.numerator; | ||
this._denominator = options.denominator; | ||
|
||
// @private {number} | ||
this._vinculumExtension = options.vinculumExtension; | ||
|
||
this.update(); | ||
|
||
this.mutate( options ); | ||
} | ||
|
||
/** | ||
* Updates the view of the fraction when something changes. | ||
* @private | ||
*/ | ||
update() { | ||
const hasWhole = this._whole !== null; | ||
const hasNumerator = this._numerator !== null; | ||
const hasDenominator = this._denominator !== null; | ||
|
||
this.children = [ | ||
...( hasWhole ? [ this.wholeContainer ] : [] ), | ||
...( hasNumerator || hasDenominator ? [ this.vbox ] : [] ) | ||
]; | ||
this.wholeText.text = hasWhole ? this._whole : ' '; | ||
this.numeratorText.text = hasNumerator ? this._numerator : ' '; | ||
this.denominatorText.text = hasDenominator ? this._denominator : ' '; | ||
|
||
this.vinculumNode.x1 = -this._vinculumExtension; | ||
this.vinculumNode.x2 = Math.max( this.numeratorContainer.width, this.denominatorContainer.width ) + 2 + this._vinculumExtension; | ||
} | ||
|
||
/** | ||
* Sets the whole-number part of the mixed fraction. | ||
* @public | ||
* | ||
* @param {number|null} value | ||
*/ | ||
set whole( value ) { | ||
if ( this._whole !== value ) { | ||
this._whole = value; | ||
|
||
this.update(); | ||
} | ||
} | ||
|
||
/** | ||
* Returns the current whole-number part of the mixed fraction. | ||
* @public | ||
* | ||
* @returns {number|null} | ||
*/ | ||
get whole() { | ||
return this._whole; | ||
} | ||
|
||
/** | ||
* Sets the numerator part of the mixed fraction. | ||
* @public | ||
* | ||
* @param {number|null} value | ||
*/ | ||
set numerator( value ) { | ||
if ( this._numerator !== value ) { | ||
this._numerator = value; | ||
|
||
this.update(); | ||
} | ||
} | ||
|
||
/** | ||
* Returns the current numerator part of the mixed fraction. | ||
* @public | ||
* | ||
* @returns {number|null} | ||
*/ | ||
get numerator() { | ||
return this._numerator; | ||
} | ||
|
||
/** | ||
* Sets the denominator part of the mixed fraction. | ||
* @public | ||
* | ||
* @param {number|null} value | ||
*/ | ||
set denominator( value ) { | ||
if ( this._denominator !== value ) { | ||
this._denominator = value; | ||
|
||
this.update(); | ||
} | ||
} | ||
|
||
/** | ||
* Returns the current denominator part of the mixed fraction. | ||
* @public | ||
* | ||
* @returns {number|null} | ||
*/ | ||
get denominator() { | ||
return this._denominator; | ||
} | ||
} | ||
|
||
return sceneryPhet.register( 'MixedFractionNode', MixedFractionNode ); | ||
} ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// Copyright 2018, University of Colorado Boulder | ||
|
||
/** | ||
* Displays a fraction based on a numerator/denominator Property pair. | ||
* | ||
* @author Jonathan Olson <[email protected]> | ||
*/ | ||
define( require => { | ||
'use strict'; | ||
|
||
// modules | ||
const Enumeration = require( 'PHET_CORE/Enumeration' ); | ||
const sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' ); | ||
const MixedFractionNode = require( 'SCENERY_PHET/MixedFractionNode' ); | ||
|
||
class PropertyFractionNode extends MixedFractionNode { | ||
/** | ||
* @param {Property.<number>} numeratorProperty | ||
* @param {Property.<number>} denominatorProperty | ||
* @param {Object} [options] | ||
*/ | ||
constructor( numeratorProperty, denominatorProperty, options ) { | ||
options = _.extend( { | ||
// {PropertyFractionNode.DisplayType} | ||
type: PropertyFractionNode.DisplayType.IMPROPER, | ||
|
||
// {boolean} | ||
simplify: false, | ||
|
||
// {boolean} | ||
showZeroImproperFraction: true | ||
}, options ); | ||
|
||
assert && assert( PropertyFractionNode.DisplayType.includes( options.type ) ); | ||
assert && assert( typeof options.simplify === 'boolean' ); | ||
|
||
super( options ); | ||
|
||
// @private {Property.<number>} | ||
this.numeratorProperty = numeratorProperty; | ||
this.denominatorProperty = denominatorProperty; | ||
|
||
// @private {function} | ||
this.propertyListener = this.updateFromProperties.bind( this ); | ||
|
||
// @private {PropertyFractionNode.DisplayType} | ||
this.type = options.type; | ||
|
||
// @private {boolean} | ||
this.simplify = options.simplify; | ||
this.showZeroImproperFraction = options.showZeroImproperFraction; | ||
|
||
this.numeratorProperty.lazyLink( this.propertyListener ); | ||
this.denominatorProperty.lazyLink( this.propertyListener ); | ||
this.updateFromProperties(); | ||
} | ||
|
||
/** | ||
* Updates our display based on our Property values. | ||
* @private | ||
*/ | ||
updateFromProperties() { | ||
const numerator = this.numeratorProperty.value; | ||
const denominator = this.denominatorProperty.value; | ||
|
||
const hasWhole = this.type === PropertyFractionNode.DisplayType.IMPROPER || !this.simplify || numerator === 0 || numerator >= denominator; | ||
const hasFraction = this.type === PropertyFractionNode.DisplayType.IMPROPER || !this.simplify || ( this.showZeroImproperFraction ? numerator > 0 : ( numerator % denominator !== 0 ) ); | ||
|
||
this.denominator = hasFraction ? denominator : null; | ||
|
||
if ( this.type === PropertyFractionNode.DisplayType.MIXED ) { | ||
this.whole = hasWhole ? Math.floor( numerator / denominator ) : null; | ||
this.numerator = hasFraction ? ( numerator % denominator ) : null; | ||
} | ||
else { | ||
this.numerator = numerator; | ||
} | ||
} | ||
|
||
/** | ||
* Releases references. | ||
* @public | ||
* @override | ||
*/ | ||
dispose() { | ||
this.numeratorProperty.unlink( this.propertyListener ); | ||
this.denominatorProperty.unlink( this.propertyListener ); | ||
|
||
super.dispose(); | ||
} | ||
} | ||
|
||
// @public {Enumeration} | ||
PropertyFractionNode.DisplayType = new Enumeration( [ | ||
'IMPROPER', // e.g. 3/2 | ||
'MIXED' // e.g. 1 1/2 | ||
] ); | ||
|
||
return sceneryPhet.register( 'PropertyFractionNode', PropertyFractionNode ); | ||
} ); |