Fast interactive prototyping with Sketch and d3.js

At Snips we create big data predictive technologies to make better sense of urban phenomena and help cities scale to accommodate the challenges of the twofold urban population growth in the next 40 years.

An essential part of this job is to present the predictions gathered from our models to users in a compelling and intuitive way. Finding the good balance between information content and usability requires a lot of design iterations.

In this post, I want to share how we quickly develop interactive prototypes, by designing SVG content in graphical apps like Sketch and scripting them using d3.js. The prototypes can then be shared on the web or installed as apps on a device. This allows us to quickly get how an app will feel in the hand of users.

You can check such an example at bl.ocks.org/maelp/8344014.

A simplified interactive prototype created using Sketch for the SVG and d3.js for the interaction part. This setup allows for high-content graphics, complex user interactions modeling, and fast iteration cycles. The glow on the iPhone is gratuitous and is just here to show off.

You can download the .sketch or .svg files (iPhone template by Robbie Pearce).

bl.ocks.org/maelp/8344014

Fast prototyping

When it comes to prototyping user interaction and interfaces, one has a number of choices:

  • at one extreme, wireframes, images or slides are the fastest to develop using Photoshop, Balsamiq or Pop, but only give a limited sense of the interaction,
  • at another extreme, implementing a prototype in a full-fledged development environment (XCode) gives the most power for the interaction, but takes some dedication,
  • somewhere in-between, using Flash or Quartz composer (in particular the Origami framework developped by Facebook) allows for richer content and interaction with quick interaction cycles

This last solution looks like a good compromise. However I'm not particularly fan of the "graphic programming" way of creating Quartz animations, as I much prefer the plain-text programming tools that I master.

In this post, I want to present the approach we've taken at Snips which to us seems to hit the perfect sweet spot: designing the app as SVG and adding complex interactions with d3.js.

Here are a few benefits of this approach from my vantage point:

  • design the app with your favorite graphical tools (Sketch, Inkscape, Illustrator, ...gVim)
  • fast iterations: animating SVG elements is far faster than implementing complex UI code for Android or iOS, but allows for almost everything that you would need: bouncy motion, transparency, shadows, transforms etc.
  • share prototypes on the web or show them directly on a device (we show you how in a follow-up post!)
  • most importantly be the cool kid using SVG and d3.js

This post will very quickly get to the gist of the approach, then describe my workflow with Sketch and d3.js.

In a follow-up post to be published very soon, I will present various tricks that I use when creating more complex prototypes, and among which:

  • adding invisible objects to define transitions directly in the UI
  • create elements dynamically (think iOS table view)
  • using finite state automatons to create more complex interactions
  • viewing your prototype on a real device and adding multitouch gestures

There is a lot to cover, so BRACE yourself for a long post!

How the technique works

We will be using our prefered tools (in my case, Sketch) to design a SVG file containing all the bitmaps, text, shapes making up the app screens. In order to access those elements from d3.js, we will have to assign them ids.

To understand how the technique works, I'll start with a quick prototype using a SVG file written by hand. It comprises a phone with a masked screen (so content is clipped), and a flat bubble that pops around when poked:

Basic interactive prototype created using a simple SVG file written by hand and made interactive using d3.js. This serves as a crude demonstration of the approach we use to prototype apps (although I personally like the flat look!). You can poke the bubble, she's friendly.

bl.ocks.org/maelp/8343628

Creating the SVG with my bare little hands

In order to get how the technique works, the best is to write the SVG by hand. You can download it here. The SVG contains essentially:

  • a rounded <rect> as iPhone frame and a <circle> as home button
  • a group ScreenGoup that contains all the screen content
  • a <clipPath> to clip the app content when they are outside the screen
  • a <rect> as screen background and a <circle> as interactive button
<?xml version='1.0' encoding='utf-8'?>
<svg width='400px' height='800px' viewBox='0 0 400 800'
version='1.1' xmlns='http://www.w3.org/2000/svg'>

<defs>
    <!-- Define a clipping path the size of the screen -->
    <clipPath id='ScreenMask'>
        <rect x='0' y='0' width='360' height='580'></rect>
    </clipPath>
</defs>

<!-- iPhone frame -->
<rect id='iPhone' fill='#000000'
      x='0' y='0' width='400' height='800' rx='50' ry='50'></rect>
<circle id='HomeButton' fill='#202020'
        cx='200' cy='730' r='40'></circle>

<!-- Apply the clipping path to the screen group -->
<g id='ScreenGroup' transform='translate(20, 80)'
   clip-path='url(#ScreenMask)'>
    <!-- Screen content -->
    <rect id='ScreenBackground' fill='#2B2B2B'
          x='0' y='0' width='360' height='580'></rect>
    <circle id='AppButton' fill='#9B59B6'
            cx='180' cy='290' r='50' style='cursor:pointer;'></circle>
</g>

</svg>

Note that in a more complex app, the ScreenGroup could contain other groups, one for each application screen.

Loading the SVG with d3.js

Now that we have content, we need to display it in the browser to animate it. If we just insert <img src='iPhone.svg'></img> in the DOM, the browser won't let us access the inner content of the SVG to make them interactive. We need to load the file ourselves and inline its contents in the DOM.

Thankfully, d3.js comes packed with functionalities, and in particular loading a XML file is very easy:

d3.xml('iPhone.svg', 'image/svg+xml', function (error, data) {
    if (error) {
        console.log('Error while loading the SVG file!', error);
    }
    else {
        // data contains the SVG file contents
        // ...
    }
});

If you load the file locally and you have double-checked you did not make a typo when writing the file name, you can probably skip the error testing part, and loading the file is a one-liner. Congrats.

Inlining the SVG content in the page is then a matter of creating a new node with the content in data:

d3.select('.container')
    .node()
    .appendChild(data.documentElement);

Animating elements

Once the SVG is loaded in the page, animating elements is easy using d3.js. We start by getting a handle on the <svg> element for reference, and access the various elements using svg.select:

var svg = d3.select('.container svg');
var appButton = svg.select('#AppButton')
    .on('mouseenter', function () {
        // put on some flashy color!
        appButton.style('fill', '#AB69C6');
    });

The complete code for this basic prototype is available as a gist, but is reproduced here for the sake of completeness:

window.addEventListener('load', function () {
    d3.xml('iPhone.svg', 'image/svg+xml', function (error, data) {
        // Append the SVG node to the DOM
        d3.select('body').node().appendChild(data.documentElement);
        var svg = d3.select('svg');
        var appScreen = svg.select('#ScreenBackground');
        // Get the width and height directly from the file, so
        // you can change them on the SVG and they will automatically
        // be updated in the code
        var screenWidth = +appScreen.attr('width'),
            screenHeight = +appScreen.attr('height');
        var appButton = svg.select('#AppButton')
            .on('mouseenter', function () {
                appButton.style('fill', '#AB69C6');
            })
            .on('mouseleave', function () {
                appButton.style('fill', '#9B59B6')
            })
            .on('click', function () {
                var x = Math.random() * screenWidth;
                var y = Math.random() * screenHeight;
                appButton
                    .transition()
                    .duration(1000)
                    .ease('bounce')
                    .attr('cx', x)
                    .attr('cy', y);
            });
    });
});

My workflow with Sketch

Now that it is clear how we build SVG, inline it in the DOM, and animate it with d3.js, we can use a graphical tool to build our content and not worry about setting the position of each element by hand (what a relief!).

Prototyping an app using Sketch. The Sketch mockup is exported as a SVG file, with layer names translated to SVG ids. We load and access the elements using d3.js to make them interactive.

You can download the .sketch or .svg files (iPhone template by Robbie Pearce).

bl.ocks.org/maelp/8343628

Let me show you how I use Sketch, and a few benefits that you get by using a GUI.

Graphical elements

The most obvious benefit of using Sketch is that you get to design by clicking rather than adding <rect> tags in a file. Also, you get to benefit from all the hard work of designers who share their amazing UI (Teehan+Lax) and device (Robbie Pierce) templates!

Prototyping an app using Sketch. This enables you to leverage amazing UI templates like the one from Teehan+Lax.

If you use bitmaps, you can choose to link them in the SVG as reference, which can cause headaches when comes the time to share them on a server where the relative paths to bitmaps might change. In this case, you can choose to embed the bitmaps as base-64 encoded strings.

Another benefit of creating SVG with a graphical interface like Sketch is that it makes it ridiculously easy to add SVG effects to layers that are otherwise painful to create, like drop shadows, gradients, etc.

Don't overdo shadows and gradients, we're in 2014 man. You judge, you're the boss.

Element ids

In order to make your prototype interactive, you need to access its elements which is most easily done by id. Sketch and Illustrator let you give ids to groups and elements by naming them in the editor.

Prototyping an app using Sketch. In order to interact with elements from d3.js, you have to give them an id. This can be done by giving a unique name to each element in Sketch.

The loading indicator is now accessible from d3.js as d3.select('#LoadingIndicator'). You can then easily access the inner circle and animate them:

var loadingIndicator = loadingScreen.select('#LoadingIndicator');
var circles = loadingIndicator.selectAll('circle')
circles
    .datum(function () {
        return {'x': +d3.select(this).attr('cx')};
    })
    .sort(function (a, b) { return a.x - b.x; })
    .each(function (d, i) {
        var r = +d3.select(this).attr('r');
        d3.select(this)
            .transition()
            .delay(i*250)
            .attr('r', r/2);
    });

Element ids should be unique. If you give two elements the same name, some programs will still assign them unique identifiers by appending a suffix. If you can't select the element you want using its layer name, check the SVG file for its real id.

Structure your app using groups

Groups are very important. You will typically define the content of a screen inside a layer group (use Cmd+G to create a new group in Sketch).

This allows you later to enable or disable a whole screen, transition a screen to the left or to the right, etc. Look at the screenshot above to see how I structured each screen, and even the LoadingIndicator in different groups in order to facilitate accessing inner elements or transitioning groups as a whole.

// move the main screen to the right
// while making the menu appear from the left
mainScreen.transition()
    .duration(500)
    .attr('x', 400);
menuScreen.transition()
    .duration(500)
    .attr('x', 0);

// make the popover disappear
popOver.transition()
    .duration(1000)
    .attr('opacity', 0);

Add masks

Of course, if you lay all your layers on top of an iPhone background, you do not want your layers to bleed off the screen when you move them to the left.

Prototyping an app using Sketch. Use mask to prevent content from bleeding off the screen edge when animating it.

In order to do that, just create a rectangle the size of the screen, and make it act as a mask to clip off everything that leaves the screen by putting it under the screen content and selecting 'Edit — Use as Mask'.

Prototyping an app using Sketch. Use mask to prevent content from bleeding off the screen edge when animating it.

When you create a mask with Sketch, it adds the mask to the elements above it. A <rect> element above a masking element will become a <rect mask='url(...)'>. If you move the <rect>, the mask will "move" with it, thus not acting as a screen masking outline.

In order to define a screen mask and have screen content panes moving inside or outside of the screen and being clipped, you thus have to create a master group containing those screens. In the example above, the 'ScreenContainer' group containing all the other screen groups. Therefore, if you translate the 'MainScreen' group, it will be correctly masked.

Export as SVG

In order to export your prototype as a SVG file, go to the 'Export' tab, create a slice around your elements (a slice is automatically created around visible elements the first time), click the 'Export' button and select 'SVG' in the 'Format' dropdown.

Congratulations on the SVG

Coding simple screen transitions with d3.js

You now have created a beautiful SVG file and you can start animating it with d3.js. I will now a few simple tricks to animate screens:

  • make a simple looping animation
  • transition between screens

Please refer to the complete code available at bl.ocks.org/maelp/8343848 if you want to reproduce the complete prototype. I will only describe here the essential part of each animation.

You can download a simplified SVG of the SafeSignal prototype. It consists in the phone background, a mask around the screen, a menu, and two simple screens.

A simplified version of the SafeSignal prototype. Click on the sides of the screen to reveal the menu or slide the map.

bl.ocks.org/maelp/8343848

I list here the most important elements:

<g id='AppScreen'>
    <g id='ScreenContainer'>
        <g id='MapScreen'></g>
        <g id='MainScreen'></g>
            <rect id='MapToggle'></rect> <!-- click to toggle map -->
            <rect id='MenuToggle'></rect>
            <circle id='ConnectedCircle'></circle>
        </g>
        <g id='MenuScreen'></g>
    </g>
    <!-- here we define the mask -->
</g>
<image id='iPhone'>

The MapToggle and MenuToggle rect are transparent and serves as triggers to transition between screens. The MapScreen is shifted outside the view so it is side-by-side with the MainScreen.

Simple loop animation

A possible way to create a loop animation with d3.js is to register a function that creates a transition and registers itself to be called again at the end of the transition. This works the following way:

Creating simple loops using d3.js.

// animateGrowingCircle is given a starting radius and an ending radius, and creates a transition function that cycles between those radii and loop
function animateGrowingCircle(fromRadius, toRadius) {
    var animateFunction = function () {
        d3.select(this)
            .attr('r', fromRadius)
            .transition()
            .duration(1000)
            .attr('r', toRadius)
            .transition()
            .duration(1000)
            .attr('r', fromRadius)
            .each('end', animateFunction);
    };
    return animateFunction;
}

window.addEventListener('load', function () {
    var svg = d3.select('svg');
    var circle = svg.select('#ConnectedCircle');
    // Get the initial radius directly from the SVG
    var initRadius = +circle.attr('r');
    circle
        .each(animateGrowingCircle(initRadius, initRadius * 2.0));
});

Transitioning from the main screen to the menu screen and back

Animating screens using d3.js. We create transparent rect on each side of the main screen to handle clicks on the side. In this example they have been rendered in color to show how it works. Click on the left or right sides to show two transition effects: reveal a menu, and slide to the next page. See the code for the complete prototype at bl.ocks.org/maelp/8343848.

Different programs will yield different output for the SVG. In particular, a program might either create group elements <g> without position, and position each element inside, or create a group element <g transform='translate(x, y)'> that is translated at the right position, and define inner elements relative to the position of the group. This is what Sketch uses.

Assuming you have the following structure in your layers:

<g id='MenuScreen' transform='translate(0, 0)'></g>
<g id='MainScreen' transform='translate(0, 0)'></g>
    <rect id='MapToggle'></rect>
    <rect id='MenuToggle'></rect>
</g>
<g id='MapScreen' transform='translate(200, 0)'></g>

Revealing the menu simply consists in moving the main screen to the right:

mainScreen
    .transition()
    .duration(500)
    .attr('transform', 'translate(100, 0)');

and sliding both screens simply consists in moving both screens at the same time:

mainScreen
    .transition()
    .duration(500)
    .attr('transform', 'translate(-200, 0)');
mapScreen
    .transition()
    .duration(500)
    .attr('transform', 'translate(0, 0)');

You then bind each transition in the appropriate click handler and add some state in order to toggle the transition:

svg.select('#MenuToggle').on('click', function () {
    menuShown = !menuShown;
    if (menuShown) {
        // show menu
    }
    else {
        // hide menu
    }
});

You can see the complete code at at bl.ocks.org/maelp/8343848.

If you want to use transparent <rect> for the toggles, you have to set their pointer-events style to all, because by default transparent objects do not receive pointer events (you can use menuToggle.style('pointer-events', 'all');).

Tricks for complex prototypes

I hope you've enjoyed this tutorial as much as I have enjoyed sharing this with you!

If you've been following until here, I'm pretty sure you want to tweet this:

I did it! It feels like SVG was made just for this!

You can check the code of the complete SafeSignal prototype at bl.ocks.org/maelp/8343628.

In a follow-up post I will show you some tips I've found when creating more complex prototypes:

  • adding invisible objects to define transitions directly in the UI
  • create elements dynamically (think iOS table view)
  • using fonts when sharing your prototypes
  • adding a glow over the phone like in the SafeSignal prototype
  • sorting elements by position to get predictable animations
  • adding a reload button for people to watch your sexy animations again and again!
  • using finite state automatons to create more complex interactions
  • viewing your prototype on a real device and adding multitouch gestures

Here is a slight taste, showing how to create elements from a master element template and populate them with songs:

Basic interactive prototype created using a simple SVG file created by hand and made interactive using d3.js.

Reload.