DEV Community

Christian Selig
Christian Selig

Posted on

Controlling SVG Draw Order in D3

Sooner or later when working with D3 you'll find yourself in a situation where you need to change how elements overlap each other. Most people (including myself) will first try setting the CSS property z-index, but it turns out unfortunately SVG elements don't respect this property.

The order in which SVG elements are drawn determines the order in which they appear (an element drawn last will appear "over" any other elements it overlaps). The draw order of SVG elements follows a simple rule: elements that appear first are drawn first.

For example, take the following code snippet:

The resulting HTML looks something like:

<svg>
  <circle fill="green" r=50 cx=75 cy=100></circle>
  <rect fill="blue" width=80 height=80 x=85 y=60></rect>
</svg>
Enter fullscreen mode Exit fullscreen mode

The green circle appears before the blue square, so it is drawn first and appears below.

D3's raise function

If we wanted to make the blue square appear on top, we could just reverse the order that the elements are created in the code. This works fine for this simple example, but when the code gets more complicated (and especially if we're working with abstractions we've written) we want to control draw order without having to change the order of our code.

To this end, we can use D3's selection.raise() function:

Which results in the HTML:

<svg>
  <rect fill="blue" width=80 height=80 x=85 y=60></rect>
  <circle fill="green" r=50 cx=75 cy=100></circle>
</svg>
Enter fullscreen mode Exit fullscreen mode

The green circle now appears after the blue square in the HTML, which means it appears on top of the square in the rendered SVG.

We can further illustrate this by calling raise() on element mouseover (try hovering over the shapes below).

Ordering and SVG Groups

SVG groups are often used to organize elements in a chart. We can use groups as another way to control draw ordering. In the below example the green circle appears above the blue square despite the fact that is appears before the square in the code.

This is because we've added the circle to group2 and the square to group1, and group1 appears before group2. Take a look at the HTML:

<svg>
  <g class="group1">
    <rect fill="blue" width=80 height=80 x=85 y=60></rect>
  </g>
  <g class="group2">
    <circle fill="green" r=50 cx=75 cy=100></circle>
  </g>
</svg>
Enter fullscreen mode Exit fullscreen mode

Now however we'll have a problem if we try to bind raise() to element mouseover. Notice in the below example that hovering over the shapes does nothing:

This is because raise only changes the order of the element within its parent group. In this example each group only has one element, making raise a no-op.

To make this work we could bind raise to the parent group mouseover, but this might be a little weird because the group doesn't necessarily have the same shape as the child element. A clean way to do it would be to have a function that raises the parent group on element mouseover. All we need to change then is our mouseover callback from d3.select(this).raise() to d3.select(this.parentNode).raise().

Conclusion

Hopefully these examples help clear up how SVG draw order works! Keeping these simple rules in mind can help you exert finer control over the details when working with complicated visualizations.

Top comments (0)