Function

What do you want to graph? Create a parametric function in JavaScript. Name it f(). For each input value t, between 0 and 1, f() returns a point like { x: 1, y: -0.08333333 }. You can do one time setup before defining f(). See the examples and additional instructions, below.

Inputs

You can access these sliders from inside your code. The name of each slider is written next to it in blue. The samples will update immediately as you move the slider.

support.input(0) = 0.50000
support.input(1) = 0.50000

Output

The first image shows your function tracing out the path. The second image shows how well each approximation matches the original. The table details the progress in the second image.

If you see a dark red or pink line, that means that there's a problem drawing the red line. If you see these extra lines, the normal solution is to ramp up the support.sampleCount.

Circles Amplitude
Using
Adding
Available

More Information

My take on the classic problem.

Complex Fourier Series

In this context “complex” means 2 dimensional. This page will convert any function into a series of circles, called a Fourier series. Add the circles back together to get the original function. Circles (or sines and cosines) are often essential because they easier to use than the original function.

This page lets you explore the Fourier series for a function. You can see how well the series approximates the original function, and how quickly it converges. And you can tweak the function and its parameterization to see the result.

The input to this page is a parametric function written in JavaScript. Your function takes one input, t, and returns a point. As t goes from 0 to 1, your function will trace out a path.

Different functions can trace out the same path. These are called different “parameterizations.” The small, light blue dot traces out the results of your function as t increases at a constant rate. The red dot always moves at a constant speed, as a point of comparison.

The “Square” and “Square with Easing” examples show two parameterizations of the same shape. The former uses the simplest parameterization where the small blue dot is always moving at a constant speed. In the latter example the blue dot slows down around each of the corners, like a physical object would have to, speeding up through the straightaways. The latter example converges faster than the former, perhaps because sharp corners are difficult for a Fourier series, and this function spent more time around those trouble spots.

References

JavaScript Support

You have access to all of JavaScript. You also have access to a variable called support which provides access to the following:

Examples

Polygons and Stars
const numberOfPoints = 5;
/**
* 0 to make a polygon.
* 1 to make a star, if numberOfPoints is odd and at least 5.
* 2 to make a different star, if numberOfPoints is odd and at least 7.
*/
const skip = 1;
const rotate = 2 * Math.PI / numberOfPoints * (1 + skip);

/**
* Create a random number generator.
* Change the seed to get different values.
* random() will return a number between 0 and 1.
*/
const random = support.random("My seed 2025");

/**
* How much effect does the random number generator have.
* Far left → no randomness at all.
*/
const amplitude = support.input(0);

function jiggle() {
return (random()-0.5) * amplitude;
}

const corners = [];
for (let i = 0; i < numberOfPoints; i++) {
const θ = i * rotate;
corners.push({x: Math.cos(θ) + jiggle(), y: Math.sin(θ) + jiggle()});
}
//console.log(corners);
const tSplitter = support.makeTSplitterA(0, corners.length, 0);
function f(t) {
const segment = tSplitter(t);
return support.lerpPoints(corners[segment.index], corners[(segment.index+1)%corners.length], segment.t);
}
Square
const corners = [{x: -0.5, y: -0.5}, {x: 0.5, y: -0.5}, {x: 0.5, y: 0.5}, {x: -0.5, y: 0.5} ];
const tSplitter = support.makeTSplitterA(0, corners.length, 0);
function f(t) {
const segment = tSplitter(t);
return support.lerpPoints(corners[segment.index], corners[(segment.index+1)%corners.length], segment.t);
}
Square with Easing
const corners = [{x: -0.5, y: -0.5}, {x: 0.5, y: -0.5}, {x: 0.5, y: 0.5}, {x: -0.5, y: 0.5} ];
const tSplitter = support.makeTSplitterA(0, corners.length, 0);
function f(t) {
const segment = tSplitter(t);
return support.lerpPoints(corners[segment.index], corners[(segment.index+1)%corners.length], support.ease(segment.t));
}
Cusps
// This pushes the limits of my graphing software.
// This software is aimed at smooth curves.

// The first input is the number of cusps, 1-10
const cuspCount = Math.round(support.input(0)*9.999+0.5);

// Second input:
// Far left looks like a cloud, cusps pointing inward.
// Far right looks like a star, cusps pointing outward.
// Dead center is smooth, no cusps.
const amplitude = 2 * (support.input(1) - 0.5);


function f(t) {
// Once around the circle.
const θ = t * Math.PI * 2;

const r = 2 - amplitude * Math.abs(Math.sin(t * Math.PI * cuspCount));
const x = r * Math.cos(θ);
const y = r * Math.sin(θ);
return { x, y };
}
SVG Path
// Also consider support.samples.hilbert[0] ... support.samples.hilbert[3]
// and support.samples.peanocurve[0] ... support.samples.peanocurve[2]
support.referencePath.d = support.samples.likeShareAndSubscribe;
support.sampleCount = 2000;
support.maxKeyframes = 30;
const length = support.referencePath.length;
console.log({length});
function f(t) {
// Copy the path as is.
return support.referencePath.getPoint(t * length);
}
Simple Ellipse
// The height can be anything convenient to you.
// This software will automatically zoom and pan to show off your work.
const height = 1;
// Use the first slider to change the width of the ellipse.
const width = height * support.input(0) * 2;
function f(t) {
// Use the second slider to change the starting point on the ellipse.
// This doesn't matter in a static ellipse, but it can be important in some animations and other special cases.
const angle = (t + support.input(1)) * 2 * Math.PI;
const x = width * Math.cos(angle);
const y = height * Math.sin(angle);
return {x, y};}
Circle with Wavy Edge
const height = 1;
const width = height;
function f(t) {
const angle = t * 2 * Math.PI;
const adjustmentAngle = angle * 8;
const adjustmentFactor = Math.sin(adjustmentAngle)/10+1;
const x = width * Math.cos(angle) * adjustmentFactor;
const y = height * Math.sin(angle) * adjustmentFactor;
return {x, y};}
Lissajous Curves
const a = 1; // Amplitude in x-direction
const b = 1; // Amplitude in y-direction
const freqX = 3; // Frequency in x-direction
const freqY = 2; // Frequency in y-direction
const phase = Math.PI / 2; // Phase difference
function f(t) {
const angle = t * 2 * Math.PI;
const x = a * Math.sin(freqX * angle + phase);
const y = b * Math.sin(freqY * angle);
return {x, y};}
Hypocycloid / Astroid
const R = 1; // Radius of the large circle
const r = R / 4; // Radius of the small circle (astroid case)
function f(t) {
const angle = t * 2 * Math.PI;
const x = (R - r) * Math.cos(angle) + r * Math.cos((R - r) / r * angle);
const y = (R - r) * Math.sin(angle) - r * Math.sin((R - r) / r * angle);
return {x, y};}
Bell Curve
// Number of standard deviations in each direction:
const right = support.input(0) * 5;
const left = - right;
const width = right - left;
const height = support.input(1) * 4 + 1;
function f(t) {
const x = t * width + left;
// Negate this.
// This program works with normal graphics notation where lower values of y are higher on the display.
// Normal algebra-class graphs show lower values of y lower on the screen.
const y = - height * Math.exp(-x*x);
return {x, y};}
Archimedean Spiral with Oscillation
const scale = 1; // Overall scale of the spiral
const turns = 3; // Number of full rotations
const waveFreq = 10; // Frequency of the oscillation
const waveAmp = 0.1; // Amplitude of the oscillation
function f(t) {
const angle = t * 2 * Math.PI * turns;
const radius = scale * t; // Linear growth for Archimedean spiral
const wave = waveAmp * Math.sin(t * 2 * Math.PI * waveFreq);
const x = radius * Math.cos(angle) * (1 + wave);
const y = radius * Math.sin(angle) * (1 + wave);
return {x, y};}
Heart Curve ♡
function f(t) {
const angle = t * 2 * Math.PI;
const x = 16 * Math.pow(Math.sin(angle), 3);
const algebraClassY = (13 * Math.cos(angle) - 5 * Math.cos(2 * angle) - 2 * Math.cos(3 * angle) - Math.cos(4 * angle));
const y = - algebraClassY;
return {x, y};}
Butterfly Curve
const scale = 0.2;
function f(t) {
const angle = t * 24 * Math.PI * support.input(0); // More rotations for complexity
const e = Math.exp(1);
const x = scale * Math.sin(angle) * (e ** Math.cos(angle) - 2 * Math.cos(4 * angle) - Math.pow(Math.sin(angle / 12), 5));
const y = - scale * Math.cos(angle) * (e ** Math.cos(angle) - 2 * Math.cos(4 * angle) - Math.pow(Math.sin(angle / 12), 5));
return {x, y};}
Hollow Star ☆
const scale = 1; // Overall scale of the star
const points = 5; // Number of star points
const innerRadius = 0.4; // Radius of the inner points (controls star shape)
const roundness = 0.1; // Amplitude of the oscillation for rounding
function f(t) {
const angle = t * 2 * Math.PI; // Full circle
const starAngle = angle * points; // Angle scaled for 5 points
const radius = scale * (1 - innerRadius * (Math.cos(starAngle) + 1) / 2); // Base star shape
const rounding = roundness * Math.sin(starAngle); // Oscillation for rounding
const x = (radius + rounding) * Math.cos(angle);
const y = (radius + rounding) * Math.sin(angle);
return {x, y};}
// According to Wikipedia, if it's hollow inside, it's a star.
// If you can see the lines crossing each other, it's a pentagram.
Rotating Ellipse
const r1 = 0.5; // Short radius of the ellipse
const r2 = 1.0; // Long radius of the ellipse
const phase = support.input(0) * Math.PI; // First slider: Rotation angle in radians (0 to π)
function f(t) {
const angle = t * 2 * Math.PI; // Full circle

// Basic ellipse centered at the origin
const xEllipse = r1 * Math.cos(angle);
const yEllipse = r2 * Math.sin(angle);

// Rotate the ellipse by the phase angle
const x = xEllipse * Math.cos(phase) - yEllipse * Math.sin(phase);
const y = xEllipse * Math.sin(phase) + yEllipse * Math.cos(phase);
return {x, y};}
// I used this formula as a starting place for the rounded pentagram.
Rounded Pentagram ⛤, Heptagram, etc.
const r1 = 0.5 * support.input(0); // Short radius of the ellipse. Top slider will adjust it.
const r2 = 1.0; // Long radius of the ellipse
function f(t) {
const phase = Math.PI * t; // The reference ellipse will make one half complete rotation during the tracing process.
const numberOfTrips = support.input(1) * 10; // Effective range is 0 to 10
const angle = t * 2 * Math.PI * numberOfTrips; // Basic ellipse centered at the origin
const xEllipse = r1 * Math.cos(angle);
const yEllipse = r2 * Math.sin(angle);// Rotate the ellipse by the phase angle
const x = xEllipse * Math.cos(phase) - yEllipse * Math.sin(phase);
const y = xEllipse * Math.sin(phase) + yEllipse * Math.cos(phase);
return {x, y};}
// The top slider controls the amount of curvature in the output.
// The second slider controls the number of lobes.
// Try values like 0.05, 0.15, 0.25, …, 0.95 for closed shapes.
Squaring the Circle
// This will trace out the shape of a dog tag using epicycles.
// Use the first slider to choose how many circles to use in
// this approximation, from 1 to 20.

// I was originally trying to use epicycles to create a square.
// But I ran into some problems,
// so this a square where two of the sides bulge out some.

const numberOfCircles = 1 + 19 * support.input(0);
const circlesToConsider = Math.ceil(numberOfCircles);
const attenuation = numberOfCircles - Math.floor(numberOfCircles);
function f(t) {
let x = 0;
let y = 0;
for (let k = 0; k < circlesToConsider; k++) {
const n = 2 * k + 1; // Odd frequencies: 1, 3, 5, ...
const radius = (4 * Math.sqrt(2)) / (Math.PI * Math.PI * n * n);
const phase = k % 2 === 0 ? -Math.PI / 4 : Math.PI / 4;
const factor = (k === circlesToConsider - 1 && attenuation > 0) ? attenuation : 1;
const baseAngle = t * 2 * Math.PI;
x += factor * radius * Math.cos(n * baseAngle + phase);
y += factor * radius * Math.sin(n * baseAngle + phase);
}
return {x, y};}
A Better Square
// Inspired by https://www.youtube.com/watch?v=t99CmgJAXbg
// Square Orbits Part 1: Moon Orbits

const R = 0.573; // Match our first circle's radius
const moonRadius = (7 / 45) * R;
function f(t) {
const planetAngle = t * 2 * Math.PI; // Frequency 1
const moonAngle = -3 * planetAngle; // Frequency 3, opposite direction
const planetX = R * Math.cos(planetAngle);
const planetY = R * Math.sin(planetAngle);
const moonX = moonRadius * Math.cos(moonAngle);
const moonY = moonRadius * Math.sin(moonAngle);
const x = (planetX + moonX) * 1.2;
const y = (planetY + moonY) * 1.2;
return {x, y};}
Fourier square wave
// Use the first slider to choose how many sine waves to use in
// this approximation, from 1 to 20.

const numberOfCircles = 1 + 19 * support.input(0);
const circlesToConsider = Math.ceil(numberOfCircles);
const attenuation = numberOfCircles - Math.floor(numberOfCircles);
function f(t) {
let ySum = 0;
for (let k = 0; k < circlesToConsider; k++) {
const n = 2 * k + 1; // Odd frequencies: 1, 3, 5, ...
const amplitude = (4 / Math.PI) / n;
const factor = (k === circlesToConsider - 1 && attenuation > 0) ? attenuation : 1;
const baseAngle = 2 * Math.PI * 2.5 * t + Math.PI / 2; // 2.5 cycles, shift for vertical center
ySum += factor * amplitude * Math.sin(n * baseAngle);
}
const x = (t * 5) - 2.5; // Span x from -2.5 to 2.5
const y = ySum;
return {x, y};}