SVG charts without JavaScript – Part 1

I recently needed to visualize some rather simple data in a chart. After playing around with a few ideas, I ended up generating the plain SVG code from the data, without using any frameworks or even JavaScript. In these posts I’d like to share some of the things I learned.

My initial thought was to just throw a fancy JavaScript library at it, let it do the work and call it a day. Because that’s what the cool kids do nowadays, right? But doing it this ways would have added yet another dependency to our project as well as making us dependant on JavaScript being loaded in the first place, just to see the chart. While I’m perfectly fine with using JavaScript to progressively enhance things, I believe we should strive to provide the basic functionality without it.

Also, it was a perfect opportunity for me to learn a bit more about SVG, which would have been useful, even if this didn’t turned out the way I wanted.

After reading this post by Rich Harris about how he built charts for the New York Times using plain SVG I started to think that maybe I could do it in a similar way.

Lets get started!

NOTE: To follow the code examples, you should have a basic understanding of how SVG works. I won’t go into much detail about the basics of SVG in general, as there are plenty of good tutorials out there already.

What I wanted to achieve

Here’s one of the early sketches of what I wanted the chart to look like in the end:

40

Pretty straight forward, right?

Apart from the <svg> element itself, which acts as our “artboard” if you will, the chart can be broken down into the following parts:

<polygon>
.chart__area

<polyline>
.chart__line

<circle>
.chart__circle

# #

<text>
.chart__text

  1. a path for the background fill area
  2. a path for the line
  3. a circle for each of the data points
  4. a text element in the top right, to display the number value of the last point

In our SVG code, we end up with something like this:

<svg class="chart" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <polygon class="chart__area" points="..." />
    <polyline class="chart__line" points="..." />
    <circle class="chart__circle" cx="..." cy="..." r="..." />
    ...
    <circle class="chart__circle" cx="..." cy="..." r="..." />
    <text class="chart__text" x="75" y="0">#</text>
</svg>

Now, to make those elements actually paint something, we need to feed them some data. (Later on we’ll generate this dynamically, but for the sake of readability let’s do it manually now.)

We use an SVG with a 100 x 100 viewBox to make calculations easier, and we use the following five data points: 50, 30, 40, 50, 60.

<svg class="chart" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <polygon class="chart__area" points="0,50 25,30 50,50 75,40 100,60 100,100 0,100 />
    <polyline class="chart__line" points="0,50 25,30 50,50 75,40 100,60" />
    <circle class="chart__circle" cx="0" cy="50" r="2" />
    <circle class="chart__circle" cx="25" cy="30" r="2" />
    <circle class="chart__circle" cx="50" cy="50" r="2" />
    <circle class="chart__circle" cx="75" cy="40" r="2" />
    <circle class="chart__circle" cx="100" cy="60" r="2" />
    <text class="chart__text" x="75" y="0">40</text>
</svg>

For the background fill path (.chart__area), we add some additional points at 100 100 and 0 100 to go around the bottom and close the path.

Next we set a stroke color and stroke-width to make the paths visible. This could be written right into the SVG, but I prefer to keep the styling to CSS.

.chart__area {
    fill: #fee9c0;
}

.chart__line {
    stroke: #efa216;
    stroke-width: 1px;
}

.chart__circle {
    stroke: #efa216;
    stroke-width: 1px;
    fill: #fafbff;
}

.chart__text {
    font-size: .7em;
    fill: #efa216;
}

This already comes pretty close to what I wanted to achieve.

But at this point, I realized that the chart started to look very crowded when adding more than just the few points from my initial sketch. While we likely won’t have thousands of them in this project, I wanted the design to be able to handle this scenario as well (it’s always a good idea to have the “Just in Case Mindset” when designing).

My first idea was to hide the circles, and only display them on hover, by adding the following CSS.

.chart__circle {
    stroke: #ccc;
    stroke-width: 1px;
    opacity: 0;
}

.chart__circle:hover {
    opacity: 1;
}
40
40

As you can see in those examples, hiding the circles and revealing them on hover (hover over the line in the second example above to see the effect in action) looks pretty good already and even gives it a touch of interactivity. But I missed two things:

  • the hover interaction doesn’t feel very smooth because the hoverable area is only exactly over the circle itself
  • to quickly see the number value of a point when hovering, I wanted to display the number on the top right of the chart, when a point is hovered

Some of you might say: This is the point were JavaScript comes into play! Sure, sure, it could. But maybe there’s a way we can achieve this with just native browser features.

First thing I found, is that there is a <title> element (not an attribute) in SVG, which will render the same way on hover as we are used to from a title attribute. To make this work with our circles, we’d need to group the <circle> in a <g> element and put a <title> element afterwards with the value inside.

<svg class="chart" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
    <polygon class="chart__area" points="0,50 25,30 50,50 75,40 100,60 100,100 0,100 />
    <polyline class="chart__line" points="0,50 25,30 50,50 75,40 100,60" />
    <g>
        <circle class="chart__circle" cx="0" cy="50" r="2" />
        <title>50</title>
    </g>
    <g>
        <circle class="chart__circle" cx="25" cy="30" r="2" />
        <title>70</title>
    </g>
    <g>
        <circle class="chart__circle" cx="50" cy="50" r="2" />
        <title>50</title>
    </g>
    <g>
        <circle class="chart__circle" cx="75" cy="40" r="2" />
        <title>60</title>
    </g>
    <g>
        <circle class="chart__circle" cx="100" cy="60" r="2" />
        <title>40</title>
    </g>
    <text class="chart__text" x="75" y="0">40</text>
</svg>

Et voilà: If you hover over the line and wait for a second, you should see the title in action. Not perfect, but it works and it gives us the number value on hover.

50 70 50 60 40 40

So far, we built a basic chart by using only plain SVG and a little bit of CSS. We even added a bit of interactivity with the hover effect.

At this point, I maybe became a little bit obsessed with this whole idea of building the whole chart including as much of the interactivity as possible without JavaScript.

Of course if we want to update or re-render data, we will still use JavaScript to enhance this later on. But for the first initial rendering and some basic interactivity? Maybe not as much as I thought.

Next step will be to find out how far we can take the interactivity, which I’ll cover in the next post.