Skip to content

Commit

Permalink
instrumentation of SoluteIO, #92
Browse files Browse the repository at this point in the history
  • Loading branch information
pixelzoom committed Jan 30, 2020
1 parent 3cefccb commit 799c136
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 75 deletions.
130 changes: 62 additions & 68 deletions js/common/model/Solute.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ define( require => {
const phScale = require( 'PH_SCALE/phScale' );
const PHScaleColors = require( 'PH_SCALE/common/PHScaleColors' );
const PHScaleConstants = require( 'PH_SCALE/common/PHScaleConstants' );
const SoluteIO = require( 'PH_SCALE/common/model/SoluteIO' );
const Tandem = require( 'TANDEM/Tandem' );
const Water = require( 'PH_SCALE/common/model/Water' );

Expand All @@ -36,41 +37,45 @@ define( require => {
class Solute extends PhetioObject {

/**
* @param {string} name
* @param {number} pH
* @param {stockColor:Color, [dilutedColor]:Color, colorStop:{color:{Color}, [ratio]:Number} } colorScheme
* @param {string} name - the name of the solute, displayed to the user
* @param {number} pH - the pH of the solute
* @param {Color} stockColor - color of the solute in stock solution (no dilution)
* @param {Object} [options]
*
* colorScheme is an object literal with these properties:
* stockColor: color of the solute in stock solution (no dilution)
* dilutedColor: color when the solute is barely present in solution (fully diluted), optional, defaults to Water.color
* colorStop: color when soluteVolume/totalVolume === ratio, used to smooth out some color transitions if provided, optional
* ratio: ratio for the color-stop, (0,1) exclusive, optional, defaults to 0.25
*
*/
constructor( name, pH, colorScheme, options ) {
constructor( name, pH, stockColor, options ) {

assert && assert( PHScaleConstants.PH_RANGE.contains( pH ), `invalid pH: ${pH}` );
assert && assert( stockColor instanceof Color, 'invalid color' );

options = merge( {

// {Color} color when the solute is barely present in solution (fully diluted)
dilutedColor: Water.color,

// {Color|null} optional color use to smooth out some color transitions
colorStopColor: null,

// {number} ratio for the color-stop, (0,1) exclusive, ignored if colorStopColor is null
colorStopRatio: 0.25,

// phet-io
tandem: Tandem.REQUIRED
tandem: Tandem.OPTIONAL, //TODO #92 should be REQUIRED
phetioState: false,
phetioType: SoluteIO
}, options );

super( options );

if ( !PHScaleConstants.PH_RANGE.contains( pH ) ) {
throw new Error( 'Solute constructor, pH value is out of range: ' + pH );
}

this.name = name; // @public
this.pH = pH; // @public

// unpack the colors to make accessing them more convenient in client code
this.stockColor = colorScheme.stockColor; // @public
this.dilutedColor = colorScheme.dilutedColor || Water.color; // @private
this.colorStop = colorScheme.colorStop; // @private, optional, color computation will ignore it if undefined
if ( this.colorStop ) {
this.colorStop.ratio = this.colorStop.ratio || 0.25;
}
// @public (read-only)
this.name = name;
this.pH = pH;
this.stockColor = stockColor;

// @private
this.dilutedColor = options.dilutedColor;
this.colorStop = options.colorStop;
this.colorStopRatio = options.colorStopRatio;
}

/**
Expand All @@ -79,7 +84,7 @@ define( require => {
* @public
*/
toString() {
return 'Solution[name:' + this.name + ' pH:' + this.pH + ']';
return `Solution[name:${this.name}, pH:${this.pH}]`;
}

/**
Expand All @@ -92,12 +97,13 @@ define( require => {
assert && assert( ratio >= 0 && ratio <= 1 );
let color;
if ( this.colorStop ) {
// solute has an optional color-stop
if ( ratio > this.colorStop.ratio ) {
color = Color.interpolateRGBA( this.colorStop.color, this.stockColor, ( ratio - this.colorStop.ratio ) / ( 1 - this.colorStop.ratio) );
if ( ratio > this.colorStopRatio ) {
color = Color.interpolateRGBA( this.colorStopColor, this.stockColor,
( ratio - this.colorStopRatio ) / ( 1 - this.colorStopRatio) );
}
else {
color = Color.interpolateRGBA( this.dilutedColor, this.colorStop.color, ratio / this.colorStop.ratio );
color = Color.interpolateRGBA( this.dilutedColor, this.colorStopColor,
ratio / this.colorStopRatio );
}
}
else {
Expand All @@ -113,82 +119,70 @@ define( require => {
* @public
*/
static createCustom( pH ) {
return new Solute( choiceCustomString, pH, { stockColor: PHScaleColors.WATER } );
return new Solute( choiceCustomString, pH, PHScaleColors.WATER );
}
}

// 'real world' immutable solutions
// 'real world' immutable solutions -------------------------------------------------------

// tandem for all static instances of Solute, which are used across all screens
//TODO #92 what should this be? why is there no global.solutes in the Studio tree?
const SOLUTES_TANDEM = Tandem.GLOBAL.createTandem( 'solutes' );
//TODO #92 is global.model.solutes the correct place for this?
const SOLUTES_TANDEM = Tandem.GLOBAL.createTandem( 'model').createTandem( 'solutes' );

Solute.DRAIN_CLEANER = new Solute( choiceDrainCleanerString, 13, {
stockColor: new Color( 255, 255, 0 ),
colorStop: { color: new Color( 255, 255, 204 ) },
Solute.DRAIN_CLEANER = new Solute( choiceDrainCleanerString, 13, new Color( 255, 255, 0 ), {
colorStopColor: new Color( 255, 255, 204 ),
tandem: SOLUTES_TANDEM.createTandem( 'drainCleaner' )
} );

Solute.HAND_SOAP = new Solute( choiceHandSoapString, 10, {
stockColor: new Color( 224, 141, 242 ),
colorStop: { color: new Color( 232, 204, 255 ) },
Solute.HAND_SOAP = new Solute( choiceHandSoapString, 10, new Color( 224, 141, 242 ), {
colorStopColor: new Color( 232, 204, 255 ),
tandem: SOLUTES_TANDEM.createTandem( 'handSoap' )
} );

Solute.BLOOD = new Solute( choiceBloodString, 7.4, {
stockColor: new Color( 211, 79, 68 ),
colorStop: { color: new Color( 255, 207, 204 ) },
Solute.BLOOD = new Solute( choiceBloodString, 7.4, new Color( 211, 79, 68 ), {
colorStopColor: new Color( 255, 207, 204 ),
tandem: SOLUTES_TANDEM.createTandem( 'blood' )
} );

Solute.SPIT = new Solute( choiceSpitString, 7.4, {
stockColor: new Color( 202, 240, 239 ),
tandem: SOLUTES_TANDEM.createTandem( 'SPIT' )
Solute.SPIT = new Solute( choiceSpitString, 7.4, new Color( 202, 240, 239 ), {
tandem: SOLUTES_TANDEM.createTandem( 'spit' )
} );

Solute.WATER = new Solute( Water.name, Water.pH, {
stockColor: Water.color,
Solute.WATER = new Solute( Water.name, Water.pH, Water.color, {
tandem: SOLUTES_TANDEM.createTandem( 'water' )
} );

Solute.MILK = new Solute( choiceMilkString, 6.5, {
stockColor: new Color( 250, 250, 250 ),
Solute.MILK = new Solute( choiceMilkString, 6.5, new Color( 250, 250, 250 ), {
tandem: SOLUTES_TANDEM.createTandem( 'milk' )
} );

Solute.CHICKEN_SOUP = new Solute( choiceChickenSoupString, 5.8, {
stockColor: new Color( 255, 240, 104 ),
colorStop: { color: new Color( 255, 250, 204 ) },
Solute.CHICKEN_SOUP = new Solute( choiceChickenSoupString, 5.8, new Color( 255, 240, 104 ), {
colorStopColor: new Color( 255, 250, 204 ),
tandem: SOLUTES_TANDEM.createTandem( 'chickenSoup' )
} );

Solute.COFFEE = new Solute( choiceCoffeeString, 5, {
stockColor: new Color( 164, 99, 7 ),
colorStop: { color: new Color( 255, 240, 204 ) },
Solute.COFFEE = new Solute( choiceCoffeeString, 5, new Color( 164, 99, 7 ), {
colorStopColor: new Color( 255, 240, 204 ),
tandem: SOLUTES_TANDEM.createTandem( 'coffee' )
} );

Solute.ORANGE_JUICE = new Solute( choiceOrangeJuiceString, 3.5, {
stockColor: new Color( 255, 180, 0 ),
colorStop: { color: new Color( 255, 242, 204 ) },
Solute.ORANGE_JUICE = new Solute( choiceOrangeJuiceString, 3.5, new Color( 255, 180, 0 ), {
colorStopColor: new Color( 255, 242, 204 ),
tandem: SOLUTES_TANDEM.createTandem( 'orangeJuice' )
} );

Solute.SODA = new Solute( choiceSodaString, 2.5, {
stockColor: new Color( 204, 255, 102 ),
colorStop: { color: new Color( 238, 255, 204 ) },
Solute.SODA = new Solute( choiceSodaString, 2.5, new Color( 204, 255, 102 ), {
colorStopColor: new Color( 238, 255, 204 ),
tandem: SOLUTES_TANDEM.createTandem( 'soda' )
} );

Solute.VOMIT = new Solute( choiceVomitString, 2, {
stockColor: new Color( 255, 171, 120 ),
colorStop: { color: new Color( 255, 224, 204 ) },
Solute.VOMIT = new Solute( choiceVomitString, 2, new Color( 255, 171, 120 ), {
colorStopColor: new Color( 255, 224, 204 ),
tandem: SOLUTES_TANDEM.createTandem( 'vomit' )
} );

Solute.BATTERY_ACID = new Solute( choiceBatteryAcidString, 1, {
stockColor: new Color( 255, 255, 0 ),
colorStop: { color: new Color( 255, 224, 204 ) },
Solute.BATTERY_ACID = new Solute( choiceBatteryAcidString, 1, new Color( 255, 255, 0 ), {
colorStopColor: new Color( 255, 224, 204 ),
tandem: SOLUTES_TANDEM.createTandem( 'batteryAcid' )
} );

Expand Down
8 changes: 2 additions & 6 deletions js/common/model/SoluteIO.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,14 @@ define( require => {
const ObjectIO = require( 'TANDEM/types/ObjectIO' );
const phScale = require( 'PH_SCALE/phScale' );
const ReferenceIO = require( 'TANDEM/types/ReferenceIO' );
const Solute = require( 'PH_SCALE/common/model/Solute' );

// Objects are statically created, use reference equality to look up instances for toStateObject/fromStateObject
class SoluteIO extends ReferenceIO {}

SoluteIO.documentation = 'the selected solute';
SoluteIO.typeName = 'SoluteIO';
SoluteIO.validator = { isValidValue: v => v instanceof Solute };
SoluteIO.validator = { isValidValue: value => value instanceof Object }; //TODO #92 require(Solute) is cyclic
ObjectIO.validateSubtype( SoluteIO ); //TODO #92 is this the same info as SoluteIO.validator?

//TODO #92 why does Studio show all values as "phScale.requiredTandem" ?

return phScale.register( 'SoluteIO', SoluteIO );
} );

} );
2 changes: 1 addition & 1 deletion js/common/view/PHMeterNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ define( require => {
} );
pHValueProperty.link( pH => {
if ( pH !== null && pH !== solution.pHProperty.get() ) {
solution.soluteProperty.set( Solute.createCustom( pH ) );
solution.soluteProperty.set( Solute.createCustom( pH ) ); //TODO #92 a new solute is created for every pH change
}
upArrowNode.enabled = ( pH < PHScaleConstants.PH_RANGE.max );
downArrowNode.enabled = ( pH > PHScaleConstants.PH_RANGE.min );
Expand Down
1 change: 1 addition & 0 deletions js/common/view/graph/GraphIndicatorDragHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ define( require => {
pH = Utils.clamp( pH, PHScaleConstants.PH_RANGE.min, PHScaleConstants.PH_RANGE.max );

// Instantiate a new 'custom' solute with the desired pH, and use it with the solution.
//TODO #92 a new solute is created for every pH change
solution.soluteProperty.set( Solute.createCustom( pH ) );
}
}
Expand Down
2 changes: 2 additions & 0 deletions js/mysolution/model/MySolutionModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ define( require => {
// @public Beaker, everything else is positioned relative to it. Offset constants were set by visual inspection.
this.beaker = new Beaker( new Vector2( 750, 580 ), new Dimension2( 450, 300 ) );

//TODO #92 this is problematic, we probably do not want to instrument sub-elements soluteVolumeProperty and waterVolumeProperty
//TODO #92 a new solute is created for every pH change by Solute.createCustom
// @public Solution in the beaker
this.solution = new Solution( new Property( Solute.createCustom( 7 ) ), 0.5, 0, this.beaker.volume, {
tandem: tandem.createTandem( 'solution' )
Expand Down
Loading

0 comments on commit 799c136

Please sign in to comment.