s.times(25, () => {
  s.strokedPath((attr) =>
    attr.fill(
      s.sample([0, 20, 40]), 90, 50, 0.2)
  )
    .moveTo(s.randomPoint())
    .arcTo(s.randomPoint())
})

Solandra-SVG

A little, zero dependency, library for drawing in SVG, with a fluent TypeScript (many things use chained methods). This style is a little unconventional but has some interesting benefits with fewer imports or things you have to remember.

There are four key parts

SolandraSvg class

This is the main class. It is the thing that creates SVGs, but it also offers many convenient functions and handles pseudo-randomness. In most examples an instance of this is accessed via s.

Path class

The main thing that allows you to assemble drawings. Get started with s.path or another more specific call.

Attributes class and Transform class

These set up things like strokes and fills and transforms. You can create a new instance of each with s.T or s.A and start using chained calls to configure.

Why this library?

Because you want to create SVGs with TypeScript.

I initially made this to create things with an AxiDraw plotter. My first generated drawings for a 2D plotter. And my Second collection. And a Third collection. And a Fourth collection. I've been using it more recently for creating cut and fold scupltures/pop ups with a Cricut machine.

Try it

Try out

A ready to play with CodeSandbox.

GitHub

Full soure code for the library and this site

Install
npm install solandra-svg
pnpm install solandra-svg
yarn add solandra-svg

New

Support for OkLCH colours.

Easy Regular Polygons.

Tiling

Quickly create graphics that tile the canvas without having to worry about all the low lever details.

s.forTiling(
  { n: 5, type: "square", margin: 0.1 },
  ([x, y], [dX]) => {
    const path = s
      .strokedPath((attr) =>
        attr.stroke(355, 10, 10, 0.9).fill(340, 90, 70, 0.2)
      )
      .moveTo([0.5, 0.5])

    s.times(10, () => {
      const pt = s.randomPoint()
      path.lineTo([x + pt[0] * dX, y + pt[1] * dX])
    })
  }
)

Hello Curves

Curves are easy and fun to draw with an API from Solandra that actually makes sense.

s.times(15, () => {
  let start = [s.random(), bottom] as Point2D
  let end = [s.random(), bottom] as Point2D
  s.strokedPath((attr) => attr.stroke(20, 90, 60, 0.5))
    .moveTo(start)
    .curveTo(end, { curveSize: 3 })

  start = [s.random(), 0] as Point2D
  end = [s.random(), 0] as Point2D
  s.strokedPath((attr) => attr.stroke(0, 90, 60, 0.5))
    .moveTo(start)
    .curveTo(end, { polarlity: -1, curveSize: 3 })
})

Hello Rectangles

Rectangles are easy to draw and the framework takes care of alignment.

s.times(25, () => {
  s.strokedPath((attr) => attr.fill(220, 90, 50, 0.2)).rect(
    s.randomPoint(),
    s.gaussian({ sd: 0.05, mean: 0.2 }),
    s.gaussian({ sd: 0.1, mean: 0.3 })
  )
})

Hello Ellipses

Ellipses are easy to draw and the framework takes care of alignment.

s.times(35, () => {
  const size = s.gaussian({ sd: 0.2, mean: 0.25 })
  s.strokedPath((attr) =>
    attr.fill(s.sample([130, 200, 210]), 90, 40, 0.2)
  ).ellipse(s.randomPoint(), size, size / 1.25)
})

Hello Chaiken

An elegant algorithm for smooth a path of lines. Repeatedly cut the corners. In solandra-svg this is only applied to lines (as it doesn't make sense for other path segments).

const { bottom } = s.meta
s.times(4, (n) => {
  const path = s
    .strokedPath((attr) =>
      attr.strokeOpacity(0.2 + n * 0.1).stroke(15, 90, 60)
    )
    .moveTo([0.1, bottom * 0.4])
  for (let i = 0.1; i <= 0.9; i += 0.2) {
    path.lineTo([i, bottom * 0.4 + 0.3 * Math.cos(i * 10)])
  }

  path
    .map((el) => {
      if (el.kind === "line" || el.kind === "move") {
        return { ...el, to: v.add(el.to, [0, 0.1 * n]) }
      } else {
        return el
      }
    })
    .chaiken(n + 1)
})

Hello Transforms

s.strokedPath((attr) =>
  attr
    .fill(210, 90, 20, 0.5)
    .transform(s.T.rotate(Math.PI / 8))
).rect([0.3, 0.3], 0.2, 0.3)

Hello Clone

const path = s.strokedPath().ellipse([0, 0], 0.3, 0.4)

s.times(20, (n) => {
  s.clonePath(path).configureAttributes((attr) =>
    attr
      .transform(s.T.scale(n / 2, n / 2))
      .stroke(n * 5, 90, 40)
  )
})

Hello Groups

solandra-svg offers a closure based API for building svg groups. You use the same fluent Attributes api to set up their attributes.

const { center } = s.meta
s.times(8, (n) => {
  s.group(
    Attributes.stroked.transform(
      s.T.translate(center).scale((4 + n) / 14)
    ),
    () => {
      s.path(s.A.opacity((8 - n) / 10)).rect([0, 0], 1, 1)

      s.path(
        s.A.stroke(n * 4, 90, 50).transform(s.T.rotate(n))
      ).ellipse([0, 0], 1, 0.8)
    }
  )
})

Solandra SVG is made by James Porter. The full code for all these examples is available on GitHub.