4

I want to build something similar to desmos, where you can draw a graph in a canvas and then move it around.

I've been successful so far, but the only thing left is the user input.

Using a <input type="text"> tag I want the user to write for example:

"5x + 2"

The result should be:

var f = 5*x + 2;

I searched a lot for a way to do this and the only things I found were some Maths libraries in JavaScript and the eval() function. The last one is really helpful, because I could replace the x with the x value in the graph and it would work to build the graph of the function. The problem is that it lags a lot when I want to move the graph around so it is not the best idea.

I'm sure that it lags because the eval() function has to convert the string each time for every x value of the canvas for about 40-50 times a second.

What I want to achieve is convert the string into a Math function just once, and then use it.

Is it possible? Can anyone please help

EDIT 1: This is my function:

function f (pixelX) {
    var x = getCoordX (pixelX);

    var f = 2 * x + 2;

    return getPixelY(f);
}
  • 1
    Why do you need it to be converted 40-50 times a second? The problem is not with `eval`. You are asking the wrong question. – XCS Dec 22 '16 at 13:08
  • 2
    Possible duplicate of [Evaluating a string as a mathematical expression in JavaScript](http://stackoverflow.com/questions/2276021/evaluating-a-string-as-a-mathematical-expression-in-javascript) – Dekel Dec 22 '16 at 13:09
  • @Cristy When I move the mouse, the coordinate system moves as well. I measured it with a count variable that it is about 50 times a second –  Dec 22 '16 at 13:11
  • Yes, the question is how you re-draw the canvas. The eval is instant, the problem is the time it takes to redraw the canvas, that's why it lags. Try calling your `draw` function inside a `requestAnimationFrame` callback. – XCS Dec 22 '16 at 13:15
  • [mathJS](http://mathjs.org/docs/expressions/parsing.html) does exactly what you need with its parsing engine. – Jamiec Dec 22 '16 at 13:18

2 Answers2

3

To answer your question (even though this won't solve your problem).

You can do this.

var myString = "5 * x + 2";
var f = Function("x", "return " + myString);

This creates a function from a string. The first argument is the name of the first parameter, x, and the second argument is the body of the function (the return statement);

And then you can call it like: f(3) and the result would be 17.

Note that you have to write multiplication in your equation like 5 * x not like 5x.

This way you only evaluate the string into a function once, and then you can call it as many times you want with different parameters.

Your problem is not that eval takes a long time to compute but that re-drawing the canvas is very expensive. Try limiting the number of draws or only calling the draw function inside a requestAnimationFrame callback, this way the browser will redraw the canvas only when it is ready to do it.

XCS
  • 27,244
  • 26
  • 101
  • 151
  • 1
    The canvas redraws only when the mousebutton is pressed the the mouse is moved. I haven't yet limited the number of draws, and I didn't know anything about `requestAnimationFrame`. I will see if your example works. –  Dec 22 '16 at 13:25
  • 1
    It did work, and it worked much better than `eval()`. _This way you only evaluate the string into a function once, and then you can call it as many times you want with different parameters._ That is what I was looking for. I will look more deeply into this `requestAnimationFrame`. Thanks a lot. Will upvote and mark your answer as the right one. ;) –  Dec 22 '16 at 13:44
2

You can use eval, but it may behave differently on each browser's JavaScript engine.

This is an easy find-and-replace:

function solveForX(equation, xValue) {
  var expanded = equation.replace(/(\d+(?:\.\d+|))x/g, '$1 * x');
  return eval(expanded.replace(/x/g, xValue));
}

console.log(solveForX("5x + 2", 3));       // 17
console.log(solveForX("-4.2x + 3x", 5));   // -6
.as-console-wrapper { top: 0; max-height: 100% !important; }

Advanced — Multi-variable Expressions

const expFormat = '(\\d+(?:\\.\\d+|)){{@}}';
var expressionCache = {};
function lookupExpansion(v) {
  if (!expressionCache[v]) {
    expressionCache[v] = new RegExp(expFormat.replace(/\{\{\@\}\}/, v), 'g');
  }
  return expressionCache[v];
}

function toFunction(equation, variables) {
  variables.forEach(variable => {
    equation = equation.replace(lookupExpansion(variable), '$1 * ' + variable);
  });
  equation = equation.replace(/\b([a-z])([a-z])\b/g, '$1 * $2');
  console.log('[DEBUG]: Expanded => ' + equation);
  return Function.apply(null, variables.concat('return ' + equation));
}

// ======================== Simple ============================== //
var simpleMultiVariableFn = toFunction("x + 4x + 2y", ['x', 'y']);
console.log(simpleMultiVariableFn(3, 5)); // 25

// ======================== Advanced ============================ //
var slopeInterceptFunction = (slope, yIntercept) => {
  return x => toFunction("mx + b", ['m', 'x', 'b']).call(null, slope, x, yIntercept);
};
var interceptFn = slopeInterceptFunction(1, 2); // Reusable!
console.log(interceptFn(3)); // 5
console.log(interceptFn(4)); // 6
.as-console-wrapper { top: 0; max-height: 100% !important; }

Works with floats and negative numbers.

Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
  • 1
    Thanks for your answer. Will look into this more deeply later. ;) –  Dec 22 '16 at 14:31