Skip to content

Commit

Permalink
Moving general fraction display code to common code, see phetsims/fra…
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanolson committed Jun 19, 2019
1 parent 3dd8e0e commit a32ad0b
Show file tree
Hide file tree
Showing 2 changed files with 311 additions and 0 deletions.
211 changes: 211 additions & 0 deletions js/MixedFractionNode.js
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 );
} );
100 changes: 100 additions & 0 deletions js/PropertyFractionNode.js
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 );
} );

0 comments on commit a32ad0b

Please sign in to comment.