5.4 Developing the Inputs Panel
The first step, as usual, is to create a .js
file for the inputs panel. In my case, I named it as MyLineControlPanel.js
. Once you’ve done that the next step is to import a bunch of files that you are going to potentially need for this part of the simulation.
5.4.1 Imports
Here are the imports I used for this simulation:
import Utils from '../../../../dot/js/Utils.js';
import Dimension2 from '../../../../dot/js/Dimension2.js';
import Range from '../../../../dot/js/Range.js';
import HStrut from '../../../../scenery/js/nodes/HStrut.js';
import HBox from '../../../../scenery/js/nodes/HBox.js';
import RichText from '../../../../scenery/js/nodes/RichText.js';
import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
import LayoutBox from '../../../../scenery/js/nodes/LayoutBox.js';
import Node from '../../../../scenery/js/nodes/Node.js';
import Text from '../../../../scenery/js/nodes/Text.js';
import Checkbox from '../../../../sun/js/Checkbox.js';
import Panel from '../../../../sun/js/Panel.js';
import HSlider from '../../../../sun/js/HSlider.js';
import Font from '../../../../scenery/js/util/Font.js';
import newtonRaphson from '../../newtonRaphson.js';
import newtonRaphsonStrings from '../../newtonRaphsonStrings.js';
import HorizontalAquaRadioButtonGroup
from '../../../../sun/js/HorizontalAquaRadioButtonGroup.js';
import VerticalAquaRadioButtonGroup
from '../../../../sun/js/VerticalAquaRadioButtonGroup.js';
import NewtonRaphsonConstants from '../NewtonRaphsonConstants.js';
import EquationNode from './EquationNode.js';
Now I know all of these files seems a like of a whole lot but it’s not too complicated. Here’s a basic overview of the components being used:
Component Description
- Utils - This library is used for numerical operations. For example: fixing the number of significant digits.
- Dimension2 - Basic width and height, like a Bounds2 but without the location defined. We are using it for the slider track size and thumb size.
- Range - It is basically a numeric range but with additional methods that help other classes that need to use this component.
- HStrut - It is a Node meant to just take up horizontal space (usually for layout purposes).
- HBox - This is a type of a LayoutBox used as a placeholder for all the other nodes. It’s orientation is horizontal.
- RichText - Displays rich text by interpreting the input text as HTML, supporting a limited set of tags that prevent any security vulnerabilities. It does this by parsing the input HTML and splitting it into multiple Text children recursively.
- PhetFont - It has the standard fonts used by PhET simulations.
- LayoutBox - LayoutBox lays out its children in a row, either horizontally or vertically (based on an optional parameter).
- Node - A Node for the Scenery scene graph. Supports general directed acyclic graphics (DAGs). Handles multiple layers with assorted types (Canvas 2D, SVG, DOM, WebGL, etc.).
- Text - Displays text that can be filled/stroked.
- Checkbox - Checkbox is a typical checkbox UI component.
- Panel - This holds the content node. Dynamically adjusts its size to fit its contents.
- HSlider - This is the class for a horizontal slider, in this case we are using this but you can also use a VSlider which is a vertical slider.
- Font - This is used to define font size, type, and bold characteristics etc.
- newtonRaphson - Creates the namespace for this simulation.
- newtonRaphsonStrings - This is used to include any strings you have in your newton-raphson-strings_en.json file.
- HorizontalAquaRadioButtonGroup - This is used for making radio button groups. The orientation is going to be horizontal.
- VerticalAquaRadioButtonGroup - This is used for making radio button groups. The orientation is going to be vertical.
- NewtonRaphsonConstants - Constants that are used in the Newton Raphson Simulation.
- EquationNode - It renders a text node of a linear equation of the form y = m x + b where m and b are numerical values.
With all that overwhelming information out of the way, let’s start coding for our inputs panel.
5.4.2 Constants
We’ll start of by assigning some constants before we declare our MyLineControlPanel
class.
// constants
const SLIDER_OPTIONS = {
trackFill: 'black',
trackSize: new Dimension2( 120, 4 ),
thumbSize: new Dimension2( 30, 15 ),
thumbTouchAreaXDilation: 8,
majorTickLength: 0,
// changing the interval of slider change.
constrainValue: value => Utils.roundToInterval( value, 0.01 )
;
}
// decides the width of the equations and checkbox text.
const MAX_WIDTH = 200;
// used for the radio button group.
const BUTTON_FONT_SIZE = 16;
const BUTTON_FONT = new Font( { size: BUTTON_FONT_SIZE } );
The slider options are used later when we will create a new slider for our graph.xProperty
that we assigned to be a new NumberProperty(0.01)
in Graph.js
. More about that can be found at Graph.js. Most of the properties inside SLIDER_OPTIONS
are self-explanatory but I would like to point your attention towards the constrainValue
property which decides the interval of the change in the slider value when it is moved by the user.
As I mentioned before, after all my explanation, there is a section for the actual code for the file. So, to be considerate of space and time, I will be talking about how to use different components. However, I will be doing it once for each component because in every other occurence of the component, the functionality and implementation remains unchanged for the most part.
5.4.3 EquationNode
Let’s start with creating a new EquationNode
for our \(x_0\).
const equationCharacterMaxWidth = MAX_WIDTH / 6;
const xOText = new EquationNode( { maxCharacterWidth: equationCharacterMaxWidth },
'x<sub>0<sub>' );
The equationCharacterMaxWidth
is an arbitrary value, I found that this works for me by trial and error.
5.4.4 Panel
Next, here’s how you would go about making the panel (with white background) for the xOText
:
// create the equation panel with white background for x value
const equationPanel = new Panel( xOText, {
fill: 'white',
cornerRadius: NewtonRaphsonConstants.SMALL_PANEL_CORNER_RADIUS,
stroke: NewtonRaphsonConstants.SMALL_PANEL_STROKE,
resize: false
; } )
For a quick review on NewtonRaphsonConstants
checkout Defining your own Constants. The fill
decides the color of the background and resize
decides if based on input the size should increase dynamically or not.
5.4.5 Slider
Next up, here’s how one would go about creating a slider:
// the xSlider controls the x value
const xRange = new Range( 0.01, 6.11);
const xSlider = new HSlider( graph.xProperty, xRange, SLIDER_OPTIONS );
const checkboxTextOptions = { font: NewtonRaphsonConstants.CHECKBOX_TEXT_FONT,
maxWidth: MAX_WIDTH };
Notice how we are giving new HSlider
the graph.xProperty
. This is crucial because later we can use a .link()
for this property which will also us to capture the change in the slider values in real time.
5.4.7 Assembly
Finally, after you have made all of the components for your inputs panel, now you can start placing them in a layout box with your desired orientation like so:
// assemble all the previous nodes in a vertical box
const tempBox = new HBox( {
spacing: 10,
children: [
,
xSlider,
equationPanel
]
})const mainBox = new LayoutBox( {
spacing: 10,
children: [
,
functionTextPanel,
functionRadioButtonGroupPanel,
iterationTextPanel,
iterationGroupPanel,
sliderTextPanel
tempBox,
]align: 'left',
excludeInvisibleChildrenFromBounds: false
; } )
5.4.8 Layout
I placed the slider and the equation for the slider in a new HBox
because I wanted those two to have the same center.y
. Later I even manually changed the layout of the slider and the equation based on how I wanted it to be. Later, I called the super()
to call the constructor
of the parent class.
// layout the internal nodes of the right Aligned Node
.top = 61;
xSlider.left = xSlider.right;
equationPanel
super( mainBox, options );
5.4.9 Property.link()
Now we come to the interactive part. Let’s say a user uses the slider to change the values of the slider but they notice that the value of the equation isn’t getting updated. This is because we haven’t defined the behavior for what’s supposed to happen when the slider is moved. In this case, whenever the slider is moved I am calling the function updateTextXValue
to update the value of the equation in real time. This is what it looks like:
// updates the value of the equation
function updateTextXValue( xValue ) {
.setText( xValue );
xOText
}
// updating the x value based on xSlider input.
.xProperty.link( xValue => {
graphupdateTextXValue( xValue );
; } )
5.4.10 Code
Here’s the complete code of this file for better comprehension:
/**
* My Line Control Panel component and all of its methods and data members.
*
* @author Mayank Pandey
*/
import Utils from '../../../../dot/js/Utils.js';
import Dimension2 from '../../../../dot/js/Dimension2.js';
import Range from '../../../../dot/js/Range.js';
import HStrut from '../../../../scenery/js/nodes/HStrut.js';
import HBox from '../../../../scenery/js/nodes/HBox.js';
import RichText from '../../../../scenery/js/nodes/RichText.js';
import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
import LayoutBox from '../../../../scenery/js/nodes/LayoutBox.js';
import Node from '../../../../scenery/js/nodes/Node.js';
import Text from '../../../../scenery/js/nodes/Text.js';
import Checkbox from '../../../../sun/js/Checkbox.js';
import Panel from '../../../../sun/js/Panel.js';
import HSlider from '../../../../sun/js/HSlider.js';
import Font from '../../../../scenery/js/util/Font.js';
import newtonRaphson from '../../newtonRaphson.js';
import newtonRaphsonStrings from '../../newtonRaphsonStrings.js';
import HorizontalAquaRadioButtonGroup
from '../../../../sun/js/HorizontalAquaRadioButtonGroup.js';
import VerticalAquaRadioButtonGroup
from '../../../../sun/js/VerticalAquaRadioButtonGroup.js';
import NewtonRaphsonConstants from '../NewtonRaphsonConstants.js';
import EquationNode from './EquationNode.js';
// constants
const SLIDER_OPTIONS = {
trackFill: 'black',
trackSize: new Dimension2( 120, 4 ),
thumbSize: new Dimension2( 30, 15 ),
thumbTouchAreaXDilation: 8,
majorTickLength: 0,
// changing the interval of slider change.
constrainValue: value => Utils.roundToInterval( value, 0.01 )
;
}
// decides the width of the equations and checkbox text.
const MAX_WIDTH = 200;
// used for the radio button group.
const BUTTON_FONT_SIZE = 16;
const BUTTON_FONT = new Font( { size: BUTTON_FONT_SIZE } );
class MyLineControlPanel extends Panel {
constructor( graph, options ) {
const textOptions = {
font: NewtonRaphsonConstants.TEXT_BOLD_FONT,
maxDecimalPlaces: 10
;
}
const equationCharacterMaxWidth = MAX_WIDTH / 6;
const xOText = new EquationNode( { maxCharacterWidth: equationCharacterMaxWidth },
'x<sub>0<sub>' );
// updates the value of the equation
function updateTextXValue( xValue ) {
.setText( xValue );
xOText
}
// create the equation panel with white background for x value
const equationPanel = new Panel( xOText, {
fill: 'white',
cornerRadius: NewtonRaphsonConstants.SMALL_PANEL_CORNER_RADIUS,
stroke: NewtonRaphsonConstants.SMALL_PANEL_STROKE,
resize: false
;
} )
// the xSlider controls the x value
const xRange = new Range( 0.01, 6.11);
const xSlider = new HSlider( graph.xProperty, xRange, SLIDER_OPTIONS );
const checkboxTextOptions = { font: NewtonRaphsonConstants.CHECKBOX_TEXT_FONT,
maxWidth: MAX_WIDTH };
// creating radio button group for iterations.
const iterationValues = [ '0', '1', '2', '3', '4', '5' ];
const iterationGroupContent = _.map( iterationValues, stringValue => {
return {
value: stringValue,
node: new Text( stringValue, { font: BUTTON_FONT } ),
labelContent: stringValue
;
};
} )const iterationGroup = new HorizontalAquaRadioButtonGroup(
.iterationValuesProperty,
graph, {
iterationGroupContentspacing: 8,
enabledProperty: graph.buttonsEnabledProperty
;
} )const iterationGroupPanel = new Panel( iterationGroup, {
stroke: 'black',
xMargin: 10,
yMargin: 10
;
} )
// creating radio button group for functions.
const functionValues = [ 'x<sup>2</sup> - 2 = 0', 'cos(x) = 0',
'x<sup>3</sup> - 7 = 0', 'x<sup>3</sup> - 3x<sup>2</sup> + x - 1 = 0'];
const functionRadioButtonGroupContent = _.map( functionValues, stringValue => {
return {
value: stringValue,
node: new RichText( stringValue, { font: BUTTON_FONT } ),
labelContent: stringValue
;
};
} )const functionRadioButtonGroup = new VerticalAquaRadioButtonGroup(
.functionValuesProperty, functionRadioButtonGroupContent, {
graphspacing: 2,
enabledProperty: graph.functionEnabledProperty
;
} )const functionRadioButtonGroupPanel = new Panel( functionRadioButtonGroup, {
stroke: 'black',
xMargin: 10,
yMargin: 10
;
} )
// creating text panels.
const iterationTextPanel = new Panel( new Text('Number of Iterations', textOptions), {
fill: 'white',
cornerRadius: NewtonRaphsonConstants.SMALL_PANEL_CORNER_RADIUS,
stroke: NewtonRaphsonConstants.SMALL_PANEL_STROKE,
resize: false
;
} )const functionTextPanel = new Panel( new Text('Equation', textOptions), {
fill: 'white',
cornerRadius: NewtonRaphsonConstants.SMALL_PANEL_CORNER_RADIUS,
stroke: NewtonRaphsonConstants.SMALL_PANEL_STROKE,
resize: false
;
} )const sliderTextPanel = new Panel( new Text('Initial Estimate', textOptions), {
fill: 'white',
cornerRadius: NewtonRaphsonConstants.SMALL_PANEL_CORNER_RADIUS,
stroke: NewtonRaphsonConstants.SMALL_PANEL_STROKE,
resize: false
;
} )
// assemble all the previous nodes in a vertical box
const tempBox = new HBox( {
spacing: 10,
children: [
,
xSlider,
equationPanel
]
})const mainBox = new LayoutBox( {
spacing: 10,
children: [
,
functionTextPanel,
functionRadioButtonGroupPanel,
iterationTextPanel,
iterationGroupPanel,
sliderTextPanel
tempBox,
]align: 'left',
excludeInvisibleChildrenFromBounds: false
;
} )
// layout the internal nodes of the right Aligned Node
.top = 61;
xSlider.left = xSlider.right;
equationPanel
super( mainBox, options );
// updating the x value based on xSlider input.
.xProperty.link( xValue => {
graphupdateTextXValue( xValue );
;
} )
}
}
.register( 'MyLineControlPanel', MyLineControlPanel );
newtonRaphsonexport default MyLineControlPanel;