Function

What do you want to graph? Create a parametric function in JavaScript. For each input value t, between 0 and 1, return an object with x and y parameters, both numbers.

function (t /* A value between 0 and 1, inclusive. */,
support) {
"use strict";
return { x, y };
}

Offset

The far left means that t sweeps from 0 to 1. This is the default. Set this ¼ of the way from the left to make t start at 0.25, sweep toward 1, wrap around at 0, then move back to 0.25 to finish. The right end of the scale means to start from 1, which is effectively the same as starting from 0. For a closed path this setting should not change the basic shape that gets stroked or filled. However, the difference can be obvious in some animations and other special effects.

t starts at: 0.00000

Precision

This software will make a path approximating your function. This software takes samples at n evenly spread values of t. At each t, this software will record the position of your function and its derivative. Each segment is made of a part of a parabola chosen to best match the samples at either end.

If you plan to smoothly morph between two paths, make sure this parameter is the same when you create each path. The rest should work itself out automatically.

Number of segments:  20

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 Samples

The output from this program is a css path, a string. That path can be used a lot of different ways. Here are a few examples.

𝜏 π Quantitas Materiæ est mensura ejusdem orta ex illius Densitate et Magnitudine conjunctim. Corpus omne, quod ex partibus ejusdem conditionis constat, dicitur homogeneum; si ex diversis, heterogeneum.

I have provided one demo using style.clipPath. style.clipPath is a very powerful tool. However, I usually prefer to work with a mask instead of a clipPath. I get a superset of the abilities with a lot less drama.

I have provided two demos using style.maskImage. The first is a pointer to a live <mask> in a live <svg>. This gives incredible control. The second is a data-url. You can create the data-url in advance and use it without any JavaScript. You can use a variety of style.mask-* properties to customize this mask.

A bright and colorful photo including the colors of sunset.  It looks slightly unreal because I used “Night Sight” mode on my Android camera. A bright and colorful photo including the colors of sunset.  It looks slightly unreal because I used “Night Sight” mode on my Android camera. A bright and colorful photo including the colors of sunset.  It looks slightly unreal because I used “Night Sight” mode on my Android camera.
Path length = 15.302271842956543. Bounding box = {top: -1.0035405158996582, left: -1.0006287097930908, height: 2.0070810317993164, width: 2.0012574195861816}
<path d="M 1,0 Q 1,0.3128693 0.58778525,0.58778525 Q 0.21224744,0.8381438 -0.30901699,0.95105652 Q -0.79360449,1.0560245 -0.95105652,0.95105652 Q -1.0980894,0.8530346 -0.80901699,0.58778525 Q -0.61596605,0.41064403 -0.00000000000000024492936,0.00000000000000012246468 Q 0.61596605,-0.41064403 0.80901699,-0.58778525 Q 1.0980894,-0.8530346 0.95105652,-0.95105652 Q 0.79360449,-1.0560245 0.30901699,-0.95105652 Q -0.21224744,-0.8381438 -0.58778525,-0.58778525 Q -1,-0.31297542 -1,-0.00000000000000024492936 Q -1,0.31297542 -0.58778525,0.58778525 Q -0.21224744,0.8381438 0.30901699,0.95105652 Q 0.79360449,1.0560245 0.95105652,0.95105652 Q 1.0980894,0.8530346 0.80901699,0.58778525 Q 0.61596605,0.41064403 0.0000000000000006123234,0.00000000000000036739404 Q -0.61596605,-0.41064403 -0.80901699,-0.58778525 Q -1.0980894,-0.8530346 -0.95105652,-0.95105652 Q -0.79360449,-1.0560245 -0.30901699,-0.95105652 Q 0.21224744,-0.8381438 0.58778525,-0.58778525 Q 1,-0.31297542 1,0 Z"></path>

More Information

Desmos can draw your equations very well. But what if you want to display your results somewhere else? A path can be used in so many places so you can integrate your results with a bigger project. This code's been around for a while, but I just built this user interface to let you poke around without any serious programming.

This video will show you how to use this page, and it will discuss the origins of this page and this project. You can find general information about this project here.

Syntax and Other Picky Details

You are creating a parametric function for use with PathShape.parametric(). You can call that code directly from a JavaScript project. Or you can enter a simple parametric function directly into the big text area at the top of this page then hit the “Go” button.

Notice the blue, code style text above and below the text area. This program automatically writes some of the code for you. You have no control over the input arguments. The body of your function always starts with "use strict"; and ends with return { x, y };. You just type the unique part of your code in the middle.

Your function will take in a value from 0 to 1 in a parameter named t. Your function needs to return an object of the form {x: 4, y: 5.01}. Most of the examples create x and y as constants or variables, and use the implicit return { x, y }; at the bottom of the function to return the result. However, an explicit return is also allowed.

The range of the output doesn't matter. This web page will automatically scale and pan your path to fit. It will preserve the aspect ratio.

Your function will take in a second parameter named support. You can call support.input(n) to read a value from one of the sliders on this page. 0 for the first slider, 1 for the second, etc. The value will always come back in the range 0-1.

Examples

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;
const angle = t * 2 * Math.PI;
const x = width * Math.cos(angle);
const y = height * Math.sin(angle);
Square
// Square from Polar Form
const θ = t * Math.PI * 2;
const r = 1/(Math.abs(Math.cos(θ) - Math.sin(θ)) + Math.abs(Math.cos(θ) + Math.sin(θ)));
const x = r * Math.cos(θ);
const y = r * Math.sin(θ);
Cusps
// This pushes the limits of my graphing software.
// This software is aimed at smooth curves.

// Once around the circle.
const θ = t * Math.PI * 2;

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

// 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);

const r = 2 - amplitude * Math.abs(Math.sin(t * Math.PI * cuspCount));
const x = r * Math.cos(θ);
const y = r * Math.sin(θ);
Circle with Wavy Edge
// Make sure you use enough segments.
// This includes a lot of inflection points, which means you need a lot of segments.
const height = 1;
const width = height;
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;
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
const angle = t * 2 * Math.PI;
const x = a * Math.sin(freqX * angle + phase);
const y = b * Math.sin(freqY * angle);

// This works well with my approximations.
// There are only two inflection points and they are both in regions where the path is almost linear.
Hypocycloid / Astroid
const R = 1; // Radius of the large circle
const r = R / 4; // Radius of the small circle (astroid case)
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);

// The sharp corners in this curve push my model to its limits.
// However, it does a decent job as long as you use enough segments.
Bell Curve
// Number of standard deviations in each direction:
const right = support.input(0) * 5;
const left = - right;
const width = right - left;
const x = t * width + left;
const height = support.input(1) * 4 + 1;
// 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);
Spirograph Curve (⟟)
// Spirograph Curve (⟟) - A general Spirograph pattern with adjustable parameters
// Sliders: rolling circle radius (⟟), pen distance (⟠), number of turns (⟡)
const R = 1.0; // Fixed circle radius
const r = support.input(0) * 2 - 1; // Rolling circle radius: -1 to 1 (⟟). Negative for inside, positive for outside
const d = support.input(1) * 2; // Pen distance from rolling circle center: 0 to 2 (⟠)
const numTurns = support.input(2) * 10; // Number of turns: 0 to 10 (⟡)
const angle = t * 2 * Math.PI * numTurns;

// Determine if rolling inside (hypotrochoid) or outside (epitrochoid)
const k = r < 0 ? (R - r) / r : (R + r) / r; // Frequency ratio
const baseRadius = r < 0 ? (R - r) : (R + r); // Base radius for the rolling circle's center

// Parametric equations
const x = baseRadius * Math.cos(angle) + (r < 0 ? d : -d) * Math.cos(k * angle);
const y = baseRadius * Math.sin(angle) - (r < 0 ? d : -d) * Math.sin(k * angle);
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
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);
Heart Curve ♡
const scale = 1;
const angle = t * 2 * Math.PI;
const x = scale * (16 * Math.pow(Math.sin(angle), 3));
const algebraClassY = scale * (13 * Math.cos(angle) - 5 * Math.cos(2 * angle) - 2 * Math.cos(3 * angle) - Math.cos(4 * angle));
const y = - algebraClassY;
Butterfly Curve
const scale = 0.2;
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));

// This will require a lot of segments to display correctly.
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
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);

// 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 π)
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);

// 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
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);

// 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.
Cardioid with Nodal Loops (क⋏)
// Cardioid with Nodal Loops (क⋏) - A heart-shaped curve with adjustable loops
// Slider adjusts the number of nodal loops (⋰)
const r = 0.5; // Radius of the base circles for the cardioid
const nodalFreq = Math.round(support.input(0) * 10); // Frequency of nodal loops (⋰). First slider: 0 to 10
const nodalAmp = 0.1; // Amplitude of the nodal loops
const angle = t * 2 * Math.PI; // Full circle

// Base cardioid: point on a circle rolling around another circle
const xCardioid = r * (2 * Math.cos(angle) - Math.cos(2 * angle));
const yCardioid = r * (2 * Math.sin(angle) - Math.sin(2 * angle));

// Add nodal loops along the curve
const nodalOffset = nodalAmp * Math.sin(nodalFreq * angle);
const x = xCardioid + nodalOffset * Math.cos(angle);
const y = yCardioid + nodalOffset * Math.sin(angle);
Lissajous Śpiral (श)
// Lissajous Śpiral (श) - A spiraling Lissajous curve with adjustable frequency
// Slider adjusts the frequency ratio (⟐)
const scale = 1.0; // Base scale of the curve
const freqRatio = 1 + support.input(0) * 4; // Frequency ratio x:y (⟐). First slider: 1 to 5
const spiralFactor = t; // Linearly increasing amplitude for spiral effect
const angle = t * 2 * Math.PI; // Full circle

// Lissajous curve with spiraling amplitude
const x = scale * spiralFactor * Math.cos(angle);
const y = scale * spiralFactor * Math.sin(freqRatio * angle);
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 issues.
// See "A Better Square" for my second attempt, which worked much better.

const numberOfCircles = 1 + 19 * support.input(0);
const circlesToConsider = Math.ceil(numberOfCircles);
const attenuation = numberOfCircles - Math.floor(numberOfCircles);
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);
}
A Better Square
// Draw a series of approximations of a square.
// Each is created from looking at the first n items in the complex Fourier series for a square.
// The demo interpolates when n is not an integer.
//
// I computed this list using the “Square with Easing” example at
// https://tradeideasphilip.github.io/random-svg-tests/complex-fourier-series.html
const circles = [
{
"frequency": 1,
"amplitude": 0.6002108774487057,
"phase": -2.356194490192345
},
{
"frequency": -3,
"amplitude": 0.12004217545570839,
"phase": -2.356194490192345
},
{
"frequency": 5,
"amplitude": 0.017148882159337513,
"phase": 0.7853981633974483
},
{
"frequency": -7,
"amplitude": 0.0057162939963831035,
"phase": -2.356194490192344
},
{
"frequency": 9,
"amplitude": 0.0025983153910070288,
"phase": 0.7853981633974468
},
{
"frequency": -11,
"amplitude": 0.0013990928373741655,
"phase": -2.356194490192343
},
{
"frequency": 13,
"amplitude": 0.0008394556343162563,
"phase": 0.7853981633974426
},
{
"frequency": -15,
"amplitude": 0.000543177105018045,
"phase": -2.3561944901923386
},
{
"frequency": 17,
"amplitude": 0.00037164742117819677,
"phase": 0.7853981633974505
},
{
"frequency": -19,
"amplitude": 0.0002654623706666915,
"phase": -2.3561944901923475
}
];

const numberOfCircles = 1 + (circles.length-1) * support.input(0);
const circlesToConsider = Math.ceil(numberOfCircles);
const attenuation = numberOfCircles - Math.floor(numberOfCircles);
let x = 0;
let y = 0;
for (let k = 0; k < circlesToConsider; k++) {
const { frequency, amplitude, phase } = circles[k];
const angle = 2 * Math.PI * frequency * t + phase;
const factor = (k === circlesToConsider - 1 && attenuation > 0) ? attenuation : 1;
x += factor * amplitude * Math.cos(angle);
y += factor * amplitude * Math.sin(angle);
}
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);
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;