Making a Cyma Curve Generator

An interface for building a Cyma curve and exporting it to SVG or Figma.

The generator is embedded below, or you can open it in a new window.

launch Cyma curve generatorin a new window

What is a Cyma curve?

A Cyma curve is a specific type of ogee (or S shape) curve commonly found in architecture and woodworking. It is constructed from two circular arcs—one convex, and one concave—joined seamlessly at an inflection point.

The name “Cyma” comes from the Greek word κύμα, meaning “wave”.

Why make a Cyma curve generator?

While it’s not prohibitively complicated to make a Cyma curve in design tools like Figma and Adobe Illustrator, it still takes several steps. And once the curve is created and joined into a single path, fine-tuning either of the arcs becomes cumbersome.

I wanted a tool with sliders that could let designers quickly explore variations of the curve, with an easy export to SVG or a Figma-pastable clipboard string.

How does it work?

Under the hood, the tool is SVG and JavaScript.

SVG path commands

SVG is a human-readable XML-based language for composing vector graphics using a coordinate system. Curves can be defined with a <path d=""> element, where the d attribute is a path command string.

SVG’s path command syntax has a built-in elliptical arc curve command made up of seven components (rx, ry, angle, large-arc-flag, sweep-flag, x, y).

  • rx and ry define the radii of the ellipse (since Cyma curves deal with circular arcs, these will always be identical)
  • angle is the rotation of the ellipse
  • large-arc-flag and sweep-flag are either 1 or 0, and together define which of the four possible arc segments of the ellipse are used (finding the right values took trial and error)
  • x and y define the starting point for the next path command in the string

So the process for creating a Cyma curve is compose a path command string with two compatible elliptical arc commands. The tool starts with an SVG in place featuring one such path command string:

svg#svg(viewBox='0 0 1000 1000', xmlns='http://www.w3.org/2000/svg')
  path#cyma(stroke='black', fill='none', stroke-width='10', d='M 500 0 A 500 500 0 0 1 500 500 A 500 500 0 0 0 500 1000')

Note: This is written in Pug syntax, which compiles into XML-based languages like SVG.

Constructing Paths with JavaScript

The tool uses dat.gui to create two variable input controls called sliceAngle and circleRatio.

  • sliceAngle defines how deep or shallow the scalloping of the circular arcs is
  • circleRatio controls the scale difference between the two circular arcs’ radii

These are the only two inputs necessary to define a Cyma curve. To constrain the size of the radii, a max constant is also defined.

The only really tricky bit was figuring out the math for the path commands for the first arc, specifically the radius. It needed to start where the first arc ended, while respecting the angle at which the path was moving (to get a smooth inflection point), while scaling apprioriately to the circleRatio proportions, while terminating at the right position on the canvas. Additionally, the input value of the ratio variable needed to be log-scaled to make it feel smooth.

It ended up looking something like this:

r A = - ( 1 - ( θ slice - 1 ) 2 ) ( max - l A 2 ) 2 + max

In JavaScript, the full draw function incorporating the formula above looks like the below. It takes the variables input by the user, and calculates and writes the new path command string to the SVG on-screen.

const cyma = document.getElementById('cyma')

// redraw svg on variable change
const draw = () => {
  // largest possible circle radius
  const max = 3000
  // get length of first segment as a fraction of the total curve length
  let lengthA = 1000 * variables.circleRatio
  // get length of second remaining segment
  let lengthB = 1000 - lengthA
  // ellipse formula to log-scale the ratio and get radius of first curve
  let radiusA = -1 * Math.sqrt((1 - Math.pow(variables.sliceAngle - 1, 2)) * Math.pow(max - (lengthA / 2), 2)) + max
  // get radius of second curve proportionally
  let radiusB = (lengthB / lengthA) * radiusA
  // write svg path data using the arc directive
  let path = `
    M 500 0
    A ${radiusA} ${radiusA} 0 0 1 500 ${lengthA}
    A ${radiusB} ${radiusB} 0 0 0 500 1000`
  // apply data to svg path (d) attribute
  cyma.setAttribute('d', path)
}

Additionally, there are buttons to either download the current curve as an SVG file, or to copy the SVG code to the clipboard in a format that can be easily pasted into Figma.

Try it out

Play with the tool below.

Embedded Cyma Curve Generator Tool