Little Design Things

from Red Blob Games
21 Feb 2019

Here I have an unorganized collection of "little things" about my interactive diagram design. I am using this as a reference for myself, so that when I write a new page I can look through things I've done previously. I have previously listed a few of these in my interactive tutorial about how to make interactive tutorials[1].

 1  Visuals#

 1.1. Inline legend

Text on the page might describe elements in a diagram. For example, I might write "the blue lines represent graph edges". For describing elements, we aren't limited to using text. We can draw those elements directly, like we do in hand-drawn notes. For example, I could write, "the blue lines represent graph edges".

<SVG> is an inline element, like <img>. It can be placed in the text of a document. Here are examples where I connect the text and diagram by including elements of the diagram in the text.

Example of inline legendsAnother example of inline legends
Inline legends: describe elements of the diagram in the text

Use <svg width="3em" height="1em" viewBox="…"> to scale it to fit the text. The width and height are the size in the text and the view box is the coordinates of the diagram.

Related: sparklines[2] show data inline in the text. What I'm doing here is showing the legend inline in the text.

 1.2. Color in diagrams match text

I sometimes use colors in the text to match the color in the diagrams. Here's an example from the hexagon guide.

Colors represent concepts in the diagramThe colors represent the same concepts in the code
Semantic colorization: use the same colors for code as used in the diagram

Unfortunately because the text is x, y, z, the colors are not easy to see. I think the colors on the pathfinding pages worked better.

 1.3. Color in diagrams matches code, text

In this diagram on my breadth first search page, I use a red blob for a pathfinding node, blue for the set of nodes to be explored, green for the neighboring nodes, yellow for the current node, brown for visited nodes. In the code, instead of using standard colorizations for strings, keywords, etc., I use the same colors that I used in the diagram. I use the same colors in the text.

Colors represent concepts in the diagramThe colors represent the same concepts in the text
Semantic colorization: use the same colors for text as used in the diagram

I think I used too many colors. It would have been better to break this up into several steps, each focusing on one concept. In each step, I would use color only for the one concept I am trying to explain. Alternatively, this visualization[3] shows all the colors, and then hides all but the one concept the reader is focusing on.

Screenshot of diagram and sample code
Color in code matches color in diagram

I use both color and icons sometimes:

Diagram has iconsCode can have same icons
Code can use icons to match the diagrams

Here I colored the controls that correspond to the lines on the chart:

Color in sliders matches lines on chart
Color in sliders matches the lines in the diagram


 1.4. Negative space

Negative space[6] is the space around the thing we draw. We can use this to our advantage, creating shapes in the mind without actually drawing them. A common example of this for page design is avoid drawing borders. We can often get the same effect by using background colors and leaving some space:

Sidebar sidebar sidebar

I guess I'm a little weird. I like to talk to trees and animals. That's okay though; I have more fun than most people. Let's do that again. There we are. Let's get wild today.

You can do anything here - the only pre-requisite is that it makes you happy. The light is your friend. Preserve it. Maybe there was an old trapper that lived out here and maybe one day he went to check his beaver traps, and maybe he fell into the river and drowned. We don't have to be concerned about it. We just have to let it fall where it will. — Bob Ross text generator[7]

Sidebar with border vs negative space

The first style focuses on the border; the second style focuses on the content.

I use the same idea for diagrams. It's especially noticeable with grids:

Grid with border vs negative space

The first style emphasizes the edges (which is what I want on a page about edges); the second style emphasizes the tiles (which is what I want most of the time). It's not always practical though in some color schemes.

I occasionally use this with node-and-edge graphs:

Circles with border vs negative space

The first one emphasizes the edges and the the second one emphasizes the nodes. Which works better depends on which I want the reader to focus on.

 1.5. Yellow light and blue shadows

I had read somewhere that artists use yellowish tints for sunlight and bluish tints for shadows. I used this idea for my 2D visibility page:

Blue light with yellow shadowYellow light with blue shadow
Yellow light and blue shadows look better

I don't know a lot about this though, and haven't used this idea much. Also see: The Muller Formula[8], which says lighter colors should be hue shifted towards yellow and darker colors should be hue shifted towards violet.

 1.6. Custom controls

Web pages as documents have links that navigate to another page. Web pages as "apps" have that perform actions. My pages tend to have a few controls that don't quite match either of these.

I'm clearly not consistent across pages. I think I should spend more time looking at what controls other interactive pages use, and see if I can be more consistent with them.

 2  Interactivity#

 2.1. Interaction feedback

Highlighting options

These effects and others can be combined. Also the drag and hover effects can be different. For example, set the mouse pointer and fill color on hover, and then add a drop shadow and slight rotation on drag. Try dragging this shape:

Interactive example: drag this shape

 2.2. Drag point

The simplest way to deal with dragging is to set the object's position to the mouse position. This works fine for small shapes but it has a problem that's noticeable with larger shapes. Suppose we pick up the square from its corner and drag to the right. What should happen? If setting the square's position to the mouse position, it will "snap" down and to the right. Instead, we should remember where on the square we're dragging from, and postition the square relative to that.

Moving a shape to the mouse pointer: center vs offset

Try it yourself:

Interactive example: try dragging the square from its corner

What does the code look like?

  1. When starting a drag operation, remember the offset between the object position and the mouse position. offset = {dx: pos.x - event.x, dy: pos.y - event.y};
  2. When dragging, add the offset to the mouse position to get the object's position. pos.x = event.x + offset.dx; pos.y = event.y + offset.dy;

There are other equivalent ways to implement this, and which way is easiest will depend on the API and what else I'm doing with the mouse coordinates. When I'm using d3, d3-drag[12] supports this feature by setting the "subject" for the drag operation.

A different approach is each time mousemove is called, increment the object's position by the change in mouse position. In some browsers, mousemoveX, mousemoveY contain the change, and they continue to work even if the mouse is taken off screen (using Pointer Lock). I prefer using the absolute change since the drag started instead of relative position since the last mousemove. It allows constrained movement as I explore on this page, and as you can see in the demo above.

 2.3. Expanded hit area

The previous section was about dragging something large. We have a different problem when dragging something small. With some input devices, it's hard to be precise enough to point to a small object. Even if I am able to point to it, the act of clicking will move my pointer slightly for some types of input, and that causes me to miss. Even when I do grab it, the mouse pointer or my finger can hide the object.

Interactive example: try dragging the circles

On the left is the regular object drag approach. On the right:

  1. An invisible draggable area is underneath the circle, to make it easier to pick up the cricle.
  2. The cursor goes away while dragging so that I can see the object being dragged. (Implementation could be better.) (Alternatively, use the crosshaircursor[13].)

How do I implement this with SVG? I usually make the invisible area draggable, and it updates an underlying position object. I then draw a non-draggable object on top of it at that same position. The next problem is that the non-draggable object eats up the mouse events before the invisible area receives them. Solution:

.draggable { cursor: grab; pointer-events: all; }
.object { pointer-events: none; }

Now SVG will not pass pointer events to the visible object on top, and only pass them to the invisible object underneath. The pointer-events[14] CSS property is quite useful here!

When using Canvas or WebGL, I can't use invisible SVG objects so I have to reimplement all of this. Either go through all your objects and find something close enough to the mouse pointer, or use Voronoi to build to build a map of which object is closest the mouse pointer. The Voronoi approach is used in the D3 community[15], in part because D3 comes with a nice Voronoi library.

 2.4. Transitions

The main reason I would use a transition is because I want to show the states in between two states the reader is looking at. Most of the time, the in-between states are automatically visible as the reader moves a slider or other control between one state and another. If I can redraw the diagram quickly enough, I don't need to add transitions or animations in those situations. Transitions are useful when switching between states where the slider/control doesn't go through the intermediate states. An example is the two orientations of hexagonal grids, "flat topped" and "pointy topped". Compare without and with transitions:

Interactive example: without and with transitions

On the left, you can see the two orientations but it's hard to see that they're related. The transition on the right diagram shows the connection between the two grid types.

I think the default transitions in CSS and d3 are pretty good, but for the hexagon page I added a slight overshoot, like easeInOutBack[16] but subtle.

 2.5. Comparisons

It's been observed that it's harder to assign a 1-5 star rating to something than to say whether one thing is better or worse than another. I first ran across this in the 1990s and kept noticing it after that. For visualizations, I find it's often hard to evaluate something on its own, but it's easier to see when it's next to something else.

I use this on my A* page, for:

I also use this on my hexagon page, for:

It's often better to show all the variants than to ask the reader to interact with the diagram to see the variants. Interactivity isn't always needed. Related: Ladder of Abstraction[17]

 2.6. Touch events

I'd love to write everything for desktop browsers and not worry about mobile, but the reality is that “desktop” and “mobile” are not great categories. Instead, I have:

narrow, wide, small, large, portrait, landscape, even print — lots of variety! I use responsive design for this, including switching between two and one column layout on some pages.
mouse, trackball, trackpad, touch.

I first design pages for mouse/trackball input. This means: left-click, left-drag, right-click, right-drag, hover. However, given how many trackpads there are, these days I limit myself to left-click, left-drag, hover.

 2.7. SVG outside its container

By default, <svg> elements clip the content to their container. This can be changed! By default, SVG uses overflow: hidden[18]. Compare:

Interactive example: hidden vs visible overflow on SVG

Try dragging the green circle to see how it feels different with and without the walls.

I discovered this feature many years ago (probably 2011) when I was testing in Internet Explorer, which used to use overflow: visible as the default, whereas Firefox used overflow: hidden as the default. At first I was annoyed that Internet Explorer was different, but then I realized, maybe its behavior is useful! I have experimented a little bit over the years:

Note that some browsers won't catch input events outside the boundary.

 2.8. Detect bad input

When making static diagrams, we can pick good illustrative examples. With the interactive diagrams, sometimes the reader will end up in a bad or unhandled state. Ideally, I'll design the controls so that the bad states aren't reachable. But if I can't do that, I try to detect the bad states and alert the reader. For an example of this, see the 2d visibility page. Move the squares so they overlap. It'll highlight the conflict in red because the algorithm doesn't handle overlapping lines.

 2.9. Canvas + SVG overlaid

Sometimes I want to use both Canvas and SVG for the same diagram. SVG provides easy interactive elements; Canvas provides better performance and image handling. In these situations I will put an SVG over a Canvas:

<div style="position:relative">
  <canvas style="position:absolute;z-index:0" width="800" height="800"/>
  <svg style="position:relative;z-index:1" viewBox="0 0 800 800"/>

I've been using this technique less often these days, but I think CSS Grid would be a reasonable way to implement it too.

 2.10. Continuity

One problem I run into is that an algorithm that calculates f(x) is fine in production, but in an interactive setting, it needs to be continuous with respect to x. That way, as you manipulate x you can see how it affects the output f(x).

With pathfinding, this showed up when there are many ties for paths on the map. Each time I ran pathfinding, it might pick a different one. This is fine for a normal implementation, but for the interactive diagram, it was distracting to see the path "flicker" as you changed some unrelated parameter.

I also had this happen with map generation. Minor changes to some parameter wold cause the generated rivers to be in completely different places. I had to switch the interactive version to use a slower deterministic algorithm so that it wouldn't flicker.

 3  General design#

 3.1. Color & size

Use brighter (saturated) colors for important things, but in small areas only. If it's large then it makes everything seem unimportant.

 3.2. HSL colors

When picking colors, the default is to use RGB. It's well known and widely supported. Web browsers also support HSL colors. I find these much easier to work with. The standard colors are easy to switch to HSL:

RGB vs HSL standard colors

Setting saturation to half (S=50%) results in colors I like better:

RGB vs HSL half saturation colors

With RGB, we replaced 0→64, 255→191. With HSL we had to change S from 100% to 50%. HSL's advantage shows up when I want to go beyond these. For example, changing hue:

RGB vs HSL change hue

or saturation:

RGB vs HSL change saturation

or lightness:

RGB vs HSL change lightness

I find it much easier to work with HSL than RGB. I can pick sets of colors that match each other. HSL is supported for SVG, HTML, and Canvas vector drawing, but not WebGL or Canvas pixel manipulation.

This is a nice article about picking colors[23].

 3.3. Text shadow on header text

I used this trick on Circular Obstacle Pathfinding[24]: add a black text shadow around white text to increase its contrast a little bit. There's not a lot to do if your colors are black and white, but a dark gray or light gray offer more options.

Text shadow
Text shadow
Dark mode: without and with faint text shadow
Text shadow
Text shadow
Light mode: without and with faint text shadow

I've mostly used this for the headers, where the fonts are big enough for the text-shadow to work. I think it doesn't work as well for dark text on a light background.

Related: Lanczos sharpening filter[25].

 3.4. TODO one/two column

Some of my pages (hexagons, curved paths, visibility) switch from one column on narrower screens to two columns on wider screens

 4  Site#

 4.1. TODO backwards compatibility - keep things up, it takes time to build traffic

"Cool URIs don't change" –[26][27]

 4.2. TODO links to sections

On reference pages especially, I make each section linkable. It's an extra step though so sometimes I forget.

 4.3. TODO minimize build step so that there's no barrier to me fixing something

This means that I don't take advantage of modern tech as much as others do. I'm not using React or Svelte or Webpack for example. On the other hand, twenty years from now, I think I will be glad I can edit a page without having to make sure I have the right version of Gulp etc.

 4.4. TODO x pages vs regular pages

I ran into a problem where my expectations for my pages were too high. This made it really hard to start on something new. I would make grand plans but never get anywhere. I decided to make a separate folder (called x) where I can put experimental projects. This worked really well! I made a lot more things this way. If something works out, I can move it out of that folder and turn it into a full project.

 4.5. TODO org, markdown, bxml

It's easier to write prose in markdown (or org mode).

 4.6. TODO prerender for hex page

To improve load time, I prerendered all the diagrams so that when you load the page, you get 600k of svg instead of javascript that has to generate the svg. This (surprisingly) improves layout speed and perceived load time.

 4.7. TODO opengraph meta tags

Marketing - when the page is shared, Twitter, Facebook, Slack, etc. will read from these meta tags to attach an image or description to the link.

Email me , or tweet @redblobgames, or comment: