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 };
}
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.
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.
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.
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.
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.
<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>
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.
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.
// 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 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(θ);
// 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(θ);
// 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;
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.
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.
// 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 (⟟) - 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);
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);
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;
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.
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.
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.
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 (क⋏) - 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 (श) - 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);
// 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);
}
// 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);
}
// 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;