BookmarkSubscribeRSS Feed

Styling a D3 donut chart in SAS Visual Analytics with #D3Thursday

Started ‎10-04-2018 by
Modified ‎10-11-2018 by
Views 2,588

This #D3Thursday's article expands on our donut chart from last time to make it look more like a standard Visual Analytics (VA) object.

 Styled Donut ChartStyled Donut Chart

This post’s topics include:

  • Setting VA’s style options
  • Creating custom color gradients
  • Designing tooltips

 

VA Report Style OptionsVA Report Style OptionsVA Style Options

Before modifying our donut chart to look more like a native VA object, we can make a few modifications to VA’s style options to make our job easier. We are going to make three changes.

 

First, let’s change the font from the default, Avenir, to Verdana. Verdana is a standard sans-serif web font that will serve as a good replacement for Avenir and won’t require the hassle of licensing or importing.

 

Secondly, let’s change the default font color to black. Alternatively, you could change the text in your visualization to the appropriate VA defaults.

 

Finally, let’s set the theme to Aqua. You can choose whichever theme you would like; just be sure to note the fill and line/marker colors for creating our custom gradients below!

 

 

 

 

 

 

 

 

 

 

 

Color Gradients

Now that we’ve set VA’s style options appropriately, we can move on to creating custom color gradients for our donut chart.

 

Hard Coding Colors

Unfortunately, there isn’t currently a way to extract VA display rule information for use in our D3 visualizations. That means we have to hard-code our colors into our JavaScript code.

const FILL = {
Hybrid: "#21b9b7",
Sedan: "#4141e0",
Sports: "#7db71a",
SUV: "#8e2f8a",
Truck: "#d38506",
Wagon: "#0abf85"
};

const STROKE = {
Hybrid: "#1d9992",
Sedan: "#2222bc",
Sports: "#6a9617",
SUV: "#6d256d",
Truck: "#ba7006",
Wagon: "#0a9e69"
};

Note that you could also store this information in a separate JSON file and use an asynchronous request to load the color information into a variable. Doing so would mean instead of changing the HTML file for the visualization, you would have to move it into a separate directory with your JSON file.

 

Creating the Gradient

Now that we have our color information hardcoded we can move on to creating our gradients. VA’s default data-skin is a linear 30% white gradient. We can imitate a 30% white gradient by creating a gradient from an opacity of 1 to an opacity of .7 with a white background.

 

Note that this method would not work if the background wasn’t white. Instead, you would have to either change the fill colors of the color stops, or you would have to create a white gradient to lie on top of the solid fill color.

GRADIENTS = DEFS.selectAll(".gradient").data(DATA, function(d) {
return d.category;
});

GRADIENTS.enter()
.append("linearGradient")
.classed("gradient", true)
.attr("id", function(d) {
return d.category + "-gradient";
})
.attr("x1", "0%")
.attr("x2", "100%")
.attr("y1", "50%")
.attr("y2", "50%")
.each(function(d, i) {
// Append color stops
d3.select(this)
.append("stop")
.attr("class", "start")
.attr("offset", "0%")
.attr("stop-color", function() {
return FILL[d.category] ? FILL[d.category] : COLOR_SCALE(i);
})
.attr("stop-opacity", 1);

d3.select(this)
.append("stop")
.attr("class", "end")
.attr("offset", "100%")
.attr("stop-color", function() {
return FILL[d.category] ? FILL[d.category] : COLOR_SCALE(i);
})
.attr("stop-opacity", 0.7);
});

The result of the above code is a set of gradients based on our hard-coded fill colors. Notice that in the case that fill colors have not been specified, we will instead use a D3 color scale to create our gradients. This is not ideal, as we will have the same problem with changing colors that we saw in our last post, but it will work as a backup.

 

Tooltips

Finally, let’s create some tooltips so users can see what data is used to create each donut slice.

 

Import d3-tip

Luckily, most of the hard work here has already been done for us by the existing d3-tip library.

 <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.9.1/d3-tip.min.js"></script>

This user created addition to D3.js allows us to create and position custom tooltips easily.

 

Initialize Tooltips

Now that we’ve imported d3-tip, we can initialize our tooltips. For more information on d3-tip see the links at the bottom of this post.

// Append svg and save reference
d3.select("body")
...
.each(initializeTips);

...

function initializeTips() {
TIP = d3
.tip()
.attr("class", "d3-tip")
.offset([-8, 0])
.html(function(d) {
return (
"<table class='d3-tip-content'> <tr> <td> " +
METADATA.category +
":\t</td> <td>" +
d.category +
"</td> </tr>" +
"<tr> <td> " +
METADATA.measure +
":\t</td> <td>" +
d.measure +
"</td> </tr> </table>"
);
});

d3.select(this).call(TIP);
}

Since we only want our tooltips to be initialized one time, we can place the initialization code in a callback that is executed after the SVG is created.

 

Add Tooltip Tracker

Under normal use cases of d3-tip, you would show and hide tooltips using some code like this:

DATA_ARCS.enter()
.append("path")
...
.on("mouseover", TIP.show)
.on("mouseout", TIP.hide)
...

Unfortunately, the above method won’t work for our donut chart. Because d3-tip uses the element container to place the tooltip, the above method would result in tooltips that aren’t consistently placed on our pie slices.

 

To solve this issue, we need to add another element to bind our tooltips to.

G_CHART_AREA.selectAll("#tooltip-tracker")
.data([DATA])
.enter()
.append("circle")
.attr("id", "tooltip-tracker")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 1)
.style("opacity", 0);

...

DATA_ARCS.enter()
.append("path")
...
.on("mouseover", function(d) {
// Move tooltip tracker to midpoint of arc
d3.select("#tooltip-tracker")
.attr("cx", +RADIUS * Math.sin((d.startAngle + d.endAngle) / 2))
.attr("cy", -RADIUS * Math.cos((d.startAngle + d.endAngle) / 2));

// Show tooltip
TIP.show(d, d3.select("#tooltip-tracker").node());
})
.on("mouseout", TIP.hide)
...

Instead of binding our tooltips directly to the donut slices, we bind our tooltips to a circle with a radius of 1 and opacity of 0 that is located at the central angle of the donut slice.

 

The Result

The result of this work is a tooltip that looks and behaves very similarly to VA’s tooltips.Styled Donut with TooltipStyled Donut with Tooltip

 There is, however, one major exception. Unlike VA charts, we cannot create tooltips that stretch outside of our object container. To compensate for this issue, we can allocate additional space around our graph for our tooltips.

RADIUS =
Math.min(
WIDTH - 2 * TOOLTIP_SIDE_PAD,
HEIGHT - LEG_HEIGHT - LEG_TOP_PAD - TOOLTIP_TOP_PAD
) / 2;

Still, this solution is less than perfect as it will take away needed screen real estate from our chart. Stay tuned for a more elegant mobile friendly tooltip that we will create on in a future post.

Additional Resources

Next Post

Next post we will take a look at how we can use D3 with Leaflet, an open-source map library, to create an interactive map based visualizations.

 D3Thursday Logo.png

 

Remember to follow these articles on the SAS Communities Library and the #D3Thursday Twitter hashtag. Comment below or Tweet your questions and comments!

Version history
Last update:
‎10-11-2018 09:47 AM
Updated by:
Contributors

sas-innovate-2024.png

Available on demand!

Missed SAS Innovate Las Vegas? Watch all the action for free! View the keynotes, general sessions and 22 breakouts on demand.

 

Register now!

Free course: Data Literacy Essentials

Data Literacy is for all, even absolute beginners. Jump on board with this free e-learning  and boost your career prospects.

Get Started

Article Labels
Article Tags