From dc5731c6080f865247ae374299618fd0a54a73e9 Mon Sep 17 00:00:00 2001 From: jbphet Date: Fri, 31 Jul 2020 12:35:24 -0600 Subject: [PATCH] separated phase diagram from accordion box, see https://github.com/phetsims/states-of-matter/issues/313 --- js/phase-changes/view/PhaseDiagram.js | 414 ++++++++++++++++++ .../view/PhaseDiagramAccordionBox.js | 364 +-------------- 2 files changed, 425 insertions(+), 353 deletions(-) create mode 100644 js/phase-changes/view/PhaseDiagram.js diff --git a/js/phase-changes/view/PhaseDiagram.js b/js/phase-changes/view/PhaseDiagram.js new file mode 100644 index 00000000..a96db24d --- /dev/null +++ b/js/phase-changes/view/PhaseDiagram.js @@ -0,0 +1,414 @@ +// Copyright 2014-2020, University of Colorado Boulder + +/** + * A phase diagram that is specific to the needs of the States of Matter simulation. This is not very general. + * + * @author Siddhartha Chinthapally (Actual Concepts) + * @author John Blanco (PhET Interactive Simulations) + */ + +import Vector2 from '../../../../dot/js/Vector2.js'; +import Shape from '../../../../kite/js/Shape.js'; +import ArrowNode from '../../../../scenery-phet/js/ArrowNode.js'; +import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; +import Node from '../../../../scenery/js/nodes/Node.js'; +import Path from '../../../../scenery/js/nodes/Path.js'; +import RichText from '../../../../scenery/js/nodes/RichText.js'; +import Text from '../../../../scenery/js/nodes/Text.js'; +import SOMColorProfile from '../../common/view/SOMColorProfile.js'; +import statesOfMatter from '../../statesOfMatter.js'; +import statesOfMatterStrings from '../../statesOfMatterStrings.js'; + +const criticalPointString = statesOfMatterStrings.criticalPoint; +const gasString = statesOfMatterStrings.gas; +const liquidString = statesOfMatterStrings.liquid; +const pressureString = statesOfMatterStrings.pressure; +const solidString = statesOfMatterStrings.solid; +const temperatureString = statesOfMatterStrings.temperature; +const triplePointString = statesOfMatterStrings.triplePoint; + +// constants that control the size of the canvas, empirically determined to work well for the nees of the sim +const WIDTH = 148; +const HEIGHT = ( WIDTH * 0.75 ); + +// constants that control the look of the axes. +const AXES_LINE_WIDTH = 2; +const AXES_ARROW_HEAD_HEIGHT = 4 * AXES_LINE_WIDTH; +const AXES_ARROW_HEAD_WIDTH = 4 * AXES_LINE_WIDTH; +const HORIZ_AXIS_SIZE_PROPORTION = 0.85; +const VERT_AXIS_SIZE_PROPORTION = 0.85; +const LIQUID_AND_GAS_LABEL_MAX_WIDTH = 35; +const SOLID_LABEL_MAX_WIDTH = 30; // has to be narrow enough to fit on the graph when modified for water +const SMALLER_INNER_TEXT_WIDTH = 30; + +// constants that control the position of the origin for the graph. +const X_ORIGIN_OFFSET = 0.10 * WIDTH; +const Y_ORIGIN_OFFSET = 0.85 * HEIGHT; +const X_USABLE_RANGE = WIDTH * HORIZ_AXIS_SIZE_PROPORTION - AXES_ARROW_HEAD_HEIGHT; +const Y_USABLE_RANGE = HEIGHT * ( VERT_AXIS_SIZE_PROPORTION - 0.11 ); + +// font for the labels used on the axes. +const AXIS_LABEL_FONT_SIZE = 12; +const AXIS_LABEL_FONT = new PhetFont( AXIS_LABEL_FONT_SIZE ); + +// fonts for labels in the interior of the diagram. +const LARGER_INNER_FONT_SIZE = 12; +const LARGER_INNER_FONT = new PhetFont( LARGER_INNER_FONT_SIZE ); +const SMALLER_INNER_FONT_SIZE = 10; +const SMALLER_INNER_FONT = new PhetFont( SMALLER_INNER_FONT_SIZE ); + +// constants that control the appearance of the phase diagram for the various substances. Note that all points are +// controlled as proportions of the overall graph size and not as absolute values. +const POINT_MARKER_DIAMETER = 2.5; +const CURRENT_STATE_MARKER_DIAMETER = 3.5; +const DEFAULT_TOP_OF_SOLID_LIQUID_LINE = new Vector2( + X_USABLE_RANGE * 0.40 + X_ORIGIN_OFFSET, + Y_ORIGIN_OFFSET - Y_USABLE_RANGE +); +const TOP_OF_SOLID_LIQUID_LINE_FOR_WATER = new Vector2( + X_USABLE_RANGE * 0.30 + X_ORIGIN_OFFSET, + Y_ORIGIN_OFFSET - Y_USABLE_RANGE +); +const DEFAULT_TRIPLE_POINT = new Vector2( + X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.35 ), + Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.2 ) +); +const DEFAULT_CRITICAL_POSITION = new Vector2( + X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.8 ), + Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.45 ) +); +const DEFAULT_SOLID_LABEL_POSITION = new Vector2( + X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.195 ), + Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.9 ) +); +const WATER_SOLID_LABEL_POSITION = new Vector2( + X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.16 ), + Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.9 ) +); +const DEFAULT_LIQUID_LABEL_POSITION = new Vector2( + X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.65 ), + Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.9 ) +); +const DEFAULT_GAS_LABEL_POSITION = new Vector2( + X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.7 ), + Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.15 ) +); + +class PhaseDiagram extends Node { + + /** + * @param {Object} [options] that can be passed on to the underlying node + */ + constructor( options ) { + + super( options ); + + // object where the components of the phase diagram are collected + const phaseDiagramComponents = {}; + + phaseDiagramComponents.gasAreaBackground = new Path( null, { + fill: '#FFBC00', + stroke: '#FFBC00' + } ); + this.addChild( phaseDiagramComponents.gasAreaBackground ); + + phaseDiagramComponents.superCriticalAreaBackground = new Path( null, { + fill: '#C3DF53' + } ); + this.addChild( phaseDiagramComponents.superCriticalAreaBackground ); + + phaseDiagramComponents.liquidAreaBackground = new Path( null, { + fill: '#83FFB9' + } ); + this.addChild( phaseDiagramComponents.liquidAreaBackground ); + + phaseDiagramComponents.solidAreaBackground = new Path( null, { + fill: '#AB9CC4' + } ); + this.addChild( phaseDiagramComponents.solidAreaBackground ); + + phaseDiagramComponents.solidLiquidLine = new Path( null, { lineWidth: 1, stroke: 'black' } ); + this.addChild( phaseDiagramComponents.solidLiquidLine ); + + phaseDiagramComponents.solidGasLine = new Path( null, { lineWidth: 1, stroke: 'black' } ); + this.addChild( phaseDiagramComponents.solidGasLine ); + + phaseDiagramComponents.liquidGasLine = new Path( null, { lineWidth: 1, stroke: 'black' } ); + this.addChild( phaseDiagramComponents.liquidGasLine ); + + phaseDiagramComponents.triplePoint = new Path( new Shape() + .ellipse( 0, 0, POINT_MARKER_DIAMETER, POINT_MARKER_DIAMETER ), { fill: 'black' } ); + this.addChild( phaseDiagramComponents.triplePoint ); + + phaseDiagramComponents.criticalPoint = new Path( new Shape() + .ellipse( 0, 0, POINT_MARKER_DIAMETER, POINT_MARKER_DIAMETER ), { fill: 'black' } ); + this.addChild( phaseDiagramComponents.criticalPoint ); + + phaseDiagramComponents.solidLabel = new Text( solidString, { + font: LARGER_INNER_FONT, + fill: 'black', + maxWidth: SOLID_LABEL_MAX_WIDTH + } ); + this.addChild( phaseDiagramComponents.solidLabel ); + + phaseDiagramComponents.liquidLabel = new Text( liquidString, { + font: LARGER_INNER_FONT, + fill: 'black', + maxWidth: LIQUID_AND_GAS_LABEL_MAX_WIDTH + } ); + this.addChild( phaseDiagramComponents.liquidLabel ); + + phaseDiagramComponents.gasLabel = new Text( gasString, { + font: LARGER_INNER_FONT, + fill: 'black', + maxWidth: LIQUID_AND_GAS_LABEL_MAX_WIDTH + } ); + this.addChild( phaseDiagramComponents.gasLabel ); + + phaseDiagramComponents.triplePointLabel = new RichText( triplePointString, { + font: SMALLER_INNER_FONT, + fill: 'black', + align: 'right' + } ); + this.addChild( phaseDiagramComponents.triplePointLabel ); + if ( phaseDiagramComponents.triplePointLabel.width > SMALLER_INNER_TEXT_WIDTH ) { + phaseDiagramComponents.triplePointLabel.setScaleMagnitude( + SMALLER_INNER_TEXT_WIDTH / phaseDiagramComponents.triplePointLabel.width + ); + } + + phaseDiagramComponents.criticalPointLabel = new RichText( criticalPointString, { + font: SMALLER_INNER_FONT, + fill: 'black', + align: 'right', + maxWidth: SMALLER_INNER_TEXT_WIDTH + } ); + this.addChild( phaseDiagramComponents.criticalPointLabel ); + + const horizontalAxis = new ArrowNode( + X_ORIGIN_OFFSET, + Y_ORIGIN_OFFSET, + X_ORIGIN_OFFSET + ( HORIZ_AXIS_SIZE_PROPORTION * WIDTH ), + Y_ORIGIN_OFFSET, + { + fill: SOMColorProfile.controlPanelTextProperty, + stroke: SOMColorProfile.controlPanelTextProperty, + headHeight: AXES_ARROW_HEAD_HEIGHT, + headWidth: AXES_ARROW_HEAD_WIDTH, + tailWidth: AXES_LINE_WIDTH + } + ); + this.addChild( horizontalAxis ); + + const verticalAxis = new ArrowNode( + X_ORIGIN_OFFSET, + Y_ORIGIN_OFFSET, + X_ORIGIN_OFFSET, + Y_ORIGIN_OFFSET - Y_USABLE_RANGE - AXES_ARROW_HEAD_HEIGHT, + { + fill: SOMColorProfile.controlPanelTextProperty, + stroke: SOMColorProfile.controlPanelTextProperty, + headHeight: AXES_ARROW_HEAD_HEIGHT, + headWidth: AXES_ARROW_HEAD_WIDTH, + tailWidth: AXES_LINE_WIDTH + } + ); + this.addChild( verticalAxis ); + + // Create and add the labels for the axes. + const horizontalAxisLabel = new Text( temperatureString, { + font: AXIS_LABEL_FONT, + fill: SOMColorProfile.controlPanelTextProperty, + maxWidth: horizontalAxis.width + } ); + horizontalAxisLabel.setTranslation( horizontalAxis.centerX - horizontalAxisLabel.width / 2, Y_ORIGIN_OFFSET + horizontalAxisLabel.height * 1.2 ); + this.addChild( horizontalAxisLabel ); + + const verticalAxisLabel = new Text( pressureString, { + font: AXIS_LABEL_FONT, + fill: SOMColorProfile.controlPanelTextProperty, + maxWidth: verticalAxis.height + } ); + verticalAxisLabel.setTranslation( X_ORIGIN_OFFSET - ( verticalAxisLabel.height / 1.5 ), verticalAxis.centerY + verticalAxisLabel.width / 2 ); + verticalAxisLabel.setRotation( 3 * Math.PI / 2 ); + this.addChild( verticalAxisLabel ); + + // Create and add the marker that shows the current phase state. + phaseDiagramComponents.currentStateMarker = new Path( + new Shape().ellipse( 0, 0, CURRENT_STATE_MARKER_DIAMETER, CURRENT_STATE_MARKER_DIAMETER ), + { fill: 'red' } + ); + this.addChild( phaseDiagramComponents.currentStateMarker ); + + // @private - flag that indicates whether water is being depicted, which alters aspects of the diagram + this.depictingWater = false; + + // @private - components of the phase diagram, updated as changes occur in the model + this.phaseDiagramComponents = phaseDiagramComponents; + + // Perform the initial drawing of the diagram. + this.drawPhaseDiagram(); + + // Set the initial position of the current phase state marker. + this.setStateMarkerPos( 0, 0 ); + } + + /** + * @public + */ + drawPhaseDiagram() { + + // Handle the variations due to water vs. non-water + const topOfSolidLiquidLine = this.depictingWater ? + TOP_OF_SOLID_LIQUID_LINE_FOR_WATER : + DEFAULT_TOP_OF_SOLID_LIQUID_LINE; + const solidLabelCenter = this.depictingWater ? WATER_SOLID_LABEL_POSITION : DEFAULT_SOLID_LABEL_POSITION; + + // convenience variable + const diagramComponents = this.phaseDiagramComponents; + + // Place the triple point marker. + diagramComponents.triplePoint.setTranslation( DEFAULT_TRIPLE_POINT.x, DEFAULT_TRIPLE_POINT.y ); + + // Add the curve that separates the solid and gaseous regions. + const solidGasCurve = new Shape() + .moveTo( X_ORIGIN_OFFSET, Y_ORIGIN_OFFSET ) + .quadraticCurveTo( + X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.2 ), + Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.02 ), + DEFAULT_TRIPLE_POINT.x, + DEFAULT_TRIPLE_POINT.y + ); + diagramComponents.solidGasLine.setShape( solidGasCurve ); + + // Add the line that separates solid and liquid. + const solidLiquidLine = new Shape() + .lineTo( DEFAULT_TRIPLE_POINT.x, DEFAULT_TRIPLE_POINT.y ) + .lineToPoint( topOfSolidLiquidLine ); + diagramComponents.solidLiquidLine.setShape( solidLiquidLine ); + + // Update the shape of the background for the area that represents the solid phase. + const solidBackground = new Shape() + .moveTo( X_ORIGIN_OFFSET, Y_ORIGIN_OFFSET ) + .quadraticCurveTo( + X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.2 ), + Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.02 ), + DEFAULT_TRIPLE_POINT.x, + DEFAULT_TRIPLE_POINT.y + ) + .lineToPoint( topOfSolidLiquidLine ) + .lineTo( X_ORIGIN_OFFSET, Y_ORIGIN_OFFSET - Y_USABLE_RANGE ) + .lineTo( X_ORIGIN_OFFSET, Y_ORIGIN_OFFSET ) + .close(); + diagramComponents.solidAreaBackground.setShape( solidBackground ); + + // Place the critical point marker. + diagramComponents.criticalPoint.setTranslation( DEFAULT_CRITICAL_POSITION.x, DEFAULT_CRITICAL_POSITION.y ); + + // Add the curve that separates liquid and gas. + const controlCurveXPos = DEFAULT_TRIPLE_POINT.x + ( ( DEFAULT_CRITICAL_POSITION.x - DEFAULT_TRIPLE_POINT.x ) / 2 ); + const controlCurveYPos = DEFAULT_TRIPLE_POINT.y; + const liquidGasCurve = new Shape() + .moveTo( DEFAULT_TRIPLE_POINT.x - 1, DEFAULT_TRIPLE_POINT.y ) + .quadraticCurveTo( + controlCurveXPos, + controlCurveYPos, + DEFAULT_CRITICAL_POSITION.x, + DEFAULT_CRITICAL_POSITION.y + ); + diagramComponents.liquidGasLine.setShape( liquidGasCurve ); + + // liquid phase (it is expected that the solid shape overlays this one) + const liquidBackground = new Shape() + .moveTo( DEFAULT_TRIPLE_POINT.x - 1, DEFAULT_TRIPLE_POINT.y ) + .quadraticCurveTo( + controlCurveXPos, + controlCurveYPos, + DEFAULT_CRITICAL_POSITION.x, + DEFAULT_CRITICAL_POSITION.y + ) + .lineTo( X_ORIGIN_OFFSET + X_USABLE_RANGE, Y_ORIGIN_OFFSET - Y_USABLE_RANGE ) + .lineTo( topOfSolidLiquidLine.x, Y_ORIGIN_OFFSET - Y_USABLE_RANGE ) + .lineTo( DEFAULT_TRIPLE_POINT.x, DEFAULT_TRIPLE_POINT.y ) + .close(); + diagramComponents.liquidAreaBackground.setShape( liquidBackground ); + + // gas phase + const gasBackground = new Shape() + .moveTo( X_ORIGIN_OFFSET, Y_ORIGIN_OFFSET ) + .lineToPoint( DEFAULT_TRIPLE_POINT ) + .lineToPoint( DEFAULT_CRITICAL_POSITION ) + .lineTo( X_ORIGIN_OFFSET + X_USABLE_RANGE, Y_ORIGIN_OFFSET ) + .lineTo( X_ORIGIN_OFFSET, Y_ORIGIN_OFFSET ) + .close(); + diagramComponents.gasAreaBackground.setShape( gasBackground ); + + const superCriticalBackground = new Shape() + .moveToPoint( DEFAULT_CRITICAL_POSITION ) + .lineTo( X_ORIGIN_OFFSET + X_USABLE_RANGE, Y_ORIGIN_OFFSET ) + .lineTo( X_ORIGIN_OFFSET + X_USABLE_RANGE, Y_ORIGIN_OFFSET - Y_USABLE_RANGE ) + .lineToPoint( DEFAULT_CRITICAL_POSITION ) + .close(); + + diagramComponents.superCriticalAreaBackground.setShape( superCriticalBackground ); + + // position the labels - some of the values were empirically determined for optimal layout + diagramComponents.solidLabel.center = solidLabelCenter; + diagramComponents.liquidLabel.center = DEFAULT_LIQUID_LABEL_POSITION; + diagramComponents.gasLabel.center = DEFAULT_GAS_LABEL_POSITION; + diagramComponents.triplePointLabel.right = DEFAULT_TRIPLE_POINT.x - 7; + diagramComponents.triplePointLabel.bottom = DEFAULT_TRIPLE_POINT.y; + diagramComponents.criticalPointLabel.right = DEFAULT_CRITICAL_POSITION.x - 7; + diagramComponents.criticalPointLabel.bottom = DEFAULT_CRITICAL_POSITION.y; + } + + /** + * Set the normalized position for this marker. + * @param normalizedTemperature - Temperature (X position) value between 0 and 1 (inclusive). + * @param normalizedPressure - Pressure (Y position) value between 0 and 1 (inclusive). + * @public + */ + setStateMarkerPos( normalizedTemperature, normalizedPressure ) { + + // parameter checking + assert && assert( normalizedTemperature >= 0 && normalizedTemperature <= 1, 'temperature value out of range' ); + assert && assert( normalizedPressure >= 0 && normalizedPressure <= 1, 'pressure value out of range' ); + + // map the normalized temperature and pressure values to x and y positions on the graph + let markerXPos = normalizedTemperature * X_USABLE_RANGE + X_ORIGIN_OFFSET; + let markerYPos = -normalizedPressure * Y_USABLE_RANGE + Y_ORIGIN_OFFSET; + + // prevent marker from going off graph on right side + markerXPos = Math.min( markerXPos, X_USABLE_RANGE + X_ORIGIN_OFFSET - CURRENT_STATE_MARKER_DIAMETER ); + markerYPos = Math.max( markerYPos, Y_ORIGIN_OFFSET - Y_USABLE_RANGE - CURRENT_STATE_MARKER_DIAMETER ); + + // set the position of the marker node + this.phaseDiagramComponents.currentStateMarker.centerX = markerXPos; + this.phaseDiagramComponents.currentStateMarker.centerY = markerYPos; + } + + /** + * Set the visibility of the state marker. + * @param {boolean} isVisible + * @public + */ + setStateMarkerVisible( isVisible ) { + this.phaseDiagramComponents.currentStateMarker.setVisible( isVisible ); + } + + /** + * Set the phase diagram to be shaped such that it looks more like the phase diagram water, which is to say that the + * solid-liquid line leans to the left rather than to the right. Note that this is a very non-general approach - it + * would be more general to allow the various points in the graph (e.g. triple point, critical point) to be + * positioned anywhere, but currently it isn't worth the extra effort to do so. Feel free if it is ever needed. + * @param {boolean} depictingWater + * @public + */ + setDepictingWater( depictingWater ) { + this.depictingWater = depictingWater; + this.drawPhaseDiagram(); + } +} + +statesOfMatter.register( 'PhaseDiagram', PhaseDiagram ); +export default PhaseDiagram; \ No newline at end of file diff --git a/js/phase-changes/view/PhaseDiagramAccordionBox.js b/js/phase-changes/view/PhaseDiagramAccordionBox.js index ce45d0b6..68a72412 100644 --- a/js/phase-changes/view/PhaseDiagramAccordionBox.js +++ b/js/phase-changes/view/PhaseDiagramAccordionBox.js @@ -6,95 +6,17 @@ * @author Siddhartha Chinthapally (Actual Concepts) */ -import Vector2 from '../../../../dot/js/Vector2.js'; -import Shape from '../../../../kite/js/Shape.js'; import merge from '../../../../phet-core/js/merge.js'; -import ArrowNode from '../../../../scenery-phet/js/ArrowNode.js'; import PhetFont from '../../../../scenery-phet/js/PhetFont.js'; -import Node from '../../../../scenery/js/nodes/Node.js'; -import Path from '../../../../scenery/js/nodes/Path.js'; -import RichText from '../../../../scenery/js/nodes/RichText.js'; import Text from '../../../../scenery/js/nodes/Text.js'; import AccordionBox from '../../../../sun/js/AccordionBox.js'; import SOMConstants from '../../common/SOMConstants.js'; import SOMColorProfile from '../../common/view/SOMColorProfile.js'; import statesOfMatter from '../../statesOfMatter.js'; import statesOfMatterStrings from '../../statesOfMatterStrings.js'; +import PhaseDiagram from './PhaseDiagram.js'; -const criticalPointString = statesOfMatterStrings.criticalPoint; -const gasString = statesOfMatterStrings.gas; -const liquidString = statesOfMatterStrings.liquid; const phaseDiagramString = statesOfMatterStrings.phaseDiagram; -const pressureString = statesOfMatterStrings.pressure; -const solidString = statesOfMatterStrings.solid; -const temperatureString = statesOfMatterStrings.temperature; -const triplePointString = statesOfMatterStrings.triplePoint; - -// constants that control the size of the canvas. -const WIDTH = 148; -const HEIGHT = ( WIDTH * 0.75 ); - -// constants that control the look of the axes. -const AXES_LINE_WIDTH = 2; -const AXES_ARROW_HEAD_HEIGHT = 4 * AXES_LINE_WIDTH; -const HORIZ_AXIS_SIZE_PROPORTION = 0.85; -const VERT_AXIS_SIZE_PROPORTION = 0.85; -const LIQUID_AND_GAS_LABEL_MAX_WIDTH = 35; -const SOLID_LABEL_MAX_WIDTH = 30; // has to be narrow enough to fit on the graph when modified for water -const SMALLER_INNER_TEXT_WIDTH = 30; - -// constants that control the position of the origin for the graph. -const X_ORIGIN_OFFSET = 0.10 * WIDTH; -const Y_ORIGIN_OFFSET = 0.85 * HEIGHT; -const X_USABLE_RANGE = WIDTH * HORIZ_AXIS_SIZE_PROPORTION - AXES_ARROW_HEAD_HEIGHT; -const Y_USABLE_RANGE = HEIGHT * ( VERT_AXIS_SIZE_PROPORTION - 0.11 ); - -// font for the labels used on the axes. -const AXIS_LABEL_FONT_SIZE = 12; -const AXIS_LABEL_FONT = new PhetFont( AXIS_LABEL_FONT_SIZE ); - -// fonts for labels in the interior of the diagram. -const LARGER_INNER_FONT_SIZE = 12; -const LARGER_INNER_FONT = new PhetFont( LARGER_INNER_FONT_SIZE ); -const SMALLER_INNER_FONT_SIZE = 10; -const SMALLER_INNER_FONT = new PhetFont( SMALLER_INNER_FONT_SIZE ); - -// constants that control the appearance of the phase diagram for the various substances. Note that all points are -// controlled as proportions of the overall graph size and not as absolute values. -const POINT_MARKER_DIAMETER = 2.5; -const CURRENT_STATE_MARKER_DIAMETER = 3.5; -const DEFAULT_TOP_OF_SOLID_LIQUID_LINE = new Vector2( - X_USABLE_RANGE * 0.40 + X_ORIGIN_OFFSET, - Y_ORIGIN_OFFSET - Y_USABLE_RANGE -); -const TOP_OF_SOLID_LIQUID_LINE_FOR_WATER = new Vector2( - X_USABLE_RANGE * 0.30 + X_ORIGIN_OFFSET, - Y_ORIGIN_OFFSET - Y_USABLE_RANGE -); -const DEFAULT_TRIPLE_POINT = new Vector2( - X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.35 ), - Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.2 ) -); -const DEFAULT_CRITICAL_POSITION = new Vector2( - X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.8 ), - Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.45 ) -); -const DEFAULT_SOLID_LABEL_POSITION = new Vector2( - X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.195 ), - Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.9 ) -); -const WATER_SOLID_LABEL_POSITION = new Vector2( - X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.16 ), - Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.9 ) -); -const DEFAULT_LIQUID_LABEL_POSITION = new Vector2( - X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.65 ), - Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.9 ) -); -const DEFAULT_GAS_LABEL_POSITION = new Vector2( - X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.7 ), - Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.15 ) -); class PhaseDiagramAccordionBox extends AccordionBox { @@ -104,152 +26,15 @@ class PhaseDiagramAccordionBox extends AccordionBox { */ constructor( expandedProperty, options ) { - const accordionContent = new Node(); - - // object where the components of the phase diagram are collected - const phaseDiagramComponents = {}; - - phaseDiagramComponents.gasAreaBackground = new Path( null, { - fill: '#FFBC00', - stroke: '#FFBC00' - } ); - accordionContent.addChild( phaseDiagramComponents.gasAreaBackground ); - - phaseDiagramComponents.superCriticalAreaBackground = new Path( null, { - fill: '#C3DF53' - } ); - accordionContent.addChild( phaseDiagramComponents.superCriticalAreaBackground ); - - phaseDiagramComponents.liquidAreaBackground = new Path( null, { - fill: '#83FFB9' - } ); - accordionContent.addChild( phaseDiagramComponents.liquidAreaBackground ); - - phaseDiagramComponents.solidAreaBackground = new Path( null, { - fill: '#AB9CC4' - } ); - accordionContent.addChild( phaseDiagramComponents.solidAreaBackground ); - - phaseDiagramComponents.solidLiquidLine = new Path( null, { lineWidth: 1, stroke: 'black' } ); - accordionContent.addChild( phaseDiagramComponents.solidLiquidLine ); - - phaseDiagramComponents.solidGasLine = new Path( null, { lineWidth: 1, stroke: 'black' } ); - accordionContent.addChild( phaseDiagramComponents.solidGasLine ); - - phaseDiagramComponents.liquidGasLine = new Path( null, { lineWidth: 1, stroke: 'black' } ); - accordionContent.addChild( phaseDiagramComponents.liquidGasLine ); - - phaseDiagramComponents.triplePoint = new Path( new Shape() - .ellipse( 0, 0, POINT_MARKER_DIAMETER, POINT_MARKER_DIAMETER ), { fill: 'black' } ); - accordionContent.addChild( phaseDiagramComponents.triplePoint ); - - phaseDiagramComponents.criticalPoint = new Path( new Shape() - .ellipse( 0, 0, POINT_MARKER_DIAMETER, POINT_MARKER_DIAMETER ), { fill: 'black' } ); - accordionContent.addChild( phaseDiagramComponents.criticalPoint ); - - phaseDiagramComponents.solidLabel = new Text( solidString, { - font: LARGER_INNER_FONT, - fill: 'black', - maxWidth: SOLID_LABEL_MAX_WIDTH - } ); - accordionContent.addChild( phaseDiagramComponents.solidLabel ); - - phaseDiagramComponents.liquidLabel = new Text( liquidString, { - font: LARGER_INNER_FONT, - fill: 'black', - maxWidth: LIQUID_AND_GAS_LABEL_MAX_WIDTH - } ); - accordionContent.addChild( phaseDiagramComponents.liquidLabel ); - - phaseDiagramComponents.gasLabel = new Text( gasString, { - font: LARGER_INNER_FONT, - fill: 'black', - maxWidth: LIQUID_AND_GAS_LABEL_MAX_WIDTH - } ); - accordionContent.addChild( phaseDiagramComponents.gasLabel ); - - phaseDiagramComponents.triplePointLabel = new RichText( triplePointString, { - font: SMALLER_INNER_FONT, - fill: 'black', - align: 'right' - } ); - accordionContent.addChild( phaseDiagramComponents.triplePointLabel ); - if ( phaseDiagramComponents.triplePointLabel.width > SMALLER_INNER_TEXT_WIDTH ) { - phaseDiagramComponents.triplePointLabel.setScaleMagnitude( - SMALLER_INNER_TEXT_WIDTH / phaseDiagramComponents.triplePointLabel.width - ); - } - - phaseDiagramComponents.criticalPointLabel = new RichText( criticalPointString, { - font: SMALLER_INNER_FONT, - fill: 'black', - align: 'right', - maxWidth: SMALLER_INNER_TEXT_WIDTH - } ); - accordionContent.addChild( phaseDiagramComponents.criticalPointLabel ); - - const horizontalAxis = new ArrowNode( - X_ORIGIN_OFFSET, - Y_ORIGIN_OFFSET, - X_ORIGIN_OFFSET + ( HORIZ_AXIS_SIZE_PROPORTION * WIDTH ), - Y_ORIGIN_OFFSET, - { - fill: SOMColorProfile.controlPanelTextProperty, - stroke: SOMColorProfile.controlPanelTextProperty, - headHeight: 8, - headWidth: 8, - tailWidth: AXES_LINE_WIDTH - } - ); - accordionContent.addChild( horizontalAxis ); - - const verticalAxis = new ArrowNode( - X_ORIGIN_OFFSET, - Y_ORIGIN_OFFSET, - X_ORIGIN_OFFSET, - Y_ORIGIN_OFFSET - Y_USABLE_RANGE - AXES_ARROW_HEAD_HEIGHT, - { - fill: SOMColorProfile.controlPanelTextProperty, - stroke: SOMColorProfile.controlPanelTextProperty, - headHeight: 8, - headWidth: 8, - tailWidth: AXES_LINE_WIDTH - } - ); - accordionContent.addChild( verticalAxis ); - - // Create and add the labels for the axes. - const horizontalAxisLabel = new Text( temperatureString, { - font: AXIS_LABEL_FONT, - fill: SOMColorProfile.controlPanelTextProperty, - maxWidth: horizontalAxis.width - } ); - horizontalAxisLabel.setTranslation( horizontalAxis.centerX - horizontalAxisLabel.width / 2, Y_ORIGIN_OFFSET + horizontalAxisLabel.height * 1.2 ); - accordionContent.addChild( horizontalAxisLabel ); - - const verticalAxisLabel = new Text( pressureString, { - font: AXIS_LABEL_FONT, - fill: SOMColorProfile.controlPanelTextProperty, - maxWidth: verticalAxis.height - } ); - verticalAxisLabel.setTranslation( X_ORIGIN_OFFSET - ( verticalAxisLabel.height / 1.5 ), verticalAxis.centerY + verticalAxisLabel.width / 2 ); - verticalAxisLabel.setRotation( 3 * Math.PI / 2 ); - accordionContent.addChild( verticalAxisLabel ); - - // Create and add the marker that shows the current phase state. - phaseDiagramComponents.currentStateMarker = new Path( - new Shape().ellipse( 0, 0, CURRENT_STATE_MARKER_DIAMETER, CURRENT_STATE_MARKER_DIAMETER ), - { fill: 'red' } - ); - accordionContent.addChild( phaseDiagramComponents.currentStateMarker ); + const phaseDiagram = new PhaseDiagram( options ); const titleNode = new Text( phaseDiagramString, { fill: SOMColorProfile.controlPanelTextProperty, font: new PhetFont( { size: 13 } ), - maxWidth: horizontalAxis.width + maxWidth: options.maxWidth } ); - super( accordionContent, merge( { + super( phaseDiagram, merge( { titleNode: titleNode, fill: SOMColorProfile.controlPanelBackgroundProperty, stroke: SOMColorProfile.controlPanelStrokeProperty, @@ -258,7 +43,7 @@ class PhaseDiagramAccordionBox extends AccordionBox { titleAlignX: 'center', buttonAlign: 'left', cornerRadius: SOMConstants.PANEL_CORNER_RADIUS, - contentYSpacing: -15, + contentYSpacing: -1, contentYMargin: 5, contentXMargin: 5, minWidth: options.minWidth, @@ -273,126 +58,15 @@ class PhaseDiagramAccordionBox extends AccordionBox { tandem: options.tandem.createTandem( 'accordionBox' ) }, options ) ); - // @private - flag that indicates whether water is being depicted, which alters aspects of the diagram - this.depictingWater = false; - - // @private - components of the phase diagram, updated as changes occur in the model - this.phaseDiagramComponents = phaseDiagramComponents; - - // Draw the initial phase diagram. - this.drawPhaseDiagram(); - - // Set the initial position of the current phase state marker. - this.setStateMarkerPos( 0, 0 ); + // @private - make phase diagram available so that methods can access it + this.phaseDiagram = phaseDiagram; } /** * @public */ drawPhaseDiagram() { - - // Handle the variations due to water vs. non-water - const topOfSolidLiquidLine = this.depictingWater ? - TOP_OF_SOLID_LIQUID_LINE_FOR_WATER : - DEFAULT_TOP_OF_SOLID_LIQUID_LINE; - const solidLabelCenter = this.depictingWater ? WATER_SOLID_LABEL_POSITION : DEFAULT_SOLID_LABEL_POSITION; - - // convenience variable - const diagramComponents = this.phaseDiagramComponents; - - // Place the triple point marker. - diagramComponents.triplePoint.setTranslation( DEFAULT_TRIPLE_POINT.x, DEFAULT_TRIPLE_POINT.y ); - - // Add the curve that separates the solid and gaseous regions. - const solidGasCurve = new Shape() - .moveTo( X_ORIGIN_OFFSET, Y_ORIGIN_OFFSET ) - .quadraticCurveTo( - X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.2 ), - Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.02 ), - DEFAULT_TRIPLE_POINT.x, - DEFAULT_TRIPLE_POINT.y - ); - diagramComponents.solidGasLine.setShape( solidGasCurve ); - - // Add the line that separates solid and liquid. - const solidLiquidLine = new Shape() - .lineTo( DEFAULT_TRIPLE_POINT.x, DEFAULT_TRIPLE_POINT.y ) - .lineToPoint( topOfSolidLiquidLine ); - diagramComponents.solidLiquidLine.setShape( solidLiquidLine ); - - // Update the shape of the background for the area that represents the solid phase. - const solidBackground = new Shape() - .moveTo( X_ORIGIN_OFFSET, Y_ORIGIN_OFFSET ) - .quadraticCurveTo( - X_ORIGIN_OFFSET + ( X_USABLE_RANGE * 0.2 ), - Y_ORIGIN_OFFSET - ( Y_USABLE_RANGE * 0.02 ), - DEFAULT_TRIPLE_POINT.x, - DEFAULT_TRIPLE_POINT.y - ) - .lineToPoint( topOfSolidLiquidLine ) - .lineTo( X_ORIGIN_OFFSET, Y_ORIGIN_OFFSET - Y_USABLE_RANGE ) - .lineTo( X_ORIGIN_OFFSET, Y_ORIGIN_OFFSET ) - .close(); - diagramComponents.solidAreaBackground.setShape( solidBackground ); - - // Place the critical point marker. - diagramComponents.criticalPoint.setTranslation( DEFAULT_CRITICAL_POSITION.x, DEFAULT_CRITICAL_POSITION.y ); - - // Add the curve that separates liquid and gas. - const controlCurveXPos = DEFAULT_TRIPLE_POINT.x + ( ( DEFAULT_CRITICAL_POSITION.x - DEFAULT_TRIPLE_POINT.x ) / 2 ); - const controlCurveYPos = DEFAULT_TRIPLE_POINT.y; - const liquidGasCurve = new Shape() - .moveTo( DEFAULT_TRIPLE_POINT.x - 1, DEFAULT_TRIPLE_POINT.y ) - .quadraticCurveTo( - controlCurveXPos, - controlCurveYPos, - DEFAULT_CRITICAL_POSITION.x, - DEFAULT_CRITICAL_POSITION.y - ); - diagramComponents.liquidGasLine.setShape( liquidGasCurve ); - - // liquid phase (it is expected that the solid shape overlays this one) - const liquidBackground = new Shape() - .moveTo( DEFAULT_TRIPLE_POINT.x - 1, DEFAULT_TRIPLE_POINT.y ) - .quadraticCurveTo( - controlCurveXPos, - controlCurveYPos, - DEFAULT_CRITICAL_POSITION.x, - DEFAULT_CRITICAL_POSITION.y - ) - .lineTo( X_ORIGIN_OFFSET + X_USABLE_RANGE, Y_ORIGIN_OFFSET - Y_USABLE_RANGE ) - .lineTo( topOfSolidLiquidLine.x, Y_ORIGIN_OFFSET - Y_USABLE_RANGE ) - .lineTo( DEFAULT_TRIPLE_POINT.x, DEFAULT_TRIPLE_POINT.y ) - .close(); - diagramComponents.liquidAreaBackground.setShape( liquidBackground ); - - // gas phase - const gasBackground = new Shape() - .moveTo( X_ORIGIN_OFFSET, Y_ORIGIN_OFFSET ) - .lineToPoint( DEFAULT_TRIPLE_POINT ) - .lineToPoint( DEFAULT_CRITICAL_POSITION ) - .lineTo( X_ORIGIN_OFFSET + X_USABLE_RANGE, Y_ORIGIN_OFFSET ) - .lineTo( X_ORIGIN_OFFSET, Y_ORIGIN_OFFSET ) - .close(); - diagramComponents.gasAreaBackground.setShape( gasBackground ); - - const superCriticalBackground = new Shape() - .moveToPoint( DEFAULT_CRITICAL_POSITION ) - .lineTo( X_ORIGIN_OFFSET + X_USABLE_RANGE, Y_ORIGIN_OFFSET ) - .lineTo( X_ORIGIN_OFFSET + X_USABLE_RANGE, Y_ORIGIN_OFFSET - Y_USABLE_RANGE ) - .lineToPoint( DEFAULT_CRITICAL_POSITION ) - .close(); - - diagramComponents.superCriticalAreaBackground.setShape( superCriticalBackground ); - - // position the labels - some of the values were empirically determined for optimal layout - diagramComponents.solidLabel.center = solidLabelCenter; - diagramComponents.liquidLabel.center = DEFAULT_LIQUID_LABEL_POSITION; - diagramComponents.gasLabel.center = DEFAULT_GAS_LABEL_POSITION; - diagramComponents.triplePointLabel.right = DEFAULT_TRIPLE_POINT.x - 7; - diagramComponents.triplePointLabel.bottom = DEFAULT_TRIPLE_POINT.y; - diagramComponents.criticalPointLabel.right = DEFAULT_CRITICAL_POSITION.x - 7; - diagramComponents.criticalPointLabel.bottom = DEFAULT_CRITICAL_POSITION.y; + this.phaseDiagram.drawPhaseDiagram(); } /** @@ -402,22 +76,7 @@ class PhaseDiagramAccordionBox extends AccordionBox { * @public */ setStateMarkerPos( normalizedTemperature, normalizedPressure ) { - - // parameter checking - assert && assert( normalizedTemperature >= 0 && normalizedTemperature <= 1, 'temperature value out of range' ); - assert && assert( normalizedPressure >= 0 && normalizedPressure <= 1, 'pressure value out of range' ); - - // map the normalized temperature and pressure values to x and y positions on the graph - let markerXPos = normalizedTemperature * X_USABLE_RANGE + X_ORIGIN_OFFSET; - let markerYPos = -normalizedPressure * Y_USABLE_RANGE + Y_ORIGIN_OFFSET; - - // prevent marker from going off graph on right side - markerXPos = Math.min( markerXPos, X_USABLE_RANGE + X_ORIGIN_OFFSET - CURRENT_STATE_MARKER_DIAMETER ); - markerYPos = Math.max( markerYPos, Y_ORIGIN_OFFSET - Y_USABLE_RANGE - CURRENT_STATE_MARKER_DIAMETER ); - - // set the position of the marker node - this.phaseDiagramComponents.currentStateMarker.centerX = markerXPos; - this.phaseDiagramComponents.currentStateMarker.centerY = markerYPos; + this.phaseDiagram.setStateMarkerPos( normalizedTemperature, normalizedPressure ); } /** @@ -426,7 +85,7 @@ class PhaseDiagramAccordionBox extends AccordionBox { * @public */ setStateMarkerVisible( isVisible ) { - this.phaseDiagramComponents.currentStateMarker.setVisible( isVisible ); + this.phaseDiagram.setStateMarkerVisible( isVisible ); } /** @@ -438,8 +97,7 @@ class PhaseDiagramAccordionBox extends AccordionBox { * @public */ setDepictingWater( depictingWater ) { - this.depictingWater = depictingWater; - this.drawPhaseDiagram(); + this.phaseDiagram.setDepictingWater( depictingWater ); } }