Blog

Inside the Minds of the Machine

APIs, Talking Tech

Working with SVG and navigating common obstacles.

Using JavaScript and jQuery, I recently built an interactive online coloring page to promote an episode of PBS’ Secrets of the Dead that focused on Van Gogh. The page allowed users to color in a stylized sketch of the Van Gogh’s famous painting, “Bedroom in Arles.” It was clear that an SVG for the stylized sketch would be the best approach to building this, since it would guarantee that the image would be scalable, and would have built-in support for filling in complex shapes and areas (the hard way to do this would be to manually map the coordinates of each shape in the image, which is timely, increases load time, and isn’t as precise). Although SVG allows you to relatively easily create interactive, complex images, it is not without its set of challenges. I’ll detail a few issues I ran into while working on this project and offer solutions for navigating around them.

 

SVG, or scalable vector graphics, is a well-supported, XML-based vector image format for graphics, meaning it can easily scale up or down without any loss in resolution. At the heart of SVG is the <path> element which holds all the information needed to create a shape, which then helps compose an image. The “d” attribute of a path contains a series of letters and numbers that serve as “instructions” for drawing a line. For example, you might see an “M”,”L”, “C”, “A” or “Z” followed by coordinates signifying the starting and ending point for the path. These letters signify “Moveto,” “Lineto”, “Curveto” “Arcto,” and “Close”, respectively (capital letters indicate absolute coordinates, lowercase letters indicate relative coordinates). A “v” (or “V”) followed by a number indicates a vertical line to that y-axis coordinate, and an “h” (or “H”) followed by a number indicates a horizontal line to that x-axis coordinate.

For a thorough exploration of SVG d attributes and how paths are drawn from coordinates, take a look at this article.

Oftentimes, graphic applications (like Illustrator) will export an SVG image in a format that is not web compatible, or may not behave as you expected. I ran into this issue when certain concentric areas inherited a fill color they weren’t meant to (i.e. the painting inheriting the color of the wall on which it was set.) To fix this, some of the paths needed to be adjusted manually, by tweaking the coordinates listed in the paths’ d attributes (on a side note, as I was editing the paths, I noticed there was a lot of extraneous information that Illustrator had exported to the d attribute:

For example, when I started out, the back wall in the painting looked like this, filling in the mirror, window, tabletop, and painting with it:

ss12000px

The path for this back wall was:

<path class='st1' d='M563,17.2L517-3H112h-3.7c1.3-1,1,5.6,3.3,11.6s1.7,15.5,1.7,18.5S112.6,41.7,113,45c0.3,3.3,4,24,3.7,29.6 c-0.3,5.7-0.7,18.3-1.7,22c-1,3.7,1,12,1,15.3s-0.3,8,0.3,13.7c0.7,5.7,1.3,16,1,22s0.7,34.8-1.8,50.3s6.5,116.5,6,128 s2,45,0,53.7c-0.8,3.3,4,3,27.2-5.9c30-11.5,104.8-7.8,148.8-7.8c44.5,0,276.4-13.4,276.4-13.4L563,17.2z'/>

 

The path for the mirror to the left of the window was:

<path class='st0' d='M126.3,144.2c0.4,1.3,0.7,2.6,0.9,3.7c0.1,0.5,0.1,1.1,0.1,1.2c0,0,0,0,0.1-0.1c0-0.1,0.1-0.1,0.1-0.2 c0,0,0.1,0,0.1-0.1c-0.1,0.1,0.2-0.2-0.2,0.2l0,0l0,0l0,0l-0.1,0.1c-0.1,0.1-0.2,0.2-0.2,0.3c-0.1,0.1-0.1,0.3-0.2,0.4l-0.1,0.2 c-0.1,0.3-0.2,0.5-0.2,0.8c-0.2,0.9-0.2,1.7-0.2,2.5s0.1,1.5,0.2,2.2c0.2,1.4,0.5,2.8,1,4.2c0.5,1.4,1,2.8,2,4.2 c0.3,0.4,0.6,0.7,1.1,1c0.2,0.2,0.5,0.3,0.8,0.4c0.3,0.1,0.8,0.2,0.9,0.2c0.6,0.1,1.5,0.1,2.2,0.1c2.8,0,5.4-0.1,8.1-0.2 c5.4-0.2,10.8-0.5,16.1-0.7h2c0.7,0,1.4-0.1,2.1-0.1c1.4-0.1,2.7-0.3,4-0.4c2.7-0.3,5.3-0.7,8-1.1h-0.1c2.3,0,4.6-0.1,6.9-0.1 c1.2,0,2.3-0.1,3.5-0.2c0.6,0,1.2-0.3,1.7-0.7s0.8-0.9,1.1-1.4l0,0l0,0c0.2-1.6,0.4-3.2,0.5-4.9c0.1-1.6,0.3-3.3,0-4.9 c-0.5-3.2-0.8-6.5-1.1-9.8v0.1l-0.4-1.4l-1.1-3.7v-0.1v-0.1c0.2-2.4,0.3-4.8,0.4-7.2c0-2.4,0.1-4.8-0.4-7.2 c-0.6-4.9-0.8-9.7-1.1-14.6c-0.3-4.8-0.5-9.7-0.8-14.5c-0.1-2.4-0.2-4.8-0.2-7.3c0-0.6,0-1.3,0.1-1.8c0-0.6,0-1.2,0-1.8 c0-1.2-0.1-2.4-0.4-3.5c-0.1-0.5-0.3-0.9-0.6-1.2c-0.2-0.2-0.6-0.2-1-0.1c-0.9,0.2-2,0.6-3.2,0.6c-2.1,0-4.2-0.3-6.3-0.5l0,0 h-9.5h-0.2l-0.1-0.2c-0.9-1.2-2.4-2.1-4-2.2c-0.8-0.1-1.6,0.1-2.4,0.3c-0.8,0.2-1.6,0.4-2.4,0.6c-1.6,0.4-3.2,0.9-4.9,1.1 c-0.8,0.1-1.7,0.1-2.5,0.1s-1.7-0.1-2.5-0.1c-3.3,0-6.7-0.1-10,0.4c-1.6,0.2-3.3,0.5-4.9,1c-0.8,0.2-1.6,0.5-2.3,0.9 c-0.7,0.4-1.4,1-1.1,1.8c0,0-0.2-0.3,0-0.8s0.9-1.1,1.8-1.5c2-0.9,5.1-1.7,8.8-2.2c0.5-0.1,0.9-0.1,1.5-0.2c0.5,0,1-0.1,1.5-0.1 c1-0.1,2.1-0.1,3.1-0.2c1.1,0,2.2-0.1,3.3-0.1c1.2,0,2.2,0,3.3-0.2c2.2-0.4,4.5-1.3,6.9-2c0.3-0.1,0.6-0.2,0.9-0.2 c0.4-0.1,0.7-0.2,1.1-0.2c0.8-0.1,1.5,0,2.3,0.2s1.5,0.5,2.2,0.9c0.3,0.2,0.6,0.5,0.9,0.7c0.3,0.3,0.5,0.5,0.9,0.9l-1.3-0.7 c3.2-0.1,6.4-0.3,9.5-0.4l0,0h0.2c1.9,0.1,3.8,0.2,5.5,0.2h0.4h0.1l0,0h0.1c0.1,0,0.3,0,0.4-0.1c0.3-0.1,0.6-0.2,1.1-0.3 c0.4-0.1,0.9-0.3,1.8-0.4c0.4,0,1.1,0,1.7,0.2c0.3,0.1,0.6,0.3,0.9,0.5l0.2,0.2l0.1,0.1l0.1,0.1l0.1,0.1l0.1,0.2l0,0l0,0v0.1 l0.2,0.3c0.1,0.2,0.2,0.4,0.3,0.5c0.2,0.3,0.3,0.6,0.3,0.9c0.2,0.6,0.3,1.1,0.4,1.6c0.2,1,0.2,1.9,0.3,2.8c0,0.9,0,1.8,0,2.6V84 v0.1l0,0v0.1v0.2v0.5c0,0.4,0,0.7,0,1.1c0.3,5.7,0.7,9.9,0.7,11.4c0,1,0.1,3,0.2,5.6c0.1,2.6,0.2,5.8,0.4,9.4 c0.1,1.8,0.2,3.6,0.3,5.5c0.1,0.9,0.1,1.9,0.2,2.7c0,0.2,0.1,0.4,0.1,0.7l0.1,0.8c0.1,0.3,0.1,0.6,0.1,0.9s0,0.6,0,0.8 c0.1,4.3-0.2,8.2-0.5,12.1l-0.1-0.8c0.5,1.9,1,3.5,1.4,5.1l0.1,0.3v0.2c0.1,2.7,0.3,5.2,0.5,7.4c0,0.5,0.1,1.1,0.1,1.6 s0.1,0.9,0.2,1.6c0,1.2-0.1,2.1-0.1,3c-0.2,1.7-0.3,3-0.5,3.9c-0.1,0.9-0.2,1.4-0.2,1.4l-0.2,1.2v0.3l-0.1,0.2 c-0.2,0.5-0.4,0.8-0.7,1.1s-0.5,0.6-0.9,0.9c-0.3,0.3-0.8,0.6-1.2,0.8c-0.2,0.1-0.5,0.2-0.9,0.3h-0.2h-0.2h-0.1 c-0.7,0.1-1.3,0.1-1.9,0.1h-1.8c-2.3,0-4.6,0-7-0.1h0.3c-2.7,0.4-5.4,0.7-8.1,1c-1.4,0.1-2.7,0.3-4.1,0.4 c-0.7,0-1.4,0.1-2.2,0.1h-2c-5.3,0-10.7,0.2-16.1,0.3c-2.7,0-5.4,0.1-8.2,0c-0.7,0-1.7-0.1-2.2-0.2h-0.1h-0.1h-0.2 c-0.2,0-0.4-0.1-0.6-0.2c-0.3-0.1-0.7-0.3-0.9-0.5c-0.5-0.4-0.9-0.8-1.2-1.2c-1.1-1.5-1.6-2.9-2-4.4c-0.4-1.4-0.7-2.8-0.9-4.3 c-0.1-0.7-0.1-1.5-0.2-2.2c0-0.8,0-1.6,0.2-2.5c0.1-0.3,0.1-0.5,0.3-0.8l0.1-0.2c0.1-0.1,0.1-0.3,0.2-0.4 c0.1-0.1,0.2-0.2,0.2-0.4l0.1-0.1l0,0l0,0l0,0c0.4-0.4,0.1-0.1,0.2-0.2c0,0-0.1,0-0.1,0.1c-0.1,0.1-0.1,0.1-0.1,0.2 c0,0.1-0.1,0.1-0.1,0.1c0-0.1,0-0.7-0.1-1.2C127,146.8,126.6,145.5,126.3,144.2z'/>

Which is a lot of information for such a small area! As you can see, this path ends where it begins (126.3), but contains a lot of information in between. It turns out that this path contained extraneous information that caused it to double back on itself and violate the non-zero winding rule (see below). In turn, this causes it to inherit a fill from its parent. Starting from the end of the “d” attribute, I shortened the path coordinates little by little, removing the extra bits of the path to:

<path class='st0' d='M126.3,144.2c0.4,1.3,0.7,2.6,0.9,3.7c0.1,0.5,0.1,1.1,0.1,1.2c0,0,0,0,0.1-0.1c0-0.1,0.1-0.1,0.1-0.2 c0,0,0.1,0,0.1-0.1c-0.1,0.1,0.2-0.2-0.2,0.2l0,0l0,0l0,0l-0.1,0.1c-0.1,0.1-0.2,0.2-0.2,0.3c-0.1,0.1-0.1,0.3-0.2,0.4l-0.1,0.2 c-0.1,0.3-0.2,0.5-0.2,0.8c-0.2,0.9-0.2,1.7-0.2,2.5s0.1,1.5,0.2,2.2c0.2,1.4,0.5,2.8,1,4.2c0.5,1.4,1,2.8,2,4.2 c0.3,0.4,0.6,0.7,1.1,1c0.2,0.2,0.5,0.3,0.8,0.4c0.3,0.1,0.8,0.2,0.9,0.2c0.6,0.1,1.5,0.1,2.2,0.1c2.8,0,5.4-0.1,8.1-0.2 c5.4-0.2,10.8-0.5,16.1-0.7h2c0.7,0,1.4-0.1,2.1-0.1c1.4-0.1,2.7-0.3,4-0.4c2.7-0.3,5.3-0.7,8-1.1h-0.1c2.3,0,4.6-0.1,6.9-0.1 c1.2,0,2.3-0.1,3.5-0.2c0.6,0,1.2-0.3,1.7-0.7s0.8-0.9,1.1-1.4l0,0l0,0c0.2-1.6,0.4-3.2,0.5-4.9c0.1-1.6,0.3-3.3,0-4.9 c-0.5-3.2-0.8-6.5-1.1-9.8v0.1l-0.4-1.4l-1.1-3.7v-0.1v-0.1c0.2-2.4,0.3-4.8,0.4-7.2c0-2.4,0.1-4.8-0.4-7.2 c-0.6-4.9-0.8-9.7-1.1-14.6c-0.3-4.8-0.5-9.7-0.8-14.5c-0.1-2.4-0.2-4.8-0.2-7.3c0-0.6,0-1.3,0.1-1.8c0-0.6,0-1.2,0-1.8 c0-1.2-0.1-2.4-0.4-3.5c-0.1-0.5-0.3-0.9-0.6-1.2c-0.2-0.2-0.6-0.2-1-0.1c-0.9,0.2-2,0.6-3.2,0.6c-2.1,0-4.2-0.3-6.3-0.5l0,0 h-9.5h-0.2l-0.1-0.2c-0.9-1.2-2.4-2.1-4-2.2c-0.8-0.1-1.6,0.1-2.4,0.3c-0.8,0.2-1.6,0.4-2.4,0.6c-1.6,0.4-3.2,0.9-4.9,1.1 c-0.8,0.1-1.7,0.1-2.5,0.1s-1.7-0.1-2.5-0.1c-3.3,0-6.7-0.1-10,0.4c-1.6,0.2-3.3c'/>

Which allowed me to fill in the mirror frame separately:

ss22000px

However, as you can see, the same problem remains for the glass in the mirror. Again, because the path doubles back on itself and violates the non-zero winding rule, The path for the mirror’s glass was:

<path class='st0" d="M136.9,135.1c0.4,2.7,0.9,5.4,1.2,8c0.1,0.7,0.1,1.3,0.2,1.9c0,0.5-0.1,1.2-0.2,1.8c-0.2,1.3-0.5,2.6-0.7,4 c-0.1,0.7-0.2,1.4-0.3,2.1c-0.1,0.7-0.2,1.4-0.2,2.3c0,0.2,0,0.4,0.1,0.8c0,0.1,0.1,0.3,0.2,0.5c0.2,0.3,0.2,0.5,0.9,0.8 c0.2,0,0.3,0.1,0.6,0.1h0.4h0.3c0.7,0,1.4,0,2.1,0c2.8-0.1,5.5-0.3,8.2-0.5l16.3-1.7c2.7-0.2,5.4-0.3,8.2-0.7 c0.7-0.1,1.4-0.2,2-0.4c0.3-0.1,0.7-0.2,1-0.3c0.2-0.1,0.3-0.1,0.5-0.3c0.1-0.2,0.2-0.3,0.3-0.4c0.6-1.2,1.1-2.5,1.5-3.8l0,0 L179,136c0,0-0.1-12.3-0.3-24.6c0-1.5-0.1-3.1-0.1-4.6c0-0.7,0-1.5-0.1-2.2c-0.1-0.8-0.1-1.6-0.2-2.3c-0.1-2.9-0.1-5.6-0.2-7.9 c-0.1-2.3-0.1-4.2-0.2-5.5c-0.1-1.3-0.2-2.1-0.2-2.1c0-0.4-0.1-0.7-0.1-1.1c-0.1-0.4-0.1-0.6-0.4-0.9c-0.4-0.5-1-0.9-1.6-1.1 c-1.3-0.5-2.8-0.4-4.3-0.2c-2.9,0.4-5.9,0.9-8.9,1.2c-1.5,0.1-3,0.2-4.5,0.1c-0.8-0.1-1.5-0.3-2.2-0.4s-1.5-0.2-2.2-0.3 c-3-0.4-5.9-0.6-8.9-0.8l0,0c-2.2,0.1-4.4,0.2-6.6,0.3c-1.1,0.1-2.2,0.2-3.3,0.4c-0.5,0.1-1.1,0.2-1.6,0.3 c-0.3,0.1-0.5,0.2-0.8,0.3c-0.2,0.1-0.4,0.3-0.3,0.4c0,0-0.1-0.1,0-0.1c0-0.1,0.2-0.3,0.4-0.4c0.6-0.3,1.5-0.5,2.6-0.7 c2.3-0.4,5.6-0.8,9.5-1.1l0,0l0,0c2.2,0,4.6,0.1,7.2,0.3c1.3,0.1,2.6,0.2,3.9,0.3c0.7,0.1,1.4,0.2,2,0.3c0.6,0.1,1.2,0.1,1.8,0 c2.5-0.1,5.2-0.6,7.8-1.1c1.3-0.3,2.6-0.5,3.9-0.8c0.4-0.1,0.7-0.1,1-0.2c0.4-0.1,0.8-0.1,1.1-0.1c0.8,0,1.5,0,2.3,0.2 s1.6,0.5,2.3,1.1c0.4,0.3,0.7,0.6,1.1,1.1L179,84l0.1,0.1c0.1,0.2,0,0.1,0.1,0.1l0,0v0.1v0.1c0.1,0.1,0.1,0.3,0.2,0.5 c0.1,0.2,0.1,0.3,0.1,0.4c0.3,1.5,0.4,2.6,0.5,3.8c0.2,2.3,0.3,4.4,0.4,6.2c0.2,3.7,0.3,6.3,0.3,7.3c0,0.3,0,0.6,0,1.1v0.1v0.2 v0.1l0,0l0,0c0,0.2-0.1-0.2,0-0.2l0,0v0.1c0,0.1,0,0.3,0,0.4s0,0.3,0,0.4s0,0.3,0,0.4c0,0.2,0,0.4,0,0.6c0,1.6,0,3.4,0,5.5 c0,4.2,0,9.4,0,14.5c0,10.3-0.1,20.6-0.1,23.2v0.3l-0.1,0.2c-0.4,1.1-0.6,1.7-0.8,2.1c-0.2,0.4-0.2,0.5-0.2,0.5 s-0.1,0.2-0.3,0.7c-0.1,0.2-0.2,0.5-0.5,0.9c-0.1,0.1-0.1,0.2-0.2,0.3l-0.1,0.1l0,0c-0.1,0.1-0.1,0.1-0.1,0.1 c-0.1,0.1-0.1,0.1-0.2,0.2c-0.5,0.4-0.8,0.5-1.1,0.6c-1.1,0.4-2.1,0.5-3.4,0.6c-1.2,0.1-2.6,0.2-4.1,0.3c-0.7,0-1.5,0.1-2.3,0.1 c-0.4,0-0.8,0-1.2,0h-0.2c0.2,0,0,0,0.1,0l0,0l0,0h-0.1h-0.3l-0.6,0.1c-1.7,0.1-3.5,0.3-5.3,0.4c-3.7,0.3-7.6,0.6-11.6,0.9 c-2,0.1-4,0.2-5.9,0.2c-1,0-2,0-3,0h-0.4c-0.1,0-0.4,0-0.6-0.1l-0.3-0.1h-0.2h-0.1l0,0l0,0c0,0-0.4-0.1-0.3-0.1 c-0.2-0.1-0.3-0.2-0.5-0.3s-0.2-0.2-0.3-0.3c-0.2-0.3-0.2-0.4-0.3-0.6c-0.1-0.2-0.1-0.3-0.1-0.4c-0.1-0.3-0.1-0.4-0.1-0.6 c0-0.3,0-0.6,0-0.9c0.2-2.1,0.6-3.8,0.9-5.5c0.2-0.8,0.3-1.6,0.5-2.4c0.1-0.4,0.1-0.7,0.2-1.1v-0.4v-0.3v-0.2 c-0.1-1.3-0.2-2.7-0.4-3.8c-0.2-1.2-0.3-2.2-0.4-3C137.1,136,136.9,135.1,136.9,135.1z'/>

And after shortening it to:

<path class='st0' d="M136.9,135.1c0.4,2.7,0.9,5.4,1.2,8c0.1,0.7,0.1,1.3,0.2,1.9c0,0.5-0.1,1.2-0.2,1.8c-0.2,1.3-0.5,2.6-0.7,4 c-0.1,0.7-0.2,1.4-0.3,2.1c-0.1,0.7-0.2,1.4-0.2,2.3c0,0.2,0,0.4,0.1,0.8c0,0.1,0.1,0.3,0.2,0.5c0.2,0.3,0.2,0.5,0.9,0.8 c0.2,0,0.3,0.1,0.6,0.1h0.4h0.3c0.7,0,1.4,0,2.1,0c2.8-0.1,5.5-0.3,8.2-0.5l16.3-1.7c2.7-0.2,5.4-0.3,8.2-0.7 c0.7-0.1,1.4-0.2,2-0.4c0.3-0.1,0.7-0.2,1-0.3c0.2-0.1,0.3-0.1,0.5-0.3c0.1-0.2,0.2-0.3,0.3-0.4c0.6-1.2,1.1-2.5,1.5-3.8l0,0 L179,136c0,0-0.1-12.3-0.3-24.6c0-1.5-0.1-3.1-0.1-4.6c0-0.7,0-1.5-0.1-2.2c-0.1-0.8-0.1-1.6-0.2-2.3c-0.1-2.9-0.1-5.6-0.2-7.9 c-0.1-2.3-0.1-4.2-0.2-5.5c-0.1-1.3-0.2-2.1-0.2-2.1c0-0.4-0.1-0.7-0.1-1.1c-0.1-0.4-0.1-0.6-0.4-0.9c-0.4-0.5-1-0.9-1.6-1.1 c-1.3-0.5-2.8-0.4-4.3-0.2c-2.9,0.4-5.9,0.9-8.9,1.2c-1.5,0.1-3,0.2-4.5,0.1c-0.8-0.1-1.5-0.3-2.2-0.4s-1.5-0.2-2.2-0.3 c-3-0.4-5.9-0.6-8.9-0.8l0,0c-2.2,0.1-4.4,0.2-6.6,0.3c-1.1,0.1-2.2,0.2-3.3,0.4c-0.5,0.1-1.1,0.2-1.6,0.3z'/>

I was able to color in the glass:

ss32000px

As I hinted at above, the direction in which the path has been drawn in a graphic application determines its fill properties. If you have two concentric circles, as pictured below, and they are both drawn clockwise, the inner circle will inherit the outer circle’s fill. However, if the outer circle is drawn clockwise and the inner circle is drawn counterclockwise, that inner circle’s fill will be independent of the outer one’s fill. This is attributed to the non-zero winding rule of computer graphics; in short, every line gets a total “winding number” that is calculated based on how many clockwise (-1) and counterclockwise intersections (+1) it has with a given ray in any direction. Whether the total comes to zero or not determines its fill property. For more information on the non-zero winding rule, click here. For more information on SVG fill properties, click here.

The non-zero winding rule:
nonzero2000

After fixing the paths and fill properties of the SVG, I needed to build functionality to allow users to share their finished work on social media. Sharing an image that originated as an SVG on social media can be tricky; because an SVG is really just markup, you can’t simply share it as you would an image file or image URL that’s already loaded on the page. First, the SVG needs to be converted to an actual image file. My first approach in doing this was to convert it to a base64 encoded dataURL. However, you’ll still need to host the image somewhere to serve it up to facebook or tumblr, for example. To achieve this, we used the imgur API, which, conveniently, will take a base64 encoded image, and return a valid image url that can be shared on social media. To convert an SVG to a dataURL, we used the JavaScript library svgtoDataURL, found here. This library takes an SVG, converts it to a dataURL, draws a canvas on the page, and drops the dataURL on the canvas. Then, you can post the dataURL to imgur via AJAX and get a valid image URL back (it may either be a .png or a .jpg, depending on what you specify as an argument). Note that this library requires on the browser’s native renderer for png export, which may fail. If this is the case, as it was for me in Firefox, you’ll need to require the canvg library, which is an SVG parser and renderer. Canvg is available here.

Working with SVG is a lot of fun, as it allows you to quickly and easily create visually-rich, interactive features. Although there are some common quirks to watch out for, there is an abundance of documentation and helpful libraries to guide you in your process and bring your SVG dreams to fruition!


The opinions expressed in this post represent those of the individual author, Lisa Buch, and not those of IEG or WNET.