diff --git a/js/least-squares-regression-main.ts b/js/least-squares-regression-main.ts index e47aa2a..ed315c8 100644 --- a/js/least-squares-regression-main.ts +++ b/js/least-squares-regression-main.ts @@ -13,16 +13,14 @@ import LeastSquaresRegressionStrings from './LeastSquaresRegressionStrings.js'; const leastSquaresRegressionTitleStringProperty = LeastSquaresRegressionStrings[ 'least-squares-regression' ].titleStringProperty; -const simOptions = { - credits: { - leadDesign: 'Amanda McGarry', - softwareDevelopment: 'Martin Veillette', - team: 'Trish Loeblein, Ariel Paul, Kathy Perkins', - qualityAssurance: 'Steele Dalton, Bryan Yoelin' - } -}; - simLauncher.launch( () => { - const sim = new Sim( leastSquaresRegressionTitleStringProperty, [ new LeastSquaresRegressionScreen() ], simOptions ); + const sim = new Sim( leastSquaresRegressionTitleStringProperty, [ new LeastSquaresRegressionScreen() ], { + credits: { + leadDesign: 'Amanda McGarry', + softwareDevelopment: 'Martin Veillette', + team: 'Trish Loeblein, Ariel Paul, Kathy Perkins', + qualityAssurance: 'Steele Dalton, Bryan Yoelin' + } + } ); sim.start(); } ); \ No newline at end of file diff --git a/js/least-squares-regression/LeastSquaresRegressionScreen.ts b/js/least-squares-regression/LeastSquaresRegressionScreen.ts index edf1190..a76c224 100644 --- a/js/least-squares-regression/LeastSquaresRegressionScreen.ts +++ b/js/least-squares-regression/LeastSquaresRegressionScreen.ts @@ -7,17 +7,20 @@ import Property from '../../../axon/js/Property.js'; import Screen from '../../../joist/js/Screen.js'; +import Tandem from '../../../tandem/js/Tandem.js'; import leastSquaresRegression from '../leastSquaresRegression.js'; import LeastSquaresRegressionConstants from './LeastSquaresRegressionConstants.js'; import LeastSquaresRegressionModel from './model/LeastSquaresRegressionModel.js'; import LeastSquaresRegressionScreenView from './view/LeastSquaresRegressionScreenView.js'; -class LeastSquaresRegressionScreen extends Screen { - constructor() { +class LeastSquaresRegressionScreen extends Screen { + public constructor() { super( () => new LeastSquaresRegressionModel(), - model => new LeastSquaresRegressionScreenView( model ), - { backgroundColorProperty: new Property( LeastSquaresRegressionConstants.BACKGROUND_COLOR ) } + model => new LeastSquaresRegressionScreenView( model ), { + backgroundColorProperty: new Property( LeastSquaresRegressionConstants.BACKGROUND_COLOR ), + tandem: Tandem.OPT_OUT + } ); } } diff --git a/js/least-squares-regression/model/DataPoint.ts b/js/least-squares-regression/model/DataPoint.ts index 5e8ad29..633e63d 100644 --- a/js/least-squares-regression/model/DataPoint.ts +++ b/js/least-squares-regression/model/DataPoint.ts @@ -14,45 +14,54 @@ import Vector2Property from '../../../../dot/js/Vector2Property.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; +// TODO https://github.com/phetsims/least-squares-regression/issues/94 is this global needed? +// TODO https://github.com/phetsims/least-squares-regression/issues/94 Use twixt? /* global TWEEN */ -class DataPoint { +// TODO: export default on all, see https://github.com/phetsims/least-squares-regression/issues/94 +export default class DataPoint { + + // Indicates where in model space the center of this data point is. + public readonly positionProperty: Vector2Property; + + // Flag that tracks whether the user is dragging this data point around. + public readonly userControlledProperty: BooleanProperty; + + // Flag that indicates whether this element is animating from one position back to the bucket. + public readonly animatingProperty: BooleanProperty; + + // Emitter that fires when the data point has returned to its origin. + public readonly returnedToOriginEmitter: Emitter; + + // TODO https://github.com/phetsims/least-squares-regression/issues/94 should this be in the subtype? + public positionUpdateListener?: () => void; + public userControlledListener?: ( userControlled: boolean ) => void; + public returnedToOriginListener?: () => void; + /** - * @param {Vector2} initialPosition + * @param initialPosition - the initial position of the DataPoint in model space */ - constructor( initialPosition ) { + public constructor( initialPosition: Vector2 ) { - // @public - indicates where in model space the center of this data point is. this.positionProperty = new Vector2Property( initialPosition ); - - // @public {Property.} - // Flag that tracks whether the user is dragging this data point around. Should be set externally, generally by a - // view node. this.userControlledProperty = new BooleanProperty( false ); - - // @public read-only {Property.} - // Flag that indicates whether this element is animating from one position to the bucket. this.animatingProperty = new BooleanProperty( false ); - - // @public this.returnedToOriginEmitter = new Emitter(); } /** - * resets all the properties of DataPoint - * @public + * Resets all the properties of DataPoint. */ - reset() { + public reset(): void { this.positionProperty.reset(); this.userControlledProperty.reset(); this.animatingProperty.reset(); } /** - * Function that animates dataPoint back to the bucket. - * @public + * Function that animates the DataPoint back to the bucket. */ - animate() { + public animate(): void { this.animatingProperty.set( true ); const position = { @@ -64,15 +73,19 @@ class DataPoint { const distance = this.positionProperty.initialValue.distance( this.positionProperty.value ); if ( distance > 0 ) { - const animationTween = new TWEEN.Tween( position ).to( { - x: this.positionProperty.initialValue.x, - y: this.positionProperty.initialValue.y - }, distance / LeastSquaresRegressionConstants.ANIMATION_SPEED ).easing( TWEEN.Easing.Cubic.In ).onUpdate( () => { - this.positionProperty.set( new Vector2( position.x, position.y ) ); - } ).onComplete( () => { - this.animatingProperty.set( false ); - this.returnedToOriginEmitter.emit(); - } ); + const animationTween = new TWEEN.Tween( position ) + .to( { + x: this.positionProperty.initialValue.x, + y: this.positionProperty.initialValue.y + }, distance / LeastSquaresRegressionConstants.ANIMATION_SPEED ) + .easing( TWEEN.Easing.Cubic.In ) + .onUpdate( () => { + this.positionProperty.set( new Vector2( position.x, position.y ) ); + } ) + .onComplete( () => { + this.animatingProperty.set( false ); + this.returnedToOriginEmitter.emit(); + } ); animationTween.start( phet.joist.elapsedTime ); } @@ -86,6 +99,4 @@ class DataPoint { } } -leastSquaresRegression.register( 'DataPoint', DataPoint ); - -export default DataPoint; \ No newline at end of file +leastSquaresRegression.register( 'DataPoint', DataPoint ); \ No newline at end of file diff --git a/js/least-squares-regression/model/DataSet.ts b/js/least-squares-regression/model/DataSet.ts index cba8fa5..297e4b4 100644 --- a/js/least-squares-regression/model/DataSet.ts +++ b/js/least-squares-regression/model/DataSet.ts @@ -207,681 +207,668 @@ const heightShoe = { source: SOURCES.HEIGHT_SHOE_SOURCE }; -/** - * Function that returns a dataSet Object - * @param {Object} name - object of the form { graphTitle: string, yAxisTitle: string, xAxisTitle: string, reference: string, source: string} - * @param {Range} yRange - * @param {Range} xRange - * @param {Array.} dataXY - array of object of the form {x: 2, y: 5} - * @returns {Object} dataSet - */ -function createDataSet( name, yRange, xRange, dataXY ) { - const dataSet = { - yRange: yRange, - xRange: xRange, - dataXY: dataXY, - name: name.graphTitle, - yAxisTitle: name.yAxisTitle, - xAxisTitle: name.xAxisTitle, - reference: name.reference, - source: name.source - }; - - return dataSet; -} - -const DataSet = {}; - -DataSet.CUSTOM = createDataSet( custom, new Range( 0, 20 ), new Range( 0, 20 ), [] ); - -DataSet.TEMPERATURE_FAHRENHEIT_LONGITUDE = createDataSet( temperatureFahrenheitLongitude, new Range( 0, 80 ), new Range( 0, 140 ), - [ - { x: 88.5, y: 44 }, - { x: 86.8, y: 38 }, - { x: 112.5, y: 35 }, - { x: 92.8, y: 31 }, - { x: 118.7, y: 47 }, - { x: 123, y: 42 }, - { x: 105.3, y: 15 }, - { x: 73.4, y: 22 }, - { x: 76.3, y: 26 }, - { x: 77.5, y: 30 }, - { x: 82.3, y: 45 }, - { x: 82, y: 65 }, - { x: 80.7, y: 58 }, - { x: 85, y: 37 }, - { x: 117.1, y: 22 }, - { x: 88, y: 19 }, - { x: 86.9, y: 21 }, - { x: 93.6, y: 11 }, - { x: 97.6, y: 22 }, - { x: 86.5, y: 27 }, - { x: 90.2, y: 45 }, - { x: 70.5, y: 12 }, - { x: 77.3, y: 25 }, - { x: 71.4, y: 23 }, - { x: 83.9, y: 21 }, - { x: 93.9, y: 2 }, - { x: 90.5, y: 24 }, - { x: 112.4, y: 8 }, - { x: 96.1, y: 13 }, - { x: 71.9, y: 11 }, - { x: 75.3, y: 27 }, - { x: 106.7, y: 24 }, - { x: 73.7, y: 14 }, - { x: 74.6, y: 27 }, - { x: 81.5, y: 34 }, - { x: 78.9, y: 31 }, - { x: 101, y: 0 }, - { x: 85, y: 26 }, - { x: 82.5, y: 21 }, - { x: 97.5, y: 28 }, - { x: 123.2, y: 33 }, - { x: 77.8, y: 24 }, - { x: 75.5, y: 24 }, - { x: 80.8, y: 38 }, - { x: 87.6, y: 31 }, - { x: 101.9, y: 24 }, - { x: 95.5, y: 49 }, - { x: 95.9, y: 44 }, - { x: 112.3, y: 18 }, - { x: 73.9, y: 7 }, - { x: 76.6, y: 32 }, - { x: 122.5, y: 33 }, - { x: 117.9, y: 19 }, - { x: 90.2, y: 9 }, - { x: 88.1, y: 13 }, - { x: 104.9, y: 14 } - ] ); +export default class DataSet { + + public constructor( + private readonly specs: { graphTitle: string; yAxisTitle: string; xAxisTitle: string; reference: string; source: string }, // TODO: better name https://github.com/phetsims/least-squares-regression/issues/94 + public readonly yRange: Range, + public readonly xRange: Range, + public readonly dataXY: Array<{ x: number; y: number }>, + public readonly name = specs.graphTitle, + public readonly yAxisTitle: string = specs.yAxisTitle, + public readonly xAxisTitle: string = specs.xAxisTitle, + public readonly reference: string = specs.reference, + public readonly source: string = specs.source + ) {} + + public static readonly CUSTOM = new DataSet( custom, new Range( 0, 20 ), new Range( 0, 20 ), [] ); + public static readonly TEMPERATURE_FAHRENHEIT_LONGITUDE = new DataSet( temperatureFahrenheitLongitude, new Range( 0, 80 ), new Range( 0, 140 ), + [ + { x: 88.5, y: 44 }, + { x: 86.8, y: 38 }, + { x: 112.5, y: 35 }, + { x: 92.8, y: 31 }, + { x: 118.7, y: 47 }, + { x: 123, y: 42 }, + { x: 105.3, y: 15 }, + { x: 73.4, y: 22 }, + { x: 76.3, y: 26 }, + { x: 77.5, y: 30 }, + { x: 82.3, y: 45 }, + { x: 82, y: 65 }, + { x: 80.7, y: 58 }, + { x: 85, y: 37 }, + { x: 117.1, y: 22 }, + { x: 88, y: 19 }, + { x: 86.9, y: 21 }, + { x: 93.6, y: 11 }, + { x: 97.6, y: 22 }, + { x: 86.5, y: 27 }, + { x: 90.2, y: 45 }, + { x: 70.5, y: 12 }, + { x: 77.3, y: 25 }, + { x: 71.4, y: 23 }, + { x: 83.9, y: 21 }, + { x: 93.9, y: 2 }, + { x: 90.5, y: 24 }, + { x: 112.4, y: 8 }, + { x: 96.1, y: 13 }, + { x: 71.9, y: 11 }, + { x: 75.3, y: 27 }, + { x: 106.7, y: 24 }, + { x: 73.7, y: 14 }, + { x: 74.6, y: 27 }, + { x: 81.5, y: 34 }, + { x: 78.9, y: 31 }, + { x: 101, y: 0 }, + { x: 85, y: 26 }, + { x: 82.5, y: 21 }, + { x: 97.5, y: 28 }, + { x: 123.2, y: 33 }, + { x: 77.8, y: 24 }, + { x: 75.5, y: 24 }, + { x: 80.8, y: 38 }, + { x: 87.6, y: 31 }, + { x: 101.9, y: 24 }, + { x: 95.5, y: 49 }, + { x: 95.9, y: 44 }, + { x: 112.3, y: 18 }, + { x: 73.9, y: 7 }, + { x: 76.6, y: 32 }, + { x: 122.5, y: 33 }, + { x: 117.9, y: 19 }, + { x: 90.2, y: 9 }, + { x: 88.1, y: 13 }, + { x: 104.9, y: 14 } + ] ); + + // celsius + public static readonly TEMPERATURE_CELSIUS_LONGITUDE = new DataSet( temperatureCelsiusLongitude, new Range( -20, 20 ), new Range( 0, 140 ), + [ + { x: 88.5, y: 6.6 }, + { x: 86.8, y: 3.3 }, + { x: 112.5, y: 1.7 }, + { x: 92.8, y: -0.6 }, + { x: 118.7, y: 8.3 }, + { x: 123, y: 5.5 }, + { x: 105.3, y: -9.4 }, + { x: 73.4, y: -5.6 }, + { x: 76.3, y: -3.3 }, + { x: 77.5, y: -1.1 }, + { x: 82.3, y: 7.2 }, + { x: 82, y: 18.3 }, + { x: 80.7, y: 14.4 }, + { x: 85, y: 2.8 }, + { x: 117.1, y: -5.6 }, + { x: 88, y: -7.2 }, + { x: 86.9, y: -6.1 }, + { x: 93.6, y: -11.7 }, + { x: 97.6, y: -5.6 }, + { x: 86.5, y: -2.8 }, + { x: 90.2, y: 7.2 }, + { x: 70.5, y: -11.1 }, + { x: 77.3, y: -3.9 }, + { x: 71.4, y: -5 }, + { x: 83.9, y: -6.1 }, + { x: 93.9, y: -16.7 }, + { x: 90.5, y: -4.4 }, + { x: 112.4, y: -13.3 }, + { x: 96.1, y: -10.6 }, + { x: 71.9, y: -11.7 }, + { x: 75.3, y: -2.8 }, + { x: 106.7, y: -4.4 }, + { x: 73.7, y: -10 }, + { x: 74.6, y: -2.8 }, + { x: 81.5, y: 1.1 }, + { x: 78.9, y: -0.6 }, + { x: 101, y: -17.8 }, + { x: 85, y: -3.3 }, + { x: 82.5, y: -6.1 }, + { x: 97.5, y: -2.2 }, + { x: 123.2, y: 0.6 }, + { x: 77.8, y: -4.4 }, + { x: 75.5, y: -4.4 }, + { x: 80.8, y: 3.3 }, + { x: 87.6, y: -0.6 }, + { x: 101.9, y: -4.4 }, + { x: 95.5, y: 9.4 }, + { x: 95.9, y: 6.6 }, + { x: 112.3, y: -7.8 }, + { x: 73.9, y: -13.9 }, + { x: 76.6, y: 0 }, + { x: 122.5, y: 0.6 }, + { x: 117.9, y: -7.2 }, + { x: 90.2, y: -12.8 }, + { x: 88.1, y: -10.6 }, + { x: 104.9, y: -10 } + ] ); + + public static readonly TEMPERATURE_FAHRENHEIT_LATITUDE = new DataSet( temperatureFahrenheitLatitude, new Range( 0, 80 ), new Range( 0, 60 ), + [ + { x: 31.2, y: 44 }, + { x: 32.9, y: 38 }, + { x: 33.6, y: 35 }, + { x: 35.4, y: 31 }, + { x: 34.3, y: 47 }, + { x: 38.4, y: 42 }, + { x: 40.7, y: 15 }, + { x: 41.7, y: 22 }, + { x: 40.5, y: 26 }, + { x: 39.7, y: 30 }, + { x: 31, y: 45 }, + { x: 25, y: 65 }, + { x: 26.3, y: 58 }, + { x: 33.9, y: 37 }, + { x: 43.7, y: 22 }, + { x: 42.3, y: 19 }, + { x: 39.8, y: 21 }, + { x: 41.8, y: 11 }, + { x: 38.1, y: 22 }, + { x: 39, y: 27 }, + { x: 30.8, y: 45 }, + { x: 44.2, y: 12 }, + { x: 39.7, y: 25 }, + { x: 42.7, y: 23 }, + { x: 43.1, y: 21 }, + { x: 45.9, y: 2 }, + { x: 39.3, y: 24 }, + { x: 47.1, y: 8 }, + { x: 41.9, y: 13 }, + { x: 43.5, y: 11 }, + { x: 39.8, y: 27 }, + { x: 35.1, y: 24 }, + { x: 42.6, y: 14 }, + { x: 40.8, y: 27 }, + { x: 35.9, y: 34 }, + { x: 36.4, y: 31 }, + { x: 47.1, y: 0 }, + { x: 39.2, y: 26 }, + { x: 42.3, y: 21 }, + { x: 35.9, y: 28 }, + { x: 45.6, y: 33 }, + { x: 40.9, y: 24 }, + { x: 40.9, y: 24 }, + { x: 33.3, y: 38 }, + { x: 36.7, y: 31 }, + { x: 35.6, y: 24 }, + { x: 29.4, y: 49 }, + { x: 30.1, y: 44 }, + { x: 41.1, y: 18 }, + { x: 45, y: 7 }, + { x: 37, y: 32 }, + { x: 48.1, y: 33 }, + { x: 48.1, y: 19 }, + { x: 43.4, y: 9 }, + { x: 43.3, y: 13 }, + { x: 41.2, y: 14 } + ] ); + + public static readonly TEMPERATURE_CELSIUS_LATITUDE = new DataSet( temperatureCelsiusLatitude, new Range( -20, 20 ), new Range( 0, 60 ), + [ + { x: 31.2, y: 6.6 }, + { x: 32.9, y: 3.3 }, + { x: 33.6, y: 1.7 }, + { x: 35.4, y: -0.6 }, + { x: 34.3, y: 8.3 }, + { x: 38.4, y: 5.5 }, + { x: 40.7, y: -9.4 }, + { x: 41.7, y: -5.6 }, + { x: 40.5, y: -3.3 }, + { x: 39.7, y: -1.1 }, + { x: 31, y: 7.2 }, + { x: 25, y: 18.3 }, + { x: 26.3, y: 14.4 }, + { x: 33.9, y: 2.8 }, + { x: 43.7, y: -5.6 }, + { x: 42.3, y: -7.2 }, + { x: 39.8, y: -6.1 }, + { x: 41.8, y: -11.7 }, + { x: 38.1, y: -5.6 }, + { x: 39, y: -2.8 }, + { x: 30.8, y: 7.2 }, + { x: 44.2, y: -11.1 }, + { x: 39.7, y: -3.9 }, + { x: 42.7, y: -5 }, + { x: 43.1, y: -6.1 }, + { x: 45.9, y: -16.7 }, + { x: 39.3, y: -4.4 }, + { x: 47.1, y: -13.3 }, + { x: 41.9, y: -10.6 }, + { x: 43.5, y: -11.7 }, + { x: 39.8, y: -2.8 }, + { x: 35.1, y: -4.4 }, + { x: 42.6, y: -10 }, + { x: 40.8, y: -2.8 }, + { x: 35.9, y: 1.1 }, + { x: 36.4, y: -0.6 }, + { x: 47.1, y: -17.8 }, + { x: 39.2, y: -3.3 }, + { x: 42.3, y: -6.1 }, + { x: 35.9, y: -2.2 }, + { x: 45.6, y: 0.6 }, + { x: 40.9, y: -4.4 }, + { x: 40.9, y: -4.4 }, + { x: 33.3, y: 3.3 }, + { x: 36.7, y: -0.6 }, + { x: 35.6, y: -4.4 }, + { x: 29.4, y: 9.4 }, + { x: 30.1, y: 6.6 }, + { x: 41.1, y: -7.8 }, + { x: 45, y: -13.9 }, + { x: 37, y: 0 }, + { x: 48.1, y: 0.6 }, + { x: 48.1, y: -7.2 }, + { x: 43.4, y: -12.8 }, + { x: 43.3, y: -10.6 }, + { x: 41.2, y: -10 } + ] ); + + public static readonly SPENDING_SALARY = new DataSet( spendingSalary, new Range( 0, 10 ), new Range( 0, 50 ), + [ + { x: 19.583, y: 3.346 }, + { x: 20.263, y: 3.114 }, + { x: 20.325, y: 3.554 }, + { x: 26.800, y: 4.642 }, + { x: 29.470, y: 4.669 }, + { x: 26.610, y: 4.888 }, + { x: 30.678, y: 5.710 }, + { x: 27.170, y: 5.536 }, + { x: 25.853, y: 4.168 }, + { x: 24.500, y: 3.547 }, + { x: 24.274, y: 3.159 }, + { x: 27.170, y: 3.621 }, + { x: 30.168, y: 3.782 }, + { x: 26.525, y: 4.247 }, + { x: 27.360, y: 3.982 }, + { x: 21.690, y: 3.568 }, + { x: 21.974, y: 3.155 }, + { x: 20.816, y: 3.059 }, + { x: 18.095, y: 2.967 }, + { x: 20.939, y: 3.285 }, + { x: 22.644, y: 3.914 }, + { x: 24.624, y: 4.517 }, + { x: 27.186, y: 4.349 }, + { x: 33.990, y: 5.020 }, + { x: 23.382, y: 3.594 }, + { x: 20.627, y: 2.821 }, + { x: 22.795, y: 3.366 }, + { x: 21.570, y: 2.920 }, + { x: 22.080, y: 2.980 }, + { x: 22.250, y: 3.731 }, + { x: 20.940, y: 2.853 }, + { x: 21.800, y: 2.533 }, + { x: 22.934, y: 2.729 }, + { x: 18.443, y: 2.305 }, + { x: 19.538, y: 2.642 }, + { x: 20.460, y: 3.124 }, + { x: 21.419, y: 2.752 }, + { x: 25.160, y: 3.429 }, + { x: 22.482, y: 3.947 }, + { x: 20.969, y: 2.509 }, + { x: 27.224, y: 5.440 }, + { x: 25.892, y: 4.042 }, + { x: 22.644, y: 3.402 }, + { x: 24.640, y: 2.829 }, + { x: 22.341, y: 2.297 }, + { x: 25.610, y: 2.932 }, + { x: 26.015, y: 3.705 }, + { x: 25.788, y: 4.123 }, + { x: 29.132, y: 3.608 }, + { x: 41.480, y: 8.349 }, + { x: 25.845, y: 3.766 } + ] ); + + public static readonly WAGE_YEAR = new DataSet( wageYear, new Range( 0, 8 ), new Range( 0, 60 ), + [ + { x: 0, y: 2.94 }, + { x: 1, y: 2.72 }, + { x: 2, y: 2.75 }, + { x: 3, y: 5.1 }, + { x: 4, y: 4.72 }, + { x: 5, y: 4.64 }, + { x: 6, y: 4.6 }, + { x: 7, y: 4.57 }, + { x: 8, y: 4.58 }, + { x: 9, y: 5.77 }, + { x: 10, y: 5.82 }, + { x: 11, y: 5.66 }, + { x: 12, y: 5.62 }, + { x: 13, y: 5.53 }, + { x: 14, y: 5.75 }, + { x: 15, y: 6.23 }, + { x: 16, y: 6.31 }, + { x: 17, y: 6.6 }, + { x: 18, y: 6.5 }, + { x: 19, y: 6.31 }, + { x: 20, y: 6.81 }, + { x: 21, y: 7.44 }, + { x: 22, y: 7.22 }, + { x: 23, y: 6.89 }, + { x: 24, y: 6.6 }, + { x: 25, y: 6.4 }, + { x: 26, y: 6.02 }, + { x: 27, y: 6.41 }, + { x: 28, y: 6.64 }, + { x: 29, y: 6.88 }, + { x: 30, y: 6.47 }, + { x: 31, y: 7.15 }, + { x: 32, y: 7.15 }, + { x: 33, y: 6.88 }, + { x: 34, y: 6.8 }, + { x: 35, y: 6.42 }, + { x: 36, y: 6.16 }, + { x: 37, y: 5.93 }, + { x: 38, y: 5.73 }, + { x: 39, y: 5.63 }, + { x: 40, y: 5.45 }, + { x: 41, y: 5.25 }, + { x: 42, y: 5.04 }, + { x: 43, y: 5.29 }, + { x: 44, y: 5.72 }, + { x: 45, y: 5.73 }, + { x: 46, y: 5.59 }, + { x: 47, y: 5.48 }, + { x: 48, y: 5.35 }, + { x: 49, y: 5.37 }, + { x: 50, y: 5.86 }, + { x: 51, y: 6.09 }, + { x: 52, y: 5.97 }, + { x: 53, y: 5.78 }, + { x: 54, y: 5.62 }, + { x: 55, y: 5.53 }, + { x: 56, y: 5.41 }, + { x: 57, y: 5.27 }, + { x: 58, y: 5.15 } + ] ); + + public static readonly MORTALITY_YEAR = new DataSet( mortalityYear, new Range( 0, 500 ), new Range( 0, 40 ), + [ + { x: 1, y: 29 }, + { x: 2, y: 62 }, + { x: 3, y: 114 }, + { x: 4, y: 84 }, + { x: 5, y: 77 }, + { x: 6, y: 63 }, + { x: 7, y: 116 }, + { x: 8, y: 114 }, + { x: 9, y: 81 }, + { x: 10, y: 128 }, + { x: 11, y: 119 }, + { x: 12, y: 122 }, + { x: 13, y: 114 }, + { x: 14, y: 133 }, + { x: 15, y: 168 }, + { x: 16, y: 206 }, + { x: 17, y: 174 }, + { x: 18, y: 163 }, + { x: 19, y: 145 }, + { x: 20, y: 193 }, + { x: 21, y: 201 }, + { x: 22, y: 415 }, + { x: 23, y: 242 }, + { x: 24, y: 231 }, + { x: 25, y: 269 }, + { x: 26, y: 272 }, + { x: 27, y: 325 }, + { x: 28, y: 305 }, + { x: 29, y: 380 }, + { x: 30, y: 276 } + ] ); + + public static readonly USER_YEAR = new DataSet( userYear, new Range( 0, 2.1 ), new Range( 0, 20 ), + [ + { x: 0, y: 0.002626964881 }, + { x: 1, y: 0.004283257864 }, + { x: 2, y: 0.006851845762 }, + { x: 3, y: 0.0098873072 }, + { x: 4, y: 0.02020656962 }, + { x: 5, y: 0.03889403814 }, + { x: 6, y: 0.07240693774 }, + { x: 7, y: 0.1160626502 }, + { x: 8, y: 0.1809450878 }, + { x: 9, y: 0.2722220605 }, + { x: 10, y: 0.38892392 }, + { x: 11, y: 0.489874705 }, + { x: 12, y: 0.6481611355 }, + { x: 13, y: 0.7590524542 }, + { x: 14, y: 0.8851731627 }, + { x: 15, y: 1.008553923 }, + { x: 16, y: 1.135996928 }, + { x: 17, y: 1.354889581 }, + { x: 18, y: 1.565562367 }, + { x: 19, y: 1.768348783 }, + { x: 20, y: 2.020788881 } + ] ); + + public static readonly GASOLINE_YEAR = new DataSet( gasolineYear, new Range( 0, 4 ), new Range( 0, 40 ), + [ + { x: 0, y: 0.73 }, + { x: 1, y: 1.01 }, + { x: 2, y: 1.25 }, + { x: 3, y: 1.21 }, + { x: 4, y: 1.10 }, + { x: 5, y: 1.12 }, + { x: 6, y: 1.08 }, + { x: 7, y: 0.83 }, + { x: 8, y: 0.85 }, + { x: 9, y: 0.85 }, + { x: 10, y: 0.89 }, + { x: 11, y: 1.09 }, + { x: 12, y: 1.02 }, + { x: 13, y: 0.96 }, + { x: 14, y: 0.94 }, + { x: 15, y: 0.92 }, + { x: 16, y: 0.89 }, + { x: 17, y: 1.02 }, + { x: 18, y: 1.01 }, + { x: 19, y: 0.88 }, + { x: 20, y: 0.90 }, + { x: 21, y: 1.36 }, + { x: 22, y: 1.31 }, + { x: 23, y: 1.16 }, + { x: 24, y: 1.40 }, + { x: 25, y: 1.65 }, + { x: 26, y: 2.22 }, + { x: 27, y: 2.50 }, + { x: 28, y: 2.68 }, + { x: 29, y: 3.75 }, + { x: 30, y: 2.51 }, + { x: 31, y: 2.93 }, + { x: 32, y: 3.71 }, + { x: 33, y: 3.75 }, + { x: 34, y: 3.72 }, + { x: 35, y: 3.80 } + ] ); + + public static readonly LIFE_TV = new DataSet( lifeTV, new Range( 0, 80 ), new Range( 0, 600 ), + [ + { x: 4, y: 70.5 }, + { x: 315, y: 53.5 }, + { x: 4, y: 65 }, + { x: 1.7, y: 76.5 }, + { x: 8, y: 70 }, + { x: 5.6, y: 71 }, + { x: 15, y: 60.5 }, + { x: 503, y: 51.5 }, + { x: 2.6, y: 78 }, + { x: 2.6, y: 76 }, + { x: 44, y: 57.5 }, + { x: 24, y: 61 }, + { x: 23, y: 64.5 }, + { x: 3.8, y: 78.5 }, + { x: 1.8, y: 79 }, + { x: 96, y: 61 }, + { x: 90, y: 70 }, + { x: 4.9, y: 70 }, + { x: 6.6, y: 72 }, + { x: 21, y: 64.5 }, + { x: 592, y: 54.5 }, + { x: 73, y: 56.5 }, + { x: 14, y: 64.5 }, + { x: 8.8, y: 64.5 }, + { x: 3.9, y: 73 }, + { x: 6, y: 72 }, + { x: 3.2, y: 69 }, + { x: 11, y: 64 }, + { x: 2.6, y: 78.5 }, + { x: 23, y: 53 }, + { x: 3.2, y: 75 }, + { x: 11, y: 68.5 }, + { x: 5, y: 70 }, + { x: 3, y: 70.5 }, + { x: 3, y: 76 }, + { x: 1.3, y: 75.5 }, + { x: 5.6, y: 74.5 }, + { x: 29, y: 65 } + ] ); + + public static readonly SPEED_DISTANCE = new DataSet( speedDistance, new Range( 0, 50 ), new Range( 0, 30 ), + [ + { x: 0.38709893, y: 47.8725 }, + { x: 0.72333199, y: 35.0214 }, + { x: 1.00000011, y: 29.7859 }, + { x: 1.52366231, y: 24.1309 }, + { x: 5.20336301, y: 13.0697 }, + { x: 9.53707032, y: 9.6724 }, + { x: 19.19126393, y: 6.8352 }, + { x: 30.06896348, y: 5.4778 } + ] ); + + public static readonly TEMPERATURE_FAHRENHEIT_CHIRP = new DataSet( temperatureFahrenheitChirp, new Range( 0, 100 ), new Range( 0, 20 ), + [ + { x: 20, y: 89 }, + { x: 16, y: 72 }, + { x: 20, y: 93 }, + { x: 18, y: 84 }, + { x: 17, y: 81 }, + { x: 16, y: 75 }, + { x: 15, y: 70 }, + { x: 17, y: 82 }, + { x: 15, y: 69 }, + { x: 16, y: 83 }, + { x: 15, y: 80 }, + { x: 17, y: 83 }, + { x: 16, y: 81 }, + { x: 17, y: 84 }, + { x: 14, y: 76 } + ] ); // celsius -DataSet.TEMPERATURE_CELSIUS_LONGITUDE = createDataSet( temperatureCelsiusLongitude, new Range( -20, 20 ), new Range( 0, 140 ), - [ - { x: 88.5, y: 6.6 }, - { x: 86.8, y: 3.3 }, - { x: 112.5, y: 1.7 }, - { x: 92.8, y: -0.6 }, - { x: 118.7, y: 8.3 }, - { x: 123, y: 5.5 }, - { x: 105.3, y: -9.4 }, - { x: 73.4, y: -5.6 }, - { x: 76.3, y: -3.3 }, - { x: 77.5, y: -1.1 }, - { x: 82.3, y: 7.2 }, - { x: 82, y: 18.3 }, - { x: 80.7, y: 14.4 }, - { x: 85, y: 2.8 }, - { x: 117.1, y: -5.6 }, - { x: 88, y: -7.2 }, - { x: 86.9, y: -6.1 }, - { x: 93.6, y: -11.7 }, - { x: 97.6, y: -5.6 }, - { x: 86.5, y: -2.8 }, - { x: 90.2, y: 7.2 }, - { x: 70.5, y: -11.1 }, - { x: 77.3, y: -3.9 }, - { x: 71.4, y: -5 }, - { x: 83.9, y: -6.1 }, - { x: 93.9, y: -16.7 }, - { x: 90.5, y: -4.4 }, - { x: 112.4, y: -13.3 }, - { x: 96.1, y: -10.6 }, - { x: 71.9, y: -11.7 }, - { x: 75.3, y: -2.8 }, - { x: 106.7, y: -4.4 }, - { x: 73.7, y: -10 }, - { x: 74.6, y: -2.8 }, - { x: 81.5, y: 1.1 }, - { x: 78.9, y: -0.6 }, - { x: 101, y: -17.8 }, - { x: 85, y: -3.3 }, - { x: 82.5, y: -6.1 }, - { x: 97.5, y: -2.2 }, - { x: 123.2, y: 0.6 }, - { x: 77.8, y: -4.4 }, - { x: 75.5, y: -4.4 }, - { x: 80.8, y: 3.3 }, - { x: 87.6, y: -0.6 }, - { x: 101.9, y: -4.4 }, - { x: 95.5, y: 9.4 }, - { x: 95.9, y: 6.6 }, - { x: 112.3, y: -7.8 }, - { x: 73.9, y: -13.9 }, - { x: 76.6, y: 0 }, - { x: 122.5, y: 0.6 }, - { x: 117.9, y: -7.2 }, - { x: 90.2, y: -12.8 }, - { x: 88.1, y: -10.6 }, - { x: 104.9, y: -10 } - ] ); - -DataSet.TEMPERATURE_FAHRENHEIT_LATITUDE = createDataSet( temperatureFahrenheitLatitude, new Range( 0, 80 ), new Range( 0, 60 ), - [ - { x: 31.2, y: 44 }, - { x: 32.9, y: 38 }, - { x: 33.6, y: 35 }, - { x: 35.4, y: 31 }, - { x: 34.3, y: 47 }, - { x: 38.4, y: 42 }, - { x: 40.7, y: 15 }, - { x: 41.7, y: 22 }, - { x: 40.5, y: 26 }, - { x: 39.7, y: 30 }, - { x: 31, y: 45 }, - { x: 25, y: 65 }, - { x: 26.3, y: 58 }, - { x: 33.9, y: 37 }, - { x: 43.7, y: 22 }, - { x: 42.3, y: 19 }, - { x: 39.8, y: 21 }, - { x: 41.8, y: 11 }, - { x: 38.1, y: 22 }, - { x: 39, y: 27 }, - { x: 30.8, y: 45 }, - { x: 44.2, y: 12 }, - { x: 39.7, y: 25 }, - { x: 42.7, y: 23 }, - { x: 43.1, y: 21 }, - { x: 45.9, y: 2 }, - { x: 39.3, y: 24 }, - { x: 47.1, y: 8 }, - { x: 41.9, y: 13 }, - { x: 43.5, y: 11 }, - { x: 39.8, y: 27 }, - { x: 35.1, y: 24 }, - { x: 42.6, y: 14 }, - { x: 40.8, y: 27 }, - { x: 35.9, y: 34 }, - { x: 36.4, y: 31 }, - { x: 47.1, y: 0 }, - { x: 39.2, y: 26 }, - { x: 42.3, y: 21 }, - { x: 35.9, y: 28 }, - { x: 45.6, y: 33 }, - { x: 40.9, y: 24 }, - { x: 40.9, y: 24 }, - { x: 33.3, y: 38 }, - { x: 36.7, y: 31 }, - { x: 35.6, y: 24 }, - { x: 29.4, y: 49 }, - { x: 30.1, y: 44 }, - { x: 41.1, y: 18 }, - { x: 45, y: 7 }, - { x: 37, y: 32 }, - { x: 48.1, y: 33 }, - { x: 48.1, y: 19 }, - { x: 43.4, y: 9 }, - { x: 43.3, y: 13 }, - { x: 41.2, y: 14 } - ] ); -// in celsius -DataSet.TEMPERATURE_CELSIUS_LATITUDE = createDataSet( temperatureCelsiusLatitude, new Range( -20, 20 ), new Range( 0, 60 ), - [ - { x: 31.2, y: 6.6 }, - { x: 32.9, y: 3.3 }, - { x: 33.6, y: 1.7 }, - { x: 35.4, y: -0.6 }, - { x: 34.3, y: 8.3 }, - { x: 38.4, y: 5.5 }, - { x: 40.7, y: -9.4 }, - { x: 41.7, y: -5.6 }, - { x: 40.5, y: -3.3 }, - { x: 39.7, y: -1.1 }, - { x: 31, y: 7.2 }, - { x: 25, y: 18.3 }, - { x: 26.3, y: 14.4 }, - { x: 33.9, y: 2.8 }, - { x: 43.7, y: -5.6 }, - { x: 42.3, y: -7.2 }, - { x: 39.8, y: -6.1 }, - { x: 41.8, y: -11.7 }, - { x: 38.1, y: -5.6 }, - { x: 39, y: -2.8 }, - { x: 30.8, y: 7.2 }, - { x: 44.2, y: -11.1 }, - { x: 39.7, y: -3.9 }, - { x: 42.7, y: -5 }, - { x: 43.1, y: -6.1 }, - { x: 45.9, y: -16.7 }, - { x: 39.3, y: -4.4 }, - { x: 47.1, y: -13.3 }, - { x: 41.9, y: -10.6 }, - { x: 43.5, y: -11.7 }, - { x: 39.8, y: -2.8 }, - { x: 35.1, y: -4.4 }, - { x: 42.6, y: -10 }, - { x: 40.8, y: -2.8 }, - { x: 35.9, y: 1.1 }, - { x: 36.4, y: -0.6 }, - { x: 47.1, y: -17.8 }, - { x: 39.2, y: -3.3 }, - { x: 42.3, y: -6.1 }, - { x: 35.9, y: -2.2 }, - { x: 45.6, y: 0.6 }, - { x: 40.9, y: -4.4 }, - { x: 40.9, y: -4.4 }, - { x: 33.3, y: 3.3 }, - { x: 36.7, y: -0.6 }, - { x: 35.6, y: -4.4 }, - { x: 29.4, y: 9.4 }, - { x: 30.1, y: 6.6 }, - { x: 41.1, y: -7.8 }, - { x: 45, y: -13.9 }, - { x: 37, y: 0 }, - { x: 48.1, y: 0.6 }, - { x: 48.1, y: -7.2 }, - { x: 43.4, y: -12.8 }, - { x: 43.3, y: -10.6 }, - { x: 41.2, y: -10 } - ] ); - -DataSet.SPENDING_SALARY = createDataSet( spendingSalary, new Range( 0, 10 ), new Range( 0, 50 ), - [ - { x: 19.583, y: 3.346 }, - { x: 20.263, y: 3.114 }, - { x: 20.325, y: 3.554 }, - { x: 26.800, y: 4.642 }, - { x: 29.470, y: 4.669 }, - { x: 26.610, y: 4.888 }, - { x: 30.678, y: 5.710 }, - { x: 27.170, y: 5.536 }, - { x: 25.853, y: 4.168 }, - { x: 24.500, y: 3.547 }, - { x: 24.274, y: 3.159 }, - { x: 27.170, y: 3.621 }, - { x: 30.168, y: 3.782 }, - { x: 26.525, y: 4.247 }, - { x: 27.360, y: 3.982 }, - { x: 21.690, y: 3.568 }, - { x: 21.974, y: 3.155 }, - { x: 20.816, y: 3.059 }, - { x: 18.095, y: 2.967 }, - { x: 20.939, y: 3.285 }, - { x: 22.644, y: 3.914 }, - { x: 24.624, y: 4.517 }, - { x: 27.186, y: 4.349 }, - { x: 33.990, y: 5.020 }, - { x: 23.382, y: 3.594 }, - { x: 20.627, y: 2.821 }, - { x: 22.795, y: 3.366 }, - { x: 21.570, y: 2.920 }, - { x: 22.080, y: 2.980 }, - { x: 22.250, y: 3.731 }, - { x: 20.940, y: 2.853 }, - { x: 21.800, y: 2.533 }, - { x: 22.934, y: 2.729 }, - { x: 18.443, y: 2.305 }, - { x: 19.538, y: 2.642 }, - { x: 20.460, y: 3.124 }, - { x: 21.419, y: 2.752 }, - { x: 25.160, y: 3.429 }, - { x: 22.482, y: 3.947 }, - { x: 20.969, y: 2.509 }, - { x: 27.224, y: 5.440 }, - { x: 25.892, y: 4.042 }, - { x: 22.644, y: 3.402 }, - { x: 24.640, y: 2.829 }, - { x: 22.341, y: 2.297 }, - { x: 25.610, y: 2.932 }, - { x: 26.015, y: 3.705 }, - { x: 25.788, y: 4.123 }, - { x: 29.132, y: 3.608 }, - { x: 41.480, y: 8.349 }, - { x: 25.845, y: 3.766 } - ] ); - -DataSet.WAGE_YEAR = createDataSet( wageYear, new Range( 0, 8 ), new Range( 0, 60 ), - [ - { x: 0, y: 2.94 }, - { x: 1, y: 2.72 }, - { x: 2, y: 2.75 }, - { x: 3, y: 5.1 }, - { x: 4, y: 4.72 }, - { x: 5, y: 4.64 }, - { x: 6, y: 4.6 }, - { x: 7, y: 4.57 }, - { x: 8, y: 4.58 }, - { x: 9, y: 5.77 }, - { x: 10, y: 5.82 }, - { x: 11, y: 5.66 }, - { x: 12, y: 5.62 }, - { x: 13, y: 5.53 }, - { x: 14, y: 5.75 }, - { x: 15, y: 6.23 }, - { x: 16, y: 6.31 }, - { x: 17, y: 6.6 }, - { x: 18, y: 6.5 }, - { x: 19, y: 6.31 }, - { x: 20, y: 6.81 }, - { x: 21, y: 7.44 }, - { x: 22, y: 7.22 }, - { x: 23, y: 6.89 }, - { x: 24, y: 6.6 }, - { x: 25, y: 6.4 }, - { x: 26, y: 6.02 }, - { x: 27, y: 6.41 }, - { x: 28, y: 6.64 }, - { x: 29, y: 6.88 }, - { x: 30, y: 6.47 }, - { x: 31, y: 7.15 }, - { x: 32, y: 7.15 }, - { x: 33, y: 6.88 }, - { x: 34, y: 6.8 }, - { x: 35, y: 6.42 }, - { x: 36, y: 6.16 }, - { x: 37, y: 5.93 }, - { x: 38, y: 5.73 }, - { x: 39, y: 5.63 }, - { x: 40, y: 5.45 }, - { x: 41, y: 5.25 }, - { x: 42, y: 5.04 }, - { x: 43, y: 5.29 }, - { x: 44, y: 5.72 }, - { x: 45, y: 5.73 }, - { x: 46, y: 5.59 }, - { x: 47, y: 5.48 }, - { x: 48, y: 5.35 }, - { x: 49, y: 5.37 }, - { x: 50, y: 5.86 }, - { x: 51, y: 6.09 }, - { x: 52, y: 5.97 }, - { x: 53, y: 5.78 }, - { x: 54, y: 5.62 }, - { x: 55, y: 5.53 }, - { x: 56, y: 5.41 }, - { x: 57, y: 5.27 }, - { x: 58, y: 5.15 } - ] ); - -DataSet.MORTALITY_YEAR = createDataSet( mortalityYear, new Range( 0, 500 ), new Range( 0, 40 ), - [ - { x: 1, y: 29 }, - { x: 2, y: 62 }, - { x: 3, y: 114 }, - { x: 4, y: 84 }, - { x: 5, y: 77 }, - { x: 6, y: 63 }, - { x: 7, y: 116 }, - { x: 8, y: 114 }, - { x: 9, y: 81 }, - { x: 10, y: 128 }, - { x: 11, y: 119 }, - { x: 12, y: 122 }, - { x: 13, y: 114 }, - { x: 14, y: 133 }, - { x: 15, y: 168 }, - { x: 16, y: 206 }, - { x: 17, y: 174 }, - { x: 18, y: 163 }, - { x: 19, y: 145 }, - { x: 20, y: 193 }, - { x: 21, y: 201 }, - { x: 22, y: 415 }, - { x: 23, y: 242 }, - { x: 24, y: 231 }, - { x: 25, y: 269 }, - { x: 26, y: 272 }, - { x: 27, y: 325 }, - { x: 28, y: 305 }, - { x: 29, y: 380 }, - { x: 30, y: 276 } - ] ); - -DataSet.USER_YEAR = createDataSet( userYear, new Range( 0, 2.1 ), new Range( 0, 20 ), - [ - { x: 0, y: 0.002626964881 }, - { x: 1, y: 0.004283257864 }, - { x: 2, y: 0.006851845762 }, - { x: 3, y: 0.0098873072 }, - { x: 4, y: 0.02020656962 }, - { x: 5, y: 0.03889403814 }, - { x: 6, y: 0.07240693774 }, - { x: 7, y: 0.1160626502 }, - { x: 8, y: 0.1809450878 }, - { x: 9, y: 0.2722220605 }, - { x: 10, y: 0.38892392 }, - { x: 11, y: 0.489874705 }, - { x: 12, y: 0.6481611355 }, - { x: 13, y: 0.7590524542 }, - { x: 14, y: 0.8851731627 }, - { x: 15, y: 1.008553923 }, - { x: 16, y: 1.135996928 }, - { x: 17, y: 1.354889581 }, - { x: 18, y: 1.565562367 }, - { x: 19, y: 1.768348783 }, - { x: 20, y: 2.020788881 } - ] ); - -DataSet.GASOLINE_YEAR = createDataSet( gasolineYear, new Range( 0, 4 ), new Range( 0, 40 ), - [ - { x: 0, y: 0.73 }, - { x: 1, y: 1.01 }, - { x: 2, y: 1.25 }, - { x: 3, y: 1.21 }, - { x: 4, y: 1.10 }, - { x: 5, y: 1.12 }, - { x: 6, y: 1.08 }, - { x: 7, y: 0.83 }, - { x: 8, y: 0.85 }, - { x: 9, y: 0.85 }, - { x: 10, y: 0.89 }, - { x: 11, y: 1.09 }, - { x: 12, y: 1.02 }, - { x: 13, y: 0.96 }, - { x: 14, y: 0.94 }, - { x: 15, y: 0.92 }, - { x: 16, y: 0.89 }, - { x: 17, y: 1.02 }, - { x: 18, y: 1.01 }, - { x: 19, y: 0.88 }, - { x: 20, y: 0.90 }, - { x: 21, y: 1.36 }, - { x: 22, y: 1.31 }, - { x: 23, y: 1.16 }, - { x: 24, y: 1.40 }, - { x: 25, y: 1.65 }, - { x: 26, y: 2.22 }, - { x: 27, y: 2.50 }, - { x: 28, y: 2.68 }, - { x: 29, y: 3.75 }, - { x: 30, y: 2.51 }, - { x: 31, y: 2.93 }, - { x: 32, y: 3.71 }, - { x: 33, y: 3.75 }, - { x: 34, y: 3.72 }, - { x: 35, y: 3.80 } - ] ); - -DataSet.LIFE_TV = createDataSet( lifeTV, new Range( 0, 80 ), new Range( 0, 600 ), - [ - { x: 4, y: 70.5 }, - { x: 315, y: 53.5 }, - { x: 4, y: 65 }, - { x: 1.7, y: 76.5 }, - { x: 8, y: 70 }, - { x: 5.6, y: 71 }, - { x: 15, y: 60.5 }, - { x: 503, y: 51.5 }, - { x: 2.6, y: 78 }, - { x: 2.6, y: 76 }, - { x: 44, y: 57.5 }, - { x: 24, y: 61 }, - { x: 23, y: 64.5 }, - { x: 3.8, y: 78.5 }, - { x: 1.8, y: 79 }, - { x: 96, y: 61 }, - { x: 90, y: 70 }, - { x: 4.9, y: 70 }, - { x: 6.6, y: 72 }, - { x: 21, y: 64.5 }, - { x: 592, y: 54.5 }, - { x: 73, y: 56.5 }, - { x: 14, y: 64.5 }, - { x: 8.8, y: 64.5 }, - { x: 3.9, y: 73 }, - { x: 6, y: 72 }, - { x: 3.2, y: 69 }, - { x: 11, y: 64 }, - { x: 2.6, y: 78.5 }, - { x: 23, y: 53 }, - { x: 3.2, y: 75 }, - { x: 11, y: 68.5 }, - { x: 5, y: 70 }, - { x: 3, y: 70.5 }, - { x: 3, y: 76 }, - { x: 1.3, y: 75.5 }, - { x: 5.6, y: 74.5 }, - { x: 29, y: 65 } - ] ); - -DataSet.SPEED_DISTANCE = createDataSet( speedDistance, new Range( 0, 50 ), new Range( 0, 30 ), - [ - { x: 0.38709893, y: 47.8725 }, - { x: 0.72333199, y: 35.0214 }, - { x: 1.00000011, y: 29.7859 }, - { x: 1.52366231, y: 24.1309 }, - { x: 5.20336301, y: 13.0697 }, - { x: 9.53707032, y: 9.6724 }, - { x: 19.19126393, y: 6.8352 }, - { x: 30.06896348, y: 5.4778 } - ] ); - -DataSet.TEMPERATURE_FAHRENHEIT_CHIRP = createDataSet( temperatureFahrenheitChirp, new Range( 0, 100 ), new Range( 0, 20 ), - [ - { x: 20, y: 89 }, - { x: 16, y: 72 }, - { x: 20, y: 93 }, - { x: 18, y: 84 }, - { x: 17, y: 81 }, - { x: 16, y: 75 }, - { x: 15, y: 70 }, - { x: 17, y: 82 }, - { x: 15, y: 69 }, - { x: 16, y: 83 }, - { x: 15, y: 80 }, - { x: 17, y: 83 }, - { x: 16, y: 81 }, - { x: 17, y: 84 }, - { x: 14, y: 76 } - ] ); + public static readonly TEMPERATURE_CELSIUS_CHIRP = new DataSet( temperatureCelsiusChirp, new Range( 0, 40 ), new Range( 0, 20 ), + [ + { x: 20, y: 31.6 }, + { x: 16, y: 22.2 }, + { x: 20, y: 33.8 }, + { x: 18, y: 28.8 }, + { x: 17, y: 27.2 }, + { x: 16, y: 23.8 }, + { x: 15, y: 21.1 }, + { x: 17, y: 27.7 }, + { x: 15, y: 20.5 }, + { x: 16, y: 28.3 }, + { x: 15, y: 26.6 }, + { x: 17, y: 28.3 }, + { x: 16, y: 27.2 }, + { x: 17, y: 28.8 }, + { x: 14, y: 24.4 } + ] ); + + public static readonly HEIGHT_SHOE = new DataSet( heightShoe, new Range( 0, 85 ), new Range( 0, 16 ), + [ + { x: 5.5, y: 60 }, + { x: 6, y: 60 }, + { x: 7, y: 60 }, + { x: 6, y: 64 }, + { x: 7, y: 64 }, + { x: 7.5, y: 64 }, + { x: 8, y: 64 }, + { x: 8, y: 64 }, + { x: 10, y: 64 }, + { x: 8, y: 64 }, + { x: 8, y: 67 }, + { x: 9, y: 65 }, + { x: 9.5, y: 65 }, + { x: 12, y: 65 }, + { x: 9, y: 66 }, + { x: 9.5, y: 66 }, + { x: 9.5, y: 66 }, + { x: 10.5, y: 66 }, + { x: 11, y: 66 }, + { x: 12, y: 66 }, + { x: 9, y: 68 }, + { x: 9.5, y: 68 }, + { x: 10, y: 68 }, + { x: 10, y: 68 }, + { x: 10.5, y: 68 }, + { x: 9.5, y: 69 }, + { x: 10.5, y: 69 }, + { x: 11, y: 69 }, + { x: 11, y: 69 }, + { x: 12, y: 69 }, + { x: 12, y: 69 }, + { x: 9, y: 70 }, + { x: 9.5, y: 70 }, + { x: 9.5, y: 70 }, + { x: 10.5, y: 70 }, + { x: 11, y: 70 }, + { x: 11, y: 70 }, + { x: 11, y: 70 }, + { x: 11, y: 70 }, + { x: 11.5, y: 70 }, + { x: 12, y: 70 }, + { x: 12, y: 70 }, + { x: 10.5, y: 70 }, + { x: 11, y: 70 }, + { x: 12, y: 70 }, + { x: 10.5, y: 71 }, + { x: 10.5, y: 71 }, + { x: 10.5, y: 71 }, + { x: 11, y: 71 }, + { x: 11, y: 71 }, + { x: 11, y: 71 }, + { x: 11, y: 71 }, + { x: 11, y: 71 }, + { x: 11, y: 71 }, + { x: 11.5, y: 71 }, + { x: 11.5, y: 71 }, + { x: 11.5, y: 71 }, + { x: 11.5, y: 71 }, + { x: 10.5, y: 72 }, + { x: 11.5, y: 72 }, + { x: 12, y: 72 }, + { x: 12, y: 72 }, + { x: 12, y: 72 }, + { x: 12, y: 72 }, + { x: 12.5, y: 72 }, + { x: 12.5, y: 72 }, + { x: 13, y: 72 }, + { x: 13, y: 72 }, + { x: 11, y: 72 }, + { x: 11.5, y: 72 }, + { x: 11.5, y: 72 }, + { x: 12, y: 72 }, + { x: 10.5, y: 73 }, + { x: 10.5, y: 73 }, + { x: 10.5, y: 73 }, + { x: 10.5, y: 73 }, + { x: 11, y: 73 }, + { x: 12, y: 73 }, + { x: 13, y: 73 }, + { x: 13, y: 73 }, + { x: 12, y: 75 }, + { x: 12, y: 75 }, + { x: 15, y: 75 }, + { x: 13, y: 75 }, + { x: 13, y: 75 }, + { x: 12, y: 75 }, + { x: 15, y: 77 }, + { x: 15, y: 77 }, + { x: 13, y: 78 }, + { x: 13, y: 78 }, + { x: 14, y: 78 }, + { x: 15, y: 80 }, + { x: 15, y: 81 } + ] ); +} -// celsius -DataSet.TEMPERATURE_CELSIUS_CHIRP = createDataSet( temperatureCelsiusChirp, new Range( 0, 40 ), new Range( 0, 20 ), - [ - { x: 20, y: 31.6 }, - { x: 16, y: 22.2 }, - { x: 20, y: 33.8 }, - { x: 18, y: 28.8 }, - { x: 17, y: 27.2 }, - { x: 16, y: 23.8 }, - { x: 15, y: 21.1 }, - { x: 17, y: 27.7 }, - { x: 15, y: 20.5 }, - { x: 16, y: 28.3 }, - { x: 15, y: 26.6 }, - { x: 17, y: 28.3 }, - { x: 16, y: 27.2 }, - { x: 17, y: 28.8 }, - { x: 14, y: 24.4 } - ] ); - -DataSet.HEIGHT_SHOE = createDataSet( heightShoe, new Range( 0, 85 ), new Range( 0, 16 ), - [ - { x: 5.5, y: 60 }, - { x: 6, y: 60 }, - { x: 7, y: 60 }, - { x: 6, y: 64 }, - { x: 7, y: 64 }, - { x: 7.5, y: 64 }, - { x: 8, y: 64 }, - { x: 8, y: 64 }, - { x: 10, y: 64 }, - { x: 8, y: 64 }, - { x: 8, y: 67 }, - { x: 9, y: 65 }, - { x: 9.5, y: 65 }, - { x: 12, y: 65 }, - { x: 9, y: 66 }, - { x: 9.5, y: 66 }, - { x: 9.5, y: 66 }, - { x: 10.5, y: 66 }, - { x: 11, y: 66 }, - { x: 12, y: 66 }, - { x: 9, y: 68 }, - { x: 9.5, y: 68 }, - { x: 10, y: 68 }, - { x: 10, y: 68 }, - { x: 10.5, y: 68 }, - { x: 9.5, y: 69 }, - { x: 10.5, y: 69 }, - { x: 11, y: 69 }, - { x: 11, y: 69 }, - { x: 12, y: 69 }, - { x: 12, y: 69 }, - { x: 9, y: 70 }, - { x: 9.5, y: 70 }, - { x: 9.5, y: 70 }, - { x: 10.5, y: 70 }, - { x: 11, y: 70 }, - { x: 11, y: 70 }, - { x: 11, y: 70 }, - { x: 11, y: 70 }, - { x: 11.5, y: 70 }, - { x: 12, y: 70 }, - { x: 12, y: 70 }, - { x: 10.5, y: 70 }, - { x: 11, y: 70 }, - { x: 12, y: 70 }, - { x: 10.5, y: 71 }, - { x: 10.5, y: 71 }, - { x: 10.5, y: 71 }, - { x: 11, y: 71 }, - { x: 11, y: 71 }, - { x: 11, y: 71 }, - { x: 11, y: 71 }, - { x: 11, y: 71 }, - { x: 11, y: 71 }, - { x: 11.5, y: 71 }, - { x: 11.5, y: 71 }, - { x: 11.5, y: 71 }, - { x: 11.5, y: 71 }, - { x: 10.5, y: 72 }, - { x: 11.5, y: 72 }, - { x: 12, y: 72 }, - { x: 12, y: 72 }, - { x: 12, y: 72 }, - { x: 12, y: 72 }, - { x: 12.5, y: 72 }, - { x: 12.5, y: 72 }, - { x: 13, y: 72 }, - { x: 13, y: 72 }, - { x: 11, y: 72 }, - { x: 11.5, y: 72 }, - { x: 11.5, y: 72 }, - { x: 12, y: 72 }, - { x: 10.5, y: 73 }, - { x: 10.5, y: 73 }, - { x: 10.5, y: 73 }, - { x: 10.5, y: 73 }, - { x: 11, y: 73 }, - { x: 12, y: 73 }, - { x: 13, y: 73 }, - { x: 13, y: 73 }, - { x: 12, y: 75 }, - { x: 12, y: 75 }, - { x: 15, y: 75 }, - { x: 13, y: 75 }, - { x: 13, y: 75 }, - { x: 12, y: 75 }, - { x: 15, y: 77 }, - { x: 15, y: 77 }, - { x: 13, y: 78 }, - { x: 13, y: 78 }, - { x: 14, y: 78 }, - { x: 15, y: 80 }, - { x: 15, y: 81 } - ] ); - -leastSquaresRegression.register( 'DataSet', DataSet ); - -export default DataSet; \ No newline at end of file +leastSquaresRegression.register( 'DataSet', DataSet ); \ No newline at end of file diff --git a/js/least-squares-regression/model/Graph.ts b/js/least-squares-regression/model/Graph.ts index aa7b541..ec74433 100644 --- a/js/least-squares-regression/model/Graph.ts +++ b/js/least-squares-regression/model/Graph.ts @@ -10,72 +10,108 @@ */ import BooleanProperty from '../../../../axon/js/BooleanProperty.js'; -import createObservableArray from '../../../../axon/js/createObservableArray.js'; +import createObservableArray, { ObservableArray } from '../../../../axon/js/createObservableArray.js'; import DerivedProperty from '../../../../axon/js/DerivedProperty.js'; import NumberProperty 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 Vector2 from '../../../../dot/js/Vector2.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; +import DataPoint from './DataPoint.js'; import Residual from './Residual.js'; +/** + * Graph model for handling data points, residuals, and statistics related to lines of best fit. + */ class Graph { - /** - * @param {Range} xRange - * @param {Range} yRange - */ - constructor( xRange, yRange ) { - // @public {Property.} in radians, a proxy for the 'my line' slope. - this.angleProperty = new NumberProperty( 0 ); + // Public Properties controlling visibility and line parameters + public readonly angleProperty: NumberProperty; // in radians, a proxy for the 'my line' slope. + public readonly interceptProperty: NumberProperty; // in graph units + public readonly myLineVisibleProperty: BooleanProperty; // visibility of My Line on the graph and associated checkbox + public readonly bestFitLineVisibleProperty: BooleanProperty; // visibility of Best Fit Line on the graph and associated checkbox + public readonly myLineShowResidualsProperty: BooleanProperty; // visibility of Residuals of My Line (checkbox only) + public readonly myLineShowSquaredResidualsProperty: BooleanProperty; // visibility of Squared Residuals of My Line (checkbox only) + public readonly bestFitLineShowResidualsProperty: BooleanProperty; // visibility of Residuals of Best Fit Line (checkbox only) + public readonly bestFitLineShowSquaredResidualsProperty: BooleanProperty; // visibility of Squared Residuals of Best Fit Line (checkbox only) + + // DerivedProperties controlling visibility of residuals based on multiple factors + public readonly myLineResidualsVisibleProperty: TReadOnlyProperty; // property that controls the visibility of the Residuals on the graph for My Line + public readonly myLineSquaredResidualsVisibleProperty: TReadOnlyProperty; // property that controls the visibility of the Square Residuals on the graph for My Line + public readonly bestFitLineResidualsVisibleProperty: TReadOnlyProperty; // property that controls the visibility of the Square Residuals on the graph for Best Fit Line + public readonly bestFitLineSquaredResidualsVisibleProperty: TReadOnlyProperty; // property that controls the visibility of the Square Residuals on the graph for Best Fit Line + + // Bounds for the graph in model coordinates, it is a unit square. This remains the same for all DataSets + public readonly bounds: Bounds2; + + // Observable arrays for residuals + // Each entry is a Property. + public readonly myLineResiduals: ObservableArray>; + public readonly bestFitLineResiduals: ObservableArray>; + + // Array of dataPoints currently on the graph + public dataPointsOnGraph: DataPoint[]; + + // TODO: https://github.com/phetsims/least-squares-regression/issues/94 when are these assigned? + // Graph domain ranges + public xRange!: Range; + public yRange!: Range; + + // TODO: https://github.com/phetsims/least-squares-regression/issues/94 when are these assigned? + // Factors for slope and intercept conversions + public slopeFactor!: number; + public interceptFactor!: number; + public interceptOffset!: number; + + // TODO: https://github.com/phetsims/least-squares-regression/issues/94 when are these assigned? + // Statistical fields + private averageOfSumOfSquaresXX!: number; + private averageOfSumOfSquaresXY!: number; + private averageOfSumOfSquaresYY!: number; + private averageOfSumOfX!: number; + private averageOfSumOfY!: number; + + public constructor( xRange: Range, yRange: Range ) { - // @public {Property.} in units of the graph bounds + this.angleProperty = new NumberProperty( 0 ); this.interceptProperty = new NumberProperty( 0 ); - - // @public {Property.} visibility of My Line on the graph and associated checkbox this.myLineVisibleProperty = new BooleanProperty( true ); - - // @public {Property.} visibility of Best Fit Line on the graph and associated checkbox this.bestFitLineVisibleProperty = new BooleanProperty( false ); - - // @public {Property.} visibility of Residuals of My Line (checkbox only) this.myLineShowResidualsProperty = new BooleanProperty( false ); - - // @public {Property.} visibility of Squared Residuals of My Line (checkbox only) this.myLineShowSquaredResidualsProperty = new BooleanProperty( false ); - - // @public {Property.} visibility of Residuals of Best Fit Line (checkbox only) this.bestFitLineShowResidualsProperty = new BooleanProperty( false ); - - // @public {Property.} visibility of Squared Residuals of Best Fit Line (checkbox only) this.bestFitLineShowSquaredResidualsProperty = new BooleanProperty( false ); - // @public {Property.} property that controls the visibility of the Residuals on the graph for My Line - this.myLineResidualsVisibleProperty = new DerivedProperty( [ this.myLineVisibleProperty, this.myLineShowResidualsProperty ], - ( myLineVisible, myLineShowResiduals ) => myLineVisible && myLineShowResiduals ); + this.myLineResidualsVisibleProperty = new DerivedProperty( + [ this.myLineVisibleProperty, this.myLineShowResidualsProperty ], + ( myLineVisible, myLineShowResiduals ) => myLineVisible && myLineShowResiduals + ); - // @public {Property.} property that controls the visibility of the Square Residuals on the graph for My Line - this.myLineSquaredResidualsVisibleProperty = new DerivedProperty( [ this.myLineVisibleProperty, this.myLineShowSquaredResidualsProperty ], - ( myLineVisible, myLineShowSquaredResiduals ) => myLineVisible && myLineShowSquaredResiduals ); + this.myLineSquaredResidualsVisibleProperty = new DerivedProperty( + [ this.myLineVisibleProperty, this.myLineShowSquaredResidualsProperty ], + ( myLineVisible, myLineShowSquaredResiduals ) => myLineVisible && myLineShowSquaredResiduals + ); - // @public {Property.} property that controls the visibility of the Square Residuals on the graph for Best Fit Line - this.bestFitLineResidualsVisibleProperty = new DerivedProperty( [ this.bestFitLineVisibleProperty, this.bestFitLineShowResidualsProperty ], - ( bestFitLineVisible, bestFitLineShowResiduals ) => bestFitLineVisible && bestFitLineShowResiduals ); + this.bestFitLineResidualsVisibleProperty = new DerivedProperty( + [ this.bestFitLineVisibleProperty, this.bestFitLineShowResidualsProperty ], + ( bestFitLineVisible, bestFitLineShowResiduals ) => bestFitLineVisible && bestFitLineShowResiduals + ); - // @public {Property.} property that controls the visibility of the Square Residuals on the graph for Best Fit Line - this.bestFitLineSquaredResidualsVisibleProperty = new DerivedProperty( [ this.bestFitLineVisibleProperty, this.bestFitLineShowSquaredResidualsProperty ], - ( bestFitLineVisible, bestFitLineShowSquaredResiduals ) => bestFitLineVisible && bestFitLineShowSquaredResiduals ); + this.bestFitLineSquaredResidualsVisibleProperty = new DerivedProperty( + [ this.bestFitLineVisibleProperty, this.bestFitLineShowSquaredResidualsProperty ], + ( bestFitLineVisible, bestFitLineShowSquaredResiduals ) => bestFitLineVisible && bestFitLineShowSquaredResiduals + ); - // Bounds for the graph in model coordinates, it is a unit square. This remains the same for all DataSets - // @public read-only this.bounds = new Bounds2( 0, 0, 1, 1 ); // observable arrays of the line and squared residuals (wrapped in a property) for MyLine and BestFitLine - this.myLineResiduals = createObservableArray(); // @public - this.bestFitLineResiduals = createObservableArray(); // @public + this.myLineResiduals = createObservableArray>(); + this.bestFitLineResiduals = createObservableArray>(); // array of the dataPoints that are overlapping the graph. - this.dataPointsOnGraph = []; // @public read-only + this.dataPointsOnGraph = []; // set the domain of the graphs (for future use by the equation Node and the graph Axes) this.setGraphDomain( xRange, yRange ); @@ -84,9 +120,8 @@ class Graph { /** * Reset the visibility of the lines and residuals as well as the angle and intercept. * Empty out the two residual arrays and the dataPoints on Graph array - * @public */ - reset() { + public reset(): void { this.angleProperty.reset(); this.interceptProperty.reset(); this.myLineVisibleProperty.reset(); @@ -101,75 +136,62 @@ class Graph { } /** - * Empty out the two residual arrays and the dataPoints on Graph array - * @public + * Empty out the two residual arrays and the dataPointsOnGraph array. */ - resetOnChangeOfDataSet() { + public resetOnChangeOfDataSet(): void { this.dataPointsOnGraph = []; this.myLineResiduals.clear(); this.bestFitLineResiduals.clear(); } /** - * Sets the horizontal and vertical graph domain of dataSets and the corresponding multiplicative factor for the slope and intercept - * @public - * @param {Range} xRange - * @param {Range} yRange + * Sets the horizontal and vertical graph domain of dataSets and the corresponding multiplicative factor + * for the slope and intercept. */ - setGraphDomain( xRange, yRange ) { - this.xRange = xRange; // @public - this.yRange = yRange; // @public - this.slopeFactor = ( yRange.max - yRange.min ) / ( xRange.max - xRange.min ) / ( this.bounds.height / this.bounds.width );// @public - this.interceptFactor = ( yRange.max - yRange.min ) / this.bounds.height; // @public - this.interceptOffset = ( yRange.min ); // @public + public setGraphDomain( xRange: Range, yRange: Range ): void { + this.xRange = xRange; + this.yRange = yRange; + this.slopeFactor = ( yRange.max - yRange.min ) / ( xRange.max - xRange.min ) / ( this.bounds.height / this.bounds.width ); + this.interceptFactor = ( yRange.max - yRange.min ) / this.bounds.height; + this.interceptOffset = ( yRange.min ); } /** - * Update the model Residuals for 'My Line' and 'Best Fit Line' - * @private + * Update the model Residuals for 'My Line' and 'Best Fit Line'. */ - update() { + private update(): void { this.updateMyLineResiduals(); this.updateBestFitLineResiduals(); } /** - * Convert the angle of a line (measured from the horizontal x axis) to a slope - * @public read-only - * @param {number} angle + * Convert the angle of a line (measured from the horizontal x axis) to a slope. */ - slope( angle ) { + public slope( angle: number ): number { return Math.tan( angle ) * this.bounds.height / this.bounds.width; } /** - * Add a 'My Line' model Residual to a dataPoint - * @private - * @param {DataPoint} dataPoint + * Add a 'My Line' model Residual to a dataPoint. */ - addMyLineResidual( dataPoint ) { + private addMyLineResidual( dataPoint: DataPoint ): void { const myLineResidual = new Residual( dataPoint, this.slope( this.angleProperty.value ), this.interceptProperty.value ); this.myLineResiduals.push( new Property( myLineResidual ) ); } /** - * Add a 'Best Fit Line' model Residual to a dataPoint - * @private - * @param {DataPoint} dataPoint + * Add a 'Best Fit Line' model Residual to a dataPoint. */ - addBestFitLineResidual( dataPoint ) { - + private addBestFitLineResidual( dataPoint: DataPoint ): void { const linearFitParameters = this.getLinearFit(); const bestFitLineResidual = new Residual( dataPoint, linearFitParameters.slope, linearFitParameters.intercept ); this.bestFitLineResiduals.push( new Property( bestFitLineResidual ) ); } /** - * Remove the 'My Line' model Residual attached to a dataPoint - * @private - * @param {DataPoint} dataPoint + * Remove the 'My Line' model Residual attached to a dataPoint. */ - removeMyLineResidual( dataPoint ) { + private removeMyLineResidual( dataPoint: DataPoint ): void { const myLineResidualsCopy = this.myLineResiduals.slice(); myLineResidualsCopy.forEach( myLineResidualProperty => { if ( myLineResidualProperty.value.dataPoint === dataPoint ) { @@ -179,11 +201,9 @@ class Graph { } /** - * Remove a 'Best Fit Line' model Residual attached to a dataPoint - * @private - * @param {DataPoint} dataPoint + * Remove a 'Best Fit Line' model Residual attached to a dataPoint. */ - removeBestFitLineResidual( dataPoint ) { + private removeBestFitLineResidual( dataPoint: DataPoint ): void { const bestFitLineResidualsCopy = this.bestFitLineResiduals.slice(); bestFitLineResidualsCopy.forEach( bestFitLineResidualProperty => { if ( bestFitLineResidualProperty.value.dataPoint === dataPoint ) { @@ -195,9 +215,8 @@ class Graph { /** * Update all 'My Line' model Residuals * (Necessary to update when the slope and the intercept of 'My Line' are modified) - * @public */ - updateMyLineResiduals() { + public updateMyLineResiduals(): void { this.myLineResiduals.forEach( residualProperty => { const dataPoint = residualProperty.value.dataPoint; residualProperty.value = new Residual( dataPoint, this.slope( this.angleProperty.value ), this.interceptProperty.value ); @@ -205,10 +224,9 @@ class Graph { } /** - * Update all 'My Best Fit Line' model Residuals - * @private + * Update all 'Best Fit Line' model Residuals. */ - updateBestFitLineResiduals() { + private updateBestFitLineResiduals(): void { if ( this.isLinearFitDefined() ) { const linearFitParameters = this.getLinearFit(); this.bestFitLineResiduals.forEach( residualProperty => { @@ -219,12 +237,9 @@ class Graph { } /** - * Add Data Points on Graph in bulk such that no update is triggered throughout the process. - * This is done for performance reason. - * @public (accessed by LeastSquareRegressionModel) - * @param {Array.} dataPoints + * Add Data Points on Graph in bulk, for performance reasons. */ - addDataPointsOnGraphAndResidualsInBulk( dataPoints ) { + public addDataPointsOnGraphAndResidualsInBulk( dataPoints: DataPoint[] ): void { // for performance reason one should add all the dataPoints on the graph // then we can calculate the best Fit Line (only once) // and then add all the Residuals. @@ -254,31 +269,23 @@ class Graph { /** * Function that returns true if the dataPoint is on the array. - * @private - * @param {DataPoint} dataPoint - * @returns {boolean} */ - isDataPointOnList( dataPoint ) { + public isDataPointOnList( dataPoint: DataPoint ): boolean { const index = this.dataPointsOnGraph.indexOf( dataPoint ); return ( index !== -1 ); } /** - * Function that determines if the Position of a Data Point is within the visual bounds of the graph - * @private - * @param {Vector2} position - * @returns {boolean} + * Function that determines if the Position of a Data Point is within the visual bounds of the graph. */ - isDataPointPositionOverlappingGraph( position ) { + public isDataPointPositionOverlappingGraph( position: Vector2 ): boolean { return this.bounds.containsPoint( position ); } /** - * Add the dataPoint top the dataPointsOnGraph Array and add 'My Line' and 'Best Fit Line' model Residuals - * @public (accessed by LeastSquareRegressionModel) - * @param {DataPoint} dataPoint + * Add the dataPoint to the dataPointsOnGraph Array and add 'My Line' and 'Best Fit Line' model Residuals. */ - addPointAndResiduals( dataPoint ) { + public addPointAndResiduals( dataPoint: DataPoint & { positionUpdateListener?: () => void } ): void { this.dataPointsOnGraph.push( dataPoint ); this.addMyLineResidual( dataPoint ); @@ -304,10 +311,8 @@ class Graph { /** * Remove a dataPoint and its associated residuals ('My Line' and 'Best Fit Line') - * @public (accessed by LeastSquareRegressionModel) - * @param {DataPoint} dataPoint */ - removePointAndResiduals( dataPoint ) { + public removePointAndResiduals( dataPoint: DataPoint ): void { assert && assert( this.isDataPointOnList( dataPoint ), ' need the point to be on the list to remove it' ); const index = this.dataPointsOnGraph.indexOf( dataPoint ); this.dataPointsOnGraph.splice( index, 1 ); @@ -321,28 +326,24 @@ class Graph { else { this.removeBestFitLineResidual( dataPoint ); } + this.update(); - if ( dataPoint.positionProperty.hasListener( dataPoint.positionUpdateListener ) ) { + if ( dataPoint.positionUpdateListener && dataPoint.positionProperty.hasListener( dataPoint.positionUpdateListener ) ) { dataPoint.positionProperty.unlink( dataPoint.positionUpdateListener ); } } /** - * Function that removes all the best Fit Line Residuals - * @private + * Function that removes all the best Fit Line Residuals. */ - removeBestFitLineResiduals() { + private removeBestFitLineResiduals(): void { this.bestFitLineResiduals.clear(); } /** * Function that returns the sum of squared residuals of all the dataPoints on the list (compared with a line with a slope and intercept) - * @private - * @param {number} slope - * @param {number} intercept - * @returns {number} sumOfSquareResiduals */ - sumOfSquaredResiduals( slope, intercept ) { + private sumOfSquaredResiduals( slope: number, intercept: number ): number { let sumOfSquareResiduals = 0; this.dataPointsOnGraph.forEach( dataPoint => { const yResidual = ( slope * dataPoint.positionProperty.value.x + intercept ) - dataPoint.positionProperty.value.y; @@ -354,10 +355,8 @@ class Graph { /** * Function that returns the sum of squared residuals of 'My Line' * The sum of squared residual is zero if there are less than one dataPoint on the graph. - * @public read-only - * @returns {number} sumOfSquareResiduals */ - getMyLineSumOfSquaredResiduals() { + public getMyLineSumOfSquaredResiduals(): number { if ( this.dataPointsOnGraph.length >= 1 ) { return this.sumOfSquaredResiduals( this.slope( this.angleProperty.value ), this.interceptProperty.value ); } @@ -369,10 +368,8 @@ class Graph { /** * Function that returns the sum of squared residuals of 'Best Fit Line' * The sum of squared residual is zero if there are less than two dataPoints on the graph - * @public read-only - * @returns {number} sumOfSquareResiduals */ - getBestFitLineSumOfSquaredResiduals() { + public getBestFitLineSumOfSquaredResiduals(): number { if ( this.isLinearFitDefined() ) { const linearFitParameters = this.getLinearFit(); return this.sumOfSquaredResiduals( linearFitParameters.slope, linearFitParameters.intercept ); @@ -384,29 +381,21 @@ class Graph { /** * Returns an array of two points that crosses the left and the right hand side of the graph bounds - * @public read-only - * @param {number} slope - * @param {number} intercept - * @returns {{point1: Vector2, point2: Vector2}} */ - getBoundaryPoints( slope, intercept ) { - + public getBoundaryPoints( slope: number, intercept: number ): { point1: Vector2; point2: Vector2 } { const yValueLeft = slope * this.bounds.minX + intercept; const yValueRight = slope * this.bounds.maxX + intercept; const boundaryPoints = { point1: new Vector2( this.bounds.minX, yValueLeft ), point2: new Vector2( this.bounds.maxX, yValueRight ) }; - return boundaryPoints; } /** * Function that updates statistical properties of the dataPoints on the graph. - * @private */ - getStatistics() { - + private getStatistics(): void { const dataPointArray = this.dataPointsOnGraph; assert && assert( dataPointArray !== null, 'dataPointsOnGraph must contain data' ); const arrayLength = dataPointArray.length; @@ -417,7 +406,7 @@ class Graph { const positionArrayX = _.map( dataPointArray, dataPoint => dataPoint.positionProperty.value.x ); const positionArrayY = _.map( dataPointArray, dataPoint => dataPoint.positionProperty.value.y ); - function add( memo, num ) { + function add( memo: number, num: number ): number { return memo + num; } @@ -435,11 +424,9 @@ class Graph { } /** - * Function that determines if a best fit line fit exists - * @public read-only - * @returns {boolean} + * Determine if a best fit line can be defined (at least two points and no vertical alignment). */ - isLinearFitDefined() { + public isLinearFitDefined(): boolean { let isDefined; // you can't have a linear fit with less than 2 data points if ( this.dataPointsOnGraph.length < 2 ) { @@ -463,10 +450,8 @@ class Graph { /** * Function that returns the 'best fit line' parameters, i.e. slope and intercept of the dataPoints on the graph. * It would be wise to check if isLinearFitDefined() is true before calling this function. - * @public read-only - * @returns {{slope: number, intercept: number}} */ - getLinearFit() { + public getLinearFit(): { slope: number; intercept: number } { this.getStatistics(); const slopeNumerator = this.averageOfSumOfSquaresXY - this.averageOfSumOfX * this.averageOfSumOfY; const slopeDenominator = this.averageOfSumOfSquaresXX - this.averageOfSumOfX * this.averageOfSumOfX; @@ -486,37 +471,34 @@ class Graph { * For two dataPoints and more, the Pearson coefficient ranges from -1 to 1. * Note that the Pearson Coefficient Correlation is an intrinsic property of a set of DataPoint * See http://en.wikipedia.org/wiki/Pearson_product-moment_correlation_coefficient - * @public read-only - * @returns {null||number} */ - getPearsonCoefficientCorrelation() { - + public getPearsonCoefficientCorrelation(): number | null { if ( !this.isLinearFitDefined() ) { return null; } else { - this.getStatistics(); + this.getStatistics(); let pearsonCoefficientCorrelationNumerator = this.averageOfSumOfSquaresXY - this.averageOfSumOfX * this.averageOfSumOfY; - if ( Math.abs( pearsonCoefficientCorrelationNumerator ) < 1E-10 ) { - pearsonCoefficientCorrelationNumerator = 0; - } + if ( Math.abs( pearsonCoefficientCorrelationNumerator ) < 1E-10 ) { + pearsonCoefficientCorrelationNumerator = 0; + } // for very small values, we can end up with a very small or negative number. In this case, return null so we // don't get a NaN for the coefficient. const number = ( this.averageOfSumOfSquaresXX - this.averageOfSumOfX * this.averageOfSumOfX ) * ( this.averageOfSumOfSquaresYY - this.averageOfSumOfY * this.averageOfSumOfY ); - if ( number < 1E-15 ) { - return null; - } - const pearsonCoefficientCorrelationDenominator = Math.sqrt( number ); + if ( number < 1E-15 ) { + return null; + } + const pearsonCoefficientCorrelationDenominator = Math.sqrt( number ); // make sure the denominator is not equal to zero, this happens if all the points are aligned vertically - if ( pearsonCoefficientCorrelationDenominator === 0 ) { + if ( pearsonCoefficientCorrelationDenominator === 0 ) { return null; // - } + } else { - const pearsonCoefficientCorrelation = pearsonCoefficientCorrelationNumerator / pearsonCoefficientCorrelationDenominator; - return pearsonCoefficientCorrelation; + const pearsonCoefficientCorrelation = pearsonCoefficientCorrelationNumerator / pearsonCoefficientCorrelationDenominator; + return pearsonCoefficientCorrelation; } } } diff --git a/js/least-squares-regression/model/LeastSquaresRegressionModel.ts b/js/least-squares-regression/model/LeastSquaresRegressionModel.ts index 0b61dce..9323e77 100644 --- a/js/least-squares-regression/model/LeastSquaresRegressionModel.ts +++ b/js/least-squares-regression/model/LeastSquaresRegressionModel.ts @@ -1,18 +1,19 @@ // Copyright 2014-2024, University of Colorado Boulder /** - * Contains all of the model logic for the screen LeastSquaresRegressionScreen. + * Contains all the model logic for the screen LeastSquaresRegressionScreen. * * @author Martin Veillette (Berea College) */ import BooleanProperty from '../../../../axon/js/BooleanProperty.js'; -import createObservableArray from '../../../../axon/js/createObservableArray.js'; +import createObservableArray, { ObservableArray } from '../../../../axon/js/createObservableArray.js'; import Emitter from '../../../../axon/js/Emitter.js'; import Property from '../../../../axon/js/Property.js'; import Dimension2 from '../../../../dot/js/Dimension2.js'; import Utils from '../../../../dot/js/Utils.js'; import Vector2 from '../../../../dot/js/Vector2.js'; +import IntentionalAny from '../../../../phet-core/js/types/IntentionalAny.js'; import Bucket from '../../../../phetcommon/js/model/Bucket.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import DataPoint from './DataPoint.js'; @@ -24,48 +25,60 @@ const BUCKET_SIZE = new Dimension2( 100, 55 ); const BUCKET_POSITION = new Vector2( 120, 480 ); class LeastSquaresRegressionModel { - constructor() { - // @public {Property.} controls the visibility of the graph grid - this.showGridProperty = new BooleanProperty( false ); + // Controls the visibility of the graph grid + public readonly showGridProperty: Property; - // @public {Property.} dataSet selected by the Combo Box: initially value set on Custom - this.selectedDataSetProperty = new Property( DataSet.CUSTOM ); + // DataSet selected by the Combo Box + public readonly selectedDataSetProperty: Property; + + // Sends an event when points are added in bulk + public readonly dataPointsAddedEmitter: Emitter; + + // Array of dataPoints (may be on or off the graph) + public readonly dataPoints: ObservableArray; + + // Various data sets that populate the Combo Box + public readonly dataSets: IntentionalAny[]; + + // Model of the graph containing all information regarding graph composition + public readonly graph: Graph; - // @public, sends an event when points are added in bulk + // Bucket model + public readonly bucket: Bucket; + + public constructor() { + this.showGridProperty = new BooleanProperty( false ); + this.selectedDataSetProperty = new Property( DataSet.CUSTOM ); this.dataPointsAddedEmitter = new Emitter(); - // Array of dataPoints in the model (may not be necessarily on the graph, could be user controlled outside the graph zone or animated) - this.dataPoints = createObservableArray(); // @public - - // The various data Sets that populates the Combo Box - // @public read-only - this.dataSets = []; - this.dataSets.push( DataSet.CUSTOM ); - this.dataSets.push( DataSet.HEIGHT_SHOE ); - this.dataSets.push( DataSet.SPENDING_SALARY ); - this.dataSets.push( DataSet.MORTALITY_YEAR ); - this.dataSets.push( DataSet.WAGE_YEAR ); - this.dataSets.push( DataSet.USER_YEAR ); - this.dataSets.push( DataSet.GASOLINE_YEAR ); - this.dataSets.push( DataSet.LIFE_TV ); - this.dataSets.push( DataSet.SPEED_DISTANCE ); - this.dataSets.push( DataSet.TEMPERATURE_FAHRENHEIT_CHIRP ); - this.dataSets.push( DataSet.TEMPERATURE_FAHRENHEIT_LONGITUDE ); - this.dataSets.push( DataSet.TEMPERATURE_FAHRENHEIT_LATITUDE ); - this.dataSets.push( DataSet.TEMPERATURE_CELSIUS_CHIRP ); - this.dataSets.push( DataSet.TEMPERATURE_CELSIUS_LONGITUDE ); - this.dataSets.push( DataSet.TEMPERATURE_CELSIUS_LATITUDE ); - - // Model of the graph that contains all information regarding the composition of the graph - // @public read-only + // Create an observable array for data points + this.dataPoints = createObservableArray(); + + // Populate dataSets + this.dataSets = [ + DataSet.CUSTOM, + DataSet.HEIGHT_SHOE, + DataSet.SPENDING_SALARY, + DataSet.MORTALITY_YEAR, + DataSet.WAGE_YEAR, + DataSet.USER_YEAR, + DataSet.GASOLINE_YEAR, + DataSet.LIFE_TV, + DataSet.SPEED_DISTANCE, + DataSet.TEMPERATURE_FAHRENHEIT_CHIRP, + DataSet.TEMPERATURE_FAHRENHEIT_LONGITUDE, + DataSet.TEMPERATURE_FAHRENHEIT_LATITUDE, + DataSet.TEMPERATURE_CELSIUS_CHIRP, + DataSet.TEMPERATURE_CELSIUS_LONGITUDE, + DataSet.TEMPERATURE_CELSIUS_LATITUDE + ]; + this.graph = new Graph( this.selectedDataSetProperty.value.xRange, this.selectedDataSetProperty.value.yRange ); - // Bucket model to be filled with dataPoint - // @public read-only this.bucket = new Bucket( { position: BUCKET_POSITION, baseColor: '#000080', @@ -73,15 +86,13 @@ class LeastSquaresRegressionModel { invertY: true } ); - // array for the CUSTOM dataPoints - let savedCustomDataPoints = []; // {Array.} + let savedCustomDataPoints: DataPoint[] = []; // What to do when the selected Data Set changes. no need to unlink, present for the lifetime of the sim this.selectedDataSetProperty.link( ( selectedDataSet, oldSelectedDataSet ) => { // saved the position data of CUSTOM if we are going from CUSTOM to another dataSet if ( oldSelectedDataSet && oldSelectedDataSet === DataSet.CUSTOM ) { - // add the current dataPoints on graph to savedCustomDataPoints savedCustomDataPoints = this.graph.dataPointsOnGraph; } @@ -102,6 +113,7 @@ class LeastSquaresRegressionModel { // Populate the dataPoints array if ( selectedDataSet === DataSet.CUSTOM ) { + // use the savedCustomDataPoints to populate the dataPoints array savedCustomDataPoints.forEach( dataPoint => { this.dataPoints.push( dataPoint ); @@ -112,15 +124,27 @@ class LeastSquaresRegressionModel { this.dataPoints.forEach( dataPoint => { this.addDataPointControlledListener( dataPoint ); } ); - } else { - // Populate the dataPoints array with the new SelectedDataSet + // Populate from selectedDataSet dataXY selectedDataSet.dataXY.forEach( position => { + // For your information, only one modelViewTransform is used throughout the simulation, the bounds of the model are set by the graph bounds // Rescale all the {X,Y} value to the normalized graph bounds - const XNormalized = Utils.linear( selectedDataSet.xRange.min, selectedDataSet.xRange.max, this.graph.bounds.minX, this.graph.bounds.maxX, position.x ); - const YNormalized = Utils.linear( selectedDataSet.yRange.min, selectedDataSet.yRange.max, this.graph.bounds.minY, this.graph.bounds.maxY, position.y ); + const XNormalized = Utils.linear( + selectedDataSet.xRange.min, + selectedDataSet.xRange.max, + this.graph.bounds.minX, + this.graph.bounds.maxX, + position.x + ); + const YNormalized = Utils.linear( + selectedDataSet.yRange.min, + selectedDataSet.yRange.max, + this.graph.bounds.minY, + this.graph.bounds.maxY, + position.y + ); const positionVector = new Vector2( XNormalized, YNormalized ); this.dataPoints.push( new DataPoint( positionVector ) ); } ); @@ -130,15 +154,13 @@ class LeastSquaresRegressionModel { } // Since we added the dataPoints in Bulk, let's send a trigger to the view this.dataPointsAddedEmitter.emit(); - } ); } /** * Resets values to their original state - * @public */ - reset() { + public reset(): void { this.showGridProperty.reset(); this.selectedDataSetProperty.reset(); this.dispose(); @@ -149,25 +171,22 @@ class LeastSquaresRegressionModel { /** * Unlink listeners to dataPoint. Listeners might have been removed when the data point was removed from the graph, * so check that they are still attached first. - * - * @private */ - dispose() { + private dispose(): void { this.dataPoints.forEach( dataPoint => { - if ( dataPoint.positionProperty.hasListener( dataPoint.positionUpdateListener ) ) { - dataPoint.positionProperty.unlink( dataPoint.positionUpdateListener ); + if ( dataPoint.positionProperty.hasListener( dataPoint.positionUpdateListener! ) ) { + dataPoint.positionProperty.unlink( dataPoint.positionUpdateListener! ); } - if ( dataPoint.userControlledProperty.hasListener( dataPoint.userControlledListener ) ) { - dataPoint.userControlledProperty.unlink( dataPoint.userControlledListener ); + if ( dataPoint.userControlledProperty.hasListener( dataPoint.userControlledListener! ) ) { + dataPoint.userControlledProperty.unlink( dataPoint.userControlledListener! ); } } ); } /** - * Function that animates all the dataPoints - * @public + * Animates all the dataPoints back to the bucket */ - returnAllDataPointsToBucket() { + public returnAllDataPointsToBucket(): void { this.dataPoints.forEach( dataPoint => { dataPoint.animate(); } ); @@ -176,25 +195,20 @@ class LeastSquaresRegressionModel { /** * Function for adding new dataPoints to this model when the user creates them, generally by clicking on * some sort of creator node. - * @public - * @param {DataPoint} dataPoint */ - addUserCreatedDataPoint( dataPoint ) { - + // TODO: Find all IntentionalAny, see https://github.com/phetsims/least-squares-regression/issues/94 + public addUserCreatedDataPoint( dataPoint: DataPoint ): void { this.dataPoints.push( dataPoint ); - this.addDataPointControlledListener( dataPoint ); } /** * Function that adds position listener and user Controlled listener; * Useful for dynamical points - * @public - * - * @param {DataPoint} dataPoint */ - addDataPointControlledListener( dataPoint ) { - dataPoint.userControlledListener = userControlled => { + public addDataPointControlledListener( dataPoint: DataPoint ): void { + + dataPoint.userControlledListener = ( userControlled: boolean ) => { const isOnGraph = this.graph.isDataPointPositionOverlappingGraph( dataPoint.positionProperty.value ); if ( !isOnGraph && !userControlled ) { // return the dataPoint to the bucket @@ -213,14 +227,19 @@ class LeastSquaresRegressionModel { this.dataPoints.remove( dataPoint ); } - if ( dataPoint.positionProperty.hasListener( dataPoint.positionUpdateListener ) ) { + if ( dataPoint.positionProperty && dataPoint.positionUpdateListener && + dataPoint.positionProperty.hasListener( dataPoint.positionUpdateListener ) ) { dataPoint.positionProperty.unlink( dataPoint.positionUpdateListener ); } - if ( dataPoint.userControlledProperty.hasListener( dataPoint.userControlledProperty ) ) { + + if ( dataPoint.userControlledProperty && dataPoint.userControlledListener && + dataPoint.userControlledProperty.hasListener( dataPoint.userControlledListener ) ) { dataPoint.userControlledProperty.unlink( dataPoint.userControlledListener ); } - if ( dataPoint.returnedToOriginEmitter.hasListener( dataPoint.returnedToOriginListener ) ) { - dataPoint.returnedToOriginEmitter.removeListener( dataPoint.returnedToOriginListener ); + + if ( dataPoint.returnedToOriginEmitter && + dataPoint.returnedToOriginEmitter.hasListener( dataPoint.returnedToOriginListener! ) ) { + dataPoint.returnedToOriginEmitter.removeListener( dataPoint.returnedToOriginListener! ); } }; diff --git a/js/least-squares-regression/model/Residual.ts b/js/least-squares-regression/model/Residual.ts index 5ae931a..3eabec7 100644 --- a/js/least-squares-regression/model/Residual.ts +++ b/js/least-squares-regression/model/Residual.ts @@ -1,4 +1,4 @@ -// Copyright 2014-2020, University of Colorado Boulder +// Copyright 2014-2024, University of Colorado Boulder /** * Type that defines a residual and a square residual. @@ -9,30 +9,48 @@ import Vector2 from '../../../../dot/js/Vector2.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; +import DataPoint from './DataPoint.js'; +/** + * Represents a residual between a data point and a line (either 'My Line' or 'Best Fit Line'). + */ class Residual { + + /** + * Position of the dataPoint. + */ + public readonly point1: Vector2; + /** - * @param {DataPoint} dataPoint - * @param {number} slope - * @param {number} intercept + * Position on the line corresponding to the dataPoint's x-coordinate. */ - constructor( dataPoint, slope, intercept ) { + public readonly point2: Vector2; - // store the dataPoint to be able to identify residual node + /** + * Indicates if the squared residual is to the left of the line. + */ + public readonly isSquaredResidualToTheLeft: boolean; + + public constructor( + public readonly dataPoint: DataPoint, + slope: number, intercept: number ) { + + // Store the dataPoint to be able to identify residual node. this.dataPoint = dataPoint; - // find the vertical position of the line following y = slope* x + intercept; + // Calculate the y-value on the line corresponding to the dataPoint's x-coordinate. const yValue = slope * dataPoint.positionProperty.value.x + intercept; - // The vertical displacement is positive if the datePoint is above the line and negative if below + // Calculate the vertical displacement: positive if above the line, negative if below. const verticalDisplacement = dataPoint.positionProperty.value.y - yValue; - // @public read-only - this.point1 = new Vector2( dataPoint.positionProperty.value.x, dataPoint.positionProperty.value.y ); // position of dataPoint - this.point2 = new Vector2( dataPoint.positionProperty.value.x, yValue ); // position of the point on the line + // Position of the dataPoint. + this.point1 = new Vector2( dataPoint.positionProperty.value.x, dataPoint.positionProperty.value.y ); + + // Position on the line corresponding to the dataPoint's x-coordinate. + this.point2 = new Vector2( dataPoint.positionProperty.value.x, yValue ); - // the square residual should not overlap the line - // @public read-only + // Determine if the squared residual is to the left of the line. this.isSquaredResidualToTheLeft = ( slope * verticalDisplacement > 0 ); } } diff --git a/js/least-squares-regression/view/BestFitLineControlPanel.ts b/js/least-squares-regression/view/BestFitLineControlPanel.ts index f3983c7..8436c5e 100644 --- a/js/least-squares-regression/view/BestFitLineControlPanel.ts +++ b/js/least-squares-regression/view/BestFitLineControlPanel.ts @@ -1,43 +1,56 @@ -// Copyright 2014-2023, University of Colorado Boulder +// Copyright 2014-2024, University of Colorado Boulder -//TODO https://github.com/phetsims/least-squares-regression/issues/85 rename to BestFitLineAccordionBox /** - * Accordion Box Node that displays checkboxes associated with properties of Best Fit Line - * This Node also displays the best Fit Line Equation and the sum of Squares Barometer Chart + * Accordion Box Node that displays checkboxes associated with properties of Best Fit Line. + * This Node also displays the Best Fit Line Equation and the Sum of Squares Barometer Chart. * * @author Martin Veillette (Berea College) */ -import merge from '../../../../phet-core/js/merge.js'; +import Emitter from '../../../../axon/js/Emitter.js'; +import { combineOptions } from '../../../../phet-core/js/optionize.js'; import { HBox, HStrut, SceneryConstants, Text, VBox } from '../../../../scenery/js/imports.js'; -import AccordionBox from '../../../../sun/js/AccordionBox.js'; +import AccordionBox, { AccordionBoxOptions } from '../../../../sun/js/AccordionBox.js'; import Checkbox from '../../../../sun/js/Checkbox.js'; import Panel from '../../../../sun/js/Panel.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionStrings from '../../LeastSquaresRegressionStrings.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; +import DataPoint from '../model/DataPoint.js'; +import Graph from '../model/Graph.js'; import EquationNode from './EquationNode.js'; import SumOfSquaredResidualsChart from './SumOfSquaredResidualsChart.js'; -// constants -const MAX_LABEL_WIDTH = 120; // max length of label text for i18n +/** + * Maximum width for label text to accommodate internationalization. + */ +const MAX_LABEL_WIDTH = 120; +// String constants from localization const bestFitLineString = LeastSquaresRegressionStrings.bestFitLine; const residualsString = LeastSquaresRegressionStrings.residuals; const squaredResidualsString = LeastSquaresRegressionStrings.squaredResiduals; class BestFitLineControlPanel extends AccordionBox { + private readonly equationText: EquationNode; + public readonly sumOfSquaredResidualsChart: SumOfSquaredResidualsChart; + /** - * @param {Graph} graph - model of the graph - * @param {Array.} dataPoints - * @param {Emitter} dataPointsAddedEmitter - * @param {Object} [options] + * @param graph - Model of the graph + * @param dataPoints - Array of data points + * @param dataPointsAddedEmitter - Emitter that signals when data points are added in bulk + * @param providedOptions - Optional customization options */ - constructor( graph, dataPoints, dataPointsAddedEmitter, options ) { - - // options for the accordion box - options = merge( { + public constructor( + private readonly graph: Graph, + dataPoints: DataPoint[], // TODO: unused? https://github.com/phetsims/least-squares-regression/issues/94 + dataPointsAddedEmitter: Emitter, + providedOptions?: AccordionBoxOptions + ) { + + // Merge provided options with default options + const options = combineOptions( { cornerRadius: 3, buttonXMargin: 10, buttonYMargin: 10, @@ -52,7 +65,7 @@ class BestFitLineControlPanel extends AccordionBox { titleXMargin: 0, contentXMargin: 10, contentYMargin: 10 - }, options ); + }, providedOptions ); // Create the chart (barometer) displaying the sum of the squares const sumOfSquaredResidualsChart = new SumOfSquaredResidualsChart( @@ -67,6 +80,8 @@ class BestFitLineControlPanel extends AccordionBox { // initial values set the spacing, the correct values for the slope and the intercept will be updated below const equationText = new EquationNode( { mode: 'bestFitLine' } ); equationText.visible = false; + + // Create a Panel to contain the Equation Node const equationPanel = new Panel( equationText, { fill: 'white', stroke: LeastSquaresRegressionConstants.SMALL_PANEL_STROKE, @@ -74,13 +89,18 @@ class BestFitLineControlPanel extends AccordionBox { resize: false } ); + // Text options for checkboxes const textOptions = { font: LeastSquaresRegressionConstants.CHECKBOX_TEXT_FONT, maxWidth: MAX_LABEL_WIDTH }; // Create the checkboxes - const lineCheckbox = new Checkbox( graph.bestFitLineVisibleProperty, new Text( bestFitLineString, textOptions ) ); + const lineCheckbox = new Checkbox( + graph.bestFitLineVisibleProperty, + new Text( bestFitLineString, textOptions ) + ); + const residualsCheckbox = new Checkbox( graph.bestFitLineShowResidualsProperty, new Text( residualsString, textOptions ) ); const squaredResidualsCheckbox = new Checkbox( graph.bestFitLineShowSquaredResidualsProperty, new Text( squaredResidualsString, textOptions ) ); @@ -116,32 +136,31 @@ class BestFitLineControlPanel extends AccordionBox { super( content, options ); - // @private - this.graph = graph; this.equationText = equationText; this.sumOfSquaredResidualsChart = sumOfSquaredResidualsChart; + // Update the Best Fit Line Equation initially this.updateBestFitLineEquation(); } /** - * @public - * @override + * Resets the control panel to its original state. */ - reset() { + public override reset(): void { this.sumOfSquaredResidualsChart.reset(); super.reset(); } /** - * Update the text of the best Fit Line Equation - * @public + * Updates the text of the Best Fit Line Equation based on the current linear fit parameters. */ - updateBestFitLineEquation() { + public updateBestFitLineEquation(): void { if ( this.graph.isLinearFitDefined() ) { const linearFitParameters = this.graph.getLinearFit(); this.equationText.setSlopeText( linearFitParameters.slope * this.graph.slopeFactor ); this.equationText.setInterceptText( linearFitParameters.intercept * this.graph.interceptFactor + this.graph.interceptOffset ); + + // Ensure the equation is visible if the Best Fit Line is enabled if ( this.graph.bestFitLineVisibleProperty.value ) { this.equationText.visible = true; } @@ -153,4 +172,5 @@ class BestFitLineControlPanel extends AccordionBox { } leastSquaresRegression.register( 'BestFitLineControlPanel', BestFitLineControlPanel ); + export default BestFitLineControlPanel; \ No newline at end of file diff --git a/js/least-squares-regression/view/DataPointCreatorNode.ts b/js/least-squares-regression/view/DataPointCreatorNode.ts index 8e56aaa..031ea5f 100644 --- a/js/least-squares-regression/view/DataPointCreatorNode.ts +++ b/js/least-squares-regression/view/DataPointCreatorNode.ts @@ -1,7 +1,7 @@ -// Copyright 2014-2022, University of Colorado Boulder +// Copyright 2014-2024, University of Colorado Boulder /** - * A Scenery node that can be clicked upon to create new dataPoints in the model. + * A Scenery node that can be clicked upon to create new DataPoints in the model. * * @author John Blanco * @author Martin Veillette (Berea College) @@ -9,18 +9,24 @@ import Vector2 from '../../../../dot/js/Vector2.js'; import ScreenView from '../../../../joist/js/ScreenView.js'; -import { Circle, Node, SimpleDragHandler } from '../../../../scenery/js/imports.js'; +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; +import { Circle, Node, NodeOptions, SceneryEvent, SimpleDragHandler } from '../../../../scenery/js/imports.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; import DataPoint from '../model/DataPoint.js'; class DataPointCreatorNode extends Node { + /** - * @param {Function} addDataPointToModel - A function for adding the created dataPoint to the model - * @param {ModelViewTransform2} modelViewTransform - * @param {Object} [options] + * @param addDataPointToModel - A function for adding the created DataPoint to the model. + * @param modelViewTransform - The ModelViewTransform2 instance for coordinate transformations. + * @param options - Optional customization options. */ - constructor( addDataPointToModel, modelViewTransform, options ) { + public constructor( + addDataPointToModel: ( dataPoint: DataPoint ) => void, + modelViewTransform: ModelViewTransform2, + options?: NodeOptions // TODO: rename to providedOptions https://github.com/phetsims/least-squares-regression/issues/94 + ) { super( { cursor: 'pointer' } ); // Create the node that the user will click upon to add a model element to the view. @@ -36,19 +42,21 @@ class DataPointCreatorNode extends Node { this.touchArea = this.localBounds.dilated( 15 ); this.mouseArea = this.localBounds.dilated( 5 ); - let parentScreenView = null; - let dataPoint; + let parentScreenView: ScreenView | null = null; + let dataPoint: DataPoint | null = null; + // Add the listener that will allow the user to click on this and create a new dataPoint, then position it in the model. this.addInputListener( new SimpleDragHandler( { // Allow moving a finger (touch) across this node to interact with it allowTouchSnag: true, - start: ( event, trail ) => { + start: ( event: SceneryEvent ) => { // find the parent screen if not already found by moving up the scene graph if ( !parentScreenView ) { - let testNode = this; // eslint-disable-line consistent-this + + let testNode = ( this as Node ) || null; // Workaround for lint errors for this alias while ( testNode !== null ) { if ( testNode instanceof ScreenView ) { parentScreenView = testNode; @@ -60,21 +68,20 @@ class DataPointCreatorNode extends Node { } // Determine the initial position (set to be one circle radius above the pointer point) - const initialPosition = parentScreenView.globalToLocalPoint( event.pointer.point.plus( new Vector2( 0, -LeastSquaresRegressionConstants.DYNAMIC_DATA_POINT_RADIUS ) ) ); + const initialPosition = parentScreenView!.globalToLocalPoint( event.pointer.point.plus( new Vector2( 0, -LeastSquaresRegressionConstants.DYNAMIC_DATA_POINT_RADIUS ) ) ); // Create and add the new model element. dataPoint = new DataPoint( modelViewTransform.viewToModelPosition( initialPosition ) ); dataPoint.userControlledProperty.set( true ); addDataPointToModel( dataPoint ); - }, - translate: translationParams => { - dataPoint.positionProperty.value = dataPoint.positionProperty.value.plus( modelViewTransform.viewToModelDelta( translationParams.delta ) ); + translate: ( translationParams: { delta: Vector2; oldPosition: Vector2; position: Vector2 } ) => { + dataPoint!.positionProperty.value = dataPoint!.positionProperty.value.plus( modelViewTransform.viewToModelDelta( translationParams.delta ) ); }, end: () => { - dataPoint.userControlledProperty.set( false ); + dataPoint!.userControlledProperty.set( false ); dataPoint = null; } } ) ); diff --git a/js/least-squares-regression/view/DataPointNode.ts b/js/least-squares-regression/view/DataPointNode.ts index 64a0f40..fde9f8e 100644 --- a/js/least-squares-regression/view/DataPointNode.ts +++ b/js/least-squares-regression/view/DataPointNode.ts @@ -7,37 +7,49 @@ * @author Martin Veillette (Berea College) */ +import Vector2 from '../../../../dot/js/Vector2.js'; +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; import { Node } from '../../../../scenery/js/imports.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; +import DataPoint from '../model/DataPoint.js'; class DataPointNode extends Node { + + /** + * A function to dispose of the DataPointNode's listeners. + */ + private readonly disposeDataPointNode: () => void; + /** - * @param {DataPoint} dataPoint - * @param {Node} representation - * @param {ModelViewTransform2} modelViewTransform + * @param dataPoint - The DataPoint model instance. + * @param representation - The visual representation Node of the DataPoint. + * @param modelViewTransform - The ModelViewTransform2 instance for coordinate transformations. */ - constructor( dataPoint, representation, modelViewTransform ) { + public constructor( + dataPoint: DataPoint, + representation: Node, + modelViewTransform: ModelViewTransform2 + ) { super( { cursor: 'pointer', children: [ representation ] } ); // Create a listener to the position of the dataPoint - const centerPositionListener = position => { + const centerPositionListener = ( position: Vector2 ) => { this.center = modelViewTransform.modelToViewPosition( position ); }; // Move this node as the model representation moves dataPoint.positionProperty.link( centerPositionListener ); - // @private: just for dispose. Named based on the type name so it won't have a name collision with parent/child ones + // TODO: use disposeEmitter, see https://github.com/phetsims/least-squares-regression/issues/94 this.disposeDataPointNode = () => { dataPoint.positionProperty.unlink( centerPositionListener ); }; } /** - * Releases references - * @public + * Releases references and listeners to prevent memory leaks. */ - dispose() { + public override dispose(): void { this.disposeDataPointNode(); super.dispose(); } diff --git a/js/least-squares-regression/view/DataSetComboBox.ts b/js/least-squares-regression/view/DataSetComboBox.ts index 5035f95..044dc20 100644 --- a/js/least-squares-regression/view/DataSetComboBox.ts +++ b/js/least-squares-regression/view/DataSetComboBox.ts @@ -1,28 +1,35 @@ -// Copyright 2014-2023, University of Colorado Boulder +// Copyright 2014-2024, University of Colorado Boulder /** - * Combo box for selecting a dataSet. + * Combo box for selecting a DataSet. * * @author Martin Veillette (Berea College) */ -import { Text } from '../../../../scenery/js/imports.js'; -import ComboBox from '../../../../sun/js/ComboBox.js'; +import Property from '../../../../axon/js/Property.js'; +import { Node, Text } from '../../../../scenery/js/imports.js'; +import ComboBox, { ComboBoxItem, ComboBoxOptions } from '../../../../sun/js/ComboBox.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; +import DataSet from '../model/DataSet.js'; -class DataSetComboBox extends ComboBox { +class DataSetComboBox extends ComboBox { /** - * @param {Property.} selectedDataSetProperty - * @param {Array.} dataSets - * @param {Node} dataSetListParent - * @param {number} maxTextWidth - max width of text in the combo box - * @constructor + * @param selectedDataSetProperty - The property that holds the currently selected DataSet. + * @param dataSets - Array of DataSets to populate the combo box. + * @param dataSetListParent - The parent node for the combo box list. + * @param maxTextWidth - Maximum width of text in the combo box items. + * @param providedOptions - Optional customization options. */ - constructor( selectedDataSetProperty, dataSets, dataSetListParent, maxTextWidth ) { - - // {ComboBoxItem[]} + public constructor( + selectedDataSetProperty: Property, + dataSets: DataSet[], + dataSetListParent: Node, + maxTextWidth: number, + providedOptions?: ComboBoxOptions // TODO: delete unused, see https://github.com/phetsims/least-squares-regression/issues/94 + ) { + // Create the ComboBoxItem array by mapping DataSets to ComboBoxItems const items = dataSets.map( dataSet => createItem( dataSet, maxTextWidth ) ); super( selectedDataSetProperty, items, dataSetListParent, { @@ -40,11 +47,8 @@ leastSquaresRegression.register( 'DataSetComboBox', DataSetComboBox ); /** * Creates an item for the combo box. - * @param {DataSet} dataSet - * @param {number} maxTextWidth - * @returns {ComboBoxItem} */ -function createItem( dataSet, maxTextWidth ) { +function createItem( dataSet: DataSet, maxTextWidth: number ): ComboBoxItem { return { value: dataSet, createNode: () => new Text( dataSet.name, { @@ -53,5 +57,4 @@ function createItem( dataSet, maxTextWidth ) { } ) }; } - export default DataSetComboBox; \ No newline at end of file diff --git a/js/least-squares-regression/view/DynamicDataPointNode.ts b/js/least-squares-regression/view/DynamicDataPointNode.ts index c4627f3..df0eefb 100644 --- a/js/least-squares-regression/view/DynamicDataPointNode.ts +++ b/js/least-squares-regression/view/DynamicDataPointNode.ts @@ -6,17 +6,17 @@ * @author Martin Veillette (Berea College) */ +import Vector2 from '../../../../dot/js/Vector2.js'; +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; import { Circle, SimpleDragHandler } from '../../../../scenery/js/imports.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; +import DataPoint from '../model/DataPoint.js'; import DataPointNode from './DataPointNode.js'; class DynamicDataPointNode extends DataPointNode { - /** - * @param {DataPoint} dataPoint - * @param {ModelViewTransform2} modelViewTransform - */ - constructor( dataPoint, modelViewTransform ) { + + public constructor( dataPoint: DataPoint, modelViewTransform: ModelViewTransform2 ) { // Create the visual representation of the DynamicDataPoint const representation = new Circle( LeastSquaresRegressionConstants.DYNAMIC_DATA_POINT_RADIUS, { @@ -36,11 +36,11 @@ class DynamicDataPointNode extends DataPointNode { allowTouchSnag: true, // Handler that moves the dataPoint in model space. - start: ( event, trail ) => { + start: () => { dataPoint.userControlledProperty.set( true ); }, - translate: args => { + translate: ( args: { delta: Vector2; oldPosition: Vector2; position: Vector2 } ) => { dataPoint.positionProperty.value = modelViewTransform.viewToModelPosition( args.position ); }, diff --git a/js/least-squares-regression/view/EquationNode.ts b/js/least-squares-regression/view/EquationNode.ts index 30ec1e6..47da0a2 100644 --- a/js/least-squares-regression/view/EquationNode.ts +++ b/js/least-squares-regression/view/EquationNode.ts @@ -1,4 +1,4 @@ -// Copyright 2014-2023, University of Colorado Boulder +// Copyright 2014-2024, University of Colorado Boulder /** * Equation Node that renders a text node of a linear equation of the form y = m x + b where m and b are numerical values @@ -7,9 +7,9 @@ */ import Utils from '../../../../dot/js/Utils.js'; -import merge from '../../../../phet-core/js/merge.js'; +import optionize from '../../../../phet-core/js/optionize.js'; import MathSymbols from '../../../../scenery-phet/js/MathSymbols.js'; -import { Node, Text } from '../../../../scenery/js/imports.js'; +import { Node, NodeOptions, Text } from '../../../../scenery/js/imports.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionStrings from '../../LeastSquaresRegressionStrings.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; @@ -17,19 +17,48 @@ import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants. const symbolXString = LeastSquaresRegressionStrings.symbol.x; const symbolYString = LeastSquaresRegressionStrings.symbol.y; +type SelfOptions = { + maxDecimalPlaces?: number; // Maximum number of decimal places for slope and intercept + mode?: 'myLine' | 'bestFitLine'; // Mode of the equation node + maxCharacterWidth?: number; // Maximum width of characters in the equation +}; + +type EquationNodeOptions = SelfOptions & NodeOptions; + +// TODO: Better name, see https://github.com/phetsims/least-squares-regression/issues/94 +type NumberString = { + absoluteNumber: string; + optionalSign: string; + sign: string; +}; + +// TODO: Replace SimpleDragHandler? see , see https://github.com/phetsims/least-squares-regression/issues/94 + class EquationNode extends Node { + + // Public Text nodes + public readonly yText: Text; + public readonly equalText: Text; + public readonly signSlopeText: Text; + public readonly valueSlopeText: Text; + public readonly xText: Text; + public readonly signInterceptText: Text; + public readonly valueInterceptText: Text; + + // Store options for later use + private readonly options: EquationNodeOptions; + /** * Scenery Node responsible for laying out the linear equation y = m x + b - * @param {Object} [options] */ - constructor( options ) { + public constructor( providedOptions?: EquationNodeOptions ) { super(); - options = merge( { - maxDecimalPlaces: 2, // maximum of number of decimal places on slope and intercept - mode: 'myLine', // valid options are 'myLine' and 'bestFitLine', - maxCharacterWidth: 25 - }, options ); + const options = optionize()( { + maxDecimalPlaces: 2, // Maximum number of decimal places on slope and intercept + mode: 'myLine', // Valid options are 'myLine' and 'bestFitLine' + maxCharacterWidth: 25 // Maximum width of text in the combo box + }, providedOptions ); this.options = options; @@ -75,7 +104,6 @@ class EquationNode extends Node { maxWidthInterceptString = `${maxWidthInterceptString}0`; } - // @public this.yText = new Text( symbolYString, stringTextOptions ); // 'y' this.equalText = new Text( MathSymbols.EQUAL_TO, stringTextOptions ); // the '=' sign this.signSlopeText = new Text( MathSymbols.PLUS, numericalTextOptions ); // + or - @@ -108,40 +136,38 @@ class EquationNode extends Node { this.addChild( mutableEquationText ); this.mutate( options ); - } /** - * Set the text of the slope and its accompanying sign - * @public - * @param {number} slope + * Sets the text of the slope and its accompanying sign. + * @param slope - The slope value to display. */ - setSlopeText( slope ) { - this.signSlopeText.string = this.numberToString( slope ).optionalSign; - this.valueSlopeText.string = this.numberToString( slope ).absoluteNumber; + public setSlopeText( slope: number ): void { + const { optionalSign, absoluteNumber } = this.numberToString( slope ); + this.signSlopeText.string = optionalSign; + this.valueSlopeText.string = absoluteNumber; } /** - * Set the text of the intercept and its accompanying sign - * @public - * @param {number} intercept + * Sets the text of the intercept and its accompanying sign. + * @param intercept - The intercept value to display. */ - setInterceptText( intercept ) { - this.signInterceptText.string = this.numberToString( intercept ).sign; - this.valueInterceptText.string = this.numberToString( intercept ).absoluteNumber; + public setInterceptText( intercept: number ): void { + const { sign, absoluteNumber } = this.numberToString( intercept ); + this.signInterceptText.string = sign; + this.valueInterceptText.string = absoluteNumber; } /** * Convert a number to a String, subject to rounding to a certain number of decimal places - * @private - * @param {number} number - * @returns {{absoluteNumber: number, optionalSign: string, sign: string}} + * @param number - The number to convert. + * @returns An object containing the absolute number string, optional sign, and sign. */ - numberToString( number ) { - const isNegative = ( this.roundNumber( number ) < 0 ); + private numberToString( number: number ): NumberString { + const isNegative = parseFloat( this.roundNumber( number ) ) < 0; const signString = isNegative ? MathSymbols.MINUS : MathSymbols.PLUS; const optionalSignString = isNegative ? MathSymbols.MINUS : ' '; - const absoluteNumber = this.roundNumber( Math.abs( this.roundNumber( number ) ) ); + const absoluteNumber = this.roundNumber( Math.abs( parseFloat( this.roundNumber( number ) ) ) ); const numberString = { absoluteNumber: absoluteNumber, optionalSign: optionalSignString, @@ -151,22 +177,20 @@ class EquationNode extends Node { } /** - * Round a number to a certain number of decimal places. Higher numbers have less decimal places. - * @private - * @param {number} number - - * @returns {number} + * Rounds a number to a certain number of decimal places based on its magnitude. + * @param number - The number to round. + * @returns The rounded number as a string. */ - roundNumber( number ) { + private roundNumber( number: number ): string { let roundedNumber; if ( Math.abs( number ) < 10 ) { - roundedNumber = Utils.toFixed( number, this.options.maxDecimalPlaces ); // eg. 9.99, 0.01 if this.options.maxDecimalPlaces=2 + roundedNumber = Utils.toFixed( number, this.options.maxDecimalPlaces! ); // eg. 9.99, 0.01 if this.options.maxDecimalPlaces=2 } else if ( Math.abs( number ) < 100 ) { - roundedNumber = Utils.toFixed( number, this.options.maxDecimalPlaces - 1 ); // eg. 10.1, 99.9 + roundedNumber = Utils.toFixed( number, this.options.maxDecimalPlaces! - 1 ); // eg. 10.1, 99.9 } else { - roundedNumber = Utils.toFixed( number, this.options.maxDecimalPlaces - 2 );// 100, 1000, 10000, 99999 + roundedNumber = Utils.toFixed( number, this.options.maxDecimalPlaces! - 2 );// 100, 1000, 10000, 99999 } return roundedNumber; } diff --git a/js/least-squares-regression/view/GraphAxesNode.ts b/js/least-squares-regression/view/GraphAxesNode.ts index d0b1a60..1488594 100644 --- a/js/least-squares-regression/view/GraphAxesNode.ts +++ b/js/least-squares-regression/view/GraphAxesNode.ts @@ -7,13 +7,18 @@ * @author Martin Veillette (Berea College) */ +import TProperty from '../../../../axon/js/TProperty.js'; +import Range from '../../../../dot/js/Range.js'; import Utils from '../../../../dot/js/Utils.js'; import Vector2 from '../../../../dot/js/Vector2.js'; import { Shape } from '../../../../kite/js/imports.js'; +import IntentionalAny from '../../../../phet-core/js/types/IntentionalAny.js'; +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; import MathSymbols from '../../../../scenery-phet/js/MathSymbols.js'; -import { Line, Node, Path, Rectangle, Text } from '../../../../scenery/js/imports.js'; +import { Line, Node, NodeOptions, Path, Rectangle, Text } from '../../../../scenery/js/imports.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; +import DataSet from '../model/DataSet.js'; //---------------------------------------------------------------------------------------- // constants @@ -55,14 +60,14 @@ const SMALL_EPSILON = 0.0000001; // for equalEpsilon check class GraphAxesNode extends Node { /** * Function responsible for laying out the ticks of the graph, the axis titles and the grid - * @param {DataSet} dataSet - * @param {ModelViewTransform2} modelViewTransform - * @param {Property.} showGridProperty + * @param dataSet + * @param modelViewTransform + * @param showGridProperty */ - constructor( dataSet, modelViewTransform, showGridProperty ) { + public constructor( dataSet: DataSet, modelViewTransform: ModelViewTransform2, showGridProperty: TProperty ) { const gridNode = new GridNode( dataSet, modelViewTransform ); - const showGridPropertyObserver = visible => { + const showGridPropertyObserver = ( visible: boolean ) => { gridNode.visible = visible; }; @@ -79,18 +84,9 @@ class GraphAxesNode extends Node { ] } ); - this.disposeGraphAxesNode = () => { + this.disposeEmitter.addListener( () => { showGridProperty.unlink( showGridPropertyObserver ); - }; - } - - /** - * Releases references - * @public - */ - dispose() { - this.disposeGraphAxesNode(); - super.dispose(); + } ); } } @@ -102,7 +98,7 @@ leastSquaresRegression.register( 'GraphAxesNode', GraphAxesNode ); class MajorTickNode extends Node { // Tick is placed at (x,y) and is either vertical or horizontal. - constructor( x, y, value, isVertical ) { + public constructor( x: number, y: number, value: string, isVertical: boolean ) { super(); @@ -122,7 +118,7 @@ class MajorTickNode extends Node { // label position if ( isVertical ) { // center label under line, compensate for minus sign - const signXOffset = ( value < 0 ) ? -( MINUS_SIGN_WIDTH / 2 ) : 0; + const signXOffset = ( parseFloat( value ) < 0 ) ? -( MINUS_SIGN_WIDTH / 2 ) : 0; tickLabelNode.left = tickLineNode.centerX - ( tickLabelNode.width / 2 ) + signXOffset; tickLabelNode.top = tickLineNode.bottom + TICK_LABEL_SPACING; } @@ -140,7 +136,7 @@ class MajorTickNode extends Node { class MinorTickNode extends Path { // Tick is placed at (x,y) and is either vertical or horizontal - constructor( x, y, isVertical ) { + public constructor( x: number, y: number, isVertical: boolean ) { super( isVertical ? Shape.lineSegment( x, y - MINOR_TICK_LENGTH, x, y + MINOR_TICK_LENGTH ) : Shape.lineSegment( x - MINOR_TICK_LENGTH, y, x + MINOR_TICK_LENGTH, y ), { @@ -153,13 +149,7 @@ class MinorTickNode extends Path { //-------------- // Tick Spacing for major and minor ticks //-------------- - -/** - * - * @param {Range} range - * @constructor - */ -function tickSpacing( range ) { +function tickSpacing( range: Range ): IntentionalAny { const width = range.max - range.min; const logOfWidth = Math.log( width ) / Math.LN10; // polyfill for Math.log10(width) const exponent = Math.floor( logOfWidth ); // width = mantissa*10^exponent @@ -211,11 +201,7 @@ function tickSpacing( range ) { //---------------------------------------------------------------------------------------- class XAxisNode extends Node { - /** - * @param {DataSet} dataSet - * @param {ModelViewTransform2} modelViewTransform - */ - constructor( dataSet, modelViewTransform ) { + public constructor( dataSet: DataSet, modelViewTransform: ModelViewTransform2 ) { super(); @@ -254,11 +240,7 @@ class XAxisNode extends Node { //---------------------------------------------------------------------------------------- class YAxisNode extends Node { - /*** - * @param {DataSet} dataSet - * @param {ModelViewTransform2} modelViewTransform - */ - constructor( dataSet, modelViewTransform ) { + public constructor( dataSet: DataSet, modelViewTransform: ModelViewTransform2 ) { super(); @@ -298,11 +280,8 @@ class YAxisNode extends Node { //---------------------------------------------------------------------------------------- class XLabelNode extends Node { - /** - * @param {DataSet} dataSet - * @param {ModelViewTransform2} modelViewTransform - */ - constructor( dataSet, modelViewTransform, options ) { + + public constructor( dataSet: DataSet, modelViewTransform: ModelViewTransform2, options?: NodeOptions ) { super( options ); @@ -324,11 +303,8 @@ class XLabelNode extends Node { //---------------------------------------------------------------------------------------- class YLabelNode extends Node { - /** - * @param {DataSet} dataSet - * @param {ModelViewTransform2} modelViewTransform - */ - constructor( dataSet, modelViewTransform ) { + + public constructor( dataSet: DataSet, modelViewTransform: ModelViewTransform2 ) { super(); @@ -352,11 +328,7 @@ class YLabelNode extends Node { //---------------------------------------------------------------------------------------- class BackgroundNode extends Node { - /** - * @param {DataSet} dataSet - * @param {ModelViewTransform2} modelViewTransform - */ - constructor( dataSet, modelViewTransform ) { + public constructor( dataSet: DataSet, modelViewTransform: ModelViewTransform2 ) { super(); const backgroundNode = new Rectangle( @@ -374,11 +346,8 @@ class BackgroundNode extends Node { //---------------------------------------------------------------------------------------- class GridNode extends Node { - /** - * @param {DataSet} dataSet - * @param {ModelViewTransform2} modelViewTransform - */ - constructor( dataSet, modelViewTransform ) { + + public constructor( dataSet: DataSet, modelViewTransform: ModelViewTransform2 ) { super(); // horizontal grid lines, one line for each unit of grid spacing diff --git a/js/least-squares-regression/view/GraphNode.ts b/js/least-squares-regression/view/GraphNode.ts index 1ad4517..af51669 100644 --- a/js/least-squares-regression/view/GraphNode.ts +++ b/js/least-squares-regression/view/GraphNode.ts @@ -8,27 +8,25 @@ */ import Multilink from '../../../../axon/js/Multilink.js'; +import Bounds2 from '../../../../dot/js/Bounds2.js'; import { Shape } from '../../../../kite/js/imports.js'; +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; import { Line, Node } from '../../../../scenery/js/imports.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; +import Graph from '../model/Graph.js'; import ResidualLineAndSquareNode from './ResidualLineAndSquareNode.js'; class GraphNode extends Node { - /** - * @param {Graph} graph - * @param {Bounds2} viewBounds - * @param {ModelViewTransform2} modelViewTransform - */ - constructor( graph, viewBounds, modelViewTransform ) { - - super(); + private readonly myLine: Line; + private readonly bestFitLine: Line; + private readonly bestFitResiduals: Node[]; - const self = this; + public constructor( public readonly graph: Graph, + public readonly viewBounds: Bounds2, + public readonly modelViewTransform: ModelViewTransform2 ) { - this.graph = graph; - this.viewBounds = viewBounds; - this.modelViewTransform = modelViewTransform; + super(); // Create 'MyLine' // First, get the two points formed by the intersection of the line and the boundary of the graph @@ -61,10 +59,8 @@ class GraphNode extends Node { /** * Update 'My Line' - * @param {number} slope - * @param {number} intercept */ - const updateMyLine = ( slope, intercept ) => { + const updateMyLine = ( slope: number, intercept: number ) => { const boundaryPoints = graph.getBoundaryPoints( slope, intercept ); this.myLine.setPoint1( modelViewTransform.modelToViewPosition( boundaryPoints.point1 ) ); this.myLine.setPoint2( modelViewTransform.modelToViewPosition( boundaryPoints.point2 ) ); @@ -91,6 +87,7 @@ class GraphNode extends Node { graph.myLineResiduals.addItemAddedListener( addedResidualProperty => { // Create and add the view representation for this residual. + // @ts-expect-error from Poolable mixin const residualNode = ResidualLineAndSquareNode.createFromPool( addedResidualProperty, LeastSquaresRegressionConstants.MY_LINE_COLOR, @@ -115,6 +112,7 @@ class GraphNode extends Node { graph.bestFitLineResiduals.addItemAddedListener( addedResidualProperty => { // Create and add the view representation for this residual. + // @ts-expect-error from Poolable mixin const residualNode = ResidualLineAndSquareNode.createFromPool( addedResidualProperty, LeastSquaresRegressionConstants.BEST_FIT_LINE_COLOR, @@ -131,9 +129,9 @@ class GraphNode extends Node { if ( removedResidualProperty === addedResidualProperty ) { // remove the residualNode from this.bestFitResiduals - const index = self.bestFitResiduals.indexOf( residualNode ); + const index = this.bestFitResiduals.indexOf( residualNode ); if ( index > -1 ) { - self.bestFitResiduals.splice( index, 1 ); + this.bestFitResiduals.splice( index, 1 ); } residualNode.release(); @@ -156,16 +154,12 @@ class GraphNode extends Node { /** * Resets values to their original state - * @public */ - reset() { + public reset(): void { this.updateBestFitLine(); } - /** - * @public - */ - update() { + public update(): void { this.updateBestFitLine(); // make sure that the best fit residuals are only visible when the best fit line is defined @@ -174,9 +168,8 @@ class GraphNode extends Node { /** * Update Best Fit Line - * @private */ - updateBestFitLine() { + private updateBestFitLine(): void { if ( this.graph.isLinearFitDefined() ) { const linearFitParameters = this.graph.getLinearFit(); const boundaryPoints = this.graph.getBoundaryPoints( linearFitParameters.slope, linearFitParameters.intercept ); @@ -193,9 +186,8 @@ class GraphNode extends Node { /** * Make sure that the best fit residuals and squares are only visible if the linear fit is defined. * This visibility is separate from the visibility handled by the control panel - * @public */ - updateBestFitResidualsVisible() { + public updateBestFitResidualsVisible(): void { for ( let i = 0; i < this.bestFitResiduals.length; i++ ) { this.bestFitResiduals[ i ].visible = this.graph.isLinearFitDefined(); } diff --git a/js/least-squares-regression/view/LeastSquaresRegressionScreenView.ts b/js/least-squares-regression/view/LeastSquaresRegressionScreenView.ts index 0aaeacb..98a666a 100644 --- a/js/least-squares-regression/view/LeastSquaresRegressionScreenView.ts +++ b/js/least-squares-regression/view/LeastSquaresRegressionScreenView.ts @@ -22,6 +22,7 @@ import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionStrings from '../../LeastSquaresRegressionStrings.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; import DataSet from '../model/DataSet.js'; +import LeastSquaresRegressionModel from '../model/LeastSquaresRegressionModel.js'; import BestFitLineControlPanel from './BestFitLineControlPanel.js'; import DataPointCreatorNode from './DataPointCreatorNode.js'; import DataSetComboBox from './DataSetComboBox.js'; @@ -68,10 +69,7 @@ const DATA_POINT_CREATOR_OFFSET_POSITIONS = [ class LeastSquaresRegressionScreenView extends ScreenView { - /** - * @param {LeastSquaresRegressionModel} model - */ - constructor( model ) { + public constructor( model: LeastSquaresRegressionModel ) { super(); @@ -112,7 +110,7 @@ class LeastSquaresRegressionScreenView extends ScreenView { const dataSetComboBox = new DataSetComboBox( model.selectedDataSetProperty, model.dataSets, dataSetListParent, dataSetLabelMaxWidth ); // Create a Push Button (next to the ComboBox) that can activate a dialog Node (Source and Reference Node) associated with each dataSet. - const sourceAndReferenceNode = new SourceAndReferenceNode( model.selectedDataSetProperty, this.layoutBounds ); + const sourceAndReferenceNode = new SourceAndReferenceNode( model.selectedDataSetProperty ); const sourceAndReferencePushButton = new TextPushButton( questionMarkString, { baseColor: 'gray', font: LeastSquaresRegressionConstants.TEXT_BOLD_FONT, @@ -125,7 +123,7 @@ class LeastSquaresRegressionScreenView extends ScreenView { // Create the nodes that will be used to layer things visually. const backLayer = new Node(); // Create the layer where the points will be placed. They are maintained in a separate layer so that they are over - // all of the point placement graphs in the z-order. + // all the point placement graphs in the z-order. const dataPointsLayer = new Node( { layerSplit: true } ); // Force the moving dataPoint into a separate layer for performance reasons. const bucketFrontLayer = new Node( { pickable: false } ); @@ -222,7 +220,7 @@ class LeastSquaresRegressionScreenView extends ScreenView { dataPointsLayer.addChild( dynamicDataPointNode ); // Listener for position - const positionPropertyListener = position => { + const positionPropertyListener = ( position: Vector2 ) => { // Check if the point is not animated and is overlapping with the graph before adding on the list of graph data Points if ( model.graph.isDataPointPositionOverlappingGraph( position ) && !addedDataPoint.animatingProperty.value ) { @@ -253,7 +251,7 @@ class LeastSquaresRegressionScreenView extends ScreenView { addedDataPoint.positionProperty.lazyLink( positionPropertyListener ); // Listener for userControlled - const userControlledPropertyListener = userControlled => { + const userControlledPropertyListener = ( userControlled: boolean ) => { if ( userControlled ) { dynamicDataPointNode.moveToFront(); } @@ -349,10 +347,9 @@ class LeastSquaresRegressionScreenView extends ScreenView { * Update the Source and Reference 'Dialog-like' Node visibility. This node has behavior which is identical to the about dialog * window, and this code is heavily borrowed from AboutDialog.js. * - * @param {SourceAndReferenceNode} sourceAndReferenceNode - The SourceAndReferenceNode whose visibility should be updated. - * @private + * @param sourceAndReferenceNode - The SourceAndReferenceNode whose visibility should be updated. */ - updateSourceAndReferenceNodeVisibility( sourceAndReferenceNode ) { + private updateSourceAndReferenceNodeVisibility( sourceAndReferenceNode: SourceAndReferenceNode ): void { // Renderer must be specified here because the plane is added directly to the scene (instead of to some other node // that already has svg renderer) diff --git a/js/least-squares-regression/view/MyLineControlPanel.ts b/js/least-squares-regression/view/MyLineControlPanel.ts index 0c3e9d0..d3f5431 100644 --- a/js/least-squares-regression/view/MyLineControlPanel.ts +++ b/js/least-squares-regression/view/MyLineControlPanel.ts @@ -6,9 +6,11 @@ * @author Martin Veillette (Berea College) */ +import Emitter from '../../../../axon/js/Emitter.js'; import Dimension2 from '../../../../dot/js/Dimension2.js'; import Range from '../../../../dot/js/Range.js'; import merge from '../../../../phet-core/js/merge.js'; +import IntentionalAny from '../../../../phet-core/js/types/IntentionalAny.js'; import MathSymbols from '../../../../scenery-phet/js/MathSymbols.js'; import { HStrut, Node, SceneryConstants, Text, VBox } from '../../../../scenery/js/imports.js'; import Checkbox from '../../../../sun/js/Checkbox.js'; @@ -17,6 +19,8 @@ import VSlider from '../../../../sun/js/VSlider.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionStrings from '../../LeastSquaresRegressionStrings.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; +import DataPoint from '../model/DataPoint.js'; +import Graph from '../model/Graph.js'; import EquationNode from './EquationNode.js'; import SumOfSquaredResidualsChart from './SumOfSquaredResidualsChart.js'; @@ -39,15 +43,10 @@ const SLIDER_OPTIONS = { const MAX_WIDTH = 150; class MyLineControlPanel extends Panel { - /** - * - * @param {Graph} graph - * @param {Array.} dataPoints - * @param {Emitter} dataPointsAddedEmitter - * @param {Object} [options] - */ - constructor( graph, dataPoints, dataPointsAddedEmitter, options ) { + public readonly sumOfSquaredResiduals: SumOfSquaredResidualsChart; + // TODO: unused param, see https://github.com/phetsims/least-squares-regression/issues/94 + public constructor( graph: Graph, dataPoints: DataPoint[], dataPointsAddedEmitter: Emitter, options: IntentionalAny ) { // Create a mutable equation y = {1} x + {2} , the slope and intercept are updated later // max width determined empirically, and there are 6 elements that make up the equation node @@ -56,18 +55,16 @@ class MyLineControlPanel extends Panel { /** * Function that updates the value of the current slope (based on the angle of the line) - * @param {number} angle */ - function updateTextSlope( angle ) { + function updateTextSlope( angle: number ): void { const slope = graph.slope( angle ); equationText.setSlopeText( slope * graph.slopeFactor ); } /** * Function that updates the value of the intercept - * @param {number} intercept */ - function updateTextIntercept( intercept ) { + function updateTextIntercept( intercept: number ): void { equationText.setInterceptText( intercept * graph.interceptFactor + graph.interceptOffset ); } @@ -161,9 +158,8 @@ class MyLineControlPanel extends Panel { graph.getMyLineSumOfSquaredResiduals.bind( graph ), dataPointsAddedEmitter, LeastSquaresRegressionConstants.MY_LINE_COLOR.SUM_OF_SQUARES_COLOR, - graph.myLineSquaredResidualsVisibleProperty, { - maxLabelWidth: MAX_WIDTH - } ); + graph.myLineSquaredResidualsVisibleProperty + ); // assemble all the previous nodes in a vertical box const mainBox = new VBox( { @@ -222,12 +218,10 @@ class MyLineControlPanel extends Panel { updateTextIntercept( graph.interceptProperty.value ); } ); - // @private this.sumOfSquaredResiduals = sumOfSquaredResiduals; } - // @public - reset() { + public reset(): void { this.sumOfSquaredResiduals.reset(); } } diff --git a/js/least-squares-regression/view/PearsonCorrelationCoefficientNode.ts b/js/least-squares-regression/view/PearsonCorrelationCoefficientNode.ts index eb13cac..5e2c941 100644 --- a/js/least-squares-regression/view/PearsonCorrelationCoefficientNode.ts +++ b/js/least-squares-regression/view/PearsonCorrelationCoefficientNode.ts @@ -8,15 +8,16 @@ */ import Utils from '../../../../dot/js/Utils.js'; -import merge from '../../../../phet-core/js/merge.js'; +import { combineOptions } from '../../../../phet-core/js/optionize.js'; import StringUtils from '../../../../phetcommon/js/util/StringUtils.js'; import MathSymbols from '../../../../scenery-phet/js/MathSymbols.js'; import { HStrut, Node, RichText, Text, VBox } from '../../../../scenery/js/imports.js'; -import AccordionBox from '../../../../sun/js/AccordionBox.js'; +import AccordionBox, { AccordionBoxOptions } from '../../../../sun/js/AccordionBox.js'; import Panel from '../../../../sun/js/Panel.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionStrings from '../../LeastSquaresRegressionStrings.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; +import Graph from '../model/Graph.js'; // string const pattern_0r_1value = '{0} {1}'; @@ -26,14 +27,12 @@ const R_EQUALS = StringUtils.format( '{0} =', LeastSquaresRegressionStrings.symb const MAX_LABEL_WIDTH = 120; // restrict width of labels for i18n class PearsonCorrelationCoefficientNode extends AccordionBox { - /** - * @param {Graph} graph - * @param {Object} [options] - */ - constructor( graph, options ) { + private readonly rightHandSideText: Text; + + public constructor( private readonly graph: Graph, options?: AccordionBoxOptions ) { // Options for the Accordion Box - options = merge( { + options = combineOptions( { cornerRadius: 3, buttonXMargin: 10, buttonYMargin: 10, @@ -93,25 +92,18 @@ class PearsonCorrelationCoefficientNode extends AccordionBox { super( content, options ); - // @private - this.graph = graph; this.rightHandSideText = rightHandSideText; } - /** - * @public - * @override - */ - reset() { + public override reset(): void { this.update(); super.reset(); } /** * Updates the value of the right hand side of the equation. - * @public */ - update() { + public update(): void { let rValueString; // Check for the existence of the rValue diff --git a/js/least-squares-regression/view/ResidualLineAndSquareNode.ts b/js/least-squares-regression/view/ResidualLineAndSquareNode.ts index 57708e1..c4f5290 100644 --- a/js/least-squares-regression/view/ResidualLineAndSquareNode.ts +++ b/js/least-squares-regression/view/ResidualLineAndSquareNode.ts @@ -7,22 +7,30 @@ * @author Martin Veillette (Berea College) */ +import Property from '../../../../axon/js/Property.js'; +import Bounds2 from '../../../../dot/js/Bounds2.js'; import { Shape } from '../../../../kite/js/imports.js'; import Poolable from '../../../../phet-core/js/Poolable.js'; +import IntentionalAny from '../../../../phet-core/js/types/IntentionalAny.js'; +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; import { Line, Node, Rectangle } from '../../../../scenery/js/imports.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; +import Residual from '../model/Residual.js'; class ResidualLineAndSquareNode extends Node { - /** - * @param {Property.} residualProperty - * @param {Object} lineColor - Object that defines all color properties of residual, squared residuals, line, etc. - * @param {Bounds2} viewBounds - * @param {ModelViewTransform2} modelViewTransform - * @param {Property.} lineVisibilityProperty - * @param {Property.} squareVisibilityProperty - */ - constructor( residualProperty, lineColor, viewBounds, modelViewTransform, lineVisibilityProperty, squareVisibilityProperty ) { + private readonly squareResidual: Rectangle; + private readonly lineResidual: Line; + private readonly lineVisibilityPropertyListener: ( visible: boolean ) => void; + private readonly squareVisibilityPropertyListener: ( visible: boolean ) => void; + private readonly updateLineAndSquareListener: () => void; + + public constructor( private residualProperty: Property, + lineColor: IntentionalAny, // TODO: Object that defines all color properties of residual, squared residuals, line, etc. See https://github.com/phetsims/least-squares-regression/issues/94 + private viewBounds: Bounds2, + private modelViewTransform: ModelViewTransform2, + private lineVisibilityProperty: Property, + private squareVisibilityProperty: Property ) { super(); // create line and square residual with nominal values, will set the correct value later @@ -51,9 +59,8 @@ class ResidualLineAndSquareNode extends Node { /** * Update the Line and Square Residual - * @public */ - updateLineAndSquare() { + public updateLineAndSquare(): void { const point1 = this.modelViewTransform.modelToViewPosition( this.residualProperty.value.point1 ); const point2 = this.modelViewTransform.modelToViewPosition( this.residualProperty.value.point2 ); @@ -80,29 +87,18 @@ class ResidualLineAndSquareNode extends Node { /** * Was dispose, see https://github.com/phetsims/scenery/issues/601 - * @public */ - release() { + public release(): void { // unlink listeners this.lineVisibilityProperty.unlink( this.lineVisibilityPropertyListener ); this.squareVisibilityProperty.unlink( this.squareVisibilityPropertyListener ); this.residualProperty.unlink( this.updateLineAndSquareListener ); + // @ts-expect-error TODO: https://github.com/phetsims/least-squares-regression/issues/94 this.freeToPool(); // will throw ResidualLineAndSquareNode into the pool } - /** - * @public - * - * @param {Property.} residualProperty - * @param {ColorDef} lineColor - * @param {Bounds2} viewBounds - * @param {ModelViewTransform2} modelViewTransform - * @param {Property.} lineVisibilityProperty - * @param {Property.} squareVisibilityProperty - * @returns {ResidualLineAndSquareNode} - */ - set( residualProperty, lineColor, viewBounds, modelViewTransform, lineVisibilityProperty, squareVisibilityProperty ) { + public set( residualProperty: Property, lineColor: IntentionalAny, viewBounds: Bounds2, modelViewTransform: ModelViewTransform2, lineVisibilityProperty: Property, squareVisibilityProperty: Property ): ResidualLineAndSquareNode { this.lineVisibilityProperty = lineVisibilityProperty; this.squareVisibilityProperty = squareVisibilityProperty; this.residualProperty = residualProperty; diff --git a/js/least-squares-regression/view/SourceAndReferenceNode.ts b/js/least-squares-regression/view/SourceAndReferenceNode.ts index 67eca8a..749a6b7 100644 --- a/js/least-squares-regression/view/SourceAndReferenceNode.ts +++ b/js/least-squares-regression/view/SourceAndReferenceNode.ts @@ -6,6 +6,7 @@ * @author Martin Veillette (Berea College) */ +import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js'; import Bounds2 from '../../../../dot/js/Bounds2.js'; import ScreenView from '../../../../joist/js/ScreenView.js'; import StringUtils from '../../../../phetcommon/js/util/StringUtils.js'; @@ -14,14 +15,13 @@ import Panel from '../../../../sun/js/Panel.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionStrings from '../../LeastSquaresRegressionStrings.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; +import DataSet from '../model/DataSet.js'; const sourcePatternString = LeastSquaresRegressionStrings.sourcePattern; class SourceAndReferenceNode extends ScreenView { - /** - * @param {Property.} selectedDataSetProperty - */ - constructor( selectedDataSetProperty ) { + + public constructor( selectedDataSetProperty: TReadOnlyProperty ) { /* * Use ScreenView, to help center and scale content. Renderer must be specified here because the window is added * directly to the scene, instead of to some other node that already has svg renderer. diff --git a/js/least-squares-regression/view/StaticDataPointNode.ts b/js/least-squares-regression/view/StaticDataPointNode.ts index e386621..9d02ef6 100644 --- a/js/least-squares-regression/view/StaticDataPointNode.ts +++ b/js/least-squares-regression/view/StaticDataPointNode.ts @@ -6,17 +6,16 @@ * @author Martin Veillette (Berea College) */ +import ModelViewTransform2 from '../../../../phetcommon/js/view/ModelViewTransform2.js'; import { Circle } from '../../../../scenery/js/imports.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; +import DataPoint from '../model/DataPoint.js'; import DataPointNode from './DataPointNode.js'; class StaticDataPointNode extends DataPointNode { - /** - * @param {DataPoint} dataPoint - * @param {ModelViewTransform2} modelViewTransform - */ - constructor( dataPoint, modelViewTransform ) { + + public constructor( dataPoint: DataPoint, modelViewTransform: ModelViewTransform2 ) { // Create and add visual representation of the dataPoint const representation = new Circle( LeastSquaresRegressionConstants.STATIC_DATA_POINT_RADIUS, { diff --git a/js/least-squares-regression/view/SumOfSquaredResidualsChart.ts b/js/least-squares-regression/view/SumOfSquaredResidualsChart.ts index c8806e4..1d09239 100644 --- a/js/least-squares-regression/view/SumOfSquaredResidualsChart.ts +++ b/js/least-squares-regression/view/SumOfSquaredResidualsChart.ts @@ -6,13 +6,15 @@ * @author Martin Veillette (Berea College) */ +import Emitter from '../../../../axon/js/Emitter.js'; import Multilink from '../../../../axon/js/Multilink.js'; -import merge from '../../../../phet-core/js/merge.js'; +import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js'; import ArrowNode from '../../../../scenery-phet/js/ArrowNode.js'; -import { Line, Node, Rectangle, Text } from '../../../../scenery/js/imports.js'; +import { Color, Line, Node, Rectangle, Text } from '../../../../scenery/js/imports.js'; import leastSquaresRegression from '../../leastSquaresRegression.js'; import LeastSquaresRegressionStrings from '../../LeastSquaresRegressionStrings.js'; import LeastSquaresRegressionConstants from '../LeastSquaresRegressionConstants.js'; +import Graph from '../model/Graph.js'; const sumString = LeastSquaresRegressionStrings.sum; @@ -26,21 +28,11 @@ const LINE_COLOR = 'black'; const FONT = LeastSquaresRegressionConstants.SUM_RESIDUALS_FONT; class SumOfSquaredResidualsChart extends Node { - /** - * @param {Graph} graph - model of a graph - * @param {Function} getSumOfSquaredResiduals - * @param {Emitter} dataPointsAddedEmitter - * @param {Color} fillColor - * @param {Property.} visibleProperty - * @param {Object} [Options] - */ - constructor( graph, getSumOfSquaredResiduals, dataPointsAddedEmitter, fillColor, visibleProperty, options ) { + public readonly updateWidth: () => void; - options = merge( { - maxLabelWidth: 150 - }, options ); + public constructor( graph: Graph, getSumOfSquaredResiduals: () => number, dataPointsAddedEmitter: Emitter, fillColor: Color | string, visibleProperty: TReadOnlyProperty ) { - super( options ); + super(); // The barometer chart is on its side, set width to 1 , will update it momentarily const rectangleBarometer = new Rectangle( 0, 0, 1, RECTANGLE_BAROMETER_HEIGHT, { @@ -65,16 +57,14 @@ class SumOfSquaredResidualsChart extends Node { font: FONT, centerX: horizontalArrow.centerX, top: horizontalArrow.bottom + 5, - maxWidth: options.maxLabelWidth + maxWidth: 150 } ); const zeroLabel = new Text( '0', { font: FONT, centerX: horizontalArrow.left, top: horizontalArrow.bottom + 5 } ); /** * For an input value ranging from 0 to infinity, the tanh function will return a value ranging between 0 and 1 - * @param {number} x - * @returns {number} */ - function tanh( x ) { + function tanh( x: number ): number { // this (particular) definition of hyperbolic tan function will work well for large positive x values return ( 1 - Math.exp( -2 * x ) ) / ( 1 + Math.exp( -2 * x ) ); } @@ -82,7 +72,7 @@ class SumOfSquaredResidualsChart extends Node { /** * Update the width of the rectangular Barometer */ - function updateWidth() { + function updateWidth(): void { // the width of the barometer is a non-linear. we use the tanh function to map an infinite range to a finite range // Note that tanh(0.5)=0.46. i.e approximately 1/2; // We want that a sum of squared residuals of 1/8 the area of the visible graph yields a width that reaches @@ -115,9 +105,8 @@ class SumOfSquaredResidualsChart extends Node { /** * Resets values to their original state - * @public */ - reset() { + public reset(): void { this.updateWidth(); } }