Skip to content

Commit

Permalink
Factor out accordion box subclasses, see #153
Browse files Browse the repository at this point in the history
  • Loading branch information
samreid committed Apr 26, 2023
1 parent f8c9289 commit f6a3059
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 179 deletions.
4 changes: 4 additions & 0 deletions js/common/view/CAVAccordionBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const BUTTON_SIDE_LENGTH = 20;

export default class CAVAccordionBox extends AccordionBox {

public readonly contentNode: Node;

// NOTE: The positions of the passed-in nodes are modified directly, so they cannot be used in the scenery DAG
public constructor( model: CAVModel, contentNode: Node, checkboxPanel: Node,
titleNode: Node, layoutBounds: Bounds2, providedOptions: CAVAccordionBoxOptions ) {
Expand Down Expand Up @@ -97,6 +99,8 @@ export default class CAVAccordionBox extends AccordionBox {
super( backgroundNode, options );

model.resetEmitter.addListener( () => this.reset() );

this.contentNode = contentNode;
}
}

Expand Down
2 changes: 1 addition & 1 deletion js/common/view/CAVScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default class CAVScreenView extends ScreenView {
protected readonly medianPredictionNode: PredictionSlider;
protected readonly meanPredictionNode: PredictionSlider;

private readonly accordionBox: CAVAccordionBox;
protected readonly accordionBox: CAVAccordionBox;

protected readonly questionBar: QuestionBar;
protected readonly playAreaNumberLineNode: NumberLineNode;
Expand Down
1 change: 1 addition & 0 deletions js/mean-and-median/view/CAVPlotNodeWithMedianBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type CAVPlotOptions = NodeOptions & PickRequired<NodeOptions, 'tandem'>;
// Prevent the median bar node from going off the top of the accordion box
const MARGIN_TO_TOP_OF_ACCORDION_BOX = 4;

// TODO: Rename to MedianPlotNode
export default class CAVPlotNodeWithMedianBar extends CAVPlotNode {

private readonly medianBarNode = new MedianBarNode( {
Expand Down
46 changes: 46 additions & 0 deletions js/mean-and-median/view/MeanAndMedianAccordionBox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2023, University of Colorado Boulder

import CAVAccordionBox from '../../common/view/CAVAccordionBox.js';
import TopRepresentationCheckboxGroup from '../../common/view/TopRepresentationCheckboxGroup.js';
import { Text, Node } from '../../../../scenery/js/imports.js';
import CenterAndVariabilityStrings from '../../CenterAndVariabilityStrings.js';
import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
import Bounds2 from '../../../../dot/js/Bounds2.js';
import Tandem from '../../../../tandem/js/Tandem.js';
import centerAndVariability from '../../centerAndVariability.js';
import MeanAndMedianModel from '../model/MeanAndMedianModel.js';
import CAVPlotNodeWithMedianBar from './CAVPlotNodeWithMedianBar.js';
import ValueReadoutsNode from '../../common/view/ValueReadoutsNode.js';

export default class MeanAndMedianAccordionBox extends CAVAccordionBox {

public constructor( model: MeanAndMedianModel, layoutBounds: Bounds2, tandem: Tandem, top: number, playAreaNumberLineNode: Node ) {

const accordionBoxContents = new CAVPlotNodeWithMedianBar( model, {
tandem: tandem.createTandem( 'plotNode' )
} );

super( model, accordionBoxContents, new TopRepresentationCheckboxGroup( model, {
medianBarIconOptions: {
notchDirection: 'down',
barStyle: 'continuous',
arrowScale: 0.75
},
showMedianCheckboxIcon: true,
tandem: tandem.createTandem( 'topRepresentationCheckboxGroup' )
} ),
new Text( CenterAndVariabilityStrings.distanceInMetersStringProperty, {
font: new PhetFont( 16 ),
maxWidth: 300
} ),
layoutBounds, {
leftMargin: 0,
tandem: tandem,
top: top,
valueReadoutsNode: new ValueReadoutsNode( model ),
centerX: layoutBounds.centerX
} );
}
}

centerAndVariability.register( 'MeanAndMedianAccordionBox', MeanAndMedianAccordionBox );
62 changes: 13 additions & 49 deletions js/mean-and-median/view/MeanAndMedianScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,12 @@ import centerAndVariability from '../../centerAndVariability.js';
import MeanAndMedianModel from '../model/MeanAndMedianModel.js';
import CAVColors from '../../common/CAVColors.js';
import CenterAndVariabilityStrings from '../../CenterAndVariabilityStrings.js';
import { ManualConstraint, Text, Node } from '../../../../scenery/js/imports.js';
import { ManualConstraint, Node } from '../../../../scenery/js/imports.js';
import StrictOmit from '../../../../phet-core/js/types/StrictOmit.js';
import CAVScreenView, { CAVScreenViewOptions } from '../../common/view/CAVScreenView.js';
import Bounds2 from '../../../../dot/js/Bounds2.js';
import CAVPlotNodeWithMedianBar from './CAVPlotNodeWithMedianBar.js';
import CAVAccordionBox from '../../common/view/CAVAccordionBox.js';
import ValueReadoutsNode from '../../common/view/ValueReadoutsNode.js';
import Tandem from '../../../../tandem/js/Tandem.js';
import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
import TopRepresentationCheckboxGroup from '../../common/view/TopRepresentationCheckboxGroup.js';
import MeanAndMedianAccordionBox from './MeanAndMedianAccordionBox.js';

type MeanAndMedianScreenViewOptions = StrictOmit<CAVScreenViewOptions, 'questionBarOptions'>;

Expand All @@ -36,50 +32,18 @@ export default class MeanAndMedianScreenView extends CAVScreenView {
}
}, providedOptions );

// TODO: Improve this pattern?
let afterInit: ( () => void ) | null = null;

super( model, ( tandem: Tandem, top: number, layoutBounds: Bounds2, playAreaNumberLineNode: Node ) => {

const accordionBoxContents = new CAVPlotNodeWithMedianBar( model, {
tandem: tandem.createTandem( 'plotNode' )
super( model, ( tandem: Tandem, top: number, layoutBounds: Bounds2, playAreaNumberLineNode: Node ) =>
new MeanAndMedianAccordionBox( model, layoutBounds, tandem, top, playAreaNumberLineNode ), options );

// NOTE: This assumes that the NumberLineNode in the play area and in the dot plot have the same characteristics:
// * Same font
// * Same offset and scale
// But given those assumptions, this code moves the dot plot so that its number line matches the play area one.
// TODO: Consider something more robust. Using globalToLocal to exactly align based on the position of the tick marks
ManualConstraint.create( this, [ this.playAreaNumberLineNode, this.accordionBox.contentNode ],
( playAreaNumberLineNodeWrapper, contentsWrapper ) => {
contentsWrapper.x = playAreaNumberLineNodeWrapper.x;
} );

afterInit = () => {
// NOTE: This assumes that the NumberLineNode in the play area and in the dot plot have the same characteristics:
// * Same font
// * Same offset and scale
// But given those assumptions, this code moves the dot plot so that its number line matches the play area one.
// TODO: Consider something more robust. Using globalToLocal to exactly align based on the position of the tick marks
ManualConstraint.create( this, [ playAreaNumberLineNode, accordionBoxContents ],
( lowerNumberLineWrapper, contentsWrapper ) => {
contentsWrapper.x = lowerNumberLineWrapper.x;
} );
};

return new CAVAccordionBox( model, accordionBoxContents, new TopRepresentationCheckboxGroup( model, {
medianBarIconOptions: {
notchDirection: 'down',
barStyle: 'continuous',
arrowScale: 0.75
},
showMedianCheckboxIcon: true,
tandem: tandem.createTandem( 'topRepresentationCheckboxGroup' )
} ),
new Text( CenterAndVariabilityStrings.distanceInMetersStringProperty, {
font: new PhetFont( 16 ),
maxWidth: 300
} ),
layoutBounds, {
leftMargin: 0,
tandem: tandem,
top: top,
valueReadoutsNode: new ValueReadoutsNode( model ),
centerX: layoutBounds.centerX
} );
}, options );

afterInit!();
}
}

Expand Down
45 changes: 45 additions & 0 deletions js/median/view/MedianAccordionBox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2023, University of Colorado Boulder

import CAVAccordionBox from '../../common/view/CAVAccordionBox.js';
import CardNodeContainer from '../../common/view/CardNodeContainer.js';
import TopRepresentationCheckboxGroup from '../../common/view/TopRepresentationCheckboxGroup.js';
import { Text } from '../../../../scenery/js/imports.js';
import CenterAndVariabilityStrings from '../../CenterAndVariabilityStrings.js';
import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
import MedianModel from '../model/MedianModel.js';
import Bounds2 from '../../../../dot/js/Bounds2.js';
import Tandem from '../../../../tandem/js/Tandem.js';
import centerAndVariability from '../../centerAndVariability.js';

export default class MedianAccordionBox extends CAVAccordionBox {

public constructor( model: MedianModel, layoutBounds: Bounds2, tandem: Tandem, top: number ) {
super( model, new CardNodeContainer( model, {

// Expose this intermediate layer to make it so that clients can hide the number cards with one call
tandem: tandem.createTandem( 'cardNodeContainer' )
} ), new TopRepresentationCheckboxGroup( model, {
includeSortData: true,
includeMean: false,
medianBarIconOptions: {
notchDirection: 'up',
barStyle: 'continuous'
},
showMedianCheckboxIcon: false,
tandem: tandem.createTandem( 'checkboxGroup' )
} ),
new Text( CenterAndVariabilityStrings.distanceInMetersStringProperty, {
font: new PhetFont( 16 ),
maxWidth: 300
} ),
layoutBounds, {
leftMargin: 0,
tandem: tandem,
top: top,
valueReadoutsNode: null,
centerX: layoutBounds.centerX
} );
}
}

centerAndVariability.register( 'MedianAccordionBox', MedianAccordionBox );
36 changes: 4 additions & 32 deletions js/median/view/MedianScreenView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,10 @@ import MedianModel from '../model/MedianModel.js';
import CAVColors from '../../common/CAVColors.js';
import CenterAndVariabilityStrings from '../../CenterAndVariabilityStrings.js';
import StrictOmit from '../../../../phet-core/js/types/StrictOmit.js';
import TopRepresentationCheckboxGroup from '../../common/view/TopRepresentationCheckboxGroup.js';
import CAVScreenView, { CAVScreenViewOptions } from '../../common/view/CAVScreenView.js';
import CAVAccordionBox from '../../common/view/CAVAccordionBox.js';
import { Text } from '../../../../scenery/js/imports.js';
import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
import CardNodeContainer from '../../common/view/CardNodeContainer.js';
import Bounds2 from '../../../../dot/js/Bounds2.js';
import Tandem from '../../../../tandem/js/Tandem.js';
import MedianAccordionBox from './MedianAccordionBox.js';

type SelfOptions = EmptySelfOptions;
type MedianScreenViewOptions =
Expand All @@ -44,33 +40,9 @@ export default class MedianScreenView extends CAVScreenView {
}
}, providedOptions );

super( model, ( tandem: Tandem, top: number, layoutBounds: Bounds2 ) => {
return new CAVAccordionBox( model, new CardNodeContainer( model, {

// Expose this intermediate layer to make it so that clients can hide the number cards with one call
tandem: tandem.createTandem( 'cardNodeContainer' )
} ), new TopRepresentationCheckboxGroup( model, {
includeSortData: true,
includeMean: false,
medianBarIconOptions: {
notchDirection: 'up',
barStyle: 'continuous'
},
showMedianCheckboxIcon: false,
tandem: tandem.createTandem( 'checkboxGroup' )
} ),
new Text( CenterAndVariabilityStrings.distanceInMetersStringProperty, {
font: new PhetFont( 16 ),
maxWidth: 300
} ),
layoutBounds, {
leftMargin: 0,
tandem: tandem,
top: top,
valueReadoutsNode: null,
centerX: layoutBounds.centerX
} );
}, options );
// TODO: Arg order looks scrambled, should it be unified or optionized?
super( model, ( tandem: Tandem, top: number, layoutBounds: Bounds2 ) =>
new MedianAccordionBox( model, layoutBounds, tandem, top ), options );
}
}

Expand Down
91 changes: 91 additions & 0 deletions js/variability/view/VariabilityAccordionBox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2023, University of Colorado Boulder

import CAVAccordionBox from '../../common/view/CAVAccordionBox.js';
import { Text } from '../../../../scenery/js/imports.js';
import CenterAndVariabilityStrings from '../../CenterAndVariabilityStrings.js';
import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
import Bounds2 from '../../../../dot/js/Bounds2.js';
import Tandem from '../../../../tandem/js/Tandem.js';
import centerAndVariability from '../../centerAndVariability.js';
import VariabilityModel from '../model/VariabilityModel.js';
import VariabilityPlotNode from './VariabilityPlotNode.js';
import InfoButton from '../../../../scenery-phet/js/buttons/InfoButton.js';
import ToggleNode from '../../../../sun/js/ToggleNode.js';
import VariabilityMeasure from '../model/VariabilityMeasure.js';
import Checkbox from '../../../../sun/js/Checkbox.js';
import VariabilityReadoutsNode from './VariabilityReadoutsNode.js';
import CAVConstants from '../../common/CAVConstants.js';
import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
import DynamicProperty from '../../../../axon/js/DynamicProperty.js';

// TODO: Copied from somewhere. What's the best pattern?
const TEXT_OPTIONS = {
font: CAVConstants.BUTTON_FONT,
maxWidth: CAVConstants.PLAY_AREA_CHECKBOX_TEXT_MAX_WIDTH
};

export default class VariabilityAccordionBox extends CAVAccordionBox {

public constructor( model: VariabilityModel, layoutBounds: Bounds2, tandem: Tandem, top: number ) {

const currentProperty = new DerivedProperty( [ model.selectedVariabilityProperty ], selectedVariability =>
selectedVariability === VariabilityMeasure.RANGE ? CenterAndVariabilityStrings.rangeStringProperty :
selectedVariability === VariabilityMeasure.IQR ? CenterAndVariabilityStrings.interquartileRangeIQRStringProperty :
CenterAndVariabilityStrings.meanAbsoluteDeviationMADStringProperty
);

const accordionBoxTitleProperty = new DynamicProperty<string, unknown, unknown>( currentProperty );

const accordionBoxContents = new VariabilityPlotNode( model, {
tandem: tandem.createTandem( 'plotNode' )
} );

const infoButton = new InfoButton( {
iconFill: 'cornflowerblue',
scale: 0.5,
touchAreaDilation: 5,
tandem: tandem.createTandem( 'infoButton' ),
listener: () => {
model.isInfoShowingProperty.value = true;
},

// TODO: How to position this properly?
top: 10,
right: accordionBoxContents.width + 130
} );
accordionBoxContents.addChild( infoButton );

super( model, accordionBoxContents, new ToggleNode( model.selectedVariabilityProperty, [ {
value: VariabilityMeasure.RANGE,

// TODO: Different string value? For now, use the same string for the accordion box title and checkbox, and a different one for the value equals pattern
createNode: tandem => new Checkbox( model.isShowingRangeProperty, new Text( CenterAndVariabilityStrings.rangeStringProperty, TEXT_OPTIONS ), {
tandem: tandem.createTandem( 'rangeCheckbox' )
} )
}, {
value: VariabilityMeasure.IQR,
createNode: tandem => new Checkbox( model.isShowingIQRProperty, new Text( CenterAndVariabilityStrings.iqrStringProperty, TEXT_OPTIONS ), {
tandem: tandem.createTandem( 'iqrCheckbox' )
} )
}, {
value: VariabilityMeasure.MAD,
createNode: tandem => new Checkbox( model.isShowingMADProperty, new Text( CenterAndVariabilityStrings.madStringProperty, TEXT_OPTIONS ), {
tandem: tandem.createTandem( 'madCheckbox' )
} )
}
] ),
new Text( accordionBoxTitleProperty, {
font: new PhetFont( 16 ),
maxWidth: 300
} ),
layoutBounds, {
leftMargin: 70,
tandem: tandem,
top: top,
valueReadoutsNode: new VariabilityReadoutsNode( model ),
right: layoutBounds.right - CAVConstants.SCREEN_VIEW_X_MARGIN
} );
}
}

centerAndVariability.register( 'VariabilityAccordionBox', VariabilityAccordionBox );
Loading

0 comments on commit f6a3059

Please sign in to comment.