5.5 Developing the Graph Panel
The next step in our development process is to work on the graph
. Just like with the inputs panel, we make a .js
file for the graph
and in this case, I named it as GraphLinePlot
. The next step is to import a bunch of classes that you are going to use for this part.
5.5.1 Imports
import Range from '../../../../dot/js/Range.js';
import Vector2 from '../../../../dot/js/Vector2.js';
import Orientation from '../../../../phet-core/js/Orientation.js';
import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
import Node from '../../../../scenery/js/nodes/Node.js';
import Text from '../../../../scenery/js/nodes/Text.js';
import AxisNode from '../../../../bamboo/js/AxisNode.js';
import ChartTransform from '../../../../bamboo/js/ChartTransform.js';
import ChartRectangle from '../../../../bamboo/js/ChartRectangle.js';
import LabelSet from '../../../../bamboo/js/LabelSet.js';
import LinePlot from '../../../../bamboo/js/LinePlot.js';
import ScatterPlot from '../../../../bamboo/js/ScatterPlot.js';
import TickMarkSet from '../../../../bamboo/js/TickMarkSet.js';
import newtonRaphson from '../../newtonRaphson.js';
Here’s a basic overview of the components used:
Component Description
- Vector2 - Basic 2-dimensional vector, represented as (x,y).
- Orientation - Either horizontal or vertical, with helper values.
- AxisNode - Shows a line that depicts an axis.
- ChartTransform - ChartTransform defines the chart dimensions in model and view coordinate frames, and provides transform methods for moving between those coordinate frames.
- ChartRectangle - Shows the background and border for a chart, and updates when the chart dimensions change.
- LabelSet - Shows a set of labels within or next to a chart.
- LinePlot - Renders a line by connecting the data points of a data set with line segments.
- ScatterPlot - Renders a scatter plot using points of some radius for each point in the data set.
- TickMarkSet - Shows tick marks within or next to a chart.
5.5.2 Functions
Let me re-introduce to you the functions that we are using for this simulation:
Function choices
- \(x^2 - 2 = 0\)
- \(cos(x) = 0\)
- \(x^3 - 7 = 0\)
- \(x^3 - 3x^2 + x - 1 = 0\)
In order to display the function for each choice on our graph, we need to use the LinePlot
library from bamboo
. The LinePlot
object for our function will need a data set to plot the function. However, this data set can’t just be an 2-D array of (x, y) data points. By virtue of design, we need to use the Vector2
class to create an object for each data point and all these data points are added to a dataSet
array. This is the array which is passed to the LinePlot
object to display our function. To create these data sets, here are the functions that I have developed:
// creating four functions for each respective dataSet.
// x^2 - 2 = 0
function createDataSetFirstFunction( min, max, delta = 0.005 ) {
const dataSet = [];
for ( let x = min; x <= max; x += delta ) {
.push( new Vector2( x, (Math.pow(x,2) - 2) ) );
dataSet
}return dataSet;
;
}
// cos(x) = 0
function createDataSetSecondFunction( min, max, delta = 0.005 ) {
const dataSet = [];
for ( let x = min; x <= max; x += delta ) {
.push( new Vector2( x, Math.cos(x) ) );
dataSet
}return dataSet;
;
}
// x^3 - 7 = 0
function createDataSetThirdFunction( min, max, delta = 0.005 ) {
const dataSet = [];
for ( let x = min; x <= max; x += delta ) {
.push( new Vector2( x, (Math.pow(x,3) -7) ) );
dataSet
}return dataSet;
;
}
// x^3 - 3x^2 + x - 1 = 0
function createDataSetFourthFunction( min, max, delta = 0.005 ) {
const dataSet = [];
for ( let x = min; x <= max; x += delta ) {
.push( new Vector2( x, (Math.pow(x,3) - 3*Math.pow(x,2) + x - 1) ) );
dataSet
}return dataSet;
; }
5.5.3 Graph Constraints
Now we need to define the size and area of the graph which is done like so:
// defines chart constraints.
const chartTransform = new ChartTransform( {
viewWidth: 400,
viewHeight: 300,
modelXRange: new Range( -0.2, 7),
modelYRange: new Range(-2, 40)
;
} )
// chart area.
const chartRectangle = new ChartRectangle( chartTransform, {
fill: 'white',
stroke: 'black',
cornerXRadius: 6,
cornerYRadius: 6
; } )
5.5.4 Axis and Ticks
Now we want to make the blank canvas look more like a graph so we can do that by giving it the x and y axis. Additionally, we can give it tick marks and labels for each axis by doing this:
// Creating ticks and labels. Creating these outside and assigning them.
let tick_y = new TickMarkSet( chartTransform, Orientation.VERTICAL, 10,
edge: 'min' } );
{ let label_y = new LabelSet( chartTransform, Orientation.VERTICAL, 10,
edge: 'min' } );
{
// Anything you want clipped goes in here
this.children = [
// Background
,
chartRectangle
// Clipped contents
new Node( {
clipArea: chartRectangle.getShape(),
children: [
// Axes nodes are clipped in the chart
new AxisNode( chartTransform, Orientation.HORIZONTAL ),
new AxisNode( chartTransform, Orientation.VERTICAL )
],
} )
// Tick marks outside the chart
,
tick_y,
label_y// tick and label for the X axis.
new TickMarkSet( chartTransform, Orientation.HORIZONTAL, 1, { edge: 'min' } ),
new LabelSet( chartTransform, Orientation.HORIZONTAL, 1, {
edge: 'min',
createLabel: value => new Text( Math.abs( value ) < 1E-6
? value.toFixed( 0 )
: value.toFixed( 2 ), {
fontSize: 12
} )
} ); ]
Now with all that out of the way, we can begin adding LinePlot
objects and ScatterPlot
objects to our graph in order to bring in functionality
5.5.5 LinePlot
Here’s how one would go about creating LinePlot
objects for the chosen function choice (black line) and tracing the estimates (red line).
// creating two LinePlots.
// First one for the function choice and the second for tracing the estimates.
let lineplotFunction = new LinePlot( chartTransform, [], { stroke: 'black',
lineWidth: 2 } );
let lineplotXline = new LinePlot( chartTransform, [], { stroke: 'red',
lineWidth: 2 } );
There are certain arguments needed to be provided to the LinePlot
constructor. The general format looks like: new LinePlot(chartTransform, dataSet, options)
Arguments
- chartTransform - This is the
ChartTransform
object we created earlier to define the constraints of the graph in terms of size.- dataSet - This is the dataSet (an array) filled with
Vector2
data points.- options - These options pertain to the physical characteristics of our line. In this case, I have defined color and width of the lines.
In order to update the LinePlot
object, we can make use of the setDataSet()
method.
// you will see this later in the guide.
.setDataSet(graph.currFuncDataSet(0.01, 7)); lineplotFunction
5.5.6 ScatterPlot
So for the purpose of this simulation, I needed a point for \(x_0\) which is controlled by the user with the input slider and 5 points to show the estimate per iteration. To accomplish that, this is what I did:
// initial value of the black point.
let dataSet_black = [new Vector2(graph.xProperty.value, 0)];
// creating 6 points. 1st point is black and controlled by slider input.
// Rest of the points adhere to function outputs.
let point0 = new ScatterPlot( chartTransform, dataSet_black, {
fill: 'black',
radius: 5
;
} )let point1 = new ScatterPlot( chartTransform, [], {
fill: 'red',
radius: 3
;
} )let point2 = new ScatterPlot( chartTransform, [], {
fill: 'red',
radius: 3
;
} )let point3 = new ScatterPlot( chartTransform, [], {
fill: 'red',
radius: 3
;
} )let point4 = new ScatterPlot( chartTransform, [], {
fill: 'red',
radius: 3
;
} )let point5 = new ScatterPlot( chartTransform, [], {
fill: 'red',
radius: 5
; } )
The general syntax for creating a ScatterPlot
object is: new ScatterPlot(chartTransform, dataSet, options)
The only difference from LinePlot
is that the options take a fill
property for color and they also take a radius
property for the size of the point.
Since there are multiple points, therefore for easier access and use, I have stored them in an array as such:
let points = [];
.push(point0, point1, point2, point3, point4, point5); points
5.5.7 Property.link()
In order to update the LinePlots
and the ScatterPlots
, we have to use listeners that call on some function to update the view.
.functionValuesProperty.link(() => {
graphupdate();
})
.iterationValuesProperty.link(() => {
graphupdate();
})
.xProperty.link(() => {
graphupdate();
})
5.5.8 Updating the graph
In order to organize the structure of updating the graph, I have segrgated the process into different functions:
function chooseFunc(value) {
if (value === 'x<sup>2</sup> - 2 = 0') {
.currFunc = getXFirstFunction;
graph.currFuncY = getYFirstFunction;
graph.currFuncDataSet = createDataSetFirstFunction;
graphelse if (value === 'cos(x) = 0') {
} .currFunc = getXSecondFunction;
graph.currFuncY = getYSecondFunction;
graph.currFuncDataSet = createDataSetSecondFunction;
graphelse if (value === 'x<sup>3</sup> - 7 = 0') {
} .currFunc = getXThirdFunction;
graph.currFuncY = getYThirdFunction;
graph.currFuncDataSet = createDataSetThirdFunction;
graphelse {
} .currFunc = getXFourthFunction;
graph.currFuncY = getYFourthFunction;
graph.currFuncDataSet = createDataSetFourthFunction;
graph
}
}
function chooseRangeSpace(value) {
if (value === 'x<sup>2</sup> - 2 = 0') {
return {y1: -2, y2: 40, spacing: 10};
else if (value === 'cos(x) = 0') {
} return {y1: -1.3, y2: 1.3, spacing: 0.5};
else if (value === 'x<sup>3</sup> - 7 = 0') {
} return {y1: -15, y2:250, spacing: 50};
else {
} return {y1: -10, y2:140, spacing:20};
}
}
function updatePoints(value_button) {
0].setDataSet([new Vector2(graph.xProperty.value, 0)]);
points[.slice(1).map(point => point.setDataSet([]));
pointslet answerSet = [];
let dataSet = [ new Vector2 (graph.xProperty.value, 0) ];
let answer = graph.currFunc(graph.xProperty.value);
if (value_button != 0) {
.push(new Vector2 (graph.xProperty.value,
dataSet.currFuncY(graph.xProperty.value)) );
graph.push(new Vector2(answer.new_x, 0));
dataSet
}.push(answer);
answerSetfor (let i = 1; i < value_button; i++) {
.push(new Vector2(answer.new_x, graph.currFuncY(answer.new_x) ) );
dataSet= graph.currFunc(answer.new_x);
answer .push(new Vector2(answer.new_x, 0));
dataSet.push(answer);
answerSet
}.setDataSet(dataSet);
lineplotXlinefor(let i = 1; i <= value_button; i++) {
.setDataSet([new Vector2(answerSet[i-1].new_x, 0)]);
points[i]
}
}
function updateGraph(y1, y2, spacing, value_button) {
chooseFunc(graph.functionValuesProperty.value);
.setDataSet(graph.currFuncDataSet(0.01, 7));
lineplotFunction.setModelYRange(new Range(y1, y2));
chartTransform.setSpacing(spacing);
tick_y.setSpacing(spacing);
label_yupdatePoints(value_button);
}
function update() {
, y2, spacing} = chooseRangeSpace(graph.functionValuesProperty.value) );
( {y1updateGraph(y1, y2, spacing, graph.iterationValuesProperty.value);
}
The update()
function that is called by the property.link()
method of the graph.functionValuesProperty
, graph.iterationValuesProperty
, and graph.xProperty
is the central point of initiation. It does two things:
update()
- chooseRangeSpace(graph.functionValuesProperty.value) - It takes the current function choice as an argument and returns an object with hard-coded values for the range of the y axis and spacing required in terms of interval difference for each function.
- updateGraph(y1, y2, spacing, graph.iterationValuesProperty.value) - It calls this function to continue the update process.
updateGraph(arguments)
- Arguments: Takes the output from chooseRangeSpace() and the current number of iterations as arguments.
- chooseFunc(graph.functionValuesProperty.value) - It calls this function to decide which function is supposed to be used for doing calculations.
- Then based on function choice updates the data set of
lineplotfunction
, updates they range
, and updates theaxis labels
.- updatePoints(graph.iterationValuesProperty.value) - It calls this function to update the
black
andred
points.
chooseFunc(arguments)
- Arguments: Takes the function choice as an argument.
- Based on function choice assigns the following:
- graph.currFunc
- graph.currFuncY
- graph.currFuncDataSet
updatePoints(arguments)
- Arguments: Takes the number of iterations as an argument.
- Based on the number of iterations, it updates the dataSet of each data point.
5.5.9 Code
Here’s the complete code of this file for better comprehension:
/**
* Displays the line plots and scatter plots on the graph.
*
* @author Mayank Pandey
*/
import Range from '../../../../dot/js/Range.js';
import Vector2 from '../../../../dot/js/Vector2.js';
import Orientation from '../../../../phet-core/js/Orientation.js';
import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
import Node from '../../../../scenery/js/nodes/Node.js';
import Text from '../../../../scenery/js/nodes/Text.js';
import AxisNode from '../../../../bamboo/js/AxisNode.js';
import ChartTransform from '../../../../bamboo/js/ChartTransform.js';
import ChartRectangle from '../../../../bamboo/js/ChartRectangle.js';
import LabelSet from '../../../../bamboo/js/LabelSet.js';
import LinePlot from '../../../../bamboo/js/LinePlot.js';
import ScatterPlot from '../../../../bamboo/js/ScatterPlot.js';
import TickMarkSet from '../../../../bamboo/js/TickMarkSet.js';
import newtonRaphson from '../../newtonRaphson.js';
class GraphLinePlot extends Node {
constructor( graph, options ) {
super();
// creating four functions for each respective dataSet.
function createDataSetFirstFunction( min, max, delta = 0.005 ) {
const dataSet = [];
for ( let x = min; x <= max; x += delta ) {
.push( new Vector2( x, (Math.pow(x,2) - 2) ) );
dataSet
}return dataSet;
;
}
function createDataSetSecondFunction( min, max, delta = 0.005 ) {
const dataSet = [];
for ( let x = min; x <= max; x += delta ) {
.push( new Vector2( x, Math.cos(x) ) );
dataSet
}return dataSet;
;
}
function createDataSetThirdFunction( min, max, delta = 0.005 ) {
const dataSet = [];
for ( let x = min; x <= max; x += delta ) {
.push( new Vector2( x, (Math.pow(x,3) -7) ) );
dataSet
}return dataSet;
;
}
function createDataSetFourthFunction( min, max, delta = 0.005 ) {
const dataSet = [];
for ( let x = min; x <= max; x += delta ) {
.push( new Vector2( x, (Math.pow(x,3) - 3*Math.pow(x,2) + x - 1) ) );
dataSet
}return dataSet;
;
}
// defines chart constraints.
const chartTransform = new ChartTransform( {
viewWidth: 400,
viewHeight: 300,
modelXRange: new Range( -0.2, 7),
modelYRange: new Range(-2, 40)
;
} )
// chart area.
const chartRectangle = new ChartRectangle( chartTransform, {
fill: 'white',
stroke: 'black',
cornerXRadius: 6,
cornerYRadius: 6
;
} )
// creating two LinePlot's.
// First one for the function choice and the second for tracing the estimates.
let lineplotFunction = new LinePlot( chartTransform, [], { stroke: 'black',
lineWidth: 2 } );
let lineplotXline = new LinePlot( chartTransform, [], { stroke: 'red',
lineWidth: 2 } );
// initial value of the black point.
let dataSet_black = [new Vector2(graph.xProperty.value, 0)];
// creating 6 points. 1st point is black and controlled by slider input.
// Rest of the points adhere to function outputs.
let point0 = new ScatterPlot( chartTransform, dataSet_black, {
fill: 'black',
radius: 5
;
} )let point1 = new ScatterPlot( chartTransform, [], {
fill: 'red',
radius: 3
;
} )let point2 = new ScatterPlot( chartTransform, [], {
fill: 'red',
radius: 3
;
} )let point3 = new ScatterPlot( chartTransform, [], {
fill: 'red',
radius: 3
;
} )let point4 = new ScatterPlot( chartTransform, [], {
fill: 'red',
radius: 3
;
} )let point5 = new ScatterPlot( chartTransform, [], {
fill: 'red',
radius: 5
;
} )
let points = [];
.push(point0, point1, point2, point3, point4, point5);
points
let y1, y2, spacing;
function chooseFunc(value) {
if (value === 'x<sup>2</sup> - 2 = 0') {
.currFunc = getXFirstFunction;
graph.currFuncY = getYFirstFunction;
graph.currFuncDataSet = createDataSetFirstFunction;
graphelse if (value === 'cos(x) = 0') {
} .currFunc = getXSecondFunction;
graph.currFuncY = getYSecondFunction;
graph.currFuncDataSet = createDataSetSecondFunction;
graphelse if (value === 'x<sup>3</sup> - 7 = 0') {
} .currFunc = getXThirdFunction;
graph.currFuncY = getYThirdFunction;
graph.currFuncDataSet = createDataSetThirdFunction;
graphelse {
} .currFunc = getXFourthFunction;
graph.currFuncY = getYFourthFunction;
graph.currFuncDataSet = createDataSetFourthFunction;
graph
}
}
function chooseRangeSpace(value) {
if (value === 'x<sup>2</sup> - 2 = 0') {
return {y1: -2, y2: 40, spacing: 10};
else if (value === 'cos(x) = 0') {
} return {y1: -1.3, y2: 1.3, spacing: 0.5};
else if (value === 'x<sup>3</sup> - 7 = 0') {
} return {y1: -15, y2:250, spacing: 50};
else {
} return {y1: -10, y2:140, spacing:20};
}
}
function updatePoints(value_button) {
0].setDataSet([new Vector2(graph.xProperty.value, 0)]);
points[.slice(1).map(point => point.setDataSet([]));
pointslet answerSet = [];
let dataSet = [ new Vector2 (graph.xProperty.value, 0) ];
let answer = graph.currFunc(graph.xProperty.value);
if (value_button != 0) {
.push(new Vector2 (graph.xProperty.value,
dataSet.currFuncY(graph.xProperty.value)) );
graph.push(new Vector2(answer.new_x, 0));
dataSet
}.push(answer);
answerSetfor (let i = 1; i < value_button; i++) {
.push(new Vector2(answer.new_x, graph.currFuncY(answer.new_x) ) );
dataSet= graph.currFunc(answer.new_x);
answer .push(new Vector2(answer.new_x, 0));
dataSet.push(answer);
answerSet
}.setDataSet(dataSet);
lineplotXlinefor(let i = 1; i <= value_button; i++) {
.setDataSet([new Vector2(answerSet[i-1].new_x, 0)]);
points[i]
}
}
function updateGraph(y1, y2, spacing, value_button) {
chooseFunc(graph.functionValuesProperty.value);
.setDataSet(graph.currFuncDataSet(0.01, 7));
lineplotFunction.setModelYRange(new Range(y1, y2));
chartTransform.setSpacing(spacing);
tick_y.setSpacing(spacing);
label_yupdatePoints(value_button);
}
function update() {
, y2, spacing} = chooseRangeSpace(graph.functionValuesProperty.value) );
( {y1updateGraph(y1, y2, spacing, graph.iterationValuesProperty.value);
}
// Creating ticks and labels.
// Creating these outside and assigning them so these can be changed later.
let tick_y = new TickMarkSet( chartTransform, Orientation.VERTICAL, 10,
edge: 'min' } );
{ let label_y = new LabelSet( chartTransform, Orientation.VERTICAL, 10,
edge: 'min' } );
{
// Anything you want clipped goes in here
this.children = [
// Background
,
chartRectangle
// Clipped contents
new Node( {
clipArea: chartRectangle.getShape(),
children: [
// Axes nodes are clipped in the chart
new AxisNode( chartTransform, Orientation.HORIZONTAL ),
new AxisNode( chartTransform, Orientation.VERTICAL ),
// All lines and functions.
,
lineplotFunction,
lineplotXline,
point0,
point1,
point2,
point3,
point4
point5
],
} )
// Tick marks outside the chart
,
tick_y,
label_y// tick and label for the X axis.
new TickMarkSet( chartTransform, Orientation.HORIZONTAL, 1, { edge: 'min' } ),
new LabelSet( chartTransform, Orientation.HORIZONTAL, 1, {
edge: 'min',
createLabel: value => new Text( Math.abs( value ) < 1E-6
? value.toFixed( 0 )
: value.toFixed( 2 ), {
fontSize: 12
} )
} );
]
.functionValuesProperty.link(() => {
graphupdate();
})
.iterationValuesProperty.link(() => {
graphupdate();
})
.xProperty.link(() => {
graphupdate();
})
}
}
.register( 'GraphLinePlot', GraphLinePlot );
newtonRaphsonexport default GraphLinePlot;