SVG charts without JavaScript – Part 2

In the first post, we built a simple chart from scratch using nothing but raw SVG code. While this was a good starting point, the hover interaction was still far from perfect and needs improvement to be really usable.

As we first used a <title> element for the tooltips, the hover interaction felt unresponsive and slow. Also they couldn’t really be styled the way I wanted. So, let’s scrap the title elements and see if there’s a better way to go about this.

What’s the goal?

The solution that I had in mind, was to make the whole chart hoverable, from top to bottom. This way you could glance over the whole chart very quickly to check the values from particular data points.

For comparism, here’s the example from the first post again, on the left, compared to what we’re trying to accomplish, on the right:

(Hover/tap to see the difference in action)

50 70 50 60 40 40

Example from Part 1

50 70 50 60 40

Goal of this post

To get there, we basically need to achieve two things:

  1. Control/enlarge the hover area to spread from top to bottom of the chart
  2. Find a way to show/hide the appropriate information (circle and number on the top right), on hover.

Thought process

I had several ideas on how this could possibly be built. Most of them didn’t work for various reasons. For the sake of documentation, here’s my thought process. (If you just want the solution, you can skip this part)

First thought: Add some padding around the circles 👎
Maybe by adding some padding around the <circle> elements we could enlarge the hover area to make it more usable. But SVG doesn’t seem to specify anything like padding, and different browsers interpret it very differently (if at all). So this was out the window very quickly.

Second thought: Pseudo-elements to the rescue 👎
If we could add a :before pseudo-element on the <circle> and position it accordingly we should be able to exactly control the hover area. As I learned, SVG elements are seen by browsers as replaced elements, like images, which are not allowed to have generated content, like pseudo-elements. Therefore, no :before or :after on SVG elements. Next!

Third thought: Style the wrapping <g> group element 👎
Change the size and position of the <g> group element that wraps each of our <circle> elements. As with the padding, I learned that container, or structural elements in SVG (like the <g> group element is) are not meant to be drawn/styled directly. So again, no luck with this one.

Fourth thought: Add HTML inside the SVGs? 👎
Maybe it is possible to add HTML code inside of the SVG code, somehow? A few google searches and some stackoverflow answers later, I learned about the <foreignObject> element in SVG. A few first tests and a quick check of the browser support (pretty good, except IE) I felt confident enough to give it a try, but after a few tests I realized that the browsers’ interpretations are unpredictable at best, which is why I threw this out as well.

Fifth thought: Adding invisible <rect> elements as hover areas 🥳
After all of the above failed (and me nearly giving up), I had another look at what some of the charting libraries out there do, and some of them make use of additional <rect> rectangle elements to act as invisible hover areas. This, combined with a little bit of CSS-trickery to hide and show the appropriate meta information, was exactly what I needed, works across browsers and is therefore what I ended up using.

Hover areas

As hover elements we add an additional <rect> element for each data point. This allows us to exactly control where the chart is hoverable and to control the display of additional meta information (like the number value on the top right).

So, for each data point, we end up with something like this:

<rect class="chart__hover" x="..." y="0" width="..." height="100%" />
<g class="chart__data">
    <circle class="chart__circle" cx="..." cy="..." r="2" />
</g>

Here’s the example of the end-goal again, with those hoverable areas highlighted in red:

(Hover/tap to see it in action)

50 70 50 60 40

By default – when nothing is hovered – I wanted the chart to display the last circle and number value, which is done by the following two lines of CSS. We use a group <g> element with class .chart__data for all the additional information. The first line sets all of them invisible (opacity: 0), the second line sets the last of them back to be visible.

.chart__data {
    opacity: 0;
}

.chart__data:last-of-type() {
    opacity: 1;
}

Show/hide information on hover

Now that we built our default state, we need to take care of what happens on hover.

To control this, we make us of the general sibling selector ~ and the adjacent sibling selector + in CSS. If used properly, those two can work like magic.

An example will show much better what they do than words could. In our example a hover over <rect class="chart__hover"> should control the display of the following <g class="chart__data">. As those are direct – aka “adjacent” – siblings, we can use the adjacent sibling selector + like so:

.chart__hover:hover + .chart__data {
    opacity: 1;
}

While this works, it also still shows the initial (last) value and circle. Luckily, there’s also a general sibling selector ~ which selects every sibling element that comes after the element we selected. So, to make every .chart__data invisible that comes after the hovered .chart__hover, we can add:

.chart__hover:hover ~ .chart__data {
    opacity: 0;
}

The combination of those two statements do all the work to show the appropriate meta data on hover.

Because we’ve put everything inside a group it’s also very easy to add additional data on hover, if needed.

As you can see, this makes for a pretty neat effect and an intuitive way of interacting with the chart, all without any JS to this point.

To further enhance this, you would probably still want to use JavaScript. Things like updating data on the fly, changing the range of the data shown without reloading the whole page each time. Stuff like this would all be viable options to use JS and for more sophisticated charts there certainly is a time and place for full-fletched data visualization frameworks like d3.js, Highcharts.js and others. But if the only thing you want to do is painting a simple line-chart, maybe a solution like this could be faster and easier to maintain than loading the whole kitchen sink.

I hope this shows how far we can take a design by utilizing native, widely supported browser features. And all of this without the need to download and parse anything more than some HTML/SVG markup and a few CSS styles.

In the next post I will quickly glance over the function I used to dynamically generate this SVG code from a given array of data.