5.6 Developing the Results Panel

If you have made it so far, give yourself a pat on the back because you’ve made it through some of the more difficult panels. Most of what we will be using in this panel has already been introduced in the Developing the Inputs Panel and Developing the Graph Panel. As usual, I will be going over the essential components of the file that need explanation, for the full picture refer to Code

5.6.1 Imports

That being said, let’s start of with some of the imports that are new to us.

import Utils from '../../../../dot/js/Utils.js';
import merge from '../../../../phet-core/js/merge.js';
import LayoutBox from '../../../../scenery/js/nodes/LayoutBox.js';
import Text from '../../../../scenery/js/nodes/Text.js';
import Panel from '../../../../sun/js/Panel.js';
import AccordionBox from '../../../../sun/js/AccordionBox.js';
import newtonRaphson from '../../newtonRaphson.js';
import NewtonRaphsonConstants from '../NewtonRaphsonConstants.js';

Component Descriptions

  • AccordionBox - Box that can be expanded/collapsed to show/hide contents.
  • NewtonRaphsonConstants - Constants that are used in the Newton Raphson Simulation.

5.6.2 Extending AccordionBox

For this part of the simulation, I have used the AccordionBox class for the the kind of panel displayed on the screen. The idea behind this decision was that the user should have the ability to choose if they want to be concerned with the results or that if they feel distracted by the numbers and just want to understand the representation graphically, they can freely do so.

That said, all we are doing here is creating a new class Results in our Results.js file. This class extends AccordionBox like so:

class Results extends AccordionBox {
  constructor(graph, options) {...}
}

5.6.3 Designing a layout

Next up, we need to decide on a layout of the results. In our case, I want the end result to look something like this:

Iteration Estimate EPS A
0 ——– ——-
1 ——– ——-
2 ——– ——-
3 ——– ——-
4 ——– ——-
5 ——– ——-

Now we just need to create text and equation panels and align them accordingly.

5.6.4 Creating Text and Equation Panels

Next up, we need to create the actual panels where our data is fed for the layout above. We perform this operation like so:

  // creating arrays for storing the texts and panels for each type: 
  // iterations, estimations, error.
  let iterationTexts = [];
  let iterationPanels = [];
  let estimationTexts = [];
  let estimationPanels = [];
  let errorTexts = [];
  let errorPanels = [];
  let headingTexts = [];
  let headingPanels = [];

  for(let i = 0; i < 6; i++) {
    iterationTexts[i] = new Text('    '+ i +'    ', textOptions);
    iterationPanels[i] = new Panel( iterationTexts[i],  {
      cornerRadius: NewtonRaphsonConstants.SMALL_PANEL_CORNER_RADIUS,
      stroke: NewtonRaphsonConstants.SMALL_PANEL_STROKE,
      resize: false,
      xMargin: 10
    } )

    estimationTexts[i] = new Text('---------', textOptions);
    estimationPanels[i] = new Panel( estimationTexts[i],  {
      cornerRadius: NewtonRaphsonConstants.SMALL_PANEL_CORNER_RADIUS,
      stroke: NewtonRaphsonConstants.SMALL_PANEL_STROKE,
      resize: false,
      xMargin: 10
    } )

    errorTexts[i] = new Text('---------', textOptions);
    errorPanels[i] = new Panel( errorTexts[i],  {
      cornerRadius: NewtonRaphsonConstants.SMALL_PANEL_CORNER_RADIUS,
      stroke: NewtonRaphsonConstants.SMALL_PANEL_STROKE,
      resize: false,
      xMargin: 10
    } )
  }

  for(let i = 0; i < 3; i++) {
    headingTexts[i] = new Text('------------', textOptions);
    headingPanels[i] = new Panel( headingTexts[i],  {
      cornerRadius: NewtonRaphsonConstants.SMALL_PANEL_CORNER_RADIUS,
      stroke: NewtonRaphsonConstants.SMALL_PANEL_STROKE,
      resize: false,
      xMargin: 10
    } )
  }

  headingTexts[0].setText('Iteration');
  headingTexts[1].setText('Estimate');
  headingTexts[2].setText('|EPS A|');

All we are doing here is looping over each iteration and creating a new Panel which gets added to its respective array. Once we have the boiler-plate for our results panel, we can then start working on functions that will update the contents of these panels on user input.

5.6.5 Updating the Results

In order to update the results panel, I have constructed the following functions:

  // Returns the adjusted values in 4 significant digits and handles 
  // values less than 10^-3.
  function getAdjustedValues(data) {
    if (Math.abs(data) < 1) {
      if (Math.abs(data) >= 0.0001 || Math.abs(data) === 0) {
        return Utils.toFixed(data, 4);
      } else if (Math.abs(data) < 0.0001) {
        return parseFloat(data.toPrecision(2)).toExponential();
      }
    } else if (Math.abs(data) >= 999) {
      return data.toPrecision(2);
    } else {
      return data.toPrecision(4);
    }
  }

  // updates the results in the panel based on user input for 
  // chosen function and slider values.
  function updateResults(value_button) {
      let answerSet = [];
      let answer = graph.currFunc(graph.xProperty.value);
      for (let i = 0; i <= value_button; i++) {
        answerSet.push(answer);
        answer = graph.currFunc(answer.new_x);
      }
      for (let i = 1; i < answerSet.length; i++) {
        estimationTexts[i].setText(getAdjustedValues(answerSet[i-1].new_x));
        errorTexts[i].setText(getAdjustedValues(answerSet[i-1].abs_rel_error));
      }
      for (let i = answerSet.length; i < estimationTexts.length; i++) {
        estimationTexts[i].text = '---------';
        errorTexts[i].text = '---------';
      }
      estimationTexts[0].text = (graph.xProperty.value < 1) 
      ? Utils.toFixed( graph.xProperty.value, 4 ) 
      : Utils.toFixed( graph.xProperty.value, 3 );
      errorTexts[0].text = '---------';
  }

The updateResults() function is called by the ‘property.link()’ method of the graph.functionValuesProperty, graph.iterationValuesProperty, and graph.xProperty.

Here’s a quick rundown of what each of these two functions does:

updateResults()

  • Arguments: It takes the number of iterations as an argument.
  • Calculates the answer for the currently selected function choice, slider value and updates the panels only till the number of iterations selected while all the iterations after the selection are given a default output of -------

getAdjustedValues()

  • Arguments: It takes a number to adjust the number of significant digits.
  • Returns the adjusted values in 4 significant digits and handles values less than 10^-3.

5.6.7 Layout

We are almost done with this part! All that’s left is to put all these panels in the design we came up with like so:

  // stores all the rows in the right panel.
  let rows = [];

  rows[0] = new LayoutBox( {
    spacing: 15,
    children: [headingPanels[0], headingPanels[1], headingPanels[2]],
    orientation: 'horizontal'
  });

  for(let i = 1; i < 7; i++) {
    rows[i] = new LayoutBox( {
      spacing: 30,
      children: [iterationPanels[i-1], estimationPanels[i-1], errorPanels[i-1]],
      orientation: 'horizontal'
    });
  }

  // putting it all together.
  const content = new LayoutBox( {
    spacing: 15,
    children: rows,
    orientation: 'vertical'
  } );

And that’s it! We’re done with this part of the simulation, now we have to combine all these parts into one frame.

5.6.8 Code

Here’s the complete code of this file for better comprehension:

/**
 * Panel for the results.
 *
 * @author Mayank Pandey
 */

import Utils from '../../../../dot/js/Utils.js';
import merge from '../../../../phet-core/js/merge.js';
import LayoutBox from '../../../../scenery/js/nodes/LayoutBox.js';
import Text from '../../../../scenery/js/nodes/Text.js';
import Panel from '../../../../sun/js/Panel.js';
import AccordionBox from '../../../../sun/js/AccordionBox.js';
import newtonRaphson from '../../newtonRaphson.js';
import NewtonRaphsonConstants from '../NewtonRaphsonConstants.js';

class Results extends AccordionBox {
  /**
   * @param {Graph} graph
   * @param {Object} [options]
   */
  constructor( graph, options ) {

    // Options for the Accordion Box
    options = merge( {
      cornerRadius: 3,
      buttonXMargin: 10,
      buttonYMargin: 10,
      expandCollapseButtonOptions: {
        touchAreaXDilation: 16,
        touchAreaYDilation: 16
      },
      titleYMargin: 10,
      titleNode: new Text( 'Results', {
        font: NewtonRaphsonConstants.TEXT_BOLD_TITLE_FONT,
        maxWidth: 250
      } ),
      titleAlignY: 'top',
      contentXMargin: 10,
      contentYMargin: 10
    }, options );

  const textOptions = {
    font: NewtonRaphsonConstants.TEXT_BOLD_FONT,
    maxDecimalPlaces: 10
   };

  let answerSet = [];

  // creating arrays for storing the texts and panels for each type:
  // iterations, estimations, error.
  let iterationTexts = [];
  let iterationPanels = [];
  let estimationTexts = [];
  let estimationPanels = [];
  let errorTexts = [];
  let errorPanels = [];
  let headingTexts = [];
  let headingPanels = [];

  for(let i = 0; i < 6; i++) {
    iterationTexts[i] = new Text('    '+ i +'    ', textOptions);
    iterationPanels[i] = new Panel( iterationTexts[i],  {
      cornerRadius: NewtonRaphsonConstants.SMALL_PANEL_CORNER_RADIUS,
      stroke: NewtonRaphsonConstants.SMALL_PANEL_STROKE,
      resize: false,
      xMargin: 10
    } )

    estimationTexts[i] = new Text('---------', textOptions);
    estimationPanels[i] = new Panel( estimationTexts[i],  {
      cornerRadius: NewtonRaphsonConstants.SMALL_PANEL_CORNER_RADIUS,
      stroke: NewtonRaphsonConstants.SMALL_PANEL_STROKE,
      resize: false,
      xMargin: 10
    } )

    errorTexts[i] = new Text('---------', textOptions);
    errorPanels[i] = new Panel( errorTexts[i],  {
      cornerRadius: NewtonRaphsonConstants.SMALL_PANEL_CORNER_RADIUS,
      stroke: NewtonRaphsonConstants.SMALL_PANEL_STROKE,
      resize: false,
      xMargin: 10
    } )
  }

  for(let i = 0; i < 3; i++) {
    headingTexts[i] = new Text('------------', textOptions);
    headingPanels[i] = new Panel( headingTexts[i],  {
      cornerRadius: NewtonRaphsonConstants.SMALL_PANEL_CORNER_RADIUS,
      stroke: NewtonRaphsonConstants.SMALL_PANEL_STROKE,
      resize: false,
      xMargin: 10
    } )
  }

  headingTexts[0].setText('Iteration');
  headingTexts[1].setText('Estimate');
  headingTexts[2].setText('|EPS A|');

  // Returns the adjusted values in 4 significant digits and handles 
  // values less than 10^-3.
  function getAdjustedValues(data) {
    if (Math.abs(data) < 1) {
      if (Math.abs(data) >= 0.0001 || Math.abs(data) === 0) {
        return Utils.toFixed(data, 4);
      } else if (Math.abs(data) < 0.0001) {
        return parseFloat(data.toPrecision(2)).toExponential();
      }
    } else if (Math.abs(data) >= 999) {
      return data.toPrecision(2);
    } else {
      return data.toPrecision(4);
    }
  }

  // updates the results in the panel based on user input for 
  // chosen function and slider values.
  function updateResults(value_button) {
      let answerSet = [];
      let answer = graph.currFunc(graph.xProperty.value);
      for (let i = 0; i <= value_button; i++) {
        answerSet.push(answer);
        answer = graph.currFunc(answer.new_x);
      }
      for (let i = 1; i < answerSet.length; i++) {
        estimationTexts[i].setText(getAdjustedValues(answerSet[i-1].new_x));
        errorTexts[i].setText(getAdjustedValues(answerSet[i-1].abs_rel_error));
      }
      for (let i = answerSet.length; i < estimationTexts.length; i++) {
        estimationTexts[i].text = '---------';
        errorTexts[i].text = '---------';
      }
      estimationTexts[0].text = (graph.xProperty.value < 1) 
      ? Utils.toFixed( graph.xProperty.value, 4 ) 
      : Utils.toFixed( graph.xProperty.value, 3 );
      errorTexts[0].text = '---------';
  }

  // stores all the rows in the right panel.
  let rows = [];

  rows[0] = new LayoutBox( {
    spacing: 15,
    children: [headingPanels[0], headingPanels[1], headingPanels[2]],
    orientation: 'horizontal'
  });

  for(let i = 1; i < 7; i++) {
    rows[i] = new LayoutBox( {
      spacing: 30,
      children: [iterationPanels[i-1], estimationPanels[i-1], errorPanels[i-1]],
      orientation: 'horizontal'
    });
  }

  // putting it all together.
  const content = new LayoutBox( {
    spacing: 15,
    children: rows,
    orientation: 'vertical'
  } );

  graph.functionValuesProperty.link(() => {
    updateResults(graph.iterationValuesProperty.value);
  })

  graph.iterationValuesProperty.link(() => {
    updateResults(graph.iterationValuesProperty.value);
  })

  graph.xProperty.link(() => {
    updateResults(graph.iterationValuesProperty.value);
  })

  super( content, options );

}
  /**
   * @public
   * @override
   */
  reset() {
    super.reset();
  }
}

newtonRaphson.register( 'Results', Results );
export default Results;