Introduction

RoughJS is a small-ish (<9kB) JavaScript graphics library that lets you draw in a sketchy, hand-drawn-like style. It lets you draw on <canvas> and with SVG. This blog post is to address the most common issue filed with RoughJS: How does it work?

A bit of history

Charmed by images of hand-drawn graphs, diagrams, and sketches; like I true nerd, I wondered what if there were a way to draw such figures through code. Emulate hand-drawing as close as possible and yet be legible and programmable. I decided to focus on primitives - lines, polygons, ellipses, and curves, creating a full 2D graphics library. Charting/Diagramming libraries and apps could be built on top of it.

After some quick research, I came across this paper by Jo Wood and others, titled Sketchy rendering for information visualization. The techniques described here formed the basis of the library, especially for drawing lines and ellipses.

I wrote the first version in 2017 which only worked on Canvas. Once the problem was solved, I lost interest. A year later I was working a lot with SVG, and decided to adapt RoughJS to also work with SVG. I also redesigned the API to be more basic and focus on the simple vector graphic primitives. I shared the 2.0 version of Hacker News and, surprisingly, it blew up. It was the second most popular post of ShowHN in 2018.

People have since created some amazing things with RoughJS — Excalidraw, Why do Cats & Dogs..., roughViz charting library, to name a few.

Now let's get to the algorithms....

Roughness

The fundamental concept behind emulating hand-drawn shapes is randomness. When you draw anything by hand, no two shapes will be exactly the same. Nothing is exact. So, every spatial point in RoughJS is adjusted by a random offset. The amount of randomness is described by a numeric parameter called roughness.

Imagine a point A and a circle around it. A is now replaced by a random point within that circle. The area of this circle of randomness is controlled by the roughness value.

Lines

Hand drawn lines are never straight and often develop a bowing curvature (described here). We randomize the two end points of the line based on the roughness. Then we also pick two other random points around the 50% and 75% marks along the line. Connecting these points by a curve gives the bowing effect.

When drawing by hand, people sometimes go quickly back and forth on the line. This could be either to highlight the line, or just as an adjustment to the straightness of the line. It looks something like this:

To give this extra sketchy effect, RoughJS draws the line twice to make it feel more sketchy. I do plan to make this more configurable in the future.

Try sketching lines on this interactive canvas surface. Adjust the roughness to see the lines change:

When drawing by hand, longer lines tend to get less straight and more curvy. So, the randomness of the offsets to create the bowing effect are a function of the line's length and the randomness value. This, however, does not scale for really long lines. In the image below, for example, concentric squares are drawn with the same random seeds - i.e. they are essentially the same random shape, but scaled differently.