The first thing I thought about when reading Wilson Miner’s article on accessible data visualization was that it shouldn’t be too difficult to do charts with bars going different directions. Doing so would allow a convenient way to represent values that could be either positive or negative, typically as compared to some other data point that remains constant throughout the entire data set. Thus, my first choice for applying this technique would be golf scores, since each hole is scored relative to par. Even though the value of par varies with the difficulty for each hole, the score relative to par is the important bit that’s recorded and used to compare the abilities of different players. So I set out to improve the efficiency and attractiveness of their leaderbord.
The problem to solve is how to encode and also position the negative values. Wilson’s article does fine with positive values, but it’s not possible to apply a negative width to an element. Instead, I needed to position the element so that it’s right-aligned, so that it extends to the left, rather than to the right. This is a simple matter of setting
right: 0 to the element, using a separate selector identified by a different class. Unfortunately, the positive values start from the left and go right, while the negative values start from the right and go left, so they sort of meet in the middle somewhere. What we really want is to start in the middle and extend outward, not start from the outsides and extend inward.
The first solution I tried was to give the positive values
left: 50% and the negative values
right: 50%, so they’d each start in the middle and extend out. This did work, but caused two other problems. First, in order to mark the zero line, I had to use a background imaged, positioned at 50%. That wouldn’t have been a deal breaker, but I’m trying to avoid images, so that stung a little. (Plus, images make styling a bit more difficult.) Worse, though, was that now all values had to be scaled down 50% in order to fit the size of the new bars. That’s not exactly difficult, but it’s an extra step that would have to done every time these data points are used, which would get rather painful.
Thankfully, I considered a different approach, which solved both problems nicely. The container object (the one marked
position: relative) is given a width of 50% and a border on its right-hand side, marking the zero line (without using an image). Then, the negative values are given
right: 0 to meet up with that line and extend to the left of it, and the positive values are given a
left: 100% so they start at the line and continue to extend to the right. Since the outer container is set to width: 50%, it already scales any bars inside of it automatically, without the need for any prior data manipulation.
The only wart with this approach (and it was there for the prior approach as well) is that the positive values actually sit on top of the zero line, rather than starting to the right of it. This not only obscufres the zero line if the bar and the line aren’t the same color, it also causes a disconnect between values to the left and to the right: the same value in different directions will be a different number of pixels away from the line. Again, though, the solution is simple. Just add a
margin-left on your positive values equal to the width of the border you used for the zero line (typically
What seems to be the only real wart left comes from my desire to place the data in a table, so that final scores could be compared alongside scores for each round. It’s tabular data, and it belongs in a table, especially for accessibility in browsers that don’t display the bars. Unfortunately, the CSS spec rears its ugly head with the most dreaded sentence a developer can see.
The effect of ‘position:relative’ on table-row-group, table-header-group, table-footer-group, table-row, table-column-group, table-column, table-cell, and table-caption elements is undefined.
For those of you who don’t read specs, “undefined” typically means there are no rules, and that no particular behavior should be relied on. A conforming browser could support it just like any other element, simply ignore the declaration, cause it to collapse into oblivion or even sprout dancing unicorns in every color of the rainbow. Unfortunately, I didn’t find any dancing unicorns, but I did have some table cells implode a bit, causing neighboring cells to overlap and cause general mayhem. So, the wart is that I need an extra layer of markup to define the relative positioned container, rather than using the table cell itself. Thankfully, though, the
width: 50% trick I’m using to position values on both sides of the line means that I couldn’t have used the table cell anyway. It just sucks that there’s not really any semantic value to a couple layers of
One huge advantage of the
width: 50% approach—which I hadn’t even considered when I came up with it—is that the zero line can now be adjusted very easily by modifying the width, and all the existing values will magically adjust to fit. Since all their widths are relative to the width of the container, adjusting it to 70% or even 80% yields higher resolution in the negative range. Of course, the drawback here is that on the positive side, values also increase, and there’s the chance that they’d overrun their table cell and overlap with wahtever’s in the next cell. Therefore, it’s only recommended when the majority of data is negative and the few positive values will be quite small. Thankfully, I’m visualizing professional golf scores, not my own, so the data fits perfectly with this approach.
If you had a data set that went the other way, however, it’s still possible to use the same approach. First, right-align the container and change the border to be on the left, rather than on the right, so you still have a zero line somewhere in the middle. Then, remove the
left property for positive values (or set it to
left: 0 if you want to be absolutely sure) so they’ll start at the zero line, and change the negative values to use
right: -100% so they’ll also start at the zero line. You’ll still need the 1px margin, but this time, you’ll set it as a
margin-right: 1px on the negatives and remove it from the positives. Then the positive values will scale nicely with the width of the container, while negative values will extend just beyond it to the left.
Of course, if your data set is evenly matched between positive and negative values, either of the preceding approaches will work just fine, since a container width of 50% will scale both sides equally, no matter what.
The finished product
Currently, the data presented is for the 2009 Buick Open (since I’m from Michigan and that’s the only one that ever really seemed to matter around here), but I’d like to expand it to the entire PGA Tour and perhaps to other similar tours held elsewhere. The main challenge is providing live results, which involves both server-side support and some client-side work to ensure that gaps in the scoring table make sense.