Skip to content

Commit

Permalink
add zoom reference marks, more levels, & make them exponential, see #171
Browse files Browse the repository at this point in the history
  • Loading branch information
jbphet committed Sep 10, 2022
1 parent 7556ff2 commit 5eeac65
Showing 1 changed file with 76 additions and 24 deletions.
100 changes: 76 additions & 24 deletions js/common/view/FluxMeterNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,25 @@

import BooleanProperty from '../../../../axon/js/BooleanProperty.js';
import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js';
import Multilink from '../../../../axon/js/Multilink.js';
import NumberProperty, { RangedProperty } from '../../../../axon/js/NumberProperty.js';
import Property from '../../../../axon/js/Property.js';
import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js';
import Bounds2 from '../../../../dot/js/Bounds2.js';
import Range from '../../../../dot/js/Range.js';
import Utils from '../../../../dot/js/Utils.js';
import Vector2 from '../../../../dot/js/Vector2.js';
import Vector2Property from '../../../../dot/js/Vector2Property.js';
import { Shape } from '../../../../kite/js/imports.js';
import optionize, { EmptySelfOptions } from '../../../../phet-core/js/optionize.js';
import PickRequired from '../../../../phet-core/js/types/PickRequired.js';
import StrictOmit from '../../../../phet-core/js/types/StrictOmit.js';
import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js';
import ArrowNode, { ArrowNodeOptions } from '../../../../scenery-phet/js/ArrowNode.js';
import WireNode from '../../../../scenery-phet/js/WireNode.js';
import MagnifyingGlassZoomButtonGroup from '../../../../scenery-phet/js/MagnifyingGlassZoomButtonGroup.js';
import { Color, DragListener, HBox, Line, Node, NodeOptions, Rectangle, SceneryEvent, Text, VBox } from '../../../../scenery/js/imports.js';
import PhetColorScheme from '../../../../scenery-phet/js/PhetColorScheme.js';
import WireNode from '../../../../scenery-phet/js/WireNode.js';
import { Color, DragListener, HBox, Line, Node, NodeOptions, Path, Rectangle, SceneryEvent, Text, VBox } from '../../../../scenery/js/imports.js';
import AccessibleSlider, { AccessibleSliderOptions } from '../../../../sun/js/accessibility/AccessibleSlider.js';
import Panel from '../../../../sun/js/Panel.js';
import greenhouseEffect from '../../greenhouseEffect.js';
Expand All @@ -35,10 +39,6 @@ import GreenhouseEffectOptions from '../GreenhouseEffectOptions.js';
import FluxMeter from '../model/FluxMeter.js';
import FluxSensor from '../model/FluxSensor.js';
import LayersModel from '../model/LayersModel.js';
import NumberProperty from '../../../../axon/js/NumberProperty.js';
import PhetColorScheme from '../../../../scenery-phet/js/PhetColorScheme.js';
import Multilink from '../../../../axon/js/Multilink.js';
import PickRequired from '../../../../phet-core/js/types/PickRequired.js';

const sunlightString = greenhouseEffectStrings.sunlight;
const infraredString = greenhouseEffectStrings.infrared;
Expand All @@ -49,9 +49,15 @@ const SENSOR_STROKE_COLOR = 'rgb(254,172,63)';
const SENSOR_FILL_COLOR = 'rgba(200,200,200,0.6)';
const CUE_ARROW_LENGTH = 28; // length of the 'drag cue' arrows around the flux sensor

// Model to view scalars that turn flux values into arrow lengths for the flux meter display will be in intervals
// of this value as the scalar value changes to support zoom buttons.
const FLUX_ARROW_ZOOM_DELTA = 1E-6;
// multiplier used to map energy flux values to arrow lengths in nominal (un-zoomed) case, empirically determined
const NOMINAL_FLUX_TO_ARROW_LENGTH_MULTIPLIER = 5E-6;

// Zoom factor for zooming in and out in the flux meter, only used if zoom is enabled. This value was empirically
// determined in conjunction with others to make sure that the max outgoing IR will fix in the flux meter.
const FLUX_ARROW_ZOOM_FACTOR = 2.2;

// the number of zoom levels in each direction
const NUMBER_OF_ZOOM_LEVELS = 2;

const CUE_ARROW_OPTIONS = {
fill: SENSOR_STROKE_COLOR,
Expand Down Expand Up @@ -84,6 +90,9 @@ class FluxMeterNode extends Node {
// a Property that tracks whether the sensor was dragged since startup or last reset, used to hide the queuing errors
private readonly wasDraggedProperty: BooleanProperty;

// zoom factor, only used if the zoom feature is enabled
private readonly zoomFactor: RangedProperty;

/**
* @param model - model component for the FluxMeter
* @param isPlayingProperty - a boolean Property that indicates whether the model in which the flux meter resides is
Expand Down Expand Up @@ -129,32 +138,34 @@ class FluxMeterNode extends Node {
maxWidth: 120
} );

// Property for the zoom buttons and flux display arrows so that we can zoom in and out of the display by
// scaling the arrow lengths
const zoomLevelProperty = new NumberProperty( 5 * FLUX_ARROW_ZOOM_DELTA, {
range: new Range( 4 * FLUX_ARROW_ZOOM_DELTA, 6 * FLUX_ARROW_ZOOM_DELTA )
this.zoomFactor = new NumberProperty( 0, {
range: new Range( -NUMBER_OF_ZOOM_LEVELS, NUMBER_OF_ZOOM_LEVELS )
} ).asRanged();

const fluxToIndicatorLengthProperty = new DerivedProperty( [ this.zoomFactor ], zoomFactor =>
NOMINAL_FLUX_TO_ARROW_LENGTH_MULTIPLIER * Math.pow( FLUX_ARROW_ZOOM_FACTOR, zoomFactor )
);

const sunlightDisplayArrow = new EnergyFluxDisplay(
model.fluxSensor.visibleLightDownEnergyRateTracker.energyRateProperty,
model.fluxSensor.visibleLightUpEnergyRateTracker.energyRateProperty,
zoomLevelProperty,
fluxToIndicatorLengthProperty,
sunlightString,
GreenhouseEffectConstants.SUNLIGHT_COLOR
);
const infraredDisplayArrow = new EnergyFluxDisplay(
model.fluxSensor.infraredLightDownEnergyRateTracker.energyRateProperty,
model.fluxSensor.infraredLightUpEnergyRateTracker.energyRateProperty,
zoomLevelProperty,
fluxToIndicatorLengthProperty,
infraredString,
GreenhouseEffectConstants.INFRARED_COLOR
);
const arrows = new HBox( { children: [ sunlightDisplayArrow, infraredDisplayArrow ], spacing: METER_SPACING } );

const zoomButtons = new MagnifyingGlassZoomButtonGroup( zoomLevelProperty, {
const zoomButtons = new MagnifyingGlassZoomButtonGroup( this.zoomFactor, {
spacing: 5,
applyZoomIn: ( currentZoom: number ) => currentZoom + FLUX_ARROW_ZOOM_DELTA,
applyZoomOut: ( currentZoom: number ) => currentZoom - FLUX_ARROW_ZOOM_DELTA,
applyZoomIn: ( currentZoom: number ) => currentZoom + 1,
applyZoomOut: ( currentZoom: number ) => currentZoom - 1,
magnifyingGlassNodeOptions: {
glassRadius: 6
},
Expand Down Expand Up @@ -270,6 +281,7 @@ class FluxMeterNode extends Node {

public reset(): void {
this.wasDraggedProperty.reset();
this.zoomFactor.reset();
}
}

Expand Down Expand Up @@ -327,6 +339,13 @@ class EnergyFluxDisplay extends Node {
} );
this.addChild( boundsRectangle );

// Add the Path that will display reference lines behind the arrows.
const referenceLinesNode = new Path( null, {
stroke: Color.GRAY.withAlpha( 0.3 ),
lineWidth: 2
} );
boundsRectangle.addChild( referenceLinesNode );

// Set a clip area so that the arrows don't go outside the background.
boundsRectangle.clipArea = Shape.bounds( boundsRectangle.getRectBounds() );

Expand Down Expand Up @@ -360,18 +379,51 @@ class EnergyFluxDisplay extends Node {
// a linear function that maps the number of photons going through the flux meter per second
const getArrowHeightFromFlux = ( flux: number, fluxToArrowLengthMultiplier: number ) => flux * fluxToArrowLengthMultiplier;

// Redraw arrows when the flux Properties change.
Multilink.multilink( [ energyDownProperty, fluxToArrowLengthMultiplierProperty ],
// Redraw arrows when the flux Properties or the display scale change.
Multilink.multilink(
[ energyDownProperty, fluxToArrowLengthMultiplierProperty ],
( energyDown, fluxToArrowLengthMultiplier ) => {
downArrow.visible = Math.abs( energyDown ) > 0;
downArrow.setTip( boundsRectangle.width / 2, boundsRectangle.height / 2 + getArrowHeightFromFlux( energyDown, fluxToArrowLengthMultiplier ) );
} );
}
);

Multilink.multilink( [ energyUpProperty, fluxToArrowLengthMultiplierProperty ],
Multilink.multilink(
[ energyUpProperty, fluxToArrowLengthMultiplierProperty ],
( energyUp, fluxToArrowLengthMultiplier ) => {
upArrow.visible = Math.abs( energyUp ) > 0;
upArrow.setTip( boundsRectangle.width / 2, boundsRectangle.height / 2 - getArrowHeightFromFlux( energyUp, fluxToArrowLengthMultiplier ) );
} );
}
);

// Define a reference flux value that will be used to define the spacing between the reference marks. This value
// was empirically determined to provide the desired look, and is based on the flux values that naturally occur in
// the model.
const referenceFlux = 5E6;

// Update the background reference marks when the zoom level changes.
fluxToArrowLengthMultiplierProperty.link( fluxToArrowLengthMultiplier => {
const referenceLinesShape = new Shape();
const interReferenceLineDistance = fluxToArrowLengthMultiplier * referenceFlux;
const referenceLineWidth = boundsRectangle.width * 0.5; // empirically determined

// Loop, creating the shape that will represent the reference lines.
for ( let distanceFromCenter = interReferenceLineDistance;
distanceFromCenter < boundsRectangle.height / 2;
distanceFromCenter += interReferenceLineDistance ) {

// Add lines in both the upward and downward directions.
referenceLinesShape.moveTo( 0, distanceFromCenter );
referenceLinesShape.lineTo( referenceLineWidth, distanceFromCenter );
referenceLinesShape.moveTo( 0, -distanceFromCenter );
referenceLinesShape.lineTo( referenceLineWidth, -distanceFromCenter );
}

// Set the shape and its position.
referenceLinesNode.setShape( referenceLinesShape );
referenceLinesNode.centerX = boundsRectangle.width / 2;
referenceLinesNode.centerY = boundsRectangle.height / 2;
} );

// layout
boundsRectangle.centerTop = labelText.centerBottom;
Expand Down

0 comments on commit 5eeac65

Please sign in to comment.