Posted on 8/4/2021
Tags: Programming, Math
I'm excited to introduce FormulaGraph, a graphing calculator for general formulas (not just functions of x)!

Whereas many graphing calculators require an equation like y = sqrt(1 - x^2), FormulaGraph can handle formulas like y^2 + x^2 = 1. You don't have to solve for y to use FormulaGraph.

Here's what else it can do:

- Polar formulas in terms of r and theta, such as r = 2*sin(2*theta)
- Animate based on time t: y = x^(t % 4)
- Inequalities: x^100 + y^100 <= 2
- Handle panning and zooming using mouse and touch/pinch
- Share links

While FormulaGraph lacks features other graphing calculators have (such as precisely calculating y- and x-intercepts), it has some unique benefits:
(I introduced this list of calculators previously)

- TI-83 Plus
  - Features only in FormulaGraph: interactive graphs, animate with time, available in any web browser, graph formulas (not just functions)

- WolframAlpha
  - Features only in FormulaGraph: interactive graphs, animate with time
  - WolframAlpha won't graph some formulas, e.g. y^2+x^x=1 and x^y=x^x^x^x^x^x

- Meta-Calculator
  - Features only in FormulaGraph: interactive graphs, animate with time, graph formulas (not just functions), modulo operator

- FooPlot
  - Features only in FormulaGraph: animate with time, graph formulas (not just functions), graph inequalities

- GeoGebra Graphing Calculator
  - Features only in FormulaGraph: available in any web browser, graph polar formulas, modulo operator
  - GeoGebra Graphing Calculator won't graph some formulas, e.g. x*y*(x-y)*(x+y) < 1

- Desmos
  - Desmos is really great. It's the best graphing calculator in this list. It can handle formulas, animations, polar formulas, and more!
  - One thing FormulaGraph handles better: Desmos doesn't support polar equations which aren't linear in r. Example: r^2 = cos(theta)
  - Desmos has another quirky inconvenience: it's not possible to copy/paste formulas or easily edit their text. FormulaGraph's formula input boxes are super convenient in comparison. This feature is a must-have to make complex shapes like the Batman Curve.

- NumWorks Graphing Calculator
  - Features only in FormulaGraph: interactive graphs, convenient UI, animate with time, available in any web browser, graph formulas (not just functions), graph polar formulas, graph inequalities

- Relplot
  - Features only in FormulaGraph: interactive graphs, animate with time
  - Relplot has some graphing correctness bugs. Examples: y = x mod 2, y = x^x, y^y = x^x, r = 2*sin(4*theta), r = theta covers a small range of theta values
  - I'm reporting these bugs and I hope they get fixed!

- Graphtoy
  - Features only in FormulaGraph: graph formulas (not just functions), graph polar formulas, graph inequalities
  - Graphtoy has visual glitches (missing segments) when drawing circles and other similar shapes (e.g. y = sqrt(1-x^2) and y = -sqrt(1-x^2))

- Bonus: FormulaGraph has tons of test coverage, including checks that graphs render accurately. You can run the tests here. Beware: they take ~1 minute to run!

FormulaGraph's origins:

This is a hobby project I made by combining code and ideas from other peoples' cool projects.

It all started with Relplot, a project by Cornell's Andrew Myers. In Professor Myers' functional programming class, we implemented formula plotters using interval arithmetic.

Over a decade later, I stumbled upon Inigo Quilez's Graphtoy. I had fun making shapes and I thought it'd be even more fun to bring Relplot's general formula capabilities to Graphtoy's interactive UI.

So that's what I did: I ported Relplot's implementation of interval arithmetic from SML to JavaScript and spliced it into Graphtoy. I really appreciate all the work Professor Myers and Inigo did, especially publishing the source code for these projects.

There was one remaining difficulty: While Graphtoy uses JavaScript's built-in parser for inputted expressions, I needed to completely replace the implementations of operators like +, -, *, /, %. JavaScript doesn't have operator overloading so I needed my own expression parser.

That's where Lawrence Kesteloot's awesome web implementation of Turbo Pascal (my backup) comes in (see his interesting writeup about the project). I simply copied and reused his expression parser. Thank you Lawrence for building and sharing that passion project! I particularly like that this project has very few dependencies. And I was able to remove the few 3rd party dependencies it had in the portion of code I reused.

Key integration points where these projects are glued together:
- relplot.js - look for "MAIntervalMath" for a reimplementation of Relplot's interval arithmetic
- graphtoy.js - look for "Draw Relplot graphs" to see where Relplot was added to Graphtoy
- utilities.js - look for "_convertFormulaToFnSyntax" to see where the parser comes into play

Here are some miscellaneous things I've learned during this project:

- Some equations have solutions that are not clearly visible on most graphing calculators. For example, y = x^x has valid solutions for negative values of x which are single points (e.g. (-1, -1)).

- New shapes:
  - Square
  - Heart
  - Crazy lotus

- Modulo
  - Create concentric shapes using modulo
  - Create repeating animations using modulo
  - There are different definitions of modulo for negative numbers
    - Many programming languages implement modulo as "truncated division"
    - Desmos, Relplot, and Graphtoy use "floored division" so I use that in FormulaPlot too

- How to convert Cartesian coordinates to polar
  - Based on Relplot's implementation, I thought I just had to replace "r" with "sqrt(x^2+y^2)" and "theta" with "atan2(y, x)".
  - This turned out to be much harder than I expected in part because Relplot's polar coodinate support is incomplete (noted above in my comparison of FormulaGraph and Relplot).
  - After what I thought would be a 20 minute feature consumed several weekends, I'm reminded of the tongue-in-cheek Programmers' Credo: "We do these things not because they are easy, but because we thought they were going to be easy"
  - I solved this with 3 techniques: graph positive r and negative r (with theta shifted by pi), translate atan2's result range to [0, 2pi] (instead of [-pi, pi]), and iteratively render with theta += 2pi.

- Debugging an incorrect graph is similar to general debugging. Try to find the simplest case that reproduces the issue. I was relieved when a glitch in y = (mod(floor(x),2))*(x-floor(x))+(1-mod(floor(x),2))*(-x-floor(-x))+(1-mod(floor(x),2))*(1-ceil(x-floor(x))) reproduced in the far simpler y = ceil(x-floor(x)).

- When using interval arithmetic to graph formulas, it's ok to return wide intervals when the input is wide as long as the result intervals converge as the inputs get smaller. I use this technique to avoid the incorrect vertical line segments some graphing calculators (including Relplot and Graphtoy) show for y = floor(x).

To conclude, here's an improved version of the bouncing ball animation (original here).

Happy graphing :)