Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Generating Lines With the Desmos API — Part 2

DZone's Guide to

Generating Lines With the Desmos API — Part 2

How to use the Desmos API to build a calculator and graphing tool using JavaScript.

· Web Dev Zone ·
Free Resource

Jumpstart your Angular applications with Indigo.Design, a unified platform for visual design, UX prototyping, code generation, and app development.

In part 1 you used the Desmos API to create an app that generates random straight lines on a set of axes. Clicking the Next button generates a new line. The finished app is shown in the figure below and you can see part 1 in action on JS Bin.

The app created in part 1

In this tutorial, part 2, you add the ability to create a set of random straight lines and navigate between them. In a lesson you might show five lines, one by one, and ask the pupils to write down the equations on their whiteboards, before discussing each question only after all the lines have been shown.

The figure below shows the updated app with the calculator displaying the fourth line out of a set of four.

The new version of the app with five buttons

The figure also shows the extra buttons for navigating between the lines in a generated set: First, Prev, Last, and Clear. See the new version in action on JS Bin.

This tutorial is in two main parts:

  1. Customizing the Desmos calculator, updating settings, and setting bounds.
  2. Adding the navigation buttons and functions.

Let's get started by learning a little more about setting up the calculator.

Customizing the Calculator

When you created a new Desmos calculator in part 1, it appeared on the page with its empty expressions list showing:

Blank calculator with its empty expressions list showing

The calculator also displayed a number of buttons and controls around its edge.

You can change the appearance of a new calculator by setting options when you create it.

var elt = document.getElementById('calculator');

calculator = Desmos.GraphingCalculator(elt, {
  expressionsCollapsed: true
});

In the listing above, you create a new calculator, passing the Desmos.GraphingCalculator method two arguments: an element in which to show the calculator and a JavaScript object literal with options you want to set. The second argument, the object literal, looks like this:

{ expressionsCollapsed: true }

You set the options as key-value pairs. In the previous example, you set one option, hiding the expressions list. There are lots of other options you can set and the next example hides most of the default calculator buttons and controls to give this bare-bones output:

Blank Desmos calculator with expressions list and buttons hidden

The options object now has four properties set:

var elt = document.getElementById('calculator');

calculator = Desmos.GraphingCalculator(elt, {
  keypad: false,
  expressions: false,
  settingsMenu: false,
  zoomButtons: false
});

calculator.setExpression({id: 'line1', latex: 'y=x-2'});

See it on JS Bin

Getting stuck on JS Bin? Switch the values from false to true, and try setting other available options.

Customizing the Axes

Having specified which controls to show for your calculator, you may also want to set up the graph axes exactly as you want them. Do you want labels? Grid lines? Arrows? Use the updateSettings method to apply your requirements.

In the next listing you hide the grid lines while enabling projector mode, as shown in this figure:

Desmos calculator with grid lines hidden and a custom axis label

Notice, you've also set the size of the steps on the x-axis and given it a custom label, "The x axis!"

var elt = document.getElementById('calculator');

calculator = Desmos.GraphingCalculator(elt, {
  expressionsCollapsed: true
});

calculator.updateSettings({
  showGrid: false,
  projectorMode: true,
  xAxisLabel: 'The x axis!',
  xAxisStep: 2
});

calculator.setExpression({id: 'line1', latex: 'y=x-2'});

There are plenty more settings for you to play with, so head over to JS Bin and experiment!

It's also possible to set the bounds for the axes, the lowest and highest values that will be displayed. The following figure shows the x-axis ranging from -2 to 5 and the y-axis from -4 to 5.

Desmos calculator with bounds set

This listing shows how to use the setMathBounds method to set values for the left, right, top, and bottom properties.

var elt = document.getElementById('calculator');

calculator = Desmos.GraphingCalculator(elt, {
  expressionsCollapsed: true
});

calculator.setMathBounds({
  left: -2,
  right: 5,
  bottom: -4,
  top: 5
});

calculator.setExpression({id: 'line1', latex: 'y=x-2'});

Okay, now you've seen how to tweak the Desmos calculator to suit your needs, it's time to get on with the show and update the app!

Updating the Line Generator Step-By-Step

To update the line generator app, you'll follow these steps:

  1. Declare variables to store the line info and the index of the current line.
  2. Add functions to navigate between stored lines.
  3. Add buttons to call the navigation functions.
  4. Define a render function to update the display.

Here's the full code listing; I'll discuss selected sections in the relevant steps.

Desmos Line Generator App 2


(function () {
  "use strict";

  var calculator;
  var pointsCollection;
  var qIndex;

  function between (a, b) {
    var range = b - a + 1;
    return a + Math.floor(Math.random() * range);
  }

  function getPoints () {
    return [
      { x: 0, y: between(-4, 4) },
      { x: 1, y: between(-5, 5) }
    ];
  }

  function getGradient (points) {
    return (points[1].y - points[0].y) / (points[1].x - points[0].x);
  }

  function pointString (point) {
    return '(' + point.x + ', ' + point.y + ')';
  }

  function lineString (points) {
    var line = "y = ";
    var gradient = getGradient(points);

    line += gradient + 'x+';
    line += points[0].y;

    return line;
  }

  function showLine () {
    var points = pointsCollection[qIndex];

    calculator.setMathBounds({
      left: - 2,
      right: 5,
      bottom: Math.min(points[0].y, points[1].y) - 3,
      top: Math.max(points[0].y, points[1].y) + 3
    });

    calculator.setExpression({id:'line', latex:lineString(points)});

    points.forEach(function (point, i) {
      calculator.setExpression({id: 'point' + i, latex: pointString(point)});
    });
  }

  function setTitle () {
    var title = 'Straight Lines and Gradients: ';
    var desmosTitle = document.getElementById('desmosTitle');

    if (pointsCollection.length) {
      title += (qIndex + 1) + ' of ' + pointsCollection.length;
    } else {
      title += 'click Next to create a new line';
    }

    desmosTitle.innerText = title;
  }

  function render () {
    showLine();
    setTitle();
  }

  function next () {
    if (qIndex === pointsCollection.length - 1) {
      pointsCollection.push(getPoints());
    }

    qIndex++;
    render();
  }

  function prev () {
    if (qIndex > 0) {
      qIndex--;
      render();
    }
  }

  function first () {
    if (pointsCollection.length) {
      qIndex = 0;
      render();
    }
  }

  function last () {
    if (pointsCollection.length) {
      qIndex = pointsCollection.length - 1;
      render();
    }
  }

  function clear () {
    qIndex = -1;
    pointsCollection = [];
    setTitle();

    calculator.removeExpressions([
      { id: 'point0' },
      { id: 'point1' },
      { id: 'line' }
    ]);
  }

  function init () {
    var elt = document.getElementById('calculator');

    calculator = Desmos.GraphingCalculator(elt, {
      expressionsCollapsed: true
    });

    calculator.updateSettings({
      xAxisMinorSubdivisions: 1,
      yAxisMinorSubdivisions: 1,
      yAxisStep: 1,
      xAxisStep: 1
    });

    qIndex = -1;
    pointsCollection = [];

    document.getElementById('btnNext').addEventListener('click', next);
    document.getElementById('btnPrev').addEventListener('click', prev);
    document.getElementById('btnFirst').addEventListener('click', first);
    document.getElementById('btnLast').addEventListener('click', last);
    document.getElementById('btnClear').addEventListener('click', clear);
  }

  init();
})();

See the new version in action on JS Bin

Declare Variables

You want to be able to create multiple lines and navigate between them. To store the lines and the index of the currently shown line, you declare a couple of variables, pointsCollection and qIndex.

var calculator;
var pointsCollection;
var qIndex;

You assign the variables values later in the program, in the init function.

calculator = Desmos.GraphingCalculator(elt, {
  expressionsCollapsed: true
});

qIndex = -1;
pointsCollection = [];

You assign an empty array to the pointsCollection variable. Each line the program creates will be added to the array. Here's the array after the program has added three lines (i.e. the user has clicked Next three times).

[
  [ { x: 0, y: -2 }, { x: 1, y:  1 } ],
  [ { x: 0, y:  1 }, { x: 1, y: -1 } ],
  [ { x: 0, y: -3 }, { x: 1, y:  3 } ]
]

An array of two points is used to specify each straight line. Each point is an object with x and y properties. The straight line specified passes through the two points.

The program navigates through the collection of lines by changing the value of qIndex. To access the second line in the collection, set qIndex to 1 (arrays are zero-based). Here's an example (not from the main listing) of working with the collection of lines.

var pointsCollection = [
  [ { x: 0, y: -2 }, { x: 1, y: 1 } ],
  [ { x: 0, y: 1 }, { x: 1, y: -1 } ],
  [ { x: 0, y: -3 }, { x: 1, y: 3 } ]
];

var qIndex = 1;
var points = pointsCollection[qIndex]; // [ { x: 0, y: 1 }, { x: 1, y: -1 } ]

points[0].x; // 0
points[0].y; // 1
points[1].x; // 1
points[1].y; // -1

Define Navigation Functions

The app uses five functions to navigate between any stored lines and to clear the store:

  • clear: empty the store and remove any lines from display.
  • first: display the first line in the store.
  • prev: display the previous line in the store.
  • next: display the next line or create a new line.
  • last: display the last line in the store.

Whenever the program calls the clear function, it resets the collection of lines to an empty array and sets the index of the current line to -1.

function clear () {
  qIndex = -1;
  pointsCollection = [];
  setTitle();

  calculator.removeExpressions([
    { id: 'point0' },
    { id: 'point1' },
    { id: 'line' }
  ]);
}

It also removes any existing expressions from the calculator's expressions list, leaving a pristine, blank calculator ready to show the first line in a new set. Shiny!

The first function checks if any lines are in the store, showing the first line if it exists.

function first () {
  if (pointsCollection.length) {
    qIndex = 0;
    render();
  }
}

If there are no lines in the store then pointsCollection.length will equal zero. Zero evaluates to false in the if condition, and the code in the block won't run.

If there is a line in the store then pointsCollection.length will be greater than zero and will evaluate to true, causing the code block to run. The block sets qIndex to 0, the index of the first line.

The render function updates both the points and the line shown on the calculator, and the app title is shown above the calculator.

The prev function shows the previous line in the collection, if there is one.

function prev () {
  if (qIndex > 0) {
    qIndex--;
    render();
  }
}

In this context, qIndex-- is equivalent to qIndex -= 1 and qIndex = qIndex - 1. They all subract 1 from qIndex.

The next function is slightly more complicated. It performs two jobs:

  • Create a new line: if the line collection is empty or if the current line is the last in the collection.
  • Show the next line: if the line collection is not empty and the current line is not the last.
function next () {
  // if the current line is the last or there are no lines
  if (qIndex === pointsCollection.length - 1) {
    // create a new line
    pointsCollection.push(getPoints());
  }

  // move to the next line
  // which may be the line just created
  qIndex++;

  // show the new line
  render();
}

If there are lines in the store, then pointsCollection.length -1 will be the index of the last line. If there are no lines in the store, then pointsCollection.length -1 will equal -1. But, qIndex is set to -1 when the program first runs and whenever the program calls the clear function.

Finally, the last function shows the last line in the collection.

function last () {
  if (pointsCollection.length) {
    qIndex = pointsCollection.length - 1;
    render();
  }
}

Wire Up the Navigation Buttons

With the navigation functions defined, it's easy to call them at the click of a button. The relevant code is in the init function:

document.getElementById('btnNext').addEventListener('click', next);
document.getElementById('btnPrev').addEventListener('click', prev);
document.getElementById('btnFirst').addEventListener('click', first);
document.getElementById('btnLast').addEventListener('click', last);
document.getElementById('btnClear').addEventListener('click', clear);

You first need to add the buttons to the HTML. You can check the HTML panel on JS Bin if you need a reminder.

Define a Render Function

Whenever a user navigates to a different line, the program needs to update the expression list on the calculator with the new points and line, and update the app title to report which line the calculator is showing. The render function makes both of those tasks happen.

function render () {
  showLine();
  setTitle();
}

The render function calls the showLine function which grabs the current line from the store, sets the bounds of the graph to fit the line, and updates the calculator's expressions list to show the points and line.

function showLine () {
  var points = pointsCollection[qIndex];

  calculator.setMathBounds({
    left: - 2,
    right: 5,
    bottom: Math.min(points[0].y, points[1].y) - 3,
    top: Math.max(points[0].y, points[1].y) + 3
  });

  calculator.setExpression({id:'line', latex:lineString(points)});

  points.forEach(function (point, i) {
    calculator.setExpression({id: 'point' + i, latex: pointString(point)});
  });
}

The function uses the smallest of the points' y-coordinates to set the bottom property and the largest to set the top property. The line fits nicely on the axes.

What's Next?

That's it for part 2! You've built a usable classroom tool and seen how to set up a Desmos calculator to better suit your needs.

In part 3, you'll use Desmos helper functions to perform calculations, generate straight lines with fractional gradients, and tidy up the expressions in the expressions list.

Find Out More About Desmos

See the calculator (and a lot more) in action at desmos.com

Investigate the API at desmos.com/api

Take a look at the Indigo.Design sample applications to learn more about how apps are created with design to code software.

Topics:
javascript ,web dev ,mathematics

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}